@kine-design/core 0.0.1-beta.11 → 0.0.1-beta.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (60) hide show
  1. package/components/base/tooltip/api.ts +3 -2
  2. package/components/base/tooltip/index.ts +3 -2
  3. package/components/base/tooltip/props.d.ts +12 -6
  4. package/components/base/tooltip/useTooltip.ts +175 -56
  5. package/compositions/commandPalette/index.ts +11 -0
  6. package/compositions/commandPalette/types.ts +29 -0
  7. package/compositions/commandPalette/useCommandPalette.ts +135 -0
  8. package/compositions/contextMenu/index.ts +10 -0
  9. package/compositions/contextMenu/types.ts +21 -0
  10. package/compositions/contextMenu/useContextMenu.ts +101 -0
  11. package/compositions/editor/index.ts +18 -0
  12. package/compositions/editor/types.ts +147 -0
  13. package/compositions/editor/useEditor.ts +224 -0
  14. package/compositions/index.ts +15 -0
  15. package/compositions/overlay/index.ts +14 -0
  16. package/compositions/overlay/useOverlayStack.ts +146 -0
  17. package/compositions/peekView/index.ts +10 -0
  18. package/compositions/peekView/usePeekView.ts +99 -0
  19. package/compositions/theme/index.ts +17 -0
  20. package/compositions/theme/presets/compact.ts +117 -0
  21. package/compositions/theme/presets/dark.ts +113 -0
  22. package/compositions/theme/presets/index.ts +11 -0
  23. package/compositions/theme/presets/light.ts +113 -0
  24. package/compositions/theme/types.ts +46 -0
  25. package/compositions/theme/useTheme.ts +269 -0
  26. package/compositions/toast/index.ts +10 -0
  27. package/compositions/toast/useToast.ts +176 -0
  28. package/compositions/tooltip/index.ts +9 -0
  29. package/compositions/tooltip/useTooltip.ts +10 -0
  30. package/dist/components/base/tooltip/index.d.ts +1 -0
  31. package/dist/components/base/tooltip/useTooltip.d.ts +20 -17
  32. package/dist/compositions/commandPalette/index.d.ts +11 -0
  33. package/dist/compositions/commandPalette/types.d.ts +20 -0
  34. package/dist/compositions/commandPalette/useCommandPalette.d.ts +32 -0
  35. package/dist/compositions/contextMenu/index.d.ts +10 -0
  36. package/dist/compositions/contextMenu/types.d.ts +12 -0
  37. package/dist/compositions/contextMenu/useContextMenu.d.ts +52 -0
  38. package/dist/compositions/editor/index.d.ts +10 -0
  39. package/dist/compositions/editor/types.d.ts +132 -0
  40. package/dist/compositions/editor/useEditor.d.ts +13 -0
  41. package/dist/compositions/index.d.ts +15 -0
  42. package/dist/compositions/overlay/index.d.ts +10 -0
  43. package/dist/compositions/overlay/useOverlayStack.d.ts +188 -0
  44. package/dist/compositions/peekView/index.d.ts +10 -0
  45. package/dist/compositions/peekView/usePeekView.d.ts +16 -0
  46. package/dist/compositions/theme/index.d.ts +11 -0
  47. package/dist/compositions/theme/presets/compact.d.ts +6 -0
  48. package/dist/compositions/theme/presets/dark.d.ts +2 -0
  49. package/dist/compositions/theme/presets/index.d.ts +11 -0
  50. package/dist/compositions/theme/presets/light.d.ts +2 -0
  51. package/dist/compositions/theme/types.d.ts +41 -0
  52. package/dist/compositions/theme/useTheme.d.ts +167 -0
  53. package/dist/compositions/toast/index.d.ts +10 -0
  54. package/dist/compositions/toast/useToast.d.ts +71 -0
  55. package/dist/compositions/tooltip/index.d.ts +9 -0
  56. package/dist/compositions/tooltip/useTooltip.d.ts +10 -0
  57. package/dist/core.js +811 -48
  58. package/dist/index.d.ts +1 -0
  59. package/index.ts +2 -0
  60. package/package.json +13 -2
@@ -0,0 +1,117 @@
1
+ /**
2
+ * @description compact theme preset — denser layout with scaled-down spacing/font/radius
3
+ * @author kine-design
4
+ * @date 2026/5/22
5
+ * @version v1.0.0
6
+ *
7
+ * 江湖的业务千篇一律,复杂的代码好几百行。
8
+ */
9
+ import type { ThemeDefinition } from '../types';
10
+
11
+ /**
12
+ * Compact theme inherits light colors but scales spacing/radius/font down by ~0.75.
13
+ * Shadow and motion stay unchanged.
14
+ */
15
+ export const compactTheme: ThemeDefinition = {
16
+ id: 'compact',
17
+ name: 'Compact',
18
+ description: 'Denser layout — smaller spacing, font sizes, and radius (0.75x scale)',
19
+ tokens: {
20
+ color: {
21
+ 'bg-primary': '#f5f5f5',
22
+ 'bg-secondary': '#ebebeb',
23
+ 'bg-tertiary': '#e0e0e0',
24
+ 'bg-hover': '#d4d4d4',
25
+ 'bg-inset': '#dcdcdc',
26
+ 'neutral-1': '#f0f0f0',
27
+ 'neutral-2': '#e5e5e5',
28
+ 'neutral-3': '#d4d4d4',
29
+ 'neutral-4': '#c0c0c0',
30
+ 'neutral-5': '#a0a0a0',
31
+ 'neutral-6': '#787878',
32
+ 'accent-light': '#e8f5ef',
33
+ 'accent-default': '#1ab873',
34
+ 'accent-hover': '#1ec87d',
35
+ 'accent-active': '#158f5a',
36
+ 'accent-alt': '#168f5a',
37
+ 'semantic-success': '#1ab873',
38
+ 'semantic-success-hover': '#1ec87d',
39
+ 'semantic-success-light': '#e8f5ef',
40
+ 'semantic-warning': '#d4820a',
41
+ 'semantic-warning-hover': '#e08f10',
42
+ 'semantic-warning-light': '#fdf4e6',
43
+ 'semantic-error': '#b03020',
44
+ 'semantic-error-hover': '#c03828',
45
+ 'semantic-error-light': '#fce8e6',
46
+ 'semantic-info': '#4a6a9e',
47
+ 'semantic-info-hover': '#3a558a',
48
+ 'semantic-info-light': '#e8eef5',
49
+ 'text-primary': '#1a1a1a',
50
+ 'text-secondary': '#4a4f4c',
51
+ 'text-muted': '#8a8a8a',
52
+ 'text-inverse': '#ffffff',
53
+ 'border-default': '#c8c9c3',
54
+ 'border-subtle': '#e0e0dc',
55
+ 'border-strong': '#1ab873',
56
+ },
57
+ interaction: {
58
+ 'hover-intensity': '15%',
59
+ 'active-intensity': '22%',
60
+ 'focus-glow': '30%',
61
+ 'focus-ring-width': '1px',
62
+ 'focus-ring-offset': '1px',
63
+ 'text-glow': '50%',
64
+ 'transition-duration': '100ms',
65
+ },
66
+ spacing: {
67
+ '1': '1px',
68
+ '2': '3px',
69
+ '3': '4px',
70
+ '4': '6px',
71
+ '5': '8px',
72
+ '6': '9px',
73
+ '7': '10px',
74
+ '8': '12px',
75
+ '10': '15px',
76
+ '12': '18px',
77
+ '14': '21px',
78
+ '16': '24px',
79
+ '20': '30px',
80
+ '24': '36px',
81
+ },
82
+ radius: {
83
+ 'xs': '1px',
84
+ 'sm': '4px',
85
+ 'md': '6px',
86
+ 'lg': '9px',
87
+ },
88
+ font: {
89
+ 'family-mono': "ui-monospace, 'SF Mono', 'Cascadia Code', 'Fira Code', monospace",
90
+ 'family-system': 'system-ui, -apple-system, sans-serif',
91
+ 'size-xs': '7px',
92
+ 'size-sm': '8px',
93
+ 'size-md': '10px',
94
+ 'size-lg': '11px',
95
+ 'size-xl': '12px',
96
+ 'size-2xl': '13px',
97
+ 'size-3xl': '16px',
98
+ 'size-4xl': '20px',
99
+ 'weight-thin': '100',
100
+ 'weight-regular': '400',
101
+ 'weight-medium': '500',
102
+ 'weight-semibold': '600',
103
+ 'weight-bold': '700',
104
+ },
105
+ shadow: {
106
+ 'glow-radius': '8px',
107
+ 'sm': '0px 1px 2px rgba(0,0,0,0.05)',
108
+ 'md': '0px 4px 8px rgba(0,0,0,0.08), 0px 2px 4px rgba(0,0,0,0.04)',
109
+ 'lg': '0px 4px 8px rgba(0,0,0,0.12), 0px 6px 12px rgba(16,24,40,0.12), 0px 1px 16px rgba(16,24,40,0.12)',
110
+ },
111
+ motion: {
112
+ 'duration-fast': '200ms',
113
+ 'duration-normal': '400ms',
114
+ 'easing-default': 'ease-in-out',
115
+ },
116
+ },
117
+ };
@@ -0,0 +1,113 @@
1
+ /**
2
+ * @description dark theme preset — Phosphor sci-fi terminal palette
3
+ * @author kine-design
4
+ * @date 2026/5/22
5
+ * @version v1.0.0
6
+ *
7
+ * 江湖的业务千篇一律,复杂的代码好几百行。
8
+ */
9
+ import type { ThemeDefinition } from '../types';
10
+
11
+ export const darkTheme: ThemeDefinition = {
12
+ id: 'dark',
13
+ name: 'Dark',
14
+ description: 'Dark mode — Phosphor terminal aesthetic, inverted colors',
15
+ tokens: {
16
+ color: {
17
+ 'bg-primary': '#000000',
18
+ 'bg-secondary': '#0a0a0a',
19
+ 'bg-tertiary': '#141414',
20
+ 'bg-hover': '#1a1a1a',
21
+ 'bg-inset': '#1e1e1e',
22
+ 'neutral-1': '#0f0f0f',
23
+ 'neutral-2': '#1a1a1a',
24
+ 'neutral-3': '#262626',
25
+ 'neutral-4': '#333333',
26
+ 'neutral-5': '#4a4a4a',
27
+ 'neutral-6': '#666666',
28
+ 'accent-light': '#0d2e1f',
29
+ 'accent-default': '#2ad98d',
30
+ 'accent-hover': '#3ae29a',
31
+ 'accent-active': '#22b072',
32
+ 'accent-alt': '#1fa86b',
33
+ 'semantic-success': '#2ad98d',
34
+ 'semantic-success-hover': '#3ae29a',
35
+ 'semantic-success-light': '#0d2e1f',
36
+ 'semantic-warning': '#fab03d',
37
+ 'semantic-warning-hover': '#fabb55',
38
+ 'semantic-warning-light': '#2e2210',
39
+ 'semantic-error': '#cb523e',
40
+ 'semantic-error-hover': '#de5f48',
41
+ 'semantic-error-light': '#2e1210',
42
+ 'semantic-info': '#8ba3c7',
43
+ 'semantic-info-hover': '#5976ba',
44
+ 'semantic-info-light': '#10182e',
45
+ 'text-primary': '#e8e8e8',
46
+ 'text-secondary': '#86908a',
47
+ 'text-muted': '#555555',
48
+ 'text-inverse': '#0a0a0a',
49
+ 'border-default': '#31322c',
50
+ 'border-subtle': '#252520',
51
+ 'border-strong': '#2ad98d',
52
+ },
53
+ interaction: {
54
+ 'hover-intensity': '18%',
55
+ 'active-intensity': '28%',
56
+ 'focus-glow': '35%',
57
+ 'focus-ring-width': '2px',
58
+ 'focus-ring-offset': '2px',
59
+ 'text-glow': '55%',
60
+ 'transition-duration': '150ms',
61
+ },
62
+ spacing: {
63
+ '1': '2px',
64
+ '2': '4px',
65
+ '3': '6px',
66
+ '4': '8px',
67
+ '5': '10px',
68
+ '6': '12px',
69
+ '7': '14px',
70
+ '8': '16px',
71
+ '10': '20px',
72
+ '12': '24px',
73
+ '14': '28px',
74
+ '16': '32px',
75
+ '20': '40px',
76
+ '24': '48px',
77
+ },
78
+ radius: {
79
+ 'xs': '2px',
80
+ 'sm': '6px',
81
+ 'md': '8px',
82
+ 'lg': '12px',
83
+ },
84
+ font: {
85
+ 'family-mono': "ui-monospace, 'SF Mono', 'Cascadia Code', 'Fira Code', monospace",
86
+ 'family-system': 'system-ui, -apple-system, sans-serif',
87
+ 'size-xs': '9px',
88
+ 'size-sm': '10px',
89
+ 'size-md': '12px',
90
+ 'size-lg': '13px',
91
+ 'size-xl': '14px',
92
+ 'size-2xl': '16px',
93
+ 'size-3xl': '20px',
94
+ 'size-4xl': '24px',
95
+ 'weight-thin': '100',
96
+ 'weight-regular': '400',
97
+ 'weight-medium': '500',
98
+ 'weight-semibold': '600',
99
+ 'weight-bold': '700',
100
+ },
101
+ shadow: {
102
+ 'glow-radius': '8px',
103
+ 'sm': '0px 1px 2px rgba(0,0,0,0.15)',
104
+ 'md': '0px 4px 8px rgba(0,0,0,0.25), 0px 2px 4px rgba(0,0,0,0.15)',
105
+ 'lg': '0px 4px 8px rgba(0,0,0,0.35), 0px 6px 12px rgba(0,0,0,0.25), 0px 1px 16px rgba(0,0,0,0.2)',
106
+ },
107
+ motion: {
108
+ 'duration-fast': '200ms',
109
+ 'duration-normal': '400ms',
110
+ 'easing-default': 'ease-in-out',
111
+ },
112
+ },
113
+ };
@@ -0,0 +1,11 @@
1
+ /**
2
+ * @description built-in theme presets barrel export
3
+ * @author kine-design
4
+ * @date 2026/5/22
5
+ * @version v1.0.0
6
+ *
7
+ * 江湖的业务千篇一律,复杂的代码好几百行。
8
+ */
9
+ export { lightTheme } from './light';
10
+ export { darkTheme } from './dark';
11
+ export { compactTheme } from './compact';
@@ -0,0 +1,113 @@
1
+ /**
2
+ * @description light theme preset — extracted from base.css :root values
3
+ * @author kine-design
4
+ * @date 2026/5/22
5
+ * @version v1.0.0
6
+ *
7
+ * 江湖的业务千篇一律,复杂的代码好几百行。
8
+ */
9
+ import type { ThemeDefinition } from '../types';
10
+
11
+ export const lightTheme: ThemeDefinition = {
12
+ id: 'light',
13
+ name: 'Light',
14
+ description: 'Default light theme — neutral backgrounds, dark text, Phosphor accent',
15
+ tokens: {
16
+ color: {
17
+ 'bg-primary': '#f5f5f5',
18
+ 'bg-secondary': '#ebebeb',
19
+ 'bg-tertiary': '#e0e0e0',
20
+ 'bg-hover': '#d4d4d4',
21
+ 'bg-inset': '#dcdcdc',
22
+ 'neutral-1': '#f0f0f0',
23
+ 'neutral-2': '#e5e5e5',
24
+ 'neutral-3': '#d4d4d4',
25
+ 'neutral-4': '#c0c0c0',
26
+ 'neutral-5': '#a0a0a0',
27
+ 'neutral-6': '#787878',
28
+ 'accent-light': '#e8f5ef',
29
+ 'accent-default': '#1ab873',
30
+ 'accent-hover': '#1ec87d',
31
+ 'accent-active': '#158f5a',
32
+ 'accent-alt': '#168f5a',
33
+ 'semantic-success': '#1ab873',
34
+ 'semantic-success-hover': '#1ec87d',
35
+ 'semantic-success-light': '#e8f5ef',
36
+ 'semantic-warning': '#d4820a',
37
+ 'semantic-warning-hover': '#e08f10',
38
+ 'semantic-warning-light': '#fdf4e6',
39
+ 'semantic-error': '#b03020',
40
+ 'semantic-error-hover': '#c03828',
41
+ 'semantic-error-light': '#fce8e6',
42
+ 'semantic-info': '#4a6a9e',
43
+ 'semantic-info-hover': '#3a558a',
44
+ 'semantic-info-light': '#e8eef5',
45
+ 'text-primary': '#1a1a1a',
46
+ 'text-secondary': '#4a4f4c',
47
+ 'text-muted': '#8a8a8a',
48
+ 'text-inverse': '#ffffff',
49
+ 'border-default': '#c8c9c3',
50
+ 'border-subtle': '#e0e0dc',
51
+ 'border-strong': '#1ab873',
52
+ },
53
+ interaction: {
54
+ 'hover-intensity': '15%',
55
+ 'active-intensity': '22%',
56
+ 'focus-glow': '30%',
57
+ 'focus-ring-width': '2px',
58
+ 'focus-ring-offset': '2px',
59
+ 'text-glow': '50%',
60
+ 'transition-duration': '150ms',
61
+ },
62
+ spacing: {
63
+ '1': '2px',
64
+ '2': '4px',
65
+ '3': '6px',
66
+ '4': '8px',
67
+ '5': '10px',
68
+ '6': '12px',
69
+ '7': '14px',
70
+ '8': '16px',
71
+ '10': '20px',
72
+ '12': '24px',
73
+ '14': '28px',
74
+ '16': '32px',
75
+ '20': '40px',
76
+ '24': '48px',
77
+ },
78
+ radius: {
79
+ 'xs': '2px',
80
+ 'sm': '6px',
81
+ 'md': '8px',
82
+ 'lg': '12px',
83
+ },
84
+ font: {
85
+ 'family-mono': "ui-monospace, 'SF Mono', 'Cascadia Code', 'Fira Code', monospace",
86
+ 'family-system': 'system-ui, -apple-system, sans-serif',
87
+ 'size-xs': '9px',
88
+ 'size-sm': '10px',
89
+ 'size-md': '12px',
90
+ 'size-lg': '13px',
91
+ 'size-xl': '14px',
92
+ 'size-2xl': '16px',
93
+ 'size-3xl': '20px',
94
+ 'size-4xl': '24px',
95
+ 'weight-thin': '100',
96
+ 'weight-regular': '400',
97
+ 'weight-medium': '500',
98
+ 'weight-semibold': '600',
99
+ 'weight-bold': '700',
100
+ },
101
+ shadow: {
102
+ 'glow-radius': '8px',
103
+ 'sm': '0px 1px 2px rgba(0,0,0,0.05)',
104
+ 'md': '0px 4px 8px rgba(0,0,0,0.08), 0px 2px 4px rgba(0,0,0,0.04)',
105
+ 'lg': '0px 4px 8px rgba(0,0,0,0.12), 0px 6px 12px rgba(16,24,40,0.12), 0px 1px 16px rgba(16,24,40,0.12)',
106
+ },
107
+ motion: {
108
+ 'duration-fast': '200ms',
109
+ 'duration-normal': '400ms',
110
+ 'easing-default': 'ease-in-out',
111
+ },
112
+ },
113
+ };
@@ -0,0 +1,46 @@
1
+ /**
2
+ * @description theme system type definitions
3
+ * @author kine-design
4
+ * @date 2026/5/22
5
+ * @version v1.0.0
6
+ *
7
+ * 江湖的业务千篇一律,复杂的代码好几百行。
8
+ */
9
+
10
+ /** Token category map: flat key-value pairs per category */
11
+ export interface ThemeTokens {
12
+ /** e.g. 'bg-primary': '#f5f5f5', 'neutral-3': '#d4d4d4', 'accent-light': '#e8f5ef' */
13
+ color: Record<string, string>
14
+ /** e.g. '1': '2px', '2': '4px' */
15
+ spacing: Record<string, string>
16
+ /** e.g. 'xs': '2px', 'sm': '6px' */
17
+ radius: Record<string, string>
18
+ /** e.g. 'size-sm': '12px', 'family-system': 'system-ui, ...' */
19
+ font: Record<string, string>
20
+ /** e.g. 'sm': '0px 1px 2px ...', 'glow-radius': '8px' */
21
+ shadow: Record<string, string>
22
+ /** e.g. 'duration-fast': '200ms', 'easing-default': 'ease-in-out' */
23
+ motion: Record<string, string>
24
+ /** e.g. 'hover-intensity': '15%', 'transition-duration': '150ms' */
25
+ interaction: Record<string, string>
26
+ }
27
+
28
+ /** All supported token category names */
29
+ export type ThemeTokenCategory = keyof ThemeTokens
30
+
31
+ /** A registered theme definition */
32
+ export interface ThemeDefinition {
33
+ id: string
34
+ name: string
35
+ description?: string
36
+ tokens: Partial<ThemeTokens>
37
+ }
38
+
39
+ /** User-level per-token overrides (sparse) */
40
+ export type ThemeOverrides = Partial<ThemeTokens>
41
+
42
+ /** Serialized state persisted to localStorage */
43
+ export interface PersistedThemeState {
44
+ currentTheme: string
45
+ overrides: ThemeOverrides
46
+ }
@@ -0,0 +1,269 @@
1
+ /**
2
+ * @description theme store — runtime design token engine with CSS variable injection
3
+ * @author kine-design
4
+ * @date 2026/5/22
5
+ * @version v1.0.0
6
+ *
7
+ * 江湖的业务千篇一律,复杂的代码好几百行。
8
+ */
9
+ import { defineStore } from 'pinia';
10
+ import { computed, ref, watch } from 'vue';
11
+ import type {
12
+ ThemeDefinition,
13
+ ThemeTokenCategory,
14
+ ThemeOverrides,
15
+ PersistedThemeState,
16
+ ThemeTokens,
17
+ } from './types';
18
+ import { lightTheme, darkTheme, compactTheme } from './presets';
19
+
20
+ const STORAGE_KEY = 'kine-theme';
21
+ const TRANSITION_CLASS = 'kine-theme-transitioning';
22
+
23
+ /**
24
+ * Detect OS-level dark mode preference.
25
+ * Returns 'dark' if the user prefers dark color scheme, 'light' otherwise.
26
+ */
27
+ function detectColorScheme(): 'light' | 'dark' {
28
+ if (typeof window === 'undefined') return 'light';
29
+ return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
30
+ }
31
+
32
+ /**
33
+ * Load persisted theme state from localStorage.
34
+ */
35
+ function loadPersistedState(): PersistedThemeState | null {
36
+ if (typeof window === 'undefined') return null;
37
+ try {
38
+ const raw = localStorage.getItem(STORAGE_KEY);
39
+ if (!raw) return null;
40
+ return JSON.parse(raw) as PersistedThemeState;
41
+ } catch {
42
+ return null;
43
+ }
44
+ }
45
+
46
+ /**
47
+ * Save theme state to localStorage.
48
+ */
49
+ function persistState(state: PersistedThemeState): void {
50
+ if (typeof window === 'undefined') return;
51
+ try {
52
+ localStorage.setItem(STORAGE_KEY, JSON.stringify(state));
53
+ } catch {
54
+ // storage full or unavailable — silently ignore
55
+ }
56
+ }
57
+
58
+ /**
59
+ * Apply a flat map of CSS variables to document.documentElement.style.
60
+ * Variables follow the naming convention `--kine-{category}-{key}`.
61
+ */
62
+ function applyCSSVariables(tokens: Partial<ThemeTokens>): void {
63
+ if (typeof document === 'undefined') return;
64
+ const root = document.documentElement;
65
+
66
+ const categories = Object.keys(tokens) as ThemeTokenCategory[];
67
+ for (const category of categories) {
68
+ const entries = tokens[category];
69
+ if (!entries) continue;
70
+ for (const [key, value] of Object.entries(entries)) {
71
+ root.style.setProperty(`--kine-${category}-${key}`, value);
72
+ }
73
+ }
74
+ }
75
+
76
+ /**
77
+ * Add a brief transition to body so theme switch feels smooth.
78
+ */
79
+ function enableTransition(): void {
80
+ if (typeof document === 'undefined') return;
81
+ const el = document.documentElement;
82
+ el.classList.add(TRANSITION_CLASS);
83
+
84
+ // inject the transition style once
85
+ let styleEl = document.getElementById('kine-theme-transition');
86
+ if (!styleEl) {
87
+ styleEl = document.createElement('style');
88
+ styleEl.id = 'kine-theme-transition';
89
+ styleEl.textContent = `
90
+ .${TRANSITION_CLASS},
91
+ .${TRANSITION_CLASS} *,
92
+ .${TRANSITION_CLASS} *::before,
93
+ .${TRANSITION_CLASS} *::after {
94
+ transition: background-color 0.3s, color 0.3s, border-color 0.3s, box-shadow 0.3s !important;
95
+ }
96
+ `;
97
+ document.head.appendChild(styleEl);
98
+ }
99
+
100
+ // remove transition class after animation completes to avoid perf overhead
101
+ setTimeout(() => {
102
+ el.classList.remove(TRANSITION_CLASS);
103
+ }, 350);
104
+ }
105
+
106
+ /**
107
+ * Toggle the `dark` attribute on <html> to coordinate with base.css html[dark] selectors.
108
+ */
109
+ function toggleDarkAttribute(isDark: boolean): void {
110
+ if (typeof document === 'undefined') return;
111
+ if (isDark) {
112
+ document.documentElement.setAttribute('dark', '');
113
+ } else {
114
+ document.documentElement.removeAttribute('dark');
115
+ }
116
+ }
117
+
118
+ /**
119
+ * Merge base theme tokens with user overrides (overrides win).
120
+ */
121
+ function mergeTokens(base: Partial<ThemeTokens>, overrides: ThemeOverrides): Partial<ThemeTokens> {
122
+ const merged: Partial<ThemeTokens> = {};
123
+ const allCategories = new Set<ThemeTokenCategory>([
124
+ ...Object.keys(base) as ThemeTokenCategory[],
125
+ ...Object.keys(overrides) as ThemeTokenCategory[],
126
+ ]);
127
+
128
+ for (const cat of allCategories) {
129
+ merged[cat] = {
130
+ ...(base[cat] ?? {}),
131
+ ...(overrides[cat] ?? {}),
132
+ };
133
+ }
134
+ return merged;
135
+ }
136
+
137
+ // ---------------------------------------------------------------------------
138
+ // Store
139
+ // ---------------------------------------------------------------------------
140
+
141
+ export const useThemeStore = defineStore('kine-theme', () => {
142
+ // --- state ---
143
+ const themes = ref<Map<string, ThemeDefinition>>(new Map());
144
+ const currentThemeId = ref<string>('light');
145
+ const overrides = ref<ThemeOverrides>({});
146
+
147
+ // --- getters ---
148
+ const currentTheme = computed<ThemeDefinition | undefined>(
149
+ () => themes.value.get(currentThemeId.value),
150
+ );
151
+
152
+ const resolvedTokens = computed<Partial<ThemeTokens>>(() => {
153
+ const base = currentTheme.value?.tokens ?? {};
154
+ return mergeTokens(base, overrides.value);
155
+ });
156
+
157
+ const isDark = computed(() => currentThemeId.value === 'dark');
158
+
159
+ const themeList = computed<ThemeDefinition[]>(() => Array.from(themes.value.values()));
160
+
161
+ // --- actions ---
162
+
163
+ /**
164
+ * Register a theme preset. If a theme with the same id exists, it is replaced.
165
+ */
166
+ function registerTheme(definition: ThemeDefinition): void {
167
+ // use a new Map so Vue detects the change
168
+ const next = new Map(themes.value);
169
+ next.set(definition.id, definition);
170
+ themes.value = next;
171
+ }
172
+
173
+ /**
174
+ * Switch to a registered theme by id.
175
+ */
176
+ function setTheme(id: string): void {
177
+ if (!themes.value.has(id)) {
178
+ console.warn(`[kine-theme] theme "${id}" is not registered`);
179
+ return;
180
+ }
181
+ enableTransition();
182
+ currentThemeId.value = id;
183
+ }
184
+
185
+ /**
186
+ * Set a single token override (survives theme switches).
187
+ */
188
+ function setTokenOverride(category: ThemeTokenCategory, key: string, value: string): void {
189
+ const prev = overrides.value[category] ?? {};
190
+ overrides.value = {
191
+ ...overrides.value,
192
+ [category]: { ...prev, [key]: value },
193
+ };
194
+ }
195
+
196
+ /**
197
+ * Clear all user-level token overrides.
198
+ */
199
+ function resetOverrides(): void {
200
+ overrides.value = {};
201
+ }
202
+
203
+ /**
204
+ * Initialize the theme system: register built-in presets, restore persisted
205
+ * state (or detect OS preference), and apply CSS variables.
206
+ * Called automatically by KThemeProvider or can be called manually.
207
+ */
208
+ function init(): void {
209
+ // register built-in presets
210
+ registerTheme(lightTheme);
211
+ registerTheme(darkTheme);
212
+ registerTheme(compactTheme);
213
+
214
+ // restore persisted state
215
+ const persisted = loadPersistedState();
216
+ if (persisted && themes.value.has(persisted.currentTheme)) {
217
+ currentThemeId.value = persisted.currentTheme;
218
+ overrides.value = persisted.overrides ?? {};
219
+ } else {
220
+ // no saved preference — use OS color scheme
221
+ currentThemeId.value = detectColorScheme();
222
+ }
223
+
224
+ // apply immediately (no transition on first paint)
225
+ applyCurrentTheme();
226
+ }
227
+
228
+ /**
229
+ * Apply the resolved tokens as CSS variables and sync dark attribute.
230
+ */
231
+ function applyCurrentTheme(): void {
232
+ applyCSSVariables(resolvedTokens.value);
233
+ toggleDarkAttribute(isDark.value);
234
+ }
235
+
236
+ // --- persistence & side-effect watchers ---
237
+
238
+ watch(
239
+ [currentThemeId, overrides],
240
+ () => {
241
+ applyCurrentTheme();
242
+ persistState({
243
+ currentTheme: currentThemeId.value,
244
+ overrides: overrides.value,
245
+ });
246
+ },
247
+ { deep: true },
248
+ );
249
+
250
+ return {
251
+ // state
252
+ currentThemeId,
253
+ themes,
254
+ overrides,
255
+
256
+ // getters
257
+ currentTheme,
258
+ resolvedTokens,
259
+ isDark,
260
+ themeList,
261
+
262
+ // actions
263
+ registerTheme,
264
+ setTheme,
265
+ setTokenOverride,
266
+ resetOverrides,
267
+ init,
268
+ };
269
+ });
@@ -0,0 +1,10 @@
1
+ /**
2
+ * @description toast composition 导出
3
+ * @author 阿怪
4
+ * @date 2026/5/22
5
+ * @version v1.0.0
6
+ *
7
+ * 江湖的业务千篇一律,复杂的代码好几百行。
8
+ */
9
+ export { useToast } from './useToast';
10
+ export type { ToastType, ToastItem, ToastShowOptions, ToastPromiseOptions } from './useToast';