@atlaskit/editor-plugin-layout 13.2.3 → 13.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +15 -0
- package/dist/cjs/pm-plugins/actions.js +75 -0
- package/dist/cjs/pm-plugins/column-resize-divider.js +41 -0
- package/dist/es2019/pm-plugins/actions.js +73 -0
- package/dist/es2019/pm-plugins/column-resize-divider.js +39 -0
- package/dist/esm/pm-plugins/actions.js +75 -0
- package/dist/esm/pm-plugins/column-resize-divider.js +41 -0
- package/package.json +8 -8
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,20 @@
|
|
|
1
1
|
# @atlaskit/editor-plugin-layout
|
|
2
2
|
|
|
3
|
+
## 13.3.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- [`5b844f57bfad8`](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/commits/5b844f57bfad8) -
|
|
8
|
+
[ED-7731] Preserve the editor selection when inserting, deleting, or resizing layout columns with
|
|
9
|
+
the cursor inside a column, instead of moving it out of the layout. Gated behind
|
|
10
|
+
platform_editor_layout_column_menu_kill_switch_1.
|
|
11
|
+
|
|
12
|
+
## 13.2.4
|
|
13
|
+
|
|
14
|
+
### Patch Changes
|
|
15
|
+
|
|
16
|
+
- Updated dependencies
|
|
17
|
+
|
|
3
18
|
## 13.2.3
|
|
4
19
|
|
|
5
20
|
### Patch Changes
|
|
@@ -566,6 +566,38 @@ var getPreviousLayoutColumnValign = function getPreviousLayoutColumnValign(selec
|
|
|
566
566
|
var hasLayoutColumnContent = function hasLayoutColumnContent(node) {
|
|
567
567
|
return !(0, _utils.isEmptyDocument)(node);
|
|
568
568
|
};
|
|
569
|
+
|
|
570
|
+
/**
|
|
571
|
+
* Remaps a selection through a position mapping, preserving its type. Used after replacing a
|
|
572
|
+
* layout section's contents so the selection stays in its column instead of being mapped out
|
|
573
|
+
* of the layout. Returns `undefined` if no valid selection can be derived (NodeSelection
|
|
574
|
+
* whose node is no longer selectable falls back to a nearby caret).
|
|
575
|
+
*/
|
|
576
|
+
var remapSelectionThroughMapping = function remapSelectionThroughMapping(selection, mapping, doc) {
|
|
577
|
+
var docSize = doc.content.size;
|
|
578
|
+
var clamp = function clamp(pos) {
|
|
579
|
+
return Math.min(Math.max(pos, 0), docSize);
|
|
580
|
+
};
|
|
581
|
+
if (selection instanceof _state.NodeSelection) {
|
|
582
|
+
var _TextSelection$findFr;
|
|
583
|
+
var mappedPos = clamp(mapping.map(selection.from));
|
|
584
|
+
var nodeAtPos = doc.nodeAt(mappedPos);
|
|
585
|
+
if (nodeAtPos && _state.NodeSelection.isSelectable(nodeAtPos)) {
|
|
586
|
+
return _state.NodeSelection.create(doc, mappedPos);
|
|
587
|
+
}
|
|
588
|
+
return (_TextSelection$findFr = _state.TextSelection.findFrom(doc.resolve(mappedPos), 1, true)) !== null && _TextSelection$findFr !== void 0 ? _TextSelection$findFr : undefined;
|
|
589
|
+
}
|
|
590
|
+
if (selection instanceof _state.TextSelection) {
|
|
591
|
+
var mappedFrom = clamp(mapping.map(selection.from));
|
|
592
|
+
var mappedTo = clamp(mapping.map(selection.to));
|
|
593
|
+
try {
|
|
594
|
+
return _state.TextSelection.create(doc, mappedFrom, mappedTo);
|
|
595
|
+
} catch (_unused) {
|
|
596
|
+
return undefined;
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
return undefined;
|
|
600
|
+
};
|
|
569
601
|
var mapLayoutColumnPreservedSelection = function mapLayoutColumnPreservedSelection(tr, api) {
|
|
570
602
|
var insertMeta = tr.getMeta(LAYOUT_COLUMN_INSERT_META);
|
|
571
603
|
if (insertMeta) {
|
|
@@ -651,7 +683,22 @@ var insertLayoutColumnAt = function insertLayoutColumnAt(side, editorAnalyticsAP
|
|
|
651
683
|
insertedColumnPos: insertedColumnPos,
|
|
652
684
|
side: side
|
|
653
685
|
});
|
|
686
|
+
|
|
687
|
+
// Capture the selection before the section content is replaced (the replace below maps
|
|
688
|
+
// it out of the layout by default). The menu path restores its own preserved selection
|
|
689
|
+
// afterwards, so this restoration only matters for the cursor-in-column case.
|
|
690
|
+
var originalSelection = tr.selection;
|
|
654
691
|
tr.replaceWith(layoutSectionPos + 1, layoutSectionPos + layoutSectionNode.nodeSize - 1, columnWidth(updatedLayoutSectionNode, tr.doc.type.schema, redistributedWidths));
|
|
692
|
+
|
|
693
|
+
// Inserting left shifts positions at/after the new column right by its size; inserting
|
|
694
|
+
// right leaves them unchanged. Remap the original selection through that mapping.
|
|
695
|
+
if (!(0, _platformFeatureFlags.fg)('platform_editor_layout_column_menu_kill_switch_1')) {
|
|
696
|
+
var insertMapping = side === 'left' ? new _transform.Mapping([new _transform.StepMap([insertedColumnPos, 0, newColumn.nodeSize])]) : new _transform.Mapping();
|
|
697
|
+
var restoredSelection = remapSelectionThroughMapping(originalSelection, insertMapping, tr.doc);
|
|
698
|
+
if (restoredSelection) {
|
|
699
|
+
tr.setSelection(restoredSelection);
|
|
700
|
+
}
|
|
701
|
+
}
|
|
655
702
|
editorAnalyticsAPI === null || editorAnalyticsAPI === void 0 || editorAnalyticsAPI.attachAnalyticsEvent({
|
|
656
703
|
action: _analytics.ACTION.INSERTED,
|
|
657
704
|
actionSubject: _analytics.ACTION_SUBJECT.DOCUMENT,
|
|
@@ -927,7 +974,35 @@ var deleteLayoutColumn = exports.deleteLayoutColumn = function deleteLayoutColum
|
|
|
927
974
|
return (0, _layoutColumnDistribution.redistributeAfterDeletion)(widths, selectedIndex, _consts.MIN_LAYOUT_COLUMN_WIDTH_PERCENT);
|
|
928
975
|
}, existingWidths);
|
|
929
976
|
var updatedLayoutSectionNode = layoutSectionNode.copy(_model.Fragment.fromArray(remainingColumns));
|
|
977
|
+
|
|
978
|
+
// The cursor-in-column (keyboard) path has a plain text selection; the menu path has a
|
|
979
|
+
// column NodeSelection whose post-delete landing is owned by the block-controls
|
|
980
|
+
// preserved-selection plugin, so we only restore the caret for the former.
|
|
981
|
+
var hadTextSelection = tr.selection instanceof _state.TextSelection;
|
|
930
982
|
tr.replaceWith(layoutSectionPos + 1, layoutSectionPos + layoutSectionNode.nodeSize - 1, columnWidth(updatedLayoutSectionNode, tr.doc.type.schema, redistributed));
|
|
983
|
+
|
|
984
|
+
// Land the caret in a remaining column — the one now occupying the deleted slot, or the
|
|
985
|
+
// last column when the deleted slot no longer exists. Otherwise the replace above maps
|
|
986
|
+
// the caret out of the layout to the following paragraph.
|
|
987
|
+
var remainingColumnCount = remainingColumns.length;
|
|
988
|
+
if (hadTextSelection && !(0, _platformFeatureFlags.fg)('platform_editor_layout_column_menu_kill_switch_1') && remainingColumnCount > 0) {
|
|
989
|
+
var targetColumnIndex = Math.min(startIndex, remainingColumnCount - 1);
|
|
990
|
+
var updatedSectionNode = tr.doc.nodeAt(layoutSectionPos);
|
|
991
|
+
if (updatedSectionNode) {
|
|
992
|
+
var columnOffset = 1;
|
|
993
|
+
for (var columnIndex = 0; columnIndex < targetColumnIndex; columnIndex++) {
|
|
994
|
+
columnOffset += updatedSectionNode.child(columnIndex).nodeSize;
|
|
995
|
+
}
|
|
996
|
+
// +1 to land inside the column's first child rather than on the column boundary.
|
|
997
|
+
var caretPos = layoutSectionPos + columnOffset + 1;
|
|
998
|
+
if (caretPos >= 0 && caretPos <= tr.doc.content.size) {
|
|
999
|
+
var caretSelection = _state.TextSelection.findFrom(tr.doc.resolve(caretPos), 1, true);
|
|
1000
|
+
if (caretSelection) {
|
|
1001
|
+
tr.setSelection(caretSelection);
|
|
1002
|
+
}
|
|
1003
|
+
}
|
|
1004
|
+
}
|
|
1005
|
+
}
|
|
931
1006
|
emitDeleteColumnAnalytics(redistributed.length);
|
|
932
1007
|
tr.setMeta('scrollIntoView', false);
|
|
933
1008
|
api === null || api === void 0 || (_api$blockControls4 = api.blockControls) === null || _api$blockControls4 === void 0 || _api$blockControls4.commands.stopPreservingSelection()({
|
|
@@ -9,11 +9,41 @@ var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/de
|
|
|
9
9
|
var _bindEventListener = require("bind-event-listener");
|
|
10
10
|
var _analytics = require("@atlaskit/editor-common/analytics");
|
|
11
11
|
var _model = require("@atlaskit/editor-prosemirror/model");
|
|
12
|
+
var _state = require("@atlaskit/editor-prosemirror/state");
|
|
12
13
|
var _view = require("@atlaskit/editor-prosemirror/view");
|
|
13
14
|
var _platformFeatureFlags = require("@atlaskit/platform-feature-flags");
|
|
14
15
|
var _consts = require("./consts");
|
|
15
16
|
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; }
|
|
16
17
|
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; }
|
|
18
|
+
/**
|
|
19
|
+
* Re-creates a selection at the same positions against the resized document. A resize only
|
|
20
|
+
* changes width attrs (not node count, order, or size), so the original positions are still
|
|
21
|
+
* valid. Returns `undefined` if no valid selection can be made.
|
|
22
|
+
*/
|
|
23
|
+
var remapResizeSelection = function remapResizeSelection(selection, doc) {
|
|
24
|
+
var docSize = doc.content.size;
|
|
25
|
+
var clamp = function clamp(pos) {
|
|
26
|
+
return Math.min(Math.max(pos, 0), docSize);
|
|
27
|
+
};
|
|
28
|
+
if (selection instanceof _state.NodeSelection) {
|
|
29
|
+
var _TextSelection$findFr;
|
|
30
|
+
var pos = clamp(selection.from);
|
|
31
|
+
var nodeAtPos = doc.nodeAt(pos);
|
|
32
|
+
if (nodeAtPos && _state.NodeSelection.isSelectable(nodeAtPos)) {
|
|
33
|
+
return _state.NodeSelection.create(doc, pos);
|
|
34
|
+
}
|
|
35
|
+
return (_TextSelection$findFr = _state.TextSelection.findFrom(doc.resolve(pos), 1, true)) !== null && _TextSelection$findFr !== void 0 ? _TextSelection$findFr : undefined;
|
|
36
|
+
}
|
|
37
|
+
if (selection instanceof _state.TextSelection) {
|
|
38
|
+
try {
|
|
39
|
+
return _state.TextSelection.create(doc, clamp(selection.from), clamp(selection.to));
|
|
40
|
+
} catch (_unused) {
|
|
41
|
+
return undefined;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
return undefined;
|
|
45
|
+
};
|
|
46
|
+
|
|
17
47
|
// Class names for the column resize divider widget — must stay in sync with layout.ts in editor-core
|
|
18
48
|
var layoutColumnDividerClassName = 'layout-column-divider';
|
|
19
49
|
var layoutColumnDividerRailClassName = 'layout-column-divider-rail';
|
|
@@ -50,9 +80,20 @@ var dispatchColumnWidths = function dispatchColumnWidths(view, sectionPos, leftC
|
|
|
50
80
|
newColumns.push(child);
|
|
51
81
|
}
|
|
52
82
|
});
|
|
83
|
+
|
|
84
|
+
// Capture the selection before the replace below, which otherwise maps the caret out of
|
|
85
|
+
// the layout to the following paragraph. Restored at the same positions afterwards.
|
|
86
|
+
var shouldPreserveSelection = !(0, _platformFeatureFlags.fg)('platform_editor_layout_column_menu_kill_switch_1');
|
|
87
|
+
var selectionBeforeResize = shouldPreserveSelection ? state.selection : null;
|
|
53
88
|
tr.replaceWith(sectionPos + 1, sectionPos + sectionNode.nodeSize - 1, _model.Fragment.from(newColumns));
|
|
54
89
|
tr.setMeta('layoutColumnResize', true);
|
|
55
90
|
tr.setMeta('scrollIntoView', false);
|
|
91
|
+
if (selectionBeforeResize) {
|
|
92
|
+
var restoredSelection = remapResizeSelection(selectionBeforeResize, tr.doc);
|
|
93
|
+
if (restoredSelection) {
|
|
94
|
+
tr.setSelection(restoredSelection);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
56
97
|
if ((0, _platformFeatureFlags.fg)('platform_editor_layout_resize_analytics')) {
|
|
57
98
|
editorAnalyticsAPI === null || editorAnalyticsAPI === void 0 || editorAnalyticsAPI.attachAnalyticsEvent({
|
|
58
99
|
action: _analytics.ACTION.DRAGGED,
|
|
@@ -539,6 +539,36 @@ const getPreviousLayoutColumnValign = selectedLayoutColumns => {
|
|
|
539
539
|
return hasMixedValign ? 'mixed' : firstValign !== null && firstValign !== void 0 ? firstValign : DEFAULT_LAYOUT_COLUMN_VALIGN;
|
|
540
540
|
};
|
|
541
541
|
const hasLayoutColumnContent = node => !isEmptyDocument(node);
|
|
542
|
+
|
|
543
|
+
/**
|
|
544
|
+
* Remaps a selection through a position mapping, preserving its type. Used after replacing a
|
|
545
|
+
* layout section's contents so the selection stays in its column instead of being mapped out
|
|
546
|
+
* of the layout. Returns `undefined` if no valid selection can be derived (NodeSelection
|
|
547
|
+
* whose node is no longer selectable falls back to a nearby caret).
|
|
548
|
+
*/
|
|
549
|
+
const remapSelectionThroughMapping = (selection, mapping, doc) => {
|
|
550
|
+
const docSize = doc.content.size;
|
|
551
|
+
const clamp = pos => Math.min(Math.max(pos, 0), docSize);
|
|
552
|
+
if (selection instanceof NodeSelection) {
|
|
553
|
+
var _TextSelection$findFr;
|
|
554
|
+
const mappedPos = clamp(mapping.map(selection.from));
|
|
555
|
+
const nodeAtPos = doc.nodeAt(mappedPos);
|
|
556
|
+
if (nodeAtPos && NodeSelection.isSelectable(nodeAtPos)) {
|
|
557
|
+
return NodeSelection.create(doc, mappedPos);
|
|
558
|
+
}
|
|
559
|
+
return (_TextSelection$findFr = TextSelection.findFrom(doc.resolve(mappedPos), 1, true)) !== null && _TextSelection$findFr !== void 0 ? _TextSelection$findFr : undefined;
|
|
560
|
+
}
|
|
561
|
+
if (selection instanceof TextSelection) {
|
|
562
|
+
const mappedFrom = clamp(mapping.map(selection.from));
|
|
563
|
+
const mappedTo = clamp(mapping.map(selection.to));
|
|
564
|
+
try {
|
|
565
|
+
return TextSelection.create(doc, mappedFrom, mappedTo);
|
|
566
|
+
} catch {
|
|
567
|
+
return undefined;
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
return undefined;
|
|
571
|
+
};
|
|
542
572
|
const mapLayoutColumnPreservedSelection = (tr, api) => {
|
|
543
573
|
const insertMeta = tr.getMeta(LAYOUT_COLUMN_INSERT_META);
|
|
544
574
|
if (insertMeta) {
|
|
@@ -625,7 +655,22 @@ const insertLayoutColumnAt = (side, editorAnalyticsAPI, inputMethod = INPUT_METH
|
|
|
625
655
|
insertedColumnPos,
|
|
626
656
|
side
|
|
627
657
|
});
|
|
658
|
+
|
|
659
|
+
// Capture the selection before the section content is replaced (the replace below maps
|
|
660
|
+
// it out of the layout by default). The menu path restores its own preserved selection
|
|
661
|
+
// afterwards, so this restoration only matters for the cursor-in-column case.
|
|
662
|
+
const originalSelection = tr.selection;
|
|
628
663
|
tr.replaceWith(layoutSectionPos + 1, layoutSectionPos + layoutSectionNode.nodeSize - 1, columnWidth(updatedLayoutSectionNode, tr.doc.type.schema, redistributedWidths));
|
|
664
|
+
|
|
665
|
+
// Inserting left shifts positions at/after the new column right by its size; inserting
|
|
666
|
+
// right leaves them unchanged. Remap the original selection through that mapping.
|
|
667
|
+
if (!fg('platform_editor_layout_column_menu_kill_switch_1')) {
|
|
668
|
+
const insertMapping = side === 'left' ? new Mapping([new StepMap([insertedColumnPos, 0, newColumn.nodeSize])]) : new Mapping();
|
|
669
|
+
const restoredSelection = remapSelectionThroughMapping(originalSelection, insertMapping, tr.doc);
|
|
670
|
+
if (restoredSelection) {
|
|
671
|
+
tr.setSelection(restoredSelection);
|
|
672
|
+
}
|
|
673
|
+
}
|
|
629
674
|
editorAnalyticsAPI === null || editorAnalyticsAPI === void 0 ? void 0 : editorAnalyticsAPI.attachAnalyticsEvent({
|
|
630
675
|
action: ACTION.INSERTED,
|
|
631
676
|
actionSubject: ACTION_SUBJECT.DOCUMENT,
|
|
@@ -888,7 +933,35 @@ export const deleteLayoutColumn = ({
|
|
|
888
933
|
// as each redistribution step shrinks the widths array.
|
|
889
934
|
.reverse().reduce((widths, selectedIndex) => redistributeAfterDeletion(widths, selectedIndex, MIN_LAYOUT_COLUMN_WIDTH_PERCENT), existingWidths);
|
|
890
935
|
const updatedLayoutSectionNode = layoutSectionNode.copy(Fragment.fromArray(remainingColumns));
|
|
936
|
+
|
|
937
|
+
// The cursor-in-column (keyboard) path has a plain text selection; the menu path has a
|
|
938
|
+
// column NodeSelection whose post-delete landing is owned by the block-controls
|
|
939
|
+
// preserved-selection plugin, so we only restore the caret for the former.
|
|
940
|
+
const hadTextSelection = tr.selection instanceof TextSelection;
|
|
891
941
|
tr.replaceWith(layoutSectionPos + 1, layoutSectionPos + layoutSectionNode.nodeSize - 1, columnWidth(updatedLayoutSectionNode, tr.doc.type.schema, redistributed));
|
|
942
|
+
|
|
943
|
+
// Land the caret in a remaining column — the one now occupying the deleted slot, or the
|
|
944
|
+
// last column when the deleted slot no longer exists. Otherwise the replace above maps
|
|
945
|
+
// the caret out of the layout to the following paragraph.
|
|
946
|
+
const remainingColumnCount = remainingColumns.length;
|
|
947
|
+
if (hadTextSelection && !fg('platform_editor_layout_column_menu_kill_switch_1') && remainingColumnCount > 0) {
|
|
948
|
+
const targetColumnIndex = Math.min(startIndex, remainingColumnCount - 1);
|
|
949
|
+
const updatedSectionNode = tr.doc.nodeAt(layoutSectionPos);
|
|
950
|
+
if (updatedSectionNode) {
|
|
951
|
+
let columnOffset = 1;
|
|
952
|
+
for (let columnIndex = 0; columnIndex < targetColumnIndex; columnIndex++) {
|
|
953
|
+
columnOffset += updatedSectionNode.child(columnIndex).nodeSize;
|
|
954
|
+
}
|
|
955
|
+
// +1 to land inside the column's first child rather than on the column boundary.
|
|
956
|
+
const caretPos = layoutSectionPos + columnOffset + 1;
|
|
957
|
+
if (caretPos >= 0 && caretPos <= tr.doc.content.size) {
|
|
958
|
+
const caretSelection = TextSelection.findFrom(tr.doc.resolve(caretPos), 1, true);
|
|
959
|
+
if (caretSelection) {
|
|
960
|
+
tr.setSelection(caretSelection);
|
|
961
|
+
}
|
|
962
|
+
}
|
|
963
|
+
}
|
|
964
|
+
}
|
|
892
965
|
emitDeleteColumnAnalytics(redistributed.length);
|
|
893
966
|
tr.setMeta('scrollIntoView', false);
|
|
894
967
|
api === null || api === void 0 ? void 0 : (_api$blockControls4 = api.blockControls) === null || _api$blockControls4 === void 0 ? void 0 : _api$blockControls4.commands.stopPreservingSelection()({
|
|
@@ -1,10 +1,38 @@
|
|
|
1
1
|
import { bind } from 'bind-event-listener';
|
|
2
2
|
import { ACTION, ACTION_SUBJECT, ACTION_SUBJECT_ID, EVENT_TYPE, INPUT_METHOD } from '@atlaskit/editor-common/analytics';
|
|
3
3
|
import { Fragment } from '@atlaskit/editor-prosemirror/model';
|
|
4
|
+
import { NodeSelection, TextSelection } from '@atlaskit/editor-prosemirror/state';
|
|
4
5
|
import { Decoration } from '@atlaskit/editor-prosemirror/view';
|
|
5
6
|
import { fg } from '@atlaskit/platform-feature-flags';
|
|
6
7
|
import { MIN_LAYOUT_COLUMN_WIDTH_PERCENT } from './consts';
|
|
7
8
|
|
|
9
|
+
/**
|
|
10
|
+
* Re-creates a selection at the same positions against the resized document. A resize only
|
|
11
|
+
* changes width attrs (not node count, order, or size), so the original positions are still
|
|
12
|
+
* valid. Returns `undefined` if no valid selection can be made.
|
|
13
|
+
*/
|
|
14
|
+
const remapResizeSelection = (selection, doc) => {
|
|
15
|
+
const docSize = doc.content.size;
|
|
16
|
+
const clamp = pos => Math.min(Math.max(pos, 0), docSize);
|
|
17
|
+
if (selection instanceof NodeSelection) {
|
|
18
|
+
var _TextSelection$findFr;
|
|
19
|
+
const pos = clamp(selection.from);
|
|
20
|
+
const nodeAtPos = doc.nodeAt(pos);
|
|
21
|
+
if (nodeAtPos && NodeSelection.isSelectable(nodeAtPos)) {
|
|
22
|
+
return NodeSelection.create(doc, pos);
|
|
23
|
+
}
|
|
24
|
+
return (_TextSelection$findFr = TextSelection.findFrom(doc.resolve(pos), 1, true)) !== null && _TextSelection$findFr !== void 0 ? _TextSelection$findFr : undefined;
|
|
25
|
+
}
|
|
26
|
+
if (selection instanceof TextSelection) {
|
|
27
|
+
try {
|
|
28
|
+
return TextSelection.create(doc, clamp(selection.from), clamp(selection.to));
|
|
29
|
+
} catch {
|
|
30
|
+
return undefined;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
return undefined;
|
|
34
|
+
};
|
|
35
|
+
|
|
8
36
|
// Class names for the column resize divider widget — must stay in sync with layout.ts in editor-core
|
|
9
37
|
const layoutColumnDividerClassName = 'layout-column-divider';
|
|
10
38
|
const layoutColumnDividerRailClassName = 'layout-column-divider-rail';
|
|
@@ -46,9 +74,20 @@ const dispatchColumnWidths = (view, sectionPos, leftColIndex, leftWidth, rightWi
|
|
|
46
74
|
newColumns.push(child);
|
|
47
75
|
}
|
|
48
76
|
});
|
|
77
|
+
|
|
78
|
+
// Capture the selection before the replace below, which otherwise maps the caret out of
|
|
79
|
+
// the layout to the following paragraph. Restored at the same positions afterwards.
|
|
80
|
+
const shouldPreserveSelection = !fg('platform_editor_layout_column_menu_kill_switch_1');
|
|
81
|
+
const selectionBeforeResize = shouldPreserveSelection ? state.selection : null;
|
|
49
82
|
tr.replaceWith(sectionPos + 1, sectionPos + sectionNode.nodeSize - 1, Fragment.from(newColumns));
|
|
50
83
|
tr.setMeta('layoutColumnResize', true);
|
|
51
84
|
tr.setMeta('scrollIntoView', false);
|
|
85
|
+
if (selectionBeforeResize) {
|
|
86
|
+
const restoredSelection = remapResizeSelection(selectionBeforeResize, tr.doc);
|
|
87
|
+
if (restoredSelection) {
|
|
88
|
+
tr.setSelection(restoredSelection);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
52
91
|
if (fg('platform_editor_layout_resize_analytics')) {
|
|
53
92
|
editorAnalyticsAPI === null || editorAnalyticsAPI === void 0 ? void 0 : editorAnalyticsAPI.attachAnalyticsEvent({
|
|
54
93
|
action: ACTION.DRAGGED,
|
|
@@ -556,6 +556,38 @@ var getPreviousLayoutColumnValign = function getPreviousLayoutColumnValign(selec
|
|
|
556
556
|
var hasLayoutColumnContent = function hasLayoutColumnContent(node) {
|
|
557
557
|
return !isEmptyDocument(node);
|
|
558
558
|
};
|
|
559
|
+
|
|
560
|
+
/**
|
|
561
|
+
* Remaps a selection through a position mapping, preserving its type. Used after replacing a
|
|
562
|
+
* layout section's contents so the selection stays in its column instead of being mapped out
|
|
563
|
+
* of the layout. Returns `undefined` if no valid selection can be derived (NodeSelection
|
|
564
|
+
* whose node is no longer selectable falls back to a nearby caret).
|
|
565
|
+
*/
|
|
566
|
+
var remapSelectionThroughMapping = function remapSelectionThroughMapping(selection, mapping, doc) {
|
|
567
|
+
var docSize = doc.content.size;
|
|
568
|
+
var clamp = function clamp(pos) {
|
|
569
|
+
return Math.min(Math.max(pos, 0), docSize);
|
|
570
|
+
};
|
|
571
|
+
if (selection instanceof NodeSelection) {
|
|
572
|
+
var _TextSelection$findFr;
|
|
573
|
+
var mappedPos = clamp(mapping.map(selection.from));
|
|
574
|
+
var nodeAtPos = doc.nodeAt(mappedPos);
|
|
575
|
+
if (nodeAtPos && NodeSelection.isSelectable(nodeAtPos)) {
|
|
576
|
+
return NodeSelection.create(doc, mappedPos);
|
|
577
|
+
}
|
|
578
|
+
return (_TextSelection$findFr = TextSelection.findFrom(doc.resolve(mappedPos), 1, true)) !== null && _TextSelection$findFr !== void 0 ? _TextSelection$findFr : undefined;
|
|
579
|
+
}
|
|
580
|
+
if (selection instanceof TextSelection) {
|
|
581
|
+
var mappedFrom = clamp(mapping.map(selection.from));
|
|
582
|
+
var mappedTo = clamp(mapping.map(selection.to));
|
|
583
|
+
try {
|
|
584
|
+
return TextSelection.create(doc, mappedFrom, mappedTo);
|
|
585
|
+
} catch (_unused) {
|
|
586
|
+
return undefined;
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
return undefined;
|
|
590
|
+
};
|
|
559
591
|
var mapLayoutColumnPreservedSelection = function mapLayoutColumnPreservedSelection(tr, api) {
|
|
560
592
|
var insertMeta = tr.getMeta(LAYOUT_COLUMN_INSERT_META);
|
|
561
593
|
if (insertMeta) {
|
|
@@ -641,7 +673,22 @@ var insertLayoutColumnAt = function insertLayoutColumnAt(side, editorAnalyticsAP
|
|
|
641
673
|
insertedColumnPos: insertedColumnPos,
|
|
642
674
|
side: side
|
|
643
675
|
});
|
|
676
|
+
|
|
677
|
+
// Capture the selection before the section content is replaced (the replace below maps
|
|
678
|
+
// it out of the layout by default). The menu path restores its own preserved selection
|
|
679
|
+
// afterwards, so this restoration only matters for the cursor-in-column case.
|
|
680
|
+
var originalSelection = tr.selection;
|
|
644
681
|
tr.replaceWith(layoutSectionPos + 1, layoutSectionPos + layoutSectionNode.nodeSize - 1, columnWidth(updatedLayoutSectionNode, tr.doc.type.schema, redistributedWidths));
|
|
682
|
+
|
|
683
|
+
// Inserting left shifts positions at/after the new column right by its size; inserting
|
|
684
|
+
// right leaves them unchanged. Remap the original selection through that mapping.
|
|
685
|
+
if (!fg('platform_editor_layout_column_menu_kill_switch_1')) {
|
|
686
|
+
var insertMapping = side === 'left' ? new Mapping([new StepMap([insertedColumnPos, 0, newColumn.nodeSize])]) : new Mapping();
|
|
687
|
+
var restoredSelection = remapSelectionThroughMapping(originalSelection, insertMapping, tr.doc);
|
|
688
|
+
if (restoredSelection) {
|
|
689
|
+
tr.setSelection(restoredSelection);
|
|
690
|
+
}
|
|
691
|
+
}
|
|
645
692
|
editorAnalyticsAPI === null || editorAnalyticsAPI === void 0 || editorAnalyticsAPI.attachAnalyticsEvent({
|
|
646
693
|
action: ACTION.INSERTED,
|
|
647
694
|
actionSubject: ACTION_SUBJECT.DOCUMENT,
|
|
@@ -917,7 +964,35 @@ export var deleteLayoutColumn = function deleteLayoutColumn() {
|
|
|
917
964
|
return redistributeAfterDeletion(widths, selectedIndex, MIN_LAYOUT_COLUMN_WIDTH_PERCENT);
|
|
918
965
|
}, existingWidths);
|
|
919
966
|
var updatedLayoutSectionNode = layoutSectionNode.copy(Fragment.fromArray(remainingColumns));
|
|
967
|
+
|
|
968
|
+
// The cursor-in-column (keyboard) path has a plain text selection; the menu path has a
|
|
969
|
+
// column NodeSelection whose post-delete landing is owned by the block-controls
|
|
970
|
+
// preserved-selection plugin, so we only restore the caret for the former.
|
|
971
|
+
var hadTextSelection = tr.selection instanceof TextSelection;
|
|
920
972
|
tr.replaceWith(layoutSectionPos + 1, layoutSectionPos + layoutSectionNode.nodeSize - 1, columnWidth(updatedLayoutSectionNode, tr.doc.type.schema, redistributed));
|
|
973
|
+
|
|
974
|
+
// Land the caret in a remaining column — the one now occupying the deleted slot, or the
|
|
975
|
+
// last column when the deleted slot no longer exists. Otherwise the replace above maps
|
|
976
|
+
// the caret out of the layout to the following paragraph.
|
|
977
|
+
var remainingColumnCount = remainingColumns.length;
|
|
978
|
+
if (hadTextSelection && !fg('platform_editor_layout_column_menu_kill_switch_1') && remainingColumnCount > 0) {
|
|
979
|
+
var targetColumnIndex = Math.min(startIndex, remainingColumnCount - 1);
|
|
980
|
+
var updatedSectionNode = tr.doc.nodeAt(layoutSectionPos);
|
|
981
|
+
if (updatedSectionNode) {
|
|
982
|
+
var columnOffset = 1;
|
|
983
|
+
for (var columnIndex = 0; columnIndex < targetColumnIndex; columnIndex++) {
|
|
984
|
+
columnOffset += updatedSectionNode.child(columnIndex).nodeSize;
|
|
985
|
+
}
|
|
986
|
+
// +1 to land inside the column's first child rather than on the column boundary.
|
|
987
|
+
var caretPos = layoutSectionPos + columnOffset + 1;
|
|
988
|
+
if (caretPos >= 0 && caretPos <= tr.doc.content.size) {
|
|
989
|
+
var caretSelection = TextSelection.findFrom(tr.doc.resolve(caretPos), 1, true);
|
|
990
|
+
if (caretSelection) {
|
|
991
|
+
tr.setSelection(caretSelection);
|
|
992
|
+
}
|
|
993
|
+
}
|
|
994
|
+
}
|
|
995
|
+
}
|
|
921
996
|
emitDeleteColumnAnalytics(redistributed.length);
|
|
922
997
|
tr.setMeta('scrollIntoView', false);
|
|
923
998
|
api === null || api === void 0 || (_api$blockControls4 = api.blockControls) === null || _api$blockControls4 === void 0 || _api$blockControls4.commands.stopPreservingSelection()({
|
|
@@ -4,10 +4,40 @@ function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t =
|
|
|
4
4
|
import { bind } from 'bind-event-listener';
|
|
5
5
|
import { ACTION, ACTION_SUBJECT, ACTION_SUBJECT_ID, EVENT_TYPE, INPUT_METHOD } from '@atlaskit/editor-common/analytics';
|
|
6
6
|
import { Fragment } from '@atlaskit/editor-prosemirror/model';
|
|
7
|
+
import { NodeSelection, TextSelection } from '@atlaskit/editor-prosemirror/state';
|
|
7
8
|
import { Decoration } from '@atlaskit/editor-prosemirror/view';
|
|
8
9
|
import { fg } from '@atlaskit/platform-feature-flags';
|
|
9
10
|
import { MIN_LAYOUT_COLUMN_WIDTH_PERCENT } from './consts';
|
|
10
11
|
|
|
12
|
+
/**
|
|
13
|
+
* Re-creates a selection at the same positions against the resized document. A resize only
|
|
14
|
+
* changes width attrs (not node count, order, or size), so the original positions are still
|
|
15
|
+
* valid. Returns `undefined` if no valid selection can be made.
|
|
16
|
+
*/
|
|
17
|
+
var remapResizeSelection = function remapResizeSelection(selection, doc) {
|
|
18
|
+
var docSize = doc.content.size;
|
|
19
|
+
var clamp = function clamp(pos) {
|
|
20
|
+
return Math.min(Math.max(pos, 0), docSize);
|
|
21
|
+
};
|
|
22
|
+
if (selection instanceof NodeSelection) {
|
|
23
|
+
var _TextSelection$findFr;
|
|
24
|
+
var pos = clamp(selection.from);
|
|
25
|
+
var nodeAtPos = doc.nodeAt(pos);
|
|
26
|
+
if (nodeAtPos && NodeSelection.isSelectable(nodeAtPos)) {
|
|
27
|
+
return NodeSelection.create(doc, pos);
|
|
28
|
+
}
|
|
29
|
+
return (_TextSelection$findFr = TextSelection.findFrom(doc.resolve(pos), 1, true)) !== null && _TextSelection$findFr !== void 0 ? _TextSelection$findFr : undefined;
|
|
30
|
+
}
|
|
31
|
+
if (selection instanceof TextSelection) {
|
|
32
|
+
try {
|
|
33
|
+
return TextSelection.create(doc, clamp(selection.from), clamp(selection.to));
|
|
34
|
+
} catch (_unused) {
|
|
35
|
+
return undefined;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
return undefined;
|
|
39
|
+
};
|
|
40
|
+
|
|
11
41
|
// Class names for the column resize divider widget — must stay in sync with layout.ts in editor-core
|
|
12
42
|
var layoutColumnDividerClassName = 'layout-column-divider';
|
|
13
43
|
var layoutColumnDividerRailClassName = 'layout-column-divider-rail';
|
|
@@ -44,9 +74,20 @@ var dispatchColumnWidths = function dispatchColumnWidths(view, sectionPos, leftC
|
|
|
44
74
|
newColumns.push(child);
|
|
45
75
|
}
|
|
46
76
|
});
|
|
77
|
+
|
|
78
|
+
// Capture the selection before the replace below, which otherwise maps the caret out of
|
|
79
|
+
// the layout to the following paragraph. Restored at the same positions afterwards.
|
|
80
|
+
var shouldPreserveSelection = !fg('platform_editor_layout_column_menu_kill_switch_1');
|
|
81
|
+
var selectionBeforeResize = shouldPreserveSelection ? state.selection : null;
|
|
47
82
|
tr.replaceWith(sectionPos + 1, sectionPos + sectionNode.nodeSize - 1, Fragment.from(newColumns));
|
|
48
83
|
tr.setMeta('layoutColumnResize', true);
|
|
49
84
|
tr.setMeta('scrollIntoView', false);
|
|
85
|
+
if (selectionBeforeResize) {
|
|
86
|
+
var restoredSelection = remapResizeSelection(selectionBeforeResize, tr.doc);
|
|
87
|
+
if (restoredSelection) {
|
|
88
|
+
tr.setSelection(restoredSelection);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
50
91
|
if (fg('platform_editor_layout_resize_analytics')) {
|
|
51
92
|
editorAnalyticsAPI === null || editorAnalyticsAPI === void 0 || editorAnalyticsAPI.attachAnalyticsEvent({
|
|
52
93
|
action: ACTION.DRAGGED,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@atlaskit/editor-plugin-layout",
|
|
3
|
-
"version": "13.
|
|
3
|
+
"version": "13.3.0",
|
|
4
4
|
"description": "Layout plugin for @atlaskit/editor-core",
|
|
5
5
|
"author": "Atlassian Pty Ltd",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -37,19 +37,19 @@
|
|
|
37
37
|
"@atlaskit/editor-plugin-width": "^13.0.0",
|
|
38
38
|
"@atlaskit/editor-prosemirror": "^8.0.0",
|
|
39
39
|
"@atlaskit/editor-shared-styles": "^4.0.0",
|
|
40
|
-
"@atlaskit/editor-toolbar": "^2.
|
|
41
|
-
"@atlaskit/editor-ui-control-model": "^2.
|
|
42
|
-
"@atlaskit/icon": "^36.
|
|
43
|
-
"@atlaskit/icon-lab": "^7.
|
|
40
|
+
"@atlaskit/editor-toolbar": "^2.1.0",
|
|
41
|
+
"@atlaskit/editor-ui-control-model": "^2.1.0",
|
|
42
|
+
"@atlaskit/icon": "^36.1.0",
|
|
43
|
+
"@atlaskit/icon-lab": "^7.2.0",
|
|
44
44
|
"@atlaskit/platform-feature-flags": "^2.0.0",
|
|
45
|
-
"@atlaskit/tmp-editor-statsig": "^
|
|
46
|
-
"@atlaskit/tokens": "^15.
|
|
45
|
+
"@atlaskit/tmp-editor-statsig": "^114.0.0",
|
|
46
|
+
"@atlaskit/tokens": "^15.1.0",
|
|
47
47
|
"@babel/runtime": "^7.0.0",
|
|
48
48
|
"@emotion/react": "^11.7.1",
|
|
49
49
|
"bind-event-listener": "^3.0.0"
|
|
50
50
|
},
|
|
51
51
|
"peerDependencies": {
|
|
52
|
-
"@atlaskit/editor-common": "^116.
|
|
52
|
+
"@atlaskit/editor-common": "^116.15.0",
|
|
53
53
|
"react": "^18.2.0",
|
|
54
54
|
"react-intl": "^5.25.1 || ^6.0.0 || ^7.0.0"
|
|
55
55
|
},
|