@aiao/code-editor-angular 0.0.23 → 0.0.24
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
CHANGED
|
@@ -8,6 +8,10 @@
|
|
|
8
8
|
pnpm add @aiao/code-editor @aiao/code-editor-angular
|
|
9
9
|
```
|
|
10
10
|
|
|
11
|
+
`@aiao/code-editor` 是 peer 依赖,且下界跟随本包版本抬升 —— 本包生成的 `.d.ts`
|
|
12
|
+
直接引用核心包的 `CodeEditorLanguageDescription` / `ResolvedCodeEditorLanguage` /
|
|
13
|
+
`CodeEditorLanguageError`,装到更旧的核心包上会在消费者侧编译失败。
|
|
14
|
+
|
|
11
15
|
## 使用
|
|
12
16
|
|
|
13
17
|
```ts
|
|
@@ -30,3 +34,128 @@ export class AppComponent {
|
|
|
30
34
|
}
|
|
31
35
|
}
|
|
32
36
|
```
|
|
37
|
+
|
|
38
|
+
## Reactive Forms
|
|
39
|
+
|
|
40
|
+
```ts
|
|
41
|
+
import { Component } from '@angular/core';
|
|
42
|
+
import { FormControl, ReactiveFormsModule } from '@angular/forms';
|
|
43
|
+
import { CodeEditor } from '@aiao/code-editor-angular';
|
|
44
|
+
|
|
45
|
+
@Component({
|
|
46
|
+
selector: 'app-editor',
|
|
47
|
+
imports: [ReactiveFormsModule, CodeEditor],
|
|
48
|
+
template: `<ao-code-editor [formControl]="code" label="SQL 查询" language="sql" />`
|
|
49
|
+
})
|
|
50
|
+
export class EditorComponent {
|
|
51
|
+
code = new FormControl('select 1', { nonNullable: true });
|
|
52
|
+
}
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
`writeValue()` 只接受 `string | null | undefined`(`null` / `undefined` 规整成空串)。
|
|
56
|
+
`FormControl` 里放别的类型会立刻抛 `TypeError`,而不是等到 CodeMirror 内部炸掉。
|
|
57
|
+
|
|
58
|
+
## Inputs
|
|
59
|
+
|
|
60
|
+
| 名称 | 类型 | 默认值 | 说明 |
|
|
61
|
+
| --------------------- | ------------------------------------------ | ------------------- | ------------------------------------------------------- |
|
|
62
|
+
| `value` | `string` | `''` | 文档内容;与 `ngModel` / `formControl` 同用时以表单为准 |
|
|
63
|
+
| `language` | `string` | `'sql'` | 语言名或别名;`''` 与 `'plaintext'` 表示不高亮 |
|
|
64
|
+
| `languages` | `readonly CodeEditorLanguageDescription[]` | `SUPPORT_LANGUAGES` | 候选语言列表 |
|
|
65
|
+
| `theme` | `'light' \| 'dark'` | `'light'` | 主题 |
|
|
66
|
+
| `setup` | `'basic' \| 'minimal' \| null` | `'basic'` | CodeMirror 预设扩展集 |
|
|
67
|
+
| `placeholder` | `string` | `''` | 空文档时的占位文本 |
|
|
68
|
+
| `disabled` | `boolean` | `false` | 禁用(只读且不可聚焦) |
|
|
69
|
+
| `readonly` | `boolean` | `false` | 只读(仍可聚焦与选择) |
|
|
70
|
+
| `lineWrapping` | `boolean` | `false` | 自动换行 |
|
|
71
|
+
| `indentUnit` | `string` | `' '` | 一级缩进使用的字符串 |
|
|
72
|
+
| `indentWithTab` | `boolean` | `false` | 允许 Tab 缩进(会牺牲键盘可达性) |
|
|
73
|
+
| `highlightWhitespace` | `boolean` | `false` | 显示空白字符 |
|
|
74
|
+
| `label` | `string` | `''` | 内部 textbox 的 `aria-label` |
|
|
75
|
+
| `labelledBy` | `string` | `''` | 内部 textbox 的 `aria-labelledby` |
|
|
76
|
+
| `describedBy` | `string` | `''` | 内部 textbox 的 `aria-describedby` |
|
|
77
|
+
| `autoFocus` | `boolean` | `false` | **仅初始化生效**,见下 |
|
|
78
|
+
| `root` | `Document \| ShadowRoot` | — | **仅初始化生效**,见下 |
|
|
79
|
+
|
|
80
|
+
三个 `aria-*` 输入落在 CodeMirror 的 `.cm-content` 上 —— 承担 `role="textbox"` 的是它,
|
|
81
|
+
不是宿主元素。空串按「未设置」处理,不产出属性。
|
|
82
|
+
|
|
83
|
+
## Outputs
|
|
84
|
+
|
|
85
|
+
| 名称 | 载荷 | 说明 |
|
|
86
|
+
| ----------------- | ------------------------- | ---------------------------------------------------------- |
|
|
87
|
+
| `aoChange` | `string` | 用户编辑导致的内容变化;程序化写入**不**触发 |
|
|
88
|
+
| `aoFocus` | `void` | 获得焦点 |
|
|
89
|
+
| `aoBlur` | `void` | 失去焦点,同时触发表单 `touched` |
|
|
90
|
+
| `aoLanguageError` | `CodeEditorLanguageError` | 语言查不到(`not-found`)或 `load()` 失败(`load-failed`) |
|
|
91
|
+
|
|
92
|
+
语言失败时编辑器**降级为纯文本**并继续可用,宿主可据此重试或提示:
|
|
93
|
+
|
|
94
|
+
```html
|
|
95
|
+
<ao-code-editor (aoLanguageError)="onLanguageError($event)" [language]="lang" />
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
```ts
|
|
99
|
+
onLanguageError(error: CodeEditorLanguageError): void {
|
|
100
|
+
if (error.kind === 'load-failed') this.retry(error.language);
|
|
101
|
+
}
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
## 仅初始化生效的输入
|
|
105
|
+
|
|
106
|
+
`autoFocus` 与 `root` 只在 `ngOnInit` 构造 `EditorView` 时读取一次,后续变更被忽略 ——
|
|
107
|
+
两者都对应 CodeMirror 无法重配置的构造期决策。需要改 `root` 时请用 `@if` 重建组件。
|
|
108
|
+
|
|
109
|
+
`autoFocus` 还受访问状态约束:`disabled` 或 `readonly` 为真时**不**抢焦点,
|
|
110
|
+
否则用户得到一个无法输入、也无从得知为什么无法输入的控件。
|
|
111
|
+
|
|
112
|
+
## disabled 与 value 的优先级
|
|
113
|
+
|
|
114
|
+
`disabled` 有**两条独立来源**,任一为真即禁用:
|
|
115
|
+
|
|
116
|
+
1. 模板上的 `[disabled]` 输入;
|
|
117
|
+
2. 表单调用的 `setDisabledState()`(`FormControl.disable()` / `.enable()`)。
|
|
118
|
+
|
|
119
|
+
它们不互相覆盖 —— Reactive Forms 在控件 enabled 时会主动调 `setDisabledState(false)`,
|
|
120
|
+
若两者压成一个值,模板上的 `[disabled]="true"` 会被静默抹掉。要恢复可编辑,两条都得放开。
|
|
121
|
+
|
|
122
|
+
`value` 与表单同时存在时以表单为准:`ngModel` / `formControl` 经 `writeValue()` 写入,
|
|
123
|
+
时序上晚于 `value` 输入。只用 `value` 而不接表单时,它是普通的单向输入 ——
|
|
124
|
+
组件不会把用户编辑写回该输入,请配合 `(aoChange)` 自行回填。
|
|
125
|
+
|
|
126
|
+
## 命令式 handle
|
|
127
|
+
|
|
128
|
+
组件实例暴露与 React / Vue **同名同义**的四个成员:
|
|
129
|
+
|
|
130
|
+
| 成员 | 类型 | 说明 |
|
|
131
|
+
| --------- | --------------------- | ------------------------------------------------ |
|
|
132
|
+
| `view` | `EditorView \| null` | 底层 CodeMirror 实例;未初始化 / 已销毁为 `null` |
|
|
133
|
+
| `host` | `HTMLElement \| null` | CodeMirror 挂载所在的元素;同上 |
|
|
134
|
+
| `focus()` | `void` | 把键盘焦点交给编辑器;未初始化时空操作 |
|
|
135
|
+
| `blur()` | `void` | 移开键盘焦点;未初始化时空操作 |
|
|
136
|
+
|
|
137
|
+
```ts
|
|
138
|
+
@Component({
|
|
139
|
+
imports: [CodeEditor],
|
|
140
|
+
template: `<ao-code-editor #editor language="sql" />`
|
|
141
|
+
})
|
|
142
|
+
export class HostComponent {
|
|
143
|
+
private readonly editor = viewChild.required(CodeEditor);
|
|
144
|
+
|
|
145
|
+
jumpIn(): void {
|
|
146
|
+
this.editor().focus();
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
`setExtensions(extensions)` 是装入自定义 CodeMirror 扩展的受支持入口,
|
|
152
|
+
**整体替换**上一次传入的那批,且只影响消费者自己的槽 —— 组件的主题、语言、
|
|
153
|
+
只读状态与变更回调都不受影响。
|
|
154
|
+
|
|
155
|
+
`setLanguage(name)` 是**无条件重载**入口,供宿主换掉了同名描述背后的 loader 实现时使用;
|
|
156
|
+
日常切换语言请改 `language` 输入(它会跳过等价的重复加载)。
|
|
157
|
+
|
|
158
|
+
其余命令式 setter(`setValue` / `setTheme` / `setReadonly` / `setEditable` /
|
|
159
|
+
`setPlaceholder` / `setIndentUnit` / `setIndentWithTab` / `setLineWrapping` /
|
|
160
|
+
`setHighlightWhitespace`)均已标记 `@deprecated`:它们与同名输入是双轨,
|
|
161
|
+
直接调用会在下一次输入同步时被覆盖。请改用对应输入,这些方法将在下一个主版本移除。
|
|
@@ -1,17 +1,21 @@
|
|
|
1
|
-
import { SUPPORT_LANGUAGES,
|
|
1
|
+
import { SUPPORT_LANGUAGES, shouldAutoFocusCodeEditor, computeMinimalDocumentChange, resolveCodeEditorLanguage, codeEditorLanguageNotFound, codeEditorLanguageLoadFailed, isSameResolvedLanguage, buildCodeEditorContentAttributes } from '@aiao/code-editor';
|
|
2
2
|
import { isPlatformBrowser } from '@angular/common';
|
|
3
3
|
import * as i0 from '@angular/core';
|
|
4
|
-
import { inject, ElementRef, PLATFORM_ID, input, booleanAttribute, output,
|
|
4
|
+
import { inject, ElementRef, PLATFORM_ID, signal, computed, input, booleanAttribute, output, forwardRef, ChangeDetectionStrategy, Component } from '@angular/core';
|
|
5
5
|
import { NG_VALUE_ACCESSOR } from '@angular/forms';
|
|
6
6
|
import { indentWithTab } from '@codemirror/commands';
|
|
7
7
|
import { indentUnit } from '@codemirror/language';
|
|
8
|
-
import { Annotation, Compartment, EditorState,
|
|
8
|
+
import { Annotation, Compartment, EditorState, Transaction } from '@codemirror/state';
|
|
9
9
|
import { oneDark } from '@codemirror/theme-one-dark';
|
|
10
10
|
import { EditorView, placeholder, keymap, highlightWhitespace } from '@codemirror/view';
|
|
11
11
|
import { basicSetup, minimalSetup } from 'codemirror';
|
|
12
12
|
|
|
13
13
|
/**
|
|
14
|
-
*
|
|
14
|
+
* 标记「外部写入」的注解,用于让 updateListener 跳过 onChange 回调。
|
|
15
|
+
*
|
|
16
|
+
* @internal
|
|
17
|
+
* 模块私有,不对外导出:每个框架绑定各有一个互不相同的 Annotation 实例,
|
|
18
|
+
* 一旦暴露就等于承诺了一个三端无法对齐的语义(React / Vue 两端同样是模块私有)。
|
|
15
19
|
*/
|
|
16
20
|
const External = Annotation.define();
|
|
17
21
|
/**
|
|
@@ -31,16 +35,18 @@ const External = Annotation.define();
|
|
|
31
35
|
*/
|
|
32
36
|
class CodeEditor {
|
|
33
37
|
/**
|
|
34
|
-
* EditorView
|
|
38
|
+
* EditorView 实例。
|
|
35
39
|
* https://codemirror.net/docs/ref/#view.EditorView
|
|
36
40
|
*/
|
|
37
41
|
#view;
|
|
38
42
|
#pendingValue;
|
|
39
43
|
#languageRequest = 0;
|
|
44
|
+
/** 最近一次**已生效**的语言解析结果,用于跳过等价的重复配置(CEA-006)。 */
|
|
45
|
+
#appliedLanguage;
|
|
40
46
|
#elementRef = inject(ElementRef);
|
|
41
47
|
#platformId = inject(PLATFORM_ID);
|
|
42
48
|
#isBrowser = isPlatformBrowser(this.#platformId);
|
|
43
|
-
// Compartments
|
|
49
|
+
// 用于动态重新配置的 Compartments。
|
|
44
50
|
// https://codemirror.net/docs/ref/#state.Compartment
|
|
45
51
|
#editableConf = new Compartment();
|
|
46
52
|
#readonlyConf = new Compartment();
|
|
@@ -51,46 +57,179 @@ class CodeEditor {
|
|
|
51
57
|
#lineWrappingConf = new Compartment();
|
|
52
58
|
#highlightWhitespaceConf = new Compartment();
|
|
53
59
|
#languageConf = new Compartment();
|
|
54
|
-
|
|
60
|
+
#a11yConf = new Compartment();
|
|
61
|
+
/** `setup` 预设的专属槽 —— 与 React(`CodeEditor.tsx`)、Vue(`CodeEditor.vue`)同构。 */
|
|
62
|
+
#setupConf = new Compartment();
|
|
63
|
+
/** 消费者经 {@link CodeEditor.setExtensions} 装入的扩展的专属槽,见 CEA-003。 */
|
|
64
|
+
#userExtensionsConf = new Compartment();
|
|
65
|
+
// 内容变化监听器。
|
|
55
66
|
#updateListener = EditorView.updateListener.of(vu => {
|
|
56
67
|
if (vu.docChanged && !vu.transactions.some(tr => tr.annotation(External))) {
|
|
57
68
|
const value = vu.state.doc.toString();
|
|
58
|
-
this
|
|
69
|
+
this.#onChange?.(value);
|
|
59
70
|
this.aoChange.emit(value);
|
|
60
71
|
}
|
|
61
72
|
});
|
|
62
|
-
//
|
|
73
|
+
// 焦点事件必须随初始 extensions 一起装配,不能在 EditorView 构造完再 addEventListener:
|
|
74
|
+
// `autoFocus` 的 `view.focus()` 紧跟构造同步派发 focus,后注册的监听器永远吞掉第一次。
|
|
75
|
+
// 用 domEventObservers 还顺带解决了裸监听器没有 removeEventListener 的问题 ——
|
|
76
|
+
// 它随 view.destroy() 一起回收。与 Vue(`CodeEditor.vue:75-78`)同构。
|
|
77
|
+
#domEventObservers = EditorView.domEventObservers({
|
|
78
|
+
focus: () => {
|
|
79
|
+
this.aoFocus.emit();
|
|
80
|
+
},
|
|
81
|
+
blur: () => {
|
|
82
|
+
this.#onTouched?.();
|
|
83
|
+
this.aoBlur.emit();
|
|
84
|
+
}
|
|
85
|
+
});
|
|
86
|
+
/**
|
|
87
|
+
* 表单侧的禁用状态,只由 {@link CodeEditor.setDisabledState} 写入。
|
|
88
|
+
*
|
|
89
|
+
* @internal
|
|
90
|
+
* 必须私有:它只在 `#effectiveDisabled` 里被消费,外部 `.set()` 写了也不会同步到编辑器。
|
|
91
|
+
*/
|
|
92
|
+
#formDisabled = signal(false, /* @ts-ignore */
|
|
93
|
+
...(ngDevMode ? [{ debugName: "#formDisabled" }] : /* istanbul ignore next */ []));
|
|
94
|
+
/**
|
|
95
|
+
* 有效禁用状态 = `disabled` 输入与表单禁用**任一为真**。
|
|
96
|
+
*
|
|
97
|
+
* @internal
|
|
98
|
+
* CEA-004:两条来源早先被压进同一个 `linkedSignal(() => this.disabled())`,
|
|
99
|
+
* 成了「最后写的赢」。它们是**独立的事实**:模板的 `[disabled]="true"` 说
|
|
100
|
+
* 「这个控件永远禁用」,`setDisabledState()` 说「这个 FormControl 当前 disable 了」。
|
|
101
|
+
* 压成一个值有两个可复现的后果 —— Reactive Forms 在控件 enabled 时会主动调
|
|
102
|
+
* `setDisabledState(false)`,把 `[disabled]="true"` 静默抹掉;反过来 `disabled`
|
|
103
|
+
* 输入一变化 `linkedSignal` 就重算,把表单侧刚设的禁用丢掉。
|
|
104
|
+
*/
|
|
105
|
+
#effectiveDisabled = computed(() => this.disabled() || this.#formDisabled(), /* @ts-ignore */
|
|
106
|
+
...(ngDevMode ? [{ debugName: "#effectiveDisabled" }] : /* istanbul ignore next */ []));
|
|
107
|
+
/** @internal 只能经 {@link registerOnChange} 注入,公开会让消费者覆盖后静默切断表单双向绑定。 */
|
|
108
|
+
#onChange;
|
|
109
|
+
/** @internal 只能经 {@link registerOnTouched} 注入,理由同 {@link registerOnChange}。 */
|
|
110
|
+
#onTouched;
|
|
111
|
+
/**
|
|
112
|
+
* 宿主元素上的 `aria-disabled`。
|
|
113
|
+
*
|
|
114
|
+
* @internal
|
|
115
|
+
* 未禁用时返回 `null` 让 Angular **移除**属性,而不是写 `aria-disabled="false"`;
|
|
116
|
+
* 理由与 {@link buildCodeEditorContentAttributes} 一致。`protected` 是可见性下限 ——
|
|
117
|
+
* host 绑定表达式读不到 `#` 私有字段。
|
|
118
|
+
*/
|
|
119
|
+
_ariaDisabled = computed(() => (this.#effectiveDisabled() ? 'true' : null), /* @ts-ignore */
|
|
120
|
+
...(ngDevMode ? [{ debugName: "_ariaDisabled" }] : /* istanbul ignore next */ []));
|
|
121
|
+
// 输入属性。
|
|
122
|
+
/** 初始化后立即聚焦编辑器。**仅初始化生效**,后续变更被忽略。 @defaultValue false */
|
|
63
123
|
autoFocus = input(false, { ...(ngDevMode ? { debugName: "autoFocus" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
124
|
+
/**
|
|
125
|
+
* 补充说明元素的 id,写成内部 textbox 的 `aria-describedby`。可动态更新。
|
|
126
|
+
*
|
|
127
|
+
* @defaultValue ''(空串按「未设置」处理,不产出属性)
|
|
128
|
+
* @remarks 属性落在 CodeMirror 的 `.cm-content` 上 —— 承担 `role="textbox"` 的是它,
|
|
129
|
+
* 不是本组件的宿主元素。三端同名同义。
|
|
130
|
+
*/
|
|
131
|
+
describedBy = input('', /* @ts-ignore */
|
|
132
|
+
...(ngDevMode ? [{ debugName: "describedBy" }] : /* istanbul ignore next */ []));
|
|
133
|
+
/**
|
|
134
|
+
* 禁用编辑(只读且不可聚焦)。可动态更新。
|
|
135
|
+
*
|
|
136
|
+
* @defaultValue false
|
|
137
|
+
* @remarks 与表单的 `setDisabledState()` 是**两条独立来源**,任一为真即禁用。
|
|
138
|
+
* 用 Reactive Forms 时把控件设成 `disabled` 与在模板上写 `[disabled]="true"` 效果一样,
|
|
139
|
+
* 但只解开其中一条不会恢复可编辑 —— 两条都得放开。
|
|
140
|
+
*/
|
|
64
141
|
disabled = input(false, { ...(ngDevMode ? { debugName: "disabled" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
142
|
+
/** 显示空白字符。可动态更新。 @defaultValue false */
|
|
65
143
|
highlightWhitespace = input(false, { ...(ngDevMode ? { debugName: "highlightWhitespace" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
66
|
-
|
|
144
|
+
/** 一级缩进使用的字符串。可动态更新。 @defaultValue ' '(两个空格,三端一致) */
|
|
145
|
+
indentUnit = input(' ', /* @ts-ignore */
|
|
67
146
|
...(ngDevMode ? [{ debugName: "indentUnit" }] : /* istanbul ignore next */ []));
|
|
147
|
+
/** 允许 Tab 键缩进(会牺牲键盘可达性)。可动态更新。 @defaultValue false */
|
|
68
148
|
indentWithTab = input(false, { ...(ngDevMode ? { debugName: "indentWithTab" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
69
|
-
|
|
149
|
+
/** 语法高亮语言名,取值见 {@link languages};`'plaintext'` 表示不高亮。可动态更新。 @defaultValue 'sql' */
|
|
150
|
+
language = input('sql', /* @ts-ignore */
|
|
70
151
|
...(ngDevMode ? [{ debugName: "language" }] : /* istanbul ignore next */ []));
|
|
152
|
+
/** 可选语言列表。可动态更新。 @defaultValue SUPPORT_LANGUAGES */
|
|
71
153
|
languages = input(SUPPORT_LANGUAGES, /* @ts-ignore */
|
|
72
154
|
...(ngDevMode ? [{ debugName: "languages" }] : /* istanbul ignore next */ []));
|
|
155
|
+
/**
|
|
156
|
+
* 可访问名称,写成内部 textbox 的 `aria-label`。可动态更新。
|
|
157
|
+
*
|
|
158
|
+
* @defaultValue ''(空串按「未设置」处理,不产出属性)
|
|
159
|
+
* @remarks 落点与 {@link CodeEditor.describedBy} 相同。
|
|
160
|
+
*/
|
|
161
|
+
label = input('', /* @ts-ignore */
|
|
162
|
+
...(ngDevMode ? [{ debugName: "label" }] : /* istanbul ignore next */ []));
|
|
163
|
+
/**
|
|
164
|
+
* 承担标签的元素 id,写成内部 textbox 的 `aria-labelledby`。可动态更新。
|
|
165
|
+
*
|
|
166
|
+
* @defaultValue ''(空串按「未设置」处理,不产出属性)
|
|
167
|
+
* @remarks 落点与 {@link CodeEditor.describedBy} 相同。
|
|
168
|
+
*/
|
|
169
|
+
labelledBy = input('', /* @ts-ignore */
|
|
170
|
+
...(ngDevMode ? [{ debugName: "labelledBy" }] : /* istanbul ignore next */ []));
|
|
171
|
+
/** 自动换行。可动态更新。 @defaultValue false */
|
|
73
172
|
lineWrapping = input(false, { ...(ngDevMode ? { debugName: "lineWrapping" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
173
|
+
/** 空文档时显示的占位文本。可动态更新。 @defaultValue '' */
|
|
74
174
|
placeholder = input('', /* @ts-ignore */
|
|
75
175
|
...(ngDevMode ? [{ debugName: "placeholder" }] : /* istanbul ignore next */ []));
|
|
176
|
+
/** 只读(仍可聚焦与选择)。可动态更新。 @defaultValue false */
|
|
76
177
|
readonly = input(false, { ...(ngDevMode ? { debugName: "readonly" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
178
|
+
/** CodeMirror 挂载的 DOM 根,用于 Shadow DOM 场景。**仅初始化生效**。 */
|
|
77
179
|
root = input(/* @ts-ignore */
|
|
78
180
|
...(ngDevMode ? [undefined, { debugName: "root" }] : /* istanbul ignore next */ []));
|
|
181
|
+
/** 预设扩展集;`null` 表示不加载任何预设。可动态更新。 @defaultValue 'basic' */
|
|
79
182
|
setup = input('basic', /* @ts-ignore */
|
|
80
183
|
...(ngDevMode ? [{ debugName: "setup" }] : /* istanbul ignore next */ []));
|
|
184
|
+
/** 主题。可动态更新。 @defaultValue 'light' */
|
|
81
185
|
theme = input('light', /* @ts-ignore */
|
|
82
186
|
...(ngDevMode ? [{ debugName: "theme" }] : /* istanbul ignore next */ []));
|
|
187
|
+
/** 文档内容。作为受控输入使用时由外部驱动;与 `ngModel` 同时使用以 `ngModel` 为准。 @defaultValue '' */
|
|
83
188
|
value = input('', /* @ts-ignore */
|
|
84
189
|
...(ngDevMode ? [{ debugName: "value" }] : /* istanbul ignore next */ []));
|
|
85
|
-
//
|
|
190
|
+
// 输出事件。
|
|
191
|
+
/** 编辑器获得焦点。 */
|
|
86
192
|
aoFocus = output();
|
|
193
|
+
/** 文档内容变化(外部写入不触发,见 {@link External})。 */
|
|
87
194
|
aoChange = output();
|
|
195
|
+
/** 编辑器失去焦点,同时触发表单 touched。 */
|
|
88
196
|
aoBlur = output();
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
197
|
+
/**
|
|
198
|
+
* 语言解析或加载失败。
|
|
199
|
+
*
|
|
200
|
+
* @remarks
|
|
201
|
+
* CEA-007:这条路径此前只有一条 `console.error`,宿主完全观测不到 ——
|
|
202
|
+
* 拿不到错误、没有重试或降级的机会,用户只看到高亮突然消失。
|
|
203
|
+
* 降级策略不变(清空语言扩展退回纯文本),日志也保留,本事件是**新增的**观测通道。
|
|
204
|
+
* 与 React 的 `onLanguageError` 属性、Vue 的 `language-error` 事件同载荷。
|
|
205
|
+
*/
|
|
206
|
+
aoLanguageError = output();
|
|
207
|
+
/**
|
|
208
|
+
* 底层 CodeMirror `EditorView`;未初始化或已销毁时为 `null`。
|
|
209
|
+
*
|
|
210
|
+
* @remarks 三端命令式面统一为 `view` / `host` / `focus()` / `blur()`(CEA-009)。
|
|
211
|
+
*/
|
|
212
|
+
get view() {
|
|
213
|
+
return this.#view ?? null;
|
|
214
|
+
}
|
|
215
|
+
/**
|
|
216
|
+
* CodeMirror 挂载所在的宿主元素;未初始化或已销毁时为 `null`。
|
|
217
|
+
*
|
|
218
|
+
* @remarks
|
|
219
|
+
* Angular 的宿主是 `<ao-code-editor>` 元素本身,React / Vue 是组件渲染出的 `div`——
|
|
220
|
+
* 类型不同但语义一致:「CodeMirror 挂在哪个元素里」。
|
|
221
|
+
*/
|
|
222
|
+
get host() {
|
|
223
|
+
return this.#view ? this.#elementRef.nativeElement : null;
|
|
224
|
+
}
|
|
225
|
+
/** 把键盘焦点交给编辑器。未初始化时是空操作。 */
|
|
226
|
+
focus() {
|
|
227
|
+
this.#view?.focus();
|
|
228
|
+
}
|
|
229
|
+
/** 移开键盘焦点。未初始化时是空操作。 */
|
|
230
|
+
blur() {
|
|
231
|
+
this.#view?.contentDOM.blur();
|
|
232
|
+
}
|
|
94
233
|
ngOnInit() {
|
|
95
234
|
if (!this.#isBrowser)
|
|
96
235
|
return;
|
|
@@ -101,57 +240,68 @@ class CodeEditor {
|
|
|
101
240
|
root: this.root(),
|
|
102
241
|
state
|
|
103
242
|
});
|
|
104
|
-
|
|
243
|
+
// 访问状态必须先落地:`autoFocus` 要读**已生效**的禁用 / 只读语义,而 `focus()`
|
|
244
|
+
// 又会同步派发 focus 事件,晚同步等于让监听者先读到一个还没对齐的状态。
|
|
245
|
+
this.#syncAccessState();
|
|
246
|
+
// CEA-008(用户可见的行为修正):`disabled` / `readonly` 都会把编辑器配成不可编辑,
|
|
247
|
+
// 此时仍然抢焦点,用户只会得到一个无法输入、也无从得知为什么无法输入的控件。
|
|
248
|
+
if (this.autoFocus() &&
|
|
249
|
+
shouldAutoFocusCodeEditor({ disabled: this.#effectiveDisabled(), readonly: this.readonly() })) {
|
|
105
250
|
this.#view.focus();
|
|
106
251
|
}
|
|
107
|
-
this.#
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
this.#
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
});
|
|
114
|
-
this.#syncAccessState();
|
|
115
|
-
this.setTheme(this.theme());
|
|
116
|
-
this.setPlaceholder(this.placeholder());
|
|
117
|
-
this.setIndentWithTab(this.indentWithTab());
|
|
118
|
-
this.setIndentUnit(this.indentUnit());
|
|
119
|
-
this.setLineWrapping(this.lineWrapping());
|
|
120
|
-
this.setHighlightWhitespace(this.highlightWhitespace());
|
|
252
|
+
this._dispatch_effects(this.#themeConf.reconfigure(this.theme() === 'dark' ? oneDark : []));
|
|
253
|
+
this._dispatch_effects(this.#placeholderConf.reconfigure(this.placeholder() ? placeholder(this.placeholder()) : []));
|
|
254
|
+
this._dispatch_effects(this.#indentWithTabConf.reconfigure(this.indentWithTab() ? keymap.of([indentWithTab]) : []));
|
|
255
|
+
this._dispatch_effects(this.#indentUnitConf.reconfigure(this.indentUnit() ? indentUnit.of(this.indentUnit()) : []));
|
|
256
|
+
this._dispatch_effects(this.#lineWrappingConf.reconfigure(this.lineWrapping() ? EditorView.lineWrapping : []));
|
|
257
|
+
this._dispatch_effects(this.#highlightWhitespaceConf.reconfigure(this.highlightWhitespace() ? highlightWhitespace() : []));
|
|
121
258
|
this.setLanguage(this.language());
|
|
122
259
|
}
|
|
123
260
|
ngOnChanges(changes) {
|
|
124
261
|
if (!this.#view)
|
|
125
262
|
return;
|
|
126
|
-
if (changes['value']) {
|
|
127
|
-
this.
|
|
263
|
+
if (changes['value'] && this.#view) {
|
|
264
|
+
const newValue = this.value();
|
|
265
|
+
const change = computeMinimalDocumentChange(this.#view.state.doc.toString(), newValue);
|
|
266
|
+
if (change) {
|
|
267
|
+
this.#view.dispatch({
|
|
268
|
+
changes: change,
|
|
269
|
+
annotations: [External.of(true), Transaction.addToHistory.of(false)],
|
|
270
|
+
scrollIntoView: false
|
|
271
|
+
});
|
|
272
|
+
}
|
|
128
273
|
}
|
|
129
274
|
if (changes['disabled'] || changes['readonly']) {
|
|
130
275
|
this.#syncAccessState();
|
|
131
276
|
}
|
|
277
|
+
if (changes['describedBy'] || changes['label'] || changes['labelledBy']) {
|
|
278
|
+
this.#syncContentAttributes();
|
|
279
|
+
}
|
|
132
280
|
if (changes['theme']) {
|
|
133
|
-
this.
|
|
281
|
+
this._dispatch_effects(this.#themeConf.reconfigure(this.theme() === 'dark' ? oneDark : []));
|
|
134
282
|
}
|
|
135
283
|
if (changes['placeholder']) {
|
|
136
|
-
this.
|
|
284
|
+
this._dispatch_effects(this.#placeholderConf.reconfigure(this.placeholder() ? placeholder(this.placeholder()) : []));
|
|
137
285
|
}
|
|
138
286
|
if (changes['indentWithTab']) {
|
|
139
|
-
this.
|
|
287
|
+
this._dispatch_effects(this.#indentWithTabConf.reconfigure(this.indentWithTab() ? keymap.of([indentWithTab]) : []));
|
|
140
288
|
}
|
|
141
289
|
if (changes['indentUnit']) {
|
|
142
|
-
this.
|
|
290
|
+
this._dispatch_effects(this.#indentUnitConf.reconfigure(this.indentUnit() ? indentUnit.of(this.indentUnit()) : []));
|
|
143
291
|
}
|
|
144
292
|
if (changes['lineWrapping']) {
|
|
145
|
-
this.
|
|
293
|
+
this._dispatch_effects(this.#lineWrappingConf.reconfigure(this.lineWrapping() ? EditorView.lineWrapping : []));
|
|
146
294
|
}
|
|
147
295
|
if (changes['highlightWhitespace']) {
|
|
148
|
-
this.
|
|
296
|
+
this._dispatch_effects(this.#highlightWhitespaceConf.reconfigure(this.highlightWhitespace() ? highlightWhitespace() : []));
|
|
149
297
|
}
|
|
150
298
|
if (changes['language'] || changes['languages']) {
|
|
151
|
-
this
|
|
299
|
+
this.#syncLanguage();
|
|
152
300
|
}
|
|
153
301
|
if (changes['setup']) {
|
|
154
|
-
|
|
302
|
+
// CEA-003:早先这里调的是 `setExtensions(this._get_all_extensions())`,
|
|
303
|
+
// 即换个预设就把整棵扩展树连同消费者自己装的扩展一起重建。
|
|
304
|
+
this._dispatch_effects(this.#setupConf.reconfigure(this.#setupExtension()));
|
|
155
305
|
}
|
|
156
306
|
}
|
|
157
307
|
ngOnDestroy() {
|
|
@@ -160,89 +310,114 @@ class CodeEditor {
|
|
|
160
310
|
this.#view = undefined;
|
|
161
311
|
view?.destroy();
|
|
162
312
|
}
|
|
313
|
+
/**
|
|
314
|
+
* `ControlValueAccessor` 的写入入口,由 Angular Forms 调用。
|
|
315
|
+
*
|
|
316
|
+
* @param value `null` / `undefined` 规整成空串;`string` 原样写入
|
|
317
|
+
* @throws TypeError 收到其他类型时立即抛出,错误信息含包名与实际类型
|
|
318
|
+
*
|
|
319
|
+
* @remarks
|
|
320
|
+
* CEA-005:签名此前写成 `string | null`,但 CVA 的契约是 `any` —— Angular
|
|
321
|
+
* **不做**运行时校验,`FormControl` 里放什么就原样送到这里。类型标注只骗过了编译期。
|
|
322
|
+
* 于是 `FormControl(42)` 在初始化前进 `#pendingValue`、在 `EditorState.create` 处炸,
|
|
323
|
+
* 初始化后又在 `computeMinimalDocumentChange` 处炸 —— 同一个错误配置,两个时机两种错,
|
|
324
|
+
* 且都指不回 `writeValue`。现在两个时机抛同一个错,且直接点名包与类型。
|
|
325
|
+
*/
|
|
163
326
|
writeValue(value) {
|
|
164
|
-
const normalized = value
|
|
327
|
+
const normalized = this.#normalizeFormValue(value);
|
|
165
328
|
if (!this.#view) {
|
|
166
329
|
this.#pendingValue = normalized;
|
|
167
330
|
return;
|
|
168
331
|
}
|
|
169
|
-
this.
|
|
332
|
+
const change = computeMinimalDocumentChange(this.#view.state.doc.toString(), normalized);
|
|
333
|
+
if (change) {
|
|
334
|
+
this.#view.dispatch({
|
|
335
|
+
changes: change,
|
|
336
|
+
annotations: [External.of(true), Transaction.addToHistory.of(false)],
|
|
337
|
+
scrollIntoView: false
|
|
338
|
+
});
|
|
339
|
+
}
|
|
170
340
|
}
|
|
171
341
|
registerOnChange(fn) {
|
|
172
|
-
this
|
|
342
|
+
this.#onChange = fn;
|
|
173
343
|
}
|
|
174
344
|
registerOnTouched(fn) {
|
|
175
|
-
this
|
|
345
|
+
this.#onTouched = fn;
|
|
176
346
|
}
|
|
347
|
+
/**
|
|
348
|
+
* 表单侧的禁用开关,由 Angular Forms 调用。
|
|
349
|
+
*
|
|
350
|
+
* @remarks 只写 {@link CodeEditor.#formDisabled} 这一条来源;模板上的
|
|
351
|
+
* `[disabled]` 输入是另一条独立来源,两者取或,见 {@link CodeEditor.#effectiveDisabled}。
|
|
352
|
+
*/
|
|
177
353
|
setDisabledState(isDisabled) {
|
|
178
|
-
this.
|
|
354
|
+
this.#formDisabled.set(isDisabled);
|
|
179
355
|
this.#syncAccessState();
|
|
180
356
|
}
|
|
357
|
+
/**
|
|
358
|
+
* 装入消费者自己的 CodeMirror 扩展。
|
|
359
|
+
*
|
|
360
|
+
* @param value 本次要生效的扩展;**整体替换**上一次传入的那批,传 `[]` 即全部撤下
|
|
361
|
+
*
|
|
362
|
+
* @remarks
|
|
363
|
+
* CEA-003:早先这里发的是全局 `StateEffect.reconfigure.of(value)`,
|
|
364
|
+
* 它替换的是**整棵扩展树** —— 一次调用就清掉 updateListener、全部 Compartment、
|
|
365
|
+
* 主题、语言与 a11y 属性,名字叫「设置扩展」,实际是「摧毁编辑器配置」。
|
|
366
|
+
* 现在只重配 {@link CodeEditor.#userExtensionsConf} 这一个专属槽,
|
|
367
|
+
* 组件自身的配置与消费者的扩展互不影响。
|
|
368
|
+
*/
|
|
181
369
|
setExtensions(value) {
|
|
182
|
-
this._dispatch_effects(
|
|
183
|
-
}
|
|
184
|
-
setValue(value, external = false) {
|
|
185
|
-
const view = this.#view;
|
|
186
|
-
if (!view || view.state.doc.toString() === value)
|
|
187
|
-
return;
|
|
188
|
-
view.dispatch({
|
|
189
|
-
changes: { from: 0, to: view.state.doc.length, insert: value },
|
|
190
|
-
annotations: external ? External.of(true) : undefined
|
|
191
|
-
});
|
|
192
|
-
}
|
|
193
|
-
setEditable(value) {
|
|
194
|
-
this._dispatch_effects(this.#editableConf.reconfigure(EditorView.editable.of(value)));
|
|
195
|
-
}
|
|
196
|
-
setReadonly(value) {
|
|
197
|
-
this._dispatch_effects(this.#readonlyConf.reconfigure(EditorState.readOnly.of(value)));
|
|
198
|
-
}
|
|
199
|
-
setTheme(value) {
|
|
200
|
-
this._dispatch_effects(this.#themeConf.reconfigure(value === 'dark' ? oneDark : []));
|
|
201
|
-
}
|
|
202
|
-
setPlaceholder(value) {
|
|
203
|
-
this._dispatch_effects(this.#placeholderConf.reconfigure(value ? placeholder(value) : []));
|
|
204
|
-
}
|
|
205
|
-
setIndentWithTab(value) {
|
|
206
|
-
this._dispatch_effects(this.#indentWithTabConf.reconfigure(value ? keymap.of([indentWithTab]) : []));
|
|
207
|
-
}
|
|
208
|
-
setIndentUnit(value) {
|
|
209
|
-
this._dispatch_effects(this.#indentUnitConf.reconfigure(value ? indentUnit.of(value) : []));
|
|
210
|
-
}
|
|
211
|
-
setLineWrapping(value) {
|
|
212
|
-
this._dispatch_effects(this.#lineWrappingConf.reconfigure(value ? EditorView.lineWrapping : []));
|
|
213
|
-
}
|
|
214
|
-
setHighlightWhitespace(value) {
|
|
215
|
-
this._dispatch_effects(this.#highlightWhitespaceConf.reconfigure(value ? highlightWhitespace() : []));
|
|
370
|
+
this._dispatch_effects(this.#userExtensionsConf.reconfigure(value));
|
|
216
371
|
}
|
|
372
|
+
/**
|
|
373
|
+
* 命令式地(重新)应用语言配置。
|
|
374
|
+
*
|
|
375
|
+
* @param lang 语言名或别名;空串与 `'plaintext'` 表示不高亮
|
|
376
|
+
*
|
|
377
|
+
* @remarks
|
|
378
|
+
* **无条件重新加载**,即使解析结果与当前生效的完全一致 —— 这是宿主换掉了同名描述
|
|
379
|
+
* 背后的 loader 实现时的显式重载入口,`language` 输入做不到(它走的是
|
|
380
|
+
* {@link CodeEditor.#syncLanguage} 里的等价性判断,CEA-006,不会重复加载)。
|
|
381
|
+
* 日常切换语言应当改 `language` 输入。
|
|
382
|
+
*
|
|
383
|
+
* 解析失败时清空语言扩展退回纯文本,并经 {@link CodeEditor.aoLanguageError} 报告。
|
|
384
|
+
*/
|
|
217
385
|
setLanguage(lang) {
|
|
218
386
|
const request = ++this.#languageRequest;
|
|
219
387
|
const view = this.#view;
|
|
220
388
|
if (!view)
|
|
221
389
|
return;
|
|
222
|
-
const
|
|
223
|
-
|
|
390
|
+
const resolved = resolveCodeEditorLanguage(lang, this.languages());
|
|
391
|
+
this.#appliedLanguage = resolved;
|
|
392
|
+
if (resolved.kind === 'none') {
|
|
224
393
|
view.dispatch({ effects: this.#languageConf.reconfigure([]) });
|
|
225
394
|
return;
|
|
226
395
|
}
|
|
227
|
-
|
|
228
|
-
if (!langDesc) {
|
|
396
|
+
if (resolved.kind === 'not-found') {
|
|
229
397
|
view.dispatch({ effects: this.#languageConf.reconfigure([]) });
|
|
398
|
+
// 日志与另两端逐字一致,作为最后兜底诊断保留;结构化事件才是给宿主的契约通道。
|
|
399
|
+
console.error(`[CodeEditor] Language '${lang}' not found.`);
|
|
400
|
+
this.aoLanguageError.emit(codeEditorLanguageNotFound(lang));
|
|
230
401
|
return;
|
|
231
402
|
}
|
|
232
|
-
|
|
403
|
+
// 直接用解析结果里的 description:早先这里又调一次 `_find_language(lang)` 重查一遍,
|
|
404
|
+
// 同一个名字解析两遍,两处判定一旦分叉就会出现「解析说找到了、重查说没有」。
|
|
405
|
+
void resolved.description.load().then(languageSupport => {
|
|
233
406
|
if (request !== this.#languageRequest || this.#view !== view)
|
|
234
407
|
return;
|
|
235
408
|
view.dispatch({ effects: this.#languageConf.reconfigure(languageSupport.extension) });
|
|
236
|
-
}, error => {
|
|
409
|
+
}, (error) => {
|
|
237
410
|
if (request !== this.#languageRequest || this.#view !== view)
|
|
238
411
|
return;
|
|
239
|
-
console.error(`[code-editor-angular] Failed to load language "${lang}":`, error);
|
|
240
412
|
view.dispatch({ effects: this.#languageConf.reconfigure([]) });
|
|
413
|
+
console.error(`[CodeEditor] Failed to load language '${lang}':`, error);
|
|
414
|
+
this.aoLanguageError.emit(codeEditorLanguageLoadFailed(lang, error));
|
|
241
415
|
});
|
|
242
416
|
}
|
|
243
417
|
_get_all_extensions() {
|
|
244
418
|
const extensions = [
|
|
245
419
|
this.#updateListener,
|
|
420
|
+
this.#domEventObservers,
|
|
246
421
|
this.#editableConf.of([]),
|
|
247
422
|
this.#readonlyConf.of([]),
|
|
248
423
|
this.#themeConf.of([]),
|
|
@@ -251,37 +426,93 @@ class CodeEditor {
|
|
|
251
426
|
this.#indentUnitConf.of([]),
|
|
252
427
|
this.#lineWrappingConf.of([]),
|
|
253
428
|
this.#highlightWhitespaceConf.of([]),
|
|
254
|
-
this.#languageConf.of([])
|
|
429
|
+
this.#languageConf.of([]),
|
|
430
|
+
this.#a11yConf.of([]),
|
|
431
|
+
this.#setupConf.of(this.#setupExtension()),
|
|
432
|
+
// 消费者扩展排在最后 —— CodeMirror 里后来的优先级更高,
|
|
433
|
+
// 宿主装的 keymap / 主题覆盖才压得住预设。
|
|
434
|
+
this.#userExtensionsConf.of([])
|
|
255
435
|
];
|
|
256
|
-
const setup = this.setup();
|
|
257
|
-
if (setup === 'basic') {
|
|
258
|
-
extensions.push(basicSetup);
|
|
259
|
-
}
|
|
260
|
-
else if (setup === 'minimal') {
|
|
261
|
-
extensions.push(minimalSetup);
|
|
262
|
-
}
|
|
263
436
|
return extensions;
|
|
264
437
|
}
|
|
265
|
-
_find_language(name) {
|
|
266
|
-
const languages = this.languages();
|
|
267
|
-
const language = findLanguageByName(name, languages);
|
|
268
|
-
if (language) {
|
|
269
|
-
return language;
|
|
270
|
-
}
|
|
271
|
-
console.error(`Language "${name}" not found. Supported: ${languages.map(lang => lang.name).join(', ')}`);
|
|
272
|
-
return null;
|
|
273
|
-
}
|
|
274
438
|
_dispatch_effects(effects) {
|
|
275
439
|
return this.#view?.dispatch({ effects });
|
|
276
440
|
}
|
|
441
|
+
/**
|
|
442
|
+
* `language` / `languages` 输入变化后的语言同步。
|
|
443
|
+
*
|
|
444
|
+
* CEA-006:早先只要 `languages` 的**数组引用**变了就无条件 `setLanguage()`。
|
|
445
|
+
* 父模板写 `[languages]="[myLang]"` 这类内联字面量时每轮变更检测都产生新数组,
|
|
446
|
+
* 于是每轮都重新 `load()` 并 reconfigure 语言 Compartment ——
|
|
447
|
+
* 大文档等于每次输入都整篇重新词法分析,自定义异步 loader 还会多发请求。
|
|
448
|
+
* 判定基准改成「当前语言实际解析到的 description identity」,与 React / Vue 同构。
|
|
449
|
+
*/
|
|
450
|
+
#syncLanguage() {
|
|
451
|
+
const language = this.language();
|
|
452
|
+
const resolved = resolveCodeEditorLanguage(language, this.languages());
|
|
453
|
+
if (isSameResolvedLanguage(this.#appliedLanguage, resolved))
|
|
454
|
+
return;
|
|
455
|
+
this.setLanguage(language);
|
|
456
|
+
}
|
|
457
|
+
/**
|
|
458
|
+
* 把无障碍属性写到真正承担 `role="textbox"` 的 `.cm-content` 上。
|
|
459
|
+
*
|
|
460
|
+
* CEA-008 / CER-006:只在宿主 `div` 上补 `aria-*` 是把缺陷换个位置 —— 屏幕阅读器
|
|
461
|
+
* 进到 textbox 之后读到的仍然是一个没有可访问名称、只报 `aria-readonly` 的多行文本框。
|
|
462
|
+
* 属性字典由核心包计算,三端共用同一份实现。
|
|
463
|
+
*/
|
|
464
|
+
#syncContentAttributes() {
|
|
465
|
+
this._dispatch_effects(this.#contentAttributesEffect(this.#effectiveDisabled()));
|
|
466
|
+
}
|
|
467
|
+
#contentAttributesEffect(disabled) {
|
|
468
|
+
const attributes = buildCodeEditorContentAttributes({
|
|
469
|
+
describedBy: this.describedBy(),
|
|
470
|
+
disabled,
|
|
471
|
+
label: this.label(),
|
|
472
|
+
labelledBy: this.labelledBy()
|
|
473
|
+
});
|
|
474
|
+
return this.#a11yConf.reconfigure(EditorView.contentAttributes.of(attributes));
|
|
475
|
+
}
|
|
277
476
|
#syncAccessState() {
|
|
278
|
-
const disabled = this
|
|
477
|
+
const disabled = this.#effectiveDisabled();
|
|
279
478
|
const readonly = this.readonly();
|
|
280
|
-
|
|
281
|
-
|
|
479
|
+
// CEA-004:三个 Compartment 必须在**同一次** dispatch 里重配。早先是
|
|
480
|
+
// readonly → editable → a11y 三次派发,中间两拍是「只读但仍标记可编辑」
|
|
481
|
+
// 这种自相矛盾的状态,装了 updateListener 的插件全都看得见,还各自重算一遍。
|
|
482
|
+
//
|
|
483
|
+
// 禁用语义也要写给辅助技术:只配 readonly 的话 CodeMirror 只写出 `aria-readonly`,
|
|
484
|
+
// 屏幕阅读器会把禁用控件读成「可编辑但当前只读」,用户以为解锁后就能输入。
|
|
485
|
+
this._dispatch_effects([
|
|
486
|
+
this.#readonlyConf.reconfigure(EditorState.readOnly.of(readonly || disabled)),
|
|
487
|
+
this.#editableConf.reconfigure(EditorView.editable.of(!readonly && !disabled)),
|
|
488
|
+
this.#contentAttributesEffect(disabled)
|
|
489
|
+
]);
|
|
490
|
+
}
|
|
491
|
+
/** 当前 `setup` 预设对应的扩展;`null` 预设为空数组。 */
|
|
492
|
+
#setupExtension() {
|
|
493
|
+
const setup = this.setup();
|
|
494
|
+
if (setup === 'basic')
|
|
495
|
+
return basicSetup;
|
|
496
|
+
if (setup === 'minimal')
|
|
497
|
+
return minimalSetup;
|
|
498
|
+
return [];
|
|
499
|
+
}
|
|
500
|
+
/**
|
|
501
|
+
* 把 `ControlValueAccessor` 收到的任意值规整成文档内容,见 {@link CodeEditor.writeValue}。
|
|
502
|
+
*
|
|
503
|
+
* @throws TypeError 值既不是字符串也不是 `null` / `undefined`
|
|
504
|
+
*/
|
|
505
|
+
#normalizeFormValue(value) {
|
|
506
|
+
if (value === null || value === undefined)
|
|
507
|
+
return '';
|
|
508
|
+
if (typeof value === 'string')
|
|
509
|
+
return value;
|
|
510
|
+
const actual = typeof value === 'object' ? (value.constructor?.name ?? 'object') : typeof value;
|
|
511
|
+
throw new TypeError(`[@aiao/code-editor-angular] writeValue() 只接受 string | null | undefined,实际收到 ${actual}。` +
|
|
512
|
+
'请检查绑定的 FormControl 初始值或 ngModel 表达式。');
|
|
282
513
|
}
|
|
283
514
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.5", ngImport: i0, type: CodeEditor, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
284
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "22.0.5", type: CodeEditor, isStandalone: true, selector: "ao-code-editor", inputs: { autoFocus: { classPropertyName: "autoFocus", publicName: "autoFocus", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, highlightWhitespace: { classPropertyName: "highlightWhitespace", publicName: "highlightWhitespace", isSignal: true, isRequired: false, transformFunction: null }, indentUnit: { classPropertyName: "indentUnit", publicName: "indentUnit", isSignal: true, isRequired: false, transformFunction: null }, indentWithTab: { classPropertyName: "indentWithTab", publicName: "indentWithTab", isSignal: true, isRequired: false, transformFunction: null }, language: { classPropertyName: "language", publicName: "language", isSignal: true, isRequired: false, transformFunction: null }, languages: { classPropertyName: "languages", publicName: "languages", isSignal: true, isRequired: false, transformFunction: null }, lineWrapping: { classPropertyName: "lineWrapping", publicName: "lineWrapping", isSignal: true, isRequired: false, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, readonly: { classPropertyName: "readonly", publicName: "readonly", isSignal: true, isRequired: false, transformFunction: null }, root: { classPropertyName: "root", publicName: "root", isSignal: true, isRequired: false, transformFunction: null }, setup: { classPropertyName: "setup", publicName: "setup", isSignal: true, isRequired: false, transformFunction: null }, theme: { classPropertyName: "theme", publicName: "theme", isSignal: true, isRequired: false, transformFunction: null }, value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { aoFocus: "aoFocus", aoChange: "aoChange", aoBlur: "aoBlur" }, providers: [
|
|
515
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "22.0.5", type: CodeEditor, isStandalone: true, selector: "ao-code-editor", inputs: { autoFocus: { classPropertyName: "autoFocus", publicName: "autoFocus", isSignal: true, isRequired: false, transformFunction: null }, describedBy: { classPropertyName: "describedBy", publicName: "describedBy", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, highlightWhitespace: { classPropertyName: "highlightWhitespace", publicName: "highlightWhitespace", isSignal: true, isRequired: false, transformFunction: null }, indentUnit: { classPropertyName: "indentUnit", publicName: "indentUnit", isSignal: true, isRequired: false, transformFunction: null }, indentWithTab: { classPropertyName: "indentWithTab", publicName: "indentWithTab", isSignal: true, isRequired: false, transformFunction: null }, language: { classPropertyName: "language", publicName: "language", isSignal: true, isRequired: false, transformFunction: null }, languages: { classPropertyName: "languages", publicName: "languages", isSignal: true, isRequired: false, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, labelledBy: { classPropertyName: "labelledBy", publicName: "labelledBy", isSignal: true, isRequired: false, transformFunction: null }, lineWrapping: { classPropertyName: "lineWrapping", publicName: "lineWrapping", isSignal: true, isRequired: false, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, readonly: { classPropertyName: "readonly", publicName: "readonly", isSignal: true, isRequired: false, transformFunction: null }, root: { classPropertyName: "root", publicName: "root", isSignal: true, isRequired: false, transformFunction: null }, setup: { classPropertyName: "setup", publicName: "setup", isSignal: true, isRequired: false, transformFunction: null }, theme: { classPropertyName: "theme", publicName: "theme", isSignal: true, isRequired: false, transformFunction: null }, value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { aoFocus: "aoFocus", aoChange: "aoChange", aoBlur: "aoBlur", aoLanguageError: "aoLanguageError" }, host: { properties: { "attr.aria-disabled": "_ariaDisabled()" } }, providers: [
|
|
285
516
|
{
|
|
286
517
|
provide: NG_VALUE_ACCESSOR,
|
|
287
518
|
useExisting: forwardRef(() => CodeEditor),
|
|
@@ -291,29 +522,34 @@ class CodeEditor {
|
|
|
291
522
|
}
|
|
292
523
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.5", ngImport: i0, type: CodeEditor, decorators: [{
|
|
293
524
|
type: Component,
|
|
294
|
-
args: [{ selector: 'ao-code-editor', template: ``, changeDetection: ChangeDetectionStrategy.OnPush,
|
|
525
|
+
args: [{ selector: 'ao-code-editor', template: ``, changeDetection: ChangeDetectionStrategy.OnPush, host: {
|
|
526
|
+
'[attr.aria-disabled]': '_ariaDisabled()'
|
|
527
|
+
}, providers: [
|
|
295
528
|
{
|
|
296
529
|
provide: NG_VALUE_ACCESSOR,
|
|
297
530
|
useExisting: forwardRef(() => CodeEditor),
|
|
298
531
|
multi: true
|
|
299
532
|
}
|
|
300
533
|
], styles: [":host{display:block;height:100%}:host ::ng-deep .cm-editor{height:100%}:host ::ng-deep .cm-editor.cm-focused{outline:2px solid Highlight;outline-offset:-2px}\n"] }]
|
|
301
|
-
}], propDecorators: { autoFocus: [{ type: i0.Input, args: [{ isSignal: true, alias: "autoFocus", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], highlightWhitespace: [{ type: i0.Input, args: [{ isSignal: true, alias: "highlightWhitespace", required: false }] }], indentUnit: [{ type: i0.Input, args: [{ isSignal: true, alias: "indentUnit", required: false }] }], indentWithTab: [{ type: i0.Input, args: [{ isSignal: true, alias: "indentWithTab", required: false }] }], language: [{ type: i0.Input, args: [{ isSignal: true, alias: "language", required: false }] }], languages: [{ type: i0.Input, args: [{ isSignal: true, alias: "languages", required: false }] }], lineWrapping: [{ type: i0.Input, args: [{ isSignal: true, alias: "lineWrapping", required: false }] }], placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], readonly: [{ type: i0.Input, args: [{ isSignal: true, alias: "readonly", required: false }] }], root: [{ type: i0.Input, args: [{ isSignal: true, alias: "root", required: false }] }], setup: [{ type: i0.Input, args: [{ isSignal: true, alias: "setup", required: false }] }], theme: [{ type: i0.Input, args: [{ isSignal: true, alias: "theme", required: false }] }], value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }], aoFocus: [{ type: i0.Output, args: ["aoFocus"] }], aoChange: [{ type: i0.Output, args: ["aoChange"] }], aoBlur: [{ type: i0.Output, args: ["aoBlur"] }] } });
|
|
534
|
+
}], propDecorators: { autoFocus: [{ type: i0.Input, args: [{ isSignal: true, alias: "autoFocus", required: false }] }], describedBy: [{ type: i0.Input, args: [{ isSignal: true, alias: "describedBy", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], highlightWhitespace: [{ type: i0.Input, args: [{ isSignal: true, alias: "highlightWhitespace", required: false }] }], indentUnit: [{ type: i0.Input, args: [{ isSignal: true, alias: "indentUnit", required: false }] }], indentWithTab: [{ type: i0.Input, args: [{ isSignal: true, alias: "indentWithTab", required: false }] }], language: [{ type: i0.Input, args: [{ isSignal: true, alias: "language", required: false }] }], languages: [{ type: i0.Input, args: [{ isSignal: true, alias: "languages", required: false }] }], label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], labelledBy: [{ type: i0.Input, args: [{ isSignal: true, alias: "labelledBy", required: false }] }], lineWrapping: [{ type: i0.Input, args: [{ isSignal: true, alias: "lineWrapping", required: false }] }], placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], readonly: [{ type: i0.Input, args: [{ isSignal: true, alias: "readonly", required: false }] }], root: [{ type: i0.Input, args: [{ isSignal: true, alias: "root", required: false }] }], setup: [{ type: i0.Input, args: [{ isSignal: true, alias: "setup", required: false }] }], theme: [{ type: i0.Input, args: [{ isSignal: true, alias: "theme", required: false }] }], value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }], aoFocus: [{ type: i0.Output, args: ["aoFocus"] }], aoChange: [{ type: i0.Output, args: ["aoChange"] }], aoBlur: [{ type: i0.Output, args: ["aoBlur"] }], aoLanguageError: [{ type: i0.Output, args: ["aoLanguageError"] }] } });
|
|
302
535
|
|
|
303
536
|
/**
|
|
304
|
-
* @aiao/code-editor-angular
|
|
537
|
+
* @fileoverview `@aiao/code-editor-angular` 包入口。
|
|
305
538
|
*
|
|
306
|
-
* Angular
|
|
539
|
+
* 导出 Angular `CodeEditor` 组件、`CodeEditorSetup` 与 `CodeEditorTheme` 类型。
|
|
540
|
+
* 与 React / Vue 绑定同名同语义,三端共用 `@aiao/code-editor` 的语言解析与无障碍工具。
|
|
307
541
|
*
|
|
308
542
|
* @example
|
|
309
543
|
* ```ts
|
|
310
544
|
* import { CodeEditor } from '@aiao/code-editor-angular';
|
|
311
545
|
* ```
|
|
546
|
+
*
|
|
547
|
+
* @module @aiao/code-editor-angular
|
|
312
548
|
*/
|
|
313
549
|
|
|
314
550
|
/**
|
|
315
551
|
* Generated bundle index. Do not edit.
|
|
316
552
|
*/
|
|
317
553
|
|
|
318
|
-
export { CodeEditor
|
|
554
|
+
export { CodeEditor };
|
|
319
555
|
//# sourceMappingURL=aiao-code-editor-angular.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"aiao-code-editor-angular.mjs","sources":["../../../../packages/code-editor-angular/src/code-editor.ts","../../../../packages/code-editor-angular/src/index.ts","../../../../packages/code-editor-angular/src/aiao-code-editor-angular.ts"],"sourcesContent":["/**\n * @file\n * @aiao/code-editor-angular\n *\n * Angular CodeMirror 代码编辑器组件实现\n */\nimport type { CodeEditorLanguageDescription, CodeEditorTheme } from '@aiao/code-editor';\nimport { findLanguageByName, SUPPORT_LANGUAGES } from '@aiao/code-editor';\nimport { isPlatformBrowser } from '@angular/common';\nimport {\n booleanAttribute,\n ChangeDetectionStrategy,\n Component,\n ElementRef,\n forwardRef,\n inject,\n input,\n linkedSignal,\n OnChanges,\n OnDestroy,\n OnInit,\n output,\n PLATFORM_ID,\n SimpleChanges\n} from '@angular/core';\nimport { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';\nimport { indentWithTab } from '@codemirror/commands';\nimport { indentUnit } from '@codemirror/language';\nimport { Annotation, Compartment, EditorState, Extension, StateEffect } from '@codemirror/state';\nimport { oneDark } from '@codemirror/theme-one-dark';\nimport { EditorView, highlightWhitespace, keymap, placeholder } from '@codemirror/view';\nimport { basicSetup, minimalSetup } from 'codemirror';\n\nexport type { CodeEditorTheme } from '@aiao/code-editor';\n\n/** CodeMirror 预设配置。 */\nexport type CodeEditorSetup = 'basic' | 'minimal' | null;\n\n/**\n * Annotation to mark external changes that should not trigger onChange\n */\nexport const External = Annotation.define<boolean>();\n\n/**\n * CodeEditor Angular 组件\n *\n * 基于 CodeMirror 6 的代码编辑器 Angular 组件,支持多种语言和主题\n *\n * @example\n * ```html\n * <ao-code-editor\n * [(ngModel)]=\"code\"\n * language=\"typescript\"\n * theme=\"dark\"\n * (aoChange)=\"onCodeChange($event)\"\n * />\n * ```\n */\n@Component({\n selector: 'ao-code-editor',\n template: ``,\n styleUrl: './code-editor.scss',\n changeDetection: ChangeDetectionStrategy.OnPush,\n providers: [\n {\n provide: NG_VALUE_ACCESSOR,\n useExisting: forwardRef(() => CodeEditor),\n multi: true\n }\n ]\n})\nexport class CodeEditor implements OnInit, ControlValueAccessor, OnDestroy, OnChanges {\n /**\n * EditorView instance\n * https://codemirror.net/docs/ref/#view.EditorView\n */\n #view?: EditorView;\n #pendingValue?: string;\n #languageRequest = 0;\n\n #elementRef = inject<ElementRef<HTMLElement>>(ElementRef);\n #platformId = inject(PLATFORM_ID);\n #isBrowser = isPlatformBrowser(this.#platformId);\n\n // Compartments for dynamic reconfiguration\n // https://codemirror.net/docs/ref/#state.Compartment\n #editableConf = new Compartment();\n #readonlyConf = new Compartment();\n #themeConf = new Compartment();\n #placeholderConf = new Compartment();\n #indentWithTabConf = new Compartment();\n #indentUnitConf = new Compartment();\n #lineWrappingConf = new Compartment();\n #highlightWhitespaceConf = new Compartment();\n #languageConf = new Compartment();\n\n // Update listener for content changes\n #updateListener = EditorView.updateListener.of(vu => {\n if (vu.docChanged && !vu.transactions.some(tr => tr.annotation(External))) {\n const value = vu.state.doc.toString();\n this.onChange?.(value);\n this.aoChange.emit(value);\n }\n });\n\n // Input properties\n autoFocus = input(false, { transform: booleanAttribute });\n disabled = input(false, { transform: booleanAttribute });\n highlightWhitespace = input(false, { transform: booleanAttribute });\n indentUnit = input<string>('');\n indentWithTab = input(false, { transform: booleanAttribute });\n language = input<string>('');\n languages = input<readonly CodeEditorLanguageDescription[]>(SUPPORT_LANGUAGES);\n lineWrapping = input(false, { transform: booleanAttribute });\n placeholder = input<string>('');\n readonly = input(false, { transform: booleanAttribute });\n root = input<Document | ShadowRoot>();\n setup = input<CodeEditorSetup>('basic');\n theme = input<CodeEditorTheme>('light');\n value = input<string>('');\n\n // Output events\n aoFocus = output<void>();\n aoChange = output<string>();\n aoBlur = output<void>();\n\n // Linked signal for disabled state\n useDisabled = linkedSignal(() => this.disabled());\n\n onChange?: (value: string) => void;\n onTouched?: () => void;\n\n ngOnInit() {\n if (!this.#isBrowser) return;\n const extensions: Extension[] = this._get_all_extensions();\n const state = EditorState.create({ doc: this.#pendingValue ?? this.value(), extensions });\n this.#view = new EditorView({\n parent: this.#elementRef.nativeElement,\n root: this.root(),\n state\n });\n\n if (this.autoFocus()) {\n this.#view.focus();\n }\n\n this.#view.contentDOM.addEventListener('focus', () => {\n this.aoFocus.emit();\n });\n\n this.#view.contentDOM.addEventListener('blur', () => {\n this.onTouched?.();\n this.aoBlur.emit();\n });\n this.#syncAccessState();\n this.setTheme(this.theme());\n this.setPlaceholder(this.placeholder());\n this.setIndentWithTab(this.indentWithTab());\n this.setIndentUnit(this.indentUnit());\n this.setLineWrapping(this.lineWrapping());\n this.setHighlightWhitespace(this.highlightWhitespace());\n this.setLanguage(this.language());\n }\n\n ngOnChanges(changes: SimpleChanges): void {\n if (!this.#view) return;\n if (changes['value']) {\n this.setValue(this.value(), true);\n }\n if (changes['disabled'] || changes['readonly']) {\n this.#syncAccessState();\n }\n if (changes['theme']) {\n this.setTheme(this.theme());\n }\n if (changes['placeholder']) {\n this.setPlaceholder(this.placeholder());\n }\n if (changes['indentWithTab']) {\n this.setIndentWithTab(this.indentWithTab());\n }\n if (changes['indentUnit']) {\n this.setIndentUnit(this.indentUnit());\n }\n if (changes['lineWrapping']) {\n this.setLineWrapping(this.lineWrapping());\n }\n if (changes['highlightWhitespace']) {\n this.setHighlightWhitespace(this.highlightWhitespace());\n }\n if (changes['language'] || changes['languages']) {\n this.setLanguage(this.language());\n }\n if (changes['setup']) {\n this.setExtensions(this._get_all_extensions());\n }\n }\n\n ngOnDestroy(): void {\n this.#languageRequest += 1;\n const view = this.#view;\n this.#view = undefined;\n view?.destroy();\n }\n\n public writeValue(value: string | null): void {\n const normalized = value ?? '';\n if (!this.#view) {\n this.#pendingValue = normalized;\n return;\n }\n this.setValue(normalized, true);\n }\n\n public registerOnChange(fn: (value: string) => void): void {\n this.onChange = fn;\n }\n\n public registerOnTouched(fn: () => void): void {\n this.onTouched = fn;\n }\n\n public setDisabledState(isDisabled: boolean): void {\n this.useDisabled.set(isDisabled);\n this.#syncAccessState();\n }\n\n setExtensions(value: Extension[]) {\n this._dispatch_effects(StateEffect.reconfigure.of(value));\n }\n\n setValue(value: string, external = false) {\n const view = this.#view;\n if (!view || view.state.doc.toString() === value) return;\n view.dispatch({\n changes: { from: 0, to: view.state.doc.length, insert: value },\n annotations: external ? External.of(true) : undefined\n });\n }\n\n setEditable(value: boolean) {\n this._dispatch_effects(this.#editableConf.reconfigure(EditorView.editable.of(value)));\n }\n\n setReadonly(value: boolean) {\n this._dispatch_effects(this.#readonlyConf.reconfigure(EditorState.readOnly.of(value)));\n }\n\n setTheme(value: CodeEditorTheme) {\n this._dispatch_effects(this.#themeConf.reconfigure(value === 'dark' ? oneDark : []));\n }\n\n setPlaceholder(value: string) {\n this._dispatch_effects(this.#placeholderConf.reconfigure(value ? placeholder(value) : []));\n }\n\n setIndentWithTab(value: boolean) {\n this._dispatch_effects(this.#indentWithTabConf.reconfigure(value ? keymap.of([indentWithTab]) : []));\n }\n\n setIndentUnit(value: string) {\n this._dispatch_effects(this.#indentUnitConf.reconfigure(value ? indentUnit.of(value) : []));\n }\n\n setLineWrapping(value: boolean) {\n this._dispatch_effects(this.#lineWrappingConf.reconfigure(value ? EditorView.lineWrapping : []));\n }\n\n setHighlightWhitespace(value: boolean) {\n this._dispatch_effects(this.#highlightWhitespaceConf.reconfigure(value ? highlightWhitespace() : []));\n }\n\n setLanguage(lang: string) {\n const request = ++this.#languageRequest;\n const view = this.#view;\n if (!view) return;\n const languages = this.languages();\n if (!lang || lang === 'plaintext' || languages.length === 0) {\n view.dispatch({ effects: this.#languageConf.reconfigure([]) });\n return;\n }\n const langDesc = this._find_language(lang);\n if (!langDesc) {\n view.dispatch({ effects: this.#languageConf.reconfigure([]) });\n return;\n }\n void langDesc.load().then(\n languageSupport => {\n if (request !== this.#languageRequest || this.#view !== view) return;\n view.dispatch({ effects: this.#languageConf.reconfigure(languageSupport.extension as Extension) });\n },\n error => {\n if (request !== this.#languageRequest || this.#view !== view) return;\n console.error(`[code-editor-angular] Failed to load language \"${lang}\":`, error);\n view.dispatch({ effects: this.#languageConf.reconfigure([]) });\n }\n );\n }\n\n protected _get_all_extensions() {\n const extensions: Extension[] = [\n this.#updateListener,\n this.#editableConf.of([]),\n this.#readonlyConf.of([]),\n this.#themeConf.of([]),\n this.#placeholderConf.of([]),\n this.#indentWithTabConf.of([]),\n this.#indentUnitConf.of([]),\n this.#lineWrappingConf.of([]),\n this.#highlightWhitespaceConf.of([]),\n this.#languageConf.of([])\n ];\n const setup = this.setup();\n if (setup === 'basic') {\n extensions.push(basicSetup);\n } else if (setup === 'minimal') {\n extensions.push(minimalSetup);\n }\n return extensions;\n }\n\n protected _find_language(name: string): CodeEditorLanguageDescription | null {\n const languages = this.languages();\n const language = findLanguageByName(name, languages);\n if (language) {\n return language;\n }\n console.error(`Language \"${name}\" not found. Supported: ${languages.map(lang => lang.name).join(', ')}`);\n return null;\n }\n\n protected _dispatch_effects(effects: StateEffect<unknown> | readonly StateEffect<unknown>[]) {\n return this.#view?.dispatch({ effects });\n }\n\n #syncAccessState() {\n const disabled = this.useDisabled();\n const readonly = this.readonly();\n this.setReadonly(readonly || disabled);\n this.setEditable(!readonly && !disabled);\n }\n}\n","/**\n * @aiao/code-editor-angular\n *\n * Angular CodeMirror 代码编辑器组件\n *\n * @example\n * ```ts\n * import { CodeEditor } from '@aiao/code-editor-angular';\n * ```\n */\nexport * from './code-editor';\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"aiao-code-editor-angular.mjs","sources":["../../../../packages/code-editor-angular/src/code-editor.ts","../../../../packages/code-editor-angular/src/index.ts","../../../../packages/code-editor-angular/src/aiao-code-editor-angular.ts"],"sourcesContent":["/**\n * @fileoverview `@aiao/code-editor-angular` —— 基于 CodeMirror 6 的 Angular 组件实现。\n *\n * 与 React / Vue 同名同语义:三端共用 `@aiao/code-editor` 的语言解析与无障碍工具,\n * 这里只承担 Angular 生命周期、`signal` 输入与 `ControlValueAccessor` 表单集成。\n *\n * @module @aiao/code-editor-angular\n */\nimport type {\n CodeEditorLanguageDescription,\n CodeEditorLanguageError,\n CodeEditorTheme,\n ResolvedCodeEditorLanguage\n} from '@aiao/code-editor';\nimport {\n buildCodeEditorContentAttributes,\n codeEditorLanguageLoadFailed,\n codeEditorLanguageNotFound,\n computeMinimalDocumentChange,\n isSameResolvedLanguage,\n resolveCodeEditorLanguage,\n shouldAutoFocusCodeEditor,\n SUPPORT_LANGUAGES\n} from '@aiao/code-editor';\nimport { isPlatformBrowser } from '@angular/common';\nimport {\n booleanAttribute,\n ChangeDetectionStrategy,\n Component,\n computed,\n ElementRef,\n forwardRef,\n inject,\n input,\n OnChanges,\n OnDestroy,\n OnInit,\n output,\n PLATFORM_ID,\n signal,\n SimpleChanges\n} from '@angular/core';\nimport { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';\nimport { indentWithTab } from '@codemirror/commands';\nimport { indentUnit } from '@codemirror/language';\nimport { Annotation, Compartment, EditorState, Extension, StateEffect, Transaction } from '@codemirror/state';\nimport { oneDark } from '@codemirror/theme-one-dark';\nimport { EditorView, highlightWhitespace, keymap, placeholder } from '@codemirror/view';\nimport { basicSetup, minimalSetup } from 'codemirror';\n\nexport type { CodeEditorTheme } from '@aiao/code-editor';\n\n/** CodeMirror 预设配置。 */\nexport type CodeEditorSetup = 'basic' | 'minimal' | null;\n\n/**\n * 标记「外部写入」的注解,用于让 updateListener 跳过 onChange 回调。\n *\n * @internal\n * 模块私有,不对外导出:每个框架绑定各有一个互不相同的 Annotation 实例,\n * 一旦暴露就等于承诺了一个三端无法对齐的语义(React / Vue 两端同样是模块私有)。\n */\nconst External = Annotation.define<boolean>();\n\n/**\n * CodeEditor Angular 组件\n *\n * 基于 CodeMirror 6 的代码编辑器 Angular 组件,支持多种语言和主题\n *\n * @example\n * ```html\n * <ao-code-editor\n * [(ngModel)]=\"code\"\n * language=\"typescript\"\n * theme=\"dark\"\n * (aoChange)=\"onCodeChange($event)\"\n * />\n * ```\n */\n@Component({\n selector: 'ao-code-editor',\n template: ``,\n styleUrl: './code-editor.scss',\n changeDetection: ChangeDetectionStrategy.OnPush,\n // 宿主上的 `aria-disabled` 与 React / Vue 同名同值。但承担 `role=\"textbox\"` 的是\n // CodeMirror 的 `.cm-content`,那一侧另走 `#a11yConf`,见 {@link CodeEditor.#syncContentAttributes}。\n host: {\n '[attr.aria-disabled]': '_ariaDisabled()'\n },\n providers: [\n {\n provide: NG_VALUE_ACCESSOR,\n useExisting: forwardRef(() => CodeEditor),\n multi: true\n }\n ]\n})\nexport class CodeEditor implements OnInit, ControlValueAccessor, OnDestroy, OnChanges {\n /**\n * EditorView 实例。\n * https://codemirror.net/docs/ref/#view.EditorView\n */\n #view?: EditorView;\n #pendingValue?: string;\n #languageRequest = 0;\n /** 最近一次**已生效**的语言解析结果,用于跳过等价的重复配置(CEA-006)。 */\n #appliedLanguage?: ResolvedCodeEditorLanguage;\n\n #elementRef = inject<ElementRef<HTMLElement>>(ElementRef);\n #platformId = inject(PLATFORM_ID);\n #isBrowser = isPlatformBrowser(this.#platformId);\n\n // 用于动态重新配置的 Compartments。\n // https://codemirror.net/docs/ref/#state.Compartment\n #editableConf = new Compartment();\n #readonlyConf = new Compartment();\n #themeConf = new Compartment();\n #placeholderConf = new Compartment();\n #indentWithTabConf = new Compartment();\n #indentUnitConf = new Compartment();\n #lineWrappingConf = new Compartment();\n #highlightWhitespaceConf = new Compartment();\n #languageConf = new Compartment();\n #a11yConf = new Compartment();\n /** `setup` 预设的专属槽 —— 与 React(`CodeEditor.tsx`)、Vue(`CodeEditor.vue`)同构。 */\n #setupConf = new Compartment();\n /** 消费者经 {@link CodeEditor.setExtensions} 装入的扩展的专属槽,见 CEA-003。 */\n #userExtensionsConf = new Compartment();\n\n // 内容变化监听器。\n #updateListener = EditorView.updateListener.of(vu => {\n if (vu.docChanged && !vu.transactions.some(tr => tr.annotation(External))) {\n const value = vu.state.doc.toString();\n this.#onChange?.(value);\n this.aoChange.emit(value);\n }\n });\n\n // 焦点事件必须随初始 extensions 一起装配,不能在 EditorView 构造完再 addEventListener:\n // `autoFocus` 的 `view.focus()` 紧跟构造同步派发 focus,后注册的监听器永远吞掉第一次。\n // 用 domEventObservers 还顺带解决了裸监听器没有 removeEventListener 的问题 ——\n // 它随 view.destroy() 一起回收。与 Vue(`CodeEditor.vue:75-78`)同构。\n #domEventObservers = EditorView.domEventObservers({\n focus: () => {\n this.aoFocus.emit();\n },\n blur: () => {\n this.#onTouched?.();\n this.aoBlur.emit();\n }\n });\n\n /**\n * 表单侧的禁用状态,只由 {@link CodeEditor.setDisabledState} 写入。\n *\n * @internal\n * 必须私有:它只在 `#effectiveDisabled` 里被消费,外部 `.set()` 写了也不会同步到编辑器。\n */\n readonly #formDisabled = signal(false);\n\n /**\n * 有效禁用状态 = `disabled` 输入与表单禁用**任一为真**。\n *\n * @internal\n * CEA-004:两条来源早先被压进同一个 `linkedSignal(() => this.disabled())`,\n * 成了「最后写的赢」。它们是**独立的事实**:模板的 `[disabled]=\"true\"` 说\n * 「这个控件永远禁用」,`setDisabledState()` 说「这个 FormControl 当前 disable 了」。\n * 压成一个值有两个可复现的后果 —— Reactive Forms 在控件 enabled 时会主动调\n * `setDisabledState(false)`,把 `[disabled]=\"true\"` 静默抹掉;反过来 `disabled`\n * 输入一变化 `linkedSignal` 就重算,把表单侧刚设的禁用丢掉。\n */\n readonly #effectiveDisabled = computed(() => this.disabled() || this.#formDisabled());\n\n /** @internal 只能经 {@link registerOnChange} 注入,公开会让消费者覆盖后静默切断表单双向绑定。 */\n #onChange?: (value: string) => void;\n /** @internal 只能经 {@link registerOnTouched} 注入,理由同 {@link registerOnChange}。 */\n #onTouched?: () => void;\n\n /**\n * 宿主元素上的 `aria-disabled`。\n *\n * @internal\n * 未禁用时返回 `null` 让 Angular **移除**属性,而不是写 `aria-disabled=\"false\"`;\n * 理由与 {@link buildCodeEditorContentAttributes} 一致。`protected` 是可见性下限 ——\n * host 绑定表达式读不到 `#` 私有字段。\n */\n protected readonly _ariaDisabled = computed(() => (this.#effectiveDisabled() ? 'true' : null));\n\n // 输入属性。\n /** 初始化后立即聚焦编辑器。**仅初始化生效**,后续变更被忽略。 @defaultValue false */\n autoFocus = input(false, { transform: booleanAttribute });\n /**\n * 补充说明元素的 id,写成内部 textbox 的 `aria-describedby`。可动态更新。\n *\n * @defaultValue ''(空串按「未设置」处理,不产出属性)\n * @remarks 属性落在 CodeMirror 的 `.cm-content` 上 —— 承担 `role=\"textbox\"` 的是它,\n * 不是本组件的宿主元素。三端同名同义。\n */\n describedBy = input<string>('');\n /**\n * 禁用编辑(只读且不可聚焦)。可动态更新。\n *\n * @defaultValue false\n * @remarks 与表单的 `setDisabledState()` 是**两条独立来源**,任一为真即禁用。\n * 用 Reactive Forms 时把控件设成 `disabled` 与在模板上写 `[disabled]=\"true\"` 效果一样,\n * 但只解开其中一条不会恢复可编辑 —— 两条都得放开。\n */\n disabled = input(false, { transform: booleanAttribute });\n /** 显示空白字符。可动态更新。 @defaultValue false */\n highlightWhitespace = input(false, { transform: booleanAttribute });\n /** 一级缩进使用的字符串。可动态更新。 @defaultValue ' '(两个空格,三端一致) */\n indentUnit = input<string>(' ');\n /** 允许 Tab 键缩进(会牺牲键盘可达性)。可动态更新。 @defaultValue false */\n indentWithTab = input(false, { transform: booleanAttribute });\n /** 语法高亮语言名,取值见 {@link languages};`'plaintext'` 表示不高亮。可动态更新。 @defaultValue 'sql' */\n language = input<string>('sql');\n /** 可选语言列表。可动态更新。 @defaultValue SUPPORT_LANGUAGES */\n languages = input<readonly CodeEditorLanguageDescription[]>(SUPPORT_LANGUAGES);\n /**\n * 可访问名称,写成内部 textbox 的 `aria-label`。可动态更新。\n *\n * @defaultValue ''(空串按「未设置」处理,不产出属性)\n * @remarks 落点与 {@link CodeEditor.describedBy} 相同。\n */\n label = input<string>('');\n /**\n * 承担标签的元素 id,写成内部 textbox 的 `aria-labelledby`。可动态更新。\n *\n * @defaultValue ''(空串按「未设置」处理,不产出属性)\n * @remarks 落点与 {@link CodeEditor.describedBy} 相同。\n */\n labelledBy = input<string>('');\n /** 自动换行。可动态更新。 @defaultValue false */\n lineWrapping = input(false, { transform: booleanAttribute });\n /** 空文档时显示的占位文本。可动态更新。 @defaultValue '' */\n placeholder = input<string>('');\n /** 只读(仍可聚焦与选择)。可动态更新。 @defaultValue false */\n readonly = input(false, { transform: booleanAttribute });\n /** CodeMirror 挂载的 DOM 根,用于 Shadow DOM 场景。**仅初始化生效**。 */\n root = input<Document | ShadowRoot>();\n /** 预设扩展集;`null` 表示不加载任何预设。可动态更新。 @defaultValue 'basic' */\n setup = input<CodeEditorSetup>('basic');\n /** 主题。可动态更新。 @defaultValue 'light' */\n theme = input<CodeEditorTheme>('light');\n /** 文档内容。作为受控输入使用时由外部驱动;与 `ngModel` 同时使用以 `ngModel` 为准。 @defaultValue '' */\n value = input<string>('');\n\n // 输出事件。\n /** 编辑器获得焦点。 */\n aoFocus = output<void>();\n /** 文档内容变化(外部写入不触发,见 {@link External})。 */\n aoChange = output<string>();\n /** 编辑器失去焦点,同时触发表单 touched。 */\n aoBlur = output<void>();\n /**\n * 语言解析或加载失败。\n *\n * @remarks\n * CEA-007:这条路径此前只有一条 `console.error`,宿主完全观测不到 ——\n * 拿不到错误、没有重试或降级的机会,用户只看到高亮突然消失。\n * 降级策略不变(清空语言扩展退回纯文本),日志也保留,本事件是**新增的**观测通道。\n * 与 React 的 `onLanguageError` 属性、Vue 的 `language-error` 事件同载荷。\n */\n aoLanguageError = output<CodeEditorLanguageError>();\n\n /**\n * 底层 CodeMirror `EditorView`;未初始化或已销毁时为 `null`。\n *\n * @remarks 三端命令式面统一为 `view` / `host` / `focus()` / `blur()`(CEA-009)。\n */\n get view(): EditorView | null {\n return this.#view ?? null;\n }\n\n /**\n * CodeMirror 挂载所在的宿主元素;未初始化或已销毁时为 `null`。\n *\n * @remarks\n * Angular 的宿主是 `<ao-code-editor>` 元素本身,React / Vue 是组件渲染出的 `div`——\n * 类型不同但语义一致:「CodeMirror 挂在哪个元素里」。\n */\n get host(): HTMLElement | null {\n return this.#view ? this.#elementRef.nativeElement : null;\n }\n\n /** 把键盘焦点交给编辑器。未初始化时是空操作。 */\n focus(): void {\n this.#view?.focus();\n }\n\n /** 移开键盘焦点。未初始化时是空操作。 */\n blur(): void {\n this.#view?.contentDOM.blur();\n }\n\n ngOnInit() {\n if (!this.#isBrowser) return;\n const extensions: Extension[] = this._get_all_extensions();\n const state = EditorState.create({ doc: this.#pendingValue ?? this.value(), extensions });\n this.#view = new EditorView({\n parent: this.#elementRef.nativeElement,\n root: this.root(),\n state\n });\n\n // 访问状态必须先落地:`autoFocus` 要读**已生效**的禁用 / 只读语义,而 `focus()`\n // 又会同步派发 focus 事件,晚同步等于让监听者先读到一个还没对齐的状态。\n this.#syncAccessState();\n // CEA-008(用户可见的行为修正):`disabled` / `readonly` 都会把编辑器配成不可编辑,\n // 此时仍然抢焦点,用户只会得到一个无法输入、也无从得知为什么无法输入的控件。\n if (\n this.autoFocus() &&\n shouldAutoFocusCodeEditor({ disabled: this.#effectiveDisabled(), readonly: this.readonly() })\n ) {\n this.#view.focus();\n }\n this._dispatch_effects(this.#themeConf.reconfigure(this.theme() === 'dark' ? oneDark : []));\n this._dispatch_effects(\n this.#placeholderConf.reconfigure(this.placeholder() ? placeholder(this.placeholder()) : [])\n );\n this._dispatch_effects(this.#indentWithTabConf.reconfigure(this.indentWithTab() ? keymap.of([indentWithTab]) : []));\n this._dispatch_effects(this.#indentUnitConf.reconfigure(this.indentUnit() ? indentUnit.of(this.indentUnit()) : []));\n this._dispatch_effects(this.#lineWrappingConf.reconfigure(this.lineWrapping() ? EditorView.lineWrapping : []));\n this._dispatch_effects(\n this.#highlightWhitespaceConf.reconfigure(this.highlightWhitespace() ? highlightWhitespace() : [])\n );\n this.setLanguage(this.language());\n }\n\n ngOnChanges(changes: SimpleChanges): void {\n if (!this.#view) return;\n if (changes['value'] && this.#view) {\n const newValue = this.value();\n const change = computeMinimalDocumentChange(this.#view.state.doc.toString(), newValue);\n if (change) {\n this.#view.dispatch({\n changes: change,\n annotations: [External.of(true), Transaction.addToHistory.of(false)],\n scrollIntoView: false\n });\n }\n }\n if (changes['disabled'] || changes['readonly']) {\n this.#syncAccessState();\n }\n if (changes['describedBy'] || changes['label'] || changes['labelledBy']) {\n this.#syncContentAttributes();\n }\n if (changes['theme']) {\n this._dispatch_effects(this.#themeConf.reconfigure(this.theme() === 'dark' ? oneDark : []));\n }\n if (changes['placeholder']) {\n this._dispatch_effects(\n this.#placeholderConf.reconfigure(this.placeholder() ? placeholder(this.placeholder()) : [])\n );\n }\n if (changes['indentWithTab']) {\n this._dispatch_effects(\n this.#indentWithTabConf.reconfigure(this.indentWithTab() ? keymap.of([indentWithTab]) : [])\n );\n }\n if (changes['indentUnit']) {\n this._dispatch_effects(\n this.#indentUnitConf.reconfigure(this.indentUnit() ? indentUnit.of(this.indentUnit()) : [])\n );\n }\n if (changes['lineWrapping']) {\n this._dispatch_effects(this.#lineWrappingConf.reconfigure(this.lineWrapping() ? EditorView.lineWrapping : []));\n }\n if (changes['highlightWhitespace']) {\n this._dispatch_effects(\n this.#highlightWhitespaceConf.reconfigure(this.highlightWhitespace() ? highlightWhitespace() : [])\n );\n }\n if (changes['language'] || changes['languages']) {\n this.#syncLanguage();\n }\n if (changes['setup']) {\n // CEA-003:早先这里调的是 `setExtensions(this._get_all_extensions())`,\n // 即换个预设就把整棵扩展树连同消费者自己装的扩展一起重建。\n this._dispatch_effects(this.#setupConf.reconfigure(this.#setupExtension()));\n }\n }\n\n ngOnDestroy(): void {\n this.#languageRequest += 1;\n const view = this.#view;\n this.#view = undefined;\n view?.destroy();\n }\n\n /**\n * `ControlValueAccessor` 的写入入口,由 Angular Forms 调用。\n *\n * @param value `null` / `undefined` 规整成空串;`string` 原样写入\n * @throws TypeError 收到其他类型时立即抛出,错误信息含包名与实际类型\n *\n * @remarks\n * CEA-005:签名此前写成 `string | null`,但 CVA 的契约是 `any` —— Angular\n * **不做**运行时校验,`FormControl` 里放什么就原样送到这里。类型标注只骗过了编译期。\n * 于是 `FormControl(42)` 在初始化前进 `#pendingValue`、在 `EditorState.create` 处炸,\n * 初始化后又在 `computeMinimalDocumentChange` 处炸 —— 同一个错误配置,两个时机两种错,\n * 且都指不回 `writeValue`。现在两个时机抛同一个错,且直接点名包与类型。\n */\n public writeValue(value: unknown): void {\n const normalized = this.#normalizeFormValue(value);\n if (!this.#view) {\n this.#pendingValue = normalized;\n return;\n }\n const change = computeMinimalDocumentChange(this.#view.state.doc.toString(), normalized);\n if (change) {\n this.#view.dispatch({\n changes: change,\n annotations: [External.of(true), Transaction.addToHistory.of(false)],\n scrollIntoView: false\n });\n }\n }\n\n public registerOnChange(fn: (value: string) => void): void {\n this.#onChange = fn;\n }\n\n public registerOnTouched(fn: () => void): void {\n this.#onTouched = fn;\n }\n\n /**\n * 表单侧的禁用开关,由 Angular Forms 调用。\n *\n * @remarks 只写 {@link CodeEditor.#formDisabled} 这一条来源;模板上的\n * `[disabled]` 输入是另一条独立来源,两者取或,见 {@link CodeEditor.#effectiveDisabled}。\n */\n public setDisabledState(isDisabled: boolean): void {\n this.#formDisabled.set(isDisabled);\n this.#syncAccessState();\n }\n\n /**\n * 装入消费者自己的 CodeMirror 扩展。\n *\n * @param value 本次要生效的扩展;**整体替换**上一次传入的那批,传 `[]` 即全部撤下\n *\n * @remarks\n * CEA-003:早先这里发的是全局 `StateEffect.reconfigure.of(value)`,\n * 它替换的是**整棵扩展树** —— 一次调用就清掉 updateListener、全部 Compartment、\n * 主题、语言与 a11y 属性,名字叫「设置扩展」,实际是「摧毁编辑器配置」。\n * 现在只重配 {@link CodeEditor.#userExtensionsConf} 这一个专属槽,\n * 组件自身的配置与消费者的扩展互不影响。\n */\n setExtensions(value: Extension[]) {\n this._dispatch_effects(this.#userExtensionsConf.reconfigure(value));\n }\n\n /**\n * 命令式地(重新)应用语言配置。\n *\n * @param lang 语言名或别名;空串与 `'plaintext'` 表示不高亮\n *\n * @remarks\n * **无条件重新加载**,即使解析结果与当前生效的完全一致 —— 这是宿主换掉了同名描述\n * 背后的 loader 实现时的显式重载入口,`language` 输入做不到(它走的是\n * {@link CodeEditor.#syncLanguage} 里的等价性判断,CEA-006,不会重复加载)。\n * 日常切换语言应当改 `language` 输入。\n *\n * 解析失败时清空语言扩展退回纯文本,并经 {@link CodeEditor.aoLanguageError} 报告。\n */\n setLanguage(lang: string) {\n const request = ++this.#languageRequest;\n const view = this.#view;\n if (!view) return;\n const resolved = resolveCodeEditorLanguage(lang, this.languages());\n this.#appliedLanguage = resolved;\n if (resolved.kind === 'none') {\n view.dispatch({ effects: this.#languageConf.reconfigure([]) });\n return;\n }\n if (resolved.kind === 'not-found') {\n view.dispatch({ effects: this.#languageConf.reconfigure([]) });\n // 日志与另两端逐字一致,作为最后兜底诊断保留;结构化事件才是给宿主的契约通道。\n console.error(`[CodeEditor] Language '${lang}' not found.`);\n this.aoLanguageError.emit(codeEditorLanguageNotFound(lang));\n return;\n }\n // 直接用解析结果里的 description:早先这里又调一次 `_find_language(lang)` 重查一遍,\n // 同一个名字解析两遍,两处判定一旦分叉就会出现「解析说找到了、重查说没有」。\n void resolved.description.load().then(\n languageSupport => {\n if (request !== this.#languageRequest || this.#view !== view) return;\n view.dispatch({ effects: this.#languageConf.reconfigure(languageSupport.extension as Extension) });\n },\n (error: unknown) => {\n if (request !== this.#languageRequest || this.#view !== view) return;\n view.dispatch({ effects: this.#languageConf.reconfigure([]) });\n console.error(`[CodeEditor] Failed to load language '${lang}':`, error);\n this.aoLanguageError.emit(codeEditorLanguageLoadFailed(lang, error));\n }\n );\n }\n\n protected _get_all_extensions() {\n const extensions: Extension[] = [\n this.#updateListener,\n this.#domEventObservers,\n this.#editableConf.of([]),\n this.#readonlyConf.of([]),\n this.#themeConf.of([]),\n this.#placeholderConf.of([]),\n this.#indentWithTabConf.of([]),\n this.#indentUnitConf.of([]),\n this.#lineWrappingConf.of([]),\n this.#highlightWhitespaceConf.of([]),\n this.#languageConf.of([]),\n this.#a11yConf.of([]),\n this.#setupConf.of(this.#setupExtension()),\n // 消费者扩展排在最后 —— CodeMirror 里后来的优先级更高,\n // 宿主装的 keymap / 主题覆盖才压得住预设。\n this.#userExtensionsConf.of([])\n ];\n return extensions;\n }\n\n protected _dispatch_effects(effects: StateEffect<unknown> | readonly StateEffect<unknown>[]) {\n return this.#view?.dispatch({ effects });\n }\n\n /**\n * `language` / `languages` 输入变化后的语言同步。\n *\n * CEA-006:早先只要 `languages` 的**数组引用**变了就无条件 `setLanguage()`。\n * 父模板写 `[languages]=\"[myLang]\"` 这类内联字面量时每轮变更检测都产生新数组,\n * 于是每轮都重新 `load()` 并 reconfigure 语言 Compartment ——\n * 大文档等于每次输入都整篇重新词法分析,自定义异步 loader 还会多发请求。\n * 判定基准改成「当前语言实际解析到的 description identity」,与 React / Vue 同构。\n */\n #syncLanguage() {\n const language = this.language();\n const resolved = resolveCodeEditorLanguage(language, this.languages());\n if (isSameResolvedLanguage(this.#appliedLanguage, resolved)) return;\n this.setLanguage(language);\n }\n\n /**\n * 把无障碍属性写到真正承担 `role=\"textbox\"` 的 `.cm-content` 上。\n *\n * CEA-008 / CER-006:只在宿主 `div` 上补 `aria-*` 是把缺陷换个位置 —— 屏幕阅读器\n * 进到 textbox 之后读到的仍然是一个没有可访问名称、只报 `aria-readonly` 的多行文本框。\n * 属性字典由核心包计算,三端共用同一份实现。\n */\n #syncContentAttributes() {\n this._dispatch_effects(this.#contentAttributesEffect(this.#effectiveDisabled()));\n }\n\n #contentAttributesEffect(disabled: boolean): StateEffect<unknown> {\n const attributes = buildCodeEditorContentAttributes({\n describedBy: this.describedBy(),\n disabled,\n label: this.label(),\n labelledBy: this.labelledBy()\n });\n return this.#a11yConf.reconfigure(EditorView.contentAttributes.of(attributes));\n }\n\n #syncAccessState() {\n const disabled = this.#effectiveDisabled();\n const readonly = this.readonly();\n // CEA-004:三个 Compartment 必须在**同一次** dispatch 里重配。早先是\n // readonly → editable → a11y 三次派发,中间两拍是「只读但仍标记可编辑」\n // 这种自相矛盾的状态,装了 updateListener 的插件全都看得见,还各自重算一遍。\n //\n // 禁用语义也要写给辅助技术:只配 readonly 的话 CodeMirror 只写出 `aria-readonly`,\n // 屏幕阅读器会把禁用控件读成「可编辑但当前只读」,用户以为解锁后就能输入。\n this._dispatch_effects([\n this.#readonlyConf.reconfigure(EditorState.readOnly.of(readonly || disabled)),\n this.#editableConf.reconfigure(EditorView.editable.of(!readonly && !disabled)),\n this.#contentAttributesEffect(disabled)\n ]);\n }\n\n /** 当前 `setup` 预设对应的扩展;`null` 预设为空数组。 */\n #setupExtension(): Extension {\n const setup = this.setup();\n if (setup === 'basic') return basicSetup;\n if (setup === 'minimal') return minimalSetup;\n return [];\n }\n\n /**\n * 把 `ControlValueAccessor` 收到的任意值规整成文档内容,见 {@link CodeEditor.writeValue}。\n *\n * @throws TypeError 值既不是字符串也不是 `null` / `undefined`\n */\n #normalizeFormValue(value: unknown): string {\n if (value === null || value === undefined) return '';\n if (typeof value === 'string') return value;\n const actual = typeof value === 'object' ? (value.constructor?.name ?? 'object') : typeof value;\n throw new TypeError(\n `[@aiao/code-editor-angular] writeValue() 只接受 string | null | undefined,实际收到 ${actual}。` +\n '请检查绑定的 FormControl 初始值或 ngModel 表达式。'\n );\n }\n}\n","/**\n * @fileoverview `@aiao/code-editor-angular` 包入口。\n *\n * 导出 Angular `CodeEditor` 组件、`CodeEditorSetup` 与 `CodeEditorTheme` 类型。\n * 与 React / Vue 绑定同名同语义,三端共用 `@aiao/code-editor` 的语言解析与无障碍工具。\n *\n * @example\n * ```ts\n * import { CodeEditor } from '@aiao/code-editor-angular';\n * ```\n *\n * @module @aiao/code-editor-angular\n */\nexport { CodeEditor } from './code-editor';\nexport type { CodeEditorSetup, CodeEditorTheme } from './code-editor';\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,17 +1,18 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aiao/code-editor-angular",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.24",
|
|
4
|
+
"license": "MIT",
|
|
4
5
|
"dependencies": {
|
|
5
6
|
"@codemirror/commands": "^6.10.4",
|
|
6
7
|
"@codemirror/language": "^6.12.4",
|
|
7
8
|
"@codemirror/state": "6.7.1",
|
|
8
9
|
"@codemirror/theme-one-dark": "^6.1.3",
|
|
9
|
-
"@codemirror/view": "^6.43.
|
|
10
|
+
"@codemirror/view": "^6.43.8",
|
|
10
11
|
"codemirror": "^6.0.1",
|
|
11
12
|
"tslib": "^2.3.0"
|
|
12
13
|
},
|
|
13
14
|
"peerDependencies": {
|
|
14
|
-
"@aiao/code-editor": "
|
|
15
|
+
"@aiao/code-editor": ">=0.0.21",
|
|
15
16
|
"@angular/common": ">=20.0.0",
|
|
16
17
|
"@angular/core": ">=20.0.0",
|
|
17
18
|
"@angular/forms": ">=20.0.0",
|
|
@@ -1,17 +1,13 @@
|
|
|
1
1
|
import * as _angular_core from '@angular/core';
|
|
2
2
|
import { OnInit, OnDestroy, OnChanges, SimpleChanges } from '@angular/core';
|
|
3
|
-
import
|
|
4
|
-
import { Extension, StateEffect } from '@codemirror/state';
|
|
5
|
-
import { CodeEditorLanguageDescription, CodeEditorTheme } from '@aiao/code-editor';
|
|
3
|
+
import { CodeEditorLanguageDescription, CodeEditorTheme, CodeEditorLanguageError } from '@aiao/code-editor';
|
|
6
4
|
export { CodeEditorTheme } from '@aiao/code-editor';
|
|
7
5
|
import { ControlValueAccessor } from '@angular/forms';
|
|
6
|
+
import { Extension, StateEffect } from '@codemirror/state';
|
|
7
|
+
import { EditorView } from '@codemirror/view';
|
|
8
8
|
|
|
9
9
|
/** CodeMirror 预设配置。 */
|
|
10
10
|
type CodeEditorSetup = 'basic' | 'minimal' | null;
|
|
11
|
-
/**
|
|
12
|
-
* Annotation to mark external changes that should not trigger onChange
|
|
13
|
-
*/
|
|
14
|
-
declare const External: _codemirror_state.AnnotationType<boolean>;
|
|
15
11
|
/**
|
|
16
12
|
* CodeEditor Angular 组件
|
|
17
13
|
*
|
|
@@ -29,50 +25,164 @@ declare const External: _codemirror_state.AnnotationType<boolean>;
|
|
|
29
25
|
*/
|
|
30
26
|
declare class CodeEditor implements OnInit, ControlValueAccessor, OnDestroy, OnChanges {
|
|
31
27
|
#private;
|
|
28
|
+
/**
|
|
29
|
+
* 宿主元素上的 `aria-disabled`。
|
|
30
|
+
*
|
|
31
|
+
* @internal
|
|
32
|
+
* 未禁用时返回 `null` 让 Angular **移除**属性,而不是写 `aria-disabled="false"`;
|
|
33
|
+
* 理由与 {@link buildCodeEditorContentAttributes} 一致。`protected` 是可见性下限 ——
|
|
34
|
+
* host 绑定表达式读不到 `#` 私有字段。
|
|
35
|
+
*/
|
|
36
|
+
protected readonly _ariaDisabled: _angular_core.Signal<"true" | null>;
|
|
37
|
+
/** 初始化后立即聚焦编辑器。**仅初始化生效**,后续变更被忽略。 @defaultValue false */
|
|
32
38
|
autoFocus: _angular_core.InputSignalWithTransform<boolean, unknown>;
|
|
39
|
+
/**
|
|
40
|
+
* 补充说明元素的 id,写成内部 textbox 的 `aria-describedby`。可动态更新。
|
|
41
|
+
*
|
|
42
|
+
* @defaultValue ''(空串按「未设置」处理,不产出属性)
|
|
43
|
+
* @remarks 属性落在 CodeMirror 的 `.cm-content` 上 —— 承担 `role="textbox"` 的是它,
|
|
44
|
+
* 不是本组件的宿主元素。三端同名同义。
|
|
45
|
+
*/
|
|
46
|
+
describedBy: _angular_core.InputSignal<string>;
|
|
47
|
+
/**
|
|
48
|
+
* 禁用编辑(只读且不可聚焦)。可动态更新。
|
|
49
|
+
*
|
|
50
|
+
* @defaultValue false
|
|
51
|
+
* @remarks 与表单的 `setDisabledState()` 是**两条独立来源**,任一为真即禁用。
|
|
52
|
+
* 用 Reactive Forms 时把控件设成 `disabled` 与在模板上写 `[disabled]="true"` 效果一样,
|
|
53
|
+
* 但只解开其中一条不会恢复可编辑 —— 两条都得放开。
|
|
54
|
+
*/
|
|
33
55
|
disabled: _angular_core.InputSignalWithTransform<boolean, unknown>;
|
|
56
|
+
/** 显示空白字符。可动态更新。 @defaultValue false */
|
|
34
57
|
highlightWhitespace: _angular_core.InputSignalWithTransform<boolean, unknown>;
|
|
58
|
+
/** 一级缩进使用的字符串。可动态更新。 @defaultValue ' '(两个空格,三端一致) */
|
|
35
59
|
indentUnit: _angular_core.InputSignal<string>;
|
|
60
|
+
/** 允许 Tab 键缩进(会牺牲键盘可达性)。可动态更新。 @defaultValue false */
|
|
36
61
|
indentWithTab: _angular_core.InputSignalWithTransform<boolean, unknown>;
|
|
62
|
+
/** 语法高亮语言名,取值见 {@link languages};`'plaintext'` 表示不高亮。可动态更新。 @defaultValue 'sql' */
|
|
37
63
|
language: _angular_core.InputSignal<string>;
|
|
64
|
+
/** 可选语言列表。可动态更新。 @defaultValue SUPPORT_LANGUAGES */
|
|
38
65
|
languages: _angular_core.InputSignal<readonly CodeEditorLanguageDescription[]>;
|
|
66
|
+
/**
|
|
67
|
+
* 可访问名称,写成内部 textbox 的 `aria-label`。可动态更新。
|
|
68
|
+
*
|
|
69
|
+
* @defaultValue ''(空串按「未设置」处理,不产出属性)
|
|
70
|
+
* @remarks 落点与 {@link CodeEditor.describedBy} 相同。
|
|
71
|
+
*/
|
|
72
|
+
label: _angular_core.InputSignal<string>;
|
|
73
|
+
/**
|
|
74
|
+
* 承担标签的元素 id,写成内部 textbox 的 `aria-labelledby`。可动态更新。
|
|
75
|
+
*
|
|
76
|
+
* @defaultValue ''(空串按「未设置」处理,不产出属性)
|
|
77
|
+
* @remarks 落点与 {@link CodeEditor.describedBy} 相同。
|
|
78
|
+
*/
|
|
79
|
+
labelledBy: _angular_core.InputSignal<string>;
|
|
80
|
+
/** 自动换行。可动态更新。 @defaultValue false */
|
|
39
81
|
lineWrapping: _angular_core.InputSignalWithTransform<boolean, unknown>;
|
|
82
|
+
/** 空文档时显示的占位文本。可动态更新。 @defaultValue '' */
|
|
40
83
|
placeholder: _angular_core.InputSignal<string>;
|
|
84
|
+
/** 只读(仍可聚焦与选择)。可动态更新。 @defaultValue false */
|
|
41
85
|
readonly: _angular_core.InputSignalWithTransform<boolean, unknown>;
|
|
86
|
+
/** CodeMirror 挂载的 DOM 根,用于 Shadow DOM 场景。**仅初始化生效**。 */
|
|
42
87
|
root: _angular_core.InputSignal<Document | ShadowRoot | undefined>;
|
|
88
|
+
/** 预设扩展集;`null` 表示不加载任何预设。可动态更新。 @defaultValue 'basic' */
|
|
43
89
|
setup: _angular_core.InputSignal<CodeEditorSetup>;
|
|
90
|
+
/** 主题。可动态更新。 @defaultValue 'light' */
|
|
44
91
|
theme: _angular_core.InputSignal<CodeEditorTheme>;
|
|
92
|
+
/** 文档内容。作为受控输入使用时由外部驱动;与 `ngModel` 同时使用以 `ngModel` 为准。 @defaultValue '' */
|
|
45
93
|
value: _angular_core.InputSignal<string>;
|
|
94
|
+
/** 编辑器获得焦点。 */
|
|
46
95
|
aoFocus: _angular_core.OutputEmitterRef<void>;
|
|
96
|
+
/** 文档内容变化(外部写入不触发,见 {@link External})。 */
|
|
47
97
|
aoChange: _angular_core.OutputEmitterRef<string>;
|
|
98
|
+
/** 编辑器失去焦点,同时触发表单 touched。 */
|
|
48
99
|
aoBlur: _angular_core.OutputEmitterRef<void>;
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
100
|
+
/**
|
|
101
|
+
* 语言解析或加载失败。
|
|
102
|
+
*
|
|
103
|
+
* @remarks
|
|
104
|
+
* CEA-007:这条路径此前只有一条 `console.error`,宿主完全观测不到 ——
|
|
105
|
+
* 拿不到错误、没有重试或降级的机会,用户只看到高亮突然消失。
|
|
106
|
+
* 降级策略不变(清空语言扩展退回纯文本),日志也保留,本事件是**新增的**观测通道。
|
|
107
|
+
* 与 React 的 `onLanguageError` 属性、Vue 的 `language-error` 事件同载荷。
|
|
108
|
+
*/
|
|
109
|
+
aoLanguageError: _angular_core.OutputEmitterRef<CodeEditorLanguageError>;
|
|
110
|
+
/**
|
|
111
|
+
* 底层 CodeMirror `EditorView`;未初始化或已销毁时为 `null`。
|
|
112
|
+
*
|
|
113
|
+
* @remarks 三端命令式面统一为 `view` / `host` / `focus()` / `blur()`(CEA-009)。
|
|
114
|
+
*/
|
|
115
|
+
get view(): EditorView | null;
|
|
116
|
+
/**
|
|
117
|
+
* CodeMirror 挂载所在的宿主元素;未初始化或已销毁时为 `null`。
|
|
118
|
+
*
|
|
119
|
+
* @remarks
|
|
120
|
+
* Angular 的宿主是 `<ao-code-editor>` 元素本身,React / Vue 是组件渲染出的 `div`——
|
|
121
|
+
* 类型不同但语义一致:「CodeMirror 挂在哪个元素里」。
|
|
122
|
+
*/
|
|
123
|
+
get host(): HTMLElement | null;
|
|
124
|
+
/** 把键盘焦点交给编辑器。未初始化时是空操作。 */
|
|
125
|
+
focus(): void;
|
|
126
|
+
/** 移开键盘焦点。未初始化时是空操作。 */
|
|
127
|
+
blur(): void;
|
|
52
128
|
ngOnInit(): void;
|
|
53
129
|
ngOnChanges(changes: SimpleChanges): void;
|
|
54
130
|
ngOnDestroy(): void;
|
|
55
|
-
|
|
131
|
+
/**
|
|
132
|
+
* `ControlValueAccessor` 的写入入口,由 Angular Forms 调用。
|
|
133
|
+
*
|
|
134
|
+
* @param value `null` / `undefined` 规整成空串;`string` 原样写入
|
|
135
|
+
* @throws TypeError 收到其他类型时立即抛出,错误信息含包名与实际类型
|
|
136
|
+
*
|
|
137
|
+
* @remarks
|
|
138
|
+
* CEA-005:签名此前写成 `string | null`,但 CVA 的契约是 `any` —— Angular
|
|
139
|
+
* **不做**运行时校验,`FormControl` 里放什么就原样送到这里。类型标注只骗过了编译期。
|
|
140
|
+
* 于是 `FormControl(42)` 在初始化前进 `#pendingValue`、在 `EditorState.create` 处炸,
|
|
141
|
+
* 初始化后又在 `computeMinimalDocumentChange` 处炸 —— 同一个错误配置,两个时机两种错,
|
|
142
|
+
* 且都指不回 `writeValue`。现在两个时机抛同一个错,且直接点名包与类型。
|
|
143
|
+
*/
|
|
144
|
+
writeValue(value: unknown): void;
|
|
56
145
|
registerOnChange(fn: (value: string) => void): void;
|
|
57
146
|
registerOnTouched(fn: () => void): void;
|
|
147
|
+
/**
|
|
148
|
+
* 表单侧的禁用开关,由 Angular Forms 调用。
|
|
149
|
+
*
|
|
150
|
+
* @remarks 只写 {@link CodeEditor.#formDisabled} 这一条来源;模板上的
|
|
151
|
+
* `[disabled]` 输入是另一条独立来源,两者取或,见 {@link CodeEditor.#effectiveDisabled}。
|
|
152
|
+
*/
|
|
58
153
|
setDisabledState(isDisabled: boolean): void;
|
|
154
|
+
/**
|
|
155
|
+
* 装入消费者自己的 CodeMirror 扩展。
|
|
156
|
+
*
|
|
157
|
+
* @param value 本次要生效的扩展;**整体替换**上一次传入的那批,传 `[]` 即全部撤下
|
|
158
|
+
*
|
|
159
|
+
* @remarks
|
|
160
|
+
* CEA-003:早先这里发的是全局 `StateEffect.reconfigure.of(value)`,
|
|
161
|
+
* 它替换的是**整棵扩展树** —— 一次调用就清掉 updateListener、全部 Compartment、
|
|
162
|
+
* 主题、语言与 a11y 属性,名字叫「设置扩展」,实际是「摧毁编辑器配置」。
|
|
163
|
+
* 现在只重配 {@link CodeEditor.#userExtensionsConf} 这一个专属槽,
|
|
164
|
+
* 组件自身的配置与消费者的扩展互不影响。
|
|
165
|
+
*/
|
|
59
166
|
setExtensions(value: Extension[]): void;
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
167
|
+
/**
|
|
168
|
+
* 命令式地(重新)应用语言配置。
|
|
169
|
+
*
|
|
170
|
+
* @param lang 语言名或别名;空串与 `'plaintext'` 表示不高亮
|
|
171
|
+
*
|
|
172
|
+
* @remarks
|
|
173
|
+
* **无条件重新加载**,即使解析结果与当前生效的完全一致 —— 这是宿主换掉了同名描述
|
|
174
|
+
* 背后的 loader 实现时的显式重载入口,`language` 输入做不到(它走的是
|
|
175
|
+
* {@link CodeEditor.#syncLanguage} 里的等价性判断,CEA-006,不会重复加载)。
|
|
176
|
+
* 日常切换语言应当改 `language` 输入。
|
|
177
|
+
*
|
|
178
|
+
* 解析失败时清空语言扩展退回纯文本,并经 {@link CodeEditor.aoLanguageError} 报告。
|
|
179
|
+
*/
|
|
69
180
|
setLanguage(lang: string): void;
|
|
70
181
|
protected _get_all_extensions(): Extension[];
|
|
71
|
-
protected _find_language(name: string): CodeEditorLanguageDescription | null;
|
|
72
182
|
protected _dispatch_effects(effects: StateEffect<unknown> | readonly StateEffect<unknown>[]): void | undefined;
|
|
73
183
|
static ɵfac: _angular_core.ɵɵFactoryDeclaration<CodeEditor, never>;
|
|
74
|
-
static ɵcmp: _angular_core.ɵɵComponentDeclaration<CodeEditor, "ao-code-editor", never, { "autoFocus": { "alias": "autoFocus"; "required": false; "isSignal": true; }; "disabled": { "alias": "disabled"; "required": false; "isSignal": true; }; "highlightWhitespace": { "alias": "highlightWhitespace"; "required": false; "isSignal": true; }; "indentUnit": { "alias": "indentUnit"; "required": false; "isSignal": true; }; "indentWithTab": { "alias": "indentWithTab"; "required": false; "isSignal": true; }; "language": { "alias": "language"; "required": false; "isSignal": true; }; "languages": { "alias": "languages"; "required": false; "isSignal": true; }; "lineWrapping": { "alias": "lineWrapping"; "required": false; "isSignal": true; }; "placeholder": { "alias": "placeholder"; "required": false; "isSignal": true; }; "readonly": { "alias": "readonly"; "required": false; "isSignal": true; }; "root": { "alias": "root"; "required": false; "isSignal": true; }; "setup": { "alias": "setup"; "required": false; "isSignal": true; }; "theme": { "alias": "theme"; "required": false; "isSignal": true; }; "value": { "alias": "value"; "required": false; "isSignal": true; }; }, { "aoFocus": "aoFocus"; "aoChange": "aoChange"; "aoBlur": "aoBlur"; }, never, never, true, never>;
|
|
184
|
+
static ɵcmp: _angular_core.ɵɵComponentDeclaration<CodeEditor, "ao-code-editor", never, { "autoFocus": { "alias": "autoFocus"; "required": false; "isSignal": true; }; "describedBy": { "alias": "describedBy"; "required": false; "isSignal": true; }; "disabled": { "alias": "disabled"; "required": false; "isSignal": true; }; "highlightWhitespace": { "alias": "highlightWhitespace"; "required": false; "isSignal": true; }; "indentUnit": { "alias": "indentUnit"; "required": false; "isSignal": true; }; "indentWithTab": { "alias": "indentWithTab"; "required": false; "isSignal": true; }; "language": { "alias": "language"; "required": false; "isSignal": true; }; "languages": { "alias": "languages"; "required": false; "isSignal": true; }; "label": { "alias": "label"; "required": false; "isSignal": true; }; "labelledBy": { "alias": "labelledBy"; "required": false; "isSignal": true; }; "lineWrapping": { "alias": "lineWrapping"; "required": false; "isSignal": true; }; "placeholder": { "alias": "placeholder"; "required": false; "isSignal": true; }; "readonly": { "alias": "readonly"; "required": false; "isSignal": true; }; "root": { "alias": "root"; "required": false; "isSignal": true; }; "setup": { "alias": "setup"; "required": false; "isSignal": true; }; "theme": { "alias": "theme"; "required": false; "isSignal": true; }; "value": { "alias": "value"; "required": false; "isSignal": true; }; }, { "aoFocus": "aoFocus"; "aoChange": "aoChange"; "aoBlur": "aoBlur"; "aoLanguageError": "aoLanguageError"; }, never, never, true, never>;
|
|
75
185
|
}
|
|
76
186
|
|
|
77
|
-
export { CodeEditor
|
|
187
|
+
export { CodeEditor };
|
|
78
188
|
export type { CodeEditorSetup };
|