@atlaskit/editor-plugin-layout 16.0.15 → 16.0.16

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,26 @@
1
1
  # @atlaskit/editor-plugin-layout
2
2
 
3
+ ## 16.0.16
4
+
5
+ ### Patch Changes
6
+
7
+ - [`288e340380dbb`](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/commits/288e340380dbb) -
8
+ Behind experiment platform_editor_reduce_event_listener_count: cut the number of native DOM event
9
+ listeners the editor registers per node. React 18 registers a bubble and a capture listener for
10
+ every supported event type on each portal container (~130 listeners) and never removes them, so
11
+ (1) node views whose render() returns null no longer mount a portal at all, and (2) the breakout
12
+ resize handle tooltip portal is deferred until the pointer enters the node instead of being
13
+ mounted for every resizable node up front.
14
+
15
+ Also reduces the work the layout column divider decorations do on every `decorations(state)` read:
16
+ the mousedown listener is unbound when the widget decoration is destroyed, the widget DOM is built
17
+ lazily rather than once per call, the document walk no longer descends into node types that cannot
18
+ contain a layout, and the decoration key is derived from the layout's ordinal rather than its
19
+ position so an edit above a layout no longer invalidates and rebuilds every divider below it. The
20
+ widget now resolves its own layout section and column index from its live position at mousedown.
21
+
22
+ - Updated dependencies
23
+
3
24
  ## 16.0.15
4
25
 
5
26
  ### Patch Changes
@@ -11,6 +11,7 @@ var _analytics = require("@atlaskit/editor-common/analytics");
11
11
  var _model = require("@atlaskit/editor-prosemirror/model");
12
12
  var _state = require("@atlaskit/editor-prosemirror/state");
13
13
  var _view = require("@atlaskit/editor-prosemirror/view");
14
+ var _isExperimentEnabled = require("@atlaskit/platform-feature-experiments/is-experiment-enabled");
14
15
  var _platformFeatureFlags = require("@atlaskit/platform-feature-flags");
15
16
  var _consts = require("./consts");
16
17
  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; }
@@ -253,13 +254,51 @@ var onDragCancel = function onDragCancel() {
253
254
  onDragEnd(dragState.lastClientX);
254
255
  };
255
256
 
257
+ /**
258
+ * Resolves the layout section a divider sits in and which pair of columns it separates.
259
+ *
260
+ * When `getPos` is supplied (under `platform_editor_reduce_event_listener_count`) this is derived
261
+ * from the widget's own live position, which ProseMirror keeps up to date. That is what lets the
262
+ * decoration key omit the section position: the key previously embedded it, so inserting a single
263
+ * character above a layout changed the key of every divider below and forced ProseMirror to destroy
264
+ * and rebuild all of them. Without `getPos` it falls back to the values captured when the widget
265
+ * was created.
266
+ */
267
+ var resolveDividerTarget = function resolveDividerTarget(view, sectionPos, columnIndex, getPos) {
268
+ if (!getPos) {
269
+ var sectionNode = view.state.doc.nodeAt(sectionPos);
270
+ return sectionNode ? {
271
+ sectionNode: sectionNode,
272
+ sectionPos: sectionPos,
273
+ leftColIndex: columnIndex - 1
274
+ } : null;
275
+ }
276
+ var pos = getPos();
277
+ if (pos === undefined) {
278
+ return null;
279
+ }
280
+
281
+ // The widget is anchored at the position of the column it precedes, which resolves to a
282
+ // position directly inside the layout section.
283
+ var $pos = view.state.doc.resolve(pos);
284
+ var colIndex = $pos.index();
285
+ if ($pos.parent.type !== view.state.schema.nodes.layoutSection || colIndex < 1) {
286
+ return null;
287
+ }
288
+ return {
289
+ sectionNode: $pos.parent,
290
+ sectionPos: $pos.before(),
291
+ leftColIndex: colIndex - 1
292
+ };
293
+ };
294
+
256
295
  /**
257
296
  * Creates a column divider widget DOM element with drag-to-resize interaction for
258
297
  * the adjacent layout columns. During drag, flex-basis is mutated directly on the
259
298
  * column DOM elements for zero-overhead visual feedback (no PM transactions).
260
299
  * A single undoable PM transaction is dispatched on mouseup to commit the final widths.
261
300
  */
262
- var createColumnDividerWidget = function createColumnDividerWidget(view, sectionPos, columnIndex, editorAnalyticsAPI) {
301
+ var createColumnDividerWidget = function createColumnDividerWidget(view, sectionPos, columnIndex, editorAnalyticsAPI, getPos) {
263
302
  var ownerDoc = view.dom.ownerDocument;
264
303
 
265
304
  // Outer container: wide transparent hit area for easy grabbing, zero flex footprint
@@ -276,17 +315,19 @@ var createColumnDividerWidget = function createColumnDividerWidget(view, section
276
315
  var thumb = ownerDoc.createElement('div');
277
316
  thumb.classList.add(layoutColumnDividerThumbClassName);
278
317
  rail.appendChild(thumb);
279
- var leftColIndex = columnIndex - 1;
280
- (0, _bindEventListener.bind)(divider, {
318
+ var unbindMouseDown = (0, _bindEventListener.bind)(divider, {
281
319
  type: 'mousedown',
282
320
  listener: function listener(e) {
283
321
  var _ownerDoc$defaultView;
284
322
  e.preventDefault();
285
323
  e.stopPropagation();
286
- var sectionNode = view.state.doc.nodeAt(sectionPos);
287
- if (!sectionNode) {
324
+ var resolved = resolveDividerTarget(view, sectionPos, columnIndex, getPos);
325
+ if (!resolved) {
288
326
  return;
289
327
  }
328
+ var sectionNode = resolved.sectionNode,
329
+ leftColIndex = resolved.leftColIndex,
330
+ resolvedSectionPos = resolved.sectionPos;
290
331
 
291
332
  // Get the initial widths of the two adjacent columns
292
333
  var leftCol = null;
@@ -359,7 +400,7 @@ var createColumnDividerWidget = function createColumnDividerWidget(view, section
359
400
  lastClientX: e.clientX,
360
401
  rafId: null,
361
402
  view: view,
362
- sectionPos: sectionPos,
403
+ sectionPos: resolvedSectionPos,
363
404
  leftColIndex: leftColIndex,
364
405
  leftColEl: leftColEl,
365
406
  rightColEl: rightColEl,
@@ -379,7 +420,10 @@ var createColumnDividerWidget = function createColumnDividerWidget(view, section
379
420
  ownerDoc.body.style.cursor = 'col-resize';
380
421
  }
381
422
  });
382
- return divider;
423
+ return {
424
+ target: divider,
425
+ destroy: unbindMouseDown
426
+ };
383
427
  };
384
428
 
385
429
  /**
@@ -391,29 +435,74 @@ var getColumnDividerDecorations = exports.getColumnDividerDecorations = function
391
435
  if (!view) {
392
436
  return decorations;
393
437
  }
394
- var layoutSection = state.schema.nodes.layoutSection;
438
+ var _state$schema$nodes = state.schema.nodes,
439
+ layoutSection = _state$schema$nodes.layoutSection,
440
+ bodiedSyncBlock = _state$schema$nodes.bodiedSyncBlock;
441
+
442
+ // Read once per call: `isExperimentEnabled` fires an exposure event, so checking it per divider
443
+ // would emit one per divider per `decorations(state)` read.
444
+ var isScopedWalkEnabled = (0, _isExperimentEnabled.isExperimentEnabled)('platform_editor_reduce_event_listener_count');
445
+ var sectionIndex = 0;
395
446
  state.doc.descendants(function (node, pos) {
396
447
  if (node.type === layoutSection) {
448
+ // This layout's ordinal in the document, used below as a position-independent key.
449
+ var sectionOrdinal = sectionIndex++;
397
450
  // Walk through layout column children and add dividers between them
398
- node.forEach(function (child, offset, index) {
451
+ node.forEach(function (_, offset, index) {
399
452
  // Add a divider widget BEFORE every column except the first
400
453
  if (index > 0) {
401
454
  var sectionPos = pos;
402
455
  var colIndex = index;
403
456
  var widgetPos = pos + offset + 1; // position at the start of this column
404
- decorations.push(_view.Decoration.widget(widgetPos, function () {
405
- return createColumnDividerWidget(view, sectionPos, colIndex, editorAnalyticsAPI);
406
- }, {
407
- side: -1,
408
- // place before the position
409
- key: "layout-col-divider-".concat(pos, "-").concat(index),
410
- ignoreSelection: true
411
- }));
457
+
458
+ if (!isScopedWalkEnabled) {
459
+ decorations.push(_view.Decoration.widget(widgetPos, function () {
460
+ return createColumnDividerWidget(view, sectionPos, colIndex, editorAnalyticsAPI).target;
461
+ }, {
462
+ side: -1,
463
+ // place before the position
464
+ key: "layout-col-divider-".concat(pos, "-").concat(index),
465
+ ignoreSelection: true
466
+ }));
467
+ } else {
468
+ // Keyed by the layout's ordinal rather than its position, so an edit elsewhere in
469
+ // the document doesn't invalidate it. A position-derived key changed on every
470
+ // keystroke above a layout, making the regenerated DecorationSet compare unequal
471
+ // and forcing ProseMirror to destroy and rebuild every divider below the edit. The
472
+ // widget resolves its own section from `getPos` (see `resolveDividerTarget`), so it
473
+ // stays correct wherever it ends up.
474
+ //
475
+ // `toDOM` must also stay lazy: most of the `Decoration`s returned here are matched
476
+ // by key against the previous set and discarded without ever being rendered.
477
+ // Building the widget eagerly would create three elements and bind a `mousedown`
478
+ // listener per divider per call, and the discarded ones never receive `destroy`.
479
+ var unbindMouseDown;
480
+ decorations.push(_view.Decoration.widget(widgetPos, function (widgetView, getPos) {
481
+ var _createColumnDividerW = createColumnDividerWidget(widgetView, sectionPos, colIndex, editorAnalyticsAPI, getPos),
482
+ target = _createColumnDividerW.target,
483
+ destroy = _createColumnDividerW.destroy;
484
+ unbindMouseDown = destroy;
485
+ return target;
486
+ }, {
487
+ side: -1,
488
+ // place before the position
489
+ key: "layout-col-divider-".concat(sectionOrdinal, "-").concat(index),
490
+ ignoreSelection: true,
491
+ destroy: function destroy() {
492
+ var _unbindMouseDown;
493
+ (_unbindMouseDown = unbindMouseDown) === null || _unbindMouseDown === void 0 || _unbindMouseDown();
494
+ unbindMouseDown = undefined;
495
+ }
496
+ }));
497
+ }
412
498
  }
413
499
  });
414
500
  return false; // don't descend into children
415
501
  }
416
- return true; // continue descending
502
+ // A `layoutSection` is only valid as a direct child of `doc` or `bodiedSyncBlock`, so there
503
+ // is nothing to find anywhere else. The unrestricted walk visited every node in the
504
+ // document — every table cell, list item and text node — on every `decorations(state)` read.
505
+ return isScopedWalkEnabled ? node.type === bodiedSyncBlock : true;
417
506
  });
418
507
  return decorations;
419
508
  };
@@ -3,6 +3,7 @@ import { ACTION, ACTION_SUBJECT, ACTION_SUBJECT_ID, EVENT_TYPE, INPUT_METHOD } f
3
3
  import { Fragment } from '@atlaskit/editor-prosemirror/model';
4
4
  import { NodeSelection, TextSelection } from '@atlaskit/editor-prosemirror/state';
5
5
  import { Decoration } from '@atlaskit/editor-prosemirror/view';
6
+ import { isExperimentEnabled } from '@atlaskit/platform-feature-experiments/is-experiment-enabled';
6
7
  import { fg } from '@atlaskit/platform-feature-flags';
7
8
  import { MIN_LAYOUT_COLUMN_WIDTH_PERCENT } from './consts';
8
9
 
@@ -249,13 +250,51 @@ const onDragCancel = () => {
249
250
  onDragEnd(dragState.lastClientX);
250
251
  };
251
252
 
253
+ /**
254
+ * Resolves the layout section a divider sits in and which pair of columns it separates.
255
+ *
256
+ * When `getPos` is supplied (under `platform_editor_reduce_event_listener_count`) this is derived
257
+ * from the widget's own live position, which ProseMirror keeps up to date. That is what lets the
258
+ * decoration key omit the section position: the key previously embedded it, so inserting a single
259
+ * character above a layout changed the key of every divider below and forced ProseMirror to destroy
260
+ * and rebuild all of them. Without `getPos` it falls back to the values captured when the widget
261
+ * was created.
262
+ */
263
+ const resolveDividerTarget = (view, sectionPos, columnIndex, getPos) => {
264
+ if (!getPos) {
265
+ const sectionNode = view.state.doc.nodeAt(sectionPos);
266
+ return sectionNode ? {
267
+ sectionNode,
268
+ sectionPos,
269
+ leftColIndex: columnIndex - 1
270
+ } : null;
271
+ }
272
+ const pos = getPos();
273
+ if (pos === undefined) {
274
+ return null;
275
+ }
276
+
277
+ // The widget is anchored at the position of the column it precedes, which resolves to a
278
+ // position directly inside the layout section.
279
+ const $pos = view.state.doc.resolve(pos);
280
+ const colIndex = $pos.index();
281
+ if ($pos.parent.type !== view.state.schema.nodes.layoutSection || colIndex < 1) {
282
+ return null;
283
+ }
284
+ return {
285
+ sectionNode: $pos.parent,
286
+ sectionPos: $pos.before(),
287
+ leftColIndex: colIndex - 1
288
+ };
289
+ };
290
+
252
291
  /**
253
292
  * Creates a column divider widget DOM element with drag-to-resize interaction for
254
293
  * the adjacent layout columns. During drag, flex-basis is mutated directly on the
255
294
  * column DOM elements for zero-overhead visual feedback (no PM transactions).
256
295
  * A single undoable PM transaction is dispatched on mouseup to commit the final widths.
257
296
  */
258
- const createColumnDividerWidget = (view, sectionPos, columnIndex, editorAnalyticsAPI) => {
297
+ const createColumnDividerWidget = (view, sectionPos, columnIndex, editorAnalyticsAPI, getPos) => {
259
298
  const ownerDoc = view.dom.ownerDocument;
260
299
 
261
300
  // Outer container: wide transparent hit area for easy grabbing, zero flex footprint
@@ -272,17 +311,21 @@ const createColumnDividerWidget = (view, sectionPos, columnIndex, editorAnalytic
272
311
  const thumb = ownerDoc.createElement('div');
273
312
  thumb.classList.add(layoutColumnDividerThumbClassName);
274
313
  rail.appendChild(thumb);
275
- const leftColIndex = columnIndex - 1;
276
- bind(divider, {
314
+ const unbindMouseDown = bind(divider, {
277
315
  type: 'mousedown',
278
316
  listener: e => {
279
317
  var _ownerDoc$defaultView;
280
318
  e.preventDefault();
281
319
  e.stopPropagation();
282
- const sectionNode = view.state.doc.nodeAt(sectionPos);
283
- if (!sectionNode) {
320
+ const resolved = resolveDividerTarget(view, sectionPos, columnIndex, getPos);
321
+ if (!resolved) {
284
322
  return;
285
323
  }
324
+ const {
325
+ sectionNode,
326
+ leftColIndex,
327
+ sectionPos: resolvedSectionPos
328
+ } = resolved;
286
329
 
287
330
  // Get the initial widths of the two adjacent columns
288
331
  let leftCol = null;
@@ -355,7 +398,7 @@ const createColumnDividerWidget = (view, sectionPos, columnIndex, editorAnalytic
355
398
  lastClientX: e.clientX,
356
399
  rafId: null,
357
400
  view,
358
- sectionPos,
401
+ sectionPos: resolvedSectionPos,
359
402
  leftColIndex,
360
403
  leftColEl,
361
404
  rightColEl,
@@ -375,7 +418,10 @@ const createColumnDividerWidget = (view, sectionPos, columnIndex, editorAnalytic
375
418
  ownerDoc.body.style.cursor = 'col-resize';
376
419
  }
377
420
  });
378
- return divider;
421
+ return {
422
+ target: divider,
423
+ destroy: unbindMouseDown
424
+ };
379
425
  };
380
426
 
381
427
  /**
@@ -388,28 +434,73 @@ export const getColumnDividerDecorations = (state, view, editorAnalyticsAPI) =>
388
434
  return decorations;
389
435
  }
390
436
  const {
391
- layoutSection
437
+ layoutSection,
438
+ bodiedSyncBlock
392
439
  } = state.schema.nodes;
440
+
441
+ // Read once per call: `isExperimentEnabled` fires an exposure event, so checking it per divider
442
+ // would emit one per divider per `decorations(state)` read.
443
+ const isScopedWalkEnabled = isExperimentEnabled('platform_editor_reduce_event_listener_count');
444
+ let sectionIndex = 0;
393
445
  state.doc.descendants((node, pos) => {
394
446
  if (node.type === layoutSection) {
447
+ // This layout's ordinal in the document, used below as a position-independent key.
448
+ const sectionOrdinal = sectionIndex++;
395
449
  // Walk through layout column children and add dividers between them
396
- node.forEach((child, offset, index) => {
450
+ node.forEach((_, offset, index) => {
397
451
  // Add a divider widget BEFORE every column except the first
398
452
  if (index > 0) {
399
453
  const sectionPos = pos;
400
454
  const colIndex = index;
401
455
  const widgetPos = pos + offset + 1; // position at the start of this column
402
- decorations.push(Decoration.widget(widgetPos, () => createColumnDividerWidget(view, sectionPos, colIndex, editorAnalyticsAPI), {
403
- side: -1,
404
- // place before the position
405
- key: `layout-col-divider-${pos}-${index}`,
406
- ignoreSelection: true
407
- }));
456
+
457
+ if (!isScopedWalkEnabled) {
458
+ decorations.push(Decoration.widget(widgetPos, () => createColumnDividerWidget(view, sectionPos, colIndex, editorAnalyticsAPI).target, {
459
+ side: -1,
460
+ // place before the position
461
+ key: `layout-col-divider-${pos}-${index}`,
462
+ ignoreSelection: true
463
+ }));
464
+ } else {
465
+ // Keyed by the layout's ordinal rather than its position, so an edit elsewhere in
466
+ // the document doesn't invalidate it. A position-derived key changed on every
467
+ // keystroke above a layout, making the regenerated DecorationSet compare unequal
468
+ // and forcing ProseMirror to destroy and rebuild every divider below the edit. The
469
+ // widget resolves its own section from `getPos` (see `resolveDividerTarget`), so it
470
+ // stays correct wherever it ends up.
471
+ //
472
+ // `toDOM` must also stay lazy: most of the `Decoration`s returned here are matched
473
+ // by key against the previous set and discarded without ever being rendered.
474
+ // Building the widget eagerly would create three elements and bind a `mousedown`
475
+ // listener per divider per call, and the discarded ones never receive `destroy`.
476
+ let unbindMouseDown;
477
+ decorations.push(Decoration.widget(widgetPos, (widgetView, getPos) => {
478
+ const {
479
+ target,
480
+ destroy
481
+ } = createColumnDividerWidget(widgetView, sectionPos, colIndex, editorAnalyticsAPI, getPos);
482
+ unbindMouseDown = destroy;
483
+ return target;
484
+ }, {
485
+ side: -1,
486
+ // place before the position
487
+ key: `layout-col-divider-${sectionOrdinal}-${index}`,
488
+ ignoreSelection: true,
489
+ destroy: () => {
490
+ var _unbindMouseDown;
491
+ (_unbindMouseDown = unbindMouseDown) === null || _unbindMouseDown === void 0 ? void 0 : _unbindMouseDown();
492
+ unbindMouseDown = undefined;
493
+ }
494
+ }));
495
+ }
408
496
  }
409
497
  });
410
498
  return false; // don't descend into children
411
499
  }
412
- return true; // continue descending
500
+ // A `layoutSection` is only valid as a direct child of `doc` or `bodiedSyncBlock`, so there
501
+ // is nothing to find anywhere else. The unrestricted walk visited every node in the
502
+ // document — every table cell, list item and text node — on every `decorations(state)` read.
503
+ return isScopedWalkEnabled ? node.type === bodiedSyncBlock : true;
413
504
  });
414
505
  return decorations;
415
506
  };
@@ -6,6 +6,7 @@ import { ACTION, ACTION_SUBJECT, ACTION_SUBJECT_ID, EVENT_TYPE, INPUT_METHOD } f
6
6
  import { Fragment } from '@atlaskit/editor-prosemirror/model';
7
7
  import { NodeSelection, TextSelection } from '@atlaskit/editor-prosemirror/state';
8
8
  import { Decoration } from '@atlaskit/editor-prosemirror/view';
9
+ import { isExperimentEnabled } from '@atlaskit/platform-feature-experiments/is-experiment-enabled';
9
10
  import { fg } from '@atlaskit/platform-feature-flags';
10
11
  import { MIN_LAYOUT_COLUMN_WIDTH_PERCENT } from './consts';
11
12
 
@@ -247,13 +248,51 @@ var onDragCancel = function onDragCancel() {
247
248
  onDragEnd(dragState.lastClientX);
248
249
  };
249
250
 
251
+ /**
252
+ * Resolves the layout section a divider sits in and which pair of columns it separates.
253
+ *
254
+ * When `getPos` is supplied (under `platform_editor_reduce_event_listener_count`) this is derived
255
+ * from the widget's own live position, which ProseMirror keeps up to date. That is what lets the
256
+ * decoration key omit the section position: the key previously embedded it, so inserting a single
257
+ * character above a layout changed the key of every divider below and forced ProseMirror to destroy
258
+ * and rebuild all of them. Without `getPos` it falls back to the values captured when the widget
259
+ * was created.
260
+ */
261
+ var resolveDividerTarget = function resolveDividerTarget(view, sectionPos, columnIndex, getPos) {
262
+ if (!getPos) {
263
+ var sectionNode = view.state.doc.nodeAt(sectionPos);
264
+ return sectionNode ? {
265
+ sectionNode: sectionNode,
266
+ sectionPos: sectionPos,
267
+ leftColIndex: columnIndex - 1
268
+ } : null;
269
+ }
270
+ var pos = getPos();
271
+ if (pos === undefined) {
272
+ return null;
273
+ }
274
+
275
+ // The widget is anchored at the position of the column it precedes, which resolves to a
276
+ // position directly inside the layout section.
277
+ var $pos = view.state.doc.resolve(pos);
278
+ var colIndex = $pos.index();
279
+ if ($pos.parent.type !== view.state.schema.nodes.layoutSection || colIndex < 1) {
280
+ return null;
281
+ }
282
+ return {
283
+ sectionNode: $pos.parent,
284
+ sectionPos: $pos.before(),
285
+ leftColIndex: colIndex - 1
286
+ };
287
+ };
288
+
250
289
  /**
251
290
  * Creates a column divider widget DOM element with drag-to-resize interaction for
252
291
  * the adjacent layout columns. During drag, flex-basis is mutated directly on the
253
292
  * column DOM elements for zero-overhead visual feedback (no PM transactions).
254
293
  * A single undoable PM transaction is dispatched on mouseup to commit the final widths.
255
294
  */
256
- var createColumnDividerWidget = function createColumnDividerWidget(view, sectionPos, columnIndex, editorAnalyticsAPI) {
295
+ var createColumnDividerWidget = function createColumnDividerWidget(view, sectionPos, columnIndex, editorAnalyticsAPI, getPos) {
257
296
  var ownerDoc = view.dom.ownerDocument;
258
297
 
259
298
  // Outer container: wide transparent hit area for easy grabbing, zero flex footprint
@@ -270,17 +309,19 @@ var createColumnDividerWidget = function createColumnDividerWidget(view, section
270
309
  var thumb = ownerDoc.createElement('div');
271
310
  thumb.classList.add(layoutColumnDividerThumbClassName);
272
311
  rail.appendChild(thumb);
273
- var leftColIndex = columnIndex - 1;
274
- bind(divider, {
312
+ var unbindMouseDown = bind(divider, {
275
313
  type: 'mousedown',
276
314
  listener: function listener(e) {
277
315
  var _ownerDoc$defaultView;
278
316
  e.preventDefault();
279
317
  e.stopPropagation();
280
- var sectionNode = view.state.doc.nodeAt(sectionPos);
281
- if (!sectionNode) {
318
+ var resolved = resolveDividerTarget(view, sectionPos, columnIndex, getPos);
319
+ if (!resolved) {
282
320
  return;
283
321
  }
322
+ var sectionNode = resolved.sectionNode,
323
+ leftColIndex = resolved.leftColIndex,
324
+ resolvedSectionPos = resolved.sectionPos;
284
325
 
285
326
  // Get the initial widths of the two adjacent columns
286
327
  var leftCol = null;
@@ -353,7 +394,7 @@ var createColumnDividerWidget = function createColumnDividerWidget(view, section
353
394
  lastClientX: e.clientX,
354
395
  rafId: null,
355
396
  view: view,
356
- sectionPos: sectionPos,
397
+ sectionPos: resolvedSectionPos,
357
398
  leftColIndex: leftColIndex,
358
399
  leftColEl: leftColEl,
359
400
  rightColEl: rightColEl,
@@ -373,7 +414,10 @@ var createColumnDividerWidget = function createColumnDividerWidget(view, section
373
414
  ownerDoc.body.style.cursor = 'col-resize';
374
415
  }
375
416
  });
376
- return divider;
417
+ return {
418
+ target: divider,
419
+ destroy: unbindMouseDown
420
+ };
377
421
  };
378
422
 
379
423
  /**
@@ -385,29 +429,74 @@ export var getColumnDividerDecorations = function getColumnDividerDecorations(st
385
429
  if (!view) {
386
430
  return decorations;
387
431
  }
388
- var layoutSection = state.schema.nodes.layoutSection;
432
+ var _state$schema$nodes = state.schema.nodes,
433
+ layoutSection = _state$schema$nodes.layoutSection,
434
+ bodiedSyncBlock = _state$schema$nodes.bodiedSyncBlock;
435
+
436
+ // Read once per call: `isExperimentEnabled` fires an exposure event, so checking it per divider
437
+ // would emit one per divider per `decorations(state)` read.
438
+ var isScopedWalkEnabled = isExperimentEnabled('platform_editor_reduce_event_listener_count');
439
+ var sectionIndex = 0;
389
440
  state.doc.descendants(function (node, pos) {
390
441
  if (node.type === layoutSection) {
442
+ // This layout's ordinal in the document, used below as a position-independent key.
443
+ var sectionOrdinal = sectionIndex++;
391
444
  // Walk through layout column children and add dividers between them
392
- node.forEach(function (child, offset, index) {
445
+ node.forEach(function (_, offset, index) {
393
446
  // Add a divider widget BEFORE every column except the first
394
447
  if (index > 0) {
395
448
  var sectionPos = pos;
396
449
  var colIndex = index;
397
450
  var widgetPos = pos + offset + 1; // position at the start of this column
398
- decorations.push(Decoration.widget(widgetPos, function () {
399
- return createColumnDividerWidget(view, sectionPos, colIndex, editorAnalyticsAPI);
400
- }, {
401
- side: -1,
402
- // place before the position
403
- key: "layout-col-divider-".concat(pos, "-").concat(index),
404
- ignoreSelection: true
405
- }));
451
+
452
+ if (!isScopedWalkEnabled) {
453
+ decorations.push(Decoration.widget(widgetPos, function () {
454
+ return createColumnDividerWidget(view, sectionPos, colIndex, editorAnalyticsAPI).target;
455
+ }, {
456
+ side: -1,
457
+ // place before the position
458
+ key: "layout-col-divider-".concat(pos, "-").concat(index),
459
+ ignoreSelection: true
460
+ }));
461
+ } else {
462
+ // Keyed by the layout's ordinal rather than its position, so an edit elsewhere in
463
+ // the document doesn't invalidate it. A position-derived key changed on every
464
+ // keystroke above a layout, making the regenerated DecorationSet compare unequal
465
+ // and forcing ProseMirror to destroy and rebuild every divider below the edit. The
466
+ // widget resolves its own section from `getPos` (see `resolveDividerTarget`), so it
467
+ // stays correct wherever it ends up.
468
+ //
469
+ // `toDOM` must also stay lazy: most of the `Decoration`s returned here are matched
470
+ // by key against the previous set and discarded without ever being rendered.
471
+ // Building the widget eagerly would create three elements and bind a `mousedown`
472
+ // listener per divider per call, and the discarded ones never receive `destroy`.
473
+ var unbindMouseDown;
474
+ decorations.push(Decoration.widget(widgetPos, function (widgetView, getPos) {
475
+ var _createColumnDividerW = createColumnDividerWidget(widgetView, sectionPos, colIndex, editorAnalyticsAPI, getPos),
476
+ target = _createColumnDividerW.target,
477
+ destroy = _createColumnDividerW.destroy;
478
+ unbindMouseDown = destroy;
479
+ return target;
480
+ }, {
481
+ side: -1,
482
+ // place before the position
483
+ key: "layout-col-divider-".concat(sectionOrdinal, "-").concat(index),
484
+ ignoreSelection: true,
485
+ destroy: function destroy() {
486
+ var _unbindMouseDown;
487
+ (_unbindMouseDown = unbindMouseDown) === null || _unbindMouseDown === void 0 || _unbindMouseDown();
488
+ unbindMouseDown = undefined;
489
+ }
490
+ }));
491
+ }
406
492
  }
407
493
  });
408
494
  return false; // don't descend into children
409
495
  }
410
- return true; // continue descending
496
+ // A `layoutSection` is only valid as a direct child of `doc` or `bodiedSyncBlock`, so there
497
+ // is nothing to find anywhere else. The unrestricted walk visited every node in the
498
+ // document — every table cell, list item and text node — on every `decorations(state)` read.
499
+ return isScopedWalkEnabled ? node.type === bodiedSyncBlock : true;
411
500
  });
412
501
  return decorations;
413
502
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@atlaskit/editor-plugin-layout",
3
- "version": "16.0.15",
3
+ "version": "16.0.16",
4
4
  "description": "Layout plugin for @atlaskit/editor-core",
5
5
  "author": "Atlassian Pty Ltd",
6
6
  "license": "Apache-2.0",