svelte-comp 1.2.5 → 1.2.6

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 (84) hide show
  1. package/README.md +1 -1
  2. package/package.json +1 -1
  3. package/dist/App.svelte +0 -551
  4. package/dist/App.svelte.d.ts +0 -3
  5. package/dist/Container.svelte +0 -60
  6. package/dist/Container.svelte.d.ts +0 -12
  7. package/dist/app.css +0 -235
  8. package/dist/index.d.ts +0 -5
  9. package/dist/index.js +0 -6
  10. package/dist/lang.d.ts +0 -1081
  11. package/dist/lang.js +0 -1096
  12. package/dist/lib/Accordion.svelte +0 -155
  13. package/dist/lib/Accordion.svelte.d.ts +0 -40
  14. package/dist/lib/Button.svelte +0 -170
  15. package/dist/lib/Button.svelte.d.ts +0 -53
  16. package/dist/lib/Card.svelte +0 -103
  17. package/dist/lib/Card.svelte.d.ts +0 -42
  18. package/dist/lib/Carousel.svelte +0 -293
  19. package/dist/lib/Carousel.svelte.d.ts +0 -13
  20. package/dist/lib/CheckBox.svelte +0 -210
  21. package/dist/lib/CheckBox.svelte.d.ts +0 -53
  22. package/dist/lib/CodeView.svelte +0 -307
  23. package/dist/lib/CodeView.svelte.d.ts +0 -64
  24. package/dist/lib/ColorPicker.svelte +0 -161
  25. package/dist/lib/ColorPicker.svelte.d.ts +0 -40
  26. package/dist/lib/DatePicker.svelte +0 -170
  27. package/dist/lib/DatePicker.svelte.d.ts +0 -53
  28. package/dist/lib/Dialog.svelte +0 -235
  29. package/dist/lib/Dialog.svelte.d.ts +0 -58
  30. package/dist/lib/Field.svelte +0 -299
  31. package/dist/lib/Field.svelte.d.ts +0 -8
  32. package/dist/lib/FilePicker.svelte +0 -241
  33. package/dist/lib/FilePicker.svelte.d.ts +0 -52
  34. package/dist/lib/Form.svelte +0 -438
  35. package/dist/lib/Form.svelte.d.ts +0 -20
  36. package/dist/lib/Hamburger.svelte +0 -211
  37. package/dist/lib/Hamburger.svelte.d.ts +0 -52
  38. package/dist/lib/Menu.svelte +0 -623
  39. package/dist/lib/Menu.svelte.d.ts +0 -33
  40. package/dist/lib/PaginatedCard.svelte +0 -73
  41. package/dist/lib/PaginatedCard.svelte.d.ts +0 -11
  42. package/dist/lib/Pagination.svelte +0 -119
  43. package/dist/lib/Pagination.svelte.d.ts +0 -9
  44. package/dist/lib/PrimaryColorSelect.svelte +0 -113
  45. package/dist/lib/PrimaryColorSelect.svelte.d.ts +0 -9
  46. package/dist/lib/ProgressBar.svelte +0 -141
  47. package/dist/lib/ProgressBar.svelte.d.ts +0 -48
  48. package/dist/lib/ProgressCircle.svelte +0 -192
  49. package/dist/lib/ProgressCircle.svelte.d.ts +0 -39
  50. package/dist/lib/Radio.svelte +0 -189
  51. package/dist/lib/Radio.svelte.d.ts +0 -55
  52. package/dist/lib/SearchInput.svelte +0 -106
  53. package/dist/lib/SearchInput.svelte.d.ts +0 -13
  54. package/dist/lib/Select.svelte +0 -524
  55. package/dist/lib/Select.svelte.d.ts +0 -21
  56. package/dist/lib/Slider.svelte +0 -253
  57. package/dist/lib/Slider.svelte.d.ts +0 -56
  58. package/dist/lib/Splitter.svelte +0 -150
  59. package/dist/lib/Splitter.svelte.d.ts +0 -43
  60. package/dist/lib/Switch.svelte +0 -167
  61. package/dist/lib/Switch.svelte.d.ts +0 -42
  62. package/dist/lib/Table.svelte +0 -299
  63. package/dist/lib/Table.svelte.d.ts +0 -17
  64. package/dist/lib/Tabs.svelte +0 -213
  65. package/dist/lib/Tabs.svelte.d.ts +0 -48
  66. package/dist/lib/ThemeToggle.svelte +0 -127
  67. package/dist/lib/ThemeToggle.svelte.d.ts +0 -32
  68. package/dist/lib/TimePicker.svelte +0 -269
  69. package/dist/lib/TimePicker.svelte.d.ts +0 -48
  70. package/dist/lib/Toast.svelte +0 -226
  71. package/dist/lib/Toast.svelte.d.ts +0 -14
  72. package/dist/lib/Tooltip.svelte +0 -110
  73. package/dist/lib/Tooltip.svelte.d.ts +0 -40
  74. package/dist/lib/index.d.ts +0 -32
  75. package/dist/lib/index.js +0 -33
  76. package/dist/lib/lang.d.ts +0 -158
  77. package/dist/lib/lang.js +0 -150
  78. package/dist/lib/types/index.d.ts +0 -111
  79. package/dist/lib/types/index.js +0 -26
  80. package/dist/main.d.ts +0 -3
  81. package/dist/main.js +0 -7
  82. package/dist/styles.css +0 -232
  83. package/dist/utils/index.d.ts +0 -34
  84. package/dist/utils/index.js +0 -268
@@ -1,52 +0,0 @@
1
- /**
2
- * @component Hamburger
3
- * @description Off-canvas navigation drawer controlled by a floating hamburger button.
4
- *
5
- * @prop menuItems {Item[]} - Menu entries rendered in the drawer
6
- * @default []
7
- *
8
- * @prop activeItem {string} - ID of the currently active item
9
- * @default ""
10
- *
11
- * @prop header {Snippet} - Custom content rendered above the menu
12
- *
13
- * @prop footer {Snippet} - Custom content rendered below the menu
14
- *
15
- * @prop closeOnSelect {boolean} - Automatically closes after selecting an item
16
- * @default true
17
- *
18
- * @prop onSelect {(id: string) => void} - Fired when a menu item is chosen
19
- *
20
- * @prop onOpenChange {(v: boolean) => void} - Fired when open state changes in controlled mode
21
- *
22
- * @prop pressed {boolean} - Controlled open state
23
- *
24
- * @prop class {string} - Extra classes applied to the trigger button
25
- * @default ""
26
- *
27
- * @prop width {number | string} - Drawer width (px or CSS value)
28
- * @default 300
29
- *
30
- * @note Clicking outside the panel or pressing `Escape` closes the drawer.
31
- * @note Focus moves to the first interactive element inside the panel, is trapped while open, and returns to the trigger on close.
32
- * @note In controlled mode (`pressed` is defined), state changes are requested via `onOpenChange(open)`.
33
- * @note When `menuItems` is empty, a "No items" placeholder is shown.
34
- * @note The drawer uses `role=\"dialog\"` and `aria-modal=\"true\"`; the trigger reflects state via `aria-expanded`.
35
- */
36
- import type { Snippet } from "svelte";
37
- import type { Item } from "./types";
38
- type Props = {
39
- menuItems?: Item[];
40
- activeItem?: string;
41
- header?: Snippet;
42
- footer?: Snippet;
43
- closeOnSelect?: boolean;
44
- onSelect?: (id: string) => void;
45
- onOpenChange?: (v: boolean) => void;
46
- pressed?: boolean;
47
- class?: string;
48
- width?: number | string;
49
- };
50
- declare const Hamburger: import("svelte").Component<Props, {}, "">;
51
- type Hamburger = ReturnType<typeof Hamburger>;
52
- export default Hamburger;
@@ -1,623 +0,0 @@
1
- <!-- src/lib/Menu.svelte -->
2
- <script lang="ts">
3
- /**
4
- * @component Menu
5
- * @description A dropdown menu bar component with hover and click interactions.
6
- *
7
- * @prop menus {MenuItem[]} - Menu definitions with actions
8
- * @default []
9
- *
10
- * @prop onSelect {(menu: string, action: MenuAction) => void} - Fired when an action is chosen
11
- * @default () => {}
12
- *
13
- * @prop class {string} - Extra classes applied to the menu bar
14
- * @default ""
15
- *
16
- * @prop sz {SizeKey} - Size preset for spacing and text
17
- * @options xs|sm|md|lg|xl
18
- * @default sm
19
- *
20
- * @note Fully keyboard-safe for focus and mouse interactions.
21
- * @note Submenus open on hover when another menu is already open.
22
- * @note Actions that match size keys (`xs`, `sm`, `md`, `lg`, `xl`) are automatically highlighted to reflect the current UI size.
23
- * @note Uses the same CSS variable architecture as Tabs for consistent look across components.
24
- * @note No slots; fully controlled via the `menus` structure and `onSelect`.
25
- */
26
- import type { SizeKey, MenuItem, MenuAction } from "./types";
27
- import { TEXT } from "./types";
28
- import { cx } from "../utils";
29
-
30
- type Props = {
31
- menus?: MenuItem[];
32
- onSelect?: (menu: string, action: MenuAction) => void;
33
- class?: string;
34
- sz?: SizeKey;
35
- };
36
-
37
- let {
38
- menus = [],
39
- onSelect = () => {},
40
- class: externalClass = "",
41
- sz = "sm",
42
- }: Props = $props();
43
-
44
- let open = $state<string>("");
45
- let openSub = $state<string>("");
46
- let activeIndex = $state(-1);
47
- let activeSubIndex = $state(-1);
48
-
49
- // Refs for focus control
50
- let triggerRefs = $state<Record<string, HTMLButtonElement>>({});
51
- let menuRefs = $state<Record<string, HTMLDivElement>>({});
52
- let itemRefs = $state<Record<string, HTMLButtonElement>>({});
53
- let subItemRefs = $state<Record<string, HTMLButtonElement>>({});
54
-
55
- // Positioning
56
- let menuTop = $state(0);
57
- let menuLeft = $state(0);
58
-
59
- let subMenuRefs = $state<Record<string, HTMLDivElement>>({});
60
- let subMenuTop = $state(0);
61
- let subMenuLeft = $state(0);
62
-
63
- const sizes: Record<SizeKey, string> = {
64
- xs: "h-7 px-3",
65
- sm: "h-8 px-3",
66
- md: "h-9 px-4",
67
- lg: "h-10 px-4",
68
- xl: "h-11 px-5",
69
- };
70
-
71
- const navBase =
72
- "flex items-stretch pl-2 gap-1 border-b relative z-10 bg-[var(--color-bg-surface)] text-[var(--color-text-default)] border-[var(--border-color-default)]";
73
-
74
- const subMenuGutter = 8;
75
-
76
- const topButtonBase =
77
- "px-4 rounded-xs leading-none transition-colors outline-none focus-visible:shadow-[inset_0_0_0_2px_var(--border-color-focus)]";
78
-
79
- const topButtonActive =
80
- "bg-[var(--color-bg-muted)] text-[var(--color-text-default)]";
81
- const topButtonIdle =
82
- "hover:bg-[var(--color-bg-muted)] text-[var(--color-text-default)]";
83
-
84
- const menuStyle = $derived(
85
- `position:fixed; top:${menuTop}px; left:${menuLeft}px; width:max-content; max-width:calc(100vw - 16px);`
86
- );
87
- const subMenuStyle = $derived(
88
- `position:fixed; top:${subMenuTop}px; left:${subMenuLeft}px; width:max-content; max-width:calc(100vw - 16px);`
89
- );
90
-
91
- const textCls = $derived(TEXT[sz]);
92
- const hotkeyColCls = "flex items-center shrink-0";
93
-
94
- const navCls = $derived(cx(navBase, sizes[sz], textCls, externalClass));
95
- const topBtnBaseCls = $derived(cx(topButtonBase, sizes[sz], textCls));
96
-
97
- function actionText(a: MenuAction) {
98
- if (typeof a === "string") return a;
99
- return a.label;
100
- }
101
-
102
- function actionId(a: MenuAction) {
103
- if (typeof a === "string") return a;
104
- return a.id ?? a.label ?? "";
105
- }
106
-
107
- function actionShortcut(a: MenuAction) {
108
- return typeof a === "string" ? "" : (a.shortcut ?? "");
109
- }
110
-
111
- function isSeparator(
112
- a: MenuAction
113
- ): a is Exclude<MenuAction, string> & { type: "separator" } {
114
- return typeof a !== "string" && "type" in a && a.type === "separator";
115
- }
116
-
117
- function hasSubmenu(
118
- a: MenuAction
119
- ): a is Exclude<MenuAction, string> & { submenu: MenuAction[] } {
120
- return (
121
- typeof a !== "string" && Array.isArray(a.submenu) && a.submenu.length > 0
122
- );
123
- }
124
-
125
- function actionKey(a: MenuAction, idx: number) {
126
- const id = actionId(a);
127
- return id || `__action-${idx}`;
128
- }
129
-
130
- function select(menu: string, action: MenuAction) {
131
- closeMenus();
132
- onSelect(menu, action);
133
- }
134
-
135
- function closeMenus() {
136
- open = "";
137
- openSub = "";
138
- activeIndex = -1;
139
- activeSubIndex = -1;
140
- }
141
-
142
- // Positioning dropdown
143
- function updateMenuPosition(
144
- triggerEl: HTMLElement,
145
- menuEl?: HTMLElement | null
146
- ) {
147
- const rect = triggerEl.getBoundingClientRect();
148
- const menuWidth = Math.min(
149
- menuEl?.getBoundingClientRect().width ?? rect.width,
150
- window.innerWidth - 16
151
- );
152
- const spaceRight = window.innerWidth - rect.left;
153
- const spaceLeft = rect.right;
154
- const alignRight = spaceRight < menuWidth && spaceLeft > spaceRight;
155
- const viewportLeft = window.scrollX;
156
- const viewportRight = window.scrollX + window.innerWidth;
157
-
158
- menuTop = rect.bottom + window.scrollY;
159
- const targetLeft = alignRight
160
- ? rect.right + window.scrollX - menuWidth
161
- : rect.left + window.scrollX;
162
- const maxLeft = viewportRight - menuWidth;
163
- menuLeft = Math.max(viewportLeft, Math.min(targetLeft, maxLeft));
164
- }
165
-
166
- function updateSubMenuPosition(
167
- parentItemEl: HTMLElement,
168
- subMenuEl?: HTMLElement | null
169
- ) {
170
- const rect = parentItemEl.getBoundingClientRect();
171
- const subRect = subMenuEl?.getBoundingClientRect();
172
- const subWidth = Math.min(
173
- subRect?.width ?? rect.width,
174
- window.innerWidth - 16
175
- );
176
- const spaceRight = window.innerWidth - rect.right;
177
- const spaceLeft = rect.left;
178
- const shouldFlipLeft = spaceRight < subWidth && spaceLeft > spaceRight;
179
-
180
- subMenuTop = rect.top + window.scrollY;
181
- const viewportLeft = window.scrollX;
182
- const viewportRight = window.scrollX + window.innerWidth;
183
- const targetLeft = shouldFlipLeft
184
- ? rect.left + window.scrollX - subWidth - subMenuGutter
185
- : rect.right + window.scrollX + subMenuGutter;
186
- const maxLeft = viewportRight - subWidth - subMenuGutter;
187
- subMenuLeft = Math.max(viewportLeft, Math.min(targetLeft, maxLeft));
188
- }
189
-
190
- function firstActionIndex(actions: MenuAction[]) {
191
- return actions.findIndex((a) => !isSeparator(a));
192
- }
193
-
194
- function nextActionIndex(actions: MenuAction[], current: number) {
195
- if (!actions.length) return -1;
196
- let idx = current;
197
- for (let i = 0; i < actions.length; i++) {
198
- idx = (idx + 1 + actions.length) % actions.length;
199
- if (!isSeparator(actions[idx])) return idx;
200
- }
201
- return current;
202
- }
203
-
204
- function prevActionIndex(actions: MenuAction[], current: number) {
205
- if (!actions.length) return -1;
206
- let idx = current;
207
- for (let i = 0; i < actions.length; i++) {
208
- idx = (idx - 1 + actions.length) % actions.length;
209
- if (!isSeparator(actions[idx])) return idx;
210
- }
211
- return current;
212
- }
213
-
214
- function focusMenuAction(menuItem: MenuItem, index: number) {
215
- if (index < 0 || index >= menuItem.actions.length) return;
216
- const action = menuItem.actions[index];
217
- if (!action || isSeparator(action)) return;
218
- if (!hasSubmenu(action) || openSub !== actionId(action)) {
219
- openSub = "";
220
- activeSubIndex = -1;
221
- }
222
- activeIndex = index;
223
- queueMicrotask(() => {
224
- if (open === menuItem.name) {
225
- itemRefs[actionId(action)]?.focus();
226
- }
227
- });
228
- }
229
-
230
- function focusSubAction(parentAction: MenuAction, index: number) {
231
- if (!hasSubmenu(parentAction)) return;
232
- if (index < 0 || index >= parentAction.submenu.length) return;
233
- const subAction = parentAction.submenu[index];
234
- if (!subAction || isSeparator(subAction)) return;
235
- activeSubIndex = index;
236
- queueMicrotask(() => {
237
- if (openSub === actionId(parentAction)) {
238
- subItemRefs[actionId(subAction)]?.focus();
239
- }
240
- });
241
- }
242
-
243
- function openMenu(menuItem: MenuItem, focusFirst = false) {
244
- open = menuItem.name;
245
- openSub = "";
246
- activeSubIndex = -1;
247
- const firstIndex = focusFirst ? firstActionIndex(menuItem.actions) : -1;
248
- activeIndex = firstIndex;
249
- const triggerEl = triggerRefs[menuItem.name];
250
- if (triggerEl) {
251
- updateMenuPosition(triggerEl, menuRefs[menuItem.name]);
252
- }
253
- if (focusFirst && firstIndex !== -1) {
254
- focusMenuAction(menuItem, firstIndex);
255
- }
256
- }
257
-
258
- function openSubMenu(parentAction: MenuAction, focusFirst = false) {
259
- if (!hasSubmenu(parentAction)) return;
260
- openSub = actionId(parentAction);
261
- const parentEl = itemRefs[actionId(parentAction)];
262
- if (parentEl) {
263
- updateSubMenuPosition(parentEl, subMenuRefs[actionId(parentAction)]);
264
- }
265
- const firstIndex = focusFirst ? firstActionIndex(parentAction.submenu) : -1;
266
- activeSubIndex = firstIndex;
267
- if (focusFirst && firstIndex !== -1) {
268
- focusSubAction(parentAction, firstIndex);
269
- }
270
- }
271
-
272
- // Keyboard navigation
273
- function handleTopLevelKeydown(
274
- e: KeyboardEvent,
275
- menuItem: MenuItem,
276
- index: number
277
- ) {
278
- if (e.key === "Enter" || e.key === " " || e.key === "ArrowDown") {
279
- e.preventDefault();
280
- openMenu(menuItem, true);
281
- } else if (e.key === "ArrowRight") {
282
- e.preventDefault();
283
- const nextIndex = (index + 1) % menus.length;
284
- triggerRefs[menus[nextIndex].name]?.focus();
285
- } else if (e.key === "ArrowLeft") {
286
- e.preventDefault();
287
- const prevIndex = (index - 1 + menus.length) % menus.length;
288
- triggerRefs[menus[prevIndex].name]?.focus();
289
- }
290
- }
291
-
292
- function handleMenuKeydown(e: KeyboardEvent, menuItem: MenuItem) {
293
- e.stopPropagation();
294
- if (!open) return;
295
-
296
- const actions = menuItem.actions;
297
- const firstIndex = firstActionIndex(actions);
298
- if (firstIndex === -1) return;
299
- const currentIndex = activeIndex === -1 ? firstIndex : activeIndex;
300
-
301
- if (e.key === "Escape") {
302
- e.preventDefault();
303
- closeMenus();
304
- triggerRefs[menuItem.name]?.focus();
305
- } else if (e.key === "ArrowDown") {
306
- e.preventDefault();
307
- const next = nextActionIndex(actions, currentIndex);
308
- focusMenuAction(menuItem, next);
309
- } else if (e.key === "ArrowUp") {
310
- e.preventDefault();
311
- const prev = prevActionIndex(actions, currentIndex);
312
- focusMenuAction(menuItem, prev);
313
- } else if (e.key === "ArrowRight") {
314
- e.preventDefault();
315
- const action = actions[currentIndex];
316
- if (action && hasSubmenu(action)) {
317
- openSubMenu(action, true);
318
- }
319
- } else if (e.key === "ArrowLeft" && openSub) {
320
- e.preventDefault();
321
- openSub = "";
322
- activeSubIndex = -1;
323
- focusMenuAction(menuItem, currentIndex);
324
- } else if (e.key === "Enter" || e.key === " ") {
325
- e.preventDefault();
326
- const action = actions[currentIndex];
327
- if (action) {
328
- if (hasSubmenu(action)) {
329
- openSubMenu(action, true);
330
- } else {
331
- select(menuItem.name, action);
332
- }
333
- }
334
- } else if (e.key === "Tab") {
335
- e.preventDefault();
336
- const target = e.shiftKey
337
- ? prevActionIndex(actions, currentIndex)
338
- : nextActionIndex(actions, currentIndex);
339
- focusMenuAction(menuItem, target);
340
- }
341
- }
342
-
343
- function handleSubMenuKeydown(
344
- e: KeyboardEvent,
345
- parentAction: MenuAction,
346
- menuName: string
347
- ) {
348
- e.stopPropagation();
349
- if (!openSub || !hasSubmenu(parentAction)) return;
350
-
351
- const subActions = parentAction.submenu;
352
- const firstIndex = firstActionIndex(subActions);
353
- if (firstIndex === -1) return;
354
- const currentIndex = activeSubIndex === -1 ? firstIndex : activeSubIndex;
355
-
356
- if (e.key === "Escape") {
357
- e.preventDefault();
358
- openSub = "";
359
- activeSubIndex = -1;
360
- itemRefs[actionId(parentAction)]?.focus();
361
- } else if (e.key === "ArrowDown") {
362
- e.preventDefault();
363
- const next = nextActionIndex(subActions, currentIndex);
364
- focusSubAction(parentAction, next);
365
- } else if (e.key === "ArrowUp") {
366
- e.preventDefault();
367
- const prev = prevActionIndex(subActions, currentIndex);
368
- focusSubAction(parentAction, prev);
369
- } else if (e.key === "ArrowLeft") {
370
- e.preventDefault();
371
- openSub = "";
372
- activeSubIndex = -1;
373
- itemRefs[actionId(parentAction)]?.focus();
374
- } else if (e.key === "Enter" || e.key === " ") {
375
- e.preventDefault();
376
- const action = subActions[currentIndex];
377
- if (action) {
378
- select(menuName, action);
379
- }
380
- } else if (e.key === "Tab") {
381
- e.preventDefault();
382
- const target = e.shiftKey
383
- ? prevActionIndex(subActions, currentIndex)
384
- : nextActionIndex(subActions, currentIndex);
385
- focusSubAction(parentAction, target);
386
- }
387
- }
388
-
389
- // Position update
390
- $effect(() => {
391
- if (open) {
392
- const triggerEl = triggerRefs[open];
393
- if (triggerEl) {
394
- updateMenuPosition(triggerEl, menuRefs[open]);
395
-
396
- const handleScrollResize = () => {
397
- updateMenuPosition(triggerEl, menuRefs[open]);
398
- };
399
-
400
- window.addEventListener("scroll", handleScrollResize, true);
401
- window.addEventListener("resize", handleScrollResize);
402
-
403
- return () => {
404
- window.removeEventListener("scroll", handleScrollResize, true);
405
- window.removeEventListener("resize", handleScrollResize);
406
- };
407
- }
408
- }
409
- });
410
-
411
- $effect(() => {
412
- if (openSub) {
413
- const itemEl = itemRefs[openSub];
414
- const subEl = subMenuRefs[openSub];
415
- if (itemEl) {
416
- updateSubMenuPosition(itemEl, subEl);
417
-
418
- const handleScrollResize = () => {
419
- updateSubMenuPosition(itemEl, subMenuRefs[openSub]);
420
- };
421
-
422
- window.addEventListener("scroll", handleScrollResize, true);
423
- window.addEventListener("resize", handleScrollResize);
424
-
425
- return () => {
426
- window.removeEventListener("scroll", handleScrollResize, true);
427
- window.removeEventListener("resize", handleScrollResize);
428
- };
429
- }
430
- }
431
- });
432
- </script>
433
-
434
- <nav class={navCls} aria-label="Menu bar">
435
- {#each menus as menuItem, idx (menuItem.name)}
436
- <div role="group" class="relative inline-block overflow-visible">
437
- <button
438
- bind:this={triggerRefs[menuItem.name]}
439
- type="button"
440
- class={cx(
441
- topBtnBaseCls,
442
- open === menuItem.name ? topButtonActive : topButtonIdle
443
- )}
444
- aria-haspopup="menu"
445
- aria-expanded={open === menuItem.name}
446
- onmousedown={(e) => e.preventDefault()}
447
- onclick={() => {
448
- if (open === menuItem.name) {
449
- closeMenus();
450
- } else {
451
- openMenu(menuItem, true);
452
- }
453
- }}
454
- onmouseenter={() => {
455
- if (open && open !== menuItem.name) {
456
- openMenu(menuItem, true);
457
- }
458
- }}
459
- onkeydown={(e) => handleTopLevelKeydown(e, menuItem, idx)}
460
- >
461
- {menuItem.name}
462
- </button>
463
- </div>
464
- {/each}
465
- </nav>
466
-
467
- <!-- Dropdown Menu -->
468
- {#if open}
469
- {#each menus as menuItem (menuItem.name)}
470
- {#if open === menuItem.name}
471
- <!-- Overlay to close -->
472
- <div
473
- role="presentation"
474
- tabindex="-1"
475
- class="fixed inset-0 z-40"
476
- onmousedown={closeMenus}
477
- ></div>
478
-
479
- <!-- Main Menu -->
480
- <div
481
- bind:this={menuRefs[menuItem.name]}
482
- class={cx(
483
- "fixed z-50 min-w-44 p-2 rounded-xs shadow-[0_2px_4px_var(--shadow-color)] ",
484
- "border border-[var(--border-color-default)] bg-[var(--color-bg-surface)]"
485
- )}
486
- style={menuStyle}
487
- role="menu"
488
- tabindex="-1"
489
- onkeydown={(e) => handleMenuKeydown(e, menuItem)}
490
- >
491
- {#each menuItem.actions as action, i (actionKey(action, i))}
492
- {#if isSeparator(action)}
493
- <div
494
- class="my-1 mx-1 border-t border-[var(--border-color-default)]"
495
- role="separator"
496
- ></div>
497
- {:else}
498
- <div class="relative">
499
- <button
500
- bind:this={itemRefs[actionId(action)]}
501
- type="button"
502
- role="menuitem"
503
- class={cx(
504
- "relative text-left rounded-xs transition-colors outline-none px-1.5 py-0.5 my-1 mr-1 min-w-full flex items-center",
505
- "gap-3 hover:bg-[var(--color-bg-muted)] focus-visible:bg-[var(--color-bg-muted)]",
506
- "focus-visible:shadow-[inset_0_0_0_2px_var(--border-color-focus)]",
507
- textCls
508
- )}
509
- onmousedown={(e) => e.preventDefault()}
510
- onclick={() => {
511
- if (!hasSubmenu(action)) {
512
- select(menuItem.name, action);
513
- } else {
514
- focusMenuAction(menuItem, i);
515
- openSubMenu(action, true);
516
- }
517
- }}
518
- onmouseenter={() => {
519
- activeIndex = i;
520
- if (hasSubmenu(action) && openSub !== actionId(action)) {
521
- openSubMenu(action);
522
- } else if (!hasSubmenu(action)) {
523
- openSub = "";
524
- activeSubIndex = -1;
525
- }
526
- }}
527
- onfocus={() => {
528
- focusMenuAction(menuItem, i);
529
- }}
530
- >
531
- <span class="flex items-center gap-2 flex-1 min-w-0">
532
- <span class="truncate">{actionText(action)}</span>
533
- </span>
534
-
535
- <span class="flex items-center shrink-0 ml-auto gap-1">
536
- {#if actionShortcut(action)}
537
- <span
538
- class={cx(
539
- "text-[var(--color-text-muted)] text-right",
540
- textCls
541
- )}
542
- >
543
- {actionShortcut(action)}
544
- </span>
545
- {/if}
546
-
547
- {#if hasSubmenu(action)}
548
- <span
549
- class={cx(
550
- "text-[var(--color-text-muted)] flex-shrink-0",
551
- textCls
552
- )}
553
- >
554
- &gt;
555
- </span>
556
- {/if}
557
- </span>
558
- </button>
559
-
560
- <!-- Sub Menu -->
561
- {#if hasSubmenu(action) && openSub === actionId(action)}
562
- <div
563
- bind:this={subMenuRefs[actionId(action)]}
564
- class={cx(
565
- "fixed z-50 min-w-44 p-2 rounded-xs shadow-[0_2px_4px_var(--shadow-color)]",
566
- "border border-[var(--border-color-default)] bg-[var(--color-bg-surface)]"
567
- )}
568
- style={subMenuStyle}
569
- role="menu"
570
- tabindex="0"
571
- onkeydown={(e) =>
572
- handleSubMenuKeydown(e, action, menuItem.name)}
573
- >
574
- {#each action.submenu as sub, j (actionKey(sub, j))}
575
- {#if isSeparator(sub)}
576
- <div
577
- class="my-1 mx-1 border-t border-[var(--border-color-default)]"
578
- role="separator"
579
- ></div>
580
- {:else}
581
- <button
582
- bind:this={subItemRefs[actionId(sub)]}
583
- type="button"
584
- role="menuitem"
585
- class={cx(
586
- "relative text-left rounded-xs transition-colors outline-none px-1.5 py-0.5",
587
- "my-1 mr-1 w-full flex items-center justify-between gap-3",
588
- "hover:bg-[var(--color-bg-muted)] focus-visible:bg-[var(--color-bg-muted)]",
589
- "focus-visible:shadow-[inset_0_0_0_2px_var(--border-color-focus)]",
590
- "decoration-[var(--color-text-default)]",
591
- textCls
592
- )}
593
- onmousedown={(e) => e.preventDefault()}
594
- onclick={() => select(menuItem.name, sub)}
595
- onmouseenter={() => (activeSubIndex = j)}
596
- onfocus={() => (activeSubIndex = j)}
597
- >
598
- <span class="flex items-center gap-2 flex-1 min-w-0">
599
- <span class="truncate">{actionText(sub)}</span>
600
- </span>
601
-
602
- <span class={hotkeyColCls}>
603
- {#if actionShortcut(sub)}
604
- <span
605
- class={cx(
606
- "text-[var(--color-text-muted)]",
607
- textCls
608
- )}>{actionShortcut(sub)}</span
609
- >
610
- {/if}
611
- </span>
612
- </button>
613
- {/if}
614
- {/each}
615
- </div>
616
- {/if}
617
- </div>
618
- {/if}
619
- {/each}
620
- </div>
621
- {/if}
622
- {/each}
623
- {/if}
@@ -1,33 +0,0 @@
1
- /**
2
- * @component Menu
3
- * @description A dropdown menu bar component with hover and click interactions.
4
- *
5
- * @prop menus {MenuItem[]} - Menu definitions with actions
6
- * @default []
7
- *
8
- * @prop onSelect {(menu: string, action: MenuAction) => void} - Fired when an action is chosen
9
- * @default () => {}
10
- *
11
- * @prop class {string} - Extra classes applied to the menu bar
12
- * @default ""
13
- *
14
- * @prop sz {SizeKey} - Size preset for spacing and text
15
- * @options xs|sm|md|lg|xl
16
- * @default sm
17
- *
18
- * @note Fully keyboard-safe for focus and mouse interactions.
19
- * @note Submenus open on hover when another menu is already open.
20
- * @note Actions that match size keys (`xs`, `sm`, `md`, `lg`, `xl`) are automatically highlighted to reflect the current UI size.
21
- * @note Uses the same CSS variable architecture as Tabs for consistent look across components.
22
- * @note No slots; fully controlled via the `menus` structure and `onSelect`.
23
- */
24
- import type { SizeKey, MenuItem, MenuAction } from "./types";
25
- type Props = {
26
- menus?: MenuItem[];
27
- onSelect?: (menu: string, action: MenuAction) => void;
28
- class?: string;
29
- sz?: SizeKey;
30
- };
31
- declare const Menu: import("svelte").Component<Props, {}, "">;
32
- type Menu = ReturnType<typeof Menu>;
33
- export default Menu;