svelte-incant 0.1.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 +75 -0
- package/dist/attachment.svelte.d.ts +8 -0
- package/dist/attachment.svelte.js +93 -0
- package/dist/combobox-example.svelte +155 -0
- package/dist/combobox-example.svelte.d.ts +3 -0
- package/dist/components/CodeBlock.svelte +213 -0
- package/dist/components/CodeBlock.svelte.d.ts +12 -0
- package/dist/components/header.svelte +155 -0
- package/dist/components/header.svelte.d.ts +3 -0
- package/dist/components/kbds.svelte +31 -0
- package/dist/components/kbds.svelte.d.ts +7 -0
- package/dist/components/ui/badge/badge.svelte +49 -0
- package/dist/components/ui/badge/badge.svelte.d.ts +32 -0
- package/dist/components/ui/badge/index.d.ts +2 -0
- package/dist/components/ui/badge/index.js +2 -0
- package/dist/components/ui/button/button.svelte +82 -0
- package/dist/components/ui/button/button.svelte.d.ts +64 -0
- package/dist/components/ui/button/index.d.ts +2 -0
- package/dist/components/ui/button/index.js +4 -0
- package/dist/components/ui/card/card-action.svelte +20 -0
- package/dist/components/ui/card/card-action.svelte.d.ts +5 -0
- package/dist/components/ui/card/card-content.svelte +15 -0
- package/dist/components/ui/card/card-content.svelte.d.ts +5 -0
- package/dist/components/ui/card/card-description.svelte +20 -0
- package/dist/components/ui/card/card-description.svelte.d.ts +5 -0
- package/dist/components/ui/card/card-footer.svelte +20 -0
- package/dist/components/ui/card/card-footer.svelte.d.ts +5 -0
- package/dist/components/ui/card/card-header.svelte +23 -0
- package/dist/components/ui/card/card-header.svelte.d.ts +5 -0
- package/dist/components/ui/card/card-title.svelte +20 -0
- package/dist/components/ui/card/card-title.svelte.d.ts +5 -0
- package/dist/components/ui/card/card.svelte +23 -0
- package/dist/components/ui/card/card.svelte.d.ts +5 -0
- package/dist/components/ui/card/index.d.ts +8 -0
- package/dist/components/ui/card/index.js +10 -0
- package/dist/components/ui/command/command-dialog.svelte +40 -0
- package/dist/components/ui/command/command-dialog.svelte.d.ts +12 -0
- package/dist/components/ui/command/command-empty.svelte +17 -0
- package/dist/components/ui/command/command-empty.svelte.d.ts +4 -0
- package/dist/components/ui/command/command-group.svelte +30 -0
- package/dist/components/ui/command/command-group.svelte.d.ts +7 -0
- package/dist/components/ui/command/command-input.svelte +26 -0
- package/dist/components/ui/command/command-input.svelte.d.ts +4 -0
- package/dist/components/ui/command/command-item.svelte +20 -0
- package/dist/components/ui/command/command-item.svelte.d.ts +4 -0
- package/dist/components/ui/command/command-link-item.svelte +20 -0
- package/dist/components/ui/command/command-link-item.svelte.d.ts +4 -0
- package/dist/components/ui/command/command-list.svelte +17 -0
- package/dist/components/ui/command/command-list.svelte.d.ts +4 -0
- package/dist/components/ui/command/command-loading.svelte +7 -0
- package/dist/components/ui/command/command-loading.svelte.d.ts +4 -0
- package/dist/components/ui/command/command-separator.svelte +17 -0
- package/dist/components/ui/command/command-separator.svelte.d.ts +4 -0
- package/dist/components/ui/command/command-shortcut.svelte +20 -0
- package/dist/components/ui/command/command-shortcut.svelte.d.ts +5 -0
- package/dist/components/ui/command/command.svelte +28 -0
- package/dist/components/ui/command/command.svelte.d.ts +8 -0
- package/dist/components/ui/command/index.d.ts +12 -0
- package/dist/components/ui/command/index.js +14 -0
- package/dist/components/ui/dialog/dialog-close.svelte +7 -0
- package/dist/components/ui/dialog/dialog-close.svelte.d.ts +4 -0
- package/dist/components/ui/dialog/dialog-content.svelte +45 -0
- package/dist/components/ui/dialog/dialog-content.svelte.d.ts +13 -0
- package/dist/components/ui/dialog/dialog-description.svelte +17 -0
- package/dist/components/ui/dialog/dialog-description.svelte.d.ts +4 -0
- package/dist/components/ui/dialog/dialog-footer.svelte +20 -0
- package/dist/components/ui/dialog/dialog-footer.svelte.d.ts +5 -0
- package/dist/components/ui/dialog/dialog-header.svelte +20 -0
- package/dist/components/ui/dialog/dialog-header.svelte.d.ts +5 -0
- package/dist/components/ui/dialog/dialog-overlay.svelte +20 -0
- package/dist/components/ui/dialog/dialog-overlay.svelte.d.ts +4 -0
- package/dist/components/ui/dialog/dialog-portal.svelte +7 -0
- package/dist/components/ui/dialog/dialog-portal.svelte.d.ts +3 -0
- package/dist/components/ui/dialog/dialog-title.svelte +17 -0
- package/dist/components/ui/dialog/dialog-title.svelte.d.ts +4 -0
- package/dist/components/ui/dialog/dialog-trigger.svelte +7 -0
- package/dist/components/ui/dialog/dialog-trigger.svelte.d.ts +4 -0
- package/dist/components/ui/dialog/dialog.svelte +7 -0
- package/dist/components/ui/dialog/dialog.svelte.d.ts +3 -0
- package/dist/components/ui/dialog/index.d.ts +11 -0
- package/dist/components/ui/dialog/index.js +13 -0
- package/dist/components/ui/input/index.d.ts +2 -0
- package/dist/components/ui/input/index.js +4 -0
- package/dist/components/ui/input/input.svelte +52 -0
- package/dist/components/ui/input/input.svelte.d.ts +13 -0
- package/dist/components/ui/kbd/index.d.ts +3 -0
- package/dist/components/ui/kbd/index.js +5 -0
- package/dist/components/ui/kbd/kbd-group.svelte +20 -0
- package/dist/components/ui/kbd/kbd-group.svelte.d.ts +5 -0
- package/dist/components/ui/kbd/kbd.svelte +25 -0
- package/dist/components/ui/kbd/kbd.svelte.d.ts +5 -0
- package/dist/components/ui/popover/index.d.ts +6 -0
- package/dist/components/ui/popover/index.js +8 -0
- package/dist/components/ui/popover/popover-close.svelte +7 -0
- package/dist/components/ui/popover/popover-close.svelte.d.ts +4 -0
- package/dist/components/ui/popover/popover-content.svelte +31 -0
- package/dist/components/ui/popover/popover-content.svelte.d.ts +10 -0
- package/dist/components/ui/popover/popover-portal.svelte +7 -0
- package/dist/components/ui/popover/popover-portal.svelte.d.ts +3 -0
- package/dist/components/ui/popover/popover-trigger.svelte +17 -0
- package/dist/components/ui/popover/popover-trigger.svelte.d.ts +4 -0
- package/dist/components/ui/popover/popover.svelte +7 -0
- package/dist/components/ui/popover/popover.svelte.d.ts +3 -0
- package/dist/components/ui/table/index.d.ts +9 -0
- package/dist/components/ui/table/index.js +11 -0
- package/dist/components/ui/table/table-body.svelte +20 -0
- package/dist/components/ui/table/table-body.svelte.d.ts +5 -0
- package/dist/components/ui/table/table-caption.svelte +20 -0
- package/dist/components/ui/table/table-caption.svelte.d.ts +5 -0
- package/dist/components/ui/table/table-cell.svelte +23 -0
- package/dist/components/ui/table/table-cell.svelte.d.ts +5 -0
- package/dist/components/ui/table/table-footer.svelte +20 -0
- package/dist/components/ui/table/table-footer.svelte.d.ts +5 -0
- package/dist/components/ui/table/table-head.svelte +23 -0
- package/dist/components/ui/table/table-head.svelte.d.ts +5 -0
- package/dist/components/ui/table/table-header.svelte +20 -0
- package/dist/components/ui/table/table-header.svelte.d.ts +5 -0
- package/dist/components/ui/table/table-row.svelte +23 -0
- package/dist/components/ui/table/table-row.svelte.d.ts +5 -0
- package/dist/components/ui/table/table.svelte +22 -0
- package/dist/components/ui/table/table.svelte.d.ts +5 -0
- package/dist/components/ui/tabs/index.d.ts +5 -0
- package/dist/components/ui/tabs/index.js +7 -0
- package/dist/components/ui/tabs/tabs-content.svelte +17 -0
- package/dist/components/ui/tabs/tabs-content.svelte.d.ts +4 -0
- package/dist/components/ui/tabs/tabs-list.svelte +20 -0
- package/dist/components/ui/tabs/tabs-list.svelte.d.ts +4 -0
- package/dist/components/ui/tabs/tabs-trigger.svelte +20 -0
- package/dist/components/ui/tabs/tabs-trigger.svelte.d.ts +4 -0
- package/dist/components/ui/tabs/tabs.svelte +19 -0
- package/dist/components/ui/tabs/tabs.svelte.d.ts +4 -0
- package/dist/components/ui/tooltip/index.d.ts +6 -0
- package/dist/components/ui/tooltip/index.js +8 -0
- package/dist/components/ui/tooltip/tooltip-content.svelte +52 -0
- package/dist/components/ui/tooltip/tooltip-content.svelte.d.ts +11 -0
- package/dist/components/ui/tooltip/tooltip-portal.svelte +7 -0
- package/dist/components/ui/tooltip/tooltip-portal.svelte.d.ts +4 -0
- package/dist/components/ui/tooltip/tooltip-provider.svelte +7 -0
- package/dist/components/ui/tooltip/tooltip-provider.svelte.d.ts +4 -0
- package/dist/components/ui/tooltip/tooltip-trigger.svelte +7 -0
- package/dist/components/ui/tooltip/tooltip-trigger.svelte.d.ts +4 -0
- package/dist/components/ui/tooltip/tooltip.svelte +7 -0
- package/dist/components/ui/tooltip/tooltip.svelte.d.ts +4 -0
- package/dist/focus.svelte +56 -0
- package/dist/focus.svelte.d.ts +13 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.js +7 -0
- package/dist/overlay-component.svelte +19 -0
- package/dist/overlay-component.svelte.d.ts +6 -0
- package/dist/palette.svelte +132 -0
- package/dist/palette.svelte.d.ts +7 -0
- package/dist/palette.svelte.js +177 -0
- package/dist/shortcut.svelte +26 -0
- package/dist/shortcut.svelte.d.ts +8 -0
- package/dist/utils.d.ts +13 -0
- package/dist/utils.js +32 -0
- package/package.json +87 -0
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { Tabs as TabsPrimitive } from "bits-ui";
|
|
3
|
+
import { cn } from "../../../utils.js";
|
|
4
|
+
|
|
5
|
+
let {
|
|
6
|
+
ref = $bindable(null),
|
|
7
|
+
class: className,
|
|
8
|
+
...restProps
|
|
9
|
+
}: TabsPrimitive.TriggerProps = $props();
|
|
10
|
+
</script>
|
|
11
|
+
|
|
12
|
+
<TabsPrimitive.Trigger
|
|
13
|
+
bind:ref
|
|
14
|
+
data-slot="tabs-trigger"
|
|
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",
|
|
17
|
+
className
|
|
18
|
+
)}
|
|
19
|
+
{...restProps}
|
|
20
|
+
/>
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { Tabs as TabsPrimitive } from "bits-ui";
|
|
3
|
+
import { cn } from "../../../utils.js";
|
|
4
|
+
|
|
5
|
+
let {
|
|
6
|
+
ref = $bindable(null),
|
|
7
|
+
value = $bindable(""),
|
|
8
|
+
class: className,
|
|
9
|
+
...restProps
|
|
10
|
+
}: TabsPrimitive.RootProps = $props();
|
|
11
|
+
</script>
|
|
12
|
+
|
|
13
|
+
<TabsPrimitive.Root
|
|
14
|
+
bind:ref
|
|
15
|
+
bind:value
|
|
16
|
+
data-slot="tabs"
|
|
17
|
+
class={cn("flex flex-col gap-2", className)}
|
|
18
|
+
{...restProps}
|
|
19
|
+
/>
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import Root from './tooltip.svelte';
|
|
2
|
+
import Trigger from './tooltip-trigger.svelte';
|
|
3
|
+
import Content from './tooltip-content.svelte';
|
|
4
|
+
import Provider from './tooltip-provider.svelte';
|
|
5
|
+
import Portal from './tooltip-portal.svelte';
|
|
6
|
+
export { Root, Trigger, Content, Provider, Portal, Root as Tooltip, Content as TooltipContent, Trigger as TooltipTrigger, Provider as TooltipProvider, Portal as TooltipPortal };
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import Root from './tooltip.svelte';
|
|
2
|
+
import Trigger from './tooltip-trigger.svelte';
|
|
3
|
+
import Content from './tooltip-content.svelte';
|
|
4
|
+
import Provider from './tooltip-provider.svelte';
|
|
5
|
+
import Portal from './tooltip-portal.svelte';
|
|
6
|
+
export { Root, Trigger, Content, Provider, Portal,
|
|
7
|
+
//
|
|
8
|
+
Root as Tooltip, Content as TooltipContent, Trigger as TooltipTrigger, Provider as TooltipProvider, Portal as TooltipPortal };
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { Tooltip as TooltipPrimitive } from 'bits-ui';
|
|
3
|
+
import { cn } from '../../../utils.js';
|
|
4
|
+
import TooltipPortal from './tooltip-portal.svelte';
|
|
5
|
+
import type { ComponentProps } from 'svelte';
|
|
6
|
+
import type { WithoutChildrenOrChild } from '../../../utils.js';
|
|
7
|
+
|
|
8
|
+
let {
|
|
9
|
+
ref = $bindable(null),
|
|
10
|
+
class: className,
|
|
11
|
+
sideOffset = 0,
|
|
12
|
+
side = 'top',
|
|
13
|
+
children,
|
|
14
|
+
arrowClasses,
|
|
15
|
+
portalProps,
|
|
16
|
+
...restProps
|
|
17
|
+
}: TooltipPrimitive.ContentProps & {
|
|
18
|
+
arrowClasses?: string;
|
|
19
|
+
portalProps?: WithoutChildrenOrChild<ComponentProps<typeof TooltipPortal>>;
|
|
20
|
+
} = $props();
|
|
21
|
+
</script>
|
|
22
|
+
|
|
23
|
+
<TooltipPortal {...portalProps}>
|
|
24
|
+
<TooltipPrimitive.Content
|
|
25
|
+
bind:ref
|
|
26
|
+
data-slot="tooltip-content"
|
|
27
|
+
{sideOffset}
|
|
28
|
+
{side}
|
|
29
|
+
class={cn(
|
|
30
|
+
'z-50 w-fit origin-(--bits-tooltip-content-transform-origin) animate-in rounded-md bg-foreground px-3 py-1.5 text-xs text-balance text-background fade-in-0 zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-end-2 data-[side=right]:slide-in-from-start-2 data-[side=top]:slide-in-from-bottom-2 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95',
|
|
31
|
+
className
|
|
32
|
+
)}
|
|
33
|
+
{...restProps}
|
|
34
|
+
>
|
|
35
|
+
{@render children?.()}
|
|
36
|
+
<TooltipPrimitive.Arrow>
|
|
37
|
+
{#snippet child({ props })}
|
|
38
|
+
<div
|
|
39
|
+
class={cn(
|
|
40
|
+
'z-50 size-2.5 rotate-45 rounded-[2px] bg-primary',
|
|
41
|
+
'data-[side=top]:translate-x-1/2 data-[side=top]:translate-y-[calc(-50%_+_2px)]',
|
|
42
|
+
'data-[side=bottom]:-translate-x-1/2 data-[side=bottom]:-translate-y-[calc(-50%_+_1px)]',
|
|
43
|
+
'data-[side=right]:translate-x-[calc(50%_+_2px)] data-[side=right]:translate-y-1/2',
|
|
44
|
+
'data-[side=left]:-translate-y-[calc(50%_-_3px)]',
|
|
45
|
+
arrowClasses
|
|
46
|
+
)}
|
|
47
|
+
{...props}
|
|
48
|
+
></div>
|
|
49
|
+
{/snippet}
|
|
50
|
+
</TooltipPrimitive.Arrow>
|
|
51
|
+
</TooltipPrimitive.Content>
|
|
52
|
+
</TooltipPortal>
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { Tooltip as TooltipPrimitive } from 'bits-ui';
|
|
2
|
+
import TooltipPortal from './tooltip-portal.svelte';
|
|
3
|
+
import type { ComponentProps } from 'svelte';
|
|
4
|
+
import type { WithoutChildrenOrChild } from '../../../utils.js';
|
|
5
|
+
type $$ComponentProps = TooltipPrimitive.ContentProps & {
|
|
6
|
+
arrowClasses?: string;
|
|
7
|
+
portalProps?: WithoutChildrenOrChild<ComponentProps<typeof TooltipPortal>>;
|
|
8
|
+
};
|
|
9
|
+
declare const TooltipContent: import("svelte").Component<$$ComponentProps, {}, "ref">;
|
|
10
|
+
type TooltipContent = ReturnType<typeof TooltipContent>;
|
|
11
|
+
export default TooltipContent;
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { Snippet } from 'svelte';
|
|
3
|
+
import { shortcut } from './attachment.svelte';
|
|
4
|
+
import type { ClassValue } from 'svelte/elements';
|
|
5
|
+
|
|
6
|
+
let {
|
|
7
|
+
keys,
|
|
8
|
+
description,
|
|
9
|
+
element,
|
|
10
|
+
children,
|
|
11
|
+
after_focus,
|
|
12
|
+
class: className
|
|
13
|
+
}: {
|
|
14
|
+
keys: string | string[] | string[][];
|
|
15
|
+
description?: string;
|
|
16
|
+
element?: HTMLElement;
|
|
17
|
+
children: Snippet;
|
|
18
|
+
after_focus?: () => void;
|
|
19
|
+
class?: ClassValue;
|
|
20
|
+
} = $props();
|
|
21
|
+
|
|
22
|
+
let container: HTMLElement;
|
|
23
|
+
|
|
24
|
+
function focusChild() {
|
|
25
|
+
if (element) {
|
|
26
|
+
element.focus();
|
|
27
|
+
} else if (container) {
|
|
28
|
+
// Try to find the first focusable element
|
|
29
|
+
const focusable = container.querySelector<HTMLElement>(
|
|
30
|
+
'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
|
|
31
|
+
);
|
|
32
|
+
|
|
33
|
+
if (focusable) {
|
|
34
|
+
focusable.focus();
|
|
35
|
+
} else {
|
|
36
|
+
// If no focusable element found, focus the container itself
|
|
37
|
+
container.focus();
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
after_focus?.();
|
|
42
|
+
}
|
|
43
|
+
</script>
|
|
44
|
+
|
|
45
|
+
<div
|
|
46
|
+
bind:this={container}
|
|
47
|
+
tabindex="-1"
|
|
48
|
+
class={className}
|
|
49
|
+
{@attach shortcut({
|
|
50
|
+
keys,
|
|
51
|
+
description: description,
|
|
52
|
+
action: focusChild
|
|
53
|
+
})}
|
|
54
|
+
>
|
|
55
|
+
{@render children()}
|
|
56
|
+
</div>
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { Snippet } from 'svelte';
|
|
2
|
+
import type { ClassValue } from 'svelte/elements';
|
|
3
|
+
type $$ComponentProps = {
|
|
4
|
+
keys: string | string[] | string[][];
|
|
5
|
+
description?: string;
|
|
6
|
+
element?: HTMLElement;
|
|
7
|
+
children: Snippet;
|
|
8
|
+
after_focus?: () => void;
|
|
9
|
+
class?: ClassValue;
|
|
10
|
+
};
|
|
11
|
+
declare const Focus: import("svelte").Component<$$ComponentProps, {}, "">;
|
|
12
|
+
type Focus = ReturnType<typeof Focus>;
|
|
13
|
+
export default Focus;
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
// Simple index.ts to export available components
|
|
2
|
+
export { default as Palette } from './palette.svelte';
|
|
3
|
+
export { default as Shortcut } from './shortcut.svelte';
|
|
4
|
+
export { default as Focus } from './focus.svelte';
|
|
5
|
+
// Export functions
|
|
6
|
+
export { shortcut } from './attachment.svelte.js';
|
|
7
|
+
export { shortcuts } from './palette.svelte.js';
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { PressedKeys } from 'runed';
|
|
3
|
+
import Kbds from './components/kbds.svelte';
|
|
4
|
+
|
|
5
|
+
export type OverlayComponentProps = {
|
|
6
|
+
keys: string | string[] | string[][];
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
let { keys }: OverlayComponentProps = $props();
|
|
10
|
+
|
|
11
|
+
const pressed_keys = new PressedKeys();
|
|
12
|
+
const visible = $derived(pressed_keys.has('alt'));
|
|
13
|
+
</script>
|
|
14
|
+
|
|
15
|
+
{#if visible}
|
|
16
|
+
<div class="pointer-events-none absolute bottom-0 left-1/2 z-50 -translate-x-1/2 translate-y-2/3">
|
|
17
|
+
<Kbds {keys} />
|
|
18
|
+
</div>
|
|
19
|
+
{/if}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export type OverlayComponentProps = {
|
|
2
|
+
keys: string | string[] | string[][];
|
|
3
|
+
};
|
|
4
|
+
declare const OverlayComponent: import("svelte").Component<OverlayComponentProps, {}, "">;
|
|
5
|
+
type OverlayComponent = ReturnType<typeof OverlayComponent>;
|
|
6
|
+
export default OverlayComponent;
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
<script lang="ts" module>
|
|
2
|
+
export type PalettePosition =
|
|
3
|
+
| 'top-left'
|
|
4
|
+
| 'top-center'
|
|
5
|
+
| 'top-right'
|
|
6
|
+
| 'bottom-left'
|
|
7
|
+
| 'bottom-center'
|
|
8
|
+
| 'bottom-right'
|
|
9
|
+
| 'none';
|
|
10
|
+
</script>
|
|
11
|
+
|
|
12
|
+
<script lang="ts">
|
|
13
|
+
import { buttonVariants } from './components/ui/button';
|
|
14
|
+
import * as Dialog from './components/ui/dialog/index.js';
|
|
15
|
+
import * as Kbd from './components/ui/kbd/index.js';
|
|
16
|
+
import * as Table from './components/ui/table';
|
|
17
|
+
import * as Tooltip from './components/ui/tooltip';
|
|
18
|
+
import { Keyboard, ToggleLeft, ToggleRight } from '@lucide/svelte';
|
|
19
|
+
import { PressedKeys } from 'runed';
|
|
20
|
+
import { registry, slugify } from './palette.svelte.js';
|
|
21
|
+
import Shortcut from './shortcut.svelte';
|
|
22
|
+
import Kbds from './components/kbds.svelte';
|
|
23
|
+
|
|
24
|
+
let {
|
|
25
|
+
position = 'bottom-right'
|
|
26
|
+
}: {
|
|
27
|
+
position?: PalettePosition;
|
|
28
|
+
} = $props();
|
|
29
|
+
|
|
30
|
+
let open = $state(false);
|
|
31
|
+
let tooltip_open = $state(false);
|
|
32
|
+
|
|
33
|
+
const pressed_keys = new PressedKeys();
|
|
34
|
+
const all_keys = $derived(pressed_keys.all);
|
|
35
|
+
|
|
36
|
+
const filtered_shortcuts = $derived.by(() => {
|
|
37
|
+
const _all_keys = all_keys.filter((key) => ['?', '/', ' '].indexOf(key) === -1);
|
|
38
|
+
return registry.filteredShortcuts(_all_keys);
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
const positionClass = $derived.by(() => {
|
|
42
|
+
switch (position) {
|
|
43
|
+
case 'top-left':
|
|
44
|
+
return 'fixed left-4 top-4';
|
|
45
|
+
case 'top-center':
|
|
46
|
+
return 'fixed left-1/2 top-4 -translate-x-1/2';
|
|
47
|
+
case 'top-right':
|
|
48
|
+
return 'fixed right-4 top-4';
|
|
49
|
+
case 'bottom-left':
|
|
50
|
+
return 'fixed left-4 bottom-4';
|
|
51
|
+
case 'bottom-center':
|
|
52
|
+
return 'fixed left-1/2 bottom-4 -translate-x-1/2';
|
|
53
|
+
case 'bottom-right':
|
|
54
|
+
return 'fixed right-4 bottom-4';
|
|
55
|
+
case 'none':
|
|
56
|
+
return '';
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
</script>
|
|
60
|
+
|
|
61
|
+
<Shortcut keys={[['?'], ['/']]} description="Open shortcut palette" action={() => (open = !open)} />
|
|
62
|
+
|
|
63
|
+
<Tooltip.Provider delayDuration={0}>
|
|
64
|
+
<Tooltip.Root bind:open={tooltip_open}>
|
|
65
|
+
<Tooltip.Trigger
|
|
66
|
+
class={[buttonVariants({ size: 'icon-lg' }), positionClass, 'cursor-pointer']}
|
|
67
|
+
onclick={() => {
|
|
68
|
+
open = !open;
|
|
69
|
+
}}
|
|
70
|
+
>
|
|
71
|
+
<Keyboard />
|
|
72
|
+
</Tooltip.Trigger>
|
|
73
|
+
<Tooltip.Content>
|
|
74
|
+
Press <Kbd.Root>?</Kbd.Root>
|
|
75
|
+
</Tooltip.Content>
|
|
76
|
+
</Tooltip.Root>
|
|
77
|
+
</Tooltip.Provider>
|
|
78
|
+
|
|
79
|
+
<Dialog.Root bind:open>
|
|
80
|
+
<Dialog.Content>
|
|
81
|
+
<Dialog.Header>
|
|
82
|
+
<Dialog.Title>Keyboard Shortcuts</Dialog.Title>
|
|
83
|
+
<Dialog.Description class="my-8">
|
|
84
|
+
<p class="mb-4 text-sm text-muted-foreground">
|
|
85
|
+
Press any key to filter shortcuts containing that key. Matching keys will be highlighted
|
|
86
|
+
in green.
|
|
87
|
+
</p>
|
|
88
|
+
<Table.Root>
|
|
89
|
+
<Table.Header>
|
|
90
|
+
<Table.Row>
|
|
91
|
+
<Table.Head>Keys</Table.Head>
|
|
92
|
+
<Table.Head>Description</Table.Head>
|
|
93
|
+
<Table.Head class="text-right">Enabled</Table.Head>
|
|
94
|
+
</Table.Row>
|
|
95
|
+
</Table.Header>
|
|
96
|
+
<Table.Body>
|
|
97
|
+
{#each filtered_shortcuts as shortcut (slugify(shortcut.keys))}
|
|
98
|
+
<Table.Row>
|
|
99
|
+
<Table.Cell class="font-medium">
|
|
100
|
+
<Kbds keys={shortcut.keys} />
|
|
101
|
+
</Table.Cell>
|
|
102
|
+
<Table.Cell>{shortcut.description}</Table.Cell>
|
|
103
|
+
<Table.Cell class="text-right">
|
|
104
|
+
<button
|
|
105
|
+
class="inline-flex items-center justify-center rounded-md p-2 transition-colors hover:bg-accent hover:text-accent-foreground"
|
|
106
|
+
onclick={() => registry.toggle(shortcut.keys)}
|
|
107
|
+
aria-label={shortcut.enabled ? 'Disable shortcut' : 'Enable shortcut'}
|
|
108
|
+
>
|
|
109
|
+
{#if shortcut.enabled !== false}
|
|
110
|
+
<ToggleRight class="h-5 w-5 text-green-500" />
|
|
111
|
+
{:else}
|
|
112
|
+
<ToggleLeft class="h-5 w-5 text-muted-foreground" />
|
|
113
|
+
{/if}
|
|
114
|
+
</button>
|
|
115
|
+
</Table.Cell>
|
|
116
|
+
</Table.Row>
|
|
117
|
+
{:else}
|
|
118
|
+
<Table.Row>
|
|
119
|
+
<Table.Cell colspan={3} class="text-center py-4 text-sm text-muted-foreground">
|
|
120
|
+
No shortcuts containing
|
|
121
|
+
<Kbds keys={all_keys} />
|
|
122
|
+
|
|
123
|
+
.
|
|
124
|
+
</Table.Cell>
|
|
125
|
+
</Table.Row>
|
|
126
|
+
{/each}
|
|
127
|
+
</Table.Body>
|
|
128
|
+
</Table.Root>
|
|
129
|
+
</Dialog.Description>
|
|
130
|
+
</Dialog.Header>
|
|
131
|
+
</Dialog.Content>
|
|
132
|
+
</Dialog.Root>
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export type PalettePosition = 'top-left' | 'top-center' | 'top-right' | 'bottom-left' | 'bottom-center' | 'bottom-right' | 'none';
|
|
2
|
+
type $$ComponentProps = {
|
|
3
|
+
position?: PalettePosition;
|
|
4
|
+
};
|
|
5
|
+
declare const Palette: import("svelte").Component<$$ComponentProps, {}, "">;
|
|
6
|
+
type Palette = ReturnType<typeof Palette>;
|
|
7
|
+
export default Palette;
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
import { PressedKeys, activeElement } from 'runed';
|
|
2
|
+
class ShortcutRegistry {
|
|
3
|
+
shortcuts = $state({});
|
|
4
|
+
registeredCombos = $state(new Set());
|
|
5
|
+
pressedKeys = new PressedKeys();
|
|
6
|
+
cleanupCallbacks = new Map();
|
|
7
|
+
isListening = false;
|
|
8
|
+
constructor() { }
|
|
9
|
+
startListening() {
|
|
10
|
+
if (this.isListening)
|
|
11
|
+
return;
|
|
12
|
+
this.isListening = true;
|
|
13
|
+
$effect(() => {
|
|
14
|
+
this.syncKeyboardListeners();
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
normalizeKeys(keys) {
|
|
18
|
+
if (typeof keys === 'string') {
|
|
19
|
+
return [[keys]];
|
|
20
|
+
}
|
|
21
|
+
if (keys.length === 0) {
|
|
22
|
+
return [];
|
|
23
|
+
}
|
|
24
|
+
if (Array.isArray(keys[0])) {
|
|
25
|
+
return keys.map((combo) => this.sortCombo(combo));
|
|
26
|
+
}
|
|
27
|
+
return [this.sortCombo(keys)];
|
|
28
|
+
}
|
|
29
|
+
sortCombo(combo) {
|
|
30
|
+
return [...combo].sort((a, b) => a.localeCompare(b));
|
|
31
|
+
}
|
|
32
|
+
comboToString(combo) {
|
|
33
|
+
return combo.join('-');
|
|
34
|
+
}
|
|
35
|
+
slugify(keys) {
|
|
36
|
+
const normalized = this.normalizeKeys(keys);
|
|
37
|
+
return normalized
|
|
38
|
+
.map((combo) => this.comboToString(combo).toLowerCase().replace(/\s+/g, '-'))
|
|
39
|
+
.join('|');
|
|
40
|
+
}
|
|
41
|
+
checkCollision(keys, description) {
|
|
42
|
+
for (const combo of keys) {
|
|
43
|
+
const comboString = this.comboToString(combo);
|
|
44
|
+
if (this.registeredCombos.has(comboString)) {
|
|
45
|
+
console.warn(`Shortcut collision detected: "${comboString}" already registered${description ? ` (trying to register: "${description}")` : ''}`);
|
|
46
|
+
return true;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
return false;
|
|
50
|
+
}
|
|
51
|
+
add(shortcut) {
|
|
52
|
+
this.startListening();
|
|
53
|
+
const normalizedKeys = this.normalizeKeys(shortcut.keys);
|
|
54
|
+
if (normalizedKeys.length === 0) {
|
|
55
|
+
console.warn('Cannot add shortcut with no keys');
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
this.checkCollision(normalizedKeys, shortcut.description);
|
|
59
|
+
const slug = this.slugify(shortcut.keys);
|
|
60
|
+
for (const combo of normalizedKeys) {
|
|
61
|
+
const comboString = this.comboToString(combo);
|
|
62
|
+
this.registeredCombos.add(comboString);
|
|
63
|
+
}
|
|
64
|
+
this.shortcuts[slug] = {
|
|
65
|
+
...shortcut,
|
|
66
|
+
keys: normalizedKeys,
|
|
67
|
+
enabled: shortcut.enabled ?? true
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
remove(keys) {
|
|
71
|
+
const slug = this.slugify(keys);
|
|
72
|
+
const shortcut = this.shortcuts[slug];
|
|
73
|
+
if (!shortcut) {
|
|
74
|
+
console.warn(`Shortcut not found for keys: ${JSON.stringify(keys)}`);
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
for (const combo of shortcut.keys) {
|
|
78
|
+
const comboString = this.comboToString(combo);
|
|
79
|
+
this.registeredCombos.delete(comboString);
|
|
80
|
+
}
|
|
81
|
+
delete this.shortcuts[slug];
|
|
82
|
+
}
|
|
83
|
+
toggle(keys) {
|
|
84
|
+
const slug = this.slugify(keys);
|
|
85
|
+
if (this.shortcuts[slug]) {
|
|
86
|
+
this.shortcuts[slug].enabled = !this.shortcuts[slug].enabled;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
getShortcuts() {
|
|
90
|
+
this.startListening();
|
|
91
|
+
return Object.values(this.shortcuts);
|
|
92
|
+
}
|
|
93
|
+
filteredShortcuts(pressedKeys) {
|
|
94
|
+
const allShortcuts = this.getShortcuts();
|
|
95
|
+
if (pressedKeys.length === 0) {
|
|
96
|
+
return allShortcuts;
|
|
97
|
+
}
|
|
98
|
+
const filteredPressedKeys = pressedKeys.filter((key) => ['?', '/', ' ', 'escape'].indexOf(key) === -1);
|
|
99
|
+
return allShortcuts.filter((shortcut) => {
|
|
100
|
+
return shortcut.keys.some((keyCombo) => keyCombo.some((key) => filteredPressedKeys.some((pressedKey) => key.toLowerCase() === pressedKey.toLowerCase())));
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
syncKeyboardListeners() {
|
|
104
|
+
for (const [slug, cleanup] of this.cleanupCallbacks) {
|
|
105
|
+
cleanup();
|
|
106
|
+
this.cleanupCallbacks.delete(slug);
|
|
107
|
+
}
|
|
108
|
+
for (const [slug, shortcut] of Object.entries(this.shortcuts)) {
|
|
109
|
+
const cleanup = this.setupKeyboardListener(shortcut);
|
|
110
|
+
this.cleanupCallbacks.set(slug, cleanup);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
setupKeyboardListener(shortcut) {
|
|
114
|
+
for (const keyCombo of shortcut.keys) {
|
|
115
|
+
this.pressedKeys.onKeys(keyCombo, () => {
|
|
116
|
+
const target = activeElement.current;
|
|
117
|
+
const hasModifier = this.hasModifierKey(keyCombo);
|
|
118
|
+
if (shortcut.enabled && (hasModifier || !this.isTypingElement(target))) {
|
|
119
|
+
shortcut.action();
|
|
120
|
+
}
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
return () => { };
|
|
124
|
+
}
|
|
125
|
+
hasModifierKey(keys) {
|
|
126
|
+
const modifierKeys = ['control', 'ctrl', 'alt', 'meta', 'command', 'cmd'];
|
|
127
|
+
return keys.some((key) => modifierKeys.includes(key.toLowerCase()));
|
|
128
|
+
}
|
|
129
|
+
isTypingElement(element) {
|
|
130
|
+
if (!element)
|
|
131
|
+
return false;
|
|
132
|
+
const tagName = element.tagName.toLowerCase();
|
|
133
|
+
if (element.isContentEditable)
|
|
134
|
+
return true;
|
|
135
|
+
if (tagName === 'textarea')
|
|
136
|
+
return true;
|
|
137
|
+
if (tagName === 'input') {
|
|
138
|
+
const inputType = element.type.toLowerCase();
|
|
139
|
+
const textTypes = [
|
|
140
|
+
'text',
|
|
141
|
+
'password',
|
|
142
|
+
'email',
|
|
143
|
+
'search',
|
|
144
|
+
'tel',
|
|
145
|
+
'url',
|
|
146
|
+
'number',
|
|
147
|
+
'date',
|
|
148
|
+
'datetime-local',
|
|
149
|
+
'month',
|
|
150
|
+
'time',
|
|
151
|
+
'week'
|
|
152
|
+
];
|
|
153
|
+
return textTypes.includes(inputType);
|
|
154
|
+
}
|
|
155
|
+
return false;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
export const registry = new ShortcutRegistry();
|
|
159
|
+
export const shortcuts = registry.shortcuts;
|
|
160
|
+
export function add_shortcut(shortcut) {
|
|
161
|
+
registry.add(shortcut);
|
|
162
|
+
}
|
|
163
|
+
export function remove_shortcut(keys) {
|
|
164
|
+
registry.remove(keys);
|
|
165
|
+
}
|
|
166
|
+
export function toggle_shortcut(keys) {
|
|
167
|
+
registry.toggle(keys);
|
|
168
|
+
}
|
|
169
|
+
export function slugify(keys) {
|
|
170
|
+
return registry.slugify(keys);
|
|
171
|
+
}
|
|
172
|
+
export function isArrayOfArrays(keys) {
|
|
173
|
+
return Array.isArray(keys) && keys.length > 0 && Array.isArray(keys[0]);
|
|
174
|
+
}
|
|
175
|
+
export function normalizeKeys(keys) {
|
|
176
|
+
return registry.normalizeKeys(keys);
|
|
177
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { onMount } from 'svelte';
|
|
3
|
+
import { add_shortcut, remove_shortcut } from './palette.svelte.js';
|
|
4
|
+
|
|
5
|
+
let {
|
|
6
|
+
keys,
|
|
7
|
+
description,
|
|
8
|
+
action
|
|
9
|
+
}: {
|
|
10
|
+
keys: string | string[] | string[][];
|
|
11
|
+
description?: string;
|
|
12
|
+
action: () => void;
|
|
13
|
+
} = $props();
|
|
14
|
+
|
|
15
|
+
onMount(() => {
|
|
16
|
+
add_shortcut({
|
|
17
|
+
keys,
|
|
18
|
+
description,
|
|
19
|
+
action
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
return () => {
|
|
23
|
+
remove_shortcut(keys);
|
|
24
|
+
};
|
|
25
|
+
});
|
|
26
|
+
</script>
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
type $$ComponentProps = {
|
|
2
|
+
keys: string | string[] | string[][];
|
|
3
|
+
description?: string;
|
|
4
|
+
action: () => void;
|
|
5
|
+
};
|
|
6
|
+
declare const Shortcut: import("svelte").Component<$$ComponentProps, {}, "">;
|
|
7
|
+
type Shortcut = ReturnType<typeof Shortcut>;
|
|
8
|
+
export default Shortcut;
|