@atlaskit/editor-plugin-selection 2.0.7 → 2.1.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 CHANGED
@@ -1,5 +1,18 @@
1
1
  # @atlaskit/editor-plugin-selection
2
2
 
3
+ ## 2.1.0
4
+
5
+ ### Minor Changes
6
+
7
+ - [#124688](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/pull-requests/124688)
8
+ [`9b1137bda6f87`](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/commits/9b1137bda6f87) -
9
+ [ux] ED-25486 Updates cmd+a behaviour to progressively select nodes behind
10
+ platform_editor_cmd_a_progressively_select_nodes experiment.
11
+
12
+ ### Patch Changes
13
+
14
+ - Updated dependencies
15
+
3
16
  ## 2.0.7
4
17
 
5
18
  ### Patch Changes
@@ -3,14 +3,15 @@
3
3
  Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
- exports.setSelectionRelativeToNode = exports.setSelectionInsideAtNodeEnd = exports.selectNearNode = exports.arrowRight = exports.arrowLeft = void 0;
6
+ exports.setSelectionRelativeToNode = exports.setSelectionInsideAtNodeEnd = exports.selectNodeWithModA = exports.selectNearNode = exports.arrowRight = exports.arrowLeft = void 0;
7
7
  var _selection = require("@atlaskit/editor-common/selection");
8
8
  var _utils = require("@atlaskit/editor-common/utils");
9
9
  var _state = require("@atlaskit/editor-prosemirror/state");
10
+ var _utils2 = require("@atlaskit/editor-tables/utils");
10
11
  var _types = require("../types");
11
12
  var _actions = require("./actions");
12
13
  var _pluginFactory = require("./plugin-factory");
13
- var _utils2 = require("./utils");
14
+ var _utils3 = require("./utils");
14
15
  /* eslint-disable import/no-extraneous-dependencies */
15
16
 
16
17
  var selectNearNode = exports.selectNearNode = function selectNearNode(selectionRelativeToNode, selection) {
@@ -64,12 +65,12 @@ var arrowRightFromGapCursor = function arrowRightFromGapCursor(selection) {
64
65
  $to = selection.$to,
65
66
  side = selection.side;
66
67
  if (side === _selection.Side.LEFT) {
67
- var selectableNode = (0, _utils2.findSelectableContainerAfter)($to, state.doc);
68
+ var selectableNode = (0, _utils3.findSelectableContainerAfter)($to, state.doc);
68
69
  if (selectableNode) {
69
70
  return setSelectionRelativeToNode(_selection.RelativeSelectionPos.Start, _state.NodeSelection.create(state.doc, selectableNode.pos))(state, dispatch);
70
71
  }
71
- } else if (side === _selection.Side.RIGHT && (0, _utils2.isSelectionAtEndOfParentNode)($from, selection)) {
72
- var _selectableNode = (0, _utils2.findSelectableContainerParent)(selection);
72
+ } else if (side === _selection.Side.RIGHT && (0, _utils3.isSelectionAtEndOfParentNode)($from, selection)) {
73
+ var _selectableNode = (0, _utils3.findSelectableContainerParent)(selection);
73
74
  if (_selectableNode) {
74
75
  return setSelectionRelativeToNode(_selection.RelativeSelectionPos.End, _state.NodeSelection.create(state.doc, _selectableNode.pos))(state, dispatch);
75
76
  }
@@ -84,15 +85,15 @@ var arrowLeftFromGapCursor = function arrowLeftFromGapCursor(selection) {
84
85
  var _getPluginState = (0, _pluginFactory.getPluginState)(state),
85
86
  selectionRelativeToNode = _getPluginState.selectionRelativeToNode;
86
87
  if (side === _selection.Side.RIGHT) {
87
- var selectableNode = (0, _utils2.findSelectableContainerBefore)($from, state.doc);
88
+ var selectableNode = (0, _utils3.findSelectableContainerBefore)($from, state.doc);
88
89
  if (selectableNode) {
89
90
  return setSelectionRelativeToNode(_selection.RelativeSelectionPos.End, _state.NodeSelection.create(state.doc, selectableNode.pos))(state, dispatch);
90
91
  }
91
- } else if (side === _selection.Side.LEFT && (0, _utils2.isSelectionAtStartOfParentNode)($from, selection)) {
92
+ } else if (side === _selection.Side.LEFT && (0, _utils3.isSelectionAtStartOfParentNode)($from, selection)) {
92
93
  if (selectionRelativeToNode === _selection.RelativeSelectionPos.Before) {
93
94
  var $parent = state.doc.resolve(selection.$from.before(selection.$from.depth));
94
95
  if ($parent) {
95
- var _selectableNode2 = (0, _utils2.findSelectableContainerBefore)($parent, state.doc);
96
+ var _selectableNode2 = (0, _utils3.findSelectableContainerBefore)($parent, state.doc);
96
97
  if (_selectableNode2 && (0, _selection.isIgnored)(_selectableNode2.node)) {
97
98
  // selection is inside node without gap cursor preceeded by another node without gap cursor - set node selection for previous node
98
99
  return setSelectionRelativeToNode(_selection.RelativeSelectionPos.End, _state.NodeSelection.create(state.doc, _selectableNode2.pos))(state, dispatch);
@@ -102,7 +103,7 @@ var arrowLeftFromGapCursor = function arrowLeftFromGapCursor(selection) {
102
103
  // from responding to arrow left key
103
104
  setSelectionRelativeToNode()(state, dispatch);
104
105
  } else {
105
- var _selectableNode3 = (0, _utils2.findSelectableContainerParent)(selection);
106
+ var _selectableNode3 = (0, _utils3.findSelectableContainerParent)(selection);
106
107
  if (_selectableNode3) {
107
108
  return setSelectionRelativeToNode(_selection.RelativeSelectionPos.Start, _state.NodeSelection.create(state.doc, _selectableNode3.pos))(state, dispatch);
108
109
  }
@@ -119,7 +120,7 @@ var arrowRightFromNode = function arrowRightFromNode(selection) {
119
120
  var _getPluginState2 = (0, _pluginFactory.getPluginState)(state),
120
121
  selectionRelativeToNode = _getPluginState2.selectionRelativeToNode;
121
122
  if (node.isAtom) {
122
- if ((0, _utils2.isSelectionAtEndOfParentNode)($to, selection) && (node.isInline || (0, _selection.isIgnored)(node))) {
123
+ if ((0, _utils3.isSelectionAtEndOfParentNode)($to, selection) && (node.isInline || (0, _selection.isIgnored)(node))) {
123
124
  // selection is for inline node or atom node which is ignored by gap-cursor and that is the last child of its parent node - set text selection after it
124
125
  return findAndSetTextSelection(_selection.RelativeSelectionPos.End, state.doc.resolve(from + 1), _types.SelectionDirection.After)(state, dispatch);
125
126
  }
@@ -128,7 +129,7 @@ var arrowRightFromNode = function arrowRightFromNode(selection) {
128
129
  // selection is for container node - set selection inside it at the start
129
130
  return setSelectionInsideAtNodeStart(_selection.RelativeSelectionPos.Inside, node, from)(state, dispatch);
130
131
  } else if ((0, _selection.isIgnored)(node) && (!selectionRelativeToNode || selectionRelativeToNode === _selection.RelativeSelectionPos.End)) {
131
- var selectableNode = (0, _utils2.findSelectableContainerAfter)($to, state.doc);
132
+ var selectableNode = (0, _utils3.findSelectableContainerAfter)($to, state.doc);
132
133
  if (selectableNode && (0, _selection.isIgnored)(selectableNode.node)) {
133
134
  // selection is for node without gap cursor followed by another node without gap cursor - set node selection for next node
134
135
  return setSelectionRelativeToNode(_selection.RelativeSelectionPos.Start, _state.NodeSelection.create(state.doc, selectableNode.pos))(state, dispatch);
@@ -146,7 +147,7 @@ var arrowLeftFromNode = function arrowLeftFromNode(selection) {
146
147
  var _getPluginState3 = (0, _pluginFactory.getPluginState)(state),
147
148
  selectionRelativeToNode = _getPluginState3.selectionRelativeToNode;
148
149
  if (node.isAtom) {
149
- if ((0, _utils2.isSelectionAtStartOfParentNode)($from, selection) && (node.isInline || (0, _selection.isIgnored)(node))) {
150
+ if ((0, _utils3.isSelectionAtStartOfParentNode)($from, selection) && (node.isInline || (0, _selection.isIgnored)(node))) {
150
151
  // selection is for inline node or atom node which is ignored by gap-cursor and that is the first child of its parent node - set text selection before it
151
152
  return findAndSetTextSelection(_selection.RelativeSelectionPos.Start, state.doc.resolve(from), _types.SelectionDirection.Before)(state, dispatch);
152
153
  }
@@ -160,7 +161,7 @@ var arrowLeftFromNode = function arrowLeftFromNode(selection) {
160
161
  return setSelectionInsideAtNodeStart(_selection.RelativeSelectionPos.Before, node, from)(state, dispatch);
161
162
  } else if ((0, _selection.isIgnored)(node) && selectionRelativeToNode === _selection.RelativeSelectionPos.Start) {
162
163
  // selection is for node without gap cursor preceeded by another node without gap cursor - set node selection for previous node
163
- var selectableNode = (0, _utils2.findSelectableContainerBefore)($from, state.doc);
164
+ var selectableNode = (0, _utils3.findSelectableContainerBefore)($from, state.doc);
164
165
  if (selectableNode && (0, _selection.isIgnored)(selectableNode.node)) {
165
166
  return setSelectionRelativeToNode(_selection.RelativeSelectionPos.End, _state.NodeSelection.create(state.doc, selectableNode.pos))(state, dispatch);
166
167
  }
@@ -170,8 +171,8 @@ var arrowLeftFromNode = function arrowLeftFromNode(selection) {
170
171
  };
171
172
  var arrowRightFromText = function arrowRightFromText(selection) {
172
173
  return function (state, dispatch) {
173
- if ((0, _utils2.isSelectionAtEndOfParentNode)(selection.$to, selection)) {
174
- var selectableNode = (0, _utils2.findSelectableContainerParent)(selection);
174
+ if ((0, _utils3.isSelectionAtEndOfParentNode)(selection.$to, selection)) {
175
+ var selectableNode = (0, _utils3.findSelectableContainerParent)(selection);
175
176
  if (selectableNode) {
176
177
  return setSelectionRelativeToNode(_selection.RelativeSelectionPos.End, _state.NodeSelection.create(state.doc, selectableNode.pos))(state, dispatch);
177
178
  }
@@ -184,7 +185,7 @@ var arrowLeftFromText = function arrowLeftFromText(selection) {
184
185
  var _getPluginState4 = (0, _pluginFactory.getPluginState)(state),
185
186
  selectionRelativeToNode = _getPluginState4.selectionRelativeToNode;
186
187
  if (selectionRelativeToNode === _selection.RelativeSelectionPos.Before) {
187
- var selectableNode = (0, _utils2.findSelectableContainerBefore)(selection.$from, state.doc);
188
+ var selectableNode = (0, _utils3.findSelectableContainerBefore)(selection.$from, state.doc);
188
189
  if (selectableNode && (0, _selection.isIgnored)(selectableNode.node)) {
189
190
  // selection is inside node without gap cursor preceeded by another node without gap cursor - set node selection for previous node
190
191
  return setSelectionRelativeToNode(_selection.RelativeSelectionPos.End, _state.NodeSelection.create(state.doc, selectableNode.pos))(state, dispatch);
@@ -192,8 +193,8 @@ var arrowLeftFromText = function arrowLeftFromText(selection) {
192
193
  // we don't return this as we want to reset the relative pos, but not block other plugins
193
194
  // from responding to arrow left key
194
195
  setSelectionRelativeToNode(undefined)(state, dispatch);
195
- } else if ((0, _utils2.isSelectionAtStartOfParentNode)(selection.$from, selection)) {
196
- var _selectableNode4 = (0, _utils2.findSelectableContainerParent)(selection);
196
+ } else if ((0, _utils3.isSelectionAtStartOfParentNode)(selection.$from, selection)) {
197
+ var _selectableNode4 = (0, _utils3.findSelectableContainerParent)(selection);
197
198
  if (_selectableNode4) {
198
199
  return setSelectionRelativeToNode(_selection.RelativeSelectionPos.Start, _state.NodeSelection.create(state.doc, _selectableNode4.pos))(state, dispatch);
199
200
  }
@@ -215,7 +216,7 @@ var setSelectionInsideAtNodeStart = function setSelectionInsideAtNodeStart(selec
215
216
  if ((0, _utils.isNodeEmpty)(node)) {
216
217
  return findAndSetTextSelection(selectionRelativeToNode, state.doc.resolve(pos), _types.SelectionDirection.After)(state, dispatch);
217
218
  }
218
- var selectableNode = (0, _utils2.findFirstChildNodeToSelect)(node);
219
+ var selectableNode = (0, _utils3.findFirstChildNodeToSelect)(node);
219
220
  if (selectableNode) {
220
221
  var childNode = selectableNode.node,
221
222
  childPos = selectableNode.pos;
@@ -227,7 +228,7 @@ var setSelectionInsideAtNodeStart = function setSelectionInsideAtNodeStart(selec
227
228
  return findAndSetTextSelection(selectionRelativeToNode, state.doc.resolve(selectionPos + 1), _types.SelectionDirection.Before)(state, dispatch);
228
229
  } else if (!(0, _selection.isIgnored)(node)) {
229
230
  return setSelectionRelativeToNode(selectionRelativeToNode, new _selection.GapCursorSelection(state.doc.resolve(selectionPos), _selection.Side.LEFT))(state, dispatch);
230
- } else if ((0, _utils2.isSelectableContainerNode)(node)) {
231
+ } else if ((0, _utils3.isSelectableContainerNode)(node)) {
231
232
  return setSelectionRelativeToNode(selectionRelativeToNode, _state.NodeSelection.create(state.doc, selectionPos))(state, dispatch);
232
233
  }
233
234
  }
@@ -239,7 +240,7 @@ var setSelectionInsideAtNodeEnd = exports.setSelectionInsideAtNodeEnd = function
239
240
  if ((0, _utils.isNodeEmpty)(node)) {
240
241
  return findAndSetTextSelection(selectionRelativeToNode, state.doc.resolve(to), _types.SelectionDirection.Before)(state, dispatch);
241
242
  }
242
- var selectableNode = (0, _utils2.findLastChildNodeToSelect)(node);
243
+ var selectableNode = (0, _utils3.findLastChildNodeToSelect)(node);
243
244
  if (selectableNode) {
244
245
  var childNode = selectableNode.node,
245
246
  childPos = selectableNode.pos;
@@ -251,10 +252,42 @@ var setSelectionInsideAtNodeEnd = exports.setSelectionInsideAtNodeEnd = function
251
252
  return findAndSetTextSelection(selectionRelativeToNode, state.doc.resolve(selectionPos), _types.SelectionDirection.After)(state, dispatch);
252
253
  } else if (!(0, _selection.isIgnored)(node)) {
253
254
  return setSelectionRelativeToNode(selectionRelativeToNode, new _selection.GapCursorSelection(state.doc.resolve(selectionPos + 1), _selection.Side.RIGHT))(state, dispatch);
254
- } else if ((0, _utils2.isSelectableContainerNode)(node)) {
255
+ } else if ((0, _utils3.isSelectableContainerNode)(node)) {
255
256
  return setSelectionRelativeToNode(selectionRelativeToNode, _state.NodeSelection.create(state.doc, selectionPos))(state, dispatch);
256
257
  }
257
258
  }
258
259
  return false;
259
260
  };
261
+ };
262
+ var selectNodeWithModA = exports.selectNodeWithModA = function selectNodeWithModA() {
263
+ return function (state, dispatch) {
264
+ var selection = state.selection;
265
+ var $from = selection.$from,
266
+ $to = selection.$to;
267
+ // Check if the selection is at the top level (e.g., in a paragraph)
268
+ var isTopLevelSelection = $from.depth === 1 || $to.depth === 1;
269
+
270
+ // Determine if the selection is within a code block
271
+ var isInCodeBlock = $from.sameParent($to) && $from.parent.type === state.schema.nodes.codeBlock;
272
+
273
+ // If the selection is at the top level and not in a code block, or if a table is selected, do nothing
274
+ if (isTopLevelSelection && !isInCodeBlock || (0, _utils2.isTableSelected)(selection)) {
275
+ return false;
276
+ }
277
+
278
+ // Get the depth of the first common ancestor node
279
+ var commonAncestorDepth = $from.sharedDepth($to.pos);
280
+ for (var depth = commonAncestorDepth; depth > 0; depth--) {
281
+ var node = $from.node(depth);
282
+ var isParentBlockQuote = node.type.name === 'blockquote';
283
+ var isSelectable = _state.NodeSelection.isSelectable(node) && !isParentBlockQuote;
284
+ if (isSelectable) {
285
+ if (dispatch) {
286
+ dispatch(state.tr.setSelection(_state.NodeSelection.create(state.doc, $from.before(depth))));
287
+ }
288
+ return true;
289
+ }
290
+ }
291
+ return false;
292
+ };
260
293
  };
@@ -87,8 +87,6 @@ var toDOM = exports.toDOM = function toDOM(view, getPos) {
87
87
  var gapCursor = element.firstChild;
88
88
  gapCursor.style.height = "".concat(measureHeight(style), "px");
89
89
  var layoutMode = node && (0, _utils.getLayoutModeFromTargetNode)(node);
90
-
91
- // TODO remove this table specific piece. need to figure out margin collapsing logic
92
90
  if (nodeStart !== 0 || layoutMode || (node === null || node === void 0 ? void 0 : node.type.name) === 'table') {
93
91
  gapCursor.style.marginTop = style.getPropertyValue('margin-top');
94
92
  }
@@ -6,6 +6,7 @@ Object.defineProperty(exports, "__esModule", {
6
6
  exports.default = void 0;
7
7
  var _keymaps = require("@atlaskit/editor-common/keymaps");
8
8
  var _keymap = require("@atlaskit/editor-prosemirror/keymap");
9
+ var _experiments = require("@atlaskit/tmp-editor-statsig/experiments");
9
10
  var _commands = require("./commands");
10
11
  function keymapPlugin() {
11
12
  var list = {};
@@ -17,6 +18,11 @@ function keymapPlugin() {
17
18
  // Ignored via go/ees005
18
19
  // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
19
20
  (0, _keymaps.bindKeymapWithCommand)(_keymaps.moveLeft.common, _commands.arrowLeft, list);
21
+ if ((0, _experiments.editorExperiment)('platform_editor_cmd_a_progressively_select_nodes', true)) {
22
+ // Ignored via go/ees005
23
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
24
+ (0, _keymaps.bindKeymapWithCommand)(_keymaps.selectNode.common, (0, _commands.selectNodeWithModA)(), list);
25
+ }
20
26
  return (0, _keymap.keymap)(list);
21
27
  }
22
28
  var _default = exports.default = keymapPlugin;
@@ -0,0 +1,74 @@
1
+ "use strict";
2
+
3
+ var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
4
+ Object.defineProperty(exports, "__esModule", {
5
+ value: true
6
+ });
7
+ exports.selectionAnalyticsPluginKey = exports.createSelectionAnalyticsPlugin = void 0;
8
+ var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty"));
9
+ var _analytics = require("@atlaskit/editor-common/analytics");
10
+ var _safePlugin = require("@atlaskit/editor-common/safe-plugin");
11
+ var _state = require("@atlaskit/editor-prosemirror/state");
12
+ 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; }
13
+ 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; }
14
+ var selectionAnalyticsPluginKey = exports.selectionAnalyticsPluginKey = new _state.PluginKey('selectionAnalyticsPluginKey');
15
+ var createSelectionAnalyticsPlugin = exports.createSelectionAnalyticsPlugin = function createSelectionAnalyticsPlugin(dispatchAnalyticsEvent) {
16
+ var keyActions = new Map([['c', 'copy'], ['x', 'cut'], ['z', 'undo'], ['Escape', 'escape'], ['Backspace', 'delete']]);
17
+ var isFollowUpKey = function isFollowUpKey(event) {
18
+ return ['c', 'x', 'z'].includes(event.key) && (event.metaKey || event.ctrlKey) || ['Escape', 'Backspace'].includes(event.key);
19
+ };
20
+ var dispatchEvent = function dispatchEvent(fromDepth, followedBy) {
21
+ dispatchAnalyticsEvent({
22
+ action: _analytics.ACTION.SELECT_ALL,
23
+ actionSubject: _analytics.ACTION_SUBJECT.DOCUMENT,
24
+ actionSubjectId: _analytics.ACTION_SUBJECT_ID.ALL,
25
+ eventType: _analytics.EVENT_TYPE.TRACK,
26
+ attributes: {
27
+ followedBy: followedBy,
28
+ fromDepth: fromDepth
29
+ }
30
+ });
31
+ };
32
+ return new _safePlugin.SafePlugin({
33
+ key: selectionAnalyticsPluginKey,
34
+ state: {
35
+ init: function init() {
36
+ return {
37
+ lastCmdAPress: 0,
38
+ trackingCmdA: false
39
+ };
40
+ },
41
+ apply: function apply(tr, state) {
42
+ var meta = tr.getMeta(selectionAnalyticsPluginKey);
43
+ return meta ? _objectSpread(_objectSpread({}, state), meta) : state;
44
+ }
45
+ },
46
+ props: {
47
+ handleDOMEvents: {
48
+ keydown: function keydown(view, event) {
49
+ var _selectionAnalyticsPl = selectionAnalyticsPluginKey.getState(view.state),
50
+ lastCmdAPress = _selectionAnalyticsPl.lastCmdAPress,
51
+ trackingCmdA = _selectionAnalyticsPl.trackingCmdA;
52
+ var tr = view.state.tr;
53
+ var depth = view.state.selection.$from.depth;
54
+ var metaKey = event.metaKey || event.ctrlKey;
55
+ if (event.key === 'a' && metaKey) {
56
+ tr.setMeta(selectionAnalyticsPluginKey, {
57
+ lastCmdAPress: Date.now(),
58
+ trackingCmdA: true
59
+ });
60
+ dispatchEvent(depth);
61
+ view.dispatch(tr);
62
+ } else if (trackingCmdA && Date.now() - lastCmdAPress < 5000 && isFollowUpKey(event)) {
63
+ tr.setMeta(selectionAnalyticsPluginKey, {
64
+ trackingCmdA: false
65
+ });
66
+ dispatchEvent(depth, keyActions.get(event.key));
67
+ view.dispatch(tr);
68
+ }
69
+ return false;
70
+ }
71
+ }
72
+ }
73
+ });
74
+ };
@@ -15,6 +15,7 @@ var _gapCursorMain = _interopRequireDefault(require("./pm-plugins/gap-cursor-mai
15
15
  var _gapCursorPluginKey = require("./pm-plugins/gap-cursor-plugin-key");
16
16
  var _keymap = _interopRequireDefault(require("./pm-plugins/keymap"));
17
17
  var _markBoundaryCursorMain = require("./pm-plugins/mark-boundary-cursor-main");
18
+ var _selectionAnalytics = require("./pm-plugins/selection-analytics");
18
19
  var _selectionMain = require("./pm-plugins/selection-main");
19
20
  var _types = require("./types");
20
21
  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; }
@@ -109,7 +110,14 @@ var selectionPlugin = exports.selectionPlugin = function selectionPlugin(_ref4)
109
110
  plugin: function plugin() {
110
111
  return (0, _autoExpandSelectionRangeOnInlineNodeMain.createAutoExpandSelectionRangeOnInlineNodePlugin)();
111
112
  }
112
- }] : []));
113
+ }] : []), [{
114
+ name: 'selectionAnalytics',
115
+ plugin: function plugin(_ref7) {
116
+ var dispatch = _ref7.dispatch,
117
+ dispatchAnalyticsEvent = _ref7.dispatchAnalyticsEvent;
118
+ return (0, _selectionAnalytics.createSelectionAnalyticsPlugin)(dispatchAnalyticsEvent);
119
+ }
120
+ }]);
113
121
  }
114
122
  };
115
123
  };
@@ -2,6 +2,7 @@
2
2
  import { isIgnored as isIgnoredByGapCursor, RelativeSelectionPos, GapCursorSelection, Side } from '@atlaskit/editor-common/selection';
3
3
  import { isEmptyParagraph, isNodeEmpty } from '@atlaskit/editor-common/utils';
4
4
  import { NodeSelection, Selection, TextSelection } from '@atlaskit/editor-prosemirror/state';
5
+ import { isTableSelected } from '@atlaskit/editor-tables/utils';
5
6
  import { SelectionDirection, selectionPluginKey } from '../types';
6
7
  import { SelectionActionTypes } from './actions';
7
8
  import { createCommand, getPluginState } from './plugin-factory';
@@ -249,4 +250,38 @@ export const setSelectionInsideAtNodeEnd = (selectionRelativeToNode, node, from,
249
250
  }
250
251
  }
251
252
  return false;
253
+ };
254
+ export const selectNodeWithModA = () => (state, dispatch) => {
255
+ const {
256
+ selection
257
+ } = state;
258
+ const {
259
+ $from,
260
+ $to
261
+ } = selection;
262
+ // Check if the selection is at the top level (e.g., in a paragraph)
263
+ const isTopLevelSelection = $from.depth === 1 || $to.depth === 1;
264
+
265
+ // Determine if the selection is within a code block
266
+ const isInCodeBlock = $from.sameParent($to) && $from.parent.type === state.schema.nodes.codeBlock;
267
+
268
+ // If the selection is at the top level and not in a code block, or if a table is selected, do nothing
269
+ if (isTopLevelSelection && !isInCodeBlock || isTableSelected(selection)) {
270
+ return false;
271
+ }
272
+
273
+ // Get the depth of the first common ancestor node
274
+ const commonAncestorDepth = $from.sharedDepth($to.pos);
275
+ for (let depth = commonAncestorDepth; depth > 0; depth--) {
276
+ const node = $from.node(depth);
277
+ const isParentBlockQuote = node.type.name === 'blockquote';
278
+ const isSelectable = NodeSelection.isSelectable(node) && !isParentBlockQuote;
279
+ if (isSelectable) {
280
+ if (dispatch) {
281
+ dispatch(state.tr.setSelection(NodeSelection.create(state.doc, $from.before(depth))));
282
+ }
283
+ return true;
284
+ }
285
+ }
286
+ return false;
252
287
  };
@@ -78,8 +78,6 @@ export const toDOM = (view, getPos) => {
78
78
  const gapCursor = element.firstChild;
79
79
  gapCursor.style.height = `${measureHeight(style)}px`;
80
80
  const layoutMode = node && getLayoutModeFromTargetNode(node);
81
-
82
- // TODO remove this table specific piece. need to figure out margin collapsing logic
83
81
  if (nodeStart !== 0 || layoutMode || (node === null || node === void 0 ? void 0 : node.type.name) === 'table') {
84
82
  gapCursor.style.marginTop = style.getPropertyValue('margin-top');
85
83
  }
@@ -1,6 +1,7 @@
1
- import { bindKeymapWithCommand, moveLeft, moveRight } from '@atlaskit/editor-common/keymaps';
1
+ import { bindKeymapWithCommand, moveLeft, moveRight, selectNode } from '@atlaskit/editor-common/keymaps';
2
2
  import { keymap } from '@atlaskit/editor-prosemirror/keymap';
3
- import { arrowLeft, arrowRight } from './commands';
3
+ import { editorExperiment } from '@atlaskit/tmp-editor-statsig/experiments';
4
+ import { arrowLeft, arrowRight, selectNodeWithModA } from './commands';
4
5
  function keymapPlugin() {
5
6
  const list = {};
6
7
 
@@ -11,6 +12,11 @@ function keymapPlugin() {
11
12
  // Ignored via go/ees005
12
13
  // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
13
14
  bindKeymapWithCommand(moveLeft.common, arrowLeft, list);
15
+ if (editorExperiment('platform_editor_cmd_a_progressively_select_nodes', true)) {
16
+ // Ignored via go/ees005
17
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
18
+ bindKeymapWithCommand(selectNode.common, selectNodeWithModA(), list);
19
+ }
14
20
  return keymap(list);
15
21
  }
16
22
  export default keymapPlugin;
@@ -0,0 +1,64 @@
1
+ import { ACTION, ACTION_SUBJECT, ACTION_SUBJECT_ID, EVENT_TYPE } from '@atlaskit/editor-common/analytics';
2
+ import { SafePlugin } from '@atlaskit/editor-common/safe-plugin';
3
+ import { PluginKey } from '@atlaskit/editor-prosemirror/state';
4
+ export const selectionAnalyticsPluginKey = new PluginKey('selectionAnalyticsPluginKey');
5
+ export const createSelectionAnalyticsPlugin = dispatchAnalyticsEvent => {
6
+ const keyActions = new Map([['c', 'copy'], ['x', 'cut'], ['z', 'undo'], ['Escape', 'escape'], ['Backspace', 'delete']]);
7
+ const isFollowUpKey = event => ['c', 'x', 'z'].includes(event.key) && (event.metaKey || event.ctrlKey) || ['Escape', 'Backspace'].includes(event.key);
8
+ const dispatchEvent = (fromDepth, followedBy) => {
9
+ dispatchAnalyticsEvent({
10
+ action: ACTION.SELECT_ALL,
11
+ actionSubject: ACTION_SUBJECT.DOCUMENT,
12
+ actionSubjectId: ACTION_SUBJECT_ID.ALL,
13
+ eventType: EVENT_TYPE.TRACK,
14
+ attributes: {
15
+ followedBy,
16
+ fromDepth
17
+ }
18
+ });
19
+ };
20
+ return new SafePlugin({
21
+ key: selectionAnalyticsPluginKey,
22
+ state: {
23
+ init: () => ({
24
+ lastCmdAPress: 0,
25
+ trackingCmdA: false
26
+ }),
27
+ apply(tr, state) {
28
+ const meta = tr.getMeta(selectionAnalyticsPluginKey);
29
+ return meta ? {
30
+ ...state,
31
+ ...meta
32
+ } : state;
33
+ }
34
+ },
35
+ props: {
36
+ handleDOMEvents: {
37
+ keydown(view, event) {
38
+ const {
39
+ lastCmdAPress,
40
+ trackingCmdA
41
+ } = selectionAnalyticsPluginKey.getState(view.state);
42
+ const tr = view.state.tr;
43
+ const depth = view.state.selection.$from.depth;
44
+ const metaKey = event.metaKey || event.ctrlKey;
45
+ if (event.key === 'a' && metaKey) {
46
+ tr.setMeta(selectionAnalyticsPluginKey, {
47
+ lastCmdAPress: Date.now(),
48
+ trackingCmdA: true
49
+ });
50
+ dispatchEvent(depth);
51
+ view.dispatch(tr);
52
+ } else if (trackingCmdA && Date.now() - lastCmdAPress < 5000 && isFollowUpKey(event)) {
53
+ tr.setMeta(selectionAnalyticsPluginKey, {
54
+ trackingCmdA: false
55
+ });
56
+ dispatchEvent(depth, keyActions.get(event.key));
57
+ view.dispatch(tr);
58
+ }
59
+ return false;
60
+ }
61
+ }
62
+ }
63
+ });
64
+ };
@@ -6,6 +6,7 @@ import gapCursorPlugin from './pm-plugins/gap-cursor-main';
6
6
  import { gapCursorPluginKey } from './pm-plugins/gap-cursor-plugin-key';
7
7
  import selectionKeymapPlugin from './pm-plugins/keymap';
8
8
  import { createMarkBoundaryCursorPlugin } from './pm-plugins/mark-boundary-cursor-main';
9
+ import { createSelectionAnalyticsPlugin } from './pm-plugins/selection-analytics';
9
10
  import { createPlugin } from './pm-plugins/selection-main';
10
11
  import { selectionPluginKey } from './types';
11
12
  const displayGapCursor = toggle => ({
@@ -87,7 +88,13 @@ export const selectionPlugin = ({
87
88
  }, ...(fg('editor_auto_expand_selection_on_inline_node') ? [{
88
89
  name: 'autoExpandSelectionRangeOnInlineNode',
89
90
  plugin: () => createAutoExpandSelectionRangeOnInlineNodePlugin()
90
- }] : [])];
91
+ }] : []), {
92
+ name: 'selectionAnalytics',
93
+ plugin: ({
94
+ dispatch,
95
+ dispatchAnalyticsEvent
96
+ }) => createSelectionAnalyticsPlugin(dispatchAnalyticsEvent)
97
+ }];
91
98
  }
92
99
  });
93
100
  export default selectionPlugin;
@@ -2,6 +2,7 @@
2
2
  import { isIgnored as isIgnoredByGapCursor, RelativeSelectionPos, GapCursorSelection, Side } from '@atlaskit/editor-common/selection';
3
3
  import { isEmptyParagraph, isNodeEmpty } from '@atlaskit/editor-common/utils';
4
4
  import { NodeSelection, Selection, TextSelection } from '@atlaskit/editor-prosemirror/state';
5
+ import { isTableSelected } from '@atlaskit/editor-tables/utils';
5
6
  import { SelectionDirection, selectionPluginKey } from '../types';
6
7
  import { SelectionActionTypes } from './actions';
7
8
  import { createCommand, getPluginState } from './plugin-factory';
@@ -250,4 +251,36 @@ export var setSelectionInsideAtNodeEnd = function setSelectionInsideAtNodeEnd(se
250
251
  }
251
252
  return false;
252
253
  };
254
+ };
255
+ export var selectNodeWithModA = function selectNodeWithModA() {
256
+ return function (state, dispatch) {
257
+ var selection = state.selection;
258
+ var $from = selection.$from,
259
+ $to = selection.$to;
260
+ // Check if the selection is at the top level (e.g., in a paragraph)
261
+ var isTopLevelSelection = $from.depth === 1 || $to.depth === 1;
262
+
263
+ // Determine if the selection is within a code block
264
+ var isInCodeBlock = $from.sameParent($to) && $from.parent.type === state.schema.nodes.codeBlock;
265
+
266
+ // If the selection is at the top level and not in a code block, or if a table is selected, do nothing
267
+ if (isTopLevelSelection && !isInCodeBlock || isTableSelected(selection)) {
268
+ return false;
269
+ }
270
+
271
+ // Get the depth of the first common ancestor node
272
+ var commonAncestorDepth = $from.sharedDepth($to.pos);
273
+ for (var depth = commonAncestorDepth; depth > 0; depth--) {
274
+ var node = $from.node(depth);
275
+ var isParentBlockQuote = node.type.name === 'blockquote';
276
+ var isSelectable = NodeSelection.isSelectable(node) && !isParentBlockQuote;
277
+ if (isSelectable) {
278
+ if (dispatch) {
279
+ dispatch(state.tr.setSelection(NodeSelection.create(state.doc, $from.before(depth))));
280
+ }
281
+ return true;
282
+ }
283
+ }
284
+ return false;
285
+ };
253
286
  };
@@ -81,8 +81,6 @@ export var toDOM = function toDOM(view, getPos) {
81
81
  var gapCursor = element.firstChild;
82
82
  gapCursor.style.height = "".concat(measureHeight(style), "px");
83
83
  var layoutMode = node && getLayoutModeFromTargetNode(node);
84
-
85
- // TODO remove this table specific piece. need to figure out margin collapsing logic
86
84
  if (nodeStart !== 0 || layoutMode || (node === null || node === void 0 ? void 0 : node.type.name) === 'table') {
87
85
  gapCursor.style.marginTop = style.getPropertyValue('margin-top');
88
86
  }
@@ -1,6 +1,7 @@
1
- import { bindKeymapWithCommand, moveLeft, moveRight } from '@atlaskit/editor-common/keymaps';
1
+ import { bindKeymapWithCommand, moveLeft, moveRight, selectNode } from '@atlaskit/editor-common/keymaps';
2
2
  import { keymap } from '@atlaskit/editor-prosemirror/keymap';
3
- import { arrowLeft, arrowRight } from './commands';
3
+ import { editorExperiment } from '@atlaskit/tmp-editor-statsig/experiments';
4
+ import { arrowLeft, arrowRight, selectNodeWithModA } from './commands';
4
5
  function keymapPlugin() {
5
6
  var list = {};
6
7
 
@@ -11,6 +12,11 @@ function keymapPlugin() {
11
12
  // Ignored via go/ees005
12
13
  // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
13
14
  bindKeymapWithCommand(moveLeft.common, arrowLeft, list);
15
+ if (editorExperiment('platform_editor_cmd_a_progressively_select_nodes', true)) {
16
+ // Ignored via go/ees005
17
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
18
+ bindKeymapWithCommand(selectNode.common, selectNodeWithModA(), list);
19
+ }
14
20
  return keymap(list);
15
21
  }
16
22
  export default keymapPlugin;
@@ -0,0 +1,67 @@
1
+ import _defineProperty from "@babel/runtime/helpers/defineProperty";
2
+ function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
3
+ function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
4
+ import { ACTION, ACTION_SUBJECT, ACTION_SUBJECT_ID, EVENT_TYPE } from '@atlaskit/editor-common/analytics';
5
+ import { SafePlugin } from '@atlaskit/editor-common/safe-plugin';
6
+ import { PluginKey } from '@atlaskit/editor-prosemirror/state';
7
+ export var selectionAnalyticsPluginKey = new PluginKey('selectionAnalyticsPluginKey');
8
+ export var createSelectionAnalyticsPlugin = function createSelectionAnalyticsPlugin(dispatchAnalyticsEvent) {
9
+ var keyActions = new Map([['c', 'copy'], ['x', 'cut'], ['z', 'undo'], ['Escape', 'escape'], ['Backspace', 'delete']]);
10
+ var isFollowUpKey = function isFollowUpKey(event) {
11
+ return ['c', 'x', 'z'].includes(event.key) && (event.metaKey || event.ctrlKey) || ['Escape', 'Backspace'].includes(event.key);
12
+ };
13
+ var dispatchEvent = function dispatchEvent(fromDepth, followedBy) {
14
+ dispatchAnalyticsEvent({
15
+ action: ACTION.SELECT_ALL,
16
+ actionSubject: ACTION_SUBJECT.DOCUMENT,
17
+ actionSubjectId: ACTION_SUBJECT_ID.ALL,
18
+ eventType: EVENT_TYPE.TRACK,
19
+ attributes: {
20
+ followedBy: followedBy,
21
+ fromDepth: fromDepth
22
+ }
23
+ });
24
+ };
25
+ return new SafePlugin({
26
+ key: selectionAnalyticsPluginKey,
27
+ state: {
28
+ init: function init() {
29
+ return {
30
+ lastCmdAPress: 0,
31
+ trackingCmdA: false
32
+ };
33
+ },
34
+ apply: function apply(tr, state) {
35
+ var meta = tr.getMeta(selectionAnalyticsPluginKey);
36
+ return meta ? _objectSpread(_objectSpread({}, state), meta) : state;
37
+ }
38
+ },
39
+ props: {
40
+ handleDOMEvents: {
41
+ keydown: function keydown(view, event) {
42
+ var _selectionAnalyticsPl = selectionAnalyticsPluginKey.getState(view.state),
43
+ lastCmdAPress = _selectionAnalyticsPl.lastCmdAPress,
44
+ trackingCmdA = _selectionAnalyticsPl.trackingCmdA;
45
+ var tr = view.state.tr;
46
+ var depth = view.state.selection.$from.depth;
47
+ var metaKey = event.metaKey || event.ctrlKey;
48
+ if (event.key === 'a' && metaKey) {
49
+ tr.setMeta(selectionAnalyticsPluginKey, {
50
+ lastCmdAPress: Date.now(),
51
+ trackingCmdA: true
52
+ });
53
+ dispatchEvent(depth);
54
+ view.dispatch(tr);
55
+ } else if (trackingCmdA && Date.now() - lastCmdAPress < 5000 && isFollowUpKey(event)) {
56
+ tr.setMeta(selectionAnalyticsPluginKey, {
57
+ trackingCmdA: false
58
+ });
59
+ dispatchEvent(depth, keyActions.get(event.key));
60
+ view.dispatch(tr);
61
+ }
62
+ return false;
63
+ }
64
+ }
65
+ }
66
+ });
67
+ };
@@ -10,6 +10,7 @@ import gapCursorPlugin from './pm-plugins/gap-cursor-main';
10
10
  import { gapCursorPluginKey } from './pm-plugins/gap-cursor-plugin-key';
11
11
  import selectionKeymapPlugin from './pm-plugins/keymap';
12
12
  import { createMarkBoundaryCursorPlugin } from './pm-plugins/mark-boundary-cursor-main';
13
+ import { createSelectionAnalyticsPlugin } from './pm-plugins/selection-analytics';
13
14
  import { createPlugin } from './pm-plugins/selection-main';
14
15
  import { selectionPluginKey } from './types';
15
16
  var displayGapCursor = function displayGapCursor(toggle) {
@@ -102,7 +103,14 @@ export var selectionPlugin = function selectionPlugin(_ref4) {
102
103
  plugin: function plugin() {
103
104
  return createAutoExpandSelectionRangeOnInlineNodePlugin();
104
105
  }
105
- }] : []));
106
+ }] : []), [{
107
+ name: 'selectionAnalytics',
108
+ plugin: function plugin(_ref7) {
109
+ var dispatch = _ref7.dispatch,
110
+ dispatchAnalyticsEvent = _ref7.dispatchAnalyticsEvent;
111
+ return createSelectionAnalyticsPlugin(dispatchAnalyticsEvent);
112
+ }
113
+ }]);
106
114
  }
107
115
  };
108
116
  };
@@ -7,3 +7,4 @@ export declare const setSelectionRelativeToNode: (selectionRelativeToNode?: Rela
7
7
  export declare const arrowRight: Command;
8
8
  export declare const arrowLeft: Command;
9
9
  export declare const setSelectionInsideAtNodeEnd: (selectionRelativeToNode: RelativeSelectionPos, node: PmNode, from: number, to: number) => Command;
10
+ export declare const selectNodeWithModA: () => Command;
@@ -0,0 +1,10 @@
1
+ import { type DispatchAnalyticsEvent } from '@atlaskit/editor-common/analytics';
2
+ import { SafePlugin } from '@atlaskit/editor-common/safe-plugin';
3
+ import { PluginKey } from '@atlaskit/editor-prosemirror/state';
4
+ export declare const selectionAnalyticsPluginKey: PluginKey<any>;
5
+ interface SelectionAnalyticsState {
6
+ lastCmdAPress: number;
7
+ trackingCmdA: boolean;
8
+ }
9
+ export declare const createSelectionAnalyticsPlugin: (dispatchAnalyticsEvent: DispatchAnalyticsEvent) => SafePlugin<SelectionAnalyticsState>;
10
+ export {};
@@ -7,3 +7,4 @@ export declare const setSelectionRelativeToNode: (selectionRelativeToNode?: Rela
7
7
  export declare const arrowRight: Command;
8
8
  export declare const arrowLeft: Command;
9
9
  export declare const setSelectionInsideAtNodeEnd: (selectionRelativeToNode: RelativeSelectionPos, node: PmNode, from: number, to: number) => Command;
10
+ export declare const selectNodeWithModA: () => Command;
@@ -0,0 +1,10 @@
1
+ import { type DispatchAnalyticsEvent } from '@atlaskit/editor-common/analytics';
2
+ import { SafePlugin } from '@atlaskit/editor-common/safe-plugin';
3
+ import { PluginKey } from '@atlaskit/editor-prosemirror/state';
4
+ export declare const selectionAnalyticsPluginKey: PluginKey<any>;
5
+ interface SelectionAnalyticsState {
6
+ lastCmdAPress: number;
7
+ trackingCmdA: boolean;
8
+ }
9
+ export declare const createSelectionAnalyticsPlugin: (dispatchAnalyticsEvent: DispatchAnalyticsEvent) => SafePlugin<SelectionAnalyticsState>;
10
+ export {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@atlaskit/editor-plugin-selection",
3
- "version": "2.0.7",
3
+ "version": "2.1.0",
4
4
  "description": "Selection plugin for @atlaskit/editor-core",
5
5
  "publishConfig": {
6
6
  "registry": "https://registry.npmjs.org/"
@@ -20,12 +20,12 @@
20
20
  "runReact18": true
21
21
  },
22
22
  "dependencies": {
23
- "@atlaskit/editor-common": "^102.0.0",
23
+ "@atlaskit/editor-common": "^102.3.0",
24
24
  "@atlaskit/editor-prosemirror": "7.0.0",
25
25
  "@atlaskit/editor-shared-styles": "^3.4.0",
26
26
  "@atlaskit/editor-tables": "^2.9.0",
27
27
  "@atlaskit/platform-feature-flags": "^1.1.0",
28
- "@atlaskit/tmp-editor-statsig": "^3.4.0",
28
+ "@atlaskit/tmp-editor-statsig": "^3.5.0",
29
29
  "@atlaskit/tokens": "^4.3.0",
30
30
  "@babel/runtime": "^7.0.0"
31
31
  },