@insymetri/styleguide 0.1.59 → 0.1.60

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.
@@ -8,6 +8,7 @@
8
8
  import {useFloating, portal, clickOutside} from '../utils/menu'
9
9
  import MenuSearchInput from '../utils/menu/MenuSearchInput.svelte'
10
10
  import MenuNoResults from '../utils/menu/MenuNoResults.svelte'
11
+ import MenuItemTooltip from '../utils/menu/MenuItemTooltip.svelte'
11
12
 
12
13
  type Props = {
13
14
  items: MenuItem[]
@@ -69,6 +70,7 @@
69
70
  let triggerEl = $state<HTMLElement | null>(null)
70
71
  let floatingEl = $state<HTMLElement | null>(null)
71
72
  let highlightedIndex = $state(-1)
73
+ let tooltipState = $state<{el: HTMLElement, text: string} | null>(null)
72
74
 
73
75
  $effect(() => {
74
76
  if (autofocus && triggerEl) triggerEl.focus({focusVisible: true} as FocusOptions)
@@ -112,6 +114,7 @@
112
114
  })
113
115
 
114
116
  function handleSelect(item: MenuItem) {
117
+ if (item.disabled) return
115
118
  value = item.value
116
119
  open = false
117
120
  onSelect?.(item.value)
@@ -119,6 +122,14 @@
119
122
  triggerEl?.focus()
120
123
  }
121
124
 
125
+ function showTooltip(el: HTMLElement, text: string) {
126
+ tooltipState = {el, text}
127
+ }
128
+
129
+ function hideTooltip(el: HTMLElement) {
130
+ if (tooltipState?.el === el) tooltipState = null
131
+ }
132
+
122
133
  function handleSelectValue(val: string) {
123
134
  const item = items.find(i => i.value === val)
124
135
  if (item) handleSelect(item)
@@ -208,6 +219,7 @@
208
219
  if (open && searchable) {
209
220
  search.resetAndFocus()
210
221
  }
222
+ if (!open) tooltipState = null
211
223
  })
212
224
 
213
225
  // Initialize highlight to selected (or first enabled) when opened (non-searchable)
@@ -284,10 +296,10 @@
284
296
  data-disabled={item.disabled ? '' : undefined}
285
297
  data-index={index}
286
298
  class={cn(
287
- 'flex items-center justify-between gap-12 px-12 rounded-6 text-dropdown-item cursor-default outline-none data-[disabled]:opacity-50 data-[disabled]:cursor-not-allowed',
299
+ 'flex items-center justify-between gap-12 px-12 rounded-6 text-dropdown-item cursor-default outline-none data-[disabled]:opacity-50',
288
300
  itemDensityClasses[density.value],
289
301
  value === item.value && 'text-dropdown-item-selected',
290
- isHighlighted && 'bg-dropdown-item-hover'
302
+ isHighlighted && !item.disabled && 'bg-dropdown-item-hover'
291
303
  )}
292
304
  data-search-item={searchable ? '' : undefined}
293
305
  onclick={() => handleSelect(item)}
@@ -295,6 +307,12 @@
295
307
  onpointermove={searchable
296
308
  ? () => search.setHighlight(index)
297
309
  : () => { if (!item.disabled) highlightedIndex = index }}
310
+ onpointerenter={(e) => {
311
+ if (item.disabled && item.disabledReason) showTooltip(e.currentTarget as HTMLElement, item.disabledReason)
312
+ }}
313
+ onpointerleave={(e) => {
314
+ if (item.disabled && item.disabledReason) hideTooltip(e.currentTarget as HTMLElement)
315
+ }}
298
316
  >
299
317
  {#if renderItem}
300
318
  {@render renderItem(item, value === item.value)}
@@ -318,6 +336,9 @@
318
336
  {/if}
319
337
  </div>
320
338
  </div>
339
+ {#if tooltipState}
340
+ <MenuItemTooltip referenceEl={tooltipState.el} text={tooltipState.text} />
341
+ {/if}
321
342
  </div>
322
343
  {/if}
323
344
  {#if showError && errorMessage}
@@ -8,6 +8,7 @@
8
8
  import MenuItemContent from '../utils/menu/MenuItemContent.svelte'
9
9
  import MenuSearchInput from '../utils/menu/MenuSearchInput.svelte'
10
10
  import MenuNoResults from '../utils/menu/MenuNoResults.svelte'
11
+ import MenuItemTooltip from '../utils/menu/MenuItemTooltip.svelte'
11
12
  import IIDropdownMenuSub from './IIDropdownMenuSub.svelte'
12
13
  import IIDropdownMenuSubSimple from './IIDropdownMenuSubSimple.svelte'
13
14
 
@@ -45,6 +46,15 @@
45
46
  let floatingEl = $state<HTMLElement | null>(null)
46
47
  let openSubIndex = $state<number | null>(null)
47
48
  let subTriggerEls: Record<number, HTMLElement | null> = {}
49
+ let tooltipState = $state<{el: HTMLElement, text: string} | null>(null)
50
+
51
+ function showTooltip(el: HTMLElement, text: string) {
52
+ tooltipState = {el, text}
53
+ }
54
+
55
+ function hideTooltip(el: HTMLElement) {
56
+ if (tooltipState?.el === el) tooltipState = null
57
+ }
48
58
 
49
59
  const placement = $derived(align === 'center' ? side : `${side}-${align}` as const)
50
60
 
@@ -89,6 +99,7 @@
89
99
  function close() {
90
100
  open = false
91
101
  openSubIndex = null
102
+ tooltipState = null
92
103
  triggerEl?.focus()
93
104
  }
94
105
 
@@ -197,15 +208,28 @@
197
208
  role="menuitem"
198
209
  tabindex="-1"
199
210
  data-disabled={item.disabled ? '' : undefined}
200
- class={menuItemClass({variant: item.variant, searchable, isHighlighted: index === search.highlightedIndex})}
211
+ class={menuItemClass({variant: item.variant, searchable, isHighlighted: !item.disabled && index === search.highlightedIndex})}
201
212
  data-search-item={searchable ? '' : undefined}
202
- onclick={() => handleSelect(item.value)}
213
+ onclick={() => {
214
+ if (item.disabled) return
215
+ handleSelect(item.value)
216
+ }}
203
217
  onfocus={searchable ? search.refocusInput : undefined}
204
- onpointermove={searchable ? () => search.setHighlight(index) : undefined}
205
- onpointerenter={!searchable ? (e) => {
206
- openSubIndex = null
207
- ;(e.currentTarget as HTMLElement).focus()
208
- } : undefined}
218
+ onpointermove={searchable && !item.disabled ? () => search.setHighlight(index) : undefined}
219
+ onpointerenter={(e) => {
220
+ const el = e.currentTarget as HTMLElement
221
+ if (item.disabled) {
222
+ if (item.disabledReason) showTooltip(el, item.disabledReason)
223
+ return
224
+ }
225
+ if (!searchable) {
226
+ openSubIndex = null
227
+ el.focus()
228
+ }
229
+ }}
230
+ onpointerleave={(e) => {
231
+ if (item.disabled && item.disabledReason) hideTooltip(e.currentTarget as HTMLElement)
232
+ }}
209
233
  >
210
234
  {@render itemContent(item)}
211
235
  </div>
@@ -224,11 +248,20 @@
224
248
  class={cn(menuStyles.item, menuStyles.itemDefault)}
225
249
  bind:this={subTriggerEls[subIdx]}
226
250
  onpointerenter={(e) => {
251
+ const el = e.currentTarget as HTMLElement
252
+ if (entry.disabled) {
253
+ if (entry.disabledReason) showTooltip(el, entry.disabledReason)
254
+ return
255
+ }
227
256
  openSubIndex = subIdx
228
- ;(e.currentTarget as HTMLElement).focus()
257
+ el.focus()
258
+ }}
259
+ onpointerleave={(e) => {
260
+ if (entry.disabled && entry.disabledReason) hideTooltip(e.currentTarget as HTMLElement)
261
+ e.stopImmediatePropagation()
229
262
  }}
230
- onpointerleave={(e) => e.stopImmediatePropagation()}
231
263
  onclick={() => {
264
+ if (entry.disabled) return
232
265
  openSubIndex = isOpen ? null : subIdx
233
266
  }}
234
267
  >
@@ -331,5 +364,8 @@
331
364
  <MenuNoResults />
332
365
  {/if}
333
366
  </div>
367
+ {#if tooltipState}
368
+ <MenuItemTooltip referenceEl={tooltipState.el} text={tooltipState.text} />
369
+ {/if}
334
370
  </div>
335
371
  {/if}
@@ -5,6 +5,7 @@
5
5
  import {useFloating, portal} from '../utils/menu'
6
6
  import MenuSearchInput from '../utils/menu/MenuSearchInput.svelte'
7
7
  import MenuNoResults from '../utils/menu/MenuNoResults.svelte'
8
+ import MenuItemTooltip from '../utils/menu/MenuItemTooltip.svelte'
8
9
 
9
10
  type Props = {
10
11
  items: MenuEntry[]
@@ -31,6 +32,15 @@
31
32
  })
32
33
 
33
34
  let floatingEl = $state<HTMLElement | null>(null)
35
+ let tooltipState = $state<{el: HTMLElement, text: string} | null>(null)
36
+
37
+ function showTooltip(el: HTMLElement, text: string) {
38
+ tooltipState = {el, text}
39
+ }
40
+
41
+ function hideTooltip(el: HTMLElement) {
42
+ if (tooltipState?.el === el) tooltipState = null
43
+ }
34
44
 
35
45
  useFloating({
36
46
  reference: () => triggerEl,
@@ -99,10 +109,19 @@
99
109
  role="menuitem"
100
110
  tabindex="-1"
101
111
  data-disabled={item.disabled ? '' : undefined}
102
- class={menuItemClass({variant: item.variant, searchable: true, isHighlighted: index === search.highlightedIndex})}
112
+ class={menuItemClass({variant: item.variant, searchable: true, isHighlighted: !item.disabled && index === search.highlightedIndex})}
103
113
  data-search-item=""
104
- onclick={() => onSelect(item.value)}
105
- onpointermove={() => search.setHighlight(index)}
114
+ onclick={() => {
115
+ if (item.disabled) return
116
+ onSelect(item.value)
117
+ }}
118
+ onpointermove={() => { if (!item.disabled) search.setHighlight(index) }}
119
+ onpointerenter={(e) => {
120
+ if (item.disabled && item.disabledReason) showTooltip(e.currentTarget as HTMLElement, item.disabledReason)
121
+ }}
122
+ onpointerleave={(e) => {
123
+ if (item.disabled && item.disabledReason) hideTooltip(e.currentTarget as HTMLElement)
124
+ }}
106
125
  >
107
126
  {@render renderItemContent(item)}
108
127
  </div>
@@ -114,10 +133,19 @@
114
133
  role="menuitem"
115
134
  tabindex="-1"
116
135
  data-disabled={entry.disabled ? '' : undefined}
117
- class={menuItemClass({variant: entry.variant, searchable: true, isHighlighted: index === search.highlightedIndex})}
136
+ class={menuItemClass({variant: entry.variant, searchable: true, isHighlighted: !entry.disabled && index === search.highlightedIndex})}
118
137
  data-search-item=""
119
- onclick={() => onSelect(entry.value)}
120
- onpointermove={() => search.setHighlight(index)}
138
+ onclick={() => {
139
+ if (entry.disabled) return
140
+ onSelect(entry.value)
141
+ }}
142
+ onpointermove={() => { if (!entry.disabled) search.setHighlight(index) }}
143
+ onpointerenter={(e) => {
144
+ if (entry.disabled && entry.disabledReason) showTooltip(e.currentTarget as HTMLElement, entry.disabledReason)
145
+ }}
146
+ onpointerleave={(e) => {
147
+ if (entry.disabled && entry.disabledReason) hideTooltip(e.currentTarget as HTMLElement)
148
+ }}
121
149
  >
122
150
  {@render renderItemContent(entry)}
123
151
  </div>
@@ -128,4 +156,7 @@
128
156
  {/if}
129
157
  </div>
130
158
  </div>
159
+ {#if tooltipState}
160
+ <MenuItemTooltip referenceEl={tooltipState.el} text={tooltipState.text} />
161
+ {/if}
131
162
  </div>
@@ -2,6 +2,7 @@
2
2
  import type {Snippet} from 'svelte'
3
3
  import {isItem, isGroup, isSub, isSeparator, menuStyles, menuItemClass, type MenuItem, type MenuEntry, type SubEntry} from '../utils/menu'
4
4
  import {useFloating, portal} from '../utils/menu'
5
+ import MenuItemTooltip from '../utils/menu/MenuItemTooltip.svelte'
5
6
 
6
7
  type Props = {
7
8
  items: MenuEntry[]
@@ -22,6 +23,15 @@
22
23
  }: Props = $props()
23
24
 
24
25
  let floatingEl = $state<HTMLElement | null>(null)
26
+ let tooltipState = $state<{el: HTMLElement, text: string} | null>(null)
27
+
28
+ function showTooltip(el: HTMLElement, text: string) {
29
+ tooltipState = {el, text}
30
+ }
31
+
32
+ function hideTooltip(el: HTMLElement) {
33
+ if (tooltipState?.el === el) tooltipState = null
34
+ }
25
35
 
26
36
  useFloating({
27
37
  reference: () => triggerEl,
@@ -107,8 +117,21 @@
107
117
  tabindex="-1"
108
118
  data-disabled={groupItem.disabled ? '' : undefined}
109
119
  class={menuItemClass({variant: groupItem.variant})}
110
- onclick={() => onSelect(groupItem.value)}
111
- onpointerenter={(e) => (e.currentTarget as HTMLElement).focus()}
120
+ onclick={() => {
121
+ if (groupItem.disabled) return
122
+ onSelect(groupItem.value)
123
+ }}
124
+ onpointerenter={(e) => {
125
+ const el = e.currentTarget as HTMLElement
126
+ if (groupItem.disabled) {
127
+ if (groupItem.disabledReason) showTooltip(el, groupItem.disabledReason)
128
+ return
129
+ }
130
+ el.focus()
131
+ }}
132
+ onpointerleave={(e) => {
133
+ if (groupItem.disabled && groupItem.disabledReason) hideTooltip(e.currentTarget as HTMLElement)
134
+ }}
112
135
  >
113
136
  {@render renderItemContent(groupItem)}
114
137
  </div>
@@ -123,12 +146,28 @@
123
146
  tabindex="-1"
124
147
  data-disabled={entry.disabled ? '' : undefined}
125
148
  class={menuItemClass({variant: entry.variant})}
126
- onclick={() => onSelect(entry.value)}
127
- onpointerenter={(e) => (e.currentTarget as HTMLElement).focus()}
149
+ onclick={() => {
150
+ if (entry.disabled) return
151
+ onSelect(entry.value)
152
+ }}
153
+ onpointerenter={(e) => {
154
+ const el = e.currentTarget as HTMLElement
155
+ if (entry.disabled) {
156
+ if (entry.disabledReason) showTooltip(el, entry.disabledReason)
157
+ return
158
+ }
159
+ el.focus()
160
+ }}
161
+ onpointerleave={(e) => {
162
+ if (entry.disabled && entry.disabledReason) hideTooltip(e.currentTarget as HTMLElement)
163
+ }}
128
164
  >
129
165
  {@render renderItemContent(entry)}
130
166
  </div>
131
167
  {/if}
132
168
  {/each}
133
169
  </div>
170
+ {#if tooltipState}
171
+ <MenuItemTooltip referenceEl={tooltipState.el} text={tooltipState.text} />
172
+ {/if}
134
173
  </div>
@@ -0,0 +1,30 @@
1
+ <script lang="ts">
2
+ import {useFloating} from './use-floating.svelte'
3
+ import {portal} from './use-portal'
4
+
5
+ type Props = {
6
+ referenceEl: HTMLElement | null
7
+ text: string
8
+ }
9
+
10
+ let {referenceEl, text}: Props = $props()
11
+
12
+ let floatingEl = $state<HTMLElement | null>(null)
13
+
14
+ useFloating({
15
+ reference: () => referenceEl,
16
+ floating: () => floatingEl,
17
+ placement: 'right',
18
+ offset: 8,
19
+ })
20
+ </script>
21
+
22
+ <div use:portal>
23
+ <div
24
+ bind:this={floatingEl}
25
+ role="tooltip"
26
+ class="bg-dark text-surface text-tiny px-8 py-4 rounded-4 max-w-200 z-16 pointer-events-none shadow-dropdown"
27
+ >
28
+ {text}
29
+ </div>
30
+ </div>
@@ -0,0 +1,7 @@
1
+ type Props = {
2
+ referenceEl: HTMLElement | null;
3
+ text: string;
4
+ };
5
+ declare const MenuItemTooltip: import("svelte").Component<Props, {}, "">;
6
+ type MenuItemTooltip = ReturnType<typeof MenuItemTooltip>;
7
+ export default MenuItemTooltip;
@@ -1,6 +1,7 @@
1
1
  export { default as MenuSearchInput } from './MenuSearchInput.svelte';
2
2
  export { default as MenuNoResults } from './MenuNoResults.svelte';
3
3
  export { default as MenuItemContent } from './MenuItemContent.svelte';
4
+ export { default as MenuItemTooltip } from './MenuItemTooltip.svelte';
4
5
  export { menuStyles, menuItemClass } from './menu-styles';
5
6
  export { portal } from './use-portal';
6
7
  export { clickOutside } from './use-click-outside';
@@ -1,6 +1,7 @@
1
1
  export { default as MenuSearchInput } from './MenuSearchInput.svelte';
2
2
  export { default as MenuNoResults } from './MenuNoResults.svelte';
3
3
  export { default as MenuItemContent } from './MenuItemContent.svelte';
4
+ export { default as MenuItemTooltip } from './MenuItemTooltip.svelte';
4
5
  export { menuStyles, menuItemClass } from './menu-styles';
5
6
  export { portal } from './use-portal';
6
7
  export { clickOutside } from './use-click-outside';
@@ -4,6 +4,7 @@ export type MenuItem = {
4
4
  value: string;
5
5
  icon?: Snippet;
6
6
  disabled?: boolean;
7
+ disabledReason?: string;
7
8
  variant?: 'default' | 'destructive';
8
9
  shortcut?: string;
9
10
  };
@@ -20,6 +21,7 @@ export type SubEntry = {
20
21
  label: string;
21
22
  icon?: Snippet;
22
23
  disabled?: boolean;
24
+ disabledReason?: string;
23
25
  shortcut?: string;
24
26
  items: MenuEntry[];
25
27
  searchable?: boolean;
@@ -1,7 +1,7 @@
1
1
  export declare const menuStyles: {
2
- readonly item: "flex items-center gap-8 px-12 py-8 rounded-4 text-small cursor-default select-none outline-none data-[disabled]:opacity-50 data-[disabled]:cursor-not-allowed data-[disabled]:pointer-events-none motion-reduce:transition-none";
3
- readonly itemDefault: "text-dropdown-item hover:bg-dropdown-item-hover focus:bg-dropdown-item-hover data-[highlighted]:bg-dropdown-item-hover data-[highlighted]:outline-none data-[state=open]:bg-dropdown-item-hover";
4
- readonly itemDestructive: "text-error hover:bg-error-bg focus:bg-error-bg data-[highlighted]:bg-error-bg data-[highlighted]:outline-none";
2
+ readonly item: "flex items-center gap-8 px-12 py-8 rounded-4 text-small cursor-default select-none outline-none data-[disabled]:opacity-50 motion-reduce:transition-none";
3
+ readonly itemDefault: "text-dropdown-item hover:bg-dropdown-item-hover focus:bg-dropdown-item-hover data-[highlighted]:bg-dropdown-item-hover data-[highlighted]:outline-none data-[state=open]:bg-dropdown-item-hover data-[disabled]:hover:bg-transparent data-[disabled]:focus:bg-transparent";
4
+ readonly itemDestructive: "text-error hover:bg-error-bg focus:bg-error-bg data-[highlighted]:bg-error-bg data-[highlighted]:outline-none data-[disabled]:hover:bg-transparent data-[disabled]:focus:bg-transparent";
5
5
  readonly content: "min-w-48 max-h-300 overflow-y-auto bg-dropdown-bg border-[0.5px] border-dropdown-border rounded-10 shadow-dropdown p-4 z-12 pointer-events-auto outline-none";
6
6
  readonly subContent: "min-w-48 max-h-300 overflow-y-auto bg-dropdown-bg border-[0.5px] border-dropdown-border rounded-10 shadow-submenu p-4 z-12 outline-none animate-fade-in motion-reduce:animate-none";
7
7
  readonly searchableSubContent: "min-w-48 bg-dropdown-bg border-[0.5px] border-dropdown-border rounded-10 shadow-submenu p-4 z-12 pointer-events-auto outline-none overflow-hidden animate-fade-in motion-reduce:animate-none";
@@ -1,8 +1,8 @@
1
1
  import { cn } from '../cn';
2
2
  export const menuStyles = {
3
- item: 'flex items-center gap-8 px-12 py-8 rounded-4 text-small cursor-default select-none outline-none data-[disabled]:opacity-50 data-[disabled]:cursor-not-allowed data-[disabled]:pointer-events-none motion-reduce:transition-none',
4
- itemDefault: 'text-dropdown-item hover:bg-dropdown-item-hover focus:bg-dropdown-item-hover data-[highlighted]:bg-dropdown-item-hover data-[highlighted]:outline-none data-[state=open]:bg-dropdown-item-hover',
5
- itemDestructive: 'text-error hover:bg-error-bg focus:bg-error-bg data-[highlighted]:bg-error-bg data-[highlighted]:outline-none',
3
+ item: 'flex items-center gap-8 px-12 py-8 rounded-4 text-small cursor-default select-none outline-none data-[disabled]:opacity-50 motion-reduce:transition-none',
4
+ itemDefault: 'text-dropdown-item hover:bg-dropdown-item-hover focus:bg-dropdown-item-hover data-[highlighted]:bg-dropdown-item-hover data-[highlighted]:outline-none data-[state=open]:bg-dropdown-item-hover data-[disabled]:hover:bg-transparent data-[disabled]:focus:bg-transparent',
5
+ itemDestructive: 'text-error hover:bg-error-bg focus:bg-error-bg data-[highlighted]:bg-error-bg data-[highlighted]:outline-none data-[disabled]:hover:bg-transparent data-[disabled]:focus:bg-transparent',
6
6
  content: 'min-w-48 max-h-300 overflow-y-auto bg-dropdown-bg border-[0.5px] border-dropdown-border rounded-10 shadow-dropdown p-4 z-12 pointer-events-auto outline-none',
7
7
  subContent: 'min-w-48 max-h-300 overflow-y-auto bg-dropdown-bg border-[0.5px] border-dropdown-border rounded-10 shadow-submenu p-4 z-12 outline-none animate-fade-in motion-reduce:animate-none',
8
8
  searchableSubContent: 'min-w-48 bg-dropdown-bg border-[0.5px] border-dropdown-border rounded-10 shadow-submenu p-4 z-12 pointer-events-auto outline-none overflow-hidden animate-fade-in motion-reduce:animate-none',
@@ -13,7 +13,7 @@ export const menuStyles = {
13
13
  export function menuItemClass(opts) {
14
14
  const { variant = 'default', searchable = false, isHighlighted = false } = opts;
15
15
  if (variant === 'destructive') {
16
- return cn(menuStyles.item, 'text-error', !searchable && 'hover:bg-error-bg focus:bg-error-bg data-[highlighted]:bg-error-bg', searchable && isHighlighted && 'bg-error-bg');
16
+ return cn(menuStyles.item, 'text-error', !searchable && 'hover:bg-error-bg focus:bg-error-bg data-[highlighted]:bg-error-bg', searchable && isHighlighted && 'bg-error-bg', 'data-[disabled]:hover:bg-transparent data-[disabled]:focus:bg-transparent');
17
17
  }
18
- return cn(menuStyles.item, 'text-dropdown-item', !searchable && 'hover:bg-dropdown-item-hover focus:bg-dropdown-item-hover data-[highlighted]:bg-dropdown-item-hover', searchable && isHighlighted && 'bg-dropdown-item-hover');
18
+ return cn(menuStyles.item, 'text-dropdown-item', !searchable && 'hover:bg-dropdown-item-hover focus:bg-dropdown-item-hover data-[highlighted]:bg-dropdown-item-hover', searchable && isHighlighted && 'bg-dropdown-item-hover', 'data-[disabled]:hover:bg-transparent data-[disabled]:focus:bg-transparent');
19
19
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@insymetri/styleguide",
3
- "version": "0.1.59",
3
+ "version": "0.1.60",
4
4
  "description": "Insymetri shared UI component library built with Svelte 5",
5
5
  "type": "module",
6
6
  "scripts": {