@dxos/react-ui-editor 0.3.11-main.0e40963 → 0.3.11-main.1c19c60
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/dist/lib/browser/index.mjs +1274 -811
- package/dist/lib/browser/index.mjs.map +4 -4
- package/dist/lib/browser/meta.json +1 -1
- package/dist/types/src/components/TextEditor/TextEditor.stories.d.ts +3 -0
- package/dist/types/src/components/TextEditor/TextEditor.stories.d.ts.map +1 -1
- package/dist/types/src/{hooks/automerge → components/TextEditor}/automerge.stories.d.ts +6 -1
- package/dist/types/src/components/TextEditor/automerge.stories.d.ts.map +1 -0
- package/dist/types/src/components/TextEditor/codemirror.stories.d.ts.map +1 -1
- package/dist/types/src/{hooks/yjs → components/TextEditor}/yjs.stories.d.ts +1 -1
- package/dist/types/src/components/TextEditor/yjs.stories.d.ts.map +1 -0
- package/dist/types/src/extensions/automerge/automerge.d.ts +9 -0
- package/dist/types/src/extensions/automerge/automerge.d.ts.map +1 -0
- package/dist/types/src/extensions/automerge/cursor.d.ts +5 -0
- package/dist/types/src/extensions/automerge/cursor.d.ts.map +1 -0
- package/dist/types/src/extensions/automerge/defs.d.ts +26 -0
- package/dist/types/src/extensions/automerge/defs.d.ts.map +1 -0
- package/dist/types/src/extensions/automerge/index.d.ts +3 -0
- package/dist/types/src/extensions/automerge/index.d.ts.map +1 -0
- package/dist/types/src/{hooks → extensions}/automerge/semaphore.d.ts +4 -4
- package/dist/types/src/extensions/automerge/semaphore.d.ts.map +1 -0
- package/dist/types/src/extensions/automerge/update-automerge.d.ts +5 -0
- package/dist/types/src/extensions/automerge/update-automerge.d.ts.map +1 -0
- package/dist/types/src/extensions/automerge/update-codemirror.d.ts.map +1 -0
- package/dist/types/src/extensions/awareness.d.ts +46 -0
- package/dist/types/src/extensions/awareness.d.ts.map +1 -0
- package/dist/types/src/extensions/comments.d.ts +3 -1
- package/dist/types/src/extensions/comments.d.ts.map +1 -1
- package/dist/types/src/extensions/index.d.ts +3 -0
- package/dist/types/src/extensions/index.d.ts.map +1 -1
- package/dist/types/src/extensions/yjs/cursor.d.ts +4 -0
- package/dist/types/src/extensions/yjs/cursor.d.ts.map +1 -0
- package/dist/types/src/extensions/yjs/index.d.ts +2 -0
- package/dist/types/src/extensions/yjs/index.d.ts.map +1 -0
- package/dist/types/src/extensions/yjs/yjs.d.ts +4 -0
- package/dist/types/src/extensions/yjs/yjs.d.ts.map +1 -0
- package/dist/types/src/extensions/yjs/yjs.test.d.ts.map +1 -0
- package/dist/types/src/hooks/automerge.d.ts +3 -0
- package/dist/types/src/hooks/automerge.d.ts.map +1 -0
- package/dist/types/src/hooks/awareness-provider.d.ts +28 -0
- package/dist/types/src/hooks/awareness-provider.d.ts.map +1 -0
- package/dist/types/src/hooks/index.d.ts +2 -0
- package/dist/types/src/hooks/index.d.ts.map +1 -1
- package/dist/types/src/hooks/useTextModel.d.ts +8 -7
- package/dist/types/src/hooks/useTextModel.d.ts.map +1 -1
- package/dist/types/src/hooks/{yjs/space-provider.d.ts → yjs.d.ts} +8 -8
- package/dist/types/src/hooks/yjs.d.ts.map +1 -0
- package/dist/types/src/styles/cursors.d.ts.map +1 -0
- package/dist/types/src/styles/index.d.ts +1 -0
- package/dist/types/src/styles/index.d.ts.map +1 -1
- package/dist/types/src/testing/replicator.d.ts +2 -2
- package/dist/types/src/testing/replicator.d.ts.map +1 -1
- package/dist/types/src/util/cursor.d.ts +17 -0
- package/dist/types/src/util/cursor.d.ts.map +1 -0
- package/dist/types/src/util/index.d.ts +3 -0
- package/dist/types/src/util/index.d.ts.map +1 -0
- package/dist/types/src/util/util.d.ts.map +1 -0
- package/package.json +22 -21
- package/src/components/TextEditor/TextEditor.stories.tsx +13 -0
- package/src/components/TextEditor/TextEditor.tsx +1 -1
- package/src/{hooks/automerge → components/TextEditor}/automerge.stories.tsx +66 -6
- package/src/components/TextEditor/codemirror.stories.ts +3 -2
- package/src/{hooks/yjs → components/TextEditor}/yjs.stories.tsx +1 -1
- package/src/extensions/automerge/automerge.ts +94 -0
- package/src/extensions/automerge/cursor.ts +51 -0
- package/src/extensions/automerge/defs.ts +62 -0
- package/src/extensions/automerge/index.ts +6 -0
- package/src/{hooks → extensions}/automerge/semaphore.ts +14 -12
- package/src/{hooks → extensions}/automerge/update-automerge.ts +5 -6
- package/src/{hooks → extensions}/automerge/update-codemirror.ts +13 -5
- package/src/extensions/awareness.ts +316 -0
- package/src/extensions/comments.ts +45 -15
- package/src/extensions/index.ts +3 -0
- package/src/extensions/yjs/cursor.ts +21 -0
- package/src/extensions/yjs/index.ts +5 -0
- package/src/{hooks → extensions}/yjs/yjs.test.ts +1 -25
- package/src/extensions/yjs/yjs.ts +16 -0
- package/src/hooks/automerge.ts +52 -0
- package/src/hooks/awareness-provider.ts +120 -0
- package/src/hooks/index.ts +2 -0
- package/src/hooks/useTextModel.ts +19 -93
- package/src/hooks/{yjs/space-provider.ts → yjs.ts} +71 -19
- package/src/styles/index.ts +1 -0
- package/src/testing/replicator.ts +10 -15
- package/src/util/cursor.ts +29 -0
- package/src/util/index.ts +6 -0
- package/dist/types/src/extensions/util.d.ts.map +0 -1
- package/dist/types/src/hooks/automerge/automerge.stories.d.ts.map +0 -1
- package/dist/types/src/hooks/automerge/handle.d.ts +0 -9
- package/dist/types/src/hooks/automerge/handle.d.ts.map +0 -1
- package/dist/types/src/hooks/automerge/index.d.ts +0 -3
- package/dist/types/src/hooks/automerge/index.d.ts.map +0 -1
- package/dist/types/src/hooks/automerge/plugin.d.ts +0 -26
- package/dist/types/src/hooks/automerge/plugin.d.ts.map +0 -1
- package/dist/types/src/hooks/automerge/semaphore.d.ts.map +0 -1
- package/dist/types/src/hooks/automerge/update-automerge.d.ts +0 -6
- package/dist/types/src/hooks/automerge/update-automerge.d.ts.map +0 -1
- package/dist/types/src/hooks/automerge/update-codemirror.d.ts.map +0 -1
- package/dist/types/src/hooks/yjs/cursors.d.ts.map +0 -1
- package/dist/types/src/hooks/yjs/index.d.ts +0 -3
- package/dist/types/src/hooks/yjs/index.d.ts.map +0 -1
- package/dist/types/src/hooks/yjs/space-provider.d.ts.map +0 -1
- package/dist/types/src/hooks/yjs/yjs.stories.d.ts.map +0 -1
- package/dist/types/src/hooks/yjs/yjs.test.d.ts.map +0 -1
- package/src/hooks/automerge/handle.ts +0 -14
- package/src/hooks/automerge/index.ts +0 -6
- package/src/hooks/automerge/plugin.ts +0 -134
- package/src/hooks/yjs/index.ts +0 -6
- /package/dist/types/src/{hooks → extensions}/automerge/update-codemirror.d.ts +0 -0
- /package/dist/types/src/{hooks → extensions}/yjs/yjs.test.d.ts +0 -0
- /package/dist/types/src/{hooks/yjs → styles}/cursors.d.ts +0 -0
- /package/dist/types/src/{extensions → util}/util.d.ts +0 -0
- /package/src/{hooks/yjs → styles}/cursors.ts +0 -0
- /package/src/{extensions → util}/util.ts +0 -0
|
@@ -1,9 +1,8 @@
|
|
|
1
|
-
import { type Extension
|
|
2
|
-
import type * as
|
|
1
|
+
import { StateField, type Extension } from '@codemirror/state';
|
|
2
|
+
import type * as YP from 'y-protocols/awareness';
|
|
3
3
|
import { type DocAccessor, type Space, type TextObject } from '@dxos/react-client/echo';
|
|
4
4
|
import { type Identity } from '@dxos/react-client/halo';
|
|
5
5
|
import type { YText, YXmlFragment } from '@dxos/text-model';
|
|
6
|
-
type Awareness = awarenessProtocol.Awareness;
|
|
7
6
|
/**
|
|
8
7
|
* State field makes the model available to other extensions.
|
|
9
8
|
*/
|
|
@@ -18,12 +17,15 @@ export type CommentRange = {
|
|
|
18
17
|
};
|
|
19
18
|
export type EditorModel = {
|
|
20
19
|
id: string;
|
|
21
|
-
content: string | YText | YXmlFragment | DocAccessor;
|
|
22
20
|
text: () => string;
|
|
21
|
+
extension?: Extension;
|
|
22
|
+
content: string | YText | YXmlFragment | DocAccessor;
|
|
23
|
+
/**
|
|
24
|
+
* @deprecated Use CursorConverter.
|
|
25
|
+
*/
|
|
23
26
|
getCursorFromRange?: (value: Range) => string;
|
|
24
27
|
getRangeFromCursor?: (cursor: string) => Range | undefined;
|
|
25
|
-
|
|
26
|
-
awareness?: Awareness;
|
|
28
|
+
awareness?: YP.Awareness;
|
|
27
29
|
peer?: {
|
|
28
30
|
id: string;
|
|
29
31
|
name?: string;
|
|
@@ -35,5 +37,4 @@ export type UseTextModelProps = {
|
|
|
35
37
|
text?: TextObject;
|
|
36
38
|
};
|
|
37
39
|
export declare const useTextModel: (props: UseTextModelProps) => EditorModel | undefined;
|
|
38
|
-
export {};
|
|
39
40
|
//# sourceMappingURL=useTextModel.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useTextModel.d.ts","sourceRoot":"","sources":["../../../../src/hooks/useTextModel.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,KAAK,SAAS,EAAE,
|
|
1
|
+
{"version":3,"file":"useTextModel.d.ts","sourceRoot":"","sources":["../../../../src/hooks/useTextModel.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,UAAU,EAAE,KAAK,SAAS,EAAE,MAAM,mBAAmB,CAAC;AAE/D,OAAO,KAAK,KAAK,EAAE,MAAM,uBAAuB,CAAC;AAEjD,OAAO,EAAqB,KAAK,WAAW,EAAE,KAAK,KAAK,EAAE,KAAK,UAAU,EAAE,MAAM,yBAAyB,CAAC;AAC3G,OAAO,EAAE,KAAK,QAAQ,EAAE,MAAM,yBAAyB,CAAC;AACxD,OAAO,KAAK,EAAE,KAAK,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AAO5D;;GAEG;AACH,eAAO,MAAM,UAAU,qCAGrB,CAAC;AAEH,MAAM,MAAM,KAAK,GAAG;IAClB,IAAI,EAAE,MAAM,CAAC;IACb,EAAE,EAAE,MAAM,CAAC;CACZ,CAAC;AAEF,MAAM,MAAM,YAAY,GAAG;IACzB,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,EAAE,MAAM,CAAC;CAChB,CAAC;AAGF,MAAM,MAAM,WAAW,GAAG;IACxB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,SAAS,CAAC;IAGtB,OAAO,EAAE,MAAM,GAAG,KAAK,GAAG,YAAY,GAAG,WAAW,CAAC;IACrD;;OAEG;IACH,kBAAkB,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,MAAM,CAAC;IAC9C,kBAAkB,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,KAAK,GAAG,SAAS,CAAC;IAE3D,SAAS,CAAC,EAAE,EAAE,CAAC,SAAS,CAAC;IAEzB,IAAI,CAAC,EAAE;QACL,EAAE,EAAE,MAAM,CAAC;QACX,IAAI,CAAC,EAAE,MAAM,CAAC;KACf,CAAC;CACH,CAAC;AAGF,MAAM,MAAM,iBAAiB,GAAG;IAC9B,QAAQ,CAAC,EAAE,QAAQ,GAAG,IAAI,CAAC;IAC3B,KAAK,CAAC,EAAE,KAAK,CAAC;IACd,IAAI,CAAC,EAAE,UAAU,CAAC;CACnB,CAAC;AAIF,eAAO,MAAM,YAAY,UAAW,iBAAiB,KAAG,WAAW,GAAG,SAKrE,CAAC"}
|
|
@@ -1,27 +1,27 @@
|
|
|
1
1
|
import { Observable } from 'lib0/observable';
|
|
2
|
-
import * as
|
|
2
|
+
import * as YP from 'y-protocols/awareness';
|
|
3
3
|
import { type Doc } from 'yjs';
|
|
4
4
|
import { type Space } from '@dxos/react-client/echo';
|
|
5
|
-
type
|
|
5
|
+
import { type EditorModel, type UseTextModelProps } from './useTextModel';
|
|
6
|
+
export declare const createYjsModel: ({ identity, space, text }: UseTextModelProps) => EditorModel;
|
|
6
7
|
/**
|
|
7
8
|
* Yjs awareness provider on top of a DXOS space.
|
|
8
9
|
*/
|
|
9
|
-
export declare class
|
|
10
|
+
export declare class YAwarenessProvider extends Observable<any> {
|
|
10
11
|
private readonly _space;
|
|
11
12
|
private readonly _awareness;
|
|
12
13
|
private readonly _clientId;
|
|
13
14
|
private readonly _channel;
|
|
14
|
-
constructor({ space, doc, channel, awareness }: {
|
|
15
|
+
constructor({ space, doc, channel, awareness, }: {
|
|
15
16
|
space: Space;
|
|
16
17
|
doc: Doc;
|
|
17
18
|
channel: string;
|
|
18
|
-
awareness?: Awareness;
|
|
19
|
+
awareness?: YP.Awareness;
|
|
19
20
|
});
|
|
20
|
-
get awareness(): Awareness;
|
|
21
|
+
get awareness(): YP.Awareness;
|
|
21
22
|
private _handleAwarenessUpdate;
|
|
22
23
|
private _handleSpaceMessage;
|
|
23
24
|
private _readMessage;
|
|
24
25
|
private _handleBeforeUnload;
|
|
25
26
|
}
|
|
26
|
-
|
|
27
|
-
//# sourceMappingURL=space-provider.d.ts.map
|
|
27
|
+
//# sourceMappingURL=yjs.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"yjs.d.ts","sourceRoot":"","sources":["../../../../src/hooks/yjs.ts"],"names":[],"mappings":"AAMA,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAC7C,OAAO,KAAK,EAAE,MAAM,uBAAuB,CAAC;AAE5C,OAAO,EAAE,KAAK,GAAG,EAAE,MAAM,KAAK,CAAC;AAI/B,OAAO,EAAE,KAAK,KAAK,EAAE,MAAM,yBAAyB,CAAC;AAKrD,OAAO,EAAE,KAAK,WAAW,EAA0B,KAAK,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AAGlG,eAAO,MAAM,cAAc,8BAA+B,iBAAiB,KAAG,WAyC7E,CAAC;AAOF;;GAEG;AACH,qBAAa,kBAAmB,SAAQ,UAAU,CAAC,GAAG,CAAC;IACrD,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAQ;IAC/B,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAe;IAC1C,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;IACnC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAS;gBAEtB,EACV,KAAK,EACL,GAAG,EACH,OAAO,EACP,SAAS,GACV,EAAE;QACD,KAAK,EAAE,KAAK,CAAC;QACb,GAAG,EAAE,GAAG,CAAC;QACT,OAAO,EAAE,MAAM,CAAC;QAChB,SAAS,CAAC,EAAE,EAAE,CAAC,SAAS,CAAC;KAC1B;IA4BD,IAAI,SAAS,IAAI,EAAE,CAAC,SAAS,CAE5B;IAED,OAAO,CAAC,sBAAsB;IAS9B,OAAO,CAAC,mBAAmB;IAS3B,OAAO,CAAC,YAAY;IAoCpB,OAAO,CAAC,mBAAmB;CAG5B"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cursors.d.ts","sourceRoot":"","sources":["../../../../src/styles/cursors.ts"],"names":[],"mappings":"AAoBA,eAAO,MAAM,WAAW;;;CAAsD,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/styles/index.ts"],"names":[],"mappings":"AAIA,cAAc,YAAY,CAAC;AAC3B,cAAc,UAAU,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/styles/index.ts"],"names":[],"mappings":"AAIA,cAAc,WAAW,CAAC;AAC1B,cAAc,YAAY,CAAC;AAC3B,cAAc,UAAU,CAAC"}
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { Observable } from 'lib0/observable';
|
|
2
|
-
import * as
|
|
2
|
+
import * as YP from 'y-protocols/awareness';
|
|
3
3
|
import { Doc } from 'yjs';
|
|
4
4
|
import { Event } from '@dxos/async';
|
|
5
5
|
import { TextKind } from '@dxos/protocols/proto/dxos/echo/model/text';
|
|
6
6
|
import { type EditorModel } from '../hooks';
|
|
7
|
-
type Awareness =
|
|
7
|
+
type Awareness = YP.Awareness;
|
|
8
8
|
/**
|
|
9
9
|
* Yjs awareness provider in-memory.
|
|
10
10
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"replicator.d.ts","sourceRoot":"","sources":["../../../../src/testing/replicator.ts"],"names":[],"mappings":"AAOA,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAE7C,OAAO,KAAK,
|
|
1
|
+
{"version":3,"file":"replicator.d.ts","sourceRoot":"","sources":["../../../../src/testing/replicator.ts"],"names":[],"mappings":"AAOA,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAE7C,OAAO,KAAK,EAAE,MAAM,uBAAuB,CAAC;AAE5C,OAAO,EAAE,GAAG,EAAE,MAAM,KAAK,CAAC;AAE1B,OAAO,EAAE,KAAK,EAAE,MAAM,aAAa,CAAC;AAEpC,OAAO,EAAE,QAAQ,EAAE,MAAM,4CAA4C,CAAC;AAEtE,OAAO,EAAE,KAAK,WAAW,EAAE,MAAM,UAAU,CAAC;AAG5C,KAAK,SAAS,GAAG,EAAE,CAAC,SAAS,CAAC;AAM9B;;GAEG;AACH,qBAAa,iBAAkB,SAAQ,UAAU,CAAC,GAAG,CAAC;IACpD,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAoB;IAClD,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAY;IACvC,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAM;gBAEf,EAAE,MAAM,EAAE,GAAG,EAAE,SAAS,EAAE,EAAE;QAAE,MAAM,EAAE,KAAK,CAAC,UAAU,CAAC,CAAC;QAAC,GAAG,EAAE,GAAG,CAAC;QAAC,SAAS,CAAC,EAAE,SAAS,CAAA;KAAE;IA4BtG,IAAI,SAAS,IAAI,SAAS,CAEzB;IAED,OAAO,CAAC,gBAAgB;IAQxB,OAAO,CAAC,sBAAsB;IAS9B,OAAO,CAAC,mBAAmB;IAS3B,OAAO,CAAC,YAAY;IA0CpB,OAAO,CAAC,mBAAmB;CAG5B;AAED;;GAEG;AACH,qBAAa,UAAU;IAIT,OAAO,CAAC,QAAQ,CAAC,KAAK;IAHlC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAA2B;IACnD,OAAO,CAAC,MAAM,CAAqB;gBAEN,KAAK,EAAE,QAAQ;IAE5C,QAAQ,CAAC,KAAK,EAAE,WAAW,EAAE;IAI7B,UAAU,OAAQ,MAAM,QAAO,GAAG,KAAe,WAAW,CAoB1D;CACH;AAED,MAAM,MAAM,kBAAkB,GAAG;IAC/B,UAAU,EAAE,UAAU,CAAC;IACvB,EAAE,EAAE,MAAM,CAAC;IACX,GAAG,CAAC,EAAE,GAAG,CAAC;CACX,CAAC;AAEF,eAAO,MAAM,WAAW,4BAA6B,kBAAkB,KAAG,WAEzE,CAAC"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { Facet } from '@codemirror/state';
|
|
2
|
+
/**
|
|
3
|
+
* Converts indexes into the text document into stable peer-independent cursors.
|
|
4
|
+
*
|
|
5
|
+
* See:
|
|
6
|
+
* - https://automerge.org/automerge/api-docs/js/functions/next.getCursor.html
|
|
7
|
+
* - https://github.com/yjs/yjs?tab=readme-ov-file#relative-positions
|
|
8
|
+
*
|
|
9
|
+
* @param {assoc} number Negative values will associate the cursor with the previous character
|
|
10
|
+
* while positive - with the next one.
|
|
11
|
+
*/
|
|
12
|
+
export interface CursorConverter {
|
|
13
|
+
toCursor(position: number, assoc?: -1 | 1 | undefined): string;
|
|
14
|
+
fromCursor(cursor: string): number;
|
|
15
|
+
}
|
|
16
|
+
export declare const CursorConverter: Facet<CursorConverter, CursorConverter>;
|
|
17
|
+
//# sourceMappingURL=cursor.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cursor.d.ts","sourceRoot":"","sources":["../../../../src/util/cursor.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,KAAK,EAAE,MAAM,mBAAmB,CAAC;AAE1C;;;;;;;;;GASG;AACH,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,CAAC,CAAC,GAAG,CAAC,GAAG,SAAS,GAAG,MAAM,CAAC;IAC/D,UAAU,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAAC;CACpC;AAOD,eAAO,MAAM,eAAe,yCAE1B,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/util/index.ts"],"names":[],"mappings":"AAIA,cAAc,UAAU,CAAC;AACzB,cAAc,QAAQ,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"util.d.ts","sourceRoot":"","sources":["../../../../src/util/util.ts"],"names":[],"mappings":"AAMA;;GAEG;AAEH,eAAO,MAAM,eAAe,kCAOR,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dxos/react-ui-editor",
|
|
3
|
-
"version": "0.3.11-main.
|
|
3
|
+
"version": "0.3.11-main.1c19c60",
|
|
4
4
|
"description": "Document editing experience within a DXOS shell.",
|
|
5
5
|
"homepage": "https://dxos.org",
|
|
6
6
|
"bugs": "https://github.com/dxos/dxos/issues",
|
|
@@ -38,19 +38,20 @@
|
|
|
38
38
|
"y-codemirror.next": "^0.3.2",
|
|
39
39
|
"y-protocols": "^1.0.5",
|
|
40
40
|
"yjs": "^13.6.10",
|
|
41
|
-
"@dxos/async": "0.3.11-main.
|
|
42
|
-
"@dxos/
|
|
43
|
-
"@dxos/
|
|
44
|
-
"@dxos/display-name": "0.3.11-main.
|
|
45
|
-
"@dxos/
|
|
46
|
-
"@dxos/
|
|
47
|
-
"@dxos/
|
|
48
|
-
"@dxos/
|
|
49
|
-
"@dxos/
|
|
50
|
-
"@dxos/react-
|
|
51
|
-
"@dxos/react-ui
|
|
52
|
-
"@dxos/text-model": "0.3.11-main.
|
|
53
|
-
"@dxos/util": "0.3.11-main.
|
|
41
|
+
"@dxos/async": "0.3.11-main.1c19c60",
|
|
42
|
+
"@dxos/context": "0.3.11-main.1c19c60",
|
|
43
|
+
"@dxos/debug": "0.3.11-main.1c19c60",
|
|
44
|
+
"@dxos/display-name": "0.3.11-main.1c19c60",
|
|
45
|
+
"@dxos/echo-schema": "0.3.11-main.1c19c60",
|
|
46
|
+
"@dxos/invariant": "0.3.11-main.1c19c60",
|
|
47
|
+
"@dxos/automerge": "0.3.11-main.1c19c60",
|
|
48
|
+
"@dxos/log": "0.3.11-main.1c19c60",
|
|
49
|
+
"@dxos/protocols": "0.3.11-main.1c19c60",
|
|
50
|
+
"@dxos/react-async": "0.3.11-main.1c19c60",
|
|
51
|
+
"@dxos/react-ui": "0.3.11-main.1c19c60",
|
|
52
|
+
"@dxos/text-model": "0.3.11-main.1c19c60",
|
|
53
|
+
"@dxos/util": "0.3.11-main.1c19c60",
|
|
54
|
+
"@dxos/react-ui-theme": "0.3.11-main.1c19c60"
|
|
54
55
|
},
|
|
55
56
|
"devDependencies": {
|
|
56
57
|
"@automerge/automerge-repo-network-broadcastchannel": "^1.0.19",
|
|
@@ -66,18 +67,18 @@
|
|
|
66
67
|
"deepsignal": "1.4.0-shallow.0",
|
|
67
68
|
"react": "^18.2.0",
|
|
68
69
|
"react-dom": "^18.2.0",
|
|
69
|
-
"@dxos/
|
|
70
|
-
"@dxos/
|
|
71
|
-
"@dxos/
|
|
72
|
-
"@dxos/
|
|
73
|
-
"@dxos/
|
|
74
|
-
"@dxos/
|
|
70
|
+
"@dxos/echo-signals": "0.3.11-main.1c19c60",
|
|
71
|
+
"@dxos/config": "0.3.11-main.1c19c60",
|
|
72
|
+
"@dxos/react-client": "0.3.11-main.1c19c60",
|
|
73
|
+
"@dxos/echo-typegen": "0.3.11-main.1c19c60",
|
|
74
|
+
"@dxos/storybook-utils": "0.3.11-main.1c19c60",
|
|
75
|
+
"@dxos/react-ui": "0.3.11-main.1c19c60"
|
|
75
76
|
},
|
|
76
77
|
"peerDependencies": {
|
|
77
78
|
"@phosphor-icons/react": "^2.0.5",
|
|
78
79
|
"react": "^18.2.0",
|
|
79
80
|
"react-dom": "^18.2.0",
|
|
80
|
-
"@dxos/react-client": "0.3.11-main.
|
|
81
|
+
"@dxos/react-client": "0.3.11-main.1c19c60"
|
|
81
82
|
},
|
|
82
83
|
"publishConfig": {
|
|
83
84
|
"access": "public"
|
|
@@ -12,6 +12,7 @@ import { fixedInsetFlexLayout, groupSurface, mx } from '@dxos/react-ui-theme';
|
|
|
12
12
|
import { withTheme } from '@dxos/storybook-utils';
|
|
13
13
|
|
|
14
14
|
import { defaultSlots, TextEditor, type TextEditorProps, type TextEditorRef, type TextEditorSlots } from './TextEditor';
|
|
15
|
+
import { listener } from '../../extensions';
|
|
15
16
|
import { useTextModel } from '../../hooks';
|
|
16
17
|
import { textTheme } from '../../themes';
|
|
17
18
|
|
|
@@ -67,6 +68,18 @@ export const Default = {
|
|
|
67
68
|
),
|
|
68
69
|
};
|
|
69
70
|
|
|
71
|
+
export const Listener = {
|
|
72
|
+
render: () => (
|
|
73
|
+
<Story
|
|
74
|
+
slots={defaultsDeep(
|
|
75
|
+
{ editor: { theme: textTheme, placeholder: 'Enter text...' } } satisfies TextEditorSlots,
|
|
76
|
+
defaultSlots,
|
|
77
|
+
)}
|
|
78
|
+
extensions={[listener({ onChange: (text) => console.log(text) })]}
|
|
79
|
+
/>
|
|
80
|
+
),
|
|
81
|
+
};
|
|
82
|
+
|
|
70
83
|
export const Text = {
|
|
71
84
|
render: () => (
|
|
72
85
|
<Story
|
|
@@ -77,7 +77,7 @@ export const BaseTextEditor = forwardRef<TextEditorRef, TextEditorProps>(
|
|
|
77
77
|
>();
|
|
78
78
|
useImperativeHandle(forwardedRef, () => ({ root, state, view }), [view, state, root]);
|
|
79
79
|
|
|
80
|
-
// TODO(burdon): Factor out
|
|
80
|
+
// TODO(burdon): Factor out as extension.
|
|
81
81
|
const { awareness, peer } = model;
|
|
82
82
|
useEffect(() => {
|
|
83
83
|
if (awareness && peer) {
|
|
@@ -2,19 +2,29 @@
|
|
|
2
2
|
// Copyright 2023 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import '@
|
|
5
|
+
import '@dxosTheme';
|
|
6
|
+
|
|
6
7
|
import { BroadcastChannelNetworkAdapter } from '@automerge/automerge-repo-network-broadcastchannel';
|
|
7
8
|
import { EditorView } from '@codemirror/view';
|
|
9
|
+
import '@preact/signals-react'; // Register react integration
|
|
8
10
|
import { basicSetup } from 'codemirror';
|
|
9
11
|
import get from 'lodash.get';
|
|
10
12
|
import React, { useEffect, useRef, useState } from 'react';
|
|
11
13
|
|
|
12
|
-
// TODO(burdon): Why separate imports?
|
|
13
14
|
import { type Prop } from '@dxos/automerge/automerge';
|
|
14
|
-
import { type DocHandle
|
|
15
|
+
import { Repo, type DocHandle } from '@dxos/automerge/automerge-repo';
|
|
16
|
+
import { Filter, setGlobalAutomergePreference } from '@dxos/echo-schema';
|
|
17
|
+
import { type PublicKey } from '@dxos/keys';
|
|
18
|
+
import { Expando, TextObject, useSpace } from '@dxos/react-client/echo';
|
|
19
|
+
import { useIdentity } from '@dxos/react-client/halo';
|
|
20
|
+
import { ClientRepeater } from '@dxos/react-client/testing';
|
|
21
|
+
import { withTheme } from '@dxos/storybook-utils';
|
|
15
22
|
|
|
16
|
-
import {
|
|
17
|
-
import {
|
|
23
|
+
import { MarkdownEditor } from './TextEditor';
|
|
24
|
+
import { type IDocHandle, automerge, awareness } from '../../extensions';
|
|
25
|
+
import { useTextModel } from '../../hooks';
|
|
26
|
+
|
|
27
|
+
// TODO(burdon): Move to components.
|
|
18
28
|
|
|
19
29
|
type EditorProps = {
|
|
20
30
|
handle: IDocHandle;
|
|
@@ -28,7 +38,7 @@ const Editor = ({ handle, path }: EditorProps) => {
|
|
|
28
38
|
useEffect(() => {
|
|
29
39
|
const view = (editorRoot.current = new EditorView({
|
|
30
40
|
doc: get(handle.docSync()!, path),
|
|
31
|
-
extensions: [basicSetup,
|
|
41
|
+
extensions: [basicSetup, automerge({ handle, path }), awareness()],
|
|
32
42
|
parent: containerRef.current as any,
|
|
33
43
|
}));
|
|
34
44
|
|
|
@@ -89,3 +99,53 @@ export default {
|
|
|
89
99
|
};
|
|
90
100
|
|
|
91
101
|
export const Default = {};
|
|
102
|
+
|
|
103
|
+
const EchoStory = ({ id, spaceKey }: { id: number; spaceKey: PublicKey }) => {
|
|
104
|
+
const identity = useIdentity();
|
|
105
|
+
const space = useSpace(spaceKey);
|
|
106
|
+
// TODO(dmaretskyi): useQuery doesn't work.
|
|
107
|
+
const [obj] = space?.db.query(Filter.from({ type: 'test' })).objects ?? [];
|
|
108
|
+
|
|
109
|
+
const model = useTextModel({
|
|
110
|
+
text: obj?.content,
|
|
111
|
+
identity,
|
|
112
|
+
space,
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
if (!model) {
|
|
116
|
+
return null;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
return (
|
|
120
|
+
// <div className={mx(fixedInsetFlexLayout, groupSurface)}>
|
|
121
|
+
<div className='flex justify-center overflow-y-scroll'>
|
|
122
|
+
<div className='flex flex-col w-[800px] py-16'>
|
|
123
|
+
<MarkdownEditor model={model} />
|
|
124
|
+
<div className='flex shrink-0 h-[300px]'></div>
|
|
125
|
+
</div>
|
|
126
|
+
</div>
|
|
127
|
+
// </div>
|
|
128
|
+
);
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
export const WithEcho = {
|
|
132
|
+
render: () => {
|
|
133
|
+
setGlobalAutomergePreference(true);
|
|
134
|
+
return (
|
|
135
|
+
<ClientRepeater
|
|
136
|
+
count={2}
|
|
137
|
+
createSpace
|
|
138
|
+
onCreateSpace={async (space) => {
|
|
139
|
+
space.db.add(
|
|
140
|
+
new Expando({
|
|
141
|
+
type: 'test',
|
|
142
|
+
content: new TextObject('Hello world!'),
|
|
143
|
+
}),
|
|
144
|
+
);
|
|
145
|
+
}}
|
|
146
|
+
Component={EchoStory}
|
|
147
|
+
/>
|
|
148
|
+
);
|
|
149
|
+
},
|
|
150
|
+
decorators: [withTheme],
|
|
151
|
+
};
|
|
@@ -17,7 +17,8 @@ import { type Identity } from '@dxos/react-client/halo';
|
|
|
17
17
|
import { joinCommonSpace, TestBuilder, textGenerator } from '@dxos/react-client/testing';
|
|
18
18
|
import { YText } from '@dxos/text-model';
|
|
19
19
|
|
|
20
|
-
import {
|
|
20
|
+
import { YAwarenessProvider } from '../../hooks';
|
|
21
|
+
import { cursorColor } from '../../styles';
|
|
21
22
|
import { EditorDocument, types as schema } from '../../testing';
|
|
22
23
|
|
|
23
24
|
export default {
|
|
@@ -35,7 +36,7 @@ const updateEditor = async (id: number, editor: HTMLDivElement, identity: Identi
|
|
|
35
36
|
return;
|
|
36
37
|
}
|
|
37
38
|
|
|
38
|
-
const { awareness } = new
|
|
39
|
+
const { awareness } = new YAwarenessProvider({ space, doc, channel: `yjs.awareness.${text.id}` });
|
|
39
40
|
awareness.setLocalStateField('user', {
|
|
40
41
|
name: identity.profile?.displayName ?? generateName(identity.identityKey.toHex()),
|
|
41
42
|
// TODO(wittjosiah): Pick colours from theme based on identity key.
|
|
@@ -12,7 +12,7 @@ import { ClientRepeater, textGenerator, useDataGenerator } from '@dxos/react-cli
|
|
|
12
12
|
import { useId, Input } from '@dxos/react-ui';
|
|
13
13
|
import { withTheme } from '@dxos/storybook-utils';
|
|
14
14
|
|
|
15
|
-
import { MarkdownEditor } from '
|
|
15
|
+
import { MarkdownEditor } from './TextEditor';
|
|
16
16
|
import { Replicator, useYjsModel } from '../../testing';
|
|
17
17
|
|
|
18
18
|
export default {
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2023 DXOS.org
|
|
3
|
+
// Copyright 2024 Automerge
|
|
4
|
+
// Ref: https://github.com/automerge/automerge-codemirror
|
|
5
|
+
//
|
|
6
|
+
|
|
7
|
+
import { Facet, StateField, type Extension, type Transaction } from '@codemirror/state';
|
|
8
|
+
import { ViewPlugin, type EditorView, type PluginValue, type ViewUpdate } from '@codemirror/view';
|
|
9
|
+
|
|
10
|
+
import { next as A } from '@dxos/automerge/automerge';
|
|
11
|
+
import { type Prop } from '@dxos/automerge/automerge';
|
|
12
|
+
|
|
13
|
+
import { cursorConverter } from './cursor';
|
|
14
|
+
import { effectType, type IDocHandle, isReconcileTx, type Value } from './defs';
|
|
15
|
+
import { PatchSemaphore } from './semaphore';
|
|
16
|
+
import { CursorConverter } from '../../util';
|
|
17
|
+
|
|
18
|
+
const semaphoreFacet = Facet.define<PatchSemaphore, PatchSemaphore>({
|
|
19
|
+
combine: (values) => values.at(-1)!, // Take last.
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
export type AutomergeOptions = {
|
|
23
|
+
handle: IDocHandle;
|
|
24
|
+
path: Prop[];
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
export const automerge = ({ handle, path }: AutomergeOptions): Extension => {
|
|
28
|
+
// TODO(burdon): Rename.
|
|
29
|
+
const stateField: StateField<Value> = StateField.define({
|
|
30
|
+
create: () => ({
|
|
31
|
+
lastHeads: A.getHeads(handle.docSync()!),
|
|
32
|
+
unreconciledTransactions: [],
|
|
33
|
+
path: path.slice(),
|
|
34
|
+
}),
|
|
35
|
+
update: (value: Value, tr: Transaction) => {
|
|
36
|
+
const result: Value = {
|
|
37
|
+
lastHeads: value.lastHeads,
|
|
38
|
+
unreconciledTransactions: value.unreconciledTransactions.slice(),
|
|
39
|
+
path: path.slice(),
|
|
40
|
+
};
|
|
41
|
+
let clearUnreconciled = false;
|
|
42
|
+
for (const effect of tr.effects) {
|
|
43
|
+
if (effect.is(effectType)) {
|
|
44
|
+
result.lastHeads = effect.value.newHeads;
|
|
45
|
+
clearUnreconciled = true;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
if (clearUnreconciled) {
|
|
49
|
+
result.unreconciledTransactions = [];
|
|
50
|
+
} else {
|
|
51
|
+
if (!isReconcileTx(tr)) {
|
|
52
|
+
result.unreconciledTransactions.push(tr);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
return result;
|
|
56
|
+
},
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
const semaphore = new PatchSemaphore(stateField);
|
|
60
|
+
|
|
61
|
+
const viewPlugin = ViewPlugin.fromClass(
|
|
62
|
+
class AutomergeCodemirrorViewPlugin implements PluginValue {
|
|
63
|
+
constructor(private readonly _view: EditorView) {
|
|
64
|
+
handle.addListener('change', this._handleChange);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
update(update: ViewUpdate) {
|
|
68
|
+
if (update.transactions.length > 0 && update.transactions.some((tr) => !isReconcileTx(tr))) {
|
|
69
|
+
// TODO(burdon): This is a hack to ensure that the view is updated after the transaction is applied.
|
|
70
|
+
queueMicrotask(() => {
|
|
71
|
+
this._view.state.facet(semaphoreFacet).reconcile(handle, this._view);
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
destroy() {
|
|
77
|
+
handle.addListener('change', this._handleChange);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
private _handleChange = () => {
|
|
81
|
+
this._view.state.facet(semaphoreFacet).reconcile(handle, this._view);
|
|
82
|
+
};
|
|
83
|
+
},
|
|
84
|
+
);
|
|
85
|
+
|
|
86
|
+
return {
|
|
87
|
+
extension: [
|
|
88
|
+
CursorConverter.of(cursorConverter(handle, path)),
|
|
89
|
+
semaphoreFacet.of(semaphore),
|
|
90
|
+
stateField,
|
|
91
|
+
viewPlugin,
|
|
92
|
+
],
|
|
93
|
+
};
|
|
94
|
+
};
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2024 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import get from 'lodash.get';
|
|
6
|
+
|
|
7
|
+
import type { Prop } from '@dxos/automerge/automerge';
|
|
8
|
+
import { next as automerge } from '@dxos/automerge/automerge';
|
|
9
|
+
|
|
10
|
+
import { type IDocHandle } from './defs';
|
|
11
|
+
import { type CursorConverter } from '../../util';
|
|
12
|
+
|
|
13
|
+
export const cursorConverter = (handle: IDocHandle, path: Prop[]): CursorConverter => ({
|
|
14
|
+
// TODO(burdon): Handle assoc to associate with a previous character.
|
|
15
|
+
toCursor: (pos) => {
|
|
16
|
+
const doc = handle.docSync();
|
|
17
|
+
if (!doc) {
|
|
18
|
+
return '';
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const value = get(doc, path);
|
|
22
|
+
if (typeof value === 'string' && value.length <= pos) {
|
|
23
|
+
return 'end';
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// NOTE: Slice is needed because getCursor mutates the array.
|
|
27
|
+
return automerge.getCursor(doc, path.slice(), pos);
|
|
28
|
+
},
|
|
29
|
+
fromCursor: (cursor) => {
|
|
30
|
+
if (cursor === '') {
|
|
31
|
+
return 0;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const doc = handle.docSync();
|
|
35
|
+
if (!doc) {
|
|
36
|
+
return 0;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if (cursor === 'end') {
|
|
40
|
+
const value = get(doc, path);
|
|
41
|
+
if (typeof value === 'string') {
|
|
42
|
+
return value.length;
|
|
43
|
+
} else {
|
|
44
|
+
return 0;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// NOTE: Slice is needed because getCursor mutates the array.
|
|
49
|
+
return automerge.getCursorPosition(doc, path.slice(), cursor);
|
|
50
|
+
},
|
|
51
|
+
});
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2023 DXOS.org
|
|
3
|
+
// Ref: https://github.com/automerge/automerge-codemirror
|
|
4
|
+
//
|
|
5
|
+
|
|
6
|
+
import {
|
|
7
|
+
Annotation,
|
|
8
|
+
StateEffect,
|
|
9
|
+
type StateField,
|
|
10
|
+
type EditorState,
|
|
11
|
+
type Transaction,
|
|
12
|
+
type TransactionSpec,
|
|
13
|
+
} from '@codemirror/state';
|
|
14
|
+
|
|
15
|
+
import { type ChangeFn, type ChangeOptions, type Doc, type Heads, type Prop } from '@dxos/automerge/automerge';
|
|
16
|
+
|
|
17
|
+
export type Value = {
|
|
18
|
+
lastHeads: Heads;
|
|
19
|
+
path: Prop[];
|
|
20
|
+
unreconciledTransactions: Transaction[];
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
type UpdateHeads = {
|
|
24
|
+
newHeads: Heads;
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
export const effectType = StateEffect.define<UpdateHeads>({});
|
|
28
|
+
|
|
29
|
+
export const updateHeads = (newHeads: Heads): StateEffect<UpdateHeads> => effectType.of({ newHeads });
|
|
30
|
+
|
|
31
|
+
export const getLastHeads = (state: EditorState, field: StateField<Value>): Heads => state.field(field).lastHeads;
|
|
32
|
+
|
|
33
|
+
export const getPath = (state: EditorState, field: StateField<Value>): Prop[] => state.field(field).path;
|
|
34
|
+
|
|
35
|
+
export const reconcileAnnotationType = Annotation.define<unknown>();
|
|
36
|
+
|
|
37
|
+
export const isReconcileTx = (tr: Transaction): boolean => !!tr.annotation(reconcileAnnotationType);
|
|
38
|
+
|
|
39
|
+
export const makeReconcile = (tr: TransactionSpec) => {
|
|
40
|
+
if (tr.annotations != null) {
|
|
41
|
+
if (tr.annotations instanceof Array) {
|
|
42
|
+
tr.annotations = [...tr.annotations, reconcileAnnotationType.of({})];
|
|
43
|
+
} else {
|
|
44
|
+
tr.annotations = [tr.annotations, reconcileAnnotationType.of({})];
|
|
45
|
+
}
|
|
46
|
+
} else {
|
|
47
|
+
tr.annotations = [reconcileAnnotationType.of({})];
|
|
48
|
+
}
|
|
49
|
+
// return {
|
|
50
|
+
// ...tr,
|
|
51
|
+
// annotations: reconcileAnnotationType.of({})
|
|
52
|
+
// }
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
export type IDocHandle<T = any> = {
|
|
56
|
+
docSync(): Doc<T> | undefined;
|
|
57
|
+
change(callback: ChangeFn<T>, options?: ChangeOptions<T>): void;
|
|
58
|
+
changeAt(heads: Heads, callback: ChangeFn<T>, options?: ChangeOptions<T>): string[] | undefined;
|
|
59
|
+
|
|
60
|
+
addListener(event: 'change', listener: () => void): void;
|
|
61
|
+
removeListener(event: 'change', listener: () => void): void;
|
|
62
|
+
};
|