svelte-incant 0.1.0 → 0.2.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.
package/README.md CHANGED
@@ -4,9 +4,42 @@ A keyboard shortcut management library for Svelte 5.
4
4
 
5
5
  ## Installation
6
6
 
7
- ```sh
8
- bun install svelte-incant
9
- ```
7
+ ### Prerequisites
8
+
9
+ This package requires **Tailwind CSS 4** and **shadcn-svelte** to be installed and configured in your project.
10
+
11
+ ### Setup
12
+
13
+ 1. **Install and configure Tailwind CSS** (if not already):
14
+
15
+ ```bash
16
+ bunx sv add tailwind
17
+ ```
18
+
19
+ 2. **Initialize shadcn-svelte** (if not already):
20
+
21
+ ```bash
22
+ bunx shadcn-svelte@latest init
23
+ ```
24
+
25
+ Follow the official [shadcn-svelte installation guide](https://shadcn-svelte.com/docs/installation) for detailed instructions.
26
+
27
+ 3. **Install the package** (peer dependencies are installed automatically):
28
+
29
+ ```bash
30
+ bun add svelte-incant
31
+ ```
32
+
33
+ 4. **Add @source directive** to your CSS file where Tailwind is configured (e.g., `src/app.css` or similar):
34
+
35
+ ```css
36
+ @import 'tailwindcss';
37
+
38
+ /* Add this line to include svelte-incant utility classes */
39
+ @source '../../node_modules/svelte-incant/**/*.{svelte,js,ts}';
40
+
41
+ /* rest of your styles here */
42
+ ```
10
43
 
11
44
  ## Usage
12
45
 
@@ -14,7 +47,7 @@ Add `Palette` component to your root layout to enable the shortcut overlay:
14
47
 
15
48
  ```svelte
16
49
  <script>
17
- import { Palette } from 'svelte-incant';
50
+ import { Palette } from 'svelte-incant';
18
51
  </script>
19
52
 
20
53
  <Palette />
@@ -26,13 +59,13 @@ Register keyboard shortcuts with the `Shortcut` component:
26
59
 
27
60
  ```svelte
28
61
  <script>
29
- import { Shortcut } from 'svelte-incant';
62
+ import { Shortcut } from 'svelte-incant';
30
63
  </script>
31
64
 
32
65
  <Shortcut
33
- keys={['control', 's']}
34
- description="Save document"
35
- action={() => console.log('Save document')}
66
+ keys={['control', 's']}
67
+ description="Save document"
68
+ action={() => console.log('Save document')}
36
69
  />
37
70
  ```
38
71
 
@@ -40,11 +73,11 @@ For focusing elements (like inputs), use the `Focus` component:
40
73
 
41
74
  ```svelte
42
75
  <script>
43
- import { Focus } from 'svelte-incant';
76
+ import { Focus } from 'svelte-incant';
44
77
  </script>
45
78
 
46
79
  <Focus keys={['control', 'e']} description="Focus search input">
47
- <input type="text" placeholder="Search..." />
80
+ <input type="text" placeholder="Search..." />
48
81
  </Focus>
49
82
  ```
50
83
 
@@ -52,20 +85,20 @@ Or attach shortcuts directly to an element using the `@attach` directive:
52
85
 
53
86
  ```svelte
54
87
  <script>
55
- import { shortcut } from 'svelte-incant';
88
+ import { shortcut } from 'svelte-incant';
56
89
  </script>
57
90
 
58
91
  <input
59
- type="text"
60
- placeholder="Type something..."
61
- {@attach shortcut({
62
- keys: ['meta', 'i'],
63
- description: 'Focus text input'
64
- })}
92
+ type="text"
93
+ placeholder="Type something..."
94
+ {@attach shortcut({
95
+ keys: ['meta', 'i'],
96
+ description: 'Focus text input'
97
+ })}
65
98
  />
66
99
  ```
67
100
 
68
- The `@attach` directive focuses the element it attaches to directly, as opposed to the `Focus` component, which wraps the children in a `div` and focuses that.
101
+ The `@attach` directive focuses the element it attaches to directly, as opposed to the `Focus` component, which wraps children in a `div` and focuses that.
69
102
 
70
103
  ## Features
71
104
 
@@ -73,3 +106,32 @@ The `@attach` directive focuses the element it attaches to directly, as opposed
73
106
  - **Route-specific Shortcuts**: Shortcuts only run when their component is mounted, allowing different shortcuts in different routes
74
107
  - **Focus Management**: Easily manage focus states with keyboard shortcuts
75
108
  - **Component-based**: Use components or directives to register shortcuts
109
+ - **Shadcn-svelte Integration**: Full styling integration with shadcn-svelte components
110
+
111
+ ## Troubleshooting
112
+
113
+ ### Styles are missing or components look unstyled
114
+
115
+ 1. **Check @source directive**: Make sure you added the `@source` directive to your CSS file as shown in the installation steps.
116
+
117
+ 2. **Verify Tailwind 4**: Ensure you're using Tailwind CSS v4, as the `@source` directive is a v4 feature:
118
+
119
+ ```bash
120
+ bun list tailwindcss
121
+ ```
122
+
123
+ 3. **Check shadcn-svelte setup**: Make sure shadcn-svelte is properly initialized with all required CSS variables and theme configuration.
124
+
125
+ 4. **Build your project**: Sometimes you need to restart your development server or rebuild your project for changes to take effect.
126
+
127
+ ### Peer dependency errors
128
+
129
+ If you see errors about missing peer dependencies, ensure you have the required packages installed:
130
+
131
+ ```bash
132
+ # Check if peer dependencies are installed
133
+ bun list | grep -E "(bits-ui|runed|@lucide/svelte)"
134
+
135
+ # If missing, install them manually
136
+ bun add bits-ui runed @lucide/svelte
137
+ ```
@@ -3,11 +3,9 @@
3
3
  import { keyToSymbol } from '../utils.js';
4
4
 
5
5
  let {
6
- keys,
7
- separator = 'or'
6
+ keys
8
7
  }: {
9
8
  keys: string | string[] | string[][];
10
- separator?: string;
11
9
  } = $props();
12
10
 
13
11
  type KeyCombination = string[];
@@ -19,13 +17,26 @@
19
17
  ? [keys as string[]]
20
18
  : (keys as KeyCombination[])
21
19
  );
20
+
21
+ const formatter: Intl.ListFormat = $derived(
22
+ new Intl.ListFormat(undefined, {
23
+ style: 'long',
24
+ type: 'disjunction'
25
+ })
26
+ );
27
+
28
+ const formattedParts = $derived.by(() => {
29
+ const combos = keyGroups.map((group) => group.map(keyToSymbol).join(' '));
30
+ return formatter.formatToParts(combos);
31
+ });
22
32
  </script>
23
33
 
24
34
  <Kbd.Group class="text-xs">
25
- {#each keyGroups as group, index (index)}
26
- {#if index > 0}
27
- <span class="mx-1 text-muted-foreground">{separator}</span>
35
+ {#each formattedParts as part (part)}
36
+ {#if part.type === 'element'}
37
+ <Kbd.Root>{part.value}</Kbd.Root>
38
+ {:else}
39
+ <span class="mx-1 text-muted-foreground">{part.value}</span>
28
40
  {/if}
29
- <Kbd.Root>{group.map(keyToSymbol).join(' ')}</Kbd.Root>
30
41
  {/each}
31
42
  </Kbd.Group>
@@ -1,6 +1,5 @@
1
1
  type $$ComponentProps = {
2
2
  keys: string | string[] | string[][];
3
- separator?: string;
4
3
  };
5
4
  declare const Kbds: import("svelte").Component<$$ComponentProps, {}, "">;
6
5
  type Kbds = ReturnType<typeof Kbds>;
@@ -1,2 +1,2 @@
1
- import Root from "./input.svelte";
2
- export { Root, Root as Input, };
1
+ import Root from './input.svelte';
2
+ export { Root, Root as Input };
@@ -1,4 +1,4 @@
1
- import Root from "./input.svelte";
1
+ import Root from './input.svelte';
2
2
  export { Root,
3
3
  //
4
- Root as Input, };
4
+ Root as Input };
@@ -1,12 +1,12 @@
1
1
  <script lang="ts">
2
- import type { HTMLInputAttributes, HTMLInputTypeAttribute } from "svelte/elements";
3
- import { cn, type WithElementRef } from "../../../utils.js";
2
+ import type { HTMLInputAttributes, HTMLInputTypeAttribute } from 'svelte/elements';
3
+ import { cn, type WithElementRef } from '../../../utils.js';
4
4
 
5
- type InputType = Exclude<HTMLInputTypeAttribute, "file">;
5
+ type InputType = Exclude<HTMLInputTypeAttribute, 'file'>;
6
6
 
7
7
  type Props = WithElementRef<
8
- Omit<HTMLInputAttributes, "type"> &
9
- ({ type: "file"; files?: FileList } | { type?: InputType; files?: undefined })
8
+ Omit<HTMLInputAttributes, 'type'> &
9
+ ({ type: 'file'; files?: FileList } | { type?: InputType; files?: undefined })
10
10
  >;
11
11
 
12
12
  let {
@@ -15,19 +15,19 @@
15
15
  type,
16
16
  files = $bindable(),
17
17
  class: className,
18
- "data-slot": dataSlot = "input",
18
+ 'data-slot': dataSlot = 'input',
19
19
  ...restProps
20
20
  }: Props = $props();
21
21
  </script>
22
22
 
23
- {#if type === "file"}
23
+ {#if type === 'file'}
24
24
  <input
25
25
  bind:this={ref}
26
26
  data-slot={dataSlot}
27
27
  class={cn(
28
- "selection:bg-primary dark:bg-input/30 selection:text-primary-foreground border-input ring-offset-background placeholder:text-muted-foreground flex h-9 w-full min-w-0 rounded-md border bg-transparent px-3 pt-1.5 text-sm font-medium shadow-xs transition-[color,box-shadow] outline-none disabled:cursor-not-allowed disabled:opacity-50",
29
- "focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px]",
30
- "aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
28
+ 'flex h-9 w-full min-w-0 rounded-md border border-input bg-transparent px-3 pt-1.5 text-sm font-medium shadow-xs ring-offset-background transition-[color,box-shadow] outline-none selection:bg-primary selection:text-primary-foreground placeholder:text-muted-foreground disabled:cursor-not-allowed disabled:opacity-50 dark:bg-input/30',
29
+ 'focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50',
30
+ 'aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40',
31
31
  className
32
32
  )}
33
33
  type="file"
@@ -40,9 +40,9 @@
40
40
  bind:this={ref}
41
41
  data-slot={dataSlot}
42
42
  class={cn(
43
- "border-input bg-background selection:bg-primary dark:bg-input/30 selection:text-primary-foreground ring-offset-background placeholder:text-muted-foreground flex h-9 w-full min-w-0 rounded-md border px-3 py-1 text-base shadow-xs transition-[color,box-shadow] outline-none disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
44
- "focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px]",
45
- "aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
43
+ 'flex h-9 w-full min-w-0 rounded-md border border-input bg-background px-3 py-1 text-base shadow-xs ring-offset-background transition-[color,box-shadow] outline-none selection:bg-primary selection:text-primary-foreground placeholder:text-muted-foreground disabled:cursor-not-allowed disabled:opacity-50 md:text-sm dark:bg-input/30',
44
+ 'focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50',
45
+ 'aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40',
46
46
  className
47
47
  )}
48
48
  {type}
@@ -1,8 +1,8 @@
1
- import type { HTMLInputAttributes, HTMLInputTypeAttribute } from "svelte/elements";
2
- import { type WithElementRef } from "../../../utils.js";
3
- type InputType = Exclude<HTMLInputTypeAttribute, "file">;
4
- type Props = WithElementRef<Omit<HTMLInputAttributes, "type"> & ({
5
- type: "file";
1
+ import type { HTMLInputAttributes, HTMLInputTypeAttribute } from 'svelte/elements';
2
+ import { type WithElementRef } from '../../../utils.js';
3
+ type InputType = Exclude<HTMLInputTypeAttribute, 'file'>;
4
+ type Props = WithElementRef<Omit<HTMLInputAttributes, 'type'> & ({
5
+ type: 'file';
6
6
  files?: FileList;
7
7
  } | {
8
8
  type?: InputType;
@@ -1,5 +1,5 @@
1
- import Root from "./tabs.svelte";
2
- import Content from "./tabs-content.svelte";
3
- import List from "./tabs-list.svelte";
4
- import Trigger from "./tabs-trigger.svelte";
5
- export { Root, Content, List, Trigger, Root as Tabs, Content as TabsContent, List as TabsList, Trigger as TabsTrigger, };
1
+ import Root from './tabs.svelte';
2
+ import Content from './tabs-content.svelte';
3
+ import List from './tabs-list.svelte';
4
+ import Trigger from './tabs-trigger.svelte';
5
+ export { Root, Content, List, Trigger, Root as Tabs, Content as TabsContent, List as TabsList, Trigger as TabsTrigger };
@@ -1,7 +1,7 @@
1
- import Root from "./tabs.svelte";
2
- import Content from "./tabs-content.svelte";
3
- import List from "./tabs-list.svelte";
4
- import Trigger from "./tabs-trigger.svelte";
1
+ import Root from './tabs.svelte';
2
+ import Content from './tabs-content.svelte';
3
+ import List from './tabs-list.svelte';
4
+ import Trigger from './tabs-trigger.svelte';
5
5
  export { Root, Content, List, Trigger,
6
6
  //
7
- Root as Tabs, Content as TabsContent, List as TabsList, Trigger as TabsTrigger, };
7
+ Root as Tabs, Content as TabsContent, List as TabsList, Trigger as TabsTrigger };
@@ -1,6 +1,6 @@
1
1
  <script lang="ts">
2
- import { Tabs as TabsPrimitive } from "bits-ui";
3
- import { cn } from "../../../utils.js";
2
+ import { Tabs as TabsPrimitive } from 'bits-ui';
3
+ import { cn } from '../../../utils.js';
4
4
 
5
5
  let {
6
6
  ref = $bindable(null),
@@ -12,6 +12,6 @@
12
12
  <TabsPrimitive.Content
13
13
  bind:ref
14
14
  data-slot="tabs-content"
15
- class={cn("flex-1 outline-none", className)}
15
+ class={cn('flex-1 outline-none', className)}
16
16
  {...restProps}
17
17
  />
@@ -1,4 +1,4 @@
1
- import { Tabs as TabsPrimitive } from "bits-ui";
1
+ import { Tabs as TabsPrimitive } from 'bits-ui';
2
2
  declare const TabsContent: import("svelte").Component<TabsPrimitive.ContentProps, {}, "ref">;
3
3
  type TabsContent = ReturnType<typeof TabsContent>;
4
4
  export default TabsContent;
@@ -1,19 +1,15 @@
1
1
  <script lang="ts">
2
- import { Tabs as TabsPrimitive } from "bits-ui";
3
- import { cn } from "../../../utils.js";
2
+ import { Tabs as TabsPrimitive } from 'bits-ui';
3
+ import { cn } from '../../../utils.js';
4
4
 
5
- let {
6
- ref = $bindable(null),
7
- class: className,
8
- ...restProps
9
- }: TabsPrimitive.ListProps = $props();
5
+ let { ref = $bindable(null), class: className, ...restProps }: TabsPrimitive.ListProps = $props();
10
6
  </script>
11
7
 
12
8
  <TabsPrimitive.List
13
9
  bind:ref
14
10
  data-slot="tabs-list"
15
11
  class={cn(
16
- "bg-muted text-muted-foreground inline-flex h-9 w-fit items-center justify-center rounded-lg p-[3px]",
12
+ 'inline-flex h-9 w-fit items-center justify-center rounded-lg bg-muted p-[3px] text-muted-foreground',
17
13
  className
18
14
  )}
19
15
  {...restProps}
@@ -1,4 +1,4 @@
1
- import { Tabs as TabsPrimitive } from "bits-ui";
1
+ import { Tabs as TabsPrimitive } from 'bits-ui';
2
2
  declare const TabsList: import("svelte").Component<TabsPrimitive.ListProps, {}, "ref">;
3
3
  type TabsList = ReturnType<typeof TabsList>;
4
4
  export default TabsList;
@@ -1,6 +1,6 @@
1
1
  <script lang="ts">
2
- import { Tabs as TabsPrimitive } from "bits-ui";
3
- import { cn } from "../../../utils.js";
2
+ import { Tabs as TabsPrimitive } from 'bits-ui';
3
+ import { cn } from '../../../utils.js';
4
4
 
5
5
  let {
6
6
  ref = $bindable(null),
@@ -13,7 +13,7 @@
13
13
  bind:ref
14
14
  data-slot="tabs-trigger"
15
15
  class={cn(
16
- "data-[state=active]:bg-background dark:data-[state=active]:text-foreground focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:outline-ring dark:data-[state=active]:border-input dark:data-[state=active]:bg-input/30 text-foreground dark:text-muted-foreground inline-flex h-[calc(100%-1px)] flex-1 items-center justify-center gap-1.5 rounded-md border border-transparent px-2 py-1 text-sm font-medium whitespace-nowrap transition-[color,box-shadow] focus-visible:ring-[3px] focus-visible:outline-1 disabled:pointer-events-none disabled:opacity-50 data-[state=active]:shadow-sm [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
16
+ "inline-flex h-[calc(100%-1px)] flex-1 items-center justify-center gap-1.5 rounded-md border border-transparent px-2 py-1 text-sm font-medium whitespace-nowrap text-foreground transition-[color,box-shadow] focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 focus-visible:outline-1 focus-visible:outline-ring disabled:pointer-events-none disabled:opacity-50 data-[state=active]:bg-background data-[state=active]:shadow-sm dark:text-muted-foreground dark:data-[state=active]:border-input dark:data-[state=active]:bg-input/30 dark:data-[state=active]:text-foreground [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
17
17
  className
18
18
  )}
19
19
  {...restProps}
@@ -1,4 +1,4 @@
1
- import { Tabs as TabsPrimitive } from "bits-ui";
1
+ import { Tabs as TabsPrimitive } from 'bits-ui';
2
2
  declare const TabsTrigger: import("svelte").Component<TabsPrimitive.TriggerProps, {}, "ref">;
3
3
  type TabsTrigger = ReturnType<typeof TabsTrigger>;
4
4
  export default TabsTrigger;
@@ -1,10 +1,10 @@
1
1
  <script lang="ts">
2
- import { Tabs as TabsPrimitive } from "bits-ui";
3
- import { cn } from "../../../utils.js";
2
+ import { Tabs as TabsPrimitive } from 'bits-ui';
3
+ import { cn } from '../../../utils.js';
4
4
 
5
5
  let {
6
6
  ref = $bindable(null),
7
- value = $bindable(""),
7
+ value = $bindable(''),
8
8
  class: className,
9
9
  ...restProps
10
10
  }: TabsPrimitive.RootProps = $props();
@@ -14,6 +14,6 @@
14
14
  bind:ref
15
15
  bind:value
16
16
  data-slot="tabs"
17
- class={cn("flex flex-col gap-2", className)}
17
+ class={cn('flex flex-col gap-2', className)}
18
18
  {...restProps}
19
19
  />
@@ -1,4 +1,4 @@
1
- import { Tabs as TabsPrimitive } from "bits-ui";
1
+ import { Tabs as TabsPrimitive } from 'bits-ui';
2
2
  declare const Tabs: import("svelte").Component<TabsPrimitive.RootProps, {}, "ref" | "value">;
3
3
  type Tabs = ReturnType<typeof Tabs>;
4
4
  export default Tabs;
package/package.json CHANGED
@@ -1,7 +1,11 @@
1
1
  {
2
2
  "name": "svelte-incant",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "license": "MIT",
5
+ "repository": "https://github.com/mastermakrela/svelte-incant",
6
+ "bugs": "https://github.com/mastermakrela/svelte-incant/issues",
7
+ "homepage": "https://svelte-incant.mastermakrela.com/",
8
+ "author": "mastermakrela <github@mastermakrela.com>",
5
9
  "scripts": {
6
10
  "dev": "vite dev",
7
11
  "build": "vite build && npm run prepack",
@@ -22,9 +26,7 @@
22
26
  "!dist/**/*.test.*",
23
27
  "!dist/**/*.spec.*"
24
28
  ],
25
- "sideEffects": [
26
- "**/*.css"
27
- ],
29
+ "sideEffects": false,
28
30
  "svelte": "./dist/index.js",
29
31
  "types": "./dist/index.d.ts",
30
32
  "type": "module",
@@ -35,7 +37,8 @@
35
37
  }
36
38
  },
37
39
  "peerDependencies": {
38
- "svelte": "^5.0.0"
40
+ "svelte": "^5.0.0",
41
+ "bits-ui": "^2.0.0"
39
42
  },
40
43
  "devDependencies": {
41
44
  "@eslint/compat": "^1.4.0",
@@ -69,12 +72,7 @@
69
72
  "typescript-eslint": "^8.48.1",
70
73
  "vite": "^7.2.6",
71
74
  "vitest": "^4.0.15",
72
- "vitest-browser-svelte": "^2.0.1"
73
- },
74
- "keywords": [
75
- "svelte"
76
- ],
77
- "dependencies": {
75
+ "vitest-browser-svelte": "^2.0.1",
78
76
  "@lucide/svelte": "^0.561.0",
79
77
  "clsx": "^2.1.1",
80
78
  "copy-to-clipboard": "^3.3.3",
@@ -83,5 +81,11 @@
83
81
  "tailwind-merge": "^3.4.0",
84
82
  "tailwind-variants": "^3.2.2",
85
83
  "tw-animate-css": "^1.4.0"
86
- }
84
+ },
85
+ "keywords": [
86
+ "svelte",
87
+ "keyboard shortcuts",
88
+ "shortcuts"
89
+ ],
90
+ "dependencies": {}
87
91
  }