@mrintel/villain-ui 0.3.0 → 0.7.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 (140) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +3490 -1296
  3. package/dist/components/buttons/Button.svelte +27 -33
  4. package/dist/components/buttons/Button.svelte.d.ts +4 -1
  5. package/dist/components/buttons/ButtonGroup.svelte +17 -30
  6. package/dist/components/buttons/FloatingActionButton.svelte +20 -44
  7. package/dist/components/buttons/FloatingActionButton.svelte.d.ts +2 -1
  8. package/dist/components/buttons/IconButton.svelte +23 -53
  9. package/dist/components/buttons/IconButton.svelte.d.ts +2 -1
  10. package/dist/components/buttons/LinkButton.svelte +24 -37
  11. package/dist/components/buttons/LinkButton.svelte.d.ts +4 -1
  12. package/dist/components/buttons/buttonClasses.d.ts +5 -0
  13. package/dist/components/buttons/buttonClasses.js +8 -3
  14. package/dist/components/cards/Card.svelte +54 -46
  15. package/dist/components/cards/Card.svelte.d.ts +9 -2
  16. package/dist/components/cards/Container.svelte +17 -33
  17. package/dist/components/cards/Divider.svelte +36 -52
  18. package/dist/components/cards/Divider.svelte.d.ts +2 -0
  19. package/dist/components/cards/Grid.svelte +55 -44
  20. package/dist/components/cards/Panel.svelte +18 -32
  21. package/dist/components/cards/Panel.svelte.d.ts +2 -1
  22. package/dist/components/cards/SectionHeader.svelte +24 -38
  23. package/dist/components/cards/SectionHeader.svelte.d.ts +1 -0
  24. package/dist/components/data/Avatar.svelte +48 -67
  25. package/dist/components/data/Badge.svelte +45 -32
  26. package/dist/components/data/Badge.svelte.d.ts +7 -1
  27. package/dist/components/data/CalendarGrid.svelte +433 -0
  28. package/dist/components/data/CalendarGrid.svelte.d.ts +25 -0
  29. package/dist/components/data/CalendarGrid.types.d.ts +7 -0
  30. package/dist/components/data/CalendarGrid.types.js +1 -0
  31. package/dist/components/data/CodeBlock.svelte +119 -121
  32. package/dist/components/data/CodeBlock.svelte.d.ts +8 -0
  33. package/dist/components/data/List.svelte +87 -64
  34. package/dist/components/data/List.svelte.d.ts +7 -0
  35. package/dist/components/data/Pagination.svelte +121 -123
  36. package/dist/components/data/Pagination.svelte.d.ts +5 -0
  37. package/dist/components/data/Sparkline.svelte +117 -0
  38. package/dist/components/data/Sparkline.svelte.d.ts +43 -0
  39. package/dist/components/data/Stat.svelte +92 -103
  40. package/dist/components/data/Table.svelte +443 -76
  41. package/dist/components/data/Table.svelte.d.ts +23 -2
  42. package/dist/components/data/Table.types.d.ts +14 -0
  43. package/dist/components/data/Table.types.js +1 -0
  44. package/dist/components/data/Tag.svelte +51 -53
  45. package/dist/components/data/Tag.svelte.d.ts +5 -1
  46. package/dist/components/data/index.d.ts +4 -0
  47. package/dist/components/data/index.js +2 -0
  48. package/dist/components/forms/Checkbox.svelte +39 -51
  49. package/dist/components/forms/Checkbox.svelte.d.ts +3 -1
  50. package/dist/components/forms/DatePicker.svelte +61 -0
  51. package/dist/components/forms/DatePicker.svelte.d.ts +15 -0
  52. package/dist/components/forms/DateTimePicker.svelte +63 -0
  53. package/dist/components/forms/DateTimePicker.svelte.d.ts +16 -0
  54. package/dist/components/forms/FileUpload.svelte +136 -164
  55. package/dist/components/forms/FileUpload.svelte.d.ts +1 -0
  56. package/dist/components/forms/Input.svelte +284 -57
  57. package/dist/components/forms/Input.svelte.d.ts +10 -3
  58. package/dist/components/forms/InputGroup.svelte +7 -7
  59. package/dist/components/forms/RadioGroup.svelte +77 -87
  60. package/dist/components/forms/RadioGroup.svelte.d.ts +3 -1
  61. package/dist/components/forms/RangeSlider.svelte +90 -116
  62. package/dist/components/forms/Select.svelte +106 -71
  63. package/dist/components/forms/Select.svelte.d.ts +3 -1
  64. package/dist/components/forms/Step.svelte +25 -0
  65. package/dist/components/forms/Step.svelte.d.ts +12 -0
  66. package/dist/components/forms/StepContext.d.ts +3 -0
  67. package/dist/components/forms/StepContext.js +5 -0
  68. package/dist/components/forms/Stepper.types.d.ts +37 -0
  69. package/dist/components/forms/Stepper.types.js +1 -0
  70. package/dist/components/forms/StepperForm.svelte +183 -0
  71. package/dist/components/forms/StepperForm.svelte.d.ts +17 -0
  72. package/dist/components/forms/Switch.svelte +44 -56
  73. package/dist/components/forms/Switch.svelte.d.ts +3 -1
  74. package/dist/components/forms/Textarea.svelte +52 -57
  75. package/dist/components/forms/Textarea.svelte.d.ts +3 -1
  76. package/dist/components/forms/TimePicker.svelte +63 -0
  77. package/dist/components/forms/TimePicker.svelte.d.ts +16 -0
  78. package/dist/components/forms/formClasses.d.ts +3 -0
  79. package/dist/components/forms/formClasses.js +3 -0
  80. package/dist/components/forms/index.d.ts +6 -0
  81. package/dist/components/forms/index.js +5 -0
  82. package/dist/components/navigation/Breadcrumbs.svelte +56 -59
  83. package/dist/components/navigation/Breadcrumbs.svelte.d.ts +1 -0
  84. package/dist/components/navigation/ContextMenu.svelte +133 -83
  85. package/dist/components/navigation/ContextMenu.svelte.d.ts +8 -1
  86. package/dist/components/navigation/DropdownMenu.svelte +139 -80
  87. package/dist/components/navigation/DropdownMenu.svelte.d.ts +8 -1
  88. package/dist/components/navigation/Menu.svelte +72 -48
  89. package/dist/components/navigation/Navbar.svelte +111 -32
  90. package/dist/components/navigation/Navbar.svelte.d.ts +6 -0
  91. package/dist/components/navigation/Sidebar.svelte +236 -35
  92. package/dist/components/navigation/Sidebar.svelte.d.ts +2 -0
  93. package/dist/components/navigation/Stepper.svelte +204 -0
  94. package/dist/components/navigation/Stepper.svelte.d.ts +34 -0
  95. package/dist/components/navigation/Tabs.svelte +86 -54
  96. package/dist/components/navigation/Tabs.svelte.d.ts +5 -1
  97. package/dist/components/navigation/index.d.ts +1 -0
  98. package/dist/components/navigation/index.js +1 -0
  99. package/dist/components/overlays/Alert.svelte +81 -99
  100. package/dist/components/overlays/Alert.svelte.d.ts +5 -1
  101. package/dist/components/overlays/CommandPalette.svelte +182 -217
  102. package/dist/components/overlays/Drawer.svelte +158 -167
  103. package/dist/components/overlays/Drawer.svelte.d.ts +3 -1
  104. package/dist/components/overlays/Dropdown.svelte +62 -30
  105. package/dist/components/overlays/Dropdown.svelte.d.ts +2 -0
  106. package/dist/components/overlays/Modal.svelte +125 -130
  107. package/dist/components/overlays/Modal.svelte.d.ts +3 -1
  108. package/dist/components/overlays/Popover.svelte +106 -131
  109. package/dist/components/overlays/ProgressBar.svelte +29 -45
  110. package/dist/components/overlays/SkeletonLoader.svelte +66 -82
  111. package/dist/components/overlays/Spinner.svelte +33 -43
  112. package/dist/components/overlays/Toast.svelte +111 -140
  113. package/dist/components/overlays/Toast.svelte.d.ts +3 -0
  114. package/dist/components/overlays/Tooltip.svelte +94 -115
  115. package/dist/components/overlays/Tooltip.svelte.d.ts +3 -1
  116. package/dist/components/typography/Code.svelte +10 -14
  117. package/dist/components/typography/Heading.svelte +15 -22
  118. package/dist/components/typography/Heading.svelte.d.ts +1 -0
  119. package/dist/components/typography/Text.svelte +21 -24
  120. package/dist/components/typography/Text.svelte.d.ts +2 -1
  121. package/dist/components/utilities/Accordion.svelte +54 -67
  122. package/dist/components/utilities/Accordion.svelte.d.ts +4 -1
  123. package/dist/components/utilities/Carousel.svelte +124 -152
  124. package/dist/components/utilities/Collapse.svelte +46 -60
  125. package/dist/components/utilities/Hero.svelte +42 -0
  126. package/dist/components/utilities/Hero.svelte.d.ts +10 -0
  127. package/dist/components/utilities/Portal.svelte +47 -72
  128. package/dist/components/utilities/ScrollArea.svelte +33 -41
  129. package/dist/components/utilities/SystemConsole.svelte +310 -0
  130. package/dist/components/utilities/SystemConsole.svelte.d.ts +20 -0
  131. package/dist/components/utilities/SystemInterface.svelte +726 -0
  132. package/dist/components/utilities/SystemInterface.svelte.d.ts +19 -0
  133. package/dist/components/utilities/index.d.ts +4 -0
  134. package/dist/components/utilities/index.js +3 -0
  135. package/dist/components/utilities/utilities.types.d.ts +46 -0
  136. package/dist/components/utilities/utilities.types.js +4 -0
  137. package/dist/index.d.ts +57 -5
  138. package/dist/index.js +5 -5
  139. package/dist/theme.css +2889 -218
  140. package/package.json +83 -76
@@ -1,167 +1,158 @@
1
- <script lang="ts">
2
- import type { Snippet } from 'svelte';
3
- import { createId } from '../../lib/internal/id.js';
4
- import ScrollArea from '../utilities/ScrollArea.svelte';
5
-
6
- interface Props {
7
- open?: boolean;
8
- side?: 'left' | 'right' | 'top' | 'bottom';
9
- size?: 'sm' | 'md' | 'lg';
10
- title?: string;
11
- closeOnBackdrop?: boolean;
12
- closeOnEscape?: boolean;
13
- children?: Snippet;
14
- footer?: Snippet;
15
- }
16
-
17
- let {
18
- open = $bindable(false),
19
- side = 'right',
20
- size = 'md',
21
- title,
22
- closeOnBackdrop = true,
23
- closeOnEscape = true,
24
- children,
25
- footer
26
- }: Props = $props();
27
-
28
- let drawerElement = $state<HTMLDivElement>();
29
- let previousFocus = $state<HTMLElement | null>(null);
30
-
31
- const titleId = createId('drawer-title');
32
-
33
- const sizeClasses = {
34
- left: {
35
- sm: 'max-w-[20rem]',
36
- md: 'max-w-[28rem]',
37
- lg: 'max-w-[36rem]'
38
- },
39
- right: {
40
- sm: 'max-w-[20rem]',
41
- md: 'max-w-[28rem]',
42
- lg: 'max-w-[36rem]'
43
- },
44
- top: {
45
- sm: 'max-h-[20rem]',
46
- md: 'max-h-[28rem]',
47
- lg: 'max-h-[36rem]'
48
- },
49
- bottom: {
50
- sm: 'max-h-[20rem]',
51
- md: 'max-h-[28rem]',
52
- lg: 'max-h-[36rem]'
53
- }
54
- };
55
-
56
- const positionClasses = {
57
- left: 'left-0 top-0 h-full',
58
- right: 'right-0 top-0 h-full',
59
- top: 'top-0 left-0 w-full',
60
- bottom: 'bottom-0 left-0 w-full'
61
- };
62
-
63
- const animationClasses = {
64
- left: 'animate-[slide-in-left_0.3s_var(--ease-luxe)]',
65
- right: 'animate-[slide-in-right_0.3s_var(--ease-luxe)]',
66
- top: 'animate-[slide-in-top_0.3s_var(--ease-luxe)]',
67
- bottom: 'animate-[slide-in-bottom_0.3s_var(--ease-luxe)]'
68
- };
69
-
70
- function handleClose() {
71
- open = false;
72
- }
73
-
74
- function handleBackdropClick(event: MouseEvent) {
75
- if (closeOnBackdrop && event.target === event.currentTarget) {
76
- handleClose();
77
- }
78
- }
79
-
80
- function handleEscape(event: KeyboardEvent) {
81
- if (closeOnEscape && event.key === 'Escape') {
82
- handleClose();
83
- }
84
- }
85
-
86
- $effect(() => {
87
- if (typeof document === 'undefined') return;
88
-
89
- if (open) {
90
- // Store previous focus
91
- previousFocus = document.activeElement as HTMLElement;
92
-
93
- // Prevent body scroll
94
- document.body.style.overflow = 'hidden';
95
-
96
- // Add event listeners
97
- document.addEventListener('keydown', handleEscape);
98
-
99
- // Focus first interactive element
100
- requestAnimationFrame(() => {
101
- const firstInteractive = drawerElement?.querySelector<HTMLElement>(
102
- 'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
103
- );
104
- firstInteractive?.focus();
105
- });
106
-
107
- return () => {
108
- // Restore body scroll
109
- document.body.style.overflow = '';
110
-
111
- // Remove event listeners
112
- document.removeEventListener('keydown', handleEscape);
113
-
114
- // Restore previous focus
115
- previousFocus?.focus();
116
- };
117
- }
118
- });
119
- </script>
120
-
121
- {#if open}
122
- <div
123
- class="fixed inset-0 z-50 bg-overlay backdrop-blur-md animate-[fade-in_0.2s_var(--ease-luxe)]"
124
- onclick={handleBackdropClick}
125
- role="presentation"
126
- >
127
- <div
128
- bind:this={drawerElement}
129
- class="glass-panel shadow-deep fixed {positionClasses[side]} {sizeClasses[side][size]} {animationClasses[side]} flex flex-col"
130
- role="dialog"
131
- aria-modal="true"
132
- aria-labelledby={title ? titleId : undefined}
133
- >
134
- {#if title}
135
- <div class="flex items-center justify-between p-6 border-b border-border">
136
- <h2 id={titleId} class="text-xl font-semibold text-text">
137
- {title}
138
- </h2>
139
- <button
140
- type="button"
141
- onclick={handleClose}
142
- class="text-text-soft hover:text-text transition-colors"
143
- aria-label="Close drawer"
144
- >
145
- <svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
146
- <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
147
- </svg>
148
- </button>
149
- </div>
150
- {/if}
151
-
152
- <div class="flex-1 overflow-hidden">
153
- <ScrollArea height="100%">
154
- <div class="p-6">
155
- {@render children?.()}
156
- </div>
157
- </ScrollArea>
158
- </div>
159
-
160
- {#if footer}
161
- <div class="flex items-center justify-end gap-3 p-6 border-t border-border">
162
- {@render footer?.()}
163
- </div>
164
- {/if}
165
- </div>
166
- </div>
167
- {/if}
1
+ <script lang="ts">import { createId } from '../../lib/internal/id.js';
2
+ import ScrollArea from '../utilities/ScrollArea.svelte';
3
+ let { open = $bindable(false), side = 'right', size = 'md', title, closeOnBackdrop = true, closeOnEscape = true, children, footer, iconBefore, class: className = '' } = $props();
4
+ let drawerElement = $state();
5
+ let previousFocus = $state(null);
6
+ const titleId = createId('drawer-title');
7
+ const focusableSelector = 'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"]), [contenteditable="true"], summary, details, audio[controls], video[controls]';
8
+ const sizeClasses = {
9
+ left: {
10
+ sm: 'max-w-[20rem]',
11
+ md: 'max-w-[28rem]',
12
+ lg: 'max-w-[36rem]'
13
+ },
14
+ right: {
15
+ sm: 'max-w-[20rem]',
16
+ md: 'max-w-[28rem]',
17
+ lg: 'max-w-[36rem]'
18
+ },
19
+ top: {
20
+ sm: 'max-h-[20rem]',
21
+ md: 'max-h-[28rem]',
22
+ lg: 'max-h-[36rem]'
23
+ },
24
+ bottom: {
25
+ sm: 'max-h-[20rem]',
26
+ md: 'max-h-[28rem]',
27
+ lg: 'max-h-[36rem]'
28
+ }
29
+ };
30
+ const positionClasses = {
31
+ left: 'left-0 top-0 h-full',
32
+ right: 'right-0 top-0 h-full',
33
+ top: 'top-0 left-0 w-full',
34
+ bottom: 'bottom-0 left-0 w-full'
35
+ };
36
+ const animationClasses = {
37
+ left: 'animate-[slide-in-left_0.3s_var(--ease-luxe)]',
38
+ right: 'animate-[slide-in-right_0.3s_var(--ease-luxe)]',
39
+ top: 'animate-[slide-in-top_0.3s_var(--ease-luxe)]',
40
+ bottom: 'animate-[slide-in-bottom_0.3s_var(--ease-luxe)]'
41
+ };
42
+ function handleClose() {
43
+ open = false;
44
+ }
45
+ function handleBackdropClick(event) {
46
+ if (closeOnBackdrop && event.target === event.currentTarget) {
47
+ handleClose();
48
+ }
49
+ }
50
+ function handleEscape(event) {
51
+ if (closeOnEscape && event.key === 'Escape') {
52
+ handleClose();
53
+ }
54
+ }
55
+ function handleFocusTrap(event) {
56
+ if (event.key !== 'Tab' || !drawerElement)
57
+ return;
58
+ const focusableElements = Array.from(drawerElement.querySelectorAll(focusableSelector));
59
+ if (focusableElements.length === 0)
60
+ return;
61
+ const firstFocusable = focusableElements[0];
62
+ const lastFocusable = focusableElements[focusableElements.length - 1];
63
+ if (event.shiftKey) {
64
+ // Shift+Tab: moving backwards
65
+ if (document.activeElement === firstFocusable) {
66
+ event.preventDefault();
67
+ lastFocusable.focus();
68
+ }
69
+ }
70
+ else {
71
+ // Tab: moving forwards
72
+ if (document.activeElement === lastFocusable) {
73
+ event.preventDefault();
74
+ firstFocusable.focus();
75
+ }
76
+ }
77
+ }
78
+ $effect(() => {
79
+ if (typeof document === 'undefined')
80
+ return;
81
+ if (open) {
82
+ // Store previous focus
83
+ previousFocus = document.activeElement;
84
+ // Prevent body scroll
85
+ document.body.style.overflow = 'hidden';
86
+ // Add event listeners
87
+ document.addEventListener('keydown', handleEscape);
88
+ document.addEventListener('keydown', handleFocusTrap);
89
+ // Focus first interactive element
90
+ requestAnimationFrame(() => {
91
+ const firstInteractive = drawerElement?.querySelector(focusableSelector);
92
+ firstInteractive?.focus();
93
+ });
94
+ return () => {
95
+ // Restore body scroll
96
+ document.body.style.overflow = '';
97
+ // Remove event listeners
98
+ document.removeEventListener('keydown', handleEscape);
99
+ document.removeEventListener('keydown', handleFocusTrap);
100
+ // Restore previous focus
101
+ previousFocus?.focus();
102
+ };
103
+ }
104
+ });
105
+ </script>
106
+
107
+ {#if open}
108
+ <div
109
+ class="fixed inset-0 z-[var(--z-50)] bg-overlay backdrop-blur-md animate-[fade-in_0.2s_var(--ease-luxe)]"
110
+ onclick={handleBackdropClick}
111
+ role="presentation"
112
+ >
113
+ <div
114
+ bind:this={drawerElement}
115
+ class="panel-floating shadow-deep fixed {positionClasses[side]} {sizeClasses[side][size]} {animationClasses[side]} {className} flex flex-col"
116
+ role="dialog"
117
+ aria-modal="true"
118
+ aria-labelledby={title ? titleId : undefined}
119
+ >
120
+ {#if title}
121
+ <div class="flex items-center justify-between p-8 border-b border-border">
122
+ <h2 id={titleId} class="text-xl font-semibold text-text flex items-center gap-3">
123
+ {#if iconBefore}
124
+ <span class="inline-flex items-center justify-center">
125
+ {@render iconBefore()}
126
+ </span>
127
+ {/if}
128
+ {title}
129
+ </h2>
130
+ <button
131
+ type="button"
132
+ onclick={handleClose}
133
+ class="text-text-soft hover:text-text transition-colors"
134
+ aria-label="Close drawer"
135
+ >
136
+ <svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
137
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
138
+ </svg>
139
+ </button>
140
+ </div>
141
+ {/if}
142
+
143
+ <div class="flex-1 overflow-hidden">
144
+ <ScrollArea height="100%">
145
+ <div class="p-8">
146
+ {@render children?.()}
147
+ </div>
148
+ </ScrollArea>
149
+ </div>
150
+
151
+ {#if footer}
152
+ <div class="flex items-center justify-end gap-4 p-8 border-t border-border">
153
+ {@render footer?.()}
154
+ </div>
155
+ {/if}
156
+ </div>
157
+ </div>
158
+ {/if}
@@ -1,5 +1,5 @@
1
1
  import type { Snippet } from 'svelte';
2
- interface Props {
2
+ export interface Props {
3
3
  open?: boolean;
4
4
  side?: 'left' | 'right' | 'top' | 'bottom';
5
5
  size?: 'sm' | 'md' | 'lg';
@@ -8,6 +8,8 @@ interface Props {
8
8
  closeOnEscape?: boolean;
9
9
  children?: Snippet;
10
10
  footer?: Snippet;
11
+ iconBefore?: Snippet;
12
+ class?: string;
11
13
  }
12
14
  declare const Drawer: import("svelte").Component<Props, {}, "open">;
13
15
  type Drawer = ReturnType<typeof Drawer>;
@@ -1,30 +1,62 @@
1
- <script lang="ts">
2
- import type { Snippet } from 'svelte';
3
-
4
- interface Props {
5
- open?: boolean;
6
- placement?: 'bottom-start' | 'bottom-end' | 'top-start' | 'top-end';
7
- children?: Snippet;
8
- }
9
-
10
- let {
11
- open = $bindable(false),
12
- placement = 'bottom-start',
13
- children
14
- }: Props = $props();
15
-
16
- const placementClasses = {
17
- 'bottom-start': 'top-full left-0 mt-2',
18
- 'bottom-end': 'top-full right-0 mt-2',
19
- 'top-start': 'bottom-full left-0 mb-2',
20
- 'top-end': 'bottom-full right-0 mb-2'
21
- };
22
- </script>
23
-
24
- {#if open}
25
- <div
26
- class="absolute {placementClasses[placement]} z-50 glass-panel rounded-[var(--radius-lg)] shadow-[var(--shadow-deep)] min-w-[12rem] animate-[fade-up_0.2s_var(--ease-luxe)]"
27
- >
28
- {@render children?.()}
29
- </div>
30
- {/if}
1
+ <script lang="ts">import { createId } from '../../lib/internal/id.js';
2
+ let { open = $bindable(false), placement = 'bottom-start', closeOnClickOutside = true, trigger, children } = $props();
3
+ let dropdownElement = $state();
4
+ let wrapperElement = $state();
5
+ const dropdownId = createId('dropdown');
6
+ const placementClasses = {
7
+ 'bottom-start': 'top-full left-0 mt-2',
8
+ 'bottom-end': 'top-full right-0 mt-2',
9
+ 'top-start': 'bottom-full left-0 mb-2',
10
+ 'top-end': 'bottom-full right-0 mb-2'
11
+ };
12
+ function toggleOpen() {
13
+ open = !open;
14
+ }
15
+ function handleClickOutside(event) {
16
+ if (closeOnClickOutside && dropdownElement && wrapperElement && !wrapperElement.contains(event.target)) {
17
+ open = false;
18
+ }
19
+ }
20
+ function handleEscape(event) {
21
+ if (event.key === 'Escape') {
22
+ open = false;
23
+ }
24
+ }
25
+ $effect(() => {
26
+ if (typeof document === 'undefined')
27
+ return;
28
+ if (open) {
29
+ document.addEventListener('click', handleClickOutside);
30
+ document.addEventListener('keydown', handleEscape);
31
+ return () => {
32
+ document.removeEventListener('click', handleClickOutside);
33
+ document.removeEventListener('keydown', handleEscape);
34
+ };
35
+ }
36
+ });
37
+ </script>
38
+
39
+ <div bind:this={wrapperElement} class="relative inline-block">
40
+ <!-- svelte-ignore a11y_no_static_element_interactions -->
41
+ <div
42
+ onclick={toggleOpen}
43
+ onkeydown={(e) => { if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); toggleOpen(); } }}
44
+ aria-haspopup="true"
45
+ aria-expanded={open}
46
+ aria-controls={open ? dropdownId : undefined}
47
+ class="inline-block"
48
+ >
49
+ {@render trigger?.()}
50
+ </div>
51
+
52
+ {#if open}
53
+ <div
54
+ bind:this={dropdownElement}
55
+ id={dropdownId}
56
+ class="absolute {placementClasses[placement]} z-[var(--z-50)] panel-floating rounded-[var(--radius-lg)] shadow-[var(--shadow-deep)] min-w-[12rem] animate-[fade-up_0.2s_var(--ease-luxe)]"
57
+ role="menu"
58
+ >
59
+ {@render children?.()}
60
+ </div>
61
+ {/if}
62
+ </div>
@@ -2,6 +2,8 @@ import type { Snippet } from 'svelte';
2
2
  interface Props {
3
3
  open?: boolean;
4
4
  placement?: 'bottom-start' | 'bottom-end' | 'top-start' | 'top-end';
5
+ closeOnClickOutside?: boolean;
6
+ trigger?: Snippet;
5
7
  children?: Snippet;
6
8
  }
7
9
  declare const Dropdown: import("svelte").Component<Props, {}, "open">;