@atlaskit/editor-plugin-collab-edit 13.1.3 → 13.2.0
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/CHANGELOG.md +46 -0
- package/dist/cjs/pm-plugins/actions.js +50 -14
- package/dist/cjs/pm-plugins/analytics.js +26 -1
- package/dist/cjs/pm-plugins/events/handlers.js +1 -1
- package/dist/cjs/pm-plugins/main/agent-shimmer-decorations.js +56 -17
- package/dist/cjs/pm-plugins/main/agent-shimmer-ranges.js +46 -27
- package/dist/cjs/pm-plugins/main/plugin-state.js +2 -1
- package/dist/es2019/pm-plugins/actions.js +49 -15
- package/dist/es2019/pm-plugins/analytics.js +23 -0
- package/dist/es2019/pm-plugins/events/handlers.js +1 -1
- package/dist/es2019/pm-plugins/main/agent-shimmer-decorations.js +52 -16
- package/dist/es2019/pm-plugins/main/agent-shimmer-ranges.js +42 -23
- package/dist/es2019/pm-plugins/main/plugin-state.js +3 -2
- package/dist/esm/pm-plugins/actions.js +51 -15
- package/dist/esm/pm-plugins/analytics.js +24 -0
- package/dist/esm/pm-plugins/events/handlers.js +1 -1
- package/dist/esm/pm-plugins/main/agent-shimmer-decorations.js +55 -16
- package/dist/esm/pm-plugins/main/agent-shimmer-ranges.js +46 -27
- package/dist/esm/pm-plugins/main/plugin-state.js +3 -2
- package/dist/types/pm-plugins/actions.d.ts +2 -2
- package/dist/types/pm-plugins/analytics.d.ts +9 -1
- package/dist/types/pm-plugins/main/agent-shimmer-decorations.d.ts +15 -4
- package/dist/types/pm-plugins/main/agent-shimmer-ranges.d.ts +2 -1
- package/package.json +2 -2
|
@@ -2,8 +2,9 @@ import { getCollabState } from '@atlaskit/prosemirror-collab';
|
|
|
2
2
|
import { expValEquals } from '@atlaskit/tmp-editor-statsig/exp-val-equals';
|
|
3
3
|
// When an agent step lands we cover the top-level block(s) it wrote with a skeleton-loader shimmer
|
|
4
4
|
// (plus a Rovo agent telepointer at the end of the range), then remove it on a timer to reveal the
|
|
5
|
-
// content. Gated behind the `platform_editor_agent_be_streaming` experiment
|
|
6
|
-
//
|
|
5
|
+
// content. Gated behind the `platform_editor_agent_be_streaming` experiment. `shimmerDurationMs` and
|
|
6
|
+
// `highlightDurationMs` size the skeleton and purple-highlight phases and toggle independently: `0` on
|
|
7
|
+
// either skips that phase, `0` on both shows nothing.
|
|
7
8
|
var agentShimmerIdCounter = 0;
|
|
8
9
|
|
|
9
10
|
// A step is position-neutral when its StepMap changes no range's length — i.e. it shifts no
|
|
@@ -20,6 +21,26 @@ export var isPositionNeutralStep = function isPositionNeutralStep(step) {
|
|
|
20
21
|
return neutral;
|
|
21
22
|
};
|
|
22
23
|
|
|
24
|
+
// Top-level block/position helpers over a doc. Kept at module scope (rather than re-created as
|
|
25
|
+
// closures on every call) so they are defined once, reusable, and unit-testable. Each clamps into
|
|
26
|
+
// valid document coordinates before resolving.
|
|
27
|
+
var clampToDoc = function clampToDoc(doc, pos) {
|
|
28
|
+
return Math.min(Math.max(pos, 1), doc.content.size);
|
|
29
|
+
};
|
|
30
|
+
var topLevelBlockIndexAt = function topLevelBlockIndexAt(doc, pos) {
|
|
31
|
+
return doc.resolve(clampToDoc(doc, pos)).index(0);
|
|
32
|
+
};
|
|
33
|
+
// Start of the content of the top-level block containing `pos`, so the whole block is covered.
|
|
34
|
+
var topLevelBlockContentStart = function topLevelBlockContentStart(doc, pos) {
|
|
35
|
+
var $pos = doc.resolve(clampToDoc(doc, pos));
|
|
36
|
+
return $pos.depth >= 1 ? $pos.start(1) : pos;
|
|
37
|
+
};
|
|
38
|
+
// End of the content of the top-level block containing `pos`, so the whole block is covered.
|
|
39
|
+
var topLevelBlockContentEnd = function topLevelBlockContentEnd(doc, pos) {
|
|
40
|
+
var $pos = doc.resolve(clampToDoc(doc, pos));
|
|
41
|
+
return $pos.depth >= 1 ? $pos.end(1) : pos;
|
|
42
|
+
};
|
|
43
|
+
|
|
23
44
|
/**
|
|
24
45
|
* Derive the shimmer ranges for the agent-authored steps in a received batch, in final-doc
|
|
25
46
|
* coordinates. `agentType` present ⇒ agent-authored (per the NCS↔Editor steps contract). Each
|
|
@@ -36,15 +57,20 @@ export var isPositionNeutralStep = function isPositionNeutralStep(step) {
|
|
|
36
57
|
* rebased local step changed sizes (a rare, safe degrade). Any unexpected error also degrades to no
|
|
37
58
|
* shimmer, so this never throws into the shared remote-step handler.
|
|
38
59
|
*/
|
|
39
|
-
export var getAgentShimmerRanges = function getAgentShimmerRanges(json, steps, tr, view,
|
|
60
|
+
export var getAgentShimmerRanges = function getAgentShimmerRanges(json, steps, tr, view, shimmerDurationMs, highlightDurationMs, telepointerEnabled, onNotShown) {
|
|
40
61
|
var _json$find;
|
|
41
62
|
if (!expValEquals('platform_editor_agent_be_streaming', 'isEnabled', true)) {
|
|
42
63
|
return [];
|
|
43
64
|
}
|
|
44
|
-
//
|
|
45
|
-
|
|
65
|
+
// The two phases toggle independently: `shimmerDurationMs` sizes the skeleton, `highlightDurationMs`
|
|
66
|
+
// sizes the purple highlight, and `0` on either skips just that phase. Nothing to reveal only when
|
|
67
|
+
// both are off.
|
|
68
|
+
if (shimmerDurationMs <= 0 && highlightDurationMs <= 0) {
|
|
46
69
|
return [];
|
|
47
70
|
}
|
|
71
|
+
// Start in the skeleton phase when it's enabled, otherwise straight into the purple highlight phase
|
|
72
|
+
// (skeleton toggled off, highlight on).
|
|
73
|
+
var initialPhase = shimmerDurationMs > 0 ? 'skeleton' : 'highlight';
|
|
48
74
|
// Telepointer label = the agent's type upper-cased (e.g. `mcp` → "MCP"), falling back to a generic
|
|
49
75
|
// "Agent"; `undefined` when the telepointer is disabled, so the plugin skips it. `agentType` is the
|
|
50
76
|
// same on every step of an agent batch, so read it from the first agent-authored step.
|
|
@@ -52,7 +78,11 @@ export var getAgentShimmerRanges = function getAgentShimmerRanges(json, steps, t
|
|
|
52
78
|
var agentType = (_json$find = json.find(function (step) {
|
|
53
79
|
return typeof (step === null || step === void 0 ? void 0 : step.agentType) === 'string';
|
|
54
80
|
})) === null || _json$find === void 0 ? void 0 : _json$find.agentType;
|
|
55
|
-
|
|
81
|
+
// No agent-authored steps in this batch — nothing to shimmer, and not a "not shown" case.
|
|
82
|
+
if (agentType === undefined) {
|
|
83
|
+
return [];
|
|
84
|
+
}
|
|
85
|
+
var telepointerLabel = telepointerEnabled ? agentType.toUpperCase() || 'Agent' : undefined;
|
|
56
86
|
// When the batch was rebased over local unconfirmed steps, our index-based range math is only
|
|
57
87
|
// valid if those local steps shifted no positions. Attribute-only steps (e.g. `localId`) and
|
|
58
88
|
// same-size replacements are position-neutral, so the shimmer stays correct. Skip only when a
|
|
@@ -63,6 +93,7 @@ export var getAgentShimmerRanges = function getAgentShimmerRanges(json, steps, t
|
|
|
63
93
|
if (unconfirmed.some(function (entry) {
|
|
64
94
|
return !isPositionNeutralStep(entry.step);
|
|
65
95
|
})) {
|
|
96
|
+
onNotShown === null || onNotShown === void 0 || onNotShown('rebasedConcurrentEdit', agentType);
|
|
66
97
|
return [];
|
|
67
98
|
}
|
|
68
99
|
}
|
|
@@ -102,6 +133,7 @@ export var getAgentShimmerRanges = function getAgentShimmerRanges(json, steps, t
|
|
|
102
133
|
});
|
|
103
134
|
});
|
|
104
135
|
if (!infos.length) {
|
|
136
|
+
onNotShown === null || onNotShown === void 0 || onNotShown('nothingToShow', agentType);
|
|
105
137
|
return [];
|
|
106
138
|
}
|
|
107
139
|
|
|
@@ -113,28 +145,12 @@ export var getAgentShimmerRanges = function getAgentShimmerRanges(json, steps, t
|
|
|
113
145
|
// touched blocks (index n and n+1) merge into one group so a multi-block rewrite reveals together;
|
|
114
146
|
// a genuinely untouched block in between (index gap > 1) splits the run, so far-apart edits stay
|
|
115
147
|
// independent.
|
|
116
|
-
var docSize = tr.doc.content.size;
|
|
117
|
-
var clampPos = function clampPos(pos) {
|
|
118
|
-
return Math.min(Math.max(pos, 1), docSize);
|
|
119
|
-
};
|
|
120
|
-
var blockIndexAt = function blockIndexAt(pos) {
|
|
121
|
-
return tr.doc.resolve(clampPos(pos)).index(0);
|
|
122
|
-
};
|
|
123
|
-
// Start/end of the content of the top-level block containing `pos`, so the whole block is covered.
|
|
124
|
-
var blockContentStart = function blockContentStart(pos) {
|
|
125
|
-
var $pos = tr.doc.resolve(clampPos(pos));
|
|
126
|
-
return $pos.depth >= 1 ? $pos.start(1) : pos;
|
|
127
|
-
};
|
|
128
|
-
var blockContentEnd = function blockContentEnd(pos) {
|
|
129
|
-
var $pos = tr.doc.resolve(clampPos(pos));
|
|
130
|
-
return $pos.depth >= 1 ? $pos.end(1) : pos;
|
|
131
|
-
};
|
|
132
148
|
var sorted = [].concat(infos).sort(function (a, b) {
|
|
133
149
|
return a.from - b.from || a.to - b.to;
|
|
134
150
|
});
|
|
135
151
|
var groups = [];
|
|
136
152
|
sorted.forEach(function (info) {
|
|
137
|
-
var block =
|
|
153
|
+
var block = topLevelBlockIndexAt(tr.doc, info.from);
|
|
138
154
|
var current = groups[groups.length - 1];
|
|
139
155
|
if (current && block <= current.maxBlock + 1) {
|
|
140
156
|
current.to = Math.max(current.to, info.to);
|
|
@@ -150,14 +166,17 @@ export var getAgentShimmerRanges = function getAgentShimmerRanges(json, steps, t
|
|
|
150
166
|
return groups.map(function (group) {
|
|
151
167
|
return {
|
|
152
168
|
shimmerId: "agent-shimmer-".concat(agentShimmerIdCounter++),
|
|
153
|
-
from:
|
|
154
|
-
to:
|
|
155
|
-
telepointerLabel: telepointerLabel
|
|
169
|
+
from: topLevelBlockContentStart(tr.doc, group.from),
|
|
170
|
+
to: topLevelBlockContentEnd(tr.doc, group.to),
|
|
171
|
+
telepointerLabel: telepointerLabel,
|
|
172
|
+
phase: initialPhase,
|
|
173
|
+
highlightDurationMs: highlightDurationMs
|
|
156
174
|
};
|
|
157
175
|
});
|
|
158
|
-
} catch (
|
|
176
|
+
} catch (err) {
|
|
159
177
|
// Never let shimmer range derivation throw into the shared remote-step handler; degrade to no
|
|
160
178
|
// shimmer.
|
|
179
|
+
onNotShown === null || onNotShown === void 0 || onNotShown('captureThrew', agentType, err);
|
|
161
180
|
return [];
|
|
162
181
|
}
|
|
163
182
|
};
|
|
@@ -7,7 +7,7 @@ import { Selection } from '@atlaskit/editor-prosemirror/state';
|
|
|
7
7
|
import { DecorationSet } from '@atlaskit/editor-prosemirror/view';
|
|
8
8
|
import { Participants } from '../participants';
|
|
9
9
|
import { createTelepointers, findPointers, getPositionOfTelepointer, isReplaceStep, hasExistingNudge } from '../utils';
|
|
10
|
-
import { ADD_AGENT_SHIMMER_META, buildAgentShimmerDecorations, reduceAgentShimmers, REMOVE_AGENT_SHIMMER_META } from './agent-shimmer-decorations';
|
|
10
|
+
import { ADD_AGENT_SHIMMER_META, buildAgentShimmerDecorations, HIGHLIGHT_AGENT_SHIMMER_META, reduceAgentShimmers, REMOVE_AGENT_SHIMMER_META } from './agent-shimmer-decorations';
|
|
11
11
|
|
|
12
12
|
/**
|
|
13
13
|
* Returns position where it's possible to place a decoration.
|
|
@@ -91,6 +91,7 @@ export var PluginState = /*#__PURE__*/function () {
|
|
|
91
91
|
var nudgeTelepointerData = tr.getMeta('nudgeTelepointer');
|
|
92
92
|
var agentShimmerData = tr.getMeta(ADD_AGENT_SHIMMER_META);
|
|
93
93
|
var removeAgentShimmerId = tr.getMeta(REMOVE_AGENT_SHIMMER_META);
|
|
94
|
+
var highlightAgentShimmerId = tr.getMeta(HIGHLIGHT_AGENT_SHIMMER_META);
|
|
94
95
|
var sessionIdData = tr.getMeta('sessionId');
|
|
95
96
|
var collabInitialised = tr.getMeta('collabInitialised');
|
|
96
97
|
if (typeof collabInitialised !== 'boolean') {
|
|
@@ -233,7 +234,7 @@ export var PluginState = /*#__PURE__*/function () {
|
|
|
233
234
|
// here can never corrupt telepointer decorations or the shared decoration set. Never mutates
|
|
234
235
|
// shimmer entries in place: each change produces a fresh array of fresh objects.
|
|
235
236
|
try {
|
|
236
|
-
var _reduceAgentShimmers = reduceAgentShimmers(this.agentShimmers, tr, agentShimmerData, removeAgentShimmerId),
|
|
237
|
+
var _reduceAgentShimmers = reduceAgentShimmers(this.agentShimmers, tr, agentShimmerData, removeAgentShimmerId, highlightAgentShimmerId),
|
|
237
238
|
changed = _reduceAgentShimmers.changed,
|
|
238
239
|
next = _reduceAgentShimmers.next;
|
|
239
240
|
if (changed) {
|
|
@@ -7,7 +7,7 @@ export declare const registerAllCustomSteps: () => void;
|
|
|
7
7
|
export declare const handleInit: (initData: CollabEventInitData, view: EditorView, options?: PrivateCollabEditOptions, editorAnalyticsApi?: EditorAnalyticsAPI) => void;
|
|
8
8
|
export declare const handleConnection: (connectionData: CollabEventConnectionData, view: EditorView) => void;
|
|
9
9
|
export declare const handlePresence: (presenceData: CollabEventPresenceData, view: EditorView) => void;
|
|
10
|
-
export declare const applyRemoteData: (remoteData: CollabEventRemoteData, view: EditorView, options: PrivateCollabEditOptions) => void;
|
|
11
|
-
export declare const applyRemoteSteps: (json: any[], view: EditorView, userIds?: (number | string)[], options?: PrivateCollabEditOptions) => void;
|
|
10
|
+
export declare const applyRemoteData: (remoteData: CollabEventRemoteData, view: EditorView, options: PrivateCollabEditOptions, editorAnalyticsApi?: EditorAnalyticsAPI) => void;
|
|
11
|
+
export declare const applyRemoteSteps: (json: any[], view: EditorView, userIds?: (number | string)[], options?: PrivateCollabEditOptions, editorAnalyticsApi?: EditorAnalyticsAPI) => void;
|
|
12
12
|
export declare const handleTelePointer: (telepointerData: CollabTelepointerPayload, view: EditorView) => void;
|
|
13
13
|
export declare const getSendableSelection: (selection: Selection) => CollabSendableSelection;
|
|
@@ -1,6 +1,14 @@
|
|
|
1
|
-
import type { EditorAnalyticsAPI } from '@atlaskit/editor-common/analytics';
|
|
1
|
+
import type { AnalyticsEventPayload, EditorAnalyticsAPI } from '@atlaskit/editor-common/analytics';
|
|
2
|
+
import type { AgentEditShimmerNotShownReason } from '@atlaskit/editor-common/analytics/types/agent-edit-shimmer-events';
|
|
2
3
|
import type { FeatureFlags } from '@atlaskit/editor-common/types';
|
|
3
4
|
import type { EditorState, Transaction } from '@atlaskit/editor-prosemirror/state';
|
|
4
5
|
export declare const addSynchronyErrorAnalytics: (state: EditorState, tr: Transaction, featureFlags: FeatureFlags, editorAnalyticsApi: EditorAnalyticsAPI | undefined) => (error: Error) => Transaction;
|
|
6
|
+
/**
|
|
7
|
+
* Builds the `agentEditShimmerNotShown` operational event fired when an agent-authored edit applied
|
|
8
|
+
* instantly without the skeleton shimmer/telepointer. Success is the ABSENCE of this event; a
|
|
9
|
+
* neutral action + `reason` keeps expected degrades out of error dashboards. Attributes are non-PII:
|
|
10
|
+
* only the agent kind and a sanitised error name/message (never the raw Error or any doc content).
|
|
11
|
+
*/
|
|
12
|
+
export declare const getAgentEditShimmerNotShownPayload: (reason: AgentEditShimmerNotShownReason, agentType?: string, error?: Error) => AnalyticsEventPayload;
|
|
5
13
|
export type EntityEventType = 'error' | 'disconnected';
|
|
6
14
|
export declare const addSynchronyEntityAnalytics: (state: EditorState, tr: Transaction) => (type: EntityEventType, editorAnalyticsApi: EditorAnalyticsAPI | undefined) => Transaction;
|
|
@@ -2,13 +2,23 @@ import type { ReadonlyTransaction } from '@atlaskit/editor-prosemirror/state';
|
|
|
2
2
|
import { Decoration } from '@atlaskit/editor-prosemirror/view';
|
|
3
3
|
/** Default time the skeleton shimmer stays on the agent-authored content (ms). */
|
|
4
4
|
export declare const AGENT_SHIMMER_DEFAULT_DURATION_MS = 3000;
|
|
5
|
+
/**
|
|
6
|
+
* Default time the purple "just edited" highlight stays after the skeleton clears (ms); `0` disables
|
|
7
|
+
* the highlight phase so the shimmer just reveals the content.
|
|
8
|
+
*/
|
|
9
|
+
export declare const AGENT_EDIT_HIGHLIGHT_DEFAULT_DURATION_MS = 2000;
|
|
5
10
|
export declare const AGENT_SHIMMER_CLASS = "collab-agent-shimmer";
|
|
11
|
+
export declare const AGENT_EDIT_HIGHLIGHT_CLASS = "collab-agent-edit-highlight";
|
|
6
12
|
export declare const ROVO_AGENT_TELEPOINTER_CLASS = "ai-in-editor-telepointer";
|
|
7
13
|
export declare const ROVO_AGENT_TELEPOINTER_LABEL_CLASS = "ai-in-editor-telepointer-label";
|
|
8
14
|
export declare const ADD_AGENT_SHIMMER_META = "addAgentShimmer";
|
|
15
|
+
export declare const HIGHLIGHT_AGENT_SHIMMER_META = "highlightAgentShimmer";
|
|
9
16
|
export declare const REMOVE_AGENT_SHIMMER_META = "removeAgentShimmer";
|
|
17
|
+
export type AgentShimmerPhase = 'skeleton' | 'highlight';
|
|
10
18
|
export type AgentShimmerRange = {
|
|
11
19
|
from: number;
|
|
20
|
+
highlightDurationMs: number;
|
|
21
|
+
phase: AgentShimmerPhase;
|
|
12
22
|
shimmerId: string;
|
|
13
23
|
telepointerLabel?: string;
|
|
14
24
|
to: number;
|
|
@@ -19,13 +29,14 @@ export type AgentShimmerRange = {
|
|
|
19
29
|
* supersedes any still-in-flight shimmer), and drops a range when its removal timer fires. Returns a
|
|
20
30
|
* fresh array (never mutates in place) plus whether anything changed.
|
|
21
31
|
*/
|
|
22
|
-
export declare const reduceAgentShimmers: (current: AgentShimmerRange[], tr: ReadonlyTransaction, added: AgentShimmerRange[] | undefined, removedShimmerId: string | undefined) => {
|
|
32
|
+
export declare const reduceAgentShimmers: (current: AgentShimmerRange[], tr: ReadonlyTransaction, added: AgentShimmerRange[] | undefined, removedShimmerId: string | undefined, highlightShimmerId: string | undefined) => {
|
|
23
33
|
changed: boolean;
|
|
24
34
|
next: AgentShimmerRange[];
|
|
25
35
|
};
|
|
26
36
|
/**
|
|
27
|
-
* Builds the inline skeleton
|
|
28
|
-
* `getValidPos` clamps a raw position to a valid
|
|
29
|
-
* One bad range is isolated via `onError` so it can't
|
|
37
|
+
* Builds the inline decorations (skeleton bar or purple highlight, per phase) plus the trailing
|
|
38
|
+
* telepointer for the active shimmer ranges. `getValidPos` clamps a raw position to a valid
|
|
39
|
+
* decoration position (owned by `plugin-state`). One bad range is isolated via `onError` so it can't
|
|
40
|
+
* kill the others.
|
|
30
41
|
*/
|
|
31
42
|
export declare const buildAgentShimmerDecorations: (tr: ReadonlyTransaction, shimmers: AgentShimmerRange[], getValidPos: (tr: ReadonlyTransaction, pos: number) => number, onError: (err: Error) => void) => Decoration[];
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { AgentEditShimmerNotShownReason } from '@atlaskit/editor-common/analytics/types/agent-edit-shimmer-events';
|
|
1
2
|
import type { Transaction } from '@atlaskit/editor-prosemirror/state';
|
|
2
3
|
import type { Step } from '@atlaskit/editor-prosemirror/transform';
|
|
3
4
|
import type { EditorView } from '@atlaskit/editor-prosemirror/view';
|
|
@@ -19,4 +20,4 @@ export declare const isPositionNeutralStep: (step: Step) => boolean;
|
|
|
19
20
|
* rebased local step changed sizes (a rare, safe degrade). Any unexpected error also degrades to no
|
|
20
21
|
* shimmer, so this never throws into the shared remote-step handler.
|
|
21
22
|
*/
|
|
22
|
-
export declare const getAgentShimmerRanges: (json: any[], steps: Step[], tr: Transaction, view: EditorView,
|
|
23
|
+
export declare const getAgentShimmerRanges: (json: any[], steps: Step[], tr: Transaction, view: EditorView, shimmerDurationMs: number, highlightDurationMs: number, telepointerEnabled: boolean, onNotShown?: (reason: AgentEditShimmerNotShownReason, agentType?: string, error?: Error) => void) => AgentShimmerRange[];
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@atlaskit/editor-plugin-collab-edit",
|
|
3
|
-
"version": "13.
|
|
3
|
+
"version": "13.2.0",
|
|
4
4
|
"description": "Collab Edit plugin for @atlaskit/editor-core",
|
|
5
5
|
"author": "Atlassian Pty Ltd",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -37,7 +37,7 @@
|
|
|
37
37
|
"memoize-one": "^6.0.0"
|
|
38
38
|
},
|
|
39
39
|
"peerDependencies": {
|
|
40
|
-
"@atlaskit/editor-common": "^116.
|
|
40
|
+
"@atlaskit/editor-common": "^116.42.0",
|
|
41
41
|
"react": "^18.2.0",
|
|
42
42
|
"react-dom": "^18.2.0"
|
|
43
43
|
},
|