@atlaskit/editor-plugin-list 19.0.1 → 19.0.4

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 CHANGED
@@ -1,5 +1,30 @@
1
1
  # @atlaskit/editor-plugin-list
2
2
 
3
+ ## 19.0.4
4
+
5
+ ### Patch Changes
6
+
7
+ - [`352bac5c0a9ed`](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/commits/352bac5c0a9ed) -
8
+ Reduce the per-transaction cost of recomputing list indentation decorations, behind the
9
+ `platform_editor_list_performance_improv` experiment. The decoration set is now maintained
10
+ incrementally: it is mapped through each transaction and only the top-level blocks that the
11
+ transaction touched are recomputed, instead of rebuilding the whole set from a full document scan.
12
+ The remaining scan also skips textblock subtrees, which can never contain a list, and each list
13
+ emits a single decoration carrying both the indentation level and the ordered-list item counter
14
+ padding instead of two decorations over the same range. Rendered output is unchanged.
15
+
16
+ ## 19.0.3
17
+
18
+ ### Patch Changes
19
+
20
+ - Updated dependencies
21
+
22
+ ## 19.0.2
23
+
24
+ ### Patch Changes
25
+
26
+ - Updated dependencies
27
+
3
28
  ## 19.0.1
4
29
 
5
30
  ### Patch Changes
@@ -4,7 +4,7 @@ var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefau
4
4
  Object.defineProperty(exports, "__esModule", {
5
5
  value: true
6
6
  });
7
- exports.pluginKey = exports.getDecorations = exports.createPlugin = void 0;
7
+ exports.updateDecorations = exports.pluginKey = exports.getDecorationsForDocument = exports.getDecorations = exports.createPlugin = void 0;
8
8
  var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty"));
9
9
  var _safePlugin = require("@atlaskit/editor-common/safe-plugin");
10
10
  var _selection = require("@atlaskit/editor-common/selection");
@@ -13,9 +13,13 @@ var _utils = require("@atlaskit/editor-common/utils");
13
13
  var _state2 = require("@atlaskit/editor-prosemirror/state");
14
14
  var _utils2 = require("@atlaskit/editor-prosemirror/utils");
15
15
  var _view = require("@atlaskit/editor-prosemirror/view");
16
+ var _isExperimentEnabled = require("@atlaskit/platform-feature-experiments/is-experiment-enabled");
16
17
  var _expValEqualsNoExposure = require("@atlaskit/tmp-editor-statsig/exp-val-equals-no-exposure");
17
18
  var _transforms = require("./transforms");
18
19
  var _selection2 = require("./utils/selection");
20
+ function _createForOfIteratorHelper(r, e) { var t = "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; if (!t) { if (Array.isArray(r) || (t = _unsupportedIterableToArray(r)) || e && r && "number" == typeof r.length) { t && (r = t); var _n = 0, F = function F() {}; return { s: F, n: function n() { return _n >= r.length ? { done: !0 } : { done: !1, value: r[_n++] }; }, e: function e(r) { throw r; }, f: F }; } throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } var o, a = !0, u = !1; return { s: function s() { t = t.call(r); }, n: function n() { var r = t.next(); return a = r.done, r; }, e: function e(r) { u = !0, o = r; }, f: function f() { try { a || null == t.return || t.return(); } finally { if (u) throw o; } } }; }
21
+ function _unsupportedIterableToArray(r, a) { if (r) { if ("string" == typeof r) return _arrayLikeToArray(r, a); var t = {}.toString.call(r).slice(8, -1); return "Object" === t && r.constructor && (t = r.constructor.name), "Map" === t || "Set" === t ? Array.from(r) : "Arguments" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) ? _arrayLikeToArray(r, a) : void 0; } }
22
+ function _arrayLikeToArray(r, a) { (null == a || a > r.length) && (a = r.length); for (var e = 0, n = Array(a); e < a; e++) n[e] = r[e]; return n; }
19
23
  function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
20
24
  function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { (0, _defineProperty2.default)(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
21
25
  var listPluginKey = new _state2.PluginKey('listPlugin');
@@ -27,6 +31,75 @@ var initialState = {
27
31
  orderedListDisabled: false,
28
32
  decorationSet: _view.DecorationSet.empty
29
33
  };
34
+
35
+ /**
36
+ * Numbered lists whose item counters reach 2+ digits need extra gutter spacing so the counter
37
+ * does not collide with the item content.
38
+ */
39
+ var getItemCounterPaddingStyle = function getItemCounterPaddingStyle(node) {
40
+ var _node$attrs;
41
+ if (node.type.name !== 'orderedList') {
42
+ return undefined;
43
+ }
44
+ var digitsSize = (0, _utils.getItemCounterDigitsSize)({
45
+ itemsCount: node === null || node === void 0 ? void 0 : node.childCount,
46
+ order: node === null || node === void 0 || (_node$attrs = node.attrs) === null || _node$attrs === void 0 ? void 0 : _node$attrs.order
47
+ });
48
+ return digitsSize && digitsSize > 1 ? (0, _styles.getOrderedListInlineStyles)(digitsSize, 'string') : undefined;
49
+ };
50
+
51
+ /**
52
+ * Builds the decorations for a range aligned to top-level block boundaries — or for the whole
53
+ * document, which is the same thing. Because a top-level block has no list ancestors, the
54
+ * indentation depth always restarts at zero here.
55
+ *
56
+ * Textblocks only hold inline content, so they can never contain a list. Skipping their subtrees
57
+ * avoids visiting every text node in the range. Each list gets a single decoration carrying both
58
+ * attributes, which renders identically to two decorations over the same range at half the
59
+ * allocation and comparison cost.
60
+ */
61
+ var getDecorationsForRange = function getDecorationsForRange(doc, from, to) {
62
+ var decorations = [];
63
+ // this stack keeps track of each (nested) list to calculate the indentation level
64
+ var processedListsStack = [];
65
+ doc.nodesBetween(from, to, function (node, currentNodeStartPos) {
66
+ if (node.isTextblock) {
67
+ return false;
68
+ }
69
+ while (processedListsStack.length > 0) {
70
+ var lastList = processedListsStack[processedListsStack.length - 1];
71
+ // once we finish iterating over each innermost list, pop the stack to
72
+ // decrease the indent level attribute accordingly
73
+ if (currentNodeStartPos >= lastList.startPos + lastList.node.nodeSize) {
74
+ processedListsStack.pop();
75
+ } else {
76
+ break;
77
+ }
78
+ }
79
+ if ((0, _utils.isListNode)(node)) {
80
+ processedListsStack.push({
81
+ node: node,
82
+ startPos: currentNodeStartPos
83
+ });
84
+ var style = getItemCounterPaddingStyle(node);
85
+ decorations.push(_view.Decoration.node(currentNodeStartPos, currentNodeStartPos + node.nodeSize, _objectSpread({
86
+ 'data-indent-level': "".concat(processedListsStack.length)
87
+ }, style ? {
88
+ style: style
89
+ } : {})));
90
+ }
91
+ return true;
92
+ });
93
+ return decorations;
94
+ };
95
+
96
+ /**
97
+ * Full-document rebuild on the improved path, used when the decoration set has no previous value
98
+ * to update. The whole document is just one aligned range.
99
+ */
100
+ var getDecorationsForDocument = exports.getDecorationsForDocument = function getDecorationsForDocument(doc) {
101
+ return _view.DecorationSet.empty.add(doc, getDecorationsForRange(doc, 0, doc.content.size));
102
+ };
30
103
  var getDecorations = exports.getDecorations = function getDecorations(doc, _state, _featureFlags) {
31
104
  var decorations = [];
32
105
 
@@ -58,12 +131,12 @@ var getDecorations = exports.getDecorations = function getDecorations(doc, _stat
58
131
  'data-indent-level': "".concat(depth)
59
132
  }));
60
133
  if (node.type.name === 'orderedList') {
61
- var _node$attrs;
134
+ var _node$attrs2;
62
135
  // If a numbered list has item counters numbering >= 100, we'll need to add special
63
136
  // spacing to account for the extra digit chars
64
137
  var digitsSize = (0, _utils.getItemCounterDigitsSize)({
65
138
  itemsCount: node === null || node === void 0 ? void 0 : node.childCount,
66
- order: node === null || node === void 0 || (_node$attrs = node.attrs) === null || _node$attrs === void 0 ? void 0 : _node$attrs.order
139
+ order: node === null || node === void 0 || (_node$attrs2 = node.attrs) === null || _node$attrs2 === void 0 ? void 0 : _node$attrs2.order
67
140
  });
68
141
  if (digitsSize && digitsSize > 1) {
69
142
  decorations.push(_view.Decoration.node(from, to, {
@@ -75,6 +148,121 @@ var getDecorations = exports.getDecorations = function getDecorations(doc, _stat
75
148
  });
76
149
  return _view.DecorationSet.empty.add(doc, decorations);
77
150
  };
151
+
152
+ /**
153
+ * The parts of a transaction the decoration update needs, so that both `Transaction` and
154
+ * `ReadonlyTransaction` can be passed in.
155
+ */
156
+
157
+ /**
158
+ * Expands the ranges touched by a transaction out to whole top-level blocks. A list decoration
159
+ * depends only on the subtree of the top-level block containing it, so recomputing whole blocks is
160
+ * enough — and it keeps the depth calculation free of any ancestor accounting.
161
+ */
162
+ var getDirtyTopLevelRanges = function getDirtyTopLevelRanges(tr, doc) {
163
+ var docSize = doc.content.size;
164
+ var wholeDoc = [{
165
+ from: 0,
166
+ to: docSize
167
+ }];
168
+ var boundsAt = function boundsAt(pos) {
169
+ // resolving a position strictly inside a block gives us its boundaries in O(depth)
170
+ var inside = Math.max(1, Math.min(pos, Math.max(1, docSize - 1)));
171
+ var $inside = doc.resolve(inside);
172
+ if ($inside.depth > 0) {
173
+ return {
174
+ from: $inside.before(1),
175
+ to: $inside.after(1)
176
+ };
177
+ }
178
+ // exactly between two top-level blocks — cover both neighbours
179
+ var $before = doc.resolve(Math.max(1, inside - 1));
180
+ var $after = doc.resolve(Math.min(Math.max(1, docSize - 1), inside + 1));
181
+ return {
182
+ from: $before.depth > 0 ? $before.before(1) : 0,
183
+ to: $after.depth > 0 ? $after.after(1) : docSize
184
+ };
185
+ };
186
+ var ranges = [];
187
+ for (var index = 0; index < tr.steps.length; index++) {
188
+ var step = tr.steps[index];
189
+ // Duck-typed rather than matched on step class so that unknown step types fall back to a
190
+ // full recompute instead of being silently skipped.
191
+ // Ignored via go/ees005
192
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
193
+ var _ref = step,
194
+ from = _ref.from,
195
+ to = _ref.to,
196
+ pos = _ref.pos;
197
+ var positions = typeof from === 'number' && typeof to === 'number' ? [from, to] : typeof pos === 'number' ? [pos] : [];
198
+ if (positions.length === 0) {
199
+ return wholeDoc;
200
+ }
201
+
202
+ // Step positions are in the document before this step, so map through the later steps. The
203
+ // range start is biased left and the end biased right, so that neither slides across a
204
+ // block boundary and leaves a dirty block out of the recompute.
205
+ var remainder = tr.mapping.slice(index);
206
+ var start = boundsAt(remainder.map(Math.min.apply(Math, positions), -1));
207
+ var end = boundsAt(remainder.map(Math.max.apply(Math, positions), 1));
208
+ ranges.push({
209
+ from: Math.min(start.from, end.from),
210
+ to: Math.max(start.to, end.to)
211
+ });
212
+ }
213
+ ranges.sort(function (a, b) {
214
+ return a.from - b.from;
215
+ });
216
+ var merged = [];
217
+ for (var _i = 0, _ranges = ranges; _i < _ranges.length; _i++) {
218
+ var range = _ranges[_i];
219
+ var last = merged[merged.length - 1];
220
+ if (last && range.from <= last.to) {
221
+ last.to = Math.max(last.to, range.to);
222
+ } else {
223
+ merged.push(_objectSpread({}, range));
224
+ }
225
+ }
226
+ return merged;
227
+ };
228
+
229
+ /**
230
+ * Maps the previous decoration set through the transaction and recomputes only the top-level blocks
231
+ * it touched, instead of rebuilding the whole set from a full document scan.
232
+ */
233
+ var updateDecorations = exports.updateDecorations = function updateDecorations(previousDecorationSet, tr, doc) {
234
+ var decorationSet = previousDecorationSet.map(tr.mapping, doc);
235
+ var _iterator = _createForOfIteratorHelper(getDirtyTopLevelRanges(tr, doc)),
236
+ _step;
237
+ try {
238
+ var _loop = function _loop() {
239
+ var _step$value = _step.value,
240
+ from = _step$value.from,
241
+ to = _step$value.to;
242
+ // find() also returns decorations that merely touch the range, so removal is restricted to
243
+ // the ones fully inside it — those are exactly the decorations recomputed below. Removing a
244
+ // decoration that only touches the boundary would drop it permanently.
245
+ var stale = decorationSet.find(from, to).filter(function (decoration) {
246
+ return decoration.from >= from && decoration.to <= to;
247
+ });
248
+ if (stale.length > 0) {
249
+ decorationSet = decorationSet.remove(stale);
250
+ }
251
+ var fresh = getDecorationsForRange(doc, from, to);
252
+ if (fresh.length > 0) {
253
+ decorationSet = decorationSet.add(doc, fresh);
254
+ }
255
+ };
256
+ for (_iterator.s(); !(_step = _iterator.n()).done;) {
257
+ _loop();
258
+ }
259
+ } catch (err) {
260
+ _iterator.e(err);
261
+ } finally {
262
+ _iterator.f();
263
+ }
264
+ return decorationSet;
265
+ };
78
266
  var getListState = function getListState(doc, selection) {
79
267
  var _doc$type$schema$node = doc.type.schema.nodes,
80
268
  bulletList = _doc$type$schema$node.bulletList,
@@ -92,12 +280,19 @@ var getListState = function getListState(doc, selection) {
92
280
  orderedListDisabled: orderedListDisabled
93
281
  };
94
282
  };
95
- var handleDocChanged = function handleDocChanged(featureFlags) {
283
+ var handleDocChangedOld = function handleDocChangedOld(featureFlags) {
284
+ return function (tr, pluginState, editorState) {
285
+ var nextPluginState = handleSelectionChanged(tr, pluginState);
286
+ return _objectSpread(_objectSpread({}, nextPluginState), {}, {
287
+ decorationSet: getDecorations(tr.doc, editorState, featureFlags)
288
+ });
289
+ };
290
+ };
291
+ var handleDocChangedNew = function handleDocChangedNew() {
96
292
  return function (tr, pluginState, editorState) {
97
293
  var nextPluginState = handleSelectionChanged(tr, pluginState);
98
- var decorationSet = getDecorations(tr.doc, editorState, featureFlags);
99
294
  return _objectSpread(_objectSpread({}, nextPluginState), {}, {
100
- decorationSet: decorationSet
295
+ decorationSet: updateDecorations(pluginState.decorationSet, tr, tr.doc)
101
296
  });
102
297
  };
103
298
  };
@@ -123,7 +318,7 @@ var reducer = function reducer() {
123
318
  return state;
124
319
  };
125
320
  };
126
- var createInitialState = function createInitialState(featureFlags, api) {
321
+ var createInitialStateOld = function createInitialStateOld(featureFlags, api) {
127
322
  return function (state) {
128
323
  var isToolbarAIFCEnabled = Boolean(api === null || api === void 0 ? void 0 : api.toolbar);
129
324
  return _objectSpread(_objectSpread({}, isToolbarAIFCEnabled ? getListState(state.doc, state.selection) : initialState), {}, {
@@ -131,15 +326,25 @@ var createInitialState = function createInitialState(featureFlags, api) {
131
326
  });
132
327
  };
133
328
  };
329
+ var createInitialStateNew = function createInitialStateNew(featureFlags, api) {
330
+ return function (state) {
331
+ var isToolbarAIFCEnabled = Boolean(api === null || api === void 0 ? void 0 : api.toolbar);
332
+ return _objectSpread(_objectSpread({}, isToolbarAIFCEnabled ? getListState(state.doc, state.selection) : initialState), {}, {
333
+ decorationSet: getDecorationsForDocument(state.doc)
334
+ });
335
+ };
336
+ };
134
337
  var createPlugin = exports.createPlugin = function createPlugin(eventDispatch, featureFlags, api) {
135
338
  var _pluginFactory = (0, _utils.pluginFactory)(listPluginKey, reducer(), {
136
- onDocChanged: handleDocChanged(featureFlags),
339
+ // Resolved once per editor instance rather than per transaction, so the exposure event fires
340
+ // once and the decoration hot path stays free of experiment lookups.
341
+ onDocChanged: (0, _isExperimentEnabled.isExperimentEnabled)('platform_editor_list_performance_improv') ? handleDocChangedNew() : handleDocChangedOld(featureFlags),
137
342
  onSelectionChanged: handleSelectionChanged
138
343
  }),
139
344
  getPluginState = _pluginFactory.getPluginState,
140
345
  createPluginState = _pluginFactory.createPluginState;
141
346
  return new _safePlugin.SafePlugin({
142
- state: createPluginState(eventDispatch, createInitialState(featureFlags, api)),
347
+ state: createPluginState(eventDispatch, (0, _isExperimentEnabled.isExperimentEnabled)('platform_editor_list_performance_improv') ? createInitialStateNew(featureFlags, api) : createInitialStateOld(featureFlags, api)),
143
348
  key: listPluginKey,
144
349
  appendTransaction: function appendTransaction(transactions, _oldState, newState) {
145
350
  if (!(0, _expValEqualsNoExposure.expValEqualsNoExposure)('platform_editor_flexible_list_schema', 'isEnabled', true)) {
@@ -5,6 +5,7 @@ import { getItemCounterDigitsSize, isListNode, pluginFactory } from '@atlaskit/e
5
5
  import { PluginKey } from '@atlaskit/editor-prosemirror/state';
6
6
  import { findParentNodeOfType } from '@atlaskit/editor-prosemirror/utils';
7
7
  import { Decoration, DecorationSet } from '@atlaskit/editor-prosemirror/view';
8
+ import { isExperimentEnabled } from '@atlaskit/platform-feature-experiments/is-experiment-enabled';
8
9
  import { expValEqualsNoExposure } from '@atlaskit/tmp-editor-statsig/exp-val-equals-no-exposure';
9
10
  import { applyListNormalisationFixes } from './transforms';
10
11
  import { isWrappingPossible } from './utils/selection';
@@ -17,6 +18,74 @@ const initialState = {
17
18
  orderedListDisabled: false,
18
19
  decorationSet: DecorationSet.empty
19
20
  };
21
+
22
+ /**
23
+ * Numbered lists whose item counters reach 2+ digits need extra gutter spacing so the counter
24
+ * does not collide with the item content.
25
+ */
26
+ const getItemCounterPaddingStyle = node => {
27
+ var _node$attrs;
28
+ if (node.type.name !== 'orderedList') {
29
+ return undefined;
30
+ }
31
+ const digitsSize = getItemCounterDigitsSize({
32
+ itemsCount: node === null || node === void 0 ? void 0 : node.childCount,
33
+ order: node === null || node === void 0 ? void 0 : (_node$attrs = node.attrs) === null || _node$attrs === void 0 ? void 0 : _node$attrs.order
34
+ });
35
+ return digitsSize && digitsSize > 1 ? getOrderedListInlineStyles(digitsSize, 'string') : undefined;
36
+ };
37
+
38
+ /**
39
+ * Builds the decorations for a range aligned to top-level block boundaries — or for the whole
40
+ * document, which is the same thing. Because a top-level block has no list ancestors, the
41
+ * indentation depth always restarts at zero here.
42
+ *
43
+ * Textblocks only hold inline content, so they can never contain a list. Skipping their subtrees
44
+ * avoids visiting every text node in the range. Each list gets a single decoration carrying both
45
+ * attributes, which renders identically to two decorations over the same range at half the
46
+ * allocation and comparison cost.
47
+ */
48
+ const getDecorationsForRange = (doc, from, to) => {
49
+ const decorations = [];
50
+ // this stack keeps track of each (nested) list to calculate the indentation level
51
+ const processedListsStack = [];
52
+ doc.nodesBetween(from, to, (node, currentNodeStartPos) => {
53
+ if (node.isTextblock) {
54
+ return false;
55
+ }
56
+ while (processedListsStack.length > 0) {
57
+ const lastList = processedListsStack[processedListsStack.length - 1];
58
+ // once we finish iterating over each innermost list, pop the stack to
59
+ // decrease the indent level attribute accordingly
60
+ if (currentNodeStartPos >= lastList.startPos + lastList.node.nodeSize) {
61
+ processedListsStack.pop();
62
+ } else {
63
+ break;
64
+ }
65
+ }
66
+ if (isListNode(node)) {
67
+ processedListsStack.push({
68
+ node,
69
+ startPos: currentNodeStartPos
70
+ });
71
+ const style = getItemCounterPaddingStyle(node);
72
+ decorations.push(Decoration.node(currentNodeStartPos, currentNodeStartPos + node.nodeSize, {
73
+ 'data-indent-level': `${processedListsStack.length}`,
74
+ ...(style ? {
75
+ style
76
+ } : {})
77
+ }));
78
+ }
79
+ return true;
80
+ });
81
+ return decorations;
82
+ };
83
+
84
+ /**
85
+ * Full-document rebuild on the improved path, used when the decoration set has no previous value
86
+ * to update. The whole document is just one aligned range.
87
+ */
88
+ export const getDecorationsForDocument = doc => DecorationSet.empty.add(doc, getDecorationsForRange(doc, 0, doc.content.size));
20
89
  export const getDecorations = (doc, _state, _featureFlags) => {
21
90
  const decorations = [];
22
91
 
@@ -48,12 +117,12 @@ export const getDecorations = (doc, _state, _featureFlags) => {
48
117
  'data-indent-level': `${depth}`
49
118
  }));
50
119
  if (node.type.name === 'orderedList') {
51
- var _node$attrs;
120
+ var _node$attrs2;
52
121
  // If a numbered list has item counters numbering >= 100, we'll need to add special
53
122
  // spacing to account for the extra digit chars
54
123
  const digitsSize = getItemCounterDigitsSize({
55
124
  itemsCount: node === null || node === void 0 ? void 0 : node.childCount,
56
- order: node === null || node === void 0 ? void 0 : (_node$attrs = node.attrs) === null || _node$attrs === void 0 ? void 0 : _node$attrs.order
125
+ order: node === null || node === void 0 ? void 0 : (_node$attrs2 = node.attrs) === null || _node$attrs2 === void 0 ? void 0 : _node$attrs2.order
57
126
  });
58
127
  if (digitsSize && digitsSize > 1) {
59
128
  decorations.push(Decoration.node(from, to, {
@@ -65,6 +134,108 @@ export const getDecorations = (doc, _state, _featureFlags) => {
65
134
  });
66
135
  return DecorationSet.empty.add(doc, decorations);
67
136
  };
137
+
138
+ /**
139
+ * The parts of a transaction the decoration update needs, so that both `Transaction` and
140
+ * `ReadonlyTransaction` can be passed in.
141
+ */
142
+
143
+ /**
144
+ * Expands the ranges touched by a transaction out to whole top-level blocks. A list decoration
145
+ * depends only on the subtree of the top-level block containing it, so recomputing whole blocks is
146
+ * enough — and it keeps the depth calculation free of any ancestor accounting.
147
+ */
148
+ const getDirtyTopLevelRanges = (tr, doc) => {
149
+ const docSize = doc.content.size;
150
+ const wholeDoc = [{
151
+ from: 0,
152
+ to: docSize
153
+ }];
154
+ const boundsAt = pos => {
155
+ // resolving a position strictly inside a block gives us its boundaries in O(depth)
156
+ const inside = Math.max(1, Math.min(pos, Math.max(1, docSize - 1)));
157
+ const $inside = doc.resolve(inside);
158
+ if ($inside.depth > 0) {
159
+ return {
160
+ from: $inside.before(1),
161
+ to: $inside.after(1)
162
+ };
163
+ }
164
+ // exactly between two top-level blocks — cover both neighbours
165
+ const $before = doc.resolve(Math.max(1, inside - 1));
166
+ const $after = doc.resolve(Math.min(Math.max(1, docSize - 1), inside + 1));
167
+ return {
168
+ from: $before.depth > 0 ? $before.before(1) : 0,
169
+ to: $after.depth > 0 ? $after.after(1) : docSize
170
+ };
171
+ };
172
+ const ranges = [];
173
+ for (let index = 0; index < tr.steps.length; index++) {
174
+ const step = tr.steps[index];
175
+ // Duck-typed rather than matched on step class so that unknown step types fall back to a
176
+ // full recompute instead of being silently skipped.
177
+ // Ignored via go/ees005
178
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
179
+ const {
180
+ from,
181
+ to,
182
+ pos
183
+ } = step;
184
+ const positions = typeof from === 'number' && typeof to === 'number' ? [from, to] : typeof pos === 'number' ? [pos] : [];
185
+ if (positions.length === 0) {
186
+ return wholeDoc;
187
+ }
188
+
189
+ // Step positions are in the document before this step, so map through the later steps. The
190
+ // range start is biased left and the end biased right, so that neither slides across a
191
+ // block boundary and leaves a dirty block out of the recompute.
192
+ const remainder = tr.mapping.slice(index);
193
+ const start = boundsAt(remainder.map(Math.min(...positions), -1));
194
+ const end = boundsAt(remainder.map(Math.max(...positions), 1));
195
+ ranges.push({
196
+ from: Math.min(start.from, end.from),
197
+ to: Math.max(start.to, end.to)
198
+ });
199
+ }
200
+ ranges.sort((a, b) => a.from - b.from);
201
+ const merged = [];
202
+ for (const range of ranges) {
203
+ const last = merged[merged.length - 1];
204
+ if (last && range.from <= last.to) {
205
+ last.to = Math.max(last.to, range.to);
206
+ } else {
207
+ merged.push({
208
+ ...range
209
+ });
210
+ }
211
+ }
212
+ return merged;
213
+ };
214
+
215
+ /**
216
+ * Maps the previous decoration set through the transaction and recomputes only the top-level blocks
217
+ * it touched, instead of rebuilding the whole set from a full document scan.
218
+ */
219
+ export const updateDecorations = (previousDecorationSet, tr, doc) => {
220
+ let decorationSet = previousDecorationSet.map(tr.mapping, doc);
221
+ for (const {
222
+ from,
223
+ to
224
+ } of getDirtyTopLevelRanges(tr, doc)) {
225
+ // find() also returns decorations that merely touch the range, so removal is restricted to
226
+ // the ones fully inside it — those are exactly the decorations recomputed below. Removing a
227
+ // decoration that only touches the boundary would drop it permanently.
228
+ const stale = decorationSet.find(from, to).filter(decoration => decoration.from >= from && decoration.to <= to);
229
+ if (stale.length > 0) {
230
+ decorationSet = decorationSet.remove(stale);
231
+ }
232
+ const fresh = getDecorationsForRange(doc, from, to);
233
+ if (fresh.length > 0) {
234
+ decorationSet = decorationSet.add(doc, fresh);
235
+ }
236
+ }
237
+ return decorationSet;
238
+ };
68
239
  const getListState = (doc, selection) => {
69
240
  const {
70
241
  bulletList,
@@ -83,12 +254,18 @@ const getListState = (doc, selection) => {
83
254
  orderedListDisabled
84
255
  };
85
256
  };
86
- const handleDocChanged = featureFlags => (tr, pluginState, editorState) => {
257
+ const handleDocChangedOld = featureFlags => (tr, pluginState, editorState) => {
87
258
  const nextPluginState = handleSelectionChanged(tr, pluginState);
88
- const decorationSet = getDecorations(tr.doc, editorState, featureFlags);
89
259
  return {
90
260
  ...nextPluginState,
91
- decorationSet
261
+ decorationSet: getDecorations(tr.doc, editorState, featureFlags)
262
+ };
263
+ };
264
+ const handleDocChangedNew = () => (tr, pluginState, editorState) => {
265
+ const nextPluginState = handleSelectionChanged(tr, pluginState);
266
+ return {
267
+ ...nextPluginState,
268
+ decorationSet: updateDecorations(pluginState.decorationSet, tr, tr.doc)
92
269
  };
93
270
  };
94
271
  const handleSelectionChanged = (tr, pluginState) => {
@@ -113,7 +290,7 @@ const handleSelectionChanged = (tr, pluginState) => {
113
290
  const reducer = () => state => {
114
291
  return state;
115
292
  };
116
- const createInitialState = (featureFlags, api) => state => {
293
+ const createInitialStateOld = (featureFlags, api) => state => {
117
294
  const isToolbarAIFCEnabled = Boolean(api === null || api === void 0 ? void 0 : api.toolbar);
118
295
  return {
119
296
  // When plugin is initialised, editor state is defined with selection
@@ -122,16 +299,27 @@ const createInitialState = (featureFlags, api) => state => {
122
299
  decorationSet: getDecorations(state.doc, state, featureFlags)
123
300
  };
124
301
  };
302
+ const createInitialStateNew = (featureFlags, api) => state => {
303
+ const isToolbarAIFCEnabled = Boolean(api === null || api === void 0 ? void 0 : api.toolbar);
304
+ return {
305
+ // When plugin is initialised, editor state is defined with selection
306
+ // hence returning the list state based on the selection to avoid list button in primary toolbar flickering during initial load
307
+ ...(isToolbarAIFCEnabled ? getListState(state.doc, state.selection) : initialState),
308
+ decorationSet: getDecorationsForDocument(state.doc)
309
+ };
310
+ };
125
311
  export const createPlugin = (eventDispatch, featureFlags, api) => {
126
312
  const {
127
313
  getPluginState,
128
314
  createPluginState
129
315
  } = pluginFactory(listPluginKey, reducer(), {
130
- onDocChanged: handleDocChanged(featureFlags),
316
+ // Resolved once per editor instance rather than per transaction, so the exposure event fires
317
+ // once and the decoration hot path stays free of experiment lookups.
318
+ onDocChanged: isExperimentEnabled('platform_editor_list_performance_improv') ? handleDocChangedNew() : handleDocChangedOld(featureFlags),
131
319
  onSelectionChanged: handleSelectionChanged
132
320
  });
133
321
  return new SafePlugin({
134
- state: createPluginState(eventDispatch, createInitialState(featureFlags, api)),
322
+ state: createPluginState(eventDispatch, isExperimentEnabled('platform_editor_list_performance_improv') ? createInitialStateNew(featureFlags, api) : createInitialStateOld(featureFlags, api)),
135
323
  key: listPluginKey,
136
324
  appendTransaction(transactions, _oldState, newState) {
137
325
  if (!expValEqualsNoExposure('platform_editor_flexible_list_schema', 'isEnabled', true)) {
@@ -1,4 +1,7 @@
1
1
  import _defineProperty from "@babel/runtime/helpers/defineProperty";
2
+ function _createForOfIteratorHelper(r, e) { var t = "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; if (!t) { if (Array.isArray(r) || (t = _unsupportedIterableToArray(r)) || e && r && "number" == typeof r.length) { t && (r = t); var _n = 0, F = function F() {}; return { s: F, n: function n() { return _n >= r.length ? { done: !0 } : { done: !1, value: r[_n++] }; }, e: function e(r) { throw r; }, f: F }; } throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } var o, a = !0, u = !1; return { s: function s() { t = t.call(r); }, n: function n() { var r = t.next(); return a = r.done, r; }, e: function e(r) { u = !0, o = r; }, f: function f() { try { a || null == t.return || t.return(); } finally { if (u) throw o; } } }; }
3
+ function _unsupportedIterableToArray(r, a) { if (r) { if ("string" == typeof r) return _arrayLikeToArray(r, a); var t = {}.toString.call(r).slice(8, -1); return "Object" === t && r.constructor && (t = r.constructor.name), "Map" === t || "Set" === t ? Array.from(r) : "Arguments" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) ? _arrayLikeToArray(r, a) : void 0; } }
4
+ function _arrayLikeToArray(r, a) { (null == a || a > r.length) && (a = r.length); for (var e = 0, n = Array(a); e < a; e++) n[e] = r[e]; return n; }
2
5
  function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
3
6
  function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
4
7
  import { SafePlugin } from '@atlaskit/editor-common/safe-plugin';
@@ -8,6 +11,7 @@ import { getItemCounterDigitsSize, isListNode, pluginFactory } from '@atlaskit/e
8
11
  import { PluginKey } from '@atlaskit/editor-prosemirror/state';
9
12
  import { findParentNodeOfType } from '@atlaskit/editor-prosemirror/utils';
10
13
  import { Decoration, DecorationSet } from '@atlaskit/editor-prosemirror/view';
14
+ import { isExperimentEnabled } from '@atlaskit/platform-feature-experiments/is-experiment-enabled';
11
15
  import { expValEqualsNoExposure } from '@atlaskit/tmp-editor-statsig/exp-val-equals-no-exposure';
12
16
  import { applyListNormalisationFixes } from './transforms';
13
17
  import { isWrappingPossible } from './utils/selection';
@@ -20,6 +24,75 @@ var initialState = {
20
24
  orderedListDisabled: false,
21
25
  decorationSet: DecorationSet.empty
22
26
  };
27
+
28
+ /**
29
+ * Numbered lists whose item counters reach 2+ digits need extra gutter spacing so the counter
30
+ * does not collide with the item content.
31
+ */
32
+ var getItemCounterPaddingStyle = function getItemCounterPaddingStyle(node) {
33
+ var _node$attrs;
34
+ if (node.type.name !== 'orderedList') {
35
+ return undefined;
36
+ }
37
+ var digitsSize = getItemCounterDigitsSize({
38
+ itemsCount: node === null || node === void 0 ? void 0 : node.childCount,
39
+ order: node === null || node === void 0 || (_node$attrs = node.attrs) === null || _node$attrs === void 0 ? void 0 : _node$attrs.order
40
+ });
41
+ return digitsSize && digitsSize > 1 ? getOrderedListInlineStyles(digitsSize, 'string') : undefined;
42
+ };
43
+
44
+ /**
45
+ * Builds the decorations for a range aligned to top-level block boundaries — or for the whole
46
+ * document, which is the same thing. Because a top-level block has no list ancestors, the
47
+ * indentation depth always restarts at zero here.
48
+ *
49
+ * Textblocks only hold inline content, so they can never contain a list. Skipping their subtrees
50
+ * avoids visiting every text node in the range. Each list gets a single decoration carrying both
51
+ * attributes, which renders identically to two decorations over the same range at half the
52
+ * allocation and comparison cost.
53
+ */
54
+ var getDecorationsForRange = function getDecorationsForRange(doc, from, to) {
55
+ var decorations = [];
56
+ // this stack keeps track of each (nested) list to calculate the indentation level
57
+ var processedListsStack = [];
58
+ doc.nodesBetween(from, to, function (node, currentNodeStartPos) {
59
+ if (node.isTextblock) {
60
+ return false;
61
+ }
62
+ while (processedListsStack.length > 0) {
63
+ var lastList = processedListsStack[processedListsStack.length - 1];
64
+ // once we finish iterating over each innermost list, pop the stack to
65
+ // decrease the indent level attribute accordingly
66
+ if (currentNodeStartPos >= lastList.startPos + lastList.node.nodeSize) {
67
+ processedListsStack.pop();
68
+ } else {
69
+ break;
70
+ }
71
+ }
72
+ if (isListNode(node)) {
73
+ processedListsStack.push({
74
+ node: node,
75
+ startPos: currentNodeStartPos
76
+ });
77
+ var style = getItemCounterPaddingStyle(node);
78
+ decorations.push(Decoration.node(currentNodeStartPos, currentNodeStartPos + node.nodeSize, _objectSpread({
79
+ 'data-indent-level': "".concat(processedListsStack.length)
80
+ }, style ? {
81
+ style: style
82
+ } : {})));
83
+ }
84
+ return true;
85
+ });
86
+ return decorations;
87
+ };
88
+
89
+ /**
90
+ * Full-document rebuild on the improved path, used when the decoration set has no previous value
91
+ * to update. The whole document is just one aligned range.
92
+ */
93
+ export var getDecorationsForDocument = function getDecorationsForDocument(doc) {
94
+ return DecorationSet.empty.add(doc, getDecorationsForRange(doc, 0, doc.content.size));
95
+ };
23
96
  export var getDecorations = function getDecorations(doc, _state, _featureFlags) {
24
97
  var decorations = [];
25
98
 
@@ -51,12 +124,12 @@ export var getDecorations = function getDecorations(doc, _state, _featureFlags)
51
124
  'data-indent-level': "".concat(depth)
52
125
  }));
53
126
  if (node.type.name === 'orderedList') {
54
- var _node$attrs;
127
+ var _node$attrs2;
55
128
  // If a numbered list has item counters numbering >= 100, we'll need to add special
56
129
  // spacing to account for the extra digit chars
57
130
  var digitsSize = getItemCounterDigitsSize({
58
131
  itemsCount: node === null || node === void 0 ? void 0 : node.childCount,
59
- order: node === null || node === void 0 || (_node$attrs = node.attrs) === null || _node$attrs === void 0 ? void 0 : _node$attrs.order
132
+ order: node === null || node === void 0 || (_node$attrs2 = node.attrs) === null || _node$attrs2 === void 0 ? void 0 : _node$attrs2.order
60
133
  });
61
134
  if (digitsSize && digitsSize > 1) {
62
135
  decorations.push(Decoration.node(from, to, {
@@ -68,6 +141,121 @@ export var getDecorations = function getDecorations(doc, _state, _featureFlags)
68
141
  });
69
142
  return DecorationSet.empty.add(doc, decorations);
70
143
  };
144
+
145
+ /**
146
+ * The parts of a transaction the decoration update needs, so that both `Transaction` and
147
+ * `ReadonlyTransaction` can be passed in.
148
+ */
149
+
150
+ /**
151
+ * Expands the ranges touched by a transaction out to whole top-level blocks. A list decoration
152
+ * depends only on the subtree of the top-level block containing it, so recomputing whole blocks is
153
+ * enough — and it keeps the depth calculation free of any ancestor accounting.
154
+ */
155
+ var getDirtyTopLevelRanges = function getDirtyTopLevelRanges(tr, doc) {
156
+ var docSize = doc.content.size;
157
+ var wholeDoc = [{
158
+ from: 0,
159
+ to: docSize
160
+ }];
161
+ var boundsAt = function boundsAt(pos) {
162
+ // resolving a position strictly inside a block gives us its boundaries in O(depth)
163
+ var inside = Math.max(1, Math.min(pos, Math.max(1, docSize - 1)));
164
+ var $inside = doc.resolve(inside);
165
+ if ($inside.depth > 0) {
166
+ return {
167
+ from: $inside.before(1),
168
+ to: $inside.after(1)
169
+ };
170
+ }
171
+ // exactly between two top-level blocks — cover both neighbours
172
+ var $before = doc.resolve(Math.max(1, inside - 1));
173
+ var $after = doc.resolve(Math.min(Math.max(1, docSize - 1), inside + 1));
174
+ return {
175
+ from: $before.depth > 0 ? $before.before(1) : 0,
176
+ to: $after.depth > 0 ? $after.after(1) : docSize
177
+ };
178
+ };
179
+ var ranges = [];
180
+ for (var index = 0; index < tr.steps.length; index++) {
181
+ var step = tr.steps[index];
182
+ // Duck-typed rather than matched on step class so that unknown step types fall back to a
183
+ // full recompute instead of being silently skipped.
184
+ // Ignored via go/ees005
185
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
186
+ var _ref = step,
187
+ from = _ref.from,
188
+ to = _ref.to,
189
+ pos = _ref.pos;
190
+ var positions = typeof from === 'number' && typeof to === 'number' ? [from, to] : typeof pos === 'number' ? [pos] : [];
191
+ if (positions.length === 0) {
192
+ return wholeDoc;
193
+ }
194
+
195
+ // Step positions are in the document before this step, so map through the later steps. The
196
+ // range start is biased left and the end biased right, so that neither slides across a
197
+ // block boundary and leaves a dirty block out of the recompute.
198
+ var remainder = tr.mapping.slice(index);
199
+ var start = boundsAt(remainder.map(Math.min.apply(Math, positions), -1));
200
+ var end = boundsAt(remainder.map(Math.max.apply(Math, positions), 1));
201
+ ranges.push({
202
+ from: Math.min(start.from, end.from),
203
+ to: Math.max(start.to, end.to)
204
+ });
205
+ }
206
+ ranges.sort(function (a, b) {
207
+ return a.from - b.from;
208
+ });
209
+ var merged = [];
210
+ for (var _i = 0, _ranges = ranges; _i < _ranges.length; _i++) {
211
+ var range = _ranges[_i];
212
+ var last = merged[merged.length - 1];
213
+ if (last && range.from <= last.to) {
214
+ last.to = Math.max(last.to, range.to);
215
+ } else {
216
+ merged.push(_objectSpread({}, range));
217
+ }
218
+ }
219
+ return merged;
220
+ };
221
+
222
+ /**
223
+ * Maps the previous decoration set through the transaction and recomputes only the top-level blocks
224
+ * it touched, instead of rebuilding the whole set from a full document scan.
225
+ */
226
+ export var updateDecorations = function updateDecorations(previousDecorationSet, tr, doc) {
227
+ var decorationSet = previousDecorationSet.map(tr.mapping, doc);
228
+ var _iterator = _createForOfIteratorHelper(getDirtyTopLevelRanges(tr, doc)),
229
+ _step;
230
+ try {
231
+ var _loop = function _loop() {
232
+ var _step$value = _step.value,
233
+ from = _step$value.from,
234
+ to = _step$value.to;
235
+ // find() also returns decorations that merely touch the range, so removal is restricted to
236
+ // the ones fully inside it — those are exactly the decorations recomputed below. Removing a
237
+ // decoration that only touches the boundary would drop it permanently.
238
+ var stale = decorationSet.find(from, to).filter(function (decoration) {
239
+ return decoration.from >= from && decoration.to <= to;
240
+ });
241
+ if (stale.length > 0) {
242
+ decorationSet = decorationSet.remove(stale);
243
+ }
244
+ var fresh = getDecorationsForRange(doc, from, to);
245
+ if (fresh.length > 0) {
246
+ decorationSet = decorationSet.add(doc, fresh);
247
+ }
248
+ };
249
+ for (_iterator.s(); !(_step = _iterator.n()).done;) {
250
+ _loop();
251
+ }
252
+ } catch (err) {
253
+ _iterator.e(err);
254
+ } finally {
255
+ _iterator.f();
256
+ }
257
+ return decorationSet;
258
+ };
71
259
  var getListState = function getListState(doc, selection) {
72
260
  var _doc$type$schema$node = doc.type.schema.nodes,
73
261
  bulletList = _doc$type$schema$node.bulletList,
@@ -85,12 +273,19 @@ var getListState = function getListState(doc, selection) {
85
273
  orderedListDisabled: orderedListDisabled
86
274
  };
87
275
  };
88
- var handleDocChanged = function handleDocChanged(featureFlags) {
276
+ var handleDocChangedOld = function handleDocChangedOld(featureFlags) {
277
+ return function (tr, pluginState, editorState) {
278
+ var nextPluginState = handleSelectionChanged(tr, pluginState);
279
+ return _objectSpread(_objectSpread({}, nextPluginState), {}, {
280
+ decorationSet: getDecorations(tr.doc, editorState, featureFlags)
281
+ });
282
+ };
283
+ };
284
+ var handleDocChangedNew = function handleDocChangedNew() {
89
285
  return function (tr, pluginState, editorState) {
90
286
  var nextPluginState = handleSelectionChanged(tr, pluginState);
91
- var decorationSet = getDecorations(tr.doc, editorState, featureFlags);
92
287
  return _objectSpread(_objectSpread({}, nextPluginState), {}, {
93
- decorationSet: decorationSet
288
+ decorationSet: updateDecorations(pluginState.decorationSet, tr, tr.doc)
94
289
  });
95
290
  };
96
291
  };
@@ -116,7 +311,7 @@ var reducer = function reducer() {
116
311
  return state;
117
312
  };
118
313
  };
119
- var createInitialState = function createInitialState(featureFlags, api) {
314
+ var createInitialStateOld = function createInitialStateOld(featureFlags, api) {
120
315
  return function (state) {
121
316
  var isToolbarAIFCEnabled = Boolean(api === null || api === void 0 ? void 0 : api.toolbar);
122
317
  return _objectSpread(_objectSpread({}, isToolbarAIFCEnabled ? getListState(state.doc, state.selection) : initialState), {}, {
@@ -124,15 +319,25 @@ var createInitialState = function createInitialState(featureFlags, api) {
124
319
  });
125
320
  };
126
321
  };
322
+ var createInitialStateNew = function createInitialStateNew(featureFlags, api) {
323
+ return function (state) {
324
+ var isToolbarAIFCEnabled = Boolean(api === null || api === void 0 ? void 0 : api.toolbar);
325
+ return _objectSpread(_objectSpread({}, isToolbarAIFCEnabled ? getListState(state.doc, state.selection) : initialState), {}, {
326
+ decorationSet: getDecorationsForDocument(state.doc)
327
+ });
328
+ };
329
+ };
127
330
  export var createPlugin = function createPlugin(eventDispatch, featureFlags, api) {
128
331
  var _pluginFactory = pluginFactory(listPluginKey, reducer(), {
129
- onDocChanged: handleDocChanged(featureFlags),
332
+ // Resolved once per editor instance rather than per transaction, so the exposure event fires
333
+ // once and the decoration hot path stays free of experiment lookups.
334
+ onDocChanged: isExperimentEnabled('platform_editor_list_performance_improv') ? handleDocChangedNew() : handleDocChangedOld(featureFlags),
130
335
  onSelectionChanged: handleSelectionChanged
131
336
  }),
132
337
  getPluginState = _pluginFactory.getPluginState,
133
338
  createPluginState = _pluginFactory.createPluginState;
134
339
  return new SafePlugin({
135
- state: createPluginState(eventDispatch, createInitialState(featureFlags, api)),
340
+ state: createPluginState(eventDispatch, isExperimentEnabled('platform_editor_list_performance_improv') ? createInitialStateNew(featureFlags, api) : createInitialStateOld(featureFlags, api)),
136
341
  key: listPluginKey,
137
342
  appendTransaction: function appendTransaction(transactions, _oldState, newState) {
138
343
  if (!expValEqualsNoExposure('platform_editor_flexible_list_schema', 'isEnabled', true)) {
@@ -2,11 +2,27 @@ import type { Dispatch } from '@atlaskit/editor-common/event-dispatcher';
2
2
  import { SafePlugin } from '@atlaskit/editor-common/safe-plugin';
3
3
  import type { ExtractInjectionAPI, FeatureFlags } from '@atlaskit/editor-common/types';
4
4
  import type { Node } from '@atlaskit/editor-prosemirror/model';
5
- import type { EditorState } from '@atlaskit/editor-prosemirror/state';
5
+ import type { EditorState, ReadonlyTransaction } from '@atlaskit/editor-prosemirror/state';
6
6
  import { PluginKey } from '@atlaskit/editor-prosemirror/state';
7
7
  import { DecorationSet } from '@atlaskit/editor-prosemirror/view';
8
8
  import type { ListPlugin } from '../listPluginType';
9
9
  import type { ListState } from '../types';
10
10
  export declare const pluginKey: PluginKey<ListState>;
11
+ /**
12
+ * Full-document rebuild on the improved path, used when the decoration set has no previous value
13
+ * to update. The whole document is just one aligned range.
14
+ */
15
+ export declare const getDecorationsForDocument: (doc: Node) => DecorationSet;
11
16
  export declare const getDecorations: (doc: Node, _state: EditorState, _featureFlags: FeatureFlags) => DecorationSet;
17
+ /**
18
+ * The parts of a transaction the decoration update needs, so that both `Transaction` and
19
+ * `ReadonlyTransaction` can be passed in.
20
+ */
21
+ type TransactionSteps = Pick<ReadonlyTransaction, 'mapping' | 'steps'>;
22
+ /**
23
+ * Maps the previous decoration set through the transaction and recomputes only the top-level blocks
24
+ * it touched, instead of rebuilding the whole set from a full document scan.
25
+ */
26
+ export declare const updateDecorations: (previousDecorationSet: DecorationSet, tr: TransactionSteps, doc: Node) => DecorationSet;
12
27
  export declare const createPlugin: (eventDispatch: Dispatch, featureFlags: FeatureFlags, api?: ExtractInjectionAPI<ListPlugin>) => SafePlugin;
28
+ export {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@atlaskit/editor-plugin-list",
3
- "version": "19.0.1",
3
+ "version": "19.0.4",
4
4
  "description": "List plugin for @atlaskit/editor-core",
5
5
  "author": "Atlassian Pty Ltd",
6
6
  "license": "Apache-2.0",
@@ -33,7 +33,7 @@
33
33
  "@atlaskit/platform-feature-flags": "^2.2.0",
34
34
  "@atlaskit/prosemirror-history": "^1.2.0",
35
35
  "@atlaskit/prosemirror-input-rules": "^4.1.0",
36
- "@atlaskit/tmp-editor-statsig": "^180.0.0",
36
+ "@atlaskit/tmp-editor-statsig": "^182.0.0",
37
37
  "@babel/runtime": "^7.0.0"
38
38
  },
39
39
  "peerDependencies": {