@sveltier/ui 0.0.0-stage → 0.1.0-alpha.1

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 (331) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +82 -2
  3. package/dist/components/Accordion/Accordion.svelte +74 -0
  4. package/dist/components/Accordion/Accordion.svelte.d.ts +25 -0
  5. package/dist/components/Accordion/accordion.css +88 -0
  6. package/dist/components/Accordion/index.d.ts +2 -0
  7. package/dist/components/Accordion/index.js +1 -0
  8. package/dist/components/Accordion/types.d.ts +64 -0
  9. package/dist/components/Accordion/types.js +1 -0
  10. package/dist/components/Alert/Alert.svelte +88 -0
  11. package/dist/components/Alert/Alert.svelte.d.ts +4 -0
  12. package/dist/components/Alert/alert.css +78 -0
  13. package/dist/components/Alert/index.d.ts +2 -0
  14. package/dist/components/Alert/index.js +1 -0
  15. package/dist/components/Alert/types.d.ts +67 -0
  16. package/dist/components/Alert/types.js +1 -0
  17. package/dist/components/Avatar/Avatar.svelte +47 -0
  18. package/dist/components/Avatar/Avatar.svelte.d.ts +4 -0
  19. package/dist/components/Avatar/avatar.css +34 -0
  20. package/dist/components/Avatar/index.d.ts +2 -0
  21. package/dist/components/Avatar/index.js +1 -0
  22. package/dist/components/Avatar/types.d.ts +47 -0
  23. package/dist/components/Avatar/types.js +1 -0
  24. package/dist/components/Badge/Badge.svelte +53 -0
  25. package/dist/components/Badge/Badge.svelte.d.ts +4 -0
  26. package/dist/components/Badge/badge.css +57 -0
  27. package/dist/components/Badge/index.d.ts +2 -0
  28. package/dist/components/Badge/index.js +1 -0
  29. package/dist/components/Badge/types.d.ts +43 -0
  30. package/dist/components/Badge/types.js +1 -0
  31. package/dist/components/Button/Button.svelte +85 -0
  32. package/dist/components/Button/Button.svelte.d.ts +4 -0
  33. package/dist/components/Button/button.css +132 -0
  34. package/dist/components/Button/index.d.ts +2 -0
  35. package/dist/components/Button/index.js +1 -0
  36. package/dist/components/Button/types.d.ts +76 -0
  37. package/dist/components/Button/types.js +1 -0
  38. package/dist/components/ButtonGroup/ButtonGroup.svelte +61 -0
  39. package/dist/components/ButtonGroup/ButtonGroup.svelte.d.ts +4 -0
  40. package/dist/components/ButtonGroup/button-group.css +39 -0
  41. package/dist/components/ButtonGroup/index.d.ts +2 -0
  42. package/dist/components/ButtonGroup/index.js +1 -0
  43. package/dist/components/ButtonGroup/types.d.ts +48 -0
  44. package/dist/components/ButtonGroup/types.js +1 -0
  45. package/dist/components/Card/Card.svelte +53 -0
  46. package/dist/components/Card/Card.svelte.d.ts +4 -0
  47. package/dist/components/Card/card.css +62 -0
  48. package/dist/components/Card/index.d.ts +2 -0
  49. package/dist/components/Card/index.js +1 -0
  50. package/dist/components/Card/types.d.ts +56 -0
  51. package/dist/components/Card/types.js +1 -0
  52. package/dist/components/Checkbox/Checkbox.svelte +105 -0
  53. package/dist/components/Checkbox/Checkbox.svelte.d.ts +4 -0
  54. package/dist/components/Checkbox/checkbox.css +138 -0
  55. package/dist/components/Checkbox/index.d.ts +2 -0
  56. package/dist/components/Checkbox/index.js +1 -0
  57. package/dist/components/Checkbox/types.d.ts +76 -0
  58. package/dist/components/Checkbox/types.js +1 -0
  59. package/dist/components/Chip/Chip.svelte +109 -0
  60. package/dist/components/Chip/Chip.svelte.d.ts +4 -0
  61. package/dist/components/Chip/chip.css +101 -0
  62. package/dist/components/Chip/index.d.ts +2 -0
  63. package/dist/components/Chip/index.js +1 -0
  64. package/dist/components/Chip/types.d.ts +69 -0
  65. package/dist/components/Chip/types.js +1 -0
  66. package/dist/components/Choice/Choice.svelte +60 -0
  67. package/dist/components/Choice/Choice.svelte.d.ts +4 -0
  68. package/dist/components/Choice/choice.css +75 -0
  69. package/dist/components/Choice/types.d.ts +43 -0
  70. package/dist/components/Choice/types.js +1 -0
  71. package/dist/components/Dialog/Dialog.svelte +36 -0
  72. package/dist/components/Dialog/Dialog.svelte.d.ts +4 -0
  73. package/dist/components/Dialog/index.d.ts +2 -0
  74. package/dist/components/Dialog/index.js +1 -0
  75. package/dist/components/Dialog/types.d.ts +50 -0
  76. package/dist/components/Dialog/types.js +1 -0
  77. package/dist/components/Divider/Divider.svelte +19 -0
  78. package/dist/components/Divider/Divider.svelte.d.ts +4 -0
  79. package/dist/components/Divider/divider.css +23 -0
  80. package/dist/components/Divider/index.d.ts +2 -0
  81. package/dist/components/Divider/index.js +1 -0
  82. package/dist/components/Divider/types.d.ts +10 -0
  83. package/dist/components/Divider/types.js +1 -0
  84. package/dist/components/Field/Field.svelte +95 -0
  85. package/dist/components/Field/Field.svelte.d.ts +4 -0
  86. package/dist/components/Field/field.css +210 -0
  87. package/dist/components/Field/index.d.ts +3 -0
  88. package/dist/components/Field/index.js +2 -0
  89. package/dist/components/Field/types.d.ts +109 -0
  90. package/dist/components/Field/types.js +2 -0
  91. package/dist/components/Form/Form.svelte +93 -0
  92. package/dist/components/Form/Form.svelte.d.ts +8 -0
  93. package/dist/components/Form/form.css +16 -0
  94. package/dist/components/Form/index.d.ts +2 -0
  95. package/dist/components/Form/index.js +1 -0
  96. package/dist/components/Form/types.d.ts +38 -0
  97. package/dist/components/Form/types.js +1 -0
  98. package/dist/components/Icon/Icon.svelte +38 -0
  99. package/dist/components/Icon/Icon.svelte.d.ts +4 -0
  100. package/dist/components/Icon/icon.css +33 -0
  101. package/dist/components/Icon/index.d.ts +2 -0
  102. package/dist/components/Icon/index.js +1 -0
  103. package/dist/components/Icon/types.d.ts +34 -0
  104. package/dist/components/Icon/types.js +1 -0
  105. package/dist/components/List/List.svelte +39 -0
  106. package/dist/components/List/List.svelte.d.ts +4 -0
  107. package/dist/components/List/ListItem.svelte +57 -0
  108. package/dist/components/List/ListItem.svelte.d.ts +4 -0
  109. package/dist/components/List/index.d.ts +3 -0
  110. package/dist/components/List/index.js +2 -0
  111. package/dist/components/List/list.css +127 -0
  112. package/dist/components/List/types.d.ts +67 -0
  113. package/dist/components/List/types.js +1 -0
  114. package/dist/components/Menu/Menu.svelte +195 -0
  115. package/dist/components/Menu/Menu.svelte.d.ts +4 -0
  116. package/dist/components/Menu/index.d.ts +2 -0
  117. package/dist/components/Menu/index.js +1 -0
  118. package/dist/components/Menu/menu.css +103 -0
  119. package/dist/components/Menu/types.d.ts +90 -0
  120. package/dist/components/Menu/types.js +1 -0
  121. package/dist/components/Modal/Modal.svelte +146 -0
  122. package/dist/components/Modal/Modal.svelte.d.ts +4 -0
  123. package/dist/components/Modal/modal.css +150 -0
  124. package/dist/components/Modal/types.d.ts +54 -0
  125. package/dist/components/Modal/types.js +1 -0
  126. package/dist/components/NativeSelect/NativeSelect.svelte +110 -0
  127. package/dist/components/NativeSelect/NativeSelect.svelte.d.ts +25 -0
  128. package/dist/components/NativeSelect/index.d.ts +2 -0
  129. package/dist/components/NativeSelect/index.js +1 -0
  130. package/dist/components/NativeSelect/native-select.css +45 -0
  131. package/dist/components/NativeSelect/types.d.ts +58 -0
  132. package/dist/components/NativeSelect/types.js +1 -0
  133. package/dist/components/NumberField/NumberField.svelte +246 -0
  134. package/dist/components/NumberField/NumberField.svelte.d.ts +4 -0
  135. package/dist/components/NumberField/index.d.ts +2 -0
  136. package/dist/components/NumberField/index.js +1 -0
  137. package/dist/components/NumberField/number-field.css +74 -0
  138. package/dist/components/NumberField/types.d.ts +73 -0
  139. package/dist/components/NumberField/types.js +1 -0
  140. package/dist/components/Popover/Popover.svelte +115 -0
  141. package/dist/components/Popover/Popover.svelte.d.ts +4 -0
  142. package/dist/components/Popover/index.d.ts +2 -0
  143. package/dist/components/Popover/index.js +1 -0
  144. package/dist/components/Popover/popover.css +67 -0
  145. package/dist/components/Popover/types.d.ts +83 -0
  146. package/dist/components/Popover/types.js +1 -0
  147. package/dist/components/Progress/ProgressCircular.svelte +61 -0
  148. package/dist/components/Progress/ProgressCircular.svelte.d.ts +4 -0
  149. package/dist/components/Progress/ProgressLinear.svelte +44 -0
  150. package/dist/components/Progress/ProgressLinear.svelte.d.ts +4 -0
  151. package/dist/components/Progress/index.d.ts +3 -0
  152. package/dist/components/Progress/index.js +2 -0
  153. package/dist/components/Progress/progress-circular.css +61 -0
  154. package/dist/components/Progress/progress-linear.css +67 -0
  155. package/dist/components/Progress/types.d.ts +53 -0
  156. package/dist/components/Progress/types.js +1 -0
  157. package/dist/components/RadioGroup/RadioGroup.svelte +149 -0
  158. package/dist/components/RadioGroup/RadioGroup.svelte.d.ts +25 -0
  159. package/dist/components/RadioGroup/index.d.ts +2 -0
  160. package/dist/components/RadioGroup/index.js +1 -0
  161. package/dist/components/RadioGroup/radio-group.css +139 -0
  162. package/dist/components/RadioGroup/types.d.ts +93 -0
  163. package/dist/components/RadioGroup/types.js +1 -0
  164. package/dist/components/SegmentedControl/SegmentedControl.svelte +70 -0
  165. package/dist/components/SegmentedControl/SegmentedControl.svelte.d.ts +25 -0
  166. package/dist/components/SegmentedControl/index.d.ts +2 -0
  167. package/dist/components/SegmentedControl/index.js +1 -0
  168. package/dist/components/SegmentedControl/segmented-control.css +68 -0
  169. package/dist/components/SegmentedControl/types.d.ts +60 -0
  170. package/dist/components/SegmentedControl/types.js +1 -0
  171. package/dist/components/Sheet/Sheet.svelte +56 -0
  172. package/dist/components/Sheet/Sheet.svelte.d.ts +4 -0
  173. package/dist/components/Sheet/index.d.ts +2 -0
  174. package/dist/components/Sheet/index.js +2 -0
  175. package/dist/components/Sheet/sheet.css +61 -0
  176. package/dist/components/Sheet/types.d.ts +51 -0
  177. package/dist/components/Sheet/types.js +1 -0
  178. package/dist/components/Skeleton/Skeleton.svelte +33 -0
  179. package/dist/components/Skeleton/Skeleton.svelte.d.ts +4 -0
  180. package/dist/components/Skeleton/index.d.ts +2 -0
  181. package/dist/components/Skeleton/index.js +1 -0
  182. package/dist/components/Skeleton/skeleton.css +70 -0
  183. package/dist/components/Skeleton/types.d.ts +25 -0
  184. package/dist/components/Skeleton/types.js +1 -0
  185. package/dist/components/Slider/Slider.svelte +153 -0
  186. package/dist/components/Slider/Slider.svelte.d.ts +4 -0
  187. package/dist/components/Slider/index.d.ts +2 -0
  188. package/dist/components/Slider/index.js +1 -0
  189. package/dist/components/Slider/slider.css +206 -0
  190. package/dist/components/Slider/types.d.ts +102 -0
  191. package/dist/components/Slider/types.js +1 -0
  192. package/dist/components/Switch/Switch.svelte +94 -0
  193. package/dist/components/Switch/Switch.svelte.d.ts +4 -0
  194. package/dist/components/Switch/index.d.ts +2 -0
  195. package/dist/components/Switch/index.js +1 -0
  196. package/dist/components/Switch/switch.css +118 -0
  197. package/dist/components/Switch/types.d.ts +67 -0
  198. package/dist/components/Switch/types.js +1 -0
  199. package/dist/components/Tabs/Tabs.svelte +93 -0
  200. package/dist/components/Tabs/Tabs.svelte.d.ts +25 -0
  201. package/dist/components/Tabs/index.d.ts +2 -0
  202. package/dist/components/Tabs/index.js +1 -0
  203. package/dist/components/Tabs/tabs.css +73 -0
  204. package/dist/components/Tabs/types.d.ts +59 -0
  205. package/dist/components/Tabs/types.js +1 -0
  206. package/dist/components/TextField/TextField.svelte +115 -0
  207. package/dist/components/TextField/TextField.svelte.d.ts +4 -0
  208. package/dist/components/TextField/index.d.ts +2 -0
  209. package/dist/components/TextField/index.js +1 -0
  210. package/dist/components/TextField/types.d.ts +57 -0
  211. package/dist/components/TextField/types.js +1 -0
  212. package/dist/components/Textarea/Textarea.svelte +116 -0
  213. package/dist/components/Textarea/Textarea.svelte.d.ts +4 -0
  214. package/dist/components/Textarea/index.d.ts +2 -0
  215. package/dist/components/Textarea/index.js +1 -0
  216. package/dist/components/Textarea/textarea.css +35 -0
  217. package/dist/components/Textarea/types.d.ts +60 -0
  218. package/dist/components/Textarea/types.js +1 -0
  219. package/dist/components/Tooltip/Tooltip.svelte +161 -0
  220. package/dist/components/Tooltip/Tooltip.svelte.d.ts +4 -0
  221. package/dist/components/Tooltip/index.d.ts +2 -0
  222. package/dist/components/Tooltip/index.js +1 -0
  223. package/dist/components/Tooltip/tooltip.css +44 -0
  224. package/dist/components/Tooltip/types.d.ts +55 -0
  225. package/dist/components/Tooltip/types.js +1 -0
  226. package/dist/core/defaults/DefaultsProvider.svelte +30 -0
  227. package/dist/core/defaults/DefaultsProvider.svelte.d.ts +13 -0
  228. package/dist/core/defaults/context.d.ts +45 -0
  229. package/dist/core/defaults/context.js +38 -0
  230. package/dist/core/display/display.d.ts +39 -0
  231. package/dist/core/display/display.js +53 -0
  232. package/dist/core/errors/codes.d.ts +124 -0
  233. package/dist/core/errors/codes.js +108 -0
  234. package/dist/core/errors/errors.d.ts +8 -0
  235. package/dist/core/errors/errors.js +7 -0
  236. package/dist/core/form/constraints.d.ts +15 -0
  237. package/dist/core/form/constraints.js +42 -0
  238. package/dist/core/form/context.d.ts +19 -0
  239. package/dist/core/form/context.js +4 -0
  240. package/dist/core/form/field.svelte.d.ts +34 -0
  241. package/dist/core/form/field.svelte.js +66 -0
  242. package/dist/core/form/number.d.ts +24 -0
  243. package/dist/core/form/number.js +71 -0
  244. package/dist/core/form/types.d.ts +22 -0
  245. package/dist/core/form/types.js +1 -0
  246. package/dist/core/locale/locale.d.ts +56 -0
  247. package/dist/core/locale/locale.js +82 -0
  248. package/dist/core/overlay/types.d.ts +33 -0
  249. package/dist/core/overlay/types.js +1 -0
  250. package/dist/core/props/classes.d.ts +21 -0
  251. package/dist/core/props/classes.js +44 -0
  252. package/dist/core/props/element.d.ts +18 -0
  253. package/dist/core/props/element.js +1 -0
  254. package/dist/core/props/types.d.ts +47 -0
  255. package/dist/core/props/types.js +16 -0
  256. package/dist/core/provider/UIProvider.svelte +63 -0
  257. package/dist/core/provider/UIProvider.svelte.d.ts +29 -0
  258. package/dist/core/theme/ThemeProvider.svelte +178 -0
  259. package/dist/core/theme/ThemeProvider.svelte.d.ts +25 -0
  260. package/dist/core/theme/color.d.ts +23 -0
  261. package/dist/core/theme/color.js +54 -0
  262. package/dist/core/theme/context.d.ts +25 -0
  263. package/dist/core/theme/context.js +14 -0
  264. package/dist/core/theme/css.d.ts +25 -0
  265. package/dist/core/theme/css.js +73 -0
  266. package/dist/core/theme/define.d.ts +11 -0
  267. package/dist/core/theme/define.js +21 -0
  268. package/dist/core/theme/resolve.d.ts +6 -0
  269. package/dist/core/theme/resolve.js +18 -0
  270. package/dist/core/theme/theme-provider.css +13 -0
  271. package/dist/core/theme/types.d.ts +35 -0
  272. package/dist/core/theme/types.js +61 -0
  273. package/dist/core/toast/ToastOutlet.svelte +192 -0
  274. package/dist/core/toast/ToastOutlet.svelte.d.ts +11 -0
  275. package/dist/core/toast/ToastProvider.svelte +39 -0
  276. package/dist/core/toast/ToastProvider.svelte.d.ts +19 -0
  277. package/dist/core/toast/toast.css +134 -0
  278. package/dist/core/toast/toast.svelte.d.ts +117 -0
  279. package/dist/core/toast/toast.svelte.js +133 -0
  280. package/dist/dev/off.d.ts +5 -0
  281. package/dist/dev/off.js +4 -0
  282. package/dist/dev/on.d.ts +20 -0
  283. package/dist/dev/on.js +49 -0
  284. package/dist/index.d.ts +67 -0
  285. package/dist/index.js +83 -0
  286. package/dist/primitives/anchored/anchored.d.ts +39 -0
  287. package/dist/primitives/anchored/anchored.js +104 -0
  288. package/dist/primitives/anchored/fallback.d.ts +31 -0
  289. package/dist/primitives/anchored/fallback.js +73 -0
  290. package/dist/primitives/focus/focus-trap.d.ts +25 -0
  291. package/dist/primitives/focus/focus-trap.js +86 -0
  292. package/dist/primitives/focus/roving-focus.d.ts +22 -0
  293. package/dist/primitives/focus/roving-focus.js +86 -0
  294. package/dist/primitives/focus/tabbable.d.ts +9 -0
  295. package/dist/primitives/focus/tabbable.js +75 -0
  296. package/dist/primitives/inert/inert-others.d.ts +12 -0
  297. package/dist/primitives/inert/inert-others.js +44 -0
  298. package/dist/primitives/keyboard/keymap.d.ts +17 -0
  299. package/dist/primitives/keyboard/keymap.js +39 -0
  300. package/dist/primitives/keyboard/typeahead.d.ts +18 -0
  301. package/dist/primitives/keyboard/typeahead.js +50 -0
  302. package/dist/primitives/layer/dismissable.d.ts +32 -0
  303. package/dist/primitives/layer/dismissable.js +91 -0
  304. package/dist/primitives/motion/easing.d.ts +9 -0
  305. package/dist/primitives/motion/easing.js +46 -0
  306. package/dist/primitives/motion/motion.d.ts +59 -0
  307. package/dist/primitives/motion/motion.js +80 -0
  308. package/dist/primitives/portal/Portal.svelte +37 -0
  309. package/dist/primitives/portal/Portal.svelte.d.ts +9 -0
  310. package/dist/primitives/portal/PortalContent.svelte +15 -0
  311. package/dist/primitives/portal/PortalContent.svelte.d.ts +9 -0
  312. package/dist/primitives/portal/portal.css +8 -0
  313. package/dist/primitives/scroll-lock/scroll-lock.d.ts +10 -0
  314. package/dist/primitives/scroll-lock/scroll-lock.js +33 -0
  315. package/dist/primitives/virtual/virtual-list.svelte.d.ts +65 -0
  316. package/dist/primitives/virtual/virtual-list.svelte.js +291 -0
  317. package/dist/styles/base.css +44 -0
  318. package/dist/styles/props.css +140 -0
  319. package/dist/styles/tokens.css +84 -0
  320. package/dist/styles/visually-hidden.css +16 -0
  321. package/dist/themes/aurora.d.ts +13 -0
  322. package/dist/themes/aurora.js +103 -0
  323. package/dist/themes/heritage.d.ts +13 -0
  324. package/dist/themes/heritage.js +102 -0
  325. package/dist/themes/maison.d.ts +13 -0
  326. package/dist/themes/maison.js +99 -0
  327. package/dist/themes/milano.d.ts +13 -0
  328. package/dist/themes/milano.js +101 -0
  329. package/dist/themes/monolith.d.ts +13 -0
  330. package/dist/themes/monolith.js +101 -0
  331. package/package.json +87 -5
@@ -0,0 +1,57 @@
1
+ <script lang="ts">
2
+ // A row of a List: text, a link or a button. See ListItemProps for the props.
3
+ import './list.css';
4
+ import type { ListItemProps } from './types.js';
5
+
6
+ let {
7
+ title,
8
+ subtitle,
9
+ href,
10
+ target,
11
+ rel,
12
+ onclick,
13
+ active = false,
14
+ disabled = false,
15
+ prepend,
16
+ append,
17
+ children,
18
+ class: className,
19
+ ref = $bindable(null),
20
+ ...rest
21
+ }: ListItemProps = $props();
22
+
23
+ const tag = $derived(href != null ? 'a' : onclick ? 'button' : 'div');
24
+ </script>
25
+
26
+ <li
27
+ bind:this={ref}
28
+ {...rest}
29
+ class={[
30
+ 'sv-list-item',
31
+ active && 'sv-list-item--active',
32
+ disabled && 'sv-list-item--disabled',
33
+ className
34
+ ]}
35
+ >
36
+ <svelte:element
37
+ this={tag}
38
+ class="sv-list-item__main"
39
+ href={tag === 'a' && !disabled ? href : undefined}
40
+ target={tag === 'a' ? target : undefined}
41
+ rel={tag === 'a' ? rel : undefined}
42
+ type={tag === 'button' ? 'button' : undefined}
43
+ role={tag === 'a' && disabled ? 'link' : undefined}
44
+ disabled={(tag === 'button' && disabled) || undefined}
45
+ aria-disabled={(tag === 'a' && disabled) || undefined}
46
+ aria-current={tag === 'a' && active ? 'page' : undefined}
47
+ onclick={tag === 'button' ? onclick : undefined}
48
+ >
49
+ {#if prepend}<span class="sv-list-item__prepend">{@render prepend()}</span>{/if}
50
+ <span class="sv-list-item__text">
51
+ {#if title}<span class="sv-list-item__title">{title}</span>{/if}
52
+ {#if subtitle}<span class="sv-list-item__subtitle">{subtitle}</span>{/if}
53
+ {@render children?.()}
54
+ </span>
55
+ </svelte:element>
56
+ {#if append}<span class="sv-list-item__append">{@render append()}</span>{/if}
57
+ </li>
@@ -0,0 +1,4 @@
1
+ import type { ListItemProps } from './types.js';
2
+ declare const ListItem: import("svelte").Component<ListItemProps, {}, "ref">;
3
+ type ListItem = ReturnType<typeof ListItem>;
4
+ export default ListItem;
@@ -0,0 +1,3 @@
1
+ export { default as List } from './List.svelte';
2
+ export { default as ListItem } from './ListItem.svelte';
3
+ export type { ListDefaults, ListItemProps, ListProps } from './types.js';
@@ -0,0 +1,2 @@
1
+ export { default as List } from './List.svelte';
2
+ export { default as ListItem } from './ListItem.svelte';
@@ -0,0 +1,127 @@
1
+ @layer sv.tokens, sv.base, sv.components, sv.utilities;
2
+
3
+ @layer sv.components {
4
+ .sv-list {
5
+ display: grid;
6
+ margin: 0;
7
+ padding: calc(var(--sv-space-5) * var(--sv-density)) 0;
8
+ list-style: none;
9
+ }
10
+
11
+ .sv-list-item {
12
+ display: flex;
13
+ align-items: center;
14
+ min-inline-size: 0;
15
+ }
16
+
17
+ /* The row: a control tall, its text a φ step from what is before it. */
18
+ .sv-list-item__main {
19
+ position: relative;
20
+ isolation: isolate;
21
+ display: flex;
22
+ flex: 1;
23
+ gap: var(--sv-space-13);
24
+ align-items: center;
25
+ box-sizing: border-box;
26
+ min-inline-size: 0;
27
+ min-block-size: calc(var(--sv-control-md) * var(--sv-density));
28
+ margin: 0;
29
+ padding: calc(var(--sv-space-5) * var(--sv-density))
30
+ calc(var(--sv-space-13) * var(--sv-density));
31
+ border: 0;
32
+ background: transparent;
33
+ color: inherit;
34
+ font: inherit;
35
+ font-size: var(--sv-text-sm);
36
+ line-height: var(--sv-leading-snug);
37
+ text-align: start;
38
+ text-decoration: none;
39
+ }
40
+
41
+ /* Rows that act share Button's state layer. */
42
+ :is(a, button).sv-list-item__main {
43
+ cursor: pointer;
44
+ -webkit-tap-highlight-color: transparent;
45
+ }
46
+
47
+ :is(a, button).sv-list-item__main::before {
48
+ content: '';
49
+ position: absolute;
50
+ inset: 0;
51
+ z-index: -1;
52
+ border-radius: inherit;
53
+ background-color: currentColor;
54
+ opacity: 0;
55
+ pointer-events: none;
56
+ transition: opacity var(--sv-duration-fast) var(--sv-ease);
57
+ }
58
+
59
+ @media (hover: hover) {
60
+ .sv-list-item:not(.sv-list-item--disabled) :is(a, button).sv-list-item__main:hover::before {
61
+ opacity: var(--sv-state-hover);
62
+ }
63
+ }
64
+
65
+ .sv-list-item:not(.sv-list-item--disabled) :is(a, button).sv-list-item__main:active::before {
66
+ opacity: var(--sv-state-press);
67
+ }
68
+
69
+ /* Navigation: rounded rows, set in from the edges. */
70
+ .sv-list--nav {
71
+ padding-inline: var(--sv-space-5);
72
+ }
73
+
74
+ .sv-list--nav .sv-list-item__main {
75
+ border-radius: var(--sv-radius-md);
76
+ }
77
+
78
+ .sv-list-item--active .sv-list-item__main {
79
+ background-color: color-mix(in oklch, var(--sv-color) var(--sv-tonal-wash), transparent);
80
+ color: color-mix(in oklch, var(--sv-color) var(--sv-tonal-text-mix), var(--sv-color-text));
81
+ font-weight: var(--sv-weight-medium);
82
+ }
83
+
84
+ .sv-list-item--disabled {
85
+ opacity: 0.382;
86
+ }
87
+
88
+ .sv-list-item--disabled .sv-list-item__main {
89
+ cursor: not-allowed;
90
+ }
91
+
92
+ .sv-list-item__prepend,
93
+ .sv-list-item__append {
94
+ display: flex;
95
+ flex: none;
96
+ align-items: center;
97
+ }
98
+
99
+ .sv-list-item__append {
100
+ padding-inline-end: calc(var(--sv-space-13) * var(--sv-density));
101
+ font-size: var(--sv-text-sm);
102
+ }
103
+
104
+ :where(.sv-list-item__prepend, .sv-list-item__append) svg {
105
+ inline-size: 1.272em;
106
+ block-size: 1.272em;
107
+ }
108
+
109
+ .sv-list-item__text {
110
+ display: grid;
111
+ flex: 1;
112
+ gap: var(--sv-space-2);
113
+ min-inline-size: 0;
114
+ }
115
+
116
+ .sv-list-item__title {
117
+ overflow: hidden;
118
+ text-overflow: ellipsis;
119
+ white-space: nowrap;
120
+ }
121
+
122
+ .sv-list-item__subtitle {
123
+ color: var(--sv-color-text-subtle);
124
+ font-size: var(--sv-text-xs);
125
+ font-weight: var(--sv-weight-regular);
126
+ }
127
+ }
@@ -0,0 +1,67 @@
1
+ import type { Snippet } from 'svelte';
2
+ import type { HTMLAnchorAttributes, HTMLAttributes, HTMLLiAttributes } from 'svelte/elements';
3
+ import type { ElementProps } from '../../core/props/element.js';
4
+ import type { Color, Density } from '../../core/props/types.js';
5
+ /** The List props that `DefaultsProvider` and `UIProvider` can set for a subtree. */
6
+ export interface ListDefaults {
7
+ /**
8
+ * Multiplies the height and padding of its items.
9
+ * @default 'default'
10
+ */
11
+ density: Density;
12
+ /**
13
+ * The colour of the active item.
14
+ * @default 'primary'
15
+ */
16
+ color: Color;
17
+ /**
18
+ * Rounded rows set in from the edges, for navigation.
19
+ * @default false
20
+ */
21
+ nav: boolean;
22
+ }
23
+ declare module '../../core/defaults/context.js' {
24
+ interface ComponentDefaults {
25
+ List: ListDefaults;
26
+ }
27
+ }
28
+ /** A list of rows: `ListItem`s. Density and colour are set here, for every item. */
29
+ export type ListProps = ElementProps<HTMLUListElement, HTMLAttributes<HTMLUListElement>, Partial<ListDefaults> & {
30
+ /** The `ListItem`s. */
31
+ children?: Snippet;
32
+ }>;
33
+ /**
34
+ * A row of a `List`. With `href` it is a link and with `onclick` a button, filling the row;
35
+ * `append` stays outside it, so it can hold controls of its own.
36
+ */
37
+ export type ListItemProps = ElementProps<HTMLLIElement, HTMLLiAttributes, {
38
+ /** The main line. */
39
+ title?: string;
40
+ /** A second line, under the title. */
41
+ subtitle?: string;
42
+ /** Makes the row a link. */
43
+ href?: string;
44
+ /** Where a link opens, as on `<a>`. */
45
+ target?: HTMLAnchorAttributes['target'];
46
+ /** The relationship of a link, as on `<a>`. */
47
+ rel?: string;
48
+ /** Makes the row a button that calls this. */
49
+ onclick?: (event: MouseEvent) => void;
50
+ /**
51
+ * The current page or the selected row: tinted in the list's colour, and
52
+ * `aria-current="page"` on a link.
53
+ * @default false
54
+ */
55
+ active?: boolean;
56
+ /**
57
+ * A disabled row cannot be pressed; a disabled link loses its `href`.
58
+ * @default false
59
+ */
60
+ disabled?: boolean;
61
+ /** Content before the text, such as an avatar or an icon. */
62
+ prepend?: Snippet;
63
+ /** Content after the row's link or button, such as a value or a menu button. */
64
+ append?: Snippet;
65
+ /** More content under the title and subtitle. */
66
+ children?: Snippet;
67
+ }>;
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,195 @@
1
+ <script lang="ts">
2
+ // A list of actions that opens from a control. See MenuProps.
3
+ import { createAttachmentKey, type Attachment } from 'svelte/attachments';
4
+ import { checkValue, DEV } from '#dev';
5
+ import { useDefaults } from '../../core/defaults/context.js';
6
+ import type { CloseReason, TriggerAttributes } from '../../core/overlay/types.js';
7
+ import { sharedClasses } from '../../core/props/classes.js';
8
+ import { ALIGNS, anchored, SIDES } from '../../primitives/anchored/anchored.js';
9
+ import { rovingFocus } from '../../primitives/focus/roving-focus.js';
10
+ import { typeahead } from '../../primitives/keyboard/typeahead.js';
11
+ import { dismissable } from '../../primitives/layer/dismissable.js';
12
+ import { pop } from '../../primitives/motion/motion.js';
13
+ import './menu.css';
14
+ import type { MenuAction, MenuDefaults, MenuProps } from './types.js';
15
+
16
+ let {
17
+ density,
18
+ items,
19
+ onselect,
20
+ trigger,
21
+ open = $bindable(false),
22
+ side = 'bottom',
23
+ align = 'start',
24
+ onclose,
25
+ id: idProp,
26
+ class: className,
27
+ ref = $bindable(null),
28
+ ...rest
29
+ }: MenuProps = $props();
30
+
31
+ const resolve = useDefaults<MenuDefaults>('Menu', { density: 'default' });
32
+ const p = $derived(resolve({ density }));
33
+
34
+ if (DEV) {
35
+ $effect(() => checkValue('Menu', 'side', side, SIDES));
36
+ $effect(() => checkValue('Menu', 'align', align, ALIGNS));
37
+ }
38
+
39
+ const uid = $props.id();
40
+ const id = $derived(idProp ?? uid);
41
+
42
+ // Runs of actions. A heading starts a named group, which a separator or the next heading ends.
43
+ interface Group {
44
+ heading?: string;
45
+ actions: MenuAction[];
46
+ }
47
+ const blocks = $derived.by(() => {
48
+ const list: Array<Group | 'separator'> = [];
49
+ let group: Group | undefined;
50
+ for (const item of items) {
51
+ if ('separator' in item) {
52
+ list.push('separator');
53
+ group = undefined;
54
+ } else if ('heading' in item) {
55
+ group = { heading: item.heading, actions: [] };
56
+ list.push(group);
57
+ } else {
58
+ if (!group) list.push((group = { actions: [] }));
59
+ group.actions.push(item);
60
+ }
61
+ }
62
+ return list;
63
+ });
64
+
65
+ // The control that opens it: the menu is placed against it, named by it, and gives focus back.
66
+ let anchor = $state<HTMLElement | null>(null);
67
+ const capture: Attachment<HTMLElement> = (node) => {
68
+ node.id ||= `${uid}-trigger`;
69
+ anchor = node;
70
+ return () => {
71
+ if (anchor === node) anchor = null;
72
+ };
73
+ };
74
+
75
+ // Which item takes focus as the menu opens: the last after ArrowUp, else the first.
76
+ let from: 'first' | 'last' = 'first';
77
+ function show(start: 'first' | 'last') {
78
+ from = start;
79
+ if (open && ref) focusEnd(ref);
80
+ open = true;
81
+ }
82
+
83
+ const ANCHOR = createAttachmentKey();
84
+ const triggerAttributes: TriggerAttributes = $derived({
85
+ 'aria-haspopup': 'menu',
86
+ 'aria-expanded': open,
87
+ 'aria-controls': open ? id : undefined,
88
+ onclick: () => (open ? close('action') : show('first')),
89
+ onkeydown: (event) => {
90
+ if (event.key !== 'ArrowDown' && event.key !== 'ArrowUp') return;
91
+ event.preventDefault();
92
+ show(event.key === 'ArrowUp' ? 'last' : 'first');
93
+ },
94
+ [ANCHOR]: capture
95
+ });
96
+
97
+ function close(reason: CloseReason) {
98
+ if (!open) return;
99
+ // Escape and a choice give focus back to the control; Tab, a press or focus elsewhere
100
+ // have already chosen where it goes.
101
+ if ((reason === 'escape' || reason === 'action') && ref?.contains(document.activeElement)) {
102
+ anchor?.focus({ preventScroll: true });
103
+ }
104
+ open = false;
105
+ onclose?.(reason);
106
+ }
107
+
108
+ function choose(item: MenuAction) {
109
+ close('action');
110
+ onselect?.(item);
111
+ }
112
+
113
+ function focusEnd(menu: HTMLElement) {
114
+ const enabled = menu.querySelectorAll<HTMLElement>('[data-sv-item]:not(:disabled)');
115
+ (from === 'last' ? enabled[enabled.length - 1] : enabled[0])?.focus({ preventScroll: true });
116
+ }
117
+
118
+ const enter: Attachment<HTMLElement> = (menu) => {
119
+ menu.showPopover();
120
+ focusEnd(menu);
121
+ };
122
+
123
+ // Tab and Shift+Tab close the menu once focus has moved on: the menu turns inert as it fades,
124
+ // and the move must not start from there.
125
+ function tab(event: KeyboardEvent) {
126
+ if (event.key === 'Tab') setTimeout(() => close('focus'));
127
+ }
128
+
129
+ // The pointer and the keyboard share one highlight: the item under the pointer takes focus.
130
+ function follow(event: PointerEvent & { currentTarget: HTMLButtonElement }) {
131
+ if (document.activeElement !== event.currentTarget) {
132
+ event.currentTarget.focus({ preventScroll: true });
133
+ }
134
+ }
135
+ </script>
136
+
137
+ {#snippet action(item: MenuAction)}
138
+ <button
139
+ type="button"
140
+ role="menuitem"
141
+ tabindex="-1"
142
+ disabled={item.disabled}
143
+ data-sv-item
144
+ data-sv-label={item.label}
145
+ class={['sv-menu__item', item.danger && 'sv-menu__item--danger']}
146
+ onclick={() => choose(item)}
147
+ onpointermove={follow}
148
+ >
149
+ {#if item.icon}
150
+ <svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d={item.icon} /></svg>
151
+ {/if}
152
+ <span class="sv-menu__label">{item.label}</span>
153
+ {#if item.shortcut}<kbd class="sv-menu__shortcut">{item.shortcut}</kbd>{/if}
154
+ </button>
155
+ {/snippet}
156
+
157
+ {@render trigger(triggerAttributes)}
158
+
159
+ {#if open}
160
+ <div
161
+ bind:this={ref}
162
+ aria-labelledby={anchor?.id}
163
+ tabindex="-1"
164
+ {...rest}
165
+ {id}
166
+ role="menu"
167
+ popover="manual"
168
+ class={['sv-menu', sharedClasses(p, 'Menu'), className]}
169
+ {@attach anchored({ anchor, side, align })}
170
+ {@attach dismissable({ onDismiss: close, inside: () => [anchor], focusOutside: true })}
171
+ {@attach rovingFocus({ orientation: 'vertical' })}
172
+ {@attach typeahead()}
173
+ {@attach enter}
174
+ onkeydown={tab}
175
+ transition:pop
176
+ >
177
+ {#each blocks as block, index (index)}
178
+ {#if block === 'separator'}
179
+ <div role="separator" class="sv-menu__separator"></div>
180
+ {:else}
181
+ <div
182
+ role={block.heading ? 'group' : 'none'}
183
+ aria-labelledby={block.heading ? `${id}-${index}` : undefined}
184
+ >
185
+ {#if block.heading}
186
+ <div role="presentation" id="{id}-{index}" class="sv-menu__heading">
187
+ {block.heading}
188
+ </div>
189
+ {/if}
190
+ {#each block.actions as item, i (i)}{@render action(item)}{/each}
191
+ </div>
192
+ {/if}
193
+ {/each}
194
+ </div>
195
+ {/if}
@@ -0,0 +1,4 @@
1
+ import type { MenuProps } from './types.js';
2
+ declare const Menu: import("svelte").Component<MenuProps, {}, "ref" | "open">;
3
+ type Menu = ReturnType<typeof Menu>;
4
+ export default Menu;
@@ -0,0 +1,2 @@
1
+ export { default as Menu } from './Menu.svelte';
2
+ export type { MenuAction, MenuDefaults, MenuHeading, MenuItem, MenuProps, MenuSeparator } from './types.js';
@@ -0,0 +1 @@
1
+ export { default as Menu } from './Menu.svelte';
@@ -0,0 +1,103 @@
1
+ @layer sv.tokens, sv.base, sv.components, sv.utilities;
2
+
3
+ @layer sv.components {
4
+ /* A raised surface of rows, as wide as its longest item within a short line. */
5
+ .sv-menu {
6
+ --sv-menu-pad: var(--sv-space-5);
7
+ box-sizing: border-box;
8
+ display: flex;
9
+ flex-direction: column;
10
+ min-inline-size: 13rem;
11
+ max-inline-size: min(21rem, 100vw - 2 * var(--sv-space-8));
12
+ max-block-size: calc(100dvh - 2 * var(--sv-space-8));
13
+ padding: var(--sv-menu-pad);
14
+ overflow: auto;
15
+ overscroll-behavior: contain;
16
+ border: var(--sv-hairline) solid var(--sv-color-border-soft);
17
+ border-radius: var(--sv-radius-lg);
18
+ outline: none;
19
+ background-color: var(--sv-color-surface);
20
+ box-shadow: var(--sv-shadow);
21
+ color: var(--sv-color-text);
22
+ font-family: var(--sv-font-ui);
23
+ font-size: var(--sv-text-sm);
24
+ line-height: var(--sv-leading-snug);
25
+ }
26
+
27
+ /* A row a small control tall, rounded to sit inside the menu's corner. */
28
+ .sv-menu__item {
29
+ display: flex;
30
+ gap: var(--sv-space-13);
31
+ align-items: center;
32
+ box-sizing: border-box;
33
+ inline-size: 100%;
34
+ min-block-size: calc(var(--sv-control-sm) * var(--sv-density));
35
+ margin: 0;
36
+ padding: calc(var(--sv-space-5) * var(--sv-density)) var(--sv-space-8);
37
+ border: 0;
38
+ border-radius: max(0px, var(--sv-radius-lg) - var(--sv-menu-pad));
39
+ outline: none;
40
+ background: transparent;
41
+ color: inherit;
42
+ font: inherit;
43
+ text-align: start;
44
+ cursor: pointer;
45
+ -webkit-tap-highlight-color: transparent;
46
+ }
47
+
48
+ /* The pointer focuses the item under it, so one highlight serves both. */
49
+ .sv-menu__item:focus {
50
+ background-color: color-mix(in oklch, currentColor var(--sv-state-press), transparent);
51
+ }
52
+
53
+ .sv-menu__item:disabled {
54
+ opacity: 0.382;
55
+ cursor: not-allowed;
56
+ }
57
+
58
+ .sv-menu__item--danger {
59
+ color: var(--sv-color-error);
60
+ }
61
+
62
+ .sv-menu__item svg {
63
+ flex: none;
64
+ inline-size: 1.272em;
65
+ block-size: 1.272em;
66
+ }
67
+
68
+ .sv-menu__label {
69
+ flex: 1;
70
+ min-inline-size: 0;
71
+ overflow-wrap: anywhere;
72
+ }
73
+
74
+ .sv-menu__shortcut {
75
+ margin-inline-start: auto;
76
+ color: var(--sv-color-text-muted);
77
+ font: inherit;
78
+ font-size: var(--sv-text-xs);
79
+ font-variant-numeric: tabular-nums;
80
+ }
81
+
82
+ .sv-menu__item--danger .sv-menu__shortcut {
83
+ color: inherit;
84
+ }
85
+
86
+ .sv-menu__separator {
87
+ flex: none;
88
+ block-size: var(--sv-hairline);
89
+ margin-block: var(--sv-menu-pad);
90
+ margin-inline: calc(-1 * var(--sv-menu-pad));
91
+ background-color: var(--sv-color-border-soft);
92
+ }
93
+
94
+ /* The theme's label voice, muted, above the items it names. */
95
+ .sv-menu__heading {
96
+ padding: var(--sv-space-8) var(--sv-space-8) var(--sv-space-3);
97
+ color: var(--sv-color-text-muted);
98
+ font-size: var(--sv-label-size);
99
+ font-weight: var(--sv-weight-medium);
100
+ letter-spacing: var(--sv-label-tracking);
101
+ text-transform: var(--sv-label-transform);
102
+ }
103
+ }
@@ -0,0 +1,90 @@
1
+ import type { HTMLAttributes } from 'svelte/elements';
2
+ import type { Snippet } from 'svelte';
3
+ import type { CloseReason, TriggerAttributes } from '../../core/overlay/types.js';
4
+ import type { ElementProps } from '../../core/props/element.js';
5
+ import type { Density } from '../../core/props/types.js';
6
+ import type { Align, Side } from '../../primitives/anchored/anchored.js';
7
+ /** The Menu props that `DefaultsProvider` and `UIProvider` can set for a subtree. */
8
+ export interface MenuDefaults {
9
+ /**
10
+ * Multiplies the height and padding of the items, never the text.
11
+ * @default 'default'
12
+ */
13
+ density: Density;
14
+ }
15
+ declare module '../../core/defaults/context.js' {
16
+ interface ComponentDefaults {
17
+ Menu: MenuDefaults;
18
+ }
19
+ }
20
+ /** An item the user can choose. */
21
+ export interface MenuAction {
22
+ /** What the item says; typing its first letters moves to it. */
23
+ label: string;
24
+ /** An identifier for `onselect` to tell the items apart. */
25
+ value?: string;
26
+ /**
27
+ * An icon before the label: an SVG path on a 24×24 grid, as icon sets such as Material Design
28
+ * Icons export them (`mdiPencil`).
29
+ */
30
+ icon?: string;
31
+ /** A keyboard shortcut shown at the end, such as `Ctrl+C`. Showing it does not bind it. */
32
+ shortcut?: string;
33
+ /** Shown but not chosen, and skipped by the arrow keys. */
34
+ disabled?: boolean;
35
+ /** In the error colour, for an action that deletes or cannot be undone. */
36
+ danger?: boolean;
37
+ }
38
+ /** A line between groups of items. */
39
+ export interface MenuSeparator {
40
+ separator: true;
41
+ }
42
+ /** A heading that names the items after it, up to the next heading or separator. */
43
+ export interface MenuHeading {
44
+ heading: string;
45
+ }
46
+ /** An entry of `items`: an action, a separator or a heading. */
47
+ export type MenuItem = MenuAction | MenuSeparator | MenuHeading;
48
+ /**
49
+ * A list of actions that opens from a control, as the APG menu button pattern describes. Arrow
50
+ * keys move through the items and wrap, Home and End jump to the ends, typing moves to the item
51
+ * that starts with it, Enter and Space choose, Escape closes and gives focus back, and Tab closes
52
+ * and moves on. `class`, `style`, `ref` and other attributes go to the menu.
53
+ *
54
+ * @example
55
+ * <Menu
56
+ * items={[
57
+ * { label: 'Rename', value: 'rename', shortcut: 'F2' },
58
+ * { separator: true },
59
+ * { label: 'Delete', value: 'delete', danger: true }
60
+ * ]}
61
+ * onselect={(item) => run(item.value)}
62
+ * >
63
+ * {#snippet trigger(attributes)}<Button {...attributes}>Actions</Button>{/snippet}
64
+ * </Menu>
65
+ */
66
+ export type MenuProps = ElementProps<HTMLDivElement, Omit<HTMLAttributes<HTMLDivElement>, 'children' | 'popover' | 'role'>, Partial<MenuDefaults> & {
67
+ /** The entries, in order. */
68
+ items: readonly MenuItem[];
69
+ /** Called with the item the user chooses, after the menu has closed. */
70
+ onselect?: (item: MenuAction) => void;
71
+ /** The control that opens it. Spread the attributes it receives onto it. */
72
+ trigger: Snippet<[TriggerAttributes]>;
73
+ /**
74
+ * Whether it is open. Bindable; it sets this to `false` when it closes itself.
75
+ * @default false
76
+ */
77
+ open?: boolean;
78
+ /**
79
+ * The side of the control it opens on, moved to the opposite side when there is no room.
80
+ * @default 'bottom'
81
+ */
82
+ side?: Side;
83
+ /**
84
+ * Its alignment along that side.
85
+ * @default 'start'
86
+ */
87
+ align?: Align;
88
+ /** Called when it closes itself, with the reason; `'action'` after a choice. */
89
+ onclose?: (reason: CloseReason) => void;
90
+ }>;
@@ -0,0 +1 @@
1
+ export {};