@atlaskit/editor-plugin-selection-extension 7.1.0 → 7.1.2

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,18 @@
1
1
  # @atlaskit/editor-plugin-selection-extension
2
2
 
3
+ ## 7.1.2
4
+
5
+ ### Patch Changes
6
+
7
+ - [`0b0f96c20a852`](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/commits/0b0f96c20a852) -
8
+ Improve getSelectionAdf api to return expanded selection
9
+
10
+ ## 7.1.1
11
+
12
+ ### Patch Changes
13
+
14
+ - Updated dependencies
15
+
3
16
  ## 7.1.0
4
17
 
5
18
  ### Minor Changes
@@ -18,6 +18,7 @@ var _editorSharedStyles = require("@atlaskit/editor-shared-styles");
18
18
  var _editorTables = require("@atlaskit/editor-tables");
19
19
  var _platformFeatureFlags = require("@atlaskit/platform-feature-flags");
20
20
  var _getBoundingBoxFromSelection = require("../../ui/getBoundingBoxFromSelection");
21
+ var _selectionHelpers = require("./selection-helpers");
21
22
  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; }
22
23
  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; }
23
24
  var getSelectedRect = function getSelectedRect(selection) {
@@ -29,24 +30,6 @@ var getSelectedRect = function getSelectedRect(selection) {
29
30
  var rect = map.rectBetween($anchorCell.pos - start, $headCell.pos - start);
30
31
  return rect;
31
32
  };
32
- var getSelectionInfoFromSameNode = function getSelectionInfoFromSameNode(selection) {
33
- var $from = selection.$from,
34
- $to = selection.$to;
35
- return {
36
- selectedNode: $from.node(),
37
- selectionRanges: [{
38
- start: {
39
- pointer: "/content/".concat($from.index(), "/text"),
40
- position: $from.parentOffset
41
- },
42
- end: {
43
- pointer: "/content/".concat($from.index(), "/text"),
44
- position: $to.parentOffset
45
- }
46
- }],
47
- nodePos: $from.before() // position before the selection
48
- };
49
- };
50
33
  var getSelectionInfoFromCellSelection = function getSelectionInfoFromCellSelection(selection) {
51
34
  var selectedNode = selection.$anchorCell.node(-1);
52
35
  var nodePos = selection.$anchorCell.before(-1);
@@ -117,12 +100,15 @@ function getSelectionAdfInfo(state) {
117
100
  nodePos: selection.$from.depth > 0 ? selection.$from.before() : selection.from
118
101
  };
119
102
  if (selection instanceof _state.TextSelection) {
120
- var $from = selection.$from,
121
- $to = selection.$to;
122
- if ($from.parent === $to.parent) {
123
- selectionInfo = getSelectionInfoFromSameNode(selection);
103
+ if ((0, _platformFeatureFlags.fg)('platform_editor_selection_extension_improvement')) {
104
+ // New implementation: unified handler for all text selections
105
+ selectionInfo = (0, _selectionHelpers.getSelectionInfo)(selection, state.schema);
124
106
  } else {
125
- // TODO: ED-28405 - when selection spans multiple nodes including nested node, we need to iterate through the nodes
107
+ var $from = selection.$from,
108
+ $to = selection.$to;
109
+ if ($from.parent === $to.parent) {
110
+ selectionInfo = (0, _selectionHelpers.getSelectionInfoFromSameNode)(selection);
111
+ }
126
112
  }
127
113
  } else if (selection instanceof _editorTables.CellSelection) {
128
114
  selectionInfo = getSelectionInfoFromCellSelection(selection);
@@ -134,17 +120,20 @@ function getSelectionAdfInfo(state) {
134
120
  });
135
121
  }
136
122
  function getSelectionAdfInfoNew(selection) {
123
+ var schema = selection.$from.doc.type.schema;
137
124
  var selectionInfo = {
138
125
  selectedNode: selection.$from.node(),
139
126
  nodePos: selection.$from.depth > 0 ? selection.$from.before() : selection.from
140
127
  };
141
128
  if (selection instanceof _state.TextSelection) {
142
- var $from = selection.$from,
143
- $to = selection.$to;
144
- if ($from.parent === $to.parent) {
145
- selectionInfo = getSelectionInfoFromSameNode(selection);
129
+ if ((0, _platformFeatureFlags.fg)('platform_editor_selection_extension_improvement')) {
130
+ selectionInfo = (0, _selectionHelpers.getSelectionInfo)(selection, schema);
146
131
  } else {
147
- // TODO: ED-28405 - when selection spans multiple nodes including nested node, we need to iterate through the nodes
132
+ var $from = selection.$from,
133
+ $to = selection.$to;
134
+ if ($from.parent === $to.parent) {
135
+ selectionInfo = (0, _selectionHelpers.getSelectionInfoFromSameNode)(selection);
136
+ }
148
137
  }
149
138
  } else if (selection instanceof _editorTables.CellSelection) {
150
139
  selectionInfo = getSelectionInfoFromCellSelection(selection);
@@ -0,0 +1,168 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.wrapNodesInDoc = exports.getSelectionInfoFromSameNode = exports.getSelectionInfo = exports.getCommonParentContainer = void 0;
7
+ var _monitoring = require("@atlaskit/editor-common/monitoring");
8
+ var _model = require("@atlaskit/editor-prosemirror/model");
9
+ var LIST_ITEM_TYPES = new Set(['taskItem', 'decisionItem', 'listItem']);
10
+ var LIST_NODE_TYPES = new Set(['taskList', 'bulletList', 'orderedList', 'decisionList']);
11
+
12
+ /**
13
+ * Find the depth of the deepest common ancestor node.
14
+ */
15
+ var getCommonAncestorDepth = function getCommonAncestorDepth($from, $to) {
16
+ var minDepth = Math.min($from.depth, $to.depth);
17
+ for (var d = 0; d <= minDepth; d++) {
18
+ if ($from.node(d) !== $to.node(d)) {
19
+ return d - 1;
20
+ }
21
+ }
22
+ return minDepth;
23
+ };
24
+
25
+ /**
26
+ * Find the closest parent container node that contains the selection.
27
+ * - For lists: returns the topmost list (to handle nested lists)
28
+ * - For other containers returns the closest one
29
+ * Returns the parent and its position.
30
+ */
31
+ var getCommonParentContainer = exports.getCommonParentContainer = function getCommonParentContainer($from, $to) {
32
+ var commonDepth = getCommonAncestorDepth($from, $to);
33
+
34
+ // Single pass: look for topmost list OR first non-list parent
35
+ var topMostList = null;
36
+ var topMostListPos = -1;
37
+ var firstNonListParent = null;
38
+ var firstNonListParentPos = -1;
39
+ for (var depth = commonDepth; depth > 0; depth--) {
40
+ var node = $from.node(depth);
41
+ if (LIST_NODE_TYPES.has(node.type.name)) {
42
+ // Keep updating to find the topmost list (last one found going upward)
43
+ topMostList = node;
44
+ topMostListPos = $from.before(depth);
45
+ } else if (!firstNonListParent && node.type.name !== 'doc') {
46
+ // Only capture the first (innermost) non-list parent
47
+ firstNonListParent = node;
48
+ firstNonListParentPos = $from.before(depth);
49
+ }
50
+ }
51
+
52
+ // Return topmost list if found, else first non-list parent
53
+ if (topMostList) {
54
+ return {
55
+ node: topMostList,
56
+ pos: topMostListPos
57
+ };
58
+ }
59
+ return {
60
+ node: firstNonListParent,
61
+ pos: firstNonListParentPos
62
+ };
63
+ };
64
+
65
+ /**
66
+ * Wraps nodes in a doc fragment if there are multiple nodes
67
+ */
68
+ var wrapNodesInDoc = exports.wrapNodesInDoc = function wrapNodesInDoc(schema, nodes) {
69
+ if (nodes.length === 0) {
70
+ return schema.nodes.doc.createChecked({}, _model.Fragment.empty);
71
+ }
72
+
73
+ // Single node: return unwrapped
74
+ if (nodes.length === 1) {
75
+ return nodes[0];
76
+ }
77
+
78
+ // For multiple nodes, wrap in doc
79
+ try {
80
+ return schema.node('doc', null, _model.Fragment.from(nodes));
81
+ } catch (error) {
82
+ (0, _monitoring.logException)(error, {
83
+ location: 'editor-plugin-selection-extension'
84
+ });
85
+ return schema.nodes.doc.createChecked({}, _model.Fragment.empty);
86
+ }
87
+ };
88
+ var getSelectionInfoFromSameNode = exports.getSelectionInfoFromSameNode = function getSelectionInfoFromSameNode(selection) {
89
+ var $from = selection.$from,
90
+ $to = selection.$to;
91
+ return {
92
+ selectedNode: $from.node(),
93
+ selectionRanges: [{
94
+ start: {
95
+ pointer: "/content/".concat($from.index(), "/text"),
96
+ position: $from.parentOffset
97
+ },
98
+ end: {
99
+ pointer: "/content/".concat($from.index(), "/text"),
100
+ position: $to.parentOffset
101
+ }
102
+ }],
103
+ nodePos: $from.before()
104
+ };
105
+ };
106
+ var getSelectionInfo = exports.getSelectionInfo = function getSelectionInfo(selection, schema) {
107
+ var $from = selection.$from,
108
+ $to = selection.$to;
109
+
110
+ // For same parent selections, check for parent container
111
+ if ($from.parent === $to.parent) {
112
+ var _getCommonParentConta = getCommonParentContainer($from, $to),
113
+ parentNode = _getCommonParentConta.node,
114
+ parentNodePos = _getCommonParentConta.pos;
115
+ if (parentNode) {
116
+ return {
117
+ selectedNode: parentNode,
118
+ nodePos: parentNodePos
119
+ };
120
+ }
121
+ var nodePos = $from.before();
122
+ return {
123
+ selectedNode: $from.node(),
124
+ nodePos: nodePos
125
+ };
126
+ }
127
+
128
+ // find the common ancestor
129
+ var range = $from.blockRange($to);
130
+ if (!range) {
131
+ return {
132
+ selectedNode: $from.node(),
133
+ nodePos: $from.depth > 0 ? $from.before() : $from.pos
134
+ };
135
+ }
136
+ if (range.parent.type.name !== 'doc') {
137
+ // If it's a list OR list item, check for topmost list parent
138
+ if (LIST_NODE_TYPES.has(range.parent.type.name) || LIST_ITEM_TYPES.has(range.parent.type.name)) {
139
+ var _getCommonParentConta2 = getCommonParentContainer($from, $to),
140
+ topList = _getCommonParentConta2.node,
141
+ topListPos = _getCommonParentConta2.pos;
142
+ if (topList) {
143
+ return {
144
+ selectedNode: topList,
145
+ nodePos: topListPos
146
+ };
147
+ }
148
+ }
149
+
150
+ // For non-list containers (panel, expand, etc.), return the immediate parent
151
+ var _nodePos = range.depth > 0 ? $from.before(range.depth) : 0;
152
+ return {
153
+ selectedNode: range.parent,
154
+ nodePos: _nodePos
155
+ };
156
+ }
157
+
158
+ // Extract complete nodes within the block range
159
+ var nodes = [];
160
+ for (var i = range.startIndex; i < range.endIndex; i++) {
161
+ nodes.push(range.parent.child(i));
162
+ }
163
+ var selectedNode = wrapNodesInDoc(schema, nodes);
164
+ return {
165
+ selectedNode: selectedNode,
166
+ nodePos: range.start
167
+ };
168
+ };
@@ -7,6 +7,7 @@ import { akEditorFullPageToolbarHeight } from '@atlaskit/editor-shared-styles';
7
7
  import { CellSelection, TableMap } from '@atlaskit/editor-tables';
8
8
  import { fg } from '@atlaskit/platform-feature-flags';
9
9
  import { getBoundingBoxFromSelection } from '../../ui/getBoundingBoxFromSelection';
10
+ import { getSelectionInfo, getSelectionInfoFromSameNode } from './selection-helpers';
10
11
  const getSelectedRect = selection => {
11
12
  const {
12
13
  $anchorCell,
@@ -18,26 +19,6 @@ const getSelectedRect = selection => {
18
19
  const rect = map.rectBetween($anchorCell.pos - start, $headCell.pos - start);
19
20
  return rect;
20
21
  };
21
- const getSelectionInfoFromSameNode = selection => {
22
- const {
23
- $from,
24
- $to
25
- } = selection;
26
- return {
27
- selectedNode: $from.node(),
28
- selectionRanges: [{
29
- start: {
30
- pointer: `/content/${$from.index()}/text`,
31
- position: $from.parentOffset
32
- },
33
- end: {
34
- pointer: `/content/${$from.index()}/text`,
35
- position: $to.parentOffset
36
- }
37
- }],
38
- nodePos: $from.before() // position before the selection
39
- };
40
- };
41
22
  const getSelectionInfoFromCellSelection = selection => {
42
23
  const selectedNode = selection.$anchorCell.node(-1);
43
24
  const nodePos = selection.$anchorCell.before(-1);
@@ -116,14 +97,17 @@ export function getSelectionAdfInfo(state) {
116
97
  nodePos: selection.$from.depth > 0 ? selection.$from.before() : selection.from
117
98
  };
118
99
  if (selection instanceof TextSelection) {
119
- const {
120
- $from,
121
- $to
122
- } = selection;
123
- if ($from.parent === $to.parent) {
124
- selectionInfo = getSelectionInfoFromSameNode(selection);
100
+ if (fg('platform_editor_selection_extension_improvement')) {
101
+ // New implementation: unified handler for all text selections
102
+ selectionInfo = getSelectionInfo(selection, state.schema);
125
103
  } else {
126
- // TODO: ED-28405 - when selection spans multiple nodes including nested node, we need to iterate through the nodes
104
+ const {
105
+ $from,
106
+ $to
107
+ } = selection;
108
+ if ($from.parent === $to.parent) {
109
+ selectionInfo = getSelectionInfoFromSameNode(selection);
110
+ }
127
111
  }
128
112
  } else if (selection instanceof CellSelection) {
129
113
  selectionInfo = getSelectionInfoFromCellSelection(selection);
@@ -136,19 +120,22 @@ export function getSelectionAdfInfo(state) {
136
120
  };
137
121
  }
138
122
  export function getSelectionAdfInfoNew(selection) {
123
+ const schema = selection.$from.doc.type.schema;
139
124
  let selectionInfo = {
140
125
  selectedNode: selection.$from.node(),
141
126
  nodePos: selection.$from.depth > 0 ? selection.$from.before() : selection.from
142
127
  };
143
128
  if (selection instanceof TextSelection) {
144
- const {
145
- $from,
146
- $to
147
- } = selection;
148
- if ($from.parent === $to.parent) {
149
- selectionInfo = getSelectionInfoFromSameNode(selection);
129
+ if (fg('platform_editor_selection_extension_improvement')) {
130
+ selectionInfo = getSelectionInfo(selection, schema);
150
131
  } else {
151
- // TODO: ED-28405 - when selection spans multiple nodes including nested node, we need to iterate through the nodes
132
+ const {
133
+ $from,
134
+ $to
135
+ } = selection;
136
+ if ($from.parent === $to.parent) {
137
+ selectionInfo = getSelectionInfoFromSameNode(selection);
138
+ }
152
139
  }
153
140
  } else if (selection instanceof CellSelection) {
154
141
  selectionInfo = getSelectionInfoFromCellSelection(selection);
@@ -0,0 +1,168 @@
1
+ import { logException } from '@atlaskit/editor-common/monitoring';
2
+ import { Fragment } from '@atlaskit/editor-prosemirror/model';
3
+ const LIST_ITEM_TYPES = new Set(['taskItem', 'decisionItem', 'listItem']);
4
+ const LIST_NODE_TYPES = new Set(['taskList', 'bulletList', 'orderedList', 'decisionList']);
5
+
6
+ /**
7
+ * Find the depth of the deepest common ancestor node.
8
+ */
9
+ const getCommonAncestorDepth = ($from, $to) => {
10
+ const minDepth = Math.min($from.depth, $to.depth);
11
+ for (let d = 0; d <= minDepth; d++) {
12
+ if ($from.node(d) !== $to.node(d)) {
13
+ return d - 1;
14
+ }
15
+ }
16
+ return minDepth;
17
+ };
18
+
19
+ /**
20
+ * Find the closest parent container node that contains the selection.
21
+ * - For lists: returns the topmost list (to handle nested lists)
22
+ * - For other containers returns the closest one
23
+ * Returns the parent and its position.
24
+ */
25
+ export const getCommonParentContainer = ($from, $to) => {
26
+ const commonDepth = getCommonAncestorDepth($from, $to);
27
+
28
+ // Single pass: look for topmost list OR first non-list parent
29
+ let topMostList = null;
30
+ let topMostListPos = -1;
31
+ let firstNonListParent = null;
32
+ let firstNonListParentPos = -1;
33
+ for (let depth = commonDepth; depth > 0; depth--) {
34
+ const node = $from.node(depth);
35
+ if (LIST_NODE_TYPES.has(node.type.name)) {
36
+ // Keep updating to find the topmost list (last one found going upward)
37
+ topMostList = node;
38
+ topMostListPos = $from.before(depth);
39
+ } else if (!firstNonListParent && node.type.name !== 'doc') {
40
+ // Only capture the first (innermost) non-list parent
41
+ firstNonListParent = node;
42
+ firstNonListParentPos = $from.before(depth);
43
+ }
44
+ }
45
+
46
+ // Return topmost list if found, else first non-list parent
47
+ if (topMostList) {
48
+ return {
49
+ node: topMostList,
50
+ pos: topMostListPos
51
+ };
52
+ }
53
+ return {
54
+ node: firstNonListParent,
55
+ pos: firstNonListParentPos
56
+ };
57
+ };
58
+
59
+ /**
60
+ * Wraps nodes in a doc fragment if there are multiple nodes
61
+ */
62
+ export const wrapNodesInDoc = (schema, nodes) => {
63
+ if (nodes.length === 0) {
64
+ return schema.nodes.doc.createChecked({}, Fragment.empty);
65
+ }
66
+
67
+ // Single node: return unwrapped
68
+ if (nodes.length === 1) {
69
+ return nodes[0];
70
+ }
71
+
72
+ // For multiple nodes, wrap in doc
73
+ try {
74
+ return schema.node('doc', null, Fragment.from(nodes));
75
+ } catch (error) {
76
+ logException(error, {
77
+ location: 'editor-plugin-selection-extension'
78
+ });
79
+ return schema.nodes.doc.createChecked({}, Fragment.empty);
80
+ }
81
+ };
82
+ export const getSelectionInfoFromSameNode = selection => {
83
+ const {
84
+ $from,
85
+ $to
86
+ } = selection;
87
+ return {
88
+ selectedNode: $from.node(),
89
+ selectionRanges: [{
90
+ start: {
91
+ pointer: `/content/${$from.index()}/text`,
92
+ position: $from.parentOffset
93
+ },
94
+ end: {
95
+ pointer: `/content/${$from.index()}/text`,
96
+ position: $to.parentOffset
97
+ }
98
+ }],
99
+ nodePos: $from.before()
100
+ };
101
+ };
102
+ export const getSelectionInfo = (selection, schema) => {
103
+ const {
104
+ $from,
105
+ $to
106
+ } = selection;
107
+
108
+ // For same parent selections, check for parent container
109
+ if ($from.parent === $to.parent) {
110
+ const {
111
+ node: parentNode,
112
+ pos: parentNodePos
113
+ } = getCommonParentContainer($from, $to);
114
+ if (parentNode) {
115
+ return {
116
+ selectedNode: parentNode,
117
+ nodePos: parentNodePos
118
+ };
119
+ }
120
+ const nodePos = $from.before();
121
+ return {
122
+ selectedNode: $from.node(),
123
+ nodePos
124
+ };
125
+ }
126
+
127
+ // find the common ancestor
128
+ const range = $from.blockRange($to);
129
+ if (!range) {
130
+ return {
131
+ selectedNode: $from.node(),
132
+ nodePos: $from.depth > 0 ? $from.before() : $from.pos
133
+ };
134
+ }
135
+ if (range.parent.type.name !== 'doc') {
136
+ // If it's a list OR list item, check for topmost list parent
137
+ if (LIST_NODE_TYPES.has(range.parent.type.name) || LIST_ITEM_TYPES.has(range.parent.type.name)) {
138
+ const {
139
+ node: topList,
140
+ pos: topListPos
141
+ } = getCommonParentContainer($from, $to);
142
+ if (topList) {
143
+ return {
144
+ selectedNode: topList,
145
+ nodePos: topListPos
146
+ };
147
+ }
148
+ }
149
+
150
+ // For non-list containers (panel, expand, etc.), return the immediate parent
151
+ const nodePos = range.depth > 0 ? $from.before(range.depth) : 0;
152
+ return {
153
+ selectedNode: range.parent,
154
+ nodePos
155
+ };
156
+ }
157
+
158
+ // Extract complete nodes within the block range
159
+ const nodes = [];
160
+ for (let i = range.startIndex; i < range.endIndex; i++) {
161
+ nodes.push(range.parent.child(i));
162
+ }
163
+ const selectedNode = wrapNodesInDoc(schema, nodes);
164
+ return {
165
+ selectedNode,
166
+ nodePos: range.start
167
+ };
168
+ };
@@ -10,6 +10,7 @@ import { akEditorFullPageToolbarHeight } from '@atlaskit/editor-shared-styles';
10
10
  import { CellSelection, TableMap } from '@atlaskit/editor-tables';
11
11
  import { fg } from '@atlaskit/platform-feature-flags';
12
12
  import { getBoundingBoxFromSelection } from '../../ui/getBoundingBoxFromSelection';
13
+ import { getSelectionInfo, getSelectionInfoFromSameNode } from './selection-helpers';
13
14
  var getSelectedRect = function getSelectedRect(selection) {
14
15
  var $anchorCell = selection.$anchorCell,
15
16
  $headCell = selection.$headCell;
@@ -19,24 +20,6 @@ var getSelectedRect = function getSelectedRect(selection) {
19
20
  var rect = map.rectBetween($anchorCell.pos - start, $headCell.pos - start);
20
21
  return rect;
21
22
  };
22
- var getSelectionInfoFromSameNode = function getSelectionInfoFromSameNode(selection) {
23
- var $from = selection.$from,
24
- $to = selection.$to;
25
- return {
26
- selectedNode: $from.node(),
27
- selectionRanges: [{
28
- start: {
29
- pointer: "/content/".concat($from.index(), "/text"),
30
- position: $from.parentOffset
31
- },
32
- end: {
33
- pointer: "/content/".concat($from.index(), "/text"),
34
- position: $to.parentOffset
35
- }
36
- }],
37
- nodePos: $from.before() // position before the selection
38
- };
39
- };
40
23
  var getSelectionInfoFromCellSelection = function getSelectionInfoFromCellSelection(selection) {
41
24
  var selectedNode = selection.$anchorCell.node(-1);
42
25
  var nodePos = selection.$anchorCell.before(-1);
@@ -107,12 +90,15 @@ export function getSelectionAdfInfo(state) {
107
90
  nodePos: selection.$from.depth > 0 ? selection.$from.before() : selection.from
108
91
  };
109
92
  if (selection instanceof TextSelection) {
110
- var $from = selection.$from,
111
- $to = selection.$to;
112
- if ($from.parent === $to.parent) {
113
- selectionInfo = getSelectionInfoFromSameNode(selection);
93
+ if (fg('platform_editor_selection_extension_improvement')) {
94
+ // New implementation: unified handler for all text selections
95
+ selectionInfo = getSelectionInfo(selection, state.schema);
114
96
  } else {
115
- // TODO: ED-28405 - when selection spans multiple nodes including nested node, we need to iterate through the nodes
97
+ var $from = selection.$from,
98
+ $to = selection.$to;
99
+ if ($from.parent === $to.parent) {
100
+ selectionInfo = getSelectionInfoFromSameNode(selection);
101
+ }
116
102
  }
117
103
  } else if (selection instanceof CellSelection) {
118
104
  selectionInfo = getSelectionInfoFromCellSelection(selection);
@@ -124,17 +110,20 @@ export function getSelectionAdfInfo(state) {
124
110
  });
125
111
  }
126
112
  export function getSelectionAdfInfoNew(selection) {
113
+ var schema = selection.$from.doc.type.schema;
127
114
  var selectionInfo = {
128
115
  selectedNode: selection.$from.node(),
129
116
  nodePos: selection.$from.depth > 0 ? selection.$from.before() : selection.from
130
117
  };
131
118
  if (selection instanceof TextSelection) {
132
- var $from = selection.$from,
133
- $to = selection.$to;
134
- if ($from.parent === $to.parent) {
135
- selectionInfo = getSelectionInfoFromSameNode(selection);
119
+ if (fg('platform_editor_selection_extension_improvement')) {
120
+ selectionInfo = getSelectionInfo(selection, schema);
136
121
  } else {
137
- // TODO: ED-28405 - when selection spans multiple nodes including nested node, we need to iterate through the nodes
122
+ var $from = selection.$from,
123
+ $to = selection.$to;
124
+ if ($from.parent === $to.parent) {
125
+ selectionInfo = getSelectionInfoFromSameNode(selection);
126
+ }
138
127
  }
139
128
  } else if (selection instanceof CellSelection) {
140
129
  selectionInfo = getSelectionInfoFromCellSelection(selection);
@@ -0,0 +1,162 @@
1
+ import { logException } from '@atlaskit/editor-common/monitoring';
2
+ import { Fragment } from '@atlaskit/editor-prosemirror/model';
3
+ var LIST_ITEM_TYPES = new Set(['taskItem', 'decisionItem', 'listItem']);
4
+ var LIST_NODE_TYPES = new Set(['taskList', 'bulletList', 'orderedList', 'decisionList']);
5
+
6
+ /**
7
+ * Find the depth of the deepest common ancestor node.
8
+ */
9
+ var getCommonAncestorDepth = function getCommonAncestorDepth($from, $to) {
10
+ var minDepth = Math.min($from.depth, $to.depth);
11
+ for (var d = 0; d <= minDepth; d++) {
12
+ if ($from.node(d) !== $to.node(d)) {
13
+ return d - 1;
14
+ }
15
+ }
16
+ return minDepth;
17
+ };
18
+
19
+ /**
20
+ * Find the closest parent container node that contains the selection.
21
+ * - For lists: returns the topmost list (to handle nested lists)
22
+ * - For other containers returns the closest one
23
+ * Returns the parent and its position.
24
+ */
25
+ export var getCommonParentContainer = function getCommonParentContainer($from, $to) {
26
+ var commonDepth = getCommonAncestorDepth($from, $to);
27
+
28
+ // Single pass: look for topmost list OR first non-list parent
29
+ var topMostList = null;
30
+ var topMostListPos = -1;
31
+ var firstNonListParent = null;
32
+ var firstNonListParentPos = -1;
33
+ for (var depth = commonDepth; depth > 0; depth--) {
34
+ var node = $from.node(depth);
35
+ if (LIST_NODE_TYPES.has(node.type.name)) {
36
+ // Keep updating to find the topmost list (last one found going upward)
37
+ topMostList = node;
38
+ topMostListPos = $from.before(depth);
39
+ } else if (!firstNonListParent && node.type.name !== 'doc') {
40
+ // Only capture the first (innermost) non-list parent
41
+ firstNonListParent = node;
42
+ firstNonListParentPos = $from.before(depth);
43
+ }
44
+ }
45
+
46
+ // Return topmost list if found, else first non-list parent
47
+ if (topMostList) {
48
+ return {
49
+ node: topMostList,
50
+ pos: topMostListPos
51
+ };
52
+ }
53
+ return {
54
+ node: firstNonListParent,
55
+ pos: firstNonListParentPos
56
+ };
57
+ };
58
+
59
+ /**
60
+ * Wraps nodes in a doc fragment if there are multiple nodes
61
+ */
62
+ export var wrapNodesInDoc = function wrapNodesInDoc(schema, nodes) {
63
+ if (nodes.length === 0) {
64
+ return schema.nodes.doc.createChecked({}, Fragment.empty);
65
+ }
66
+
67
+ // Single node: return unwrapped
68
+ if (nodes.length === 1) {
69
+ return nodes[0];
70
+ }
71
+
72
+ // For multiple nodes, wrap in doc
73
+ try {
74
+ return schema.node('doc', null, Fragment.from(nodes));
75
+ } catch (error) {
76
+ logException(error, {
77
+ location: 'editor-plugin-selection-extension'
78
+ });
79
+ return schema.nodes.doc.createChecked({}, Fragment.empty);
80
+ }
81
+ };
82
+ export var getSelectionInfoFromSameNode = function getSelectionInfoFromSameNode(selection) {
83
+ var $from = selection.$from,
84
+ $to = selection.$to;
85
+ return {
86
+ selectedNode: $from.node(),
87
+ selectionRanges: [{
88
+ start: {
89
+ pointer: "/content/".concat($from.index(), "/text"),
90
+ position: $from.parentOffset
91
+ },
92
+ end: {
93
+ pointer: "/content/".concat($from.index(), "/text"),
94
+ position: $to.parentOffset
95
+ }
96
+ }],
97
+ nodePos: $from.before()
98
+ };
99
+ };
100
+ export var getSelectionInfo = function getSelectionInfo(selection, schema) {
101
+ var $from = selection.$from,
102
+ $to = selection.$to;
103
+
104
+ // For same parent selections, check for parent container
105
+ if ($from.parent === $to.parent) {
106
+ var _getCommonParentConta = getCommonParentContainer($from, $to),
107
+ parentNode = _getCommonParentConta.node,
108
+ parentNodePos = _getCommonParentConta.pos;
109
+ if (parentNode) {
110
+ return {
111
+ selectedNode: parentNode,
112
+ nodePos: parentNodePos
113
+ };
114
+ }
115
+ var nodePos = $from.before();
116
+ return {
117
+ selectedNode: $from.node(),
118
+ nodePos: nodePos
119
+ };
120
+ }
121
+
122
+ // find the common ancestor
123
+ var range = $from.blockRange($to);
124
+ if (!range) {
125
+ return {
126
+ selectedNode: $from.node(),
127
+ nodePos: $from.depth > 0 ? $from.before() : $from.pos
128
+ };
129
+ }
130
+ if (range.parent.type.name !== 'doc') {
131
+ // If it's a list OR list item, check for topmost list parent
132
+ if (LIST_NODE_TYPES.has(range.parent.type.name) || LIST_ITEM_TYPES.has(range.parent.type.name)) {
133
+ var _getCommonParentConta2 = getCommonParentContainer($from, $to),
134
+ topList = _getCommonParentConta2.node,
135
+ topListPos = _getCommonParentConta2.pos;
136
+ if (topList) {
137
+ return {
138
+ selectedNode: topList,
139
+ nodePos: topListPos
140
+ };
141
+ }
142
+ }
143
+
144
+ // For non-list containers (panel, expand, etc.), return the immediate parent
145
+ var _nodePos = range.depth > 0 ? $from.before(range.depth) : 0;
146
+ return {
147
+ selectedNode: range.parent,
148
+ nodePos: _nodePos
149
+ };
150
+ }
151
+
152
+ // Extract complete nodes within the block range
153
+ var nodes = [];
154
+ for (var i = range.startIndex; i < range.endIndex; i++) {
155
+ nodes.push(range.parent.child(i));
156
+ }
157
+ var selectedNode = wrapNodesInDoc(schema, nodes);
158
+ return {
159
+ selectedNode: selectedNode,
160
+ nodePos: range.start
161
+ };
162
+ };
@@ -0,0 +1,34 @@
1
+ import { type Node as PMNode, type ResolvedPos, type Schema } from '@atlaskit/editor-prosemirror/model';
2
+ import type { TextSelection } from '@atlaskit/editor-prosemirror/state';
3
+ /**
4
+ * Find the closest parent container node that contains the selection.
5
+ * - For lists: returns the topmost list (to handle nested lists)
6
+ * - For other containers returns the closest one
7
+ * Returns the parent and its position.
8
+ */
9
+ export declare const getCommonParentContainer: ($from: ResolvedPos, $to: ResolvedPos) => {
10
+ node: PMNode | null;
11
+ pos: number;
12
+ };
13
+ /**
14
+ * Wraps nodes in a doc fragment if there are multiple nodes
15
+ */
16
+ export declare const wrapNodesInDoc: (schema: Schema, nodes: PMNode[]) => PMNode;
17
+ export declare const getSelectionInfoFromSameNode: (selection: TextSelection) => {
18
+ selectedNode: PMNode;
19
+ selectionRanges: {
20
+ start: {
21
+ pointer: string;
22
+ position: number;
23
+ };
24
+ end: {
25
+ pointer: string;
26
+ position: number;
27
+ };
28
+ }[];
29
+ nodePos: number;
30
+ };
31
+ export declare const getSelectionInfo: (selection: TextSelection, schema: Schema) => {
32
+ selectedNode: PMNode;
33
+ nodePos: number;
34
+ };
@@ -0,0 +1,34 @@
1
+ import { type Node as PMNode, type ResolvedPos, type Schema } from '@atlaskit/editor-prosemirror/model';
2
+ import type { TextSelection } from '@atlaskit/editor-prosemirror/state';
3
+ /**
4
+ * Find the closest parent container node that contains the selection.
5
+ * - For lists: returns the topmost list (to handle nested lists)
6
+ * - For other containers returns the closest one
7
+ * Returns the parent and its position.
8
+ */
9
+ export declare const getCommonParentContainer: ($from: ResolvedPos, $to: ResolvedPos) => {
10
+ node: PMNode | null;
11
+ pos: number;
12
+ };
13
+ /**
14
+ * Wraps nodes in a doc fragment if there are multiple nodes
15
+ */
16
+ export declare const wrapNodesInDoc: (schema: Schema, nodes: PMNode[]) => PMNode;
17
+ export declare const getSelectionInfoFromSameNode: (selection: TextSelection) => {
18
+ selectedNode: PMNode;
19
+ selectionRanges: {
20
+ start: {
21
+ pointer: string;
22
+ position: number;
23
+ };
24
+ end: {
25
+ pointer: string;
26
+ position: number;
27
+ };
28
+ }[];
29
+ nodePos: number;
30
+ };
31
+ export declare const getSelectionInfo: (selection: TextSelection, schema: Schema) => {
32
+ selectedNode: PMNode;
33
+ nodePos: number;
34
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@atlaskit/editor-plugin-selection-extension",
3
- "version": "7.1.0",
3
+ "version": "7.1.2",
4
4
  "description": "editor-plugin-selection-extension plugin for @atlaskit/editor-core",
5
5
  "author": "Atlassian Pty Ltd",
6
6
  "license": "Apache-2.0",
@@ -35,7 +35,7 @@
35
35
  "@atlaskit/adf-utils": "^19.26.0",
36
36
  "@atlaskit/editor-json-transformer": "^8.31.0",
37
37
  "@atlaskit/editor-plugin-analytics": "^6.2.0",
38
- "@atlaskit/editor-plugin-block-menu": "^5.0.0",
38
+ "@atlaskit/editor-plugin-block-menu": "^5.1.0",
39
39
  "@atlaskit/editor-plugin-editor-viewmode": "^8.0.0",
40
40
  "@atlaskit/editor-plugin-editor-viewmode-effects": "^6.1.0",
41
41
  "@atlaskit/editor-plugin-primary-toolbar": "^7.0.0",
@@ -45,18 +45,18 @@
45
45
  "@atlaskit/editor-prosemirror": "7.0.0",
46
46
  "@atlaskit/editor-shared-styles": "^3.10.0",
47
47
  "@atlaskit/editor-tables": "^2.9.0",
48
- "@atlaskit/editor-toolbar": "^0.17.0",
48
+ "@atlaskit/editor-toolbar": "^0.18.0",
49
49
  "@atlaskit/icon": "^29.0.0",
50
50
  "@atlaskit/platform-feature-flags": "^1.1.0",
51
51
  "@atlaskit/platform-feature-flags-react": "^0.4.0",
52
- "@atlaskit/tmp-editor-statsig": "^14.1.0",
52
+ "@atlaskit/tmp-editor-statsig": "^14.4.0",
53
53
  "@babel/runtime": "^7.0.0",
54
54
  "lodash": "^4.17.21",
55
55
  "react-intl-next": "npm:react-intl@^5.18.1",
56
56
  "uuid": "^3.1.0"
57
57
  },
58
58
  "peerDependencies": {
59
- "@atlaskit/editor-common": "^110.36.0",
59
+ "@atlaskit/editor-common": "^110.37.0",
60
60
  "react": "^18.2.0"
61
61
  },
62
62
  "devDependencies": {
@@ -108,6 +108,9 @@
108
108
  },
109
109
  "platform_editor_use_preferences_plugin": {
110
110
  "type": "boolean"
111
+ },
112
+ "platform_editor_selection_extension_improvement": {
113
+ "type": "boolean"
111
114
  }
112
115
  }
113
116
  }