@aiao/code-editor-vue 0.0.23 → 0.0.25
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 +71 -9
- package/dist/CodeEditor.vue.d.ts +7 -2
- package/dist/CodeEditor.vue.d.ts.map +1 -1
- package/dist/code-editor.types.d.ts +146 -2
- package/dist/code-editor.types.d.ts.map +1 -1
- package/dist/index.d.ts +1 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +125 -86
- package/package.json +15 -8
- package/src/CodeEditor.vue +316 -0
- package/src/code-editor.types.ts +177 -0
- package/src/index.ts +22 -0
- package/src/vue-shims.d.ts +5 -0
- package/dist/index.css +0 -2
package/README.md
CHANGED
|
@@ -14,7 +14,6 @@ pnpm add @aiao/code-editor-vue
|
|
|
14
14
|
<script lang="ts" setup>
|
|
15
15
|
import { ref } from 'vue';
|
|
16
16
|
import { CodeEditor } from '@aiao/code-editor-vue';
|
|
17
|
-
import '@aiao/code-editor-vue/style.css';
|
|
18
17
|
|
|
19
18
|
const code = ref('const answer = 42;');
|
|
20
19
|
</script>
|
|
@@ -51,7 +50,8 @@ const code = ref('const answer = 42;');
|
|
|
51
50
|
| `autoFocus` | `boolean` | `false` | 仅初始化时读取 |
|
|
52
51
|
| `root` | `Document \| ShadowRoot` | `undefined` | 仅初始化时传给编辑器 |
|
|
53
52
|
|
|
54
|
-
空字符串或 `plaintext`
|
|
53
|
+
空字符串或 `plaintext` 会清空语言扩展。未知语言和加载失败会**降级为纯文本**并发出 `language-error`
|
|
54
|
+
(见下),编辑器本身继续可用。快速切换语言时,只有最后一次请求可以更新当前编辑器。
|
|
55
55
|
|
|
56
56
|
自定义语言列表使用核心包的公开类型:
|
|
57
57
|
|
|
@@ -68,15 +68,77 @@ const languages: readonly CodeEditorLanguageDescription[] = [myLanguage];
|
|
|
68
68
|
|
|
69
69
|
## Events
|
|
70
70
|
|
|
71
|
-
| 事件
|
|
72
|
-
|
|
|
73
|
-
| `update:value`
|
|
74
|
-
| `change`
|
|
75
|
-
| `focus`
|
|
76
|
-
| `blur`
|
|
71
|
+
| 事件 | 参数 | 触发时机 |
|
|
72
|
+
| ---------------- | ---------------------------------- | ---------------------------------------------------------- |
|
|
73
|
+
| `update:value` | `(value: string)` | 用户修改文档 |
|
|
74
|
+
| `change` | `(value: string)` | 用户修改文档 |
|
|
75
|
+
| `focus` | `()` | 编辑器内容区域获得焦点 |
|
|
76
|
+
| `blur` | `()` | 编辑器内容区域失去焦点 |
|
|
77
|
+
| `language-error` | `(error: CodeEditorLanguageError)` | 语言查不到(`not-found`)或 `load()` 失败(`load-failed`) |
|
|
78
|
+
|
|
79
|
+
## 语言错误
|
|
80
|
+
|
|
81
|
+
降级为纯文本时编辑器仍然可用,但宿主需要能观测到这件事 —— `language-error` 就是这个通道:
|
|
82
|
+
|
|
83
|
+
```vue
|
|
84
|
+
<script lang="ts" setup>
|
|
85
|
+
import type { CodeEditorLanguageError } from '@aiao/code-editor';
|
|
86
|
+
import { CodeEditor } from '@aiao/code-editor-vue';
|
|
87
|
+
|
|
88
|
+
function onLanguageError(error: CodeEditorLanguageError) {
|
|
89
|
+
// 'not-found' 是配置错误,重试没有意义;'load-failed' 通常是网络或分包问题,可以重试。
|
|
90
|
+
if (error.kind === 'load-failed') retry(error.language);
|
|
91
|
+
}
|
|
92
|
+
</script>
|
|
93
|
+
|
|
94
|
+
<template>
|
|
95
|
+
<CodeEditor :language="lang" @language-error="onLanguageError" />
|
|
96
|
+
</template>
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
载荷形状(`kind` / `language` / `message` / `cause`)由核心包定义,三端一致:
|
|
100
|
+
Angular 是 `(aoLanguageError)` 输出,React 是 `onLanguageError` 回调。
|
|
101
|
+
|
|
102
|
+
## 模板 ref 用法
|
|
103
|
+
|
|
104
|
+
组件通过 `defineExpose` 暴露命令式 API,供模板 ref 访问底层 `EditorView`:
|
|
105
|
+
|
|
106
|
+
```vue
|
|
107
|
+
<script lang="ts" setup>
|
|
108
|
+
import { ref } from 'vue';
|
|
109
|
+
import { CodeEditor } from '@aiao/code-editor-vue';
|
|
110
|
+
import type { CodeEditorExpose } from '@aiao/code-editor-vue';
|
|
111
|
+
|
|
112
|
+
const editorRef = ref<CodeEditorExpose | null>(null);
|
|
113
|
+
const code = ref('const answer = 42;');
|
|
114
|
+
|
|
115
|
+
function focusEditor() {
|
|
116
|
+
editorRef.value?.focus();
|
|
117
|
+
}
|
|
118
|
+
</script>
|
|
119
|
+
|
|
120
|
+
<template>
|
|
121
|
+
<CodeEditor v-model:value="code" ref="editorRef" />
|
|
122
|
+
</template>
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
四个成员与 React 的 `CodeEditorHandle`、Angular 组件实例上的同名成员逐字对齐,跨端迁移时命令式面不需要改写:
|
|
126
|
+
|
|
127
|
+
| 成员 | 类型 | 说明 |
|
|
128
|
+
| ------- | ------------------------ | ----------------------------------------------- |
|
|
129
|
+
| `view` | `EditorView \| null` | 底层 CodeMirror 实例,未挂载或已卸载时为 `null` |
|
|
130
|
+
| `host` | `HTMLDivElement \| null` | 宿主 `div` 元素,未挂载或已卸载时为 `null` |
|
|
131
|
+
| `focus` | `() => void` | 聚焦编辑器内容区域 |
|
|
132
|
+
| `blur` | `() => void` | 使编辑器内容区域失焦 |
|
|
77
133
|
|
|
78
134
|
## 公开类型
|
|
79
135
|
|
|
80
136
|
```ts
|
|
81
|
-
import type {
|
|
137
|
+
import type {
|
|
138
|
+
CodeEditorEmits,
|
|
139
|
+
CodeEditorExpose,
|
|
140
|
+
CodeEditorProps,
|
|
141
|
+
CodeEditorSetup,
|
|
142
|
+
CodeEditorTheme
|
|
143
|
+
} from '@aiao/code-editor-vue';
|
|
82
144
|
```
|
package/dist/CodeEditor.vue.d.ts
CHANGED
|
@@ -1,22 +1,27 @@
|
|
|
1
|
-
import { CodeEditorProps, CodeEditorSetup } from './code-editor.types.js';
|
|
2
|
-
declare const __VLS_export: import('vue').DefineComponent<CodeEditorProps,
|
|
1
|
+
import { CodeEditorExpose, CodeEditorProps, CodeEditorSetup } from './code-editor.types.js';
|
|
2
|
+
declare const __VLS_export: import('vue').DefineComponent<CodeEditorProps, CodeEditorExpose, {}, {}, {}, import('vue').ComponentOptionsMixin, import('vue').ComponentOptionsMixin, {
|
|
3
3
|
blur: () => any;
|
|
4
4
|
change: (value: string) => any;
|
|
5
5
|
focus: () => any;
|
|
6
|
+
"language-error": (error: import('@aiao/code-editor').CodeEditorLanguageError) => any;
|
|
6
7
|
"update:value": (value: string) => any;
|
|
7
8
|
}, string, import('vue').PublicProps, Readonly<CodeEditorProps> & Readonly<{
|
|
8
9
|
onBlur?: (() => any) | undefined;
|
|
9
10
|
onChange?: ((value: string) => any) | undefined;
|
|
10
11
|
onFocus?: (() => any) | undefined;
|
|
12
|
+
"onLanguage-error"?: ((error: import('@aiao/code-editor').CodeEditorLanguageError) => any) | undefined;
|
|
11
13
|
"onUpdate:value"?: ((value: string) => any) | undefined;
|
|
12
14
|
}>, {
|
|
13
15
|
readonly autoFocus: boolean;
|
|
16
|
+
readonly describedBy: string;
|
|
14
17
|
readonly disabled: boolean;
|
|
15
18
|
readonly highlightWhitespace: boolean;
|
|
16
19
|
readonly indentUnit: string;
|
|
17
20
|
readonly indentWithTab: boolean;
|
|
18
21
|
readonly language: string;
|
|
19
22
|
readonly languages: readonly import('@aiao/code-editor').CodeEditorLanguageDescription[];
|
|
23
|
+
readonly label: string;
|
|
24
|
+
readonly labelledBy: string;
|
|
20
25
|
readonly lineWrapping: boolean;
|
|
21
26
|
readonly placeholder: string;
|
|
22
27
|
readonly readonly: boolean;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CodeEditor.vue.d.ts","sourceRoot":"","sources":["../src/CodeEditor.vue"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"CodeEditor.vue.d.ts","sourceRoot":"","sources":["../src/CodeEditor.vue"],"names":[],"mappings":"AAuVA,OAAO,KAAK,EAAmB,gBAAgB,EAAE,eAAe,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AA8UlH,QAAA,MAAM,YAAY;;;;;;;;;;;;;;;;;;;;;;;;;;;;;6EAKhB,CAAC;wBACkB,OAAO,YAAY;AAAxC,wBAAyC"}
|
|
@@ -1,29 +1,173 @@
|
|
|
1
|
-
import { CodeEditorLanguageDescription, CodeEditorTheme } from '@aiao/code-editor';
|
|
1
|
+
import { CodeEditorLanguageDescription, CodeEditorLanguageError, CodeEditorTheme } from '@aiao/code-editor';
|
|
2
|
+
import { EditorView } from '@codemirror/view';
|
|
2
3
|
/** CodeMirror 预置扩展集合。 */
|
|
3
4
|
export type CodeEditorSetup = 'basic' | 'minimal' | null;
|
|
4
|
-
/**
|
|
5
|
+
/**
|
|
6
|
+
* Vue CodeEditor 的公开属性。
|
|
7
|
+
*
|
|
8
|
+
* @remarks 与 Angular 的 inputs、React 的 props 同名同义;仅初始化生效的两项
|
|
9
|
+
* ({@link CodeEditorProps.autoFocus} / {@link CodeEditorProps.root})在各自条目上标注。
|
|
10
|
+
*/
|
|
5
11
|
export interface CodeEditorProps {
|
|
12
|
+
/**
|
|
13
|
+
* 挂载后立即聚焦编辑器。
|
|
14
|
+
*
|
|
15
|
+
* @defaultValue false
|
|
16
|
+
* @remarks **仅挂载时读取一次**,后续变更被忽略。`disabled` 或 `readonly` 为真时
|
|
17
|
+
* 不抢焦点 —— 否则用户得到一个无法输入、也无从得知为什么无法输入的控件。
|
|
18
|
+
*/
|
|
6
19
|
readonly autoFocus?: boolean;
|
|
20
|
+
/**
|
|
21
|
+
* 补充说明元素的 id,写成内部 textbox 的 `aria-describedby`。
|
|
22
|
+
*
|
|
23
|
+
* @remarks 属性落在 CodeMirror 的 `.cm-content` 上 —— 承担 `role="textbox"` 的是它,
|
|
24
|
+
* 不是组件的宿主元素。空串按「未设置」处理。三端同名同义。
|
|
25
|
+
*/
|
|
26
|
+
readonly describedBy?: string;
|
|
27
|
+
/**
|
|
28
|
+
* 禁用:只读且不可编辑,并向辅助技术报告 `aria-disabled`。
|
|
29
|
+
*
|
|
30
|
+
* @defaultValue false
|
|
31
|
+
* @remarks 与 `readonly` 是独立的两个属性,任一为真都会让编辑器不可编辑;
|
|
32
|
+
* 区别在于禁用还会写出 `aria-disabled` 并抑制 `autoFocus`。
|
|
33
|
+
*/
|
|
7
34
|
readonly disabled?: boolean;
|
|
35
|
+
/**
|
|
36
|
+
* 显示空白字符。
|
|
37
|
+
*
|
|
38
|
+
* @defaultValue false
|
|
39
|
+
*/
|
|
8
40
|
readonly highlightWhitespace?: boolean;
|
|
41
|
+
/**
|
|
42
|
+
* 一级缩进使用的字符串。
|
|
43
|
+
*
|
|
44
|
+
* @defaultValue ' '(两个空格,三端一致)
|
|
45
|
+
*/
|
|
9
46
|
readonly indentUnit?: string;
|
|
47
|
+
/**
|
|
48
|
+
* 允许 Tab 键缩进。
|
|
49
|
+
*
|
|
50
|
+
* @defaultValue false
|
|
51
|
+
* @remarks 打开后 Tab 不再移动焦点,键盘用户会被困在编辑器里 —— 这是可达性取舍,
|
|
52
|
+
* 三端默认一致地关闭。
|
|
53
|
+
*/
|
|
10
54
|
readonly indentWithTab?: boolean;
|
|
55
|
+
/**
|
|
56
|
+
* 语法高亮语言名或别名;`''` 与 `'plaintext'` 表示不高亮。
|
|
57
|
+
*
|
|
58
|
+
* @defaultValue 'sql'
|
|
59
|
+
* @remarks 查不到或 `load()` 失败时**降级为纯文本**并继续可用,同时发出
|
|
60
|
+
* `language-error` 事件(见 {@link CodeEditorEmits})。快速切换时只有最后一次请求生效。
|
|
61
|
+
*/
|
|
11
62
|
readonly language?: string;
|
|
63
|
+
/**
|
|
64
|
+
* 候选语言列表;空列表表示不高亮。
|
|
65
|
+
*
|
|
66
|
+
* @defaultValue SUPPORT_LANGUAGES
|
|
67
|
+
* @remarks 传内联字面量(`:languages="[myLang]"`)是有成本的:每轮更新都是新数组,
|
|
68
|
+
* 而 `watch` 按引用触发。组件会再判断当前语言解析到的 description 是否真的换了 ——
|
|
69
|
+
* 只有真换了才重新 `load()`,但这层判断本身仍要跑,稳定引用(模块级常量或 `computed`)更省。
|
|
70
|
+
*/
|
|
12
71
|
readonly languages?: readonly CodeEditorLanguageDescription[];
|
|
72
|
+
/**
|
|
73
|
+
* 可访问名称,写成内部 textbox 的 `aria-label`。
|
|
74
|
+
*
|
|
75
|
+
* @remarks 落点与 {@link CodeEditorProps.describedBy} 相同。
|
|
76
|
+
*/
|
|
77
|
+
readonly label?: string;
|
|
78
|
+
/**
|
|
79
|
+
* 承担标签的元素 id,写成内部 textbox 的 `aria-labelledby`。
|
|
80
|
+
*
|
|
81
|
+
* @remarks 落点与 {@link CodeEditorProps.describedBy} 相同。
|
|
82
|
+
*/
|
|
83
|
+
readonly labelledBy?: string;
|
|
84
|
+
/**
|
|
85
|
+
* 自动换行。
|
|
86
|
+
*
|
|
87
|
+
* @defaultValue false
|
|
88
|
+
*/
|
|
13
89
|
readonly lineWrapping?: boolean;
|
|
90
|
+
/**
|
|
91
|
+
* 空文档时显示的占位文本。
|
|
92
|
+
*
|
|
93
|
+
* @defaultValue ''
|
|
94
|
+
*/
|
|
14
95
|
readonly placeholder?: string;
|
|
96
|
+
/**
|
|
97
|
+
* 只读:仍可聚焦、可选择、可复制,但不能改。
|
|
98
|
+
*
|
|
99
|
+
* @defaultValue false
|
|
100
|
+
* @remarks 只挡编辑**命令**,不挡宿主同步 —— `value` 变化照样写进文档,
|
|
101
|
+
* 否则只读的受控用法直接失效。
|
|
102
|
+
*/
|
|
15
103
|
readonly readonly?: boolean;
|
|
104
|
+
/**
|
|
105
|
+
* CodeMirror 挂载的 DOM 根,用于 Shadow DOM 场景。
|
|
106
|
+
*
|
|
107
|
+
* @remarks **仅挂载时读取一次** —— CodeMirror 无法在构造后改 root。
|
|
108
|
+
* 需要换 root 时请用 `v-if` 或 `:key` 强制重建组件。
|
|
109
|
+
*/
|
|
16
110
|
readonly root?: Document | ShadowRoot;
|
|
111
|
+
/**
|
|
112
|
+
* CodeMirror 预设扩展集;`null` 表示不加载任何预设。
|
|
113
|
+
*
|
|
114
|
+
* @defaultValue 'basic'
|
|
115
|
+
*/
|
|
17
116
|
readonly setup?: CodeEditorSetup;
|
|
117
|
+
/**
|
|
118
|
+
* 主题。
|
|
119
|
+
*
|
|
120
|
+
* @defaultValue 'light'
|
|
121
|
+
*/
|
|
18
122
|
readonly theme?: CodeEditorTheme;
|
|
123
|
+
/**
|
|
124
|
+
* 文档内容,配合 `v-model:value` 使用。
|
|
125
|
+
*
|
|
126
|
+
* @defaultValue ''
|
|
127
|
+
* @remarks **不是严格受控**:组件不会把用户编辑写回本属性,也不会在每次更新时
|
|
128
|
+
* 强行拉平文档。本属性变化时只同步公共前后缀之间的那一段差量,光标、选区与滚动位置都保留,
|
|
129
|
+
* 且不进 undo 栈。父组件忽略 `update:value` 时编辑器会保留用户输入。
|
|
130
|
+
*/
|
|
19
131
|
readonly value?: string;
|
|
20
132
|
}
|
|
21
133
|
/** Vue CodeEditor 的公开事件。 */
|
|
22
134
|
export interface CodeEditorEmits {
|
|
135
|
+
/** 编辑器内容区域失去焦点。 */
|
|
23
136
|
blur: [];
|
|
137
|
+
/** 用户编辑导致的内容变化;宿主经 `value` 的程序化写入**不**触发。 */
|
|
24
138
|
change: [value: string];
|
|
139
|
+
/** 编辑器内容区域获得焦点。 */
|
|
25
140
|
focus: [];
|
|
141
|
+
/**
|
|
142
|
+
* 语言解析或加载失败。
|
|
143
|
+
*
|
|
144
|
+
* @remarks
|
|
145
|
+
* CEV-004:这条路径此前只有一条 `console.error`,宿主完全观测不到 ——
|
|
146
|
+
* 拿不到错误、没有重试或降级的机会,用户只看到高亮突然消失。
|
|
147
|
+
* 降级策略不变(清空语言扩展退回纯文本),日志也保留,本事件是**新增的**观测通道。
|
|
148
|
+
* 载荷由核心包定义,与 Angular 的 `aoLanguageError` 输出、React 的 `onLanguageError`
|
|
149
|
+
* 回调逐字一致:`kind` 为 `'not-found'` 时是配置错误,重试没有意义;
|
|
150
|
+
* 为 `'load-failed'` 时通常是网络或分包问题,可以重试。
|
|
151
|
+
*/
|
|
152
|
+
'language-error': [error: CodeEditorLanguageError];
|
|
153
|
+
/** `v-model:value` 的更新通道,触发时机与 `change` 相同。 */
|
|
26
154
|
'update:value': [value: string];
|
|
27
155
|
}
|
|
156
|
+
/**
|
|
157
|
+
* `defineExpose` 暴露给模板 ref 的命令式 API。
|
|
158
|
+
*
|
|
159
|
+
* @remarks 四个成员与 React 的 `CodeEditorHandle`、Angular 组件实例上的同名成员
|
|
160
|
+
* 逐字对齐(CEA-009)—— 跨端迁移时命令式面不需要改写。
|
|
161
|
+
*/
|
|
162
|
+
export interface CodeEditorExpose {
|
|
163
|
+
/** 底层 CodeMirror EditorView,未挂载或已卸载时为 null。 */
|
|
164
|
+
readonly view: EditorView | null;
|
|
165
|
+
/** 宿主 `div` 元素(CodeMirror 挂载所在的容器),未挂载或已卸载时为 null。 */
|
|
166
|
+
readonly host: HTMLDivElement | null;
|
|
167
|
+
/** 移开键盘焦点;未挂载时是空操作。 */
|
|
168
|
+
blur(): void;
|
|
169
|
+
/** 把键盘焦点交给编辑器内容区域;未挂载时是空操作。 */
|
|
170
|
+
focus(): void;
|
|
171
|
+
}
|
|
28
172
|
export type { CodeEditorTheme } from '@aiao/code-editor';
|
|
29
173
|
//# sourceMappingURL=code-editor.types.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"code-editor.types.d.ts","sourceRoot":"","sources":["../src/code-editor.types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,6BAA6B,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;
|
|
1
|
+
{"version":3,"file":"code-editor.types.d.ts","sourceRoot":"","sources":["../src/code-editor.types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,6BAA6B,EAAE,uBAAuB,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AACjH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAEnD,yBAAyB;AACzB,MAAM,MAAM,eAAe,GAAG,OAAO,GAAG,SAAS,GAAG,IAAI,CAAC;AAEzD;;;;;GAKG;AACH,MAAM,WAAW,eAAe;IAC9B;;;;;;OAMG;IACH,QAAQ,CAAC,SAAS,CAAC,EAAE,OAAO,CAAC;IAC7B;;;;;OAKG;IACH,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAC9B;;;;;;OAMG;IACH,QAAQ,CAAC,QAAQ,CAAC,EAAE,OAAO,CAAC;IAC5B;;;;OAIG;IACH,QAAQ,CAAC,mBAAmB,CAAC,EAAE,OAAO,CAAC;IACvC;;;;OAIG;IACH,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;IAC7B;;;;;;OAMG;IACH,QAAQ,CAAC,aAAa,CAAC,EAAE,OAAO,CAAC;IACjC;;;;;;OAMG;IACH,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAC3B;;;;;;;OAOG;IACH,QAAQ,CAAC,SAAS,CAAC,EAAE,SAAS,6BAA6B,EAAE,CAAC;IAC9D;;;;OAIG;IACH,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IACxB;;;;OAIG;IACH,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;IAC7B;;;;OAIG;IACH,QAAQ,CAAC,YAAY,CAAC,EAAE,OAAO,CAAC;IAChC;;;;OAIG;IACH,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAC9B;;;;;;OAMG;IACH,QAAQ,CAAC,QAAQ,CAAC,EAAE,OAAO,CAAC;IAC5B;;;;;OAKG;IACH,QAAQ,CAAC,IAAI,CAAC,EAAE,QAAQ,GAAG,UAAU,CAAC;IACtC;;;;OAIG;IACH,QAAQ,CAAC,KAAK,CAAC,EAAE,eAAe,CAAC;IACjC;;;;OAIG;IACH,QAAQ,CAAC,KAAK,CAAC,EAAE,eAAe,CAAC;IACjC;;;;;;;OAOG;IACH,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;CACzB;AAED,4BAA4B;AAC5B,MAAM,WAAW,eAAe;IAC9B,mBAAmB;IACnB,IAAI,EAAE,EAAE,CAAC;IACT,6CAA6C;IAC7C,MAAM,EAAE,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC;IACxB,mBAAmB;IACnB,KAAK,EAAE,EAAE,CAAC;IACV;;;;;;;;;;OAUG;IACH,gBAAgB,EAAE,CAAC,KAAK,EAAE,uBAAuB,CAAC,CAAC;IACnD,+CAA+C;IAC/C,cAAc,EAAE,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC;CACjC;AAED;;;;;GAKG;AACH,MAAM,WAAW,gBAAgB;IAC/B,+CAA+C;IAC/C,QAAQ,CAAC,IAAI,EAAE,UAAU,GAAG,IAAI,CAAC;IACjC,sDAAsD;IACtD,QAAQ,CAAC,IAAI,EAAE,cAAc,GAAG,IAAI,CAAC;IACrC,uBAAuB;IACvB,IAAI,IAAI,IAAI,CAAC;IACb,+BAA+B;IAC/B,KAAK,IAAI,IAAI,CAAC;CACf;AAED,YAAY,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC"}
|
package/dist/index.d.ts
CHANGED
|
@@ -5,7 +5,6 @@
|
|
|
5
5
|
* ```vue
|
|
6
6
|
* <script setup lang="ts">
|
|
7
7
|
* import { CodeEditor } from '@aiao/code-editor-vue';
|
|
8
|
-
* import '@aiao/code-editor-vue/style.css';
|
|
9
8
|
* </script>
|
|
10
9
|
*
|
|
11
10
|
* <template>
|
|
@@ -13,6 +12,6 @@
|
|
|
13
12
|
* </template>
|
|
14
13
|
* ```
|
|
15
14
|
*/
|
|
16
|
-
export type { CodeEditorEmits, CodeEditorProps, CodeEditorSetup, CodeEditorTheme } from './code-editor.types.js';
|
|
15
|
+
export type { CodeEditorEmits, CodeEditorExpose, CodeEditorProps, CodeEditorSetup, CodeEditorTheme } from './code-editor.types.js';
|
|
17
16
|
export { default as CodeEditor } from './CodeEditor.vue';
|
|
18
17
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AACH,YAAY,EACV,eAAe,EACf,gBAAgB,EAChB,eAAe,EACf,eAAe,EACf,eAAe,EAChB,MAAM,wBAAwB,CAAC;AAChC,OAAO,EAAE,OAAO,IAAI,UAAU,EAAE,MAAM,kBAAkB,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -1,24 +1,21 @@
|
|
|
1
|
-
import { computed as e, createElementBlock as t, defineComponent as n, mergeProps as r, onMounted as
|
|
2
|
-
import { SUPPORT_LANGUAGES as u,
|
|
1
|
+
import { computed as e, createElementBlock as t, defineComponent as n, mergeProps as r, onMounted as ee, onUnmounted as te, openBlock as i, ref as ne, shallowRef as re, watch as a } from "vue";
|
|
2
|
+
import { SUPPORT_LANGUAGES as o, buildCodeEditorContentAttributes as s, codeEditorLanguageLoadFailed as ie, codeEditorLanguageNotFound as c, computeMinimalDocumentChange as ae, isSameResolvedLanguage as l, resolveCodeEditorLanguage as u, shouldAutoFocusCodeEditor as d } from "@aiao/code-editor";
|
|
3
3
|
import { indentWithTab as f } from "@codemirror/commands";
|
|
4
|
-
import { indentUnit as
|
|
5
|
-
import { Annotation as
|
|
6
|
-
import { oneDark as
|
|
7
|
-
import { EditorView as
|
|
8
|
-
import { basicSetup as
|
|
4
|
+
import { indentUnit as oe } from "@codemirror/language";
|
|
5
|
+
import { Annotation as se, Compartment as p, EditorState as m, Transaction as h } from "@codemirror/state";
|
|
6
|
+
import { oneDark as g } from "@codemirror/theme-one-dark";
|
|
7
|
+
import { EditorView as _, highlightWhitespace as v, keymap as y, placeholder as b } from "@codemirror/view";
|
|
8
|
+
import { basicSetup as x, minimalSetup as S } from "codemirror";
|
|
9
9
|
//#endregion
|
|
10
10
|
//#region src/CodeEditor.vue
|
|
11
|
-
var
|
|
12
|
-
let n = e.__vccOpts || e;
|
|
13
|
-
for (let [e, r] of t) n[e] = r;
|
|
14
|
-
return n;
|
|
15
|
-
})(/* @__PURE__ */ n({
|
|
11
|
+
var C = /* @__PURE__ */ n({
|
|
16
12
|
__name: "CodeEditor",
|
|
17
13
|
props: {
|
|
18
14
|
autoFocus: {
|
|
19
15
|
type: Boolean,
|
|
20
16
|
default: !1
|
|
21
17
|
},
|
|
18
|
+
describedBy: { default: "" },
|
|
22
19
|
disabled: {
|
|
23
20
|
type: Boolean,
|
|
24
21
|
default: !1
|
|
@@ -33,7 +30,9 @@ var w = /*#__PURE__*/ ((e, t) => {
|
|
|
33
30
|
default: !1
|
|
34
31
|
},
|
|
35
32
|
language: { default: "sql" },
|
|
36
|
-
languages: { default: () =>
|
|
33
|
+
languages: { default: () => o },
|
|
34
|
+
label: { default: "" },
|
|
35
|
+
labelledBy: { default: "" },
|
|
37
36
|
lineWrapping: {
|
|
38
37
|
type: Boolean,
|
|
39
38
|
default: !1
|
|
@@ -52,118 +51,158 @@ var w = /*#__PURE__*/ ((e, t) => {
|
|
|
52
51
|
"blur",
|
|
53
52
|
"change",
|
|
54
53
|
"focus",
|
|
54
|
+
"language-error",
|
|
55
55
|
"update:value"
|
|
56
56
|
],
|
|
57
|
-
setup(n, { emit:
|
|
58
|
-
let w = n, T =
|
|
57
|
+
setup(n, { expose: o, emit: C }) {
|
|
58
|
+
let w = n, T = C, E = ne(null), D = re(null), ce = e(() => ({
|
|
59
59
|
"aria-disabled": w.disabled || void 0,
|
|
60
|
-
class: "aiao-code-editor"
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
60
|
+
class: "aiao-code-editor",
|
|
61
|
+
style: {
|
|
62
|
+
display: "block",
|
|
63
|
+
fontSize: "0.875rem",
|
|
64
|
+
height: "100%",
|
|
65
|
+
overflow: "hidden",
|
|
66
|
+
width: "100%"
|
|
67
|
+
}
|
|
68
|
+
})), O = new p(), k = new p(), A = new p(), j = new p(), M = new p(), N = new p(), P = new p(), F = new p(), I = new p(), L = new p(), R = new p(), z = se.define(), B = {
|
|
69
|
+
basic: x,
|
|
70
|
+
minimal: S
|
|
71
|
+
}, V = 0, H;
|
|
72
|
+
ee(() => {
|
|
66
73
|
let e = E.value;
|
|
67
74
|
if (!e) return;
|
|
68
|
-
let t =
|
|
75
|
+
let t = m.create({
|
|
76
|
+
doc: w.value,
|
|
77
|
+
extensions: [
|
|
78
|
+
O.of(W()),
|
|
79
|
+
k.of([]),
|
|
80
|
+
A.of([]),
|
|
81
|
+
j.of([]),
|
|
82
|
+
M.of([]),
|
|
83
|
+
N.of([]),
|
|
84
|
+
P.of([]),
|
|
85
|
+
F.of([]),
|
|
86
|
+
I.of([]),
|
|
87
|
+
L.of([]),
|
|
88
|
+
R.of([]),
|
|
89
|
+
_.theme({
|
|
90
|
+
"&": {
|
|
91
|
+
fontSize: "0.875rem",
|
|
92
|
+
height: "100%"
|
|
93
|
+
},
|
|
94
|
+
"&.cm-focused": {
|
|
95
|
+
outline: "2px solid Highlight",
|
|
96
|
+
outlineOffset: "-2px"
|
|
97
|
+
},
|
|
98
|
+
".cm-gutters": {
|
|
99
|
+
background: "initial",
|
|
100
|
+
border: "none"
|
|
101
|
+
},
|
|
102
|
+
".cm-scroller": { overflow: "auto" }
|
|
103
|
+
}),
|
|
104
|
+
_.domEventObservers({
|
|
105
|
+
blur: () => T("blur"),
|
|
106
|
+
focus: () => T("focus")
|
|
107
|
+
}),
|
|
108
|
+
_.updateListener.of((e) => {
|
|
109
|
+
let t = e.transactions.some((e) => e.annotation(z));
|
|
110
|
+
if (!e.docChanged || t) return;
|
|
111
|
+
let n = e.state.doc.toString();
|
|
112
|
+
T("update:value", n), T("change", n);
|
|
113
|
+
})
|
|
114
|
+
]
|
|
115
|
+
}), n = new _({
|
|
69
116
|
parent: e,
|
|
70
|
-
state:
|
|
71
|
-
doc: w.value,
|
|
72
|
-
extensions: [
|
|
73
|
-
k.of(U()),
|
|
74
|
-
A.of([]),
|
|
75
|
-
j.of([]),
|
|
76
|
-
M.of([]),
|
|
77
|
-
N.of([]),
|
|
78
|
-
P.of([]),
|
|
79
|
-
F.of([]),
|
|
80
|
-
I.of([]),
|
|
81
|
-
L.of([]),
|
|
82
|
-
R.of([]),
|
|
83
|
-
v.domEventObservers({
|
|
84
|
-
blur: () => T("blur"),
|
|
85
|
-
focus: () => T("focus")
|
|
86
|
-
}),
|
|
87
|
-
v.updateListener.of((e) => {
|
|
88
|
-
let t = e.transactions.some((e) => e.annotation(z));
|
|
89
|
-
if (!e.docChanged || t) return;
|
|
90
|
-
let n = e.state.doc.toString();
|
|
91
|
-
T("update:value", n), T("change", n);
|
|
92
|
-
})
|
|
93
|
-
]
|
|
94
|
-
}),
|
|
117
|
+
state: t,
|
|
95
118
|
...w.root ? { root: w.root } : {}
|
|
96
119
|
});
|
|
97
|
-
D.value =
|
|
98
|
-
}),
|
|
120
|
+
D.value = n, G(), K(), J(), X(), Z(), Q(), $(), Y(), w.autoFocus && d(w) && n.focus();
|
|
121
|
+
}), te(() => {
|
|
99
122
|
V += 1;
|
|
100
123
|
let e = D.value;
|
|
101
124
|
D.value = null, e?.destroy();
|
|
102
|
-
}),
|
|
125
|
+
}), a(() => w.value, (e) => {
|
|
103
126
|
let t = D.value;
|
|
104
|
-
!t
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
}
|
|
127
|
+
if (!t) return;
|
|
128
|
+
let n = ae(t.state.doc.toString(), e ?? "");
|
|
129
|
+
n && t.dispatch({
|
|
130
|
+
annotations: [z.of(!0), h.addToHistory.of(!1)],
|
|
131
|
+
changes: n,
|
|
132
|
+
scrollIntoView: !1
|
|
111
133
|
});
|
|
112
|
-
}),
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
134
|
+
}), a(() => w.setup, U), a(() => w.theme, G), a([() => w.language, () => w.languages], le), a([() => w.readonly, () => w.disabled], J), a(() => w.placeholder, X), a([() => w.indentWithTab, () => w.indentUnit], Z), a(() => w.lineWrapping, Q), a(() => w.highlightWhitespace, $), a([
|
|
135
|
+
() => w.describedBy,
|
|
136
|
+
() => w.disabled,
|
|
137
|
+
() => w.label,
|
|
138
|
+
() => w.labelledBy
|
|
139
|
+
], Y);
|
|
116
140
|
function U() {
|
|
117
|
-
|
|
141
|
+
D.value?.dispatch({ effects: O.reconfigure(W()) });
|
|
118
142
|
}
|
|
119
143
|
function W() {
|
|
120
|
-
|
|
144
|
+
return w.setup === null ? [] : B[w.setup];
|
|
121
145
|
}
|
|
122
146
|
function G() {
|
|
147
|
+
D.value?.dispatch({ effects: A.reconfigure(w.theme === "dark" ? g : []) });
|
|
148
|
+
}
|
|
149
|
+
function le() {
|
|
150
|
+
let e = u(w.language, w.languages);
|
|
151
|
+
l(H, e) || K();
|
|
152
|
+
}
|
|
153
|
+
function K() {
|
|
123
154
|
let e = ++V, t = D.value;
|
|
124
155
|
if (!t) return;
|
|
125
|
-
let n = w.language;
|
|
126
|
-
if (
|
|
127
|
-
t.dispatch({ effects:
|
|
156
|
+
let n = w.language, r = u(n, w.languages);
|
|
157
|
+
if (H = r, r.kind !== "found") {
|
|
158
|
+
t.dispatch({ effects: k.reconfigure([]) }), r.kind === "not-found" && (console.error(`[CodeEditor] Language '${n}' not found.`), T("language-error", c(n)));
|
|
128
159
|
return;
|
|
129
160
|
}
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
console.error(`[CodeEditor] Language '${n}' not found.`), t.dispatch({ effects: A.reconfigure([]) });
|
|
133
|
-
return;
|
|
134
|
-
}
|
|
135
|
-
r.load().then((n) => {
|
|
136
|
-
K(e, t) && t.dispatch({ effects: A.reconfigure(n.extension) });
|
|
161
|
+
r.description.load().then((n) => {
|
|
162
|
+
q(e, t) && t.dispatch({ effects: k.reconfigure(n.extension) });
|
|
137
163
|
}, (r) => {
|
|
138
|
-
|
|
164
|
+
q(e, t) && (t.dispatch({ effects: k.reconfigure([]) }), console.error(`[CodeEditor] Failed to load language '${n}':`, r), T("language-error", ie(n, r)));
|
|
139
165
|
});
|
|
140
166
|
}
|
|
141
|
-
function
|
|
167
|
+
function q(e, t) {
|
|
142
168
|
return e === V && D.value === t;
|
|
143
169
|
}
|
|
144
|
-
function
|
|
170
|
+
function J() {
|
|
145
171
|
let e = D.value;
|
|
146
172
|
if (!e) return;
|
|
147
173
|
let t = w.readonly || w.disabled;
|
|
148
|
-
e.dispatch({ effects: [
|
|
149
|
-
}
|
|
150
|
-
function J() {
|
|
151
|
-
D.value?.dispatch({ effects: P.reconfigure(w.placeholder ? x(w.placeholder) : []) });
|
|
174
|
+
e.dispatch({ effects: [j.reconfigure(m.readOnly.of(t)), M.reconfigure(_.editable.of(!t))] });
|
|
152
175
|
}
|
|
153
176
|
function Y() {
|
|
154
|
-
|
|
177
|
+
let e = s(w);
|
|
178
|
+
D.value?.dispatch({ effects: R.reconfigure(_.contentAttributes.of(e)) });
|
|
155
179
|
}
|
|
156
180
|
function X() {
|
|
157
|
-
D.value?.dispatch({ effects:
|
|
181
|
+
D.value?.dispatch({ effects: N.reconfigure(w.placeholder ? b(w.placeholder) : []) });
|
|
158
182
|
}
|
|
159
183
|
function Z() {
|
|
160
|
-
D.value?.dispatch({ effects:
|
|
184
|
+
D.value?.dispatch({ effects: [P.reconfigure(w.indentWithTab ? y.of([f]) : []), F.reconfigure(w.indentUnit ? oe.of(w.indentUnit) : [])] });
|
|
185
|
+
}
|
|
186
|
+
function Q() {
|
|
187
|
+
D.value?.dispatch({ effects: I.reconfigure(w.lineWrapping ? _.lineWrapping : []) });
|
|
188
|
+
}
|
|
189
|
+
function $() {
|
|
190
|
+
D.value?.dispatch({ effects: L.reconfigure(w.highlightWhitespace ? v() : []) });
|
|
161
191
|
}
|
|
162
|
-
return
|
|
192
|
+
return o({
|
|
193
|
+
get view() {
|
|
194
|
+
return D.value;
|
|
195
|
+
},
|
|
196
|
+
get host() {
|
|
197
|
+
return D.value ? E.value : null;
|
|
198
|
+
},
|
|
199
|
+
blur: () => D.value?.contentDOM.blur(),
|
|
200
|
+
focus: () => D.value?.focus()
|
|
201
|
+
}), (e, n) => (i(), t("div", r(ce.value, {
|
|
163
202
|
ref_key: "editorRef",
|
|
164
203
|
ref: E
|
|
165
204
|
}), null, 16));
|
|
166
205
|
}
|
|
167
|
-
})
|
|
206
|
+
});
|
|
168
207
|
//#endregion
|
|
169
|
-
export {
|
|
208
|
+
export { C as CodeEditor };
|
package/package.json
CHANGED
|
@@ -1,16 +1,19 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aiao/code-editor-vue",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.25",
|
|
4
|
+
"license": "MIT",
|
|
5
|
+
"repository": {
|
|
6
|
+
"type": "git",
|
|
7
|
+
"url": "git+https://github.com/aiao-io/rxdb.git",
|
|
8
|
+
"directory": "packages/code-editor-vue"
|
|
9
|
+
},
|
|
4
10
|
"type": "module",
|
|
5
11
|
"main": "./dist/index.js",
|
|
6
12
|
"module": "./dist/index.js",
|
|
7
13
|
"types": "./dist/index.d.ts",
|
|
8
|
-
"style": "./dist/index.css",
|
|
9
14
|
"exports": {
|
|
10
15
|
"./package.json": "./package.json",
|
|
11
|
-
"./style.css": "./dist/index.css",
|
|
12
16
|
".": {
|
|
13
|
-
"@aiao/source": "./src/index.ts",
|
|
14
17
|
"types": "./dist/index.d.ts",
|
|
15
18
|
"import": "./dist/index.js",
|
|
16
19
|
"default": "./dist/index.js"
|
|
@@ -18,6 +21,10 @@
|
|
|
18
21
|
},
|
|
19
22
|
"files": [
|
|
20
23
|
"dist",
|
|
24
|
+
"src",
|
|
25
|
+
"!src/**/*.spec.*",
|
|
26
|
+
"!src/**/*.test.*",
|
|
27
|
+
"!src/**/__tests__/**",
|
|
21
28
|
"!**/*.tsbuildinfo"
|
|
22
29
|
],
|
|
23
30
|
"publishConfig": {
|
|
@@ -30,15 +37,15 @@
|
|
|
30
37
|
]
|
|
31
38
|
},
|
|
32
39
|
"peerDependencies": {
|
|
33
|
-
"vue": "
|
|
40
|
+
"vue": "^3.5.41"
|
|
34
41
|
},
|
|
35
42
|
"dependencies": {
|
|
36
43
|
"@codemirror/commands": "^6.10.4",
|
|
37
|
-
"@codemirror/language": "
|
|
44
|
+
"@codemirror/language": "6.12.4",
|
|
38
45
|
"@codemirror/state": "6.7.1",
|
|
39
46
|
"@codemirror/theme-one-dark": "^6.1.3",
|
|
40
|
-
"@codemirror/view": "
|
|
47
|
+
"@codemirror/view": "6.43.8",
|
|
41
48
|
"codemirror": "^6.0.1",
|
|
42
|
-
"@aiao/code-editor": "0.0.
|
|
49
|
+
"@aiao/code-editor": "0.0.25"
|
|
43
50
|
}
|
|
44
51
|
}
|
|
@@ -0,0 +1,316 @@
|
|
|
1
|
+
<script lang="ts" setup>
|
|
2
|
+
import type { ResolvedCodeEditorLanguage } from '@aiao/code-editor';
|
|
3
|
+
import {
|
|
4
|
+
buildCodeEditorContentAttributes,
|
|
5
|
+
codeEditorLanguageLoadFailed,
|
|
6
|
+
codeEditorLanguageNotFound,
|
|
7
|
+
computeMinimalDocumentChange,
|
|
8
|
+
isSameResolvedLanguage,
|
|
9
|
+
resolveCodeEditorLanguage,
|
|
10
|
+
shouldAutoFocusCodeEditor,
|
|
11
|
+
SUPPORT_LANGUAGES
|
|
12
|
+
} from '@aiao/code-editor';
|
|
13
|
+
import { indentWithTab as indentWithTabCommand } from '@codemirror/commands';
|
|
14
|
+
import { indentUnit as indentUnitExt } from '@codemirror/language';
|
|
15
|
+
import { Annotation, Compartment, EditorState, type Extension, Transaction } from '@codemirror/state';
|
|
16
|
+
import { oneDark } from '@codemirror/theme-one-dark';
|
|
17
|
+
import {
|
|
18
|
+
EditorView,
|
|
19
|
+
highlightWhitespace as highlightWhitespaceExt,
|
|
20
|
+
keymap,
|
|
21
|
+
placeholder as placeholderExt
|
|
22
|
+
} from '@codemirror/view';
|
|
23
|
+
import { basicSetup, minimalSetup } from 'codemirror';
|
|
24
|
+
import { computed, onMounted, onUnmounted, ref, shallowRef, watch } from 'vue';
|
|
25
|
+
|
|
26
|
+
import type { CodeEditorEmits, CodeEditorExpose, CodeEditorProps, CodeEditorSetup } from './code-editor.types.js';
|
|
27
|
+
|
|
28
|
+
const props = withDefaults(defineProps<CodeEditorProps>(), {
|
|
29
|
+
autoFocus: false,
|
|
30
|
+
describedBy: '',
|
|
31
|
+
disabled: false,
|
|
32
|
+
highlightWhitespace: false,
|
|
33
|
+
indentUnit: ' ',
|
|
34
|
+
indentWithTab: false,
|
|
35
|
+
language: 'sql',
|
|
36
|
+
label: '',
|
|
37
|
+
labelledBy: '',
|
|
38
|
+
languages: () => SUPPORT_LANGUAGES,
|
|
39
|
+
lineWrapping: false,
|
|
40
|
+
placeholder: '',
|
|
41
|
+
readonly: false,
|
|
42
|
+
setup: 'basic',
|
|
43
|
+
theme: 'light',
|
|
44
|
+
value: ''
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
const emit = defineEmits<CodeEditorEmits>();
|
|
48
|
+
const editorRef = ref<HTMLDivElement | null>(null);
|
|
49
|
+
const viewRef = shallowRef<EditorView | null>(null);
|
|
50
|
+
const hostAttributes = computed(() => ({
|
|
51
|
+
'aria-disabled': props.disabled || undefined,
|
|
52
|
+
class: 'aiao-code-editor',
|
|
53
|
+
style: {
|
|
54
|
+
display: 'block',
|
|
55
|
+
fontSize: '0.875rem',
|
|
56
|
+
height: '100%',
|
|
57
|
+
overflow: 'hidden',
|
|
58
|
+
width: '100%'
|
|
59
|
+
}
|
|
60
|
+
}));
|
|
61
|
+
const setupConf = new Compartment();
|
|
62
|
+
const languageConf = new Compartment();
|
|
63
|
+
const themeConf = new Compartment();
|
|
64
|
+
const readonlyConf = new Compartment();
|
|
65
|
+
const editableConf = new Compartment();
|
|
66
|
+
const placeholderConf = new Compartment();
|
|
67
|
+
const indentWithTabConf = new Compartment();
|
|
68
|
+
const indentUnitConf = new Compartment();
|
|
69
|
+
const lineWrappingConf = new Compartment();
|
|
70
|
+
const highlightWhitespaceConf = new Compartment();
|
|
71
|
+
const a11yConf = new Compartment();
|
|
72
|
+
const External = Annotation.define<boolean>();
|
|
73
|
+
const setupExtensions = {
|
|
74
|
+
basic: basicSetup,
|
|
75
|
+
minimal: minimalSetup
|
|
76
|
+
} satisfies Record<Exclude<CodeEditorSetup, null>, Extension>;
|
|
77
|
+
let languageRequest = 0;
|
|
78
|
+
/** 最近一次**已生效**的语言解析结果,用于跳过等价的重复配置(CEV-003)。 */
|
|
79
|
+
let appliedLanguage: ResolvedCodeEditorLanguage | undefined;
|
|
80
|
+
|
|
81
|
+
onMounted(() => {
|
|
82
|
+
const parent = editorRef.value;
|
|
83
|
+
if (!parent) return;
|
|
84
|
+
|
|
85
|
+
const state = EditorState.create({
|
|
86
|
+
doc: props.value,
|
|
87
|
+
extensions: [
|
|
88
|
+
setupConf.of(getSetupExtension()),
|
|
89
|
+
languageConf.of([]),
|
|
90
|
+
themeConf.of([]),
|
|
91
|
+
readonlyConf.of([]),
|
|
92
|
+
editableConf.of([]),
|
|
93
|
+
placeholderConf.of([]),
|
|
94
|
+
indentWithTabConf.of([]),
|
|
95
|
+
indentUnitConf.of([]),
|
|
96
|
+
lineWrappingConf.of([]),
|
|
97
|
+
highlightWhitespaceConf.of([]),
|
|
98
|
+
a11yConf.of([]),
|
|
99
|
+
EditorView.theme({
|
|
100
|
+
'&': { fontSize: '0.875rem', height: '100%' },
|
|
101
|
+
'&.cm-focused': { outline: '2px solid Highlight', outlineOffset: '-2px' },
|
|
102
|
+
'.cm-gutters': { background: 'initial', border: 'none' },
|
|
103
|
+
'.cm-scroller': { overflow: 'auto' }
|
|
104
|
+
}),
|
|
105
|
+
EditorView.domEventObservers({
|
|
106
|
+
blur: () => emit('blur'),
|
|
107
|
+
focus: () => emit('focus')
|
|
108
|
+
}),
|
|
109
|
+
EditorView.updateListener.of(update => {
|
|
110
|
+
const external = update.transactions.some(transaction => transaction.annotation(External));
|
|
111
|
+
if (!update.docChanged || external) return;
|
|
112
|
+
const value = update.state.doc.toString();
|
|
113
|
+
emit('update:value', value);
|
|
114
|
+
emit('change', value);
|
|
115
|
+
})
|
|
116
|
+
]
|
|
117
|
+
});
|
|
118
|
+
const view = new EditorView({
|
|
119
|
+
parent,
|
|
120
|
+
state,
|
|
121
|
+
...(props.root ? { root: props.root } : {})
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
viewRef.value = view;
|
|
125
|
+
updateTheme();
|
|
126
|
+
updateLanguage();
|
|
127
|
+
updateReadonly();
|
|
128
|
+
updatePlaceholder();
|
|
129
|
+
updateIndent();
|
|
130
|
+
updateLineWrapping();
|
|
131
|
+
updateHighlightWhitespace();
|
|
132
|
+
updateAccessibility();
|
|
133
|
+
// CEA-008 / CER-006(用户可见的行为修正):`disabled` / `readonly` 都会把编辑器配成
|
|
134
|
+
// 不可编辑,此时仍然抢焦点,用户只会得到一个无法输入、也无从得知为什么无法输入的控件。
|
|
135
|
+
if (props.autoFocus && shouldAutoFocusCodeEditor(props)) view.focus();
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
onUnmounted(() => {
|
|
139
|
+
languageRequest += 1;
|
|
140
|
+
const view = viewRef.value;
|
|
141
|
+
viewRef.value = null;
|
|
142
|
+
view?.destroy();
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
watch(
|
|
146
|
+
() => props.value,
|
|
147
|
+
value => {
|
|
148
|
+
const view = viewRef.value;
|
|
149
|
+
if (!view) return;
|
|
150
|
+
// CEV-001:早先是 `{ from: 0, to: doc.length }` 全文替换 —— CodeMirror 会把落在
|
|
151
|
+
// 替换区内的光标映射到区间起点,宿主保存 / 格式化 / 协同同步一次就把用户送回文档开头。
|
|
152
|
+
// 差量计算走核心包,三端共用同一份实现。
|
|
153
|
+
const changes = computeMinimalDocumentChange(view.state.doc.toString(), value ?? '');
|
|
154
|
+
if (!changes) return;
|
|
155
|
+
view.dispatch({
|
|
156
|
+
// `External` 只被本组件的 update listener 用来阻止回调环路,CodeMirror 的 history
|
|
157
|
+
// **不认识**它;少了 `addToHistory.of(false)`,宿主回写照样进 undo 栈。
|
|
158
|
+
annotations: [External.of(true), Transaction.addToHistory.of(false)],
|
|
159
|
+
changes,
|
|
160
|
+
scrollIntoView: false
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
);
|
|
164
|
+
watch(() => props.setup, updateSetup);
|
|
165
|
+
watch(() => props.theme, updateTheme);
|
|
166
|
+
watch([() => props.language, () => props.languages], syncLanguage);
|
|
167
|
+
watch([() => props.readonly, () => props.disabled], updateReadonly);
|
|
168
|
+
watch(() => props.placeholder, updatePlaceholder);
|
|
169
|
+
watch([() => props.indentWithTab, () => props.indentUnit], updateIndent);
|
|
170
|
+
watch(() => props.lineWrapping, updateLineWrapping);
|
|
171
|
+
watch(() => props.highlightWhitespace, updateHighlightWhitespace);
|
|
172
|
+
watch([() => props.describedBy, () => props.disabled, () => props.label, () => props.labelledBy], updateAccessibility);
|
|
173
|
+
|
|
174
|
+
function updateSetup() {
|
|
175
|
+
viewRef.value?.dispatch({
|
|
176
|
+
effects: setupConf.reconfigure(getSetupExtension())
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
function getSetupExtension(): Extension {
|
|
181
|
+
return props.setup === null ? [] : setupExtensions[props.setup];
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
function updateTheme() {
|
|
185
|
+
viewRef.value?.dispatch({
|
|
186
|
+
effects: themeConf.reconfigure(props.theme === 'dark' ? oneDark : [])
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* `language` / `languages` 变化后的语言同步。
|
|
192
|
+
*
|
|
193
|
+
* CEV-003:`watch` 按数组**引用**触发。父组件写 `:languages="[myLang]"` 这类内联字面量时,
|
|
194
|
+
* 每轮更新都会重新查找、`load()` 并 reconfigure 语言 compartment ——
|
|
195
|
+
* 受控大文档等于每次输入都整篇重新词法分析。判定基准改成
|
|
196
|
+
* 「当前语言实际解析到的 description identity」,与 Angular / React 同构。
|
|
197
|
+
*/
|
|
198
|
+
function syncLanguage() {
|
|
199
|
+
const resolved = resolveCodeEditorLanguage(props.language, props.languages);
|
|
200
|
+
if (isSameResolvedLanguage(appliedLanguage, resolved)) return;
|
|
201
|
+
updateLanguage();
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* **无条件**重新应用语言配置。挂载时以及 {@link syncLanguage} 判定确有变化时调用。
|
|
206
|
+
*/
|
|
207
|
+
function updateLanguage() {
|
|
208
|
+
const request = ++languageRequest;
|
|
209
|
+
const view = viewRef.value;
|
|
210
|
+
if (!view) return;
|
|
211
|
+
|
|
212
|
+
const language = props.language;
|
|
213
|
+
const resolved = resolveCodeEditorLanguage(language, props.languages);
|
|
214
|
+
appliedLanguage = resolved;
|
|
215
|
+
if (resolved.kind !== 'found') {
|
|
216
|
+
view.dispatch({ effects: languageConf.reconfigure([]) });
|
|
217
|
+
// CEV-004:`none` 是「明确不高亮」,不是错误;只有 `not-found` 才报给宿主。
|
|
218
|
+
if (resolved.kind === 'not-found') {
|
|
219
|
+
console.error(`[CodeEditor] Language '${language}' not found.`);
|
|
220
|
+
emit('language-error', codeEditorLanguageNotFound(language));
|
|
221
|
+
}
|
|
222
|
+
return;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// 解析结果里已经带着命中的 description —— 早先这里又 `findLanguageByName` 查了一遍,
|
|
226
|
+
// 同一份查找逻辑跑两次,两处结果分叉时没有任何信号。
|
|
227
|
+
void resolved.description.load().then(
|
|
228
|
+
support => {
|
|
229
|
+
if (!isCurrentLanguageRequest(request, view)) return;
|
|
230
|
+
view.dispatch({ effects: languageConf.reconfigure(support.extension as Extension) });
|
|
231
|
+
},
|
|
232
|
+
(error: unknown) => {
|
|
233
|
+
if (!isCurrentLanguageRequest(request, view)) return;
|
|
234
|
+
view.dispatch({ effects: languageConf.reconfigure([]) });
|
|
235
|
+
console.error(`[CodeEditor] Failed to load language '${language}':`, error);
|
|
236
|
+
emit('language-error', codeEditorLanguageLoadFailed(language, error));
|
|
237
|
+
}
|
|
238
|
+
);
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
function isCurrentLanguageRequest(request: number, view: EditorView) {
|
|
242
|
+
return request === languageRequest && viewRef.value === view;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
function updateReadonly() {
|
|
246
|
+
const view = viewRef.value;
|
|
247
|
+
if (!view) return;
|
|
248
|
+
const readonly = props.readonly || props.disabled;
|
|
249
|
+
view.dispatch({
|
|
250
|
+
effects: [
|
|
251
|
+
readonlyConf.reconfigure(EditorState.readOnly.of(readonly)),
|
|
252
|
+
editableConf.reconfigure(EditorView.editable.of(!readonly))
|
|
253
|
+
]
|
|
254
|
+
});
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
/**
|
|
258
|
+
* 把无障碍属性写到真正承担 `role="textbox"` 的 `.cm-content` 上。
|
|
259
|
+
*
|
|
260
|
+
* CEA-008 / CER-006:只在宿主 `div` 上补 `aria-*` 是把缺陷换个位置 —— 屏幕阅读器进到
|
|
261
|
+
* textbox 之后读到的仍然是一个没有可访问名称、只报 `aria-readonly` 的多行文本框。
|
|
262
|
+
* `readonlyConf` 让 CodeMirror 写出 `aria-readonly`,但「只读」与「禁用」不是一回事,
|
|
263
|
+
* 禁用语义必须单独告知。属性字典由核心包计算,三端共用同一份实现。
|
|
264
|
+
*/
|
|
265
|
+
function updateAccessibility() {
|
|
266
|
+
const attributes = buildCodeEditorContentAttributes(props);
|
|
267
|
+
viewRef.value?.dispatch({ effects: a11yConf.reconfigure(EditorView.contentAttributes.of(attributes)) });
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
function updatePlaceholder() {
|
|
271
|
+
viewRef.value?.dispatch({
|
|
272
|
+
effects: placeholderConf.reconfigure(props.placeholder ? placeholderExt(props.placeholder) : [])
|
|
273
|
+
});
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
function updateIndent() {
|
|
277
|
+
viewRef.value?.dispatch({
|
|
278
|
+
effects: [
|
|
279
|
+
indentWithTabConf.reconfigure(props.indentWithTab ? keymap.of([indentWithTabCommand]) : []),
|
|
280
|
+
indentUnitConf.reconfigure(props.indentUnit ? indentUnitExt.of(props.indentUnit) : [])
|
|
281
|
+
]
|
|
282
|
+
});
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
function updateLineWrapping() {
|
|
286
|
+
viewRef.value?.dispatch({
|
|
287
|
+
effects: lineWrappingConf.reconfigure(props.lineWrapping ? EditorView.lineWrapping : [])
|
|
288
|
+
});
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
function updateHighlightWhitespace() {
|
|
292
|
+
viewRef.value?.dispatch({
|
|
293
|
+
effects: highlightWhitespaceConf.reconfigure(props.highlightWhitespace ? highlightWhitespaceExt() : [])
|
|
294
|
+
});
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
defineExpose<CodeEditorExpose>({
|
|
298
|
+
get view() {
|
|
299
|
+
return viewRef.value;
|
|
300
|
+
},
|
|
301
|
+
// 跟着 `view` 一起置空:`editorRef` 在卸载后仍可能残留元素引用,
|
|
302
|
+
// 而 handle 的契约是「未挂载或已卸载时为 null」,三端逐字一致(CEA-009)。
|
|
303
|
+
get host() {
|
|
304
|
+
return viewRef.value ? editorRef.value : null;
|
|
305
|
+
},
|
|
306
|
+
blur: () => viewRef.value?.contentDOM.blur(),
|
|
307
|
+
focus: () => viewRef.value?.focus()
|
|
308
|
+
});
|
|
309
|
+
</script>
|
|
310
|
+
|
|
311
|
+
<template>
|
|
312
|
+
<div
|
|
313
|
+
v-bind="hostAttributes"
|
|
314
|
+
ref="editorRef"
|
|
315
|
+
/>
|
|
316
|
+
</template>
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
import type { CodeEditorLanguageDescription, CodeEditorLanguageError, CodeEditorTheme } from '@aiao/code-editor';
|
|
2
|
+
import type { EditorView } from '@codemirror/view';
|
|
3
|
+
|
|
4
|
+
/** CodeMirror 预置扩展集合。 */
|
|
5
|
+
export type CodeEditorSetup = 'basic' | 'minimal' | null;
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Vue CodeEditor 的公开属性。
|
|
9
|
+
*
|
|
10
|
+
* @remarks 与 Angular 的 inputs、React 的 props 同名同义;仅初始化生效的两项
|
|
11
|
+
* ({@link CodeEditorProps.autoFocus} / {@link CodeEditorProps.root})在各自条目上标注。
|
|
12
|
+
*/
|
|
13
|
+
export interface CodeEditorProps {
|
|
14
|
+
/**
|
|
15
|
+
* 挂载后立即聚焦编辑器。
|
|
16
|
+
*
|
|
17
|
+
* @defaultValue false
|
|
18
|
+
* @remarks **仅挂载时读取一次**,后续变更被忽略。`disabled` 或 `readonly` 为真时
|
|
19
|
+
* 不抢焦点 —— 否则用户得到一个无法输入、也无从得知为什么无法输入的控件。
|
|
20
|
+
*/
|
|
21
|
+
readonly autoFocus?: boolean;
|
|
22
|
+
/**
|
|
23
|
+
* 补充说明元素的 id,写成内部 textbox 的 `aria-describedby`。
|
|
24
|
+
*
|
|
25
|
+
* @remarks 属性落在 CodeMirror 的 `.cm-content` 上 —— 承担 `role="textbox"` 的是它,
|
|
26
|
+
* 不是组件的宿主元素。空串按「未设置」处理。三端同名同义。
|
|
27
|
+
*/
|
|
28
|
+
readonly describedBy?: string;
|
|
29
|
+
/**
|
|
30
|
+
* 禁用:只读且不可编辑,并向辅助技术报告 `aria-disabled`。
|
|
31
|
+
*
|
|
32
|
+
* @defaultValue false
|
|
33
|
+
* @remarks 与 `readonly` 是独立的两个属性,任一为真都会让编辑器不可编辑;
|
|
34
|
+
* 区别在于禁用还会写出 `aria-disabled` 并抑制 `autoFocus`。
|
|
35
|
+
*/
|
|
36
|
+
readonly disabled?: boolean;
|
|
37
|
+
/**
|
|
38
|
+
* 显示空白字符。
|
|
39
|
+
*
|
|
40
|
+
* @defaultValue false
|
|
41
|
+
*/
|
|
42
|
+
readonly highlightWhitespace?: boolean;
|
|
43
|
+
/**
|
|
44
|
+
* 一级缩进使用的字符串。
|
|
45
|
+
*
|
|
46
|
+
* @defaultValue ' '(两个空格,三端一致)
|
|
47
|
+
*/
|
|
48
|
+
readonly indentUnit?: string;
|
|
49
|
+
/**
|
|
50
|
+
* 允许 Tab 键缩进。
|
|
51
|
+
*
|
|
52
|
+
* @defaultValue false
|
|
53
|
+
* @remarks 打开后 Tab 不再移动焦点,键盘用户会被困在编辑器里 —— 这是可达性取舍,
|
|
54
|
+
* 三端默认一致地关闭。
|
|
55
|
+
*/
|
|
56
|
+
readonly indentWithTab?: boolean;
|
|
57
|
+
/**
|
|
58
|
+
* 语法高亮语言名或别名;`''` 与 `'plaintext'` 表示不高亮。
|
|
59
|
+
*
|
|
60
|
+
* @defaultValue 'sql'
|
|
61
|
+
* @remarks 查不到或 `load()` 失败时**降级为纯文本**并继续可用,同时发出
|
|
62
|
+
* `language-error` 事件(见 {@link CodeEditorEmits})。快速切换时只有最后一次请求生效。
|
|
63
|
+
*/
|
|
64
|
+
readonly language?: string;
|
|
65
|
+
/**
|
|
66
|
+
* 候选语言列表;空列表表示不高亮。
|
|
67
|
+
*
|
|
68
|
+
* @defaultValue SUPPORT_LANGUAGES
|
|
69
|
+
* @remarks 传内联字面量(`:languages="[myLang]"`)是有成本的:每轮更新都是新数组,
|
|
70
|
+
* 而 `watch` 按引用触发。组件会再判断当前语言解析到的 description 是否真的换了 ——
|
|
71
|
+
* 只有真换了才重新 `load()`,但这层判断本身仍要跑,稳定引用(模块级常量或 `computed`)更省。
|
|
72
|
+
*/
|
|
73
|
+
readonly languages?: readonly CodeEditorLanguageDescription[];
|
|
74
|
+
/**
|
|
75
|
+
* 可访问名称,写成内部 textbox 的 `aria-label`。
|
|
76
|
+
*
|
|
77
|
+
* @remarks 落点与 {@link CodeEditorProps.describedBy} 相同。
|
|
78
|
+
*/
|
|
79
|
+
readonly label?: string;
|
|
80
|
+
/**
|
|
81
|
+
* 承担标签的元素 id,写成内部 textbox 的 `aria-labelledby`。
|
|
82
|
+
*
|
|
83
|
+
* @remarks 落点与 {@link CodeEditorProps.describedBy} 相同。
|
|
84
|
+
*/
|
|
85
|
+
readonly labelledBy?: string;
|
|
86
|
+
/**
|
|
87
|
+
* 自动换行。
|
|
88
|
+
*
|
|
89
|
+
* @defaultValue false
|
|
90
|
+
*/
|
|
91
|
+
readonly lineWrapping?: boolean;
|
|
92
|
+
/**
|
|
93
|
+
* 空文档时显示的占位文本。
|
|
94
|
+
*
|
|
95
|
+
* @defaultValue ''
|
|
96
|
+
*/
|
|
97
|
+
readonly placeholder?: string;
|
|
98
|
+
/**
|
|
99
|
+
* 只读:仍可聚焦、可选择、可复制,但不能改。
|
|
100
|
+
*
|
|
101
|
+
* @defaultValue false
|
|
102
|
+
* @remarks 只挡编辑**命令**,不挡宿主同步 —— `value` 变化照样写进文档,
|
|
103
|
+
* 否则只读的受控用法直接失效。
|
|
104
|
+
*/
|
|
105
|
+
readonly readonly?: boolean;
|
|
106
|
+
/**
|
|
107
|
+
* CodeMirror 挂载的 DOM 根,用于 Shadow DOM 场景。
|
|
108
|
+
*
|
|
109
|
+
* @remarks **仅挂载时读取一次** —— CodeMirror 无法在构造后改 root。
|
|
110
|
+
* 需要换 root 时请用 `v-if` 或 `:key` 强制重建组件。
|
|
111
|
+
*/
|
|
112
|
+
readonly root?: Document | ShadowRoot;
|
|
113
|
+
/**
|
|
114
|
+
* CodeMirror 预设扩展集;`null` 表示不加载任何预设。
|
|
115
|
+
*
|
|
116
|
+
* @defaultValue 'basic'
|
|
117
|
+
*/
|
|
118
|
+
readonly setup?: CodeEditorSetup;
|
|
119
|
+
/**
|
|
120
|
+
* 主题。
|
|
121
|
+
*
|
|
122
|
+
* @defaultValue 'light'
|
|
123
|
+
*/
|
|
124
|
+
readonly theme?: CodeEditorTheme;
|
|
125
|
+
/**
|
|
126
|
+
* 文档内容,配合 `v-model:value` 使用。
|
|
127
|
+
*
|
|
128
|
+
* @defaultValue ''
|
|
129
|
+
* @remarks **不是严格受控**:组件不会把用户编辑写回本属性,也不会在每次更新时
|
|
130
|
+
* 强行拉平文档。本属性变化时只同步公共前后缀之间的那一段差量,光标、选区与滚动位置都保留,
|
|
131
|
+
* 且不进 undo 栈。父组件忽略 `update:value` 时编辑器会保留用户输入。
|
|
132
|
+
*/
|
|
133
|
+
readonly value?: string;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/** Vue CodeEditor 的公开事件。 */
|
|
137
|
+
export interface CodeEditorEmits {
|
|
138
|
+
/** 编辑器内容区域失去焦点。 */
|
|
139
|
+
blur: [];
|
|
140
|
+
/** 用户编辑导致的内容变化;宿主经 `value` 的程序化写入**不**触发。 */
|
|
141
|
+
change: [value: string];
|
|
142
|
+
/** 编辑器内容区域获得焦点。 */
|
|
143
|
+
focus: [];
|
|
144
|
+
/**
|
|
145
|
+
* 语言解析或加载失败。
|
|
146
|
+
*
|
|
147
|
+
* @remarks
|
|
148
|
+
* CEV-004:这条路径此前只有一条 `console.error`,宿主完全观测不到 ——
|
|
149
|
+
* 拿不到错误、没有重试或降级的机会,用户只看到高亮突然消失。
|
|
150
|
+
* 降级策略不变(清空语言扩展退回纯文本),日志也保留,本事件是**新增的**观测通道。
|
|
151
|
+
* 载荷由核心包定义,与 Angular 的 `aoLanguageError` 输出、React 的 `onLanguageError`
|
|
152
|
+
* 回调逐字一致:`kind` 为 `'not-found'` 时是配置错误,重试没有意义;
|
|
153
|
+
* 为 `'load-failed'` 时通常是网络或分包问题,可以重试。
|
|
154
|
+
*/
|
|
155
|
+
'language-error': [error: CodeEditorLanguageError];
|
|
156
|
+
/** `v-model:value` 的更新通道,触发时机与 `change` 相同。 */
|
|
157
|
+
'update:value': [value: string];
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* `defineExpose` 暴露给模板 ref 的命令式 API。
|
|
162
|
+
*
|
|
163
|
+
* @remarks 四个成员与 React 的 `CodeEditorHandle`、Angular 组件实例上的同名成员
|
|
164
|
+
* 逐字对齐(CEA-009)—— 跨端迁移时命令式面不需要改写。
|
|
165
|
+
*/
|
|
166
|
+
export interface CodeEditorExpose {
|
|
167
|
+
/** 底层 CodeMirror EditorView,未挂载或已卸载时为 null。 */
|
|
168
|
+
readonly view: EditorView | null;
|
|
169
|
+
/** 宿主 `div` 元素(CodeMirror 挂载所在的容器),未挂载或已卸载时为 null。 */
|
|
170
|
+
readonly host: HTMLDivElement | null;
|
|
171
|
+
/** 移开键盘焦点;未挂载时是空操作。 */
|
|
172
|
+
blur(): void;
|
|
173
|
+
/** 把键盘焦点交给编辑器内容区域;未挂载时是空操作。 */
|
|
174
|
+
focus(): void;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
export type { CodeEditorTheme } from '@aiao/code-editor';
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @aiao/code-editor-vue
|
|
3
|
+
*
|
|
4
|
+
* @example
|
|
5
|
+
* ```vue
|
|
6
|
+
* <script setup lang="ts">
|
|
7
|
+
* import { CodeEditor } from '@aiao/code-editor-vue';
|
|
8
|
+
* </script>
|
|
9
|
+
*
|
|
10
|
+
* <template>
|
|
11
|
+
* <CodeEditor v-model:value="code" language="typescript" />
|
|
12
|
+
* </template>
|
|
13
|
+
* ```
|
|
14
|
+
*/
|
|
15
|
+
export type {
|
|
16
|
+
CodeEditorEmits,
|
|
17
|
+
CodeEditorExpose,
|
|
18
|
+
CodeEditorProps,
|
|
19
|
+
CodeEditorSetup,
|
|
20
|
+
CodeEditorTheme
|
|
21
|
+
} from './code-editor.types.js';
|
|
22
|
+
export { default as CodeEditor } from './CodeEditor.vue';
|
package/dist/index.css
DELETED