@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
|
|
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
|
|
287
|
-
if (!
|
|
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:
|
|
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
|
|
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
|
|
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 (
|
|
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
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
283
|
-
if (!
|
|
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
|
|
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((
|
|
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
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
281
|
-
if (!
|
|
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:
|
|
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
|
|
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
|
|
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 (
|
|
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
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
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
|
-
|
|
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
|
};
|