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

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 (67) hide show
  1. package/components/base/image/__tests__/useImage.test.ts +61 -0
  2. package/components/base/image/api.ts +2 -2
  3. package/components/base/image/index.ts +2 -2
  4. package/components/base/image/props.d.ts +15 -4
  5. package/components/base/image/useImage.ts +27 -16
  6. package/components/base/tooltip/api.ts +3 -2
  7. package/components/base/tooltip/index.ts +3 -2
  8. package/components/base/tooltip/props.d.ts +12 -6
  9. package/components/base/tooltip/useTooltip.ts +175 -56
  10. package/compositions/commandPalette/index.ts +11 -0
  11. package/compositions/commandPalette/types.ts +29 -0
  12. package/compositions/commandPalette/useCommandPalette.ts +135 -0
  13. package/compositions/contextMenu/index.ts +10 -0
  14. package/compositions/contextMenu/types.ts +21 -0
  15. package/compositions/contextMenu/useContextMenu.ts +101 -0
  16. package/compositions/editor/index.ts +18 -0
  17. package/compositions/editor/types.ts +147 -0
  18. package/compositions/editor/useEditor.ts +224 -0
  19. package/compositions/index.ts +15 -0
  20. package/compositions/overlay/index.ts +14 -0
  21. package/compositions/overlay/useOverlayStack.ts +146 -0
  22. package/compositions/peekView/index.ts +10 -0
  23. package/compositions/peekView/usePeekView.ts +99 -0
  24. package/compositions/theme/index.ts +17 -0
  25. package/compositions/theme/presets/compact.ts +117 -0
  26. package/compositions/theme/presets/dark.ts +113 -0
  27. package/compositions/theme/presets/index.ts +11 -0
  28. package/compositions/theme/presets/light.ts +113 -0
  29. package/compositions/theme/types.ts +46 -0
  30. package/compositions/theme/useTheme.ts +269 -0
  31. package/compositions/toast/index.ts +10 -0
  32. package/compositions/toast/useToast.ts +176 -0
  33. package/compositions/tooltip/index.ts +9 -0
  34. package/compositions/tooltip/useTooltip.ts +10 -0
  35. package/dist/components/base/image/index.d.ts +2 -2
  36. package/dist/components/base/image/useImage.d.ts +7 -0
  37. package/dist/components/base/tooltip/index.d.ts +1 -0
  38. package/dist/components/base/tooltip/useTooltip.d.ts +20 -17
  39. package/dist/compositions/commandPalette/index.d.ts +11 -0
  40. package/dist/compositions/commandPalette/types.d.ts +20 -0
  41. package/dist/compositions/commandPalette/useCommandPalette.d.ts +32 -0
  42. package/dist/compositions/contextMenu/index.d.ts +10 -0
  43. package/dist/compositions/contextMenu/types.d.ts +12 -0
  44. package/dist/compositions/contextMenu/useContextMenu.d.ts +52 -0
  45. package/dist/compositions/editor/index.d.ts +10 -0
  46. package/dist/compositions/editor/types.d.ts +132 -0
  47. package/dist/compositions/editor/useEditor.d.ts +13 -0
  48. package/dist/compositions/index.d.ts +15 -0
  49. package/dist/compositions/overlay/index.d.ts +10 -0
  50. package/dist/compositions/overlay/useOverlayStack.d.ts +188 -0
  51. package/dist/compositions/peekView/index.d.ts +10 -0
  52. package/dist/compositions/peekView/usePeekView.d.ts +16 -0
  53. package/dist/compositions/theme/index.d.ts +11 -0
  54. package/dist/compositions/theme/presets/compact.d.ts +6 -0
  55. package/dist/compositions/theme/presets/dark.d.ts +2 -0
  56. package/dist/compositions/theme/presets/index.d.ts +11 -0
  57. package/dist/compositions/theme/presets/light.d.ts +2 -0
  58. package/dist/compositions/theme/types.d.ts +41 -0
  59. package/dist/compositions/theme/useTheme.d.ts +167 -0
  60. package/dist/compositions/toast/index.d.ts +10 -0
  61. package/dist/compositions/toast/useToast.d.ts +71 -0
  62. package/dist/compositions/tooltip/index.d.ts +9 -0
  63. package/dist/compositions/tooltip/useTooltip.d.ts +10 -0
  64. package/dist/core.js +836 -56
  65. package/dist/index.d.ts +1 -0
  66. package/index.ts +2 -0
  67. package/package.json +13 -2
@@ -0,0 +1,135 @@
1
+ /**
2
+ * @description CommandPalette composable — global search + command execution
3
+ * @author kine-design
4
+ * @date 2026/5/22
5
+ * @version v1.0.0
6
+ *
7
+ * 江湖的业务千篇一律,复杂的代码好几百行。
8
+ *
9
+ * Manages open/close state, command registration, query filtering, and execution.
10
+ * Does NOT participate in overlayStack — fixed z-index 9500.
11
+ */
12
+ import { ref, computed, type Ref, type ComputedRef } from 'vue';
13
+ import type { Command } from './types';
14
+
15
+ export interface UseCommandPaletteReturn {
16
+ /** Whether the palette is open */
17
+ isOpen: Ref<boolean>;
18
+ /** Current search query */
19
+ query: Ref<string>;
20
+ /** Index of the currently highlighted item */
21
+ activeIndex: Ref<number>;
22
+ /** All registered commands */
23
+ commands: Ref<Command[]>;
24
+ /** Filtered commands based on query */
25
+ filtered: ComputedRef<Command[]>;
26
+ /** Open the palette */
27
+ open: () => void;
28
+ /** Close the palette */
29
+ close: () => void;
30
+ /** Toggle open/close */
31
+ toggle: () => void;
32
+ /** Register a command */
33
+ registerCommand: (cmd: Command) => void;
34
+ /** Unregister a command by id */
35
+ unregisterCommand: (id: string) => void;
36
+ /** Execute the command at a given index in the filtered list, then close */
37
+ execute: (index: number) => void;
38
+ }
39
+
40
+ /**
41
+ * CommandPalette composable
42
+ *
43
+ * Provides reactive state for opening, searching, and executing commands.
44
+ */
45
+ export function useCommandPalette(): UseCommandPaletteReturn {
46
+ const isOpen = ref(false);
47
+ const query = ref('');
48
+ const activeIndex = ref(0);
49
+ const commands = ref<Command[]>([]);
50
+
51
+ /** element that had focus before the palette opened */
52
+ let previousActiveElement: Element | null = null;
53
+
54
+ const open = () => {
55
+ previousActiveElement = document.activeElement;
56
+ isOpen.value = true;
57
+ query.value = '';
58
+ activeIndex.value = 0;
59
+ };
60
+
61
+ const close = () => {
62
+ isOpen.value = false;
63
+ query.value = '';
64
+ activeIndex.value = 0;
65
+ // restore focus to the previously focused element
66
+ if (previousActiveElement && previousActiveElement instanceof HTMLElement) {
67
+ previousActiveElement.focus();
68
+ }
69
+ previousActiveElement = null;
70
+ };
71
+
72
+ const toggle = () => {
73
+ if (isOpen.value) {
74
+ close();
75
+ } else {
76
+ open();
77
+ }
78
+ };
79
+
80
+ const registerCommand = (cmd: Command) => {
81
+ // prevent duplicates
82
+ const exists = commands.value.some(c => c.id === cmd.id);
83
+ if (!exists) {
84
+ commands.value.push(cmd);
85
+ }
86
+ };
87
+
88
+ const unregisterCommand = (id: string) => {
89
+ const idx = commands.value.findIndex(c => c.id === id);
90
+ if (idx !== -1) {
91
+ commands.value.splice(idx, 1);
92
+ }
93
+ };
94
+
95
+ /**
96
+ * Simple case-insensitive substring match on label + keywords.
97
+ */
98
+ const filtered = computed<Command[]>(() => {
99
+ const q = query.value.trim().toLowerCase();
100
+ if (!q) {
101
+ return commands.value;
102
+ }
103
+ return commands.value.filter(cmd => {
104
+ if (cmd.label.toLowerCase().includes(q)) {
105
+ return true;
106
+ }
107
+ if (cmd.keywords) {
108
+ return cmd.keywords.some(kw => kw.toLowerCase().includes(q));
109
+ }
110
+ return false;
111
+ });
112
+ });
113
+
114
+ const execute = (index: number) => {
115
+ const cmd = filtered.value[index];
116
+ if (cmd) {
117
+ close();
118
+ cmd.action();
119
+ }
120
+ };
121
+
122
+ return {
123
+ isOpen,
124
+ query,
125
+ activeIndex,
126
+ commands,
127
+ filtered,
128
+ open,
129
+ close,
130
+ toggle,
131
+ registerCommand,
132
+ unregisterCommand,
133
+ execute,
134
+ };
135
+ }
@@ -0,0 +1,10 @@
1
+ /**
2
+ * @description context menu composables barrel export
3
+ * @author kine-design
4
+ * @date 2026/5/22
5
+ * @version v1.0.0
6
+ *
7
+ * 江湖的业务千篇一律,复杂的代码好几百行。
8
+ */
9
+ export { useContextMenu } from './useContextMenu';
10
+ export type { MenuItem } from './types';
@@ -0,0 +1,21 @@
1
+ /**
2
+ * @description context menu types
3
+ * @author kine-design
4
+ * @date 2026/5/22
5
+ * @version v1.0.0
6
+ *
7
+ * 江湖的业务千篇一律,复杂的代码好几百行。
8
+ */
9
+ import type { VNode } from 'vue';
10
+
11
+ export interface MenuItem {
12
+ key: string;
13
+ label: string;
14
+ icon?: () => VNode;
15
+ action: () => void;
16
+ visible?: boolean;
17
+ disabled?: boolean;
18
+ danger?: boolean;
19
+ children?: MenuItem[];
20
+ separator?: boolean;
21
+ }
@@ -0,0 +1,101 @@
1
+ /**
2
+ * @description context menu composable — manages open/close state, positioning, and keyboard navigation
3
+ * @author kine-design
4
+ * @date 2026/5/22
5
+ * @version v1.0.0
6
+ *
7
+ * 江湖的业务千篇一律,复杂的代码好几百行。
8
+ */
9
+ import { ref, computed } from 'vue';
10
+ import type { MenuItem } from './types';
11
+
12
+ export function useContextMenu() {
13
+ const isOpen = ref(false);
14
+ const position = ref({ x: 0, y: 0 });
15
+ const items = ref<MenuItem[]>([]);
16
+ const activeIndex = ref(-1);
17
+
18
+ const visibleItems = computed(() =>
19
+ items.value.filter(item => item.visible !== false),
20
+ );
21
+
22
+ const open = (event: MouseEvent, menuItems: MenuItem[]) => {
23
+ event.preventDefault();
24
+ event.stopPropagation();
25
+
26
+ items.value = menuItems;
27
+ activeIndex.value = -1;
28
+
29
+ // Calculate position with viewport edge flipping
30
+ const x = event.clientX;
31
+ const y = event.clientY;
32
+
33
+ position.value = { x, y };
34
+ isOpen.value = true;
35
+ };
36
+
37
+ const close = () => {
38
+ isOpen.value = false;
39
+ activeIndex.value = -1;
40
+ };
41
+
42
+ const moveUp = () => {
43
+ const visible = visibleItems.value;
44
+ if (visible.length === 0) return;
45
+
46
+ let next = activeIndex.value - 1;
47
+ // Skip separators
48
+ while (next >= 0 && visible[next]?.separator) {
49
+ next--;
50
+ }
51
+ if (next < 0) {
52
+ // Wrap to last non-separator item
53
+ next = visible.length - 1;
54
+ while (next >= 0 && visible[next]?.separator) {
55
+ next--;
56
+ }
57
+ }
58
+ activeIndex.value = next;
59
+ };
60
+
61
+ const moveDown = () => {
62
+ const visible = visibleItems.value;
63
+ if (visible.length === 0) return;
64
+
65
+ let next = activeIndex.value + 1;
66
+ // Skip separators
67
+ while (next < visible.length && visible[next]?.separator) {
68
+ next++;
69
+ }
70
+ if (next >= visible.length) {
71
+ // Wrap to first non-separator item
72
+ next = 0;
73
+ while (next < visible.length && visible[next]?.separator) {
74
+ next++;
75
+ }
76
+ }
77
+ activeIndex.value = next;
78
+ };
79
+
80
+ const executeCurrent = () => {
81
+ const visible = visibleItems.value;
82
+ const item = visible[activeIndex.value];
83
+ if (item && !item.disabled && !item.separator) {
84
+ item.action();
85
+ close();
86
+ }
87
+ };
88
+
89
+ return {
90
+ isOpen,
91
+ position,
92
+ items,
93
+ activeIndex,
94
+ visibleItems,
95
+ open,
96
+ close,
97
+ moveUp,
98
+ moveDown,
99
+ executeCurrent,
100
+ };
101
+ }
@@ -0,0 +1,18 @@
1
+ /**
2
+ * @description Editor composition barrel export
3
+ * @author kine-design
4
+ * @date 2026/5/22
5
+ * @version v1.0.0
6
+ *
7
+ * Headless editor composable — provides logic, no rendering.
8
+ */
9
+ export { useEditor } from './useEditor';
10
+ export type {
11
+ UseEditorOptions,
12
+ EditorConfig,
13
+ EditorToolbarConfig,
14
+ EditorExtensionConfig,
15
+ EditorToolbarActions,
16
+ EditorActiveState,
17
+ UseEditorReturn,
18
+ } from './types';
@@ -0,0 +1,147 @@
1
+ /**
2
+ * @description Editor composition types
3
+ * @author kine-design
4
+ * @date 2026/5/22
5
+ * @version v1.0.0
6
+ *
7
+ * Type definitions for the rich text editor composable.
8
+ */
9
+ import type { Editor } from '@tiptap/core';
10
+ import type { ShallowRef, Ref, ComputedRef } from 'vue';
11
+
12
+ /**
13
+ * Toolbar feature toggle. Each key controls visibility of a toolbar group.
14
+ * All default to true when omitted.
15
+ */
16
+ export interface EditorToolbarConfig {
17
+ bold?: boolean;
18
+ italic?: boolean;
19
+ strike?: boolean;
20
+ code?: boolean;
21
+ heading?: boolean;
22
+ bulletList?: boolean;
23
+ orderedList?: boolean;
24
+ blockquote?: boolean;
25
+ codeBlock?: boolean;
26
+ link?: boolean;
27
+ image?: boolean;
28
+ textAlign?: boolean;
29
+ undo?: boolean;
30
+ }
31
+
32
+ /**
33
+ * Extension-level configuration that is forwarded to individual tiptap extensions.
34
+ */
35
+ export interface EditorExtensionConfig {
36
+ link?: {
37
+ openOnClick?: boolean;
38
+ HTMLAttributes?: Record<string, string>;
39
+ };
40
+ image?: {
41
+ allowBase64?: boolean;
42
+ HTMLAttributes?: Record<string, string>;
43
+ };
44
+ placeholder?: {
45
+ text?: string;
46
+ };
47
+ }
48
+
49
+ /**
50
+ * Top-level editor configuration passed via props.
51
+ */
52
+ export interface EditorConfig {
53
+ toolbar?: EditorToolbarConfig;
54
+ extensions?: EditorExtensionConfig;
55
+ }
56
+
57
+ /**
58
+ * Options accepted by the useEditor composable.
59
+ */
60
+ export interface UseEditorOptions {
61
+ /** Initial HTML or plain-text content */
62
+ content?: string;
63
+ /** When true the editor is not editable */
64
+ readonly?: boolean;
65
+ /** Placeholder text shown when the editor is empty */
66
+ placeholder?: string;
67
+ /** Feature configuration */
68
+ config?: EditorConfig;
69
+ /** Callback fired when content changes (HTML string) */
70
+ onUpdate?: (html: string) => void;
71
+ /** Callback fired when the editor is ready */
72
+ onReady?: () => void;
73
+ /** Callback fired on focus */
74
+ onFocus?: () => void;
75
+ /** Callback fired on blur */
76
+ onBlur?: () => void;
77
+ }
78
+
79
+ /**
80
+ * Toolbar action functions returned by the composable.
81
+ */
82
+ export interface EditorToolbarActions {
83
+ toggleBold: () => void;
84
+ toggleItalic: () => void;
85
+ toggleStrike: () => void;
86
+ toggleCode: () => void;
87
+ setParagraph: () => void;
88
+ setHeading: (level: 1 | 2 | 3) => void;
89
+ toggleBulletList: () => void;
90
+ toggleOrderedList: () => void;
91
+ toggleBlockquote: () => void;
92
+ toggleCodeBlock: () => void;
93
+ setLink: (href: string) => void;
94
+ unsetLink: () => void;
95
+ setImage: (src: string, alt?: string) => void;
96
+ setTextAlign: (alignment: 'left' | 'center' | 'right') => void;
97
+ undo: () => void;
98
+ redo: () => void;
99
+ focus: () => void;
100
+ blur: () => void;
101
+ clear: () => void;
102
+ }
103
+
104
+ /**
105
+ * Active-state flags for toolbar highlighting.
106
+ */
107
+ export interface EditorActiveState {
108
+ bold: boolean;
109
+ italic: boolean;
110
+ strike: boolean;
111
+ code: boolean;
112
+ paragraph: boolean;
113
+ heading1: boolean;
114
+ heading2: boolean;
115
+ heading3: boolean;
116
+ bulletList: boolean;
117
+ orderedList: boolean;
118
+ blockquote: boolean;
119
+ codeBlock: boolean;
120
+ link: boolean;
121
+ }
122
+
123
+ /**
124
+ * Return type of the useEditor composable.
125
+ */
126
+ export interface UseEditorReturn {
127
+ /** The raw tiptap Editor instance (may be null before creation) */
128
+ editor: ShallowRef<Editor | undefined>;
129
+ /** Whether the editor has been created and is ready */
130
+ isReady: Ref<boolean>;
131
+ /** Toolbar action methods */
132
+ actions: ComputedRef<EditorToolbarActions | null>;
133
+ /** Active formatting state */
134
+ activeState: ComputedRef<EditorActiveState>;
135
+ /** Update editor content programmatically */
136
+ setContent: (html: string) => void;
137
+ /** Toggle the editable state */
138
+ setEditable: (editable: boolean) => void;
139
+ /** Get current content as HTML */
140
+ getHTML: () => string;
141
+ /** Get current content as plain text */
142
+ getText: () => string;
143
+ /** Whether undo is available */
144
+ canUndo: ComputedRef<boolean>;
145
+ /** Whether redo is available */
146
+ canRedo: ComputedRef<boolean>;
147
+ }
@@ -0,0 +1,224 @@
1
+ /**
2
+ * @description Headless editor composable built on tiptap
3
+ * @author kine-design
4
+ * @date 2026/5/22
5
+ * @version v1.0.0
6
+ *
7
+ * Provides editor state, toolbar actions, and active-state tracking
8
+ * without any rendering — the UI layer consumes this composable.
9
+ */
10
+ import { ref, computed, onBeforeUnmount } from 'vue';
11
+ import { useEditor as useTiptapEditor } from '@tiptap/vue-3';
12
+ import StarterKit from '@tiptap/starter-kit';
13
+ import Link from '@tiptap/extension-link';
14
+ import Image from '@tiptap/extension-image';
15
+ import Placeholder from '@tiptap/extension-placeholder';
16
+ import TextAlign from '@tiptap/extension-text-align';
17
+ import CodeBlockLowlight from '@tiptap/extension-code-block-lowlight';
18
+ import { common, createLowlight } from 'lowlight';
19
+ import type { UseEditorOptions, EditorToolbarActions, EditorActiveState } from './types';
20
+
21
+ const lowlight = createLowlight(common);
22
+
23
+ export function useEditor(options: UseEditorOptions = {}) {
24
+ const {
25
+ content = '',
26
+ readonly = false,
27
+ placeholder = 'Start typing...',
28
+ config = {},
29
+ onUpdate,
30
+ onReady,
31
+ onFocus,
32
+ onBlur,
33
+ } = options;
34
+
35
+ const isReady = ref(false);
36
+
37
+ // Build extension array based on config
38
+ const buildExtensions = () => {
39
+ const toolbar = config.toolbar ?? {};
40
+ const ext = config.extensions ?? {};
41
+
42
+ const extensions = [
43
+ StarterKit.configure({
44
+ heading: toolbar.heading !== false ? { levels: [1, 2, 3] } : false,
45
+ bold: toolbar.bold !== false ? {} : false,
46
+ italic: toolbar.italic !== false ? {} : false,
47
+ strike: toolbar.strike !== false ? {} : false,
48
+ code: toolbar.code !== false ? {} : false,
49
+ bulletList: toolbar.bulletList !== false ? {} : false,
50
+ orderedList: toolbar.orderedList !== false ? {} : false,
51
+ blockquote: toolbar.blockquote !== false ? {} : false,
52
+ // Disable the built-in code block — we use lowlight instead
53
+ codeBlock: false,
54
+ }),
55
+ Placeholder.configure({
56
+ placeholder: ext.placeholder?.text ?? placeholder,
57
+ }),
58
+ ];
59
+
60
+ // Code block with syntax highlighting
61
+ if (toolbar.codeBlock !== false) {
62
+ extensions.push(
63
+ CodeBlockLowlight.configure({
64
+ lowlight,
65
+ HTMLAttributes: {
66
+ class: 'k-editor-code-block',
67
+ },
68
+ }),
69
+ );
70
+ }
71
+
72
+ // Link extension
73
+ if (toolbar.link !== false) {
74
+ extensions.push(
75
+ Link.configure({
76
+ openOnClick: ext.link?.openOnClick ?? false,
77
+ HTMLAttributes: {
78
+ class: 'k-editor-link',
79
+ ...ext.link?.HTMLAttributes,
80
+ },
81
+ }),
82
+ );
83
+ }
84
+
85
+ // Image extension
86
+ if (toolbar.image !== false) {
87
+ extensions.push(
88
+ Image.configure({
89
+ inline: true,
90
+ allowBase64: ext.image?.allowBase64 ?? false,
91
+ HTMLAttributes: {
92
+ class: 'k-editor-image',
93
+ ...ext.image?.HTMLAttributes,
94
+ },
95
+ }),
96
+ );
97
+ }
98
+
99
+ // Text alignment
100
+ if (toolbar.textAlign !== false) {
101
+ extensions.push(
102
+ TextAlign.configure({
103
+ types: ['heading', 'paragraph'],
104
+ }),
105
+ );
106
+ }
107
+
108
+ return extensions;
109
+ };
110
+
111
+ // Create the tiptap editor
112
+ const editor = useTiptapEditor({
113
+ content,
114
+ editable: !readonly,
115
+ extensions: buildExtensions(),
116
+ onUpdate: ({ editor: e }) => {
117
+ onUpdate?.(e.getHTML());
118
+ },
119
+ onCreate: () => {
120
+ isReady.value = true;
121
+ onReady?.();
122
+ },
123
+ onFocus: () => {
124
+ onFocus?.();
125
+ },
126
+ onBlur: () => {
127
+ onBlur?.();
128
+ },
129
+ });
130
+
131
+ // Toolbar actions
132
+ const actions = computed<EditorToolbarActions | null>(() => {
133
+ const e = editor.value;
134
+ if (!e) return null;
135
+
136
+ return {
137
+ toggleBold: () => { e.chain().focus().toggleBold().run(); },
138
+ toggleItalic: () => { e.chain().focus().toggleItalic().run(); },
139
+ toggleStrike: () => { e.chain().focus().toggleStrike().run(); },
140
+ toggleCode: () => { e.chain().focus().toggleCode().run(); },
141
+ setParagraph: () => { e.chain().focus().setParagraph().run(); },
142
+ setHeading: (level: 1 | 2 | 3) => { e.chain().focus().toggleHeading({ level }).run(); },
143
+ toggleBulletList: () => { e.chain().focus().toggleBulletList().run(); },
144
+ toggleOrderedList: () => { e.chain().focus().toggleOrderedList().run(); },
145
+ toggleBlockquote: () => { e.chain().focus().toggleBlockquote().run(); },
146
+ toggleCodeBlock: () => { e.chain().focus().toggleCodeBlock().run(); },
147
+ setLink: (href: string) => { e.chain().focus().setLink({ href }).run(); },
148
+ unsetLink: () => { e.chain().focus().unsetLink().run(); },
149
+ setImage: (src: string, alt?: string) => { e.chain().focus().setImage({ src, alt }).run(); },
150
+ setTextAlign: (alignment: 'left' | 'center' | 'right') => {
151
+ e.chain().focus().setTextAlign(alignment).run();
152
+ },
153
+ undo: () => { e.chain().focus().undo().run(); },
154
+ redo: () => { e.chain().focus().redo().run(); },
155
+ focus: () => { e.chain().focus().run(); },
156
+ blur: () => { e.commands.blur(); },
157
+ clear: () => { e.chain().focus().clearContent().run(); },
158
+ };
159
+ });
160
+
161
+ // Active state tracking
162
+ const activeState = computed<EditorActiveState>(() => {
163
+ const e = editor.value;
164
+ if (!e) {
165
+ return {
166
+ bold: false, italic: false, strike: false, code: false,
167
+ paragraph: false, heading1: false, heading2: false, heading3: false,
168
+ bulletList: false, orderedList: false, blockquote: false,
169
+ codeBlock: false, link: false,
170
+ };
171
+ }
172
+ return {
173
+ bold: e.isActive('bold'),
174
+ italic: e.isActive('italic'),
175
+ strike: e.isActive('strike'),
176
+ code: e.isActive('code'),
177
+ paragraph: e.isActive('paragraph'),
178
+ heading1: e.isActive('heading', { level: 1 }),
179
+ heading2: e.isActive('heading', { level: 2 }),
180
+ heading3: e.isActive('heading', { level: 3 }),
181
+ bulletList: e.isActive('bulletList'),
182
+ orderedList: e.isActive('orderedList'),
183
+ blockquote: e.isActive('blockquote'),
184
+ codeBlock: e.isActive('codeBlock'),
185
+ link: e.isActive('link'),
186
+ };
187
+ });
188
+
189
+ const canUndo = computed(() => editor.value?.can().undo() ?? false);
190
+ const canRedo = computed(() => editor.value?.can().redo() ?? false);
191
+
192
+ // Content management
193
+ const setContent = (html: string) => {
194
+ if (!editor.value) return;
195
+ const current = editor.value.getHTML();
196
+ if (current === html) return;
197
+ editor.value.commands.setContent(html);
198
+ };
199
+
200
+ const setEditable = (editable: boolean) => {
201
+ editor.value?.setEditable(editable);
202
+ };
203
+
204
+ const getHTML = () => editor.value?.getHTML() ?? '';
205
+ const getText = () => editor.value?.getText() ?? '';
206
+
207
+ // Cleanup
208
+ onBeforeUnmount(() => {
209
+ editor.value?.destroy();
210
+ });
211
+
212
+ return {
213
+ editor,
214
+ isReady,
215
+ actions,
216
+ activeState,
217
+ setContent,
218
+ setEditable,
219
+ getHTML,
220
+ getText,
221
+ canUndo,
222
+ canRedo,
223
+ };
224
+ }
@@ -0,0 +1,15 @@
1
+ /**
2
+ * @description compositions barrel export
3
+ * @author kine-design
4
+ * @date 2026/5/22 00:00
5
+ * @version v1.0.0
6
+ *
7
+ * 江湖的业务千篇一律,复杂的代码好几百行。
8
+ */
9
+ export * from './overlay';
10
+ export * from './contextMenu';
11
+ export * from './peekView';
12
+ export * from './commandPalette';
13
+ export * from './tooltip';
14
+ export * from './editor';
15
+ export * from './theme';
@@ -0,0 +1,14 @@
1
+ /**
2
+ * @description overlay composables barrel export
3
+ * @author kine-design
4
+ * @date 2026/5/22 00:00
5
+ * @version v1.0.0
6
+ *
7
+ * 江湖的业务千篇一律,复杂的代码好几百行。
8
+ */
9
+ export {
10
+ useOverlayStackStore,
11
+ useOverlayStack,
12
+ useOverlayItem,
13
+ } from './useOverlayStack';
14
+ export type { OverlayItem, OverlayType } from './useOverlayStack';