kosui 0.2.0 → 0.5.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 (248) hide show
  1. package/README.md +34 -3
  2. package/dist/alert/alert.svelte +16 -157
  3. package/dist/alert/alert.svelte.d.ts +3 -26
  4. package/dist/alert/index.d.ts +1 -1
  5. package/dist/alert/index.js +1 -1
  6. package/dist/autocomplete/autocomplete.svelte +126 -12
  7. package/dist/autocomplete/autocomplete.svelte.d.ts +30 -6
  8. package/dist/autocomplete/index.d.ts +1 -5
  9. package/dist/autocomplete/index.js +1 -5
  10. package/dist/avatar/avatar.svelte +16 -10
  11. package/dist/badge/badge.svelte +31 -20
  12. package/dist/badge/badge.svelte.d.ts +8 -4
  13. package/dist/badge/index.d.ts +1 -1
  14. package/dist/base.d.ts +4 -8
  15. package/dist/base.js +14 -28
  16. package/dist/box/box.svelte +2 -14
  17. package/dist/box/box.svelte.d.ts +0 -2
  18. package/dist/breadcrumbs/breadcrumbs.svelte +38 -19
  19. package/dist/breadcrumbs/breadcrumbs.svelte.d.ts +6 -4
  20. package/dist/breadcrumbs/index.d.ts +1 -1
  21. package/dist/breadcrumbs/index.js +1 -1
  22. package/dist/button/button.svelte +96 -31
  23. package/dist/button/button.svelte.d.ts +11 -5
  24. package/dist/button/index.d.ts +1 -1
  25. package/dist/button/index.js +1 -1
  26. package/dist/chip/chip.svelte +62 -14
  27. package/dist/chip/chip.svelte.d.ts +2 -1
  28. package/dist/command/action.svelte.js +13 -18
  29. package/dist/command/command-palette.svelte +163 -0
  30. package/dist/command/command-palette.svelte.d.ts +11 -0
  31. package/dist/command/index.d.ts +1 -8
  32. package/dist/command/index.js +1 -8
  33. package/dist/common/divider.svelte +10 -5
  34. package/dist/common/divider.svelte.d.ts +3 -3
  35. package/dist/common/index.d.ts +0 -2
  36. package/dist/common/index.js +0 -2
  37. package/dist/dialog/dialog-provider.svelte +70 -0
  38. package/dist/dialog/dialog-provider.svelte.d.ts +7 -0
  39. package/dist/dialog/dialog.svelte +33 -35
  40. package/dist/dialog/dialog.svelte.d.ts +4 -8
  41. package/dist/dialog/dialogs.svelte.d.ts +49 -0
  42. package/dist/dialog/dialogs.svelte.js +73 -0
  43. package/dist/dialog/index.d.ts +2 -3
  44. package/dist/dialog/index.js +2 -3
  45. package/dist/drawer/drawer.svelte +51 -0
  46. package/dist/drawer/drawer.svelte.d.ts +8 -0
  47. package/dist/drawer/index.d.ts +1 -0
  48. package/dist/drawer/index.js +1 -0
  49. package/dist/fab/fab.svelte +30 -10
  50. package/dist/fab/fab.svelte.d.ts +3 -2
  51. package/dist/index.d.ts +2 -1
  52. package/dist/index.js +2 -1
  53. package/dist/input/field.d.ts +8 -0
  54. package/dist/input/field.js +7 -0
  55. package/dist/input/index.d.ts +2 -1
  56. package/dist/input/index.js +2 -1
  57. package/dist/input/input.svelte +21 -66
  58. package/dist/input/input.svelte.d.ts +6 -16
  59. package/dist/link/link.svelte +33 -42
  60. package/dist/link/link.svelte.d.ts +4 -3
  61. package/dist/markdown/index.d.ts +2 -25
  62. package/dist/markdown/index.js +0 -24
  63. package/dist/markdown/markdown-tokens.svelte +166 -6
  64. package/dist/markdown/markdown-tokens.svelte.d.ts +2 -0
  65. package/dist/markdown/markdown.svelte +62 -173
  66. package/dist/markdown/markdown.svelte.d.ts +7 -27
  67. package/dist/markdown/parse.d.ts +5 -0
  68. package/dist/markdown/parse.js +30 -0
  69. package/dist/markdown/types.d.ts +28 -0
  70. package/dist/markdown/types.js +1 -0
  71. package/dist/menu/index.d.ts +3 -6
  72. package/dist/menu/index.js +3 -6
  73. package/dist/menu/menu-actions.svelte +44 -38
  74. package/dist/menu/menu-context.d.ts +4 -0
  75. package/dist/menu/menu-context.js +2 -0
  76. package/dist/menu/menu-group.svelte +40 -0
  77. package/dist/menu/menu-group.svelte.d.ts +11 -0
  78. package/dist/menu/menu-item.svelte +31 -54
  79. package/dist/menu/menu-item.svelte.d.ts +7 -5
  80. package/dist/menu/menu.svelte +160 -15
  81. package/dist/menu/menu.svelte.d.ts +15 -4
  82. package/dist/merge-props.d.ts +1 -1
  83. package/dist/merge-props.js +10 -5
  84. package/dist/modal/modal.svelte +67 -84
  85. package/dist/modal/modal.svelte.d.ts +7 -4
  86. package/dist/nav/index.d.ts +2 -0
  87. package/dist/nav/index.js +2 -0
  88. package/dist/nav/nav-group.svelte +40 -0
  89. package/dist/nav/nav-group.svelte.d.ts +10 -0
  90. package/dist/nav/nav-item.svelte +65 -0
  91. package/dist/nav/nav-item.svelte.d.ts +14 -0
  92. package/dist/popover/index.d.ts +1 -1
  93. package/dist/popover/index.js +1 -1
  94. package/dist/popover/popover.svelte +58 -111
  95. package/dist/popover/popover.svelte.d.ts +6 -8
  96. package/dist/progress/circular-progress.svelte +64 -30
  97. package/dist/progress/circular-progress.svelte.d.ts +7 -8
  98. package/dist/shortcut/index.d.ts +2 -2
  99. package/dist/shortcut/index.js +1 -1
  100. package/dist/shortcut/shortcut-hint.svelte +36 -0
  101. package/dist/shortcut/shortcut-hint.svelte.d.ts +9 -0
  102. package/dist/shortcut/shortcut.d.ts +19 -14
  103. package/dist/shortcut/shortcut.js +105 -52
  104. package/dist/skeleton/skeleton.svelte +13 -8
  105. package/dist/skeleton/skeleton.svelte.d.ts +2 -5
  106. package/dist/slider/index.d.ts +1 -8
  107. package/dist/slider/index.js +1 -8
  108. package/dist/slider/slider.svelte +180 -211
  109. package/dist/slider/slider.svelte.d.ts +11 -32
  110. package/dist/switch/index.d.ts +1 -2
  111. package/dist/switch/index.js +1 -2
  112. package/dist/switch/switch.svelte +36 -60
  113. package/dist/switch/switch.svelte.d.ts +6 -17
  114. package/dist/textarea/index.d.ts +1 -1
  115. package/dist/textarea/index.js +1 -1
  116. package/dist/textarea/textarea.svelte +23 -63
  117. package/dist/textarea/textarea.svelte.d.ts +8 -18
  118. package/dist/theme.css +191 -0
  119. package/dist/toggle/index.d.ts +0 -1
  120. package/dist/toggle/index.js +0 -1
  121. package/dist/toggle/toggle-button.svelte +72 -38
  122. package/dist/toggle/toggle-button.svelte.d.ts +4 -4
  123. package/dist/toggle/toggle-context.d.ts +10 -0
  124. package/dist/toggle/toggle-context.js +2 -0
  125. package/dist/toggle/toggle-group.svelte +41 -22
  126. package/dist/toggle/toggle-group.svelte.d.ts +7 -4
  127. package/dist/tooltip/tooltip.svelte +67 -26
  128. package/dist/tooltip/tooltip.svelte.d.ts +10 -4
  129. package/dist/utils.d.ts +9 -3
  130. package/dist/utils.js +10 -0
  131. package/package.json +6 -5
  132. package/dist/autocomplete/autocomplete-content.svelte +0 -47
  133. package/dist/autocomplete/autocomplete-content.svelte.d.ts +0 -10
  134. package/dist/autocomplete/autocomplete-context.svelte.d.ts +0 -12
  135. package/dist/autocomplete/autocomplete-context.svelte.js +0 -34
  136. package/dist/autocomplete/autocomplete-input.svelte +0 -36
  137. package/dist/autocomplete/autocomplete-input.svelte.d.ts +0 -5
  138. package/dist/autocomplete/autocomplete-item.svelte +0 -68
  139. package/dist/autocomplete/autocomplete-item.svelte.d.ts +0 -11
  140. package/dist/base.css +0 -71
  141. package/dist/bindable.svelte.d.ts +0 -7
  142. package/dist/bindable.svelte.js +0 -25
  143. package/dist/command/command-content.svelte +0 -15
  144. package/dist/command/command-content.svelte.d.ts +0 -8
  145. package/dist/command/command-context.svelte.d.ts +0 -5
  146. package/dist/command/command-context.svelte.js +0 -13
  147. package/dist/command/command-input.svelte +0 -44
  148. package/dist/command/command-input.svelte.d.ts +0 -7
  149. package/dist/command/command-item.svelte +0 -66
  150. package/dist/command/command-item.svelte.d.ts +0 -11
  151. package/dist/command/command-search.svelte +0 -28
  152. package/dist/command/command-search.svelte.d.ts +0 -5
  153. package/dist/command/command.svelte +0 -19
  154. package/dist/command/command.svelte.d.ts +0 -8
  155. package/dist/command/commander.svelte +0 -107
  156. package/dist/command/commander.svelte.d.ts +0 -9
  157. package/dist/common/header.svelte +0 -35
  158. package/dist/common/header.svelte.d.ts +0 -10
  159. package/dist/common/item-context.svelte.d.ts +0 -12
  160. package/dist/common/item-context.svelte.js +0 -77
  161. package/dist/dialog/dialog-button.svelte +0 -25
  162. package/dist/dialog/dialog-button.svelte.d.ts +0 -32
  163. package/dist/dialog/dialoguer-context.svelte.d.ts +0 -53
  164. package/dist/dialog/dialoguer-context.svelte.js +0 -85
  165. package/dist/dialog/dialoguer.svelte +0 -65
  166. package/dist/dialog/dialoguer.svelte.d.ts +0 -7
  167. package/dist/markdown/markdown-blockquote.svelte +0 -14
  168. package/dist/markdown/markdown-blockquote.svelte.d.ts +0 -7
  169. package/dist/markdown/markdown-br.svelte +0 -12
  170. package/dist/markdown/markdown-br.svelte.d.ts +0 -7
  171. package/dist/markdown/markdown-code.svelte +0 -14
  172. package/dist/markdown/markdown-code.svelte.d.ts +0 -7
  173. package/dist/markdown/markdown-codespan.svelte +0 -15
  174. package/dist/markdown/markdown-codespan.svelte.d.ts +0 -7
  175. package/dist/markdown/markdown-context.svelte.d.ts +0 -26
  176. package/dist/markdown/markdown-context.svelte.js +0 -31
  177. package/dist/markdown/markdown-def.svelte +0 -15
  178. package/dist/markdown/markdown-def.svelte.d.ts +0 -7
  179. package/dist/markdown/markdown-del.svelte +0 -14
  180. package/dist/markdown/markdown-del.svelte.d.ts +0 -7
  181. package/dist/markdown/markdown-em.svelte +0 -14
  182. package/dist/markdown/markdown-em.svelte.d.ts +0 -7
  183. package/dist/markdown/markdown-escape.svelte +0 -8
  184. package/dist/markdown/markdown-escape.svelte.d.ts +0 -5
  185. package/dist/markdown/markdown-heading.svelte +0 -16
  186. package/dist/markdown/markdown-heading.svelte.d.ts +0 -7
  187. package/dist/markdown/markdown-hr.svelte +0 -12
  188. package/dist/markdown/markdown-hr.svelte.d.ts +0 -7
  189. package/dist/markdown/markdown-html.svelte +0 -9
  190. package/dist/markdown/markdown-html.svelte.d.ts +0 -5
  191. package/dist/markdown/markdown-image.svelte +0 -12
  192. package/dist/markdown/markdown-image.svelte.d.ts +0 -7
  193. package/dist/markdown/markdown-link.svelte +0 -17
  194. package/dist/markdown/markdown-link.svelte.d.ts +0 -7
  195. package/dist/markdown/markdown-list-item.svelte +0 -14
  196. package/dist/markdown/markdown-list-item.svelte.d.ts +0 -7
  197. package/dist/markdown/markdown-list.svelte +0 -19
  198. package/dist/markdown/markdown-list.svelte.d.ts +0 -7
  199. package/dist/markdown/markdown-paragraph.svelte +0 -16
  200. package/dist/markdown/markdown-paragraph.svelte.d.ts +0 -7
  201. package/dist/markdown/markdown-space.svelte +0 -8
  202. package/dist/markdown/markdown-space.svelte.d.ts +0 -5
  203. package/dist/markdown/markdown-strong.svelte +0 -15
  204. package/dist/markdown/markdown-strong.svelte.d.ts +0 -7
  205. package/dist/markdown/markdown-table-cell.svelte +0 -16
  206. package/dist/markdown/markdown-table-cell.svelte.d.ts +0 -7
  207. package/dist/markdown/markdown-table.svelte +0 -36
  208. package/dist/markdown/markdown-table.svelte.d.ts +0 -7
  209. package/dist/markdown/markdown-text.svelte +0 -8
  210. package/dist/markdown/markdown-text.svelte.d.ts +0 -5
  211. package/dist/markdown/markdown-token.svelte +0 -25
  212. package/dist/markdown/markdown-token.svelte.d.ts +0 -7
  213. package/dist/menu/menu-content.svelte +0 -47
  214. package/dist/menu/menu-content.svelte.d.ts +0 -10
  215. package/dist/menu/menu-context.svelte.d.ts +0 -18
  216. package/dist/menu/menu-context.svelte.js +0 -118
  217. package/dist/menu/menu-trigger-button.svelte +0 -31
  218. package/dist/menu/menu-trigger-button.svelte.d.ts +0 -5
  219. package/dist/menu/menu-trigger.svelte +0 -42
  220. package/dist/menu/menu-trigger.svelte.d.ts +0 -10
  221. package/dist/ordered-html-elements.d.ts +0 -7
  222. package/dist/ordered-html-elements.js +0 -29
  223. package/dist/portal/index.d.ts +0 -2
  224. package/dist/portal/index.js +0 -2
  225. package/dist/portal/portal.svelte +0 -15
  226. package/dist/portal/portal.svelte.d.ts +0 -8
  227. package/dist/portal/proxy.svelte +0 -13
  228. package/dist/portal/proxy.svelte.d.ts +0 -7
  229. package/dist/shortcut/shortcut-badge.svelte +0 -41
  230. package/dist/shortcut/shortcut-badge.svelte.d.ts +0 -11
  231. package/dist/slider/slider-context.svelte.d.ts +0 -50
  232. package/dist/slider/slider-context.svelte.js +0 -216
  233. package/dist/slider/slider-handle-label-arrow.svelte +0 -18
  234. package/dist/slider/slider-handle-label-arrow.svelte.d.ts +0 -6
  235. package/dist/slider/slider-handle-label.svelte +0 -35
  236. package/dist/slider/slider-handle-label.svelte.d.ts +0 -6
  237. package/dist/slider/slider-handle.svelte +0 -54
  238. package/dist/slider/slider-handle.svelte.d.ts +0 -6
  239. package/dist/slider/slider-mark-dot.svelte +0 -25
  240. package/dist/slider/slider-mark-dot.svelte.d.ts +0 -6
  241. package/dist/slider/slider-mark-label.svelte +0 -37
  242. package/dist/slider/slider-mark-label.svelte.d.ts +0 -9
  243. package/dist/slider/slider-mark.svelte +0 -33
  244. package/dist/slider/slider-mark.svelte.d.ts +0 -9
  245. package/dist/switch/switch-thumb.svelte +0 -52
  246. package/dist/switch/switch-thumb.svelte.d.ts +0 -7
  247. package/dist/toggle/toggle-context.svelte.d.ts +0 -9
  248. package/dist/toggle/toggle-context.svelte.js +0 -32
@@ -1,11 +1,13 @@
1
- import { type Variants } from "../base.js";
2
- import type { ClassName, ElementRef } from "../utils.js";
1
+ import { type ColorProp } from "../base.js";
2
+ import { type ClassName } from "../utils.js";
3
3
  import type { Snippet } from "svelte";
4
4
  import type { HTMLButtonAttributes } from "svelte/elements";
5
5
  export type MenuItemProps = {
6
- onSelect?: (el: HTMLElement) => void;
7
- children: Snippet<[]>;
8
- } & ElementRef & ClassName & Variants & Omit<HTMLButtonAttributes, "type" | "value">;
6
+ onSelect?: () => void;
7
+ checked?: boolean;
8
+ el?: HTMLButtonElement;
9
+ children: Snippet;
10
+ } & ClassName & ColorProp & Omit<HTMLButtonAttributes, "type" | "value" | "role" | "class">;
9
11
  declare const MenuItem: import("svelte").Component<MenuItemProps, {}, "el">;
10
12
  type MenuItem = ReturnType<typeof MenuItem>;
11
13
  export default MenuItem;
@@ -1,29 +1,174 @@
1
1
  <script module lang="ts">
2
- import { MenuContext, setMenuContext } from "./index.js";
3
- import type { ElementRef } from "../utils.js";
2
+ import type { ShapeProp } from "../base.js";
3
+ import { baseClasses } from "../base.js";
4
+ import { mergeProps } from "../merge-props.js";
5
+ import {
6
+ Popover,
7
+ type PopoverPlacement,
8
+ type PopoverProps,
9
+ } from "../popover/index.js";
10
+ import { Shortcut } from "../shortcut/index.js";
11
+ import { cn, TypingBuffer } from "../utils.js";
4
12
  import type { Snippet } from "svelte";
13
+ import { createAttachmentKey, type Attachment } from "svelte/attachments";
14
+ import { setMenuContext } from "./menu-context.js";
15
+
16
+ export type MenuTriggerProps = {
17
+ [attachment: symbol]: Attachment<HTMLElement>;
18
+ id: string;
19
+ "aria-haspopup": "menu";
20
+ "aria-expanded": boolean;
21
+ "aria-controls"?: string;
22
+ onclick: (event: MouseEvent) => void;
23
+ onkeydown: (event: KeyboardEvent) => void;
24
+ };
5
25
 
6
26
  export type MenuProps = {
7
- open?: boolean;
8
- children: Snippet;
9
- } & ElementRef;
27
+ trigger: Snippet<[MenuTriggerProps]>;
28
+ placement?: PopoverPlacement;
29
+ } & ShapeProp &
30
+ Omit<PopoverProps, "placement" | "anchorEl" | "onKeydownWhileOpen">;
31
+
32
+ type Start = "first" | "last" | "menu";
10
33
  </script>
11
34
 
12
35
  <script lang="ts">
13
36
  let {
14
37
  open = $bindable(false),
15
- children,
16
38
  el = $bindable(),
39
+ trigger,
40
+ placement = "bottom-start",
41
+ shape = "xs",
42
+ class: className,
43
+ children,
44
+ ...restProps
17
45
  }: MenuProps = $props();
18
46
 
19
- setMenuContext(
20
- new MenuContext(
21
- () => open,
22
- (value) => (open = value),
23
- () => el,
24
- (value) => (el = value),
25
- ),
26
- );
47
+ const id = $props.id();
48
+ const anchor = createAttachmentKey();
49
+ const buffer = new TypingBuffer();
50
+ let anchorEl: HTMLElement | undefined = $state();
51
+ let start: Start = "menu";
52
+
53
+ function show(from: Start) {
54
+ start = from;
55
+ open = true;
56
+ }
57
+
58
+ function close() {
59
+ open = false;
60
+ anchorEl?.focus();
61
+ }
62
+
63
+ setMenuContext({ close });
64
+
65
+ $effect(() => {
66
+ if (open && el) queueMicrotask(focusStart);
67
+ });
68
+
69
+ function items(): HTMLElement[] {
70
+ return el
71
+ ? [
72
+ ...el.querySelectorAll<HTMLElement>(
73
+ '[role^="menuitem"]:not(:disabled)',
74
+ ),
75
+ ]
76
+ : [];
77
+ }
78
+
79
+ function focusStart() {
80
+ const list = items();
81
+ const target =
82
+ start === "first" ? list[0] : start === "last" ? list.at(-1) : el;
83
+ (target ?? el)?.focus();
84
+ }
85
+
86
+ function typeahead(char: string, list: HTMLElement[], index: number) {
87
+ const typed = buffer.append(char).toLowerCase();
88
+ const prefix = [...typed].every((c) => c === typed[0]) ? typed[0] : typed;
89
+ const ordered = [...list.slice(index + 1), ...list.slice(0, index + 1)];
90
+ return ordered.find((item) =>
91
+ item.textContent?.trim().toLowerCase().startsWith(prefix),
92
+ );
93
+ }
94
+
95
+ function onkeydown(event: KeyboardEvent) {
96
+ if (
97
+ Shortcut.TAB_FORWARD.matches(event) ||
98
+ Shortcut.TAB_BACKWARD.matches(event)
99
+ ) {
100
+ open = false;
101
+ return;
102
+ }
103
+ const list = items();
104
+ const index = list.indexOf(document.activeElement as HTMLElement);
105
+ let target: HTMLElement | undefined;
106
+ if (Shortcut.ARROW_DOWN.matches(event)) {
107
+ target = list[(index + 1) % list.length];
108
+ } else if (Shortcut.ARROW_UP.matches(event)) {
109
+ target = index <= 0 ? list.at(-1) : list[index - 1];
110
+ } else if (Shortcut.HOME.matches(event)) {
111
+ target = list[0];
112
+ } else if (Shortcut.END.matches(event)) {
113
+ target = list.at(-1);
114
+ } else if (Shortcut.isChar(event) && event.key !== " ") {
115
+ target = typeahead(event.key, list, index);
116
+ } else if (
117
+ !Shortcut.ENTER.matches(event) &&
118
+ !Shortcut.SPACE.matches(event)
119
+ ) {
120
+ return;
121
+ }
122
+ target?.focus();
123
+ if (target) event.preventDefault();
124
+ event.stopPropagation();
125
+ }
126
+
127
+ function onpointerleave() {
128
+ if (el?.contains(document.activeElement)) el.focus();
129
+ }
130
+
131
+ const triggerProps: MenuTriggerProps = $derived({
132
+ [anchor]: (node) => {
133
+ anchorEl = node;
134
+ },
135
+ id: `${id}-trigger`,
136
+ "aria-haspopup": "menu",
137
+ "aria-expanded": open,
138
+ "aria-controls": open ? `${id}-menu` : undefined,
139
+ onclick: (event) => {
140
+ if (open) open = false;
141
+ else show(event.detail === 0 ? "first" : "menu");
142
+ },
143
+ onkeydown: (event) => {
144
+ if (Shortcut.ARROW_DOWN.matches(event)) show("first");
145
+ else if (Shortcut.ARROW_UP.matches(event)) show("last");
146
+ else return;
147
+ event.preventDefault();
148
+ },
149
+ });
27
150
  </script>
28
151
 
29
- {@render children()}
152
+ {@render trigger(triggerProps)}
153
+
154
+ <Popover
155
+ bind:open
156
+ bind:el
157
+ {anchorEl}
158
+ {placement}
159
+ id="{id}-menu"
160
+ role="menu"
161
+ aria-labelledby="{id}-trigger"
162
+ tabindex={-1}
163
+ class={cn(
164
+ baseClasses({ shape }),
165
+ "max-h-[min(60vh,32rem)] min-w-40 overflow-y-auto bg-m3-surface-container p-1 text-m3-on-surface shadow-md shadow-m3-shadow/20 outline-none",
166
+ className,
167
+ )}
168
+ {...mergeProps(restProps, {
169
+ onkeydown,
170
+ onpointerleave,
171
+ })}
172
+ >
173
+ {@render children()}
174
+ </Popover>
@@ -1,9 +1,20 @@
1
- import type { ElementRef } from "../utils.js";
1
+ import type { ShapeProp } from "../base.js";
2
+ import { type PopoverPlacement, type PopoverProps } from "../popover/index.js";
2
3
  import type { Snippet } from "svelte";
4
+ import { type Attachment } from "svelte/attachments";
5
+ export type MenuTriggerProps = {
6
+ [attachment: symbol]: Attachment<HTMLElement>;
7
+ id: string;
8
+ "aria-haspopup": "menu";
9
+ "aria-expanded": boolean;
10
+ "aria-controls"?: string;
11
+ onclick: (event: MouseEvent) => void;
12
+ onkeydown: (event: KeyboardEvent) => void;
13
+ };
3
14
  export type MenuProps = {
4
- open?: boolean;
5
- children: Snippet;
6
- } & ElementRef;
15
+ trigger: Snippet<[MenuTriggerProps]>;
16
+ placement?: PopoverPlacement;
17
+ } & ShapeProp & Omit<PopoverProps, "placement" | "anchorEl" | "onKeydownWhileOpen">;
7
18
  declare const Menu: import("svelte").Component<MenuProps, {}, "el" | "open">;
8
19
  type Menu = ReturnType<typeof Menu>;
9
20
  export default Menu;
@@ -4,7 +4,7 @@ export declare function interceptStopImmediatePropagation(event: Event, callback
4
4
  export declare function runEventHandlers(target: EventTarget, event: Event & {
5
5
  currentTarget: EventTarget & Element;
6
6
  }, ...handlers: EventHandler[]): void;
7
- type Props = Record<string, unknown>;
7
+ type Props = Record<PropertyKey, unknown>;
8
8
  type PropsUnion<T extends Props[]> = (T extends (infer U)[] ? U extends Props ? (k: {
9
9
  [K in keyof U]: U[K];
10
10
  }) => void : never : never) extends (k: infer I) => void ? I : never;
@@ -1,4 +1,4 @@
1
- import { twMerge } from "tailwind-merge";
1
+ import { cn } from "./utils.js";
2
2
  function isClass(value) {
3
3
  if (value === null || value === undefined) {
4
4
  return true;
@@ -9,10 +9,12 @@ function isClass(value) {
9
9
  if (Array.isArray(value)) {
10
10
  return value.every((item) => isClass(item));
11
11
  }
12
- return false;
12
+ return Object.getPrototypeOf(value) === Object.prototype;
13
13
  }
14
14
  function isHandlerKey(key) {
15
- return key.startsWith("on") && key[2].toLowerCase() === key[2];
15
+ return (typeof key === "string" &&
16
+ key.startsWith("on") &&
17
+ key[2].toLowerCase() === key[2]);
16
18
  }
17
19
  function isHandler(value) {
18
20
  return typeof value === "function";
@@ -48,10 +50,13 @@ function chainCallbacks(...callbacks) {
48
50
  }
49
51
  };
50
52
  }
53
+ function ownKeys(props) {
54
+ return [...Object.keys(props), ...Object.getOwnPropertySymbols(props)];
55
+ }
51
56
  export function mergeProps(...args) {
52
57
  const result = {};
53
58
  for (const props of args) {
54
- for (const key in props) {
59
+ for (const key of ownKeys(props)) {
55
60
  const prevVal = result[key];
56
61
  const currVal = props[key];
57
62
  if (currVal === undefined) {
@@ -70,7 +75,7 @@ export function mergeProps(...args) {
70
75
  continue;
71
76
  }
72
77
  if (key === "class" && isClass(prevVal) && isClass(currVal)) {
73
- result[key] = twMerge(prevVal, currVal);
78
+ result[key] = cn(prevVal, currVal);
74
79
  continue;
75
80
  }
76
81
  result[key] = currVal;
@@ -1,123 +1,106 @@
1
1
  <script module lang="ts">
2
2
  import { events } from "../index.js";
3
+ import { baseClasses, type ShapeProp } from "../base.js";
3
4
  import { mergeProps } from "../merge-props.js";
4
- import { Shortcut } from "../shortcut/index.js";
5
- import type { ClassName } from "../utils.js";
5
+ import { cn, type ClassName } from "../utils.js";
6
6
  import type { Snippet } from "svelte";
7
7
  import type { HTMLDialogAttributes } from "svelte/elements";
8
- import { scale } from "svelte/transition";
9
- import { twMerge } from "tailwind-merge";
8
+ import { prefersReducedMotion } from "svelte/motion";
9
+ import { scale, type TransitionConfig } from "svelte/transition";
10
10
 
11
11
  export type ModalProps = {
12
- ref?: HTMLDialogElement;
12
+ el?: HTMLDialogElement;
13
13
  onCancel?: () => void;
14
+ transition?: (node: Element) => TransitionConfig;
14
15
  children: Snippet;
15
16
  } & ClassName &
16
- HTMLDialogAttributes;
17
+ ShapeProp &
18
+ Omit<HTMLDialogAttributes, "class">;
19
+
20
+ type Layer = { el: HTMLDialogElement; opener?: HTMLElement };
21
+
22
+ const layers: Layer[] = [];
23
+
24
+ function zoom(node: Element) {
25
+ return scale(node, {
26
+ duration: prefersReducedMotion.current ? 0 : 150,
27
+ start: 0.95,
28
+ });
29
+ }
30
+
31
+ function focusedElement() {
32
+ const active = document.activeElement;
33
+ return active instanceof HTMLElement && active !== document.body
34
+ ? active
35
+ : undefined;
36
+ }
37
+
38
+ function closeLayer(layer: Layer) {
39
+ const index = layers.indexOf(layer);
40
+ layers.splice(index, 1);
41
+ const above = layers[index];
42
+ if (above) {
43
+ if (!above.opener?.isConnected || layer.el.contains(above.opener)) {
44
+ above.opener = layer.opener;
45
+ }
46
+ } else if (layer.opener?.isConnected) {
47
+ layer.opener.focus();
48
+ }
49
+ }
17
50
  </script>
18
51
 
19
52
  <script lang="ts">
20
53
  let {
21
- ref = $bindable(),
54
+ el = $bindable(),
22
55
  open = $bindable(),
23
56
  onCancel,
57
+ transition = zoom,
24
58
  children,
59
+ shape = "xl",
25
60
  class: className,
26
61
  ...restProps
27
62
  }: ModalProps = $props();
28
63
 
29
- function handleKeydown(event: KeyboardEvent) {
30
- if (Shortcut.ESCAPE.matches(event)) {
31
- open = false;
32
- onCancel?.();
33
- event.preventDefault();
34
- }
64
+ function isolateKeys(event: KeyboardEvent) {
35
65
  event.stopImmediatePropagation();
36
66
  }
37
67
 
38
- function handleClickOutside(event: MouseEvent) {
39
- if (ref && ref === event.target) {
40
- const rect = ref.getBoundingClientRect();
41
- if (
42
- event.clientY < rect.top ||
43
- event.clientY > rect.top + rect.height ||
44
- event.clientX < rect.left ||
45
- event.clientX > rect.left + rect.width
46
- ) {
47
- open = false;
48
- onCancel?.();
49
- event.preventDefault();
50
- event.stopImmediatePropagation();
51
- }
52
- }
68
+ function oncancel(event: Event) {
69
+ event.preventDefault();
70
+ open = false;
71
+ onCancel?.();
53
72
  }
54
73
 
55
74
  $effect(() => {
56
- if (ref) {
57
- ref.showModal();
58
- events.on("keydown", handleKeydown);
59
- events.on("mousedown", handleClickOutside);
75
+ if (!el) return;
76
+ const layer: Layer = { el, opener: focusedElement() };
77
+ layers.push(layer);
78
+ if (!el.open) el.showModal();
79
+ events.on("keydown", isolateKeys);
60
80
 
61
- return () => {
62
- events.remove("keydown", handleKeydown);
63
- events.remove("mousedown", handleClickOutside);
64
- };
65
- }
81
+ return () => {
82
+ events.remove("keydown", isolateKeys);
83
+ closeLayer(layer);
84
+ };
66
85
  });
67
86
  </script>
68
87
 
69
88
  {#if open}
70
89
  <dialog
71
- bind:this={ref}
72
- class={twMerge("m-auto rounded-lg p-5 shadow-lg", className)}
73
- transition:scale={{ duration: 150, start: 0.95 }}
90
+ bind:this={el}
91
+ closedby="any"
92
+ class={cn(
93
+ "m-auto bg-m3-surface-container-high p-6 text-m3-on-surface shadow-lg backdrop:bg-black/50 backdrop:backdrop-blur-[2px] data-closing:backdrop:bg-transparent motion-safe:backdrop:transition-colors starting:backdrop:bg-transparent",
94
+ baseClasses({ shape }),
95
+ className,
96
+ )}
97
+ transition:transition
74
98
  {...mergeProps(restProps, {
75
- onintrostart: () => {
76
- if (ref) {
77
- ref.classList.remove("backdrop-out");
78
- ref.classList.add("backdrop-in");
79
- }
80
- },
81
- onoutrostart: () => {
82
- if (ref) {
83
- ref.classList.remove("backdrop-in");
84
- ref.classList.add("backdrop-out");
85
- }
86
- },
99
+ oncancel,
100
+ onintrostart: () => el?.removeAttribute("data-closing"),
101
+ onoutrostart: () => el?.setAttribute("data-closing", ""),
87
102
  })}
88
103
  >
89
104
  {@render children()}
90
105
  </dialog>
91
106
  {/if}
92
-
93
- <style>@layer components {
94
- .backdrop-in::backdrop {
95
- animation: backdrop-in 150ms forwards;
96
- }
97
-
98
- .backdrop-out::backdrop {
99
- animation: backdrop-out 150ms forwards;
100
- }
101
-
102
- @keyframes backdrop-in {
103
- from {
104
- background: rgba(0, 0, 0, 0);
105
- backdrop-filter: blur(0px);
106
- }
107
- to {
108
- background: rgba(0, 0, 0, 0.5);
109
- backdrop-filter: blur(2px);
110
- }
111
- }
112
-
113
- @keyframes backdrop-out {
114
- from {
115
- background: rgba(0, 0, 0, 0.5);
116
- backdrop-filter: blur(2px);
117
- }
118
- to {
119
- background: rgba(0, 0, 0, 0);
120
- backdrop-filter: blur(0px);
121
- }
122
- }
123
- }</style>
@@ -1,11 +1,14 @@
1
- import type { ClassName } from "../utils.js";
1
+ import { type ShapeProp } from "../base.js";
2
+ import { type ClassName } from "../utils.js";
2
3
  import type { Snippet } from "svelte";
3
4
  import type { HTMLDialogAttributes } from "svelte/elements";
5
+ import { type TransitionConfig } from "svelte/transition";
4
6
  export type ModalProps = {
5
- ref?: HTMLDialogElement;
7
+ el?: HTMLDialogElement;
6
8
  onCancel?: () => void;
9
+ transition?: (node: Element) => TransitionConfig;
7
10
  children: Snippet;
8
- } & ClassName & HTMLDialogAttributes;
9
- declare const Modal: import("svelte").Component<ModalProps, {}, "open" | "ref">;
11
+ } & ClassName & ShapeProp & Omit<HTMLDialogAttributes, "class">;
12
+ declare const Modal: import("svelte").Component<ModalProps, {}, "el" | "open">;
10
13
  type Modal = ReturnType<typeof Modal>;
11
14
  export default Modal;
@@ -0,0 +1,2 @@
1
+ export { default as NavGroup, type NavGroupProps } from "./nav-group.svelte";
2
+ export { default as NavItem, type NavItemProps } from "./nav-item.svelte";
@@ -0,0 +1,2 @@
1
+ export { default as NavGroup } from "./nav-group.svelte";
2
+ export { default as NavItem } from "./nav-item.svelte";
@@ -0,0 +1,40 @@
1
+ <script module lang="ts">
2
+ import { cn, type ClassName, type ElementRef } from "../utils.js";
3
+ import type { Snippet } from "svelte";
4
+ import type { HTMLAttributes } from "svelte/elements";
5
+
6
+ export type NavGroupProps = {
7
+ label: string;
8
+ children: Snippet;
9
+ } & ElementRef &
10
+ ClassName &
11
+ Omit<HTMLAttributes<HTMLDivElement>, "class">;
12
+ </script>
13
+
14
+ <script lang="ts">
15
+ let {
16
+ label,
17
+ children,
18
+ el = $bindable(),
19
+ class: className,
20
+ ...restProps
21
+ }: NavGroupProps = $props();
22
+
23
+ const id = $props.id();
24
+ </script>
25
+
26
+ <div
27
+ bind:this={el}
28
+ role="group"
29
+ aria-labelledby="{id}-label"
30
+ class={cn("flex flex-col gap-0.5", className)}
31
+ {...restProps}
32
+ >
33
+ <div
34
+ id="{id}-label"
35
+ class="px-4 pt-4 pb-1 text-xs font-medium tracking-wide text-m3-on-surface-variant uppercase"
36
+ >
37
+ {label}
38
+ </div>
39
+ {@render children()}
40
+ </div>
@@ -0,0 +1,10 @@
1
+ import { type ClassName, type ElementRef } from "../utils.js";
2
+ import type { Snippet } from "svelte";
3
+ import type { HTMLAttributes } from "svelte/elements";
4
+ export type NavGroupProps = {
5
+ label: string;
6
+ children: Snippet;
7
+ } & ElementRef & ClassName & Omit<HTMLAttributes<HTMLDivElement>, "class">;
8
+ declare const NavGroup: import("svelte").Component<NavGroupProps, {}, "el">;
9
+ type NavGroup = ReturnType<typeof NavGroup>;
10
+ export default NavGroup;
@@ -0,0 +1,65 @@
1
+ <script module lang="ts">
2
+ import {
3
+ baseClasses,
4
+ type ShapeProp,
5
+ type Size,
6
+ type SizeProp,
7
+ } from "../base.js";
8
+ import { cn, type ClassName, type ElementRef } from "../utils.js";
9
+ import type { LucideIcon } from "@lucide/svelte";
10
+ import type { Snippet } from "svelte";
11
+ import type { HTMLAnchorAttributes } from "svelte/elements";
12
+
13
+ export type NavItemProps = {
14
+ href: string;
15
+ current?: boolean;
16
+ icon?: LucideIcon;
17
+ children: Snippet;
18
+ } & ElementRef &
19
+ ClassName &
20
+ ShapeProp &
21
+ SizeProp &
22
+ Omit<HTMLAnchorAttributes, "href">;
23
+
24
+ const SIZES: Record<Size, string> = {
25
+ xs: "h-7 gap-2 px-3 text-xs",
26
+ sm: "h-9 gap-3 px-4 text-sm",
27
+ md: "h-11 gap-3 px-4 text-sm",
28
+ lg: "h-13 gap-3 px-4 text-base",
29
+ xl: "h-15 gap-4 px-5 text-base",
30
+ };
31
+ </script>
32
+
33
+ <script lang="ts">
34
+ let {
35
+ href,
36
+ current = false,
37
+ icon: Icon,
38
+ children,
39
+ el = $bindable(),
40
+ class: className,
41
+ shape = "full",
42
+ size = "md",
43
+ ...restProps
44
+ }: NavItemProps = $props();
45
+ </script>
46
+
47
+ <!-- eslint-disable svelte/no-navigation-without-resolve -->
48
+ <a
49
+ bind:this={el}
50
+ {href}
51
+ aria-current={current ? "page" : undefined}
52
+ class={cn(
53
+ baseClasses({ shape }),
54
+ SIZES[size],
55
+ "flex shrink-0 items-center text-m3-on-surface-variant hover:bg-m3-on-surface/8 focus-visible:outline-3 focus-visible:outline-offset-2 focus-visible:outline-m3-secondary aria-[current=page]:bg-m3-secondary-container aria-[current=page]:font-medium aria-[current=page]:text-m3-on-secondary-container",
56
+ className,
57
+ )}
58
+ {...restProps}
59
+ >
60
+ {#if Icon}
61
+ <Icon class="size-[1.25em] shrink-0" />
62
+ {/if}
63
+ {@render children()}
64
+ </a>
65
+ <!-- eslint-enable svelte/no-navigation-without-resolve -->
@@ -0,0 +1,14 @@
1
+ import { type ShapeProp, type SizeProp } from "../base.js";
2
+ import { type ClassName, type ElementRef } from "../utils.js";
3
+ import type { LucideIcon } from "@lucide/svelte";
4
+ import type { Snippet } from "svelte";
5
+ import type { HTMLAnchorAttributes } from "svelte/elements";
6
+ export type NavItemProps = {
7
+ href: string;
8
+ current?: boolean;
9
+ icon?: LucideIcon;
10
+ children: Snippet;
11
+ } & ElementRef & ClassName & ShapeProp & SizeProp & Omit<HTMLAnchorAttributes, "href">;
12
+ declare const NavItem: import("svelte").Component<NavItemProps, {}, "el">;
13
+ type NavItem = ReturnType<typeof NavItem>;
14
+ export default NavItem;
@@ -1 +1 @@
1
- export { default as Popover, type PopoverProps } from "./popover.svelte";
1
+ export { default as Popover, type PopoverPlacement, type PopoverProps, } from "./popover.svelte";
@@ -1 +1 @@
1
- export { default as Popover } from "./popover.svelte";
1
+ export { default as Popover, } from "./popover.svelte";