sv5ui 0.0.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.
- package/LICENSE +21 -0
- package/README.md +124 -0
- package/dist/Alert/Alert.svelte +120 -0
- package/dist/Alert/Alert.svelte.d.ts +5 -0
- package/dist/Alert/alert.types.d.ts +99 -0
- package/dist/Alert/alert.types.js +1 -0
- package/dist/Alert/alert.variants.d.ts +213 -0
- package/dist/Alert/alert.variants.js +293 -0
- package/dist/Alert/index.d.ts +2 -0
- package/dist/Alert/index.js +1 -0
- package/dist/Avatar/Avatar.svelte +70 -0
- package/dist/Avatar/Avatar.svelte.d.ts +6 -0
- package/dist/Avatar/avatar.types.d.ts +40 -0
- package/dist/Avatar/avatar.types.js +1 -0
- package/dist/Avatar/avatar.variants.d.ts +178 -0
- package/dist/Avatar/avatar.variants.js +28 -0
- package/dist/Avatar/index.d.ts +2 -0
- package/dist/Avatar/index.js +1 -0
- package/dist/AvatarGroup/AvatarGroup.svelte +66 -0
- package/dist/AvatarGroup/AvatarGroup.svelte.d.ts +5 -0
- package/dist/AvatarGroup/avatar-group.types.d.ts +34 -0
- package/dist/AvatarGroup/avatar-group.types.js +1 -0
- package/dist/AvatarGroup/avatar-group.variants.d.ts +173 -0
- package/dist/AvatarGroup/avatar-group.variants.js +45 -0
- package/dist/AvatarGroup/index.d.ts +2 -0
- package/dist/AvatarGroup/index.js +1 -0
- package/dist/Badge/Badge.svelte +74 -0
- package/dist/Badge/Badge.svelte.d.ts +5 -0
- package/dist/Badge/badge.types.d.ts +74 -0
- package/dist/Badge/badge.types.js +1 -0
- package/dist/Badge/badge.variants.d.ts +303 -0
- package/dist/Badge/badge.variants.js +245 -0
- package/dist/Badge/index.d.ts +2 -0
- package/dist/Badge/index.js +1 -0
- package/dist/Button/Button.svelte +103 -0
- package/dist/Button/Button.svelte.d.ts +6 -0
- package/dist/Button/button.types.d.ts +97 -0
- package/dist/Button/button.types.js +1 -0
- package/dist/Button/button.variants.d.ts +388 -0
- package/dist/Button/button.variants.js +461 -0
- package/dist/Button/index.d.ts +2 -0
- package/dist/Button/index.js +1 -0
- package/dist/Card/Card.svelte +53 -0
- package/dist/Card/Card.svelte.d.ts +5 -0
- package/dist/Card/card.types.d.ts +32 -0
- package/dist/Card/card.types.js +1 -0
- package/dist/Card/card.variants.d.ts +108 -0
- package/dist/Card/card.variants.js +32 -0
- package/dist/Card/index.d.ts +2 -0
- package/dist/Card/index.js +1 -0
- package/dist/Chip/Chip.svelte +50 -0
- package/dist/Chip/Chip.svelte.d.ts +5 -0
- package/dist/Chip/chip.types.d.ts +62 -0
- package/dist/Chip/chip.types.js +1 -0
- package/dist/Chip/chip.variants.d.ts +328 -0
- package/dist/Chip/chip.variants.js +58 -0
- package/dist/Chip/index.d.ts +2 -0
- package/dist/Chip/index.js +1 -0
- package/dist/Container/Container.svelte +27 -0
- package/dist/Container/Container.svelte.d.ts +5 -0
- package/dist/Container/container.types.d.ts +19 -0
- package/dist/Container/container.types.js +1 -0
- package/dist/Container/container.variants.d.ts +29 -0
- package/dist/Container/container.variants.js +9 -0
- package/dist/Container/index.d.ts +2 -0
- package/dist/Container/index.js +1 -0
- package/dist/Empty/Empty.svelte +83 -0
- package/dist/Empty/Empty.svelte.d.ts +5 -0
- package/dist/Empty/empty.types.d.ts +74 -0
- package/dist/Empty/empty.types.js +1 -0
- package/dist/Empty/empty.variants.d.ts +288 -0
- package/dist/Empty/empty.variants.js +364 -0
- package/dist/Empty/index.d.ts +2 -0
- package/dist/Empty/index.js +1 -0
- package/dist/Icon/Icon.svelte +40 -0
- package/dist/Icon/Icon.svelte.d.ts +6 -0
- package/dist/Icon/icon.types.d.ts +37 -0
- package/dist/Icon/icon.types.js +1 -0
- package/dist/Icon/index.d.ts +2 -0
- package/dist/Icon/index.js +1 -0
- package/dist/Kbd/Kbd.svelte +35 -0
- package/dist/Kbd/Kbd.svelte.d.ts +5 -0
- package/dist/Kbd/index.d.ts +3 -0
- package/dist/Kbd/index.js +2 -0
- package/dist/Kbd/kbd.types.d.ts +77 -0
- package/dist/Kbd/kbd.types.js +1 -0
- package/dist/Kbd/kbd.variants.d.ts +118 -0
- package/dist/Kbd/kbd.variants.js +96 -0
- package/dist/Kbd/useKbd.svelte.d.ts +29 -0
- package/dist/Kbd/useKbd.svelte.js +245 -0
- package/dist/Link/Link.svelte +117 -0
- package/dist/Link/Link.svelte.d.ts +5 -0
- package/dist/Link/index.d.ts +2 -0
- package/dist/Link/index.js +1 -0
- package/dist/Link/link.types.d.ts +68 -0
- package/dist/Link/link.types.js +1 -0
- package/dist/Link/link.variants.d.ts +138 -0
- package/dist/Link/link.variants.js +145 -0
- package/dist/Progress/Progress.svelte +89 -0
- package/dist/Progress/Progress.svelte.d.ts +6 -0
- package/dist/Progress/index.d.ts +2 -0
- package/dist/Progress/index.js +1 -0
- package/dist/Progress/progress.types.d.ts +63 -0
- package/dist/Progress/progress.types.js +1 -0
- package/dist/Progress/progress.variants.d.ts +483 -0
- package/dist/Progress/progress.variants.js +190 -0
- package/dist/Separator/Separator.svelte +67 -0
- package/dist/Separator/Separator.svelte.d.ts +6 -0
- package/dist/Separator/index.d.ts +2 -0
- package/dist/Separator/index.js +1 -0
- package/dist/Separator/separator.types.d.ts +48 -0
- package/dist/Separator/separator.types.js +1 -0
- package/dist/Separator/separator.variants.d.ts +488 -0
- package/dist/Separator/separator.variants.js +104 -0
- package/dist/Skeleton/Skeleton.svelte +31 -0
- package/dist/Skeleton/Skeleton.svelte.d.ts +5 -0
- package/dist/Skeleton/index.d.ts +2 -0
- package/dist/Skeleton/index.js +1 -0
- package/dist/Skeleton/skeleton.types.d.ts +18 -0
- package/dist/Skeleton/skeleton.types.js +1 -0
- package/dist/Skeleton/skeleton.variants.d.ts +18 -0
- package/dist/Skeleton/skeleton.variants.js +12 -0
- package/dist/Timeline/Timeline.svelte +106 -0
- package/dist/Timeline/Timeline.svelte.d.ts +5 -0
- package/dist/Timeline/index.d.ts +2 -0
- package/dist/Timeline/index.js +1 -0
- package/dist/Timeline/timeline.types.d.ts +130 -0
- package/dist/Timeline/timeline.types.js +1 -0
- package/dist/Timeline/timeline.variants.d.ts +413 -0
- package/dist/Timeline/timeline.variants.js +121 -0
- package/dist/User/User.svelte +94 -0
- package/dist/User/User.svelte.d.ts +5 -0
- package/dist/User/index.d.ts +2 -0
- package/dist/User/index.js +1 -0
- package/dist/User/user.types.d.ts +74 -0
- package/dist/User/user.types.js +1 -0
- package/dist/User/user.variants.d.ts +308 -0
- package/dist/User/user.variants.js +73 -0
- package/dist/config.d.ts +59 -0
- package/dist/config.js +94 -0
- package/dist/index.d.ts +19 -0
- package/dist/index.js +20 -0
- package/dist/theme.css +410 -0
- package/package.json +104 -0
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import { type VariantProps } from 'tailwind-variants';
|
|
2
|
+
export declare const kbdVariants: import("tailwind-variants").TVReturnType<{
|
|
3
|
+
color: {
|
|
4
|
+
primary: string;
|
|
5
|
+
secondary: string;
|
|
6
|
+
success: string;
|
|
7
|
+
warning: string;
|
|
8
|
+
error: string;
|
|
9
|
+
info: string;
|
|
10
|
+
surface: string;
|
|
11
|
+
};
|
|
12
|
+
size: {
|
|
13
|
+
sm: string;
|
|
14
|
+
md: string;
|
|
15
|
+
lg: string;
|
|
16
|
+
};
|
|
17
|
+
variant: {
|
|
18
|
+
solid: string;
|
|
19
|
+
outline: string;
|
|
20
|
+
soft: string;
|
|
21
|
+
subtle: string;
|
|
22
|
+
};
|
|
23
|
+
}, undefined, "inline-flex items-center justify-center px-1 rounded-sm font-medium font-sans uppercase", {
|
|
24
|
+
color: {
|
|
25
|
+
primary: string;
|
|
26
|
+
secondary: string;
|
|
27
|
+
success: string;
|
|
28
|
+
warning: string;
|
|
29
|
+
error: string;
|
|
30
|
+
info: string;
|
|
31
|
+
surface: string;
|
|
32
|
+
};
|
|
33
|
+
size: {
|
|
34
|
+
sm: string;
|
|
35
|
+
md: string;
|
|
36
|
+
lg: string;
|
|
37
|
+
};
|
|
38
|
+
variant: {
|
|
39
|
+
solid: string;
|
|
40
|
+
outline: string;
|
|
41
|
+
soft: string;
|
|
42
|
+
subtle: string;
|
|
43
|
+
};
|
|
44
|
+
}, undefined, import("tailwind-variants").TVReturnType<{
|
|
45
|
+
color: {
|
|
46
|
+
primary: string;
|
|
47
|
+
secondary: string;
|
|
48
|
+
success: string;
|
|
49
|
+
warning: string;
|
|
50
|
+
error: string;
|
|
51
|
+
info: string;
|
|
52
|
+
surface: string;
|
|
53
|
+
};
|
|
54
|
+
size: {
|
|
55
|
+
sm: string;
|
|
56
|
+
md: string;
|
|
57
|
+
lg: string;
|
|
58
|
+
};
|
|
59
|
+
variant: {
|
|
60
|
+
solid: string;
|
|
61
|
+
outline: string;
|
|
62
|
+
soft: string;
|
|
63
|
+
subtle: string;
|
|
64
|
+
};
|
|
65
|
+
}, undefined, "inline-flex items-center justify-center px-1 rounded-sm font-medium font-sans uppercase", unknown, unknown, undefined>>;
|
|
66
|
+
/** Static key symbols (same across all platforms) */
|
|
67
|
+
export declare const kbdKeysMap: Record<string, string>;
|
|
68
|
+
/** Platform-specific key mappings */
|
|
69
|
+
export declare const kbdKeysPlatformMap: Record<string, {
|
|
70
|
+
mac: string;
|
|
71
|
+
other: string;
|
|
72
|
+
}>;
|
|
73
|
+
export type KbdVariantProps = VariantProps<typeof kbdVariants>;
|
|
74
|
+
export declare const kbdDefaults: {
|
|
75
|
+
defaultVariants: import("tailwind-variants").TVDefaultVariants<{
|
|
76
|
+
color: {
|
|
77
|
+
primary: string;
|
|
78
|
+
secondary: string;
|
|
79
|
+
success: string;
|
|
80
|
+
warning: string;
|
|
81
|
+
error: string;
|
|
82
|
+
info: string;
|
|
83
|
+
surface: string;
|
|
84
|
+
};
|
|
85
|
+
size: {
|
|
86
|
+
sm: string;
|
|
87
|
+
md: string;
|
|
88
|
+
lg: string;
|
|
89
|
+
};
|
|
90
|
+
variant: {
|
|
91
|
+
solid: string;
|
|
92
|
+
outline: string;
|
|
93
|
+
soft: string;
|
|
94
|
+
subtle: string;
|
|
95
|
+
};
|
|
96
|
+
}, undefined, {
|
|
97
|
+
color: {
|
|
98
|
+
primary: string;
|
|
99
|
+
secondary: string;
|
|
100
|
+
success: string;
|
|
101
|
+
warning: string;
|
|
102
|
+
error: string;
|
|
103
|
+
info: string;
|
|
104
|
+
surface: string;
|
|
105
|
+
};
|
|
106
|
+
size: {
|
|
107
|
+
sm: string;
|
|
108
|
+
md: string;
|
|
109
|
+
lg: string;
|
|
110
|
+
};
|
|
111
|
+
variant: {
|
|
112
|
+
solid: string;
|
|
113
|
+
outline: string;
|
|
114
|
+
soft: string;
|
|
115
|
+
subtle: string;
|
|
116
|
+
};
|
|
117
|
+
}, undefined>;
|
|
118
|
+
};
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { tv } from 'tailwind-variants';
|
|
2
|
+
export const kbdVariants = tv({
|
|
3
|
+
base: 'inline-flex items-center justify-center px-1 rounded-sm font-medium font-sans uppercase',
|
|
4
|
+
variants: {
|
|
5
|
+
color: {
|
|
6
|
+
primary: '',
|
|
7
|
+
secondary: '',
|
|
8
|
+
success: '',
|
|
9
|
+
warning: '',
|
|
10
|
+
error: '',
|
|
11
|
+
info: '',
|
|
12
|
+
surface: ''
|
|
13
|
+
},
|
|
14
|
+
size: {
|
|
15
|
+
sm: 'h-4 min-w-4 text-[10px]',
|
|
16
|
+
md: 'h-5 min-w-5 text-[11px]',
|
|
17
|
+
lg: 'h-6 min-w-6 text-[12px]'
|
|
18
|
+
},
|
|
19
|
+
variant: {
|
|
20
|
+
solid: '',
|
|
21
|
+
outline: 'ring ring-inset',
|
|
22
|
+
soft: '',
|
|
23
|
+
subtle: 'ring ring-inset'
|
|
24
|
+
}
|
|
25
|
+
},
|
|
26
|
+
compoundVariants: [
|
|
27
|
+
// Solid variants
|
|
28
|
+
{ color: 'primary', variant: 'solid', class: 'bg-primary text-on-primary' },
|
|
29
|
+
{ color: 'secondary', variant: 'solid', class: 'bg-secondary text-on-secondary' },
|
|
30
|
+
{ color: 'success', variant: 'solid', class: 'bg-success text-on-success' },
|
|
31
|
+
{ color: 'warning', variant: 'solid', class: 'bg-warning text-on-warning' },
|
|
32
|
+
{ color: 'error', variant: 'solid', class: 'bg-error text-on-error' },
|
|
33
|
+
{ color: 'info', variant: 'solid', class: 'bg-info text-on-info' },
|
|
34
|
+
{ color: 'surface', variant: 'solid', class: 'bg-inverse-surface text-inverse-on-surface' },
|
|
35
|
+
// Outline variants
|
|
36
|
+
{ color: 'primary', variant: 'outline', class: 'ring-primary/50 text-primary' },
|
|
37
|
+
{ color: 'secondary', variant: 'outline', class: 'ring-secondary/50 text-secondary' },
|
|
38
|
+
{ color: 'success', variant: 'outline', class: 'ring-success/50 text-success' },
|
|
39
|
+
{ color: 'warning', variant: 'outline', class: 'ring-warning/50 text-warning' },
|
|
40
|
+
{ color: 'error', variant: 'outline', class: 'ring-error/50 text-error' },
|
|
41
|
+
{ color: 'info', variant: 'outline', class: 'ring-info/50 text-info' },
|
|
42
|
+
{ color: 'surface', variant: 'outline', class: 'ring-outline-variant text-on-surface-variant' },
|
|
43
|
+
// Soft variants
|
|
44
|
+
{ color: 'primary', variant: 'soft', class: 'bg-primary-container text-on-primary-container' },
|
|
45
|
+
{ color: 'secondary', variant: 'soft', class: 'bg-secondary-container text-on-secondary-container' },
|
|
46
|
+
{ color: 'success', variant: 'soft', class: 'bg-success-container text-on-success-container' },
|
|
47
|
+
{ color: 'warning', variant: 'soft', class: 'bg-warning-container text-on-warning-container' },
|
|
48
|
+
{ color: 'error', variant: 'soft', class: 'bg-error-container text-on-error-container' },
|
|
49
|
+
{ color: 'info', variant: 'soft', class: 'bg-info-container text-on-info-container' },
|
|
50
|
+
{ color: 'surface', variant: 'soft', class: 'bg-surface-container-highest text-on-surface' },
|
|
51
|
+
// Subtle variants
|
|
52
|
+
{ color: 'primary', variant: 'subtle', class: 'ring-primary/25 bg-primary-container text-on-primary-container' },
|
|
53
|
+
{ color: 'secondary', variant: 'subtle', class: 'ring-secondary/25 bg-secondary-container text-on-secondary-container' },
|
|
54
|
+
{ color: 'success', variant: 'subtle', class: 'ring-success/25 bg-success-container text-on-success-container' },
|
|
55
|
+
{ color: 'warning', variant: 'subtle', class: 'ring-warning/25 bg-warning-container text-on-warning-container' },
|
|
56
|
+
{ color: 'error', variant: 'subtle', class: 'ring-error/25 bg-error-container text-on-error-container' },
|
|
57
|
+
{ color: 'info', variant: 'subtle', class: 'ring-info/25 bg-info-container text-on-info-container' },
|
|
58
|
+
{ color: 'surface', variant: 'subtle', class: 'ring-outline-variant bg-surface-container-highest text-on-surface' }
|
|
59
|
+
],
|
|
60
|
+
defaultVariants: {
|
|
61
|
+
color: 'surface',
|
|
62
|
+
size: 'md',
|
|
63
|
+
variant: 'outline'
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
/** Static key symbols (same across all platforms) */
|
|
67
|
+
export const kbdKeysMap = {
|
|
68
|
+
command: '⌘',
|
|
69
|
+
shift: '⇧',
|
|
70
|
+
control: '⌃',
|
|
71
|
+
option: '⌥',
|
|
72
|
+
enter: '↵',
|
|
73
|
+
delete: '⌦',
|
|
74
|
+
backspace: '⌫',
|
|
75
|
+
escape: 'Esc',
|
|
76
|
+
tab: '⇥',
|
|
77
|
+
capslock: '⇪',
|
|
78
|
+
arrowup: '↑',
|
|
79
|
+
arrowright: '→',
|
|
80
|
+
arrowdown: '↓',
|
|
81
|
+
arrowleft: '←',
|
|
82
|
+
pageup: '⇞',
|
|
83
|
+
pagedown: '⇟',
|
|
84
|
+
home: '↖',
|
|
85
|
+
end: '↘',
|
|
86
|
+
space: '␣'
|
|
87
|
+
};
|
|
88
|
+
/** Platform-specific key mappings */
|
|
89
|
+
export const kbdKeysPlatformMap = {
|
|
90
|
+
meta: { mac: '⌘', other: 'Ctrl' },
|
|
91
|
+
ctrl: { mac: '⌃', other: 'Ctrl' },
|
|
92
|
+
alt: { mac: '⌥', other: 'Alt' }
|
|
93
|
+
};
|
|
94
|
+
export const kbdDefaults = {
|
|
95
|
+
defaultVariants: kbdVariants.defaultVariants
|
|
96
|
+
};
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { kbdKeysMap, kbdKeysPlatformMap } from './kbd.variants.js';
|
|
2
|
+
import type { UseKbdOptions, UseKbdReturn } from './kbd.types.js';
|
|
3
|
+
/** @internal */
|
|
4
|
+
export declare const __resetPlatformCache: () => void;
|
|
5
|
+
export declare const isMac: boolean;
|
|
6
|
+
export declare function resolveKey(value: string | undefined): string | null;
|
|
7
|
+
export declare function resolveShortcut(shortcut: string | undefined): string[];
|
|
8
|
+
export declare function formatShortcut(shortcut: string | undefined, separator?: string): string;
|
|
9
|
+
export declare function normalizeKey(eventKey: string): string;
|
|
10
|
+
export declare function parseShortcutBinding(shortcut: string): {
|
|
11
|
+
modifiers: {
|
|
12
|
+
ctrl: boolean;
|
|
13
|
+
shift: boolean;
|
|
14
|
+
alt: boolean;
|
|
15
|
+
meta: boolean;
|
|
16
|
+
};
|
|
17
|
+
key: string;
|
|
18
|
+
};
|
|
19
|
+
export declare function matchesShortcut(event: KeyboardEvent, binding: {
|
|
20
|
+
modifiers: {
|
|
21
|
+
ctrl: boolean;
|
|
22
|
+
shift: boolean;
|
|
23
|
+
alt: boolean;
|
|
24
|
+
meta: boolean;
|
|
25
|
+
};
|
|
26
|
+
key: string;
|
|
27
|
+
}): boolean;
|
|
28
|
+
export declare function useKbd(options?: UseKbdOptions): UseKbdReturn;
|
|
29
|
+
export { kbdKeysMap, kbdKeysPlatformMap };
|
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
import { SvelteSet } from 'svelte/reactivity';
|
|
2
|
+
import { kbdKeysMap, kbdKeysPlatformMap } from './kbd.variants.js';
|
|
3
|
+
let cachedIsMac;
|
|
4
|
+
const detectPlatform = () => {
|
|
5
|
+
if (cachedIsMac === undefined) {
|
|
6
|
+
cachedIsMac = typeof navigator !== 'undefined' && /Macintosh|Mac OS|iPhone|iPad|iPod/i.test(navigator.userAgent);
|
|
7
|
+
}
|
|
8
|
+
return cachedIsMac;
|
|
9
|
+
};
|
|
10
|
+
/** @internal */
|
|
11
|
+
export const __resetPlatformCache = () => {
|
|
12
|
+
cachedIsMac = undefined;
|
|
13
|
+
};
|
|
14
|
+
export const isMac = detectPlatform();
|
|
15
|
+
export function resolveKey(value) {
|
|
16
|
+
if (!value)
|
|
17
|
+
return null;
|
|
18
|
+
const key = value.toLowerCase();
|
|
19
|
+
if (key in kbdKeysPlatformMap) {
|
|
20
|
+
return isMac ? kbdKeysPlatformMap[key].mac : kbdKeysPlatformMap[key].other;
|
|
21
|
+
}
|
|
22
|
+
return kbdKeysMap[key] ?? value;
|
|
23
|
+
}
|
|
24
|
+
export function resolveShortcut(shortcut) {
|
|
25
|
+
if (!shortcut)
|
|
26
|
+
return [];
|
|
27
|
+
return shortcut
|
|
28
|
+
.split('+')
|
|
29
|
+
.map((key) => resolveKey(key.trim()))
|
|
30
|
+
.filter((key) => key !== null);
|
|
31
|
+
}
|
|
32
|
+
export function formatShortcut(shortcut, separator = ' + ') {
|
|
33
|
+
return resolveShortcut(shortcut).join(separator);
|
|
34
|
+
}
|
|
35
|
+
const KEY_NORMALIZE = {
|
|
36
|
+
control: 'ctrl',
|
|
37
|
+
' ': 'space'
|
|
38
|
+
};
|
|
39
|
+
export function normalizeKey(eventKey) {
|
|
40
|
+
const lower = eventKey.toLowerCase();
|
|
41
|
+
return KEY_NORMALIZE[lower] ?? lower;
|
|
42
|
+
}
|
|
43
|
+
const CAPTURABLE_MODIFIERS = { alt: true, meta: true };
|
|
44
|
+
const MODIFIER_PROPS = [
|
|
45
|
+
['ctrl', 'ctrlKey'],
|
|
46
|
+
['shift', 'shiftKey'],
|
|
47
|
+
['alt', 'altKey'],
|
|
48
|
+
['meta', 'metaKey']
|
|
49
|
+
];
|
|
50
|
+
const kbdRegistry = {
|
|
51
|
+
handlers: [],
|
|
52
|
+
active: false,
|
|
53
|
+
register(cb) {
|
|
54
|
+
this.handlers.push(cb);
|
|
55
|
+
if (!this.active && typeof window !== 'undefined') {
|
|
56
|
+
window.addEventListener('keydown', this._onKeyDown);
|
|
57
|
+
window.addEventListener('keyup', this._onKeyUp);
|
|
58
|
+
window.addEventListener('blur', this._onBlur);
|
|
59
|
+
document.addEventListener('visibilitychange', this._onVisibility);
|
|
60
|
+
this.active = true;
|
|
61
|
+
}
|
|
62
|
+
},
|
|
63
|
+
unregister(cb) {
|
|
64
|
+
const idx = this.handlers.indexOf(cb);
|
|
65
|
+
if (idx !== -1)
|
|
66
|
+
this.handlers.splice(idx, 1);
|
|
67
|
+
if (this.handlers.length === 0 && this.active) {
|
|
68
|
+
window.removeEventListener('keydown', this._onKeyDown);
|
|
69
|
+
window.removeEventListener('keyup', this._onKeyUp);
|
|
70
|
+
window.removeEventListener('blur', this._onBlur);
|
|
71
|
+
document.removeEventListener('visibilitychange', this._onVisibility);
|
|
72
|
+
this.active = false;
|
|
73
|
+
}
|
|
74
|
+
},
|
|
75
|
+
_onKeyDown(event) {
|
|
76
|
+
for (let i = 0; i < kbdRegistry.handlers.length; i++) {
|
|
77
|
+
kbdRegistry.handlers[i].keydown(event);
|
|
78
|
+
}
|
|
79
|
+
},
|
|
80
|
+
_onKeyUp(event) {
|
|
81
|
+
for (let i = 0; i < kbdRegistry.handlers.length; i++) {
|
|
82
|
+
kbdRegistry.handlers[i].keyup(event);
|
|
83
|
+
}
|
|
84
|
+
},
|
|
85
|
+
_onBlur() {
|
|
86
|
+
for (let i = 0; i < kbdRegistry.handlers.length; i++) {
|
|
87
|
+
kbdRegistry.handlers[i].clear();
|
|
88
|
+
}
|
|
89
|
+
},
|
|
90
|
+
_onVisibility() {
|
|
91
|
+
if (document.hidden)
|
|
92
|
+
kbdRegistry._onBlur();
|
|
93
|
+
}
|
|
94
|
+
};
|
|
95
|
+
export function parseShortcutBinding(shortcut) {
|
|
96
|
+
const parts = shortcut.toLowerCase().split('+').map((s) => s.trim()).filter(Boolean);
|
|
97
|
+
const modifiers = { ctrl: false, shift: false, alt: false, meta: false };
|
|
98
|
+
let key = '';
|
|
99
|
+
for (const part of parts) {
|
|
100
|
+
if (part === 'ctrl' || part === 'control') {
|
|
101
|
+
modifiers.ctrl = true;
|
|
102
|
+
}
|
|
103
|
+
else if (part === 'shift') {
|
|
104
|
+
modifiers.shift = true;
|
|
105
|
+
}
|
|
106
|
+
else if (part === 'alt') {
|
|
107
|
+
modifiers.alt = true;
|
|
108
|
+
}
|
|
109
|
+
else if (part === 'meta' || part === 'cmd' || part === 'command') {
|
|
110
|
+
modifiers.meta = true;
|
|
111
|
+
}
|
|
112
|
+
else {
|
|
113
|
+
key = part;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
return { modifiers, key };
|
|
117
|
+
}
|
|
118
|
+
export function matchesShortcut(event, binding) {
|
|
119
|
+
if (normalizeKey(event.key) !== binding.key)
|
|
120
|
+
return false;
|
|
121
|
+
if (event.ctrlKey !== binding.modifiers.ctrl)
|
|
122
|
+
return false;
|
|
123
|
+
if (event.shiftKey !== binding.modifiers.shift)
|
|
124
|
+
return false;
|
|
125
|
+
if (event.altKey !== binding.modifiers.alt)
|
|
126
|
+
return false;
|
|
127
|
+
if (event.metaKey !== binding.modifiers.meta)
|
|
128
|
+
return false;
|
|
129
|
+
return true;
|
|
130
|
+
}
|
|
131
|
+
function parseShortcuts(shortcuts) {
|
|
132
|
+
return Object.entries(shortcuts).map(([shortcut, callback]) => ({
|
|
133
|
+
...parseShortcutBinding(shortcut),
|
|
134
|
+
callback
|
|
135
|
+
}));
|
|
136
|
+
}
|
|
137
|
+
export function useKbd(options = {}) {
|
|
138
|
+
const _pressedKeys = new SvelteSet();
|
|
139
|
+
let _shortcutsRef;
|
|
140
|
+
let _parsedShortcuts = [];
|
|
141
|
+
if (typeof options.shortcuts !== 'function' && options.shortcuts) {
|
|
142
|
+
_shortcutsRef = options.shortcuts;
|
|
143
|
+
_parsedShortcuts = parseShortcuts(options.shortcuts);
|
|
144
|
+
}
|
|
145
|
+
function getParsedShortcuts() {
|
|
146
|
+
if (typeof options.shortcuts !== 'function')
|
|
147
|
+
return _parsedShortcuts;
|
|
148
|
+
const current = options.shortcuts();
|
|
149
|
+
if (current !== _shortcutsRef) {
|
|
150
|
+
_shortcutsRef = current;
|
|
151
|
+
_parsedShortcuts = parseShortcuts(current);
|
|
152
|
+
}
|
|
153
|
+
return _parsedShortcuts;
|
|
154
|
+
}
|
|
155
|
+
function getEnabled() {
|
|
156
|
+
return typeof options.enabled === 'function' ? options.enabled() : (options.enabled ?? true);
|
|
157
|
+
}
|
|
158
|
+
function getTarget() {
|
|
159
|
+
const t = typeof options.target === 'function' ? options.target() : options.target;
|
|
160
|
+
return t ?? (typeof window !== 'undefined' ? window : null);
|
|
161
|
+
}
|
|
162
|
+
/** Sync modifier state from event booleans to fix missed keyup (e.g. Alt+Tab) */
|
|
163
|
+
function reconcileModifiers(event) {
|
|
164
|
+
for (const [key, prop] of MODIFIER_PROPS) {
|
|
165
|
+
if (event[prop])
|
|
166
|
+
_pressedKeys.add(key);
|
|
167
|
+
else
|
|
168
|
+
_pressedKeys.delete(key);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
function handleKeyDown(event) {
|
|
172
|
+
if (!getEnabled())
|
|
173
|
+
return;
|
|
174
|
+
reconcileModifiers(event);
|
|
175
|
+
const key = normalizeKey(event.key);
|
|
176
|
+
_pressedKeys.add(key);
|
|
177
|
+
if (options.captureModifiers && key in CAPTURABLE_MODIFIERS) {
|
|
178
|
+
event.preventDefault();
|
|
179
|
+
}
|
|
180
|
+
if (event.repeat && !options.repeat)
|
|
181
|
+
return;
|
|
182
|
+
const bindings = getParsedShortcuts();
|
|
183
|
+
for (const binding of bindings) {
|
|
184
|
+
if (matchesShortcut(event, binding)) {
|
|
185
|
+
if (options.preventDefault !== false) {
|
|
186
|
+
event.preventDefault();
|
|
187
|
+
}
|
|
188
|
+
binding.callback(event);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
function handleKeyUp(event) {
|
|
193
|
+
if (!getEnabled())
|
|
194
|
+
return;
|
|
195
|
+
reconcileModifiers(event);
|
|
196
|
+
const key = normalizeKey(event.key);
|
|
197
|
+
_pressedKeys.delete(key);
|
|
198
|
+
}
|
|
199
|
+
function handleClear() {
|
|
200
|
+
_pressedKeys.clear();
|
|
201
|
+
}
|
|
202
|
+
$effect(() => {
|
|
203
|
+
if (!getEnabled()) {
|
|
204
|
+
_pressedKeys.clear();
|
|
205
|
+
return;
|
|
206
|
+
}
|
|
207
|
+
const target = getTarget();
|
|
208
|
+
if (!target)
|
|
209
|
+
return;
|
|
210
|
+
const isWindowTarget = typeof window !== 'undefined' && target === window;
|
|
211
|
+
const callbacks = {
|
|
212
|
+
keydown: handleKeyDown,
|
|
213
|
+
keyup: handleKeyUp,
|
|
214
|
+
clear: handleClear
|
|
215
|
+
};
|
|
216
|
+
if (isWindowTarget) {
|
|
217
|
+
kbdRegistry.register(callbacks);
|
|
218
|
+
return () => {
|
|
219
|
+
kbdRegistry.unregister(callbacks);
|
|
220
|
+
_pressedKeys.clear();
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
else {
|
|
224
|
+
const clearOnly = { keydown: () => { }, keyup: () => { }, clear: handleClear };
|
|
225
|
+
target.addEventListener('keydown', handleKeyDown);
|
|
226
|
+
target.addEventListener('keyup', handleKeyUp);
|
|
227
|
+
kbdRegistry.register(clearOnly);
|
|
228
|
+
return () => {
|
|
229
|
+
target.removeEventListener('keydown', handleKeyDown);
|
|
230
|
+
target.removeEventListener('keyup', handleKeyUp);
|
|
231
|
+
kbdRegistry.unregister(clearOnly);
|
|
232
|
+
_pressedKeys.clear();
|
|
233
|
+
};
|
|
234
|
+
}
|
|
235
|
+
});
|
|
236
|
+
return {
|
|
237
|
+
isPressed(key) {
|
|
238
|
+
return _pressedKeys.has(normalizeKey(key));
|
|
239
|
+
},
|
|
240
|
+
get pressedKeys() {
|
|
241
|
+
return _pressedKeys;
|
|
242
|
+
}
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
export { kbdKeysMap, kbdKeysPlatformMap };
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
<script lang="ts" module>
|
|
2
|
+
import type { LinkProps } from './link.types.js'
|
|
3
|
+
|
|
4
|
+
export type Props = LinkProps
|
|
5
|
+
|
|
6
|
+
function parseUrl(url: string, baseUrl: URL) {
|
|
7
|
+
try {
|
|
8
|
+
const parsed = new URL(url, baseUrl.origin)
|
|
9
|
+
return {
|
|
10
|
+
pathname: parsed.pathname,
|
|
11
|
+
query: parsed.searchParams,
|
|
12
|
+
hash: parsed.hash
|
|
13
|
+
}
|
|
14
|
+
} catch {
|
|
15
|
+
return {
|
|
16
|
+
pathname: url,
|
|
17
|
+
query: new URLSearchParams(),
|
|
18
|
+
hash: ''
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function isQueryMatch(linkQuery: URLSearchParams, currentQuery: URLSearchParams, mode: boolean | 'partial'): boolean {
|
|
24
|
+
if (mode === false) return true
|
|
25
|
+
if (mode === 'partial') {
|
|
26
|
+
for (const [key, value] of linkQuery) {
|
|
27
|
+
if (currentQuery.get(key) !== value) return false
|
|
28
|
+
}
|
|
29
|
+
return true
|
|
30
|
+
}
|
|
31
|
+
return linkQuery.toString() === currentQuery.toString()
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function isPathnameMatch(linkPath: string, currentPath: string, exactMatch: boolean): boolean {
|
|
35
|
+
if (exactMatch) return linkPath === currentPath
|
|
36
|
+
|
|
37
|
+
const link = linkPath.replace(/\/$/, '') || '/'
|
|
38
|
+
const current = currentPath.replace(/\/$/, '') || '/'
|
|
39
|
+
|
|
40
|
+
return link === '/'
|
|
41
|
+
? current === '/'
|
|
42
|
+
: current === link || current.startsWith(link + '/')
|
|
43
|
+
}
|
|
44
|
+
</script>
|
|
45
|
+
|
|
46
|
+
<script lang="ts">
|
|
47
|
+
import { page } from '$app/state'
|
|
48
|
+
import { linkVariants, linkDefaults } from './link.variants.js'
|
|
49
|
+
import { getComponentConfig } from '../config.js'
|
|
50
|
+
|
|
51
|
+
const config = getComponentConfig('link', linkDefaults)
|
|
52
|
+
|
|
53
|
+
let {
|
|
54
|
+
href,
|
|
55
|
+
color = config.defaultVariants.color,
|
|
56
|
+
active,
|
|
57
|
+
exact = false,
|
|
58
|
+
exactQuery = false,
|
|
59
|
+
exactHash = false,
|
|
60
|
+
activeClass,
|
|
61
|
+
inactiveClass,
|
|
62
|
+
disabled = false,
|
|
63
|
+
raw = false,
|
|
64
|
+
external,
|
|
65
|
+
children,
|
|
66
|
+
class: className,
|
|
67
|
+
ui,
|
|
68
|
+
target,
|
|
69
|
+
rel,
|
|
70
|
+
...restProps
|
|
71
|
+
}: Props = $props()
|
|
72
|
+
|
|
73
|
+
const isExternal = $derived(
|
|
74
|
+
external ?? (href.startsWith('http://') || href.startsWith('https://') || href.startsWith('//'))
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
const resolvedTarget = $derived(target ?? (isExternal ? '_blank' : undefined))
|
|
78
|
+
|
|
79
|
+
const resolvedRel = $derived(
|
|
80
|
+
rel ?? (isExternal || resolvedTarget === '_blank' ? 'noopener noreferrer' : undefined)
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
const isActive = $derived.by(() => {
|
|
84
|
+
if (active !== undefined) return active
|
|
85
|
+
if (!page.url || isExternal) return false
|
|
86
|
+
|
|
87
|
+
const link = parseUrl(href, page.url)
|
|
88
|
+
|
|
89
|
+
if (exactHash && link.hash !== page.url.hash) return false
|
|
90
|
+
if (!isQueryMatch(link.query, page.url.searchParams, exactQuery)) return false
|
|
91
|
+
|
|
92
|
+
return isPathnameMatch(link.pathname, page.url.pathname, exact)
|
|
93
|
+
})
|
|
94
|
+
|
|
95
|
+
const baseClass = $derived.by(() => {
|
|
96
|
+
const stateClass = isActive ? activeClass : inactiveClass
|
|
97
|
+
if (raw) return [className, stateClass].filter(Boolean).join(' ')
|
|
98
|
+
|
|
99
|
+
const slots = linkVariants({ color, active: isActive, disabled, raw })
|
|
100
|
+
return slots.base({ class: [config.slots.base, stateClass, className, ui?.base] })
|
|
101
|
+
})
|
|
102
|
+
|
|
103
|
+
const ariaCurrent = $derived(isActive && exact ? ('page' as const) : undefined)
|
|
104
|
+
</script>
|
|
105
|
+
|
|
106
|
+
<a
|
|
107
|
+
href={disabled ? undefined : href}
|
|
108
|
+
class={baseClass}
|
|
109
|
+
target={resolvedTarget}
|
|
110
|
+
rel={resolvedRel}
|
|
111
|
+
aria-disabled={disabled ? 'true' : undefined}
|
|
112
|
+
aria-current={ariaCurrent}
|
|
113
|
+
tabindex={disabled ? -1 : undefined}
|
|
114
|
+
{...restProps}
|
|
115
|
+
>
|
|
116
|
+
{@render children?.()}
|
|
117
|
+
</a>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default as Link } from './Link.svelte';
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import type { HTMLAnchorAttributes } from 'svelte/elements';
|
|
2
|
+
import type { LinkVariantProps, LinkSlots } from './link.variants.js';
|
|
3
|
+
import type { ClassNameValue } from 'tailwind-merge';
|
|
4
|
+
export type LinkProps = Omit<HTMLAnchorAttributes, 'class' | 'href'> & {
|
|
5
|
+
/**
|
|
6
|
+
* The destination URL for the anchor element.
|
|
7
|
+
*/
|
|
8
|
+
href: string;
|
|
9
|
+
/**
|
|
10
|
+
* Sets the color scheme applied to the link.
|
|
11
|
+
* @default 'primary'
|
|
12
|
+
*/
|
|
13
|
+
color?: NonNullable<LinkVariantProps['color']>;
|
|
14
|
+
/**
|
|
15
|
+
* Overrides the auto-detected active state.
|
|
16
|
+
* When omitted, the active state is inferred from the current route.
|
|
17
|
+
*/
|
|
18
|
+
active?: boolean;
|
|
19
|
+
/**
|
|
20
|
+
* Requires an exact pathname match for active state detection.
|
|
21
|
+
* @default false
|
|
22
|
+
*/
|
|
23
|
+
exact?: boolean;
|
|
24
|
+
/**
|
|
25
|
+
* Controls query parameter matching for active state detection.
|
|
26
|
+
* - `true` — requires an exact match of all query parameters.
|
|
27
|
+
* - `'partial'` — link's query params must be a subset of the current route.
|
|
28
|
+
* - `false` — query parameters are ignored.
|
|
29
|
+
* @default false
|
|
30
|
+
*/
|
|
31
|
+
exactQuery?: boolean | 'partial';
|
|
32
|
+
/**
|
|
33
|
+
* Requires an exact hash match for active state detection.
|
|
34
|
+
* @default false
|
|
35
|
+
*/
|
|
36
|
+
exactHash?: boolean;
|
|
37
|
+
/**
|
|
38
|
+
* Additional CSS class applied when the link is active.
|
|
39
|
+
*/
|
|
40
|
+
activeClass?: string;
|
|
41
|
+
/**
|
|
42
|
+
* Additional CSS class applied when the link is inactive.
|
|
43
|
+
*/
|
|
44
|
+
inactiveClass?: string;
|
|
45
|
+
/**
|
|
46
|
+
* Disables the link and prevents navigation.
|
|
47
|
+
* @default false
|
|
48
|
+
*/
|
|
49
|
+
disabled?: boolean;
|
|
50
|
+
/**
|
|
51
|
+
* Strips all default styling, applying only `class`, `activeClass`, and `inactiveClass`.
|
|
52
|
+
* @default false
|
|
53
|
+
*/
|
|
54
|
+
raw?: boolean;
|
|
55
|
+
/**
|
|
56
|
+
* Treats the link as external, adding `rel="noopener noreferrer"` and `target="_blank"`.
|
|
57
|
+
* Auto-detected from the `href` when omitted.
|
|
58
|
+
*/
|
|
59
|
+
external?: boolean;
|
|
60
|
+
/**
|
|
61
|
+
* Additional CSS classes for the root element.
|
|
62
|
+
*/
|
|
63
|
+
class?: ClassNameValue;
|
|
64
|
+
/**
|
|
65
|
+
* Override styles for specific link slots.
|
|
66
|
+
*/
|
|
67
|
+
ui?: Partial<Record<LinkSlots, ClassNameValue>>;
|
|
68
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|