Skip to content

Commit 1fa824a

Browse files
committed
Merge remote-tracking branch 'upstream/main' into chore/sync-with-upstream-0.44.0
2 parents 25b0cdd + 0758531 commit 1fa824a

36 files changed

+975
-607
lines changed

.release-please-manifest.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{
22
"packages/openscd": "0.37.0",
33
"packages/core": "0.1.4",
4-
".": "0.43.0"
4+
".": "0.44.0"
55
}

CHANGELOG.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,18 @@
11
# Changelog
22

3+
## [0.44.0](https://github.com/openscd/open-scd/compare/v0.43.0...v0.44.0) (2025-11-10)
4+
5+
6+
### Features
7+
8+
* API compliant editor ([#1719](https://github.com/openscd/open-scd/issues/1719)) ([e43ee6a](https://github.com/openscd/open-scd/commit/e43ee6a10805d1d09e5a8adb539be7d68a65ab6a))
9+
* Form library ([#1718](https://github.com/openscd/open-scd/issues/1718)) ([396bb13](https://github.com/openscd/open-scd/commit/396bb13d0d5ecf6c3994072f00958587bb9e5fcd))
10+
11+
12+
### Bug Fixes
13+
14+
* update node to 20.x in release workflow ([#1716](https://github.com/openscd/open-scd/issues/1716)) ([115f22a](https://github.com/openscd/open-scd/commit/115f22a6f1f9cfe02a4817ec03811bcc53cd1fea))
15+
316
## [0.43.0](https://github.com/openscd/open-scd/compare/v0.42.0...v0.43.0) (2025-10-27)
417

518

package-lock.json

Lines changed: 92 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/core/.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,4 @@
33
dist/
44
node_modules/
55
doc/
6+
coverage/
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
export type Subscriber<T> = (value: T) => void;
2+
export type Unsubscriber<T> = () => Subscriber<T>;
3+
4+
export class Subject<T> {
5+
private subscribers: Subscriber<T>[] = [];
6+
7+
public next(value: T): void {
8+
this.subscribers.forEach(s => s(value));
9+
}
10+
11+
public subscribe(subscriber: Subscriber<T>): Unsubscriber<T> {
12+
this.subscribers.push(subscriber);
13+
14+
return () => {
15+
this.unsubscribe(subscriber);
16+
return subscriber;
17+
}
18+
}
19+
20+
public unsubscribe(subscriber: Subscriber<T>): void {
21+
const indexToRemove = this.subscribers.findIndex(s => s === subscriber);
22+
if (indexToRemove > -1) {
23+
this.subscribers.splice(indexToRemove, 1);
24+
}
25+
}
26+
}
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
import { Transactor, TransactedCallback, Commit, CommitOptions } from '@openscd/oscd-api/dist/Transactor.js';
2+
import { EditV2 } from '@openscd/oscd-api/dist/editv2.js';
3+
4+
import { Subject } from './subject.js';
5+
import { handleEditV2 } from '../../foundation.js';
6+
7+
export interface OscdCommit<C> extends Commit<C> {
8+
time: number;
9+
}
10+
11+
export class XMLEditor implements Transactor<EditV2> {
12+
public past: OscdCommit<EditV2>[] = [];
13+
public future: OscdCommit<EditV2>[] = [];
14+
15+
private commitSubject = new Subject<OscdCommit<EditV2>>();
16+
private undoSubject = new Subject<OscdCommit<EditV2>>();
17+
private redoSubject = new Subject<OscdCommit<EditV2>>();
18+
19+
get canUndo(): boolean {
20+
return this.past.length > 0;
21+
}
22+
23+
get canRedo(): boolean {
24+
return this.future.length > 0;
25+
}
26+
27+
reset(): void {
28+
this.past = [];
29+
this.future = [];
30+
}
31+
32+
commit(change: EditV2, { title, squash }: CommitOptions = {}): OscdCommit<EditV2> {
33+
const commit: OscdCommit<EditV2> =
34+
squash && this.past.length
35+
? this.past[this.past.length - 1]
36+
: { undo: [], redo: [], time: Date.now() };
37+
// TODO: Fix type once issue is fixed https://github.com/openscd/oscd-api/issues/57
38+
const undo = handleEditV2(change as any);
39+
// typed as per https://github.com/microsoft/TypeScript/issues/49280#issuecomment-1144181818 recommendation:
40+
commit.undo.unshift(...[undo].flat(Infinity as 1));
41+
commit.redo.push(...[change].flat(Infinity as 1));
42+
if (title) commit.title = title;
43+
if (squash && this.past.length) this.past.pop();
44+
this.past.push(commit);
45+
this.future = [];
46+
this.commitSubject.next(commit);
47+
return commit;
48+
};
49+
50+
undo(): OscdCommit<EditV2> | undefined {
51+
const commit = this.past.pop();
52+
if (!commit) return;
53+
// TODO: Fix type once issue is fixed https://github.com/openscd/oscd-api/issues/57
54+
handleEditV2(commit.undo as any);
55+
this.future.unshift(commit);
56+
this.undoSubject.next(commit);
57+
return commit;
58+
};
59+
60+
redo(): OscdCommit<EditV2> | undefined {
61+
const commit = this.future.shift();
62+
if (!commit) return;
63+
// TODO: Fix type once issue is fixed https://github.com/openscd/oscd-api/issues/57
64+
handleEditV2(commit.redo as any);
65+
this.past.push(commit);
66+
this.redoSubject.next(commit);
67+
return commit;
68+
};
69+
70+
subscribe(txCallback: TransactedCallback<EditV2>): () => TransactedCallback<EditV2> {
71+
return this.commitSubject.subscribe(txCallback) as () => TransactedCallback<EditV2>;
72+
};
73+
74+
subscribeUndo(txCallback: TransactedCallback<EditV2>): () => TransactedCallback<EditV2> {
75+
return this.undoSubject.subscribe(txCallback) as () => TransactedCallback<EditV2>;
76+
}
77+
78+
subscribeRedo(txCallback: TransactedCallback<EditV2>): () => TransactedCallback<EditV2> {
79+
return this.redoSubject.subscribe(txCallback) as () => TransactedCallback<EditV2>;
80+
}
81+
82+
subscribeUndoRedo(txCallback: TransactedCallback<EditV2>): () => TransactedCallback<EditV2> {
83+
const unsubscribeUndo = this.subscribeUndo(txCallback);
84+
const unsubscribeRedo = this.subscribeRedo(txCallback);
85+
86+
return () => {
87+
unsubscribeUndo();
88+
unsubscribeRedo();
89+
return txCallback;
90+
}
91+
}
92+
}

packages/core/foundation.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,3 +68,5 @@ export function crossProduct<T>(...arrays: T[][]): T[][] {
6868
}
6969

7070
export { OscdApi } from './api/api.js';
71+
72+
export { XMLEditor } from './api/editor/xml-editor.js';

packages/core/foundation/deprecated/history.ts

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,6 @@ export interface LogDetailBase {
1212
/** The [[`LogEntry`]] for a committed [[`EditorAction`]]. */
1313
export interface CommitDetail extends LogDetailBase {
1414
kind: 'action';
15-
redo: EditV2;
16-
undo: EditV2;
17-
squash?: boolean;
1815
}
1916
/** A [[`LogEntry`]] for notifying the user. */
2017
export interface InfoDetail extends LogDetailBase {

packages/core/package.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,14 +31,16 @@
3131
"clean": "rimraf .tsbuildinfo dist",
3232
"build": "tsc -b",
3333
"doc": "typedoc --out doc foundation.ts",
34+
"test": "web-test-runner --coverage",
3435
"prepublish": "npm run lint && npm run build && npm run doc",
3536
"lint": "eslint --ext .ts,.html . --ignore-path .gitignore && prettier \"**/*.ts\" --check --ignore-path .gitignore",
3637
"format": "eslint --ext .ts,.html . --fix --ignore-path .gitignore && prettier \"**/*.ts\" --write --ignore-path .gitignore"
3738
},
3839
"dependencies": {
3940
"@lit/localize": "^0.11.4",
4041
"@open-wc/lit-helpers": "^0.5.1",
41-
"lit": "^2.2.7"
42+
"lit": "^2.2.7",
43+
"@openscd/oscd-api": "^0.1.5"
4244
},
4345
"devDependencies": {
4446
"@custom-elements-manifest/analyzer": "^0.6.3",
@@ -52,6 +54,7 @@
5254
"@typescript-eslint/parser": "^5.30.7",
5355
"@web/dev-server": "^0.1.32",
5456
"@web/test-runner": "next",
57+
"@web/dev-server-esbuild": "^0.2.16",
5558
"@web/test-runner-playwright": "^0.8.10",
5659
"@web/test-runner-visual-regression": "^0.6.6",
5760
"concurrently": "^7.3.0",

packages/core/test/subject.test.ts

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
import { expect } from '@open-wc/testing';
2+
3+
import { Subject } from '../api/editor/subject.js';
4+
5+
describe('Subject', () => {
6+
let subject: Subject<string>;
7+
8+
let subOneValues: string[];
9+
let subTwoValues: string[];
10+
11+
beforeEach(() => {
12+
subject = new Subject<string>();
13+
14+
subOneValues = [];
15+
subTwoValues = [];
16+
});
17+
18+
it('should call subscribers on next', () => {
19+
const subscriberOne = (v: string) => subOneValues.push(v);
20+
const subscriberTwo = (v: string) => subTwoValues.push(v);
21+
22+
subject.subscribe(subscriberOne);
23+
24+
subject.next('first');
25+
26+
expect(subOneValues).to.deep.equal([ 'first' ]);
27+
expect(subTwoValues).to.deep.equal([]);
28+
29+
subject.subscribe(subscriberTwo);
30+
31+
subject.next('second');
32+
33+
expect(subOneValues).to.deep.equal([ 'first', 'second' ]);
34+
expect(subTwoValues).to.deep.equal([ 'second' ]);
35+
});
36+
37+
it('should remove correct subscriber on unsubscribe', () => {
38+
const subscriberOne = (v: string) => subOneValues.push(v);
39+
const subscriberTwo = (v: string) => subTwoValues.push(v);
40+
41+
const unsubscribeOne = subject.subscribe(subscriberOne);
42+
const unsubscribeTwo = subject.subscribe(subscriberTwo);
43+
44+
subject.next('first');
45+
46+
expect(subOneValues).to.deep.equal([ 'first' ]);
47+
expect(subTwoValues).to.deep.equal([ 'first' ]);
48+
49+
unsubscribeOne();
50+
51+
subject.next('second');
52+
53+
expect(subOneValues).to.deep.equal([ 'first' ]);
54+
expect(subTwoValues).to.deep.equal([ 'first', 'second' ]);
55+
56+
unsubscribeTwo();
57+
58+
subject.next('third');
59+
60+
expect(subOneValues).to.deep.equal([ 'first' ]);
61+
expect(subTwoValues).to.deep.equal([ 'first', 'second' ]);
62+
});
63+
});

0 commit comments

Comments
 (0)