Skip to content

Commit 108a958

Browse files
committed
feat(diff-editor): implement ControlValueAccessor
1 parent 7dcf1e0 commit 108a958

File tree

3 files changed

+91
-21
lines changed

3 files changed

+91
-21
lines changed

projects/code-editor/diff-editor.ts

Lines changed: 76 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,12 @@ import {
1111
SimpleChanges,
1212
ViewEncapsulation,
1313
booleanAttribute,
14+
forwardRef,
1415
} from '@angular/core';
16+
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
1517

1618
import { DiffConfig, MergeView } from '@codemirror/merge';
17-
import { Extension } from '@codemirror/state';
19+
import { Compartment, Extension } from '@codemirror/state';
1820
import { EditorView } from '@codemirror/view';
1921
import { basicSetup, minimalSetup } from 'codemirror';
2022

@@ -24,6 +26,11 @@ export type Orientation = 'a-b' | 'b-a';
2426
export type RevertControls = 'a-to-b' | 'b-to-a';
2527
export type RenderRevertControl = () => HTMLElement;
2628

29+
export interface DiffEditorModel {
30+
original: string;
31+
modified: string;
32+
}
33+
2734
@Component({
2835
selector: 'diff-editor',
2936
standalone: true,
@@ -48,8 +55,15 @@ export type RenderRevertControl = () => HTMLElement;
4855
},
4956
encapsulation: ViewEncapsulation.None,
5057
changeDetection: ChangeDetectionStrategy.OnPush,
58+
providers: [
59+
{
60+
provide: NG_VALUE_ACCESSOR,
61+
useExisting: forwardRef(() => DiffEditor),
62+
multi: true,
63+
},
64+
],
5165
})
52-
export class DiffEditor implements OnChanges, OnInit, OnDestroy {
66+
export class DiffEditor implements OnChanges, OnInit, OnDestroy, ControlValueAccessor {
5367
/**
5468
* The editor's built-in setup. The value can be set to
5569
* [`basic`](https://codemirror.net/docs/ref/#codemirror.basicSetup),
@@ -60,7 +74,7 @@ export class DiffEditor implements OnChanges, OnInit, OnDestroy {
6074
@Input() setup: Setup = 'basic';
6175

6276
/** The diff-editor's original value. */
63-
@Input() originalValue = '';
77+
@Input() originalValue: string = '';
6478

6579
/**
6680
* The MergeView original config's
@@ -71,7 +85,7 @@ export class DiffEditor implements OnChanges, OnInit, OnDestroy {
7185
@Input() originalExtensions: Extension[] = [];
7286

7387
/** The diff-editor's modified value. */
74-
@Input() modifiedValue = '';
88+
@Input() modifiedValue: string = '';
7589

7690
/**
7791
* The MergeView modified config's
@@ -99,6 +113,9 @@ export class DiffEditor implements OnChanges, OnInit, OnDestroy {
99113
/** Controls whether a gutter marker is shown next to changed lines. */
100114
@Input({ transform: booleanAttribute }) gutter = true;
101115

116+
/** Whether the diff-editor is disabled. */
117+
@Input({ transform: booleanAttribute }) disabled = false;
118+
102119
/**
103120
* When given, long stretches of unchanged text are collapsed.
104121
* `margin` gives the number of lines to leave visible after/before
@@ -116,26 +133,39 @@ export class DiffEditor implements OnChanges, OnInit, OnDestroy {
116133
/** Event emitted when the editor's modified value changes. */
117134
@Output() modifiedValueChange = new EventEmitter<string>();
118135

136+
private _onChange: (value: DiffEditorModel) => void = () => {};
137+
private _onTouched: () => void = () => {};
138+
119139
constructor(private _elementRef: ElementRef<Element>) {}
120140

121141
/** The merge view instance. */
122142
mergeView?: MergeView;
123143

124-
private _updateListener = (valueChange: EventEmitter<string>) => {
144+
private _updateListener = (editor: 'a' | 'b') => {
125145
return EditorView.updateListener.of(vu => {
126146
if (vu.docChanged && !vu.transactions.some(tr => tr.annotation(External))) {
127147
const value = vu.state.doc.toString();
128-
valueChange.emit(value);
148+
if (editor == 'a') {
149+
this._onChange({ original: value, modified: this.modifiedValue });
150+
this.originalValue = value;
151+
this.originalValueChange.emit(value);
152+
} else if (editor == 'b') {
153+
this._onChange({ original: this.originalValue, modified: value });
154+
this.modifiedValue = value;
155+
this.modifiedValueChange.emit(value);
156+
}
129157
}
130158
});
131159
};
132160

161+
private _editableConf = new Compartment();
162+
133163
ngOnChanges(changes: SimpleChanges): void {
134164
if (changes['originalValue']) {
135-
this.setOriginalValue(this.originalValue);
165+
this.setValue('a', this.originalValue);
136166
}
137167
if (changes['modifiedValue']) {
138-
this.setModifiedValue(this.modifiedValue);
168+
this.setValue('b', this.modifiedValue);
139169
}
140170
if (changes['orientation']) {
141171
this.mergeView?.reconfigure({ orientation: this.orientation });
@@ -166,15 +196,17 @@ export class DiffEditor implements OnChanges, OnInit, OnDestroy {
166196
a: {
167197
doc: this.originalValue,
168198
extensions: [
169-
this._updateListener(this.originalValueChange),
199+
this._updateListener('a'),
200+
this._editableConf.of([]),
170201
this.setup === 'basic' ? basicSetup : this.setup === 'minimal' ? minimalSetup : [],
171202
...this.originalExtensions,
172203
],
173204
},
174205
b: {
175206
doc: this.modifiedValue,
176207
extensions: [
177-
this._updateListener(this.modifiedValueChange),
208+
this._updateListener('b'),
209+
this._editableConf.of([]),
178210
this.setup === 'basic' ? basicSetup : this.setup === 'minimal' ? minimalSetup : [],
179211
...this.modifiedExtensions,
180212
],
@@ -187,23 +219,49 @@ export class DiffEditor implements OnChanges, OnInit, OnDestroy {
187219
collapseUnchanged: this.collapseUnchanged,
188220
diffConfig: this.diffConfig,
189221
});
222+
223+
this.setEditable('a', !this.disabled);
224+
this.setEditable('b', !this.disabled);
190225
}
191226

192227
ngOnDestroy(): void {
193228
this.mergeView?.destroy();
194229
}
195230

196-
/** Sets diff-editor's original value. */
197-
setOriginalValue(value: string) {
198-
this.mergeView?.a.dispatch({
199-
changes: { from: 0, to: this.mergeView.a.state.doc.length, insert: value },
231+
writeValue(value: DiffEditorModel): void {
232+
if (this.mergeView && value != null && typeof value === 'object') {
233+
this.originalValue = value.original;
234+
this.modifiedValue = value.modified;
235+
this.setValue('a', value.original);
236+
this.setValue('b', value.modified);
237+
}
238+
}
239+
240+
registerOnChange(fn: (value: DiffEditorModel) => void) {
241+
this._onChange = fn;
242+
}
243+
244+
registerOnTouched(fn: () => void) {
245+
this._onTouched = fn;
246+
}
247+
248+
setDisabledState(isDisabled: boolean) {
249+
this.disabled = isDisabled;
250+
this.setEditable('a', !isDisabled);
251+
this.setEditable('b', !isDisabled);
252+
}
253+
254+
/** Sets diff-editor's value. */
255+
setValue(editor: 'a' | 'b', value: string) {
256+
this.mergeView?.[editor].dispatch({
257+
changes: { from: 0, to: this.mergeView[editor].state.doc.length, insert: value },
200258
});
201259
}
202260

203-
/** Sets diff-editor's modified value. */
204-
setModifiedValue(value: string) {
205-
this.mergeView?.b.dispatch({
206-
changes: { from: 0, to: this.mergeView.b.state.doc.length, insert: value },
261+
/** Sets diff-editor's editable state. */
262+
setEditable(editor: 'a' | 'b', value: boolean) {
263+
this.mergeView?.[editor].dispatch({
264+
effects: this._editableConf.reconfigure(EditorView.editable.of(value)),
207265
});
208266
}
209267
}
Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,17 @@
11
<diff-editor #editor [style.height.px]="200"
2-
[originalValue]="doc" [modifiedValue]="doc2"
2+
[(ngModel)]="value"
33
(originalValueChange)="log($event)"
44
(modifiedValueChange)="log($event)"
55
[orientation]="orientation" [revertControls]="revertControls"
6-
[highlightChanges]="highlightChanges" [gutter]="gutter" />
6+
[highlightChanges]="highlightChanges" [gutter]="gutter"
7+
[disabled]="disabled" />
78

89
<button (click)="doc='123'">change original value</button>
910
<button (click)="doc2='13\n456'">change modified value</button>
1011
<button (click)="orientation==='a-b'?orientation='b-a':orientation='a-b'">orientation</button>
1112
<button (click)="revertControls==='a-to-b'?revertControls='b-to-a':revertControls='a-to-b'">revertControls</button>
1213
<button (click)="highlightChanges=!highlightChanges">highlightChanges</button>
1314
<button (click)="gutter=!gutter">gutter</button>
15+
<button (click)="disabled=!disabled">disabled</button>
1416

1517
<code-editor [value]="doc2" [extensions]="unifiedExts" />

projects/dev-app/src/app/diff/diff.component.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
import { CodeEditor, DiffEditor, Orientation, RevertControls, Setup } from '@acrodata/code-editor';
2+
import { JsonPipe } from '@angular/common';
23
import { Component } from '@angular/core';
4+
import { FormControl, FormsModule, ReactiveFormsModule } from '@angular/forms';
35
import { unifiedMergeView } from '@codemirror/merge';
46

57
@Component({
68
selector: 'app-diff',
79
standalone: true,
8-
imports: [DiffEditor, CodeEditor],
10+
imports: [DiffEditor, CodeEditor, FormsModule, ReactiveFormsModule, JsonPipe],
911
templateUrl: './diff.component.html',
1012
styleUrl: './diff.component.scss',
1113
})
@@ -18,6 +20,13 @@ five`;
1820

1921
doc2 = this.doc.replace(/t/g, 'T') + '\nSix';
2022

23+
value = {
24+
original: this.doc,
25+
modified: this.doc2,
26+
};
27+
28+
control = new FormControl({ value: this.value, disabled: true });
29+
2130
unifiedExts = [
2231
unifiedMergeView({
2332
original: this.doc,
@@ -30,6 +39,7 @@ five`;
3039
revertControls: RevertControls = 'a-to-b';
3140
highlightChanges = true;
3241
gutter = true;
42+
disabled = false;
3343

3444
log(e: any) {
3545
console.log(e);

0 commit comments

Comments
 (0)