@atlaskit/editor-plugin-block-controls 8.0.14 → 8.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,26 @@
1
1
  # @atlaskit/editor-plugin-block-controls
2
2
 
3
+ ## 8.1.0
4
+
5
+ ### Minor Changes
6
+
7
+ - [`1d31a4729ab09`](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/commits/1d31a4729ab09) -
8
+ [ux] Implement nested block menu icon (flagged)
9
+
10
+ ### Patch Changes
11
+
12
+ - [`e00b363b9fa30`](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/commits/e00b363b9fa30) -
13
+ [ux] EDITOR-4481 Clean up platform_editor_toolbar_aifc_user_intent_fix
14
+ - Updated dependencies
15
+
16
+ ## 8.0.15
17
+
18
+ ### Patch Changes
19
+
20
+ - [`131735fb6ad06`](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/commits/131735fb6ad06) -
21
+ [ux] EDITOR-4281 Expand preserved selection edge case
22
+ - Updated dependencies
23
+
3
24
  ## 8.0.14
4
25
 
5
26
  ### Patch Changes
@@ -140,7 +140,7 @@ var blockControlsPlugin = exports.blockControlsPlugin = function blockControlsPl
140
140
  }));
141
141
  if ((menuTriggerBy === undefined || !!menuTriggerBy && menuTriggerBy === (options === null || options === void 0 ? void 0 : options.anchorName)) && currentUserIntent === 'blockMenuOpen') {
142
142
  var state = api === null || api === void 0 ? void 0 : api.blockControls.sharedState.currentState();
143
- if (state !== null && state !== void 0 && state.isSelectedViaDragHandle && (0, _platformFeatureFlags.fg)('platform_editor_toolbar_aifc_user_intent_fix')) {
143
+ if (state !== null && state !== void 0 && state.isSelectedViaDragHandle) {
144
144
  var _api$userIntent4;
145
145
  api === null || api === void 0 || (_api$userIntent4 = api.userIntent) === null || _api$userIntent4 === void 0 || _api$userIntent4.commands.setCurrentUserIntent('dragHandleSelected')({
146
146
  tr: tr
@@ -92,7 +92,7 @@ var dragHandleDecoration = exports.dragHandleDecoration = function dragHandleDec
92
92
  var getPos = function getPos() {
93
93
  try {
94
94
  return getPosUnsafe();
95
- } catch (e) {
95
+ } catch (_unused) {
96
96
  // Ignore errors from getPosUnsafe
97
97
  return undefined;
98
98
  }
@@ -5,7 +5,6 @@ Object.defineProperty(exports, "__esModule", {
5
5
  });
6
6
  exports.handleMouseDown = void 0;
7
7
  var _styles = require("@atlaskit/editor-common/styles");
8
- var _platformFeatureFlags = require("@atlaskit/platform-feature-flags");
9
8
  var _expValEquals = require("@atlaskit/tmp-editor-statsig/exp-val-equals");
10
9
  var _expValEqualsNoExposure = require("@atlaskit/tmp-editor-statsig/exp-val-equals-no-exposure");
11
10
  var handleMouseDown = exports.handleMouseDown = function handleMouseDown(api) {
@@ -45,21 +44,19 @@ var handleMouseDown = exports.handleMouseDown = function handleMouseDown(api) {
45
44
  * When block menu is enabled, reset intent back to 'default' as editor-plugin-block-menu sets the user intent to 'blockMenuOpen', and setting here
46
45
  * causes flickering as this runs before editor-plugin-block-menu.
47
46
  */
48
- if ((0, _platformFeatureFlags.fg)('platform_editor_toolbar_aifc_user_intent_fix')) {
49
- if ((0, _expValEqualsNoExposure.expValEqualsNoExposure)('platform_editor_block_menu', 'isEnabled', true)) {
50
- // if target is drag handle, block menu will be opened
51
- if (!isDragHandle) {
52
- var _api$userIntent;
53
- api === null || api === void 0 || (_api$userIntent = api.userIntent) === null || _api$userIntent === void 0 || _api$userIntent.commands.setCurrentUserIntent('default')({
54
- tr: tr
55
- });
56
- }
57
- } else {
58
- var _api$userIntent2;
59
- (_api$userIntent2 = api.userIntent) === null || _api$userIntent2 === void 0 || _api$userIntent2.commands.setCurrentUserIntent(isDragHandle ? 'dragHandleSelected' : 'default')({
47
+ if ((0, _expValEqualsNoExposure.expValEqualsNoExposure)('platform_editor_block_menu', 'isEnabled', true)) {
48
+ // if target is drag handle, block menu will be opened
49
+ if (!isDragHandle) {
50
+ var _api$userIntent;
51
+ api === null || api === void 0 || (_api$userIntent = api.userIntent) === null || _api$userIntent === void 0 || _api$userIntent.commands.setCurrentUserIntent('default')({
60
52
  tr: tr
61
53
  });
62
54
  }
55
+ } else {
56
+ var _api$userIntent2;
57
+ (_api$userIntent2 = api.userIntent) === null || _api$userIntent2 === void 0 || _api$userIntent2.commands.setCurrentUserIntent(isDragHandle ? 'dragHandleSelected' : 'default')({
58
+ tr: tr
59
+ });
63
60
  }
64
61
  return tr;
65
62
  });
@@ -3,7 +3,7 @@
3
3
  Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
- exports.mapPreservedSelection = exports.getSelectedSlicePosition = exports.getMultiSelectionIfPosInside = exports.expandSelectionHeadToNodeAtPos = exports.createPreservedSelection = exports.alignAnchorHeadInDirectionOfPos = void 0;
6
+ exports.mapPreservedSelection = exports.getSelectedSlicePosition = exports.getMultiSelectionIfPosInside = exports.expandSelectionHeadToNodeAtPos = exports.createPreservedSelection = exports.alignAnchorHeadInDirectionOfPos = exports.adjustSelectionBoundsForEdgePositions = void 0;
7
7
  var _selection = require("@atlaskit/editor-common/selection");
8
8
  var _state = require("@atlaskit/editor-prosemirror/state");
9
9
  var _utils = require("@atlaskit/editor-tables/utils");
@@ -103,7 +103,58 @@ var mapPreservedSelection = exports.mapPreservedSelection = function mapPreserve
103
103
  }
104
104
  return createPreservedSelection(tr.doc.resolve(from), tr.doc.resolve(to));
105
105
  };
106
+ var isInsideEmptyTextblock = function isInsideEmptyTextblock($pos) {
107
+ return $pos.parent.isTextblock && $pos.parent.content.size === 0;
108
+ };
109
+ var isAtEndOfParent = function isAtEndOfParent($pos) {
110
+ return $pos.parentOffset === $pos.parent.content.size;
111
+ };
112
+ var isAtStartOfParent = function isAtStartOfParent($pos) {
113
+ return $pos.parentOffset === 0;
114
+ };
106
115
 
116
+ /**
117
+ * Adjust selection bounds to exclude nodes where the selection only touches
118
+ * the edge position without selecting any content.
119
+ *
120
+ * Exception: Don't adjust if the selection is inside an empty textblock,
121
+ * as we want to include empty paragraphs in block operations.
122
+ *
123
+ * @param $from The resolved position of the start of the selection
124
+ * @param $to The resolved position of the end of the selection
125
+ * @returns Adjusted $from and $to positions
126
+ */
127
+ var adjustSelectionBoundsForEdgePositions = exports.adjustSelectionBoundsForEdgePositions = function adjustSelectionBoundsForEdgePositions($from, $to) {
128
+ var doc = $from.doc;
129
+
130
+ // Walk $from forward while at end of ancestors
131
+ var adjustedFrom = $from;
132
+ if (!isInsideEmptyTextblock($from)) {
133
+ while (adjustedFrom.depth > 0 && isAtEndOfParent(adjustedFrom)) {
134
+ var nextPos = adjustedFrom.after();
135
+ if (nextPos > doc.content.size || nextPos === adjustedFrom.pos) {
136
+ break;
137
+ }
138
+ adjustedFrom = doc.resolve(nextPos);
139
+ }
140
+ }
141
+
142
+ // Walk $to backward while at start of ancestors
143
+ var adjustedTo = $to;
144
+ if (!isInsideEmptyTextblock($to)) {
145
+ while (adjustedTo.depth > 0 && isAtStartOfParent(adjustedTo)) {
146
+ var prevPos = adjustedTo.before();
147
+ if (prevPos < 0 || prevPos === adjustedTo.pos) {
148
+ break;
149
+ }
150
+ adjustedTo = doc.resolve(prevPos);
151
+ }
152
+ }
153
+ return {
154
+ $from: adjustedFrom,
155
+ $to: adjustedTo
156
+ };
157
+ };
107
158
  /**
108
159
  * Creates a preserved selection which is expanded to block boundaries.
109
160
  *
@@ -119,9 +170,17 @@ var mapPreservedSelection = exports.mapPreservedSelection = function mapPreserve
119
170
  var createPreservedSelection = exports.createPreservedSelection = function createPreservedSelection($from, $to) {
120
171
  var _doc$nodeAt;
121
172
  var doc = $from.doc;
173
+ var isCollapsed = $from.pos === $to.pos;
174
+ var adjusted = isCollapsed ? {
175
+ $from: $from,
176
+ $to: $to
177
+ } : adjustSelectionBoundsForEdgePositions($from, $to);
178
+ if (!isCollapsed && adjusted.$from.pos >= adjusted.$to.pos) {
179
+ return undefined;
180
+ }
122
181
 
123
182
  // expand the selection range to block boundaries, so selection always includes whole nodes
124
- var expanded = (0, _selection.expandToBlockRange)($from, $to);
183
+ var expanded = (0, _selection.expandToBlockRange)(adjusted.$from, adjusted.$to);
125
184
 
126
185
  // stop preserving if selection becomes invalid
127
186
  if (expanded.$from.pos < 0 || expanded.$to.pos > doc.content.size || expanded.$from.pos >= expanded.$to.pos) {
@@ -0,0 +1,64 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.DragHandleNestedIcon = void 0;
7
+ var _react = require("@emotion/react");
8
+ /** @jsxRuntime classic */
9
+ /**
10
+ * @jsxRuntime classic
11
+ * @jsx jsx
12
+ */
13
+ // eslint-disable-next-line @atlaskit/ui-styling-standard/use-compiled -- Ignored via go/DSP-18766
14
+
15
+ var spanStyles = (0, _react.css)({
16
+ display: 'inline-block',
17
+ boxSizing: 'border-box',
18
+ flexShrink: 0
19
+ });
20
+ var svgSizeStyles = (0, _react.css)({
21
+ width: '24px',
22
+ height: '24px'
23
+ });
24
+ var svgStyles = (0, _react.css)({
25
+ color: 'currentColor',
26
+ overflow: 'hidden',
27
+ pointerEvents: 'none',
28
+ verticalAlign: 'bottom'
29
+ });
30
+
31
+ /**
32
+ * Custom 3-dot vertical drag handle icon for nested nodes.
33
+ * Similar to DragHandleVerticalIcon but with only 3 dots instead of 6.
34
+ * Hardcoded to medium size with spacious spacing.
35
+ */
36
+ var DragHandleNestedIcon = exports.DragHandleNestedIcon = function DragHandleNestedIcon() {
37
+ return (0, _react.jsx)("span", {
38
+ "aria-hidden": true,
39
+ css: spanStyles
40
+ }, (0, _react.jsx)("svg", {
41
+ width: 24,
42
+ height: 24,
43
+ viewBox: "-4 -4 24 24",
44
+ fill: "none",
45
+ xmlns: "http://www.w3.org/2000/svg",
46
+ role: "presentation",
47
+ css: [svgStyles, svgSizeStyles]
48
+ }, (0, _react.jsx)("circle", {
49
+ cx: "8",
50
+ cy: "4",
51
+ r: "1.5",
52
+ fill: "currentColor"
53
+ }), (0, _react.jsx)("circle", {
54
+ cx: "8",
55
+ cy: "8",
56
+ r: "1.5",
57
+ fill: "currentColor"
58
+ }), (0, _react.jsx)("circle", {
59
+ cx: "8",
60
+ cy: "12",
61
+ r: "1.5",
62
+ fill: "currentColor"
63
+ })));
64
+ };
@@ -41,6 +41,7 @@ var _dragHandlePositions = require("../pm-plugins/utils/drag-handle-positions");
41
41
  var _getSelection = require("../pm-plugins/utils/getSelection");
42
42
  var _selection2 = require("../pm-plugins/utils/selection");
43
43
  var _consts2 = require("./consts");
44
+ var _dragHandleNestedIcon = require("./drag-handle-nested-icon");
44
45
  var _dragPreview = require("./drag-preview");
45
46
  var _anchorName = require("./utils/anchor-name");
46
47
  var _domAttrName = require("./utils/dom-attr-name");
@@ -358,7 +359,11 @@ var getExpandedSelectionRange = function getExpandedSelectionRange(_ref) {
358
359
  var selectUp = resolvedStartPos.pos < selection.from;
359
360
  var $from = isShiftPressed && selectUp ? resolvedStartPos : selection.$from;
360
361
  var $to = isShiftPressed && !selectUp ? doc.resolve(resolvedStartPos.pos + 1) : selection.$to;
361
- return (0, _selection.expandToBlockRange)($from, $to);
362
+ var adjusted = isShiftPressed ? {
363
+ $from: $from,
364
+ $to: $to
365
+ } : (0, _selection2.adjustSelectionBoundsForEdgePositions)($from, $to);
366
+ return (0, _selection.expandToBlockRange)(adjusted.$from, adjusted.$to);
362
367
  };
363
368
  /**
364
369
  * Updates the transaction with preserved selection logic.
@@ -443,6 +448,24 @@ var DragHandle = exports.DragHandle = function DragHandle(_ref3) {
443
448
  var start = getPos();
444
449
  var isLayoutColumn = nodeType === 'layoutColumn';
445
450
  var isMultiSelect = (0, _experiments.editorExperiment)('platform_editor_element_drag_and_drop_multiselect', true);
451
+
452
+ // Dynamically calculate if node is top-level based on current position (gated by experiment)
453
+ var isTopLevelNodeDynamic = (0, _react.useMemo)(function () {
454
+ if (!(0, _expValEquals.expValEquals)('platform_editor_nested_drag_handle_icon', 'isEnabled', true)) {
455
+ return isTopLevelNode;
456
+ }
457
+ var pos = getPos();
458
+ if (typeof pos === 'number') {
459
+ var $pos = view.state.doc.resolve(pos);
460
+ return ($pos === null || $pos === void 0 ? void 0 : $pos.parent.type.name) === 'doc';
461
+ }
462
+ return true;
463
+ }, [getPos, view.state.doc, isTopLevelNode]);
464
+
465
+ // Use the dynamic value when experiment is on, otherwise use the prop
466
+ // When cleaning up the experiment, you can safely remove the isTopLevelNode as an prop and
467
+ // just rely on the dynamic value (rename it to isTopLevelNode for simplicitiy)
468
+ var isTopLevelNodeValue = (0, _expValEquals.expValEquals)('platform_editor_nested_drag_handle_icon', 'isEnabled', true) ? isTopLevelNodeDynamic : isTopLevelNode;
446
469
  (0, _react.useEffect)(function () {
447
470
  if ((0, _experiments.editorExperiment)('platform_editor_block_control_optimise_render', true)) {
448
471
  return;
@@ -533,7 +556,7 @@ var DragHandle = exports.DragHandle = function DragHandle(_ref3) {
533
556
  var $anchor = (mSelect === null || mSelect === void 0 ? void 0 : mSelect.anchor) !== undefined ? tr.doc.resolve(mSelect === null || mSelect === void 0 ? void 0 : mSelect.anchor) : tr.selection.$anchor;
534
557
  if (!isMultiSelect || tr.selection.empty || !e.shiftKey) {
535
558
  tr = (0, _getSelection.selectNode)(tr, startPos, nodeType, api);
536
- } else if (isTopLevelNode && $anchor.depth <= _consts2.DRAG_HANDLE_MAX_SHIFT_CLICK_DEPTH && e.shiftKey && (0, _platformFeatureFlags.fg)('platform_editor_elements_dnd_shift_click_select')) {
559
+ } else if (isTopLevelNodeValue && $anchor.depth <= _consts2.DRAG_HANDLE_MAX_SHIFT_CLICK_DEPTH && e.shiftKey && (0, _platformFeatureFlags.fg)('platform_editor_elements_dnd_shift_click_select')) {
537
560
  var _api$blockControls3;
538
561
  var alignAnchorHeadToSel = (0, _selection2.alignAnchorHeadInDirectionOfPos)(tr.selection, startPos);
539
562
  var selectionWithExpandedHead = (0, _selection2.expandSelectionHeadToNodeAtPos)(alignAnchorHeadToSel, startPos);
@@ -558,7 +581,7 @@ var DragHandle = exports.DragHandle = function DragHandle(_ref3) {
558
581
  return tr;
559
582
  });
560
583
  view.focus();
561
- }, [isMultiSelect, api, view, dragHandleSelected, getPos, isTopLevelNode, nodeType]);
584
+ }, [isMultiSelect, api, view, dragHandleSelected, getPos, isTopLevelNodeValue, nodeType]);
562
585
  var handleKeyDown = (0, _react.useCallback)(function (e) {
563
586
  // allow user to use spacebar to select the node
564
587
  if (!e.repeat && e.key === ' ') {
@@ -858,18 +881,18 @@ var DragHandle = exports.DragHandle = function DragHandle(_ref3) {
858
881
  var isEdgeCase = (hasResizer || isExtension || isEmbedCard || isBlockCard) && innerContainer;
859
882
  var isSticky = (0, _dragHandlePositions.shouldBeSticky)(nodeType);
860
883
  if (supportsAnchor) {
861
- var bottom = (0, _experiments.editorExperiment)('platform_editor_controls', 'variant1') ? (0, _dragHandlePositions.getControlBottomCSSValue)(safeAnchorName, isSticky, isTopLevelNode, isLayoutColumn) : {};
884
+ var bottom = (0, _experiments.editorExperiment)('platform_editor_controls', 'variant1') ? (0, _dragHandlePositions.getControlBottomCSSValue)(safeAnchorName, isSticky, isTopLevelNodeValue, isLayoutColumn) : {};
862
885
  return _objectSpread({
863
886
  left: isEdgeCase ? "calc(anchor(".concat(safeAnchorName, " start) + ").concat((0, _dragHandlePositions.getLeftPosition)(dom, nodeType, innerContainer, isMacroInteractionUpdates, parentNodeType), ")") : (0, _experiments.editorExperiment)('advanced_layouts', true) && isLayoutColumn ? "calc((anchor(".concat(safeAnchorName, " right) + anchor(").concat(safeAnchorName, " left))/2 - ").concat(_consts2.DRAG_HANDLE_HEIGHT / 2, "px)") : "calc(anchor(".concat(safeAnchorName, " start) - ").concat(_styles.DRAG_HANDLE_WIDTH, "px - ").concat((0, _consts2.dragHandleGap)(nodeType, parentNodeType), "px)"),
864
887
  top: (0, _experiments.editorExperiment)('advanced_layouts', true) && isLayoutColumn ? "calc(anchor(".concat(safeAnchorName, " top) - ").concat(_styles.DRAG_HANDLE_WIDTH, "px)") : "calc(anchor(".concat(safeAnchorName, " start)+ ").concat((0, _consts2.topPositionAdjustment)((0, _expValEquals.expValEquals)('platform_editor_native_anchor_with_dnd', 'isEnabled', true) ? $pos && $pos.nodeAfter && (0, _decorationsCommon.getNodeTypeWithLevel)($pos.nodeAfter) || nodeType : nodeType), "px)")
865
888
  }, bottom);
866
889
  }
867
- var height = (0, _experiments.editorExperiment)('platform_editor_controls', 'variant1') ? (0, _dragHandlePositions.getControlHeightCSSValue)((0, _dragHandlePositions.getNodeHeight)(dom, safeAnchorName, anchorRectCache) || 0, isSticky, isTopLevelNode, "".concat(_consts2.DRAG_HANDLE_HEIGHT), isLayoutColumn) : {};
890
+ var height = (0, _experiments.editorExperiment)('platform_editor_controls', 'variant1') ? (0, _dragHandlePositions.getControlHeightCSSValue)((0, _dragHandlePositions.getNodeHeight)(dom, safeAnchorName, anchorRectCache) || 0, isSticky, isTopLevelNodeValue, "".concat(_consts2.DRAG_HANDLE_HEIGHT), isLayoutColumn) : {};
868
891
  return _objectSpread({
869
892
  left: isEdgeCase ? "calc(".concat((dom === null || dom === void 0 ? void 0 : dom.offsetLeft) || 0, "px + ").concat((0, _dragHandlePositions.getLeftPosition)(dom, nodeType, innerContainer, isMacroInteractionUpdates, parentNodeType), ")") : (0, _dragHandlePositions.getLeftPosition)(dom, nodeType, innerContainer, isMacroInteractionUpdates, parentNodeType),
870
893
  top: (0, _dragHandlePositions.getTopPosition)(dom, nodeType)
871
894
  }, height);
872
- }, [anchorName, getPos, view, nodeType, macroInteractionUpdates, anchorRectCache, isTopLevelNode, isLayoutColumn, recalculatePosition]);
895
+ }, [anchorName, getPos, view, nodeType, macroInteractionUpdates, anchorRectCache, isTopLevelNodeValue, isLayoutColumn, recalculatePosition]);
873
896
  var calculatePositionOld = (0, _react.useCallback)(function () {
874
897
  var pos = getPos();
875
898
  var $pos = (0, _expValEquals.expValEquals)('platform_editor_native_anchor_with_dnd', 'isEnabled', true) ? typeof pos === 'number' && view.state.doc.resolve(pos) : pos && view.state.doc.resolve(pos);
@@ -904,18 +927,18 @@ var DragHandle = exports.DragHandle = function DragHandle(_ref3) {
904
927
  var isEdgeCase = (hasResizer || isExtension || isEmbedCard || isBlockCard) && innerContainer;
905
928
  var isSticky = (0, _dragHandlePositions.shouldBeSticky)(nodeType);
906
929
  if (supportsAnchor) {
907
- var bottom = (0, _experiments.editorExperiment)('platform_editor_controls', 'variant1') ? (0, _dragHandlePositions.getControlBottomCSSValue)(safeAnchorName, isSticky, isTopLevelNode, isLayoutColumn) : {};
930
+ var bottom = (0, _experiments.editorExperiment)('platform_editor_controls', 'variant1') ? (0, _dragHandlePositions.getControlBottomCSSValue)(safeAnchorName, isSticky, isTopLevelNodeValue, isLayoutColumn) : {};
908
931
  return _objectSpread({
909
932
  left: isEdgeCase ? "calc(anchor(".concat(safeAnchorName, " start) + ").concat((0, _dragHandlePositions.getLeftPosition)(dom, nodeType, innerContainer, isMacroInteractionUpdates, parentNodeType), ")") : (0, _experiments.editorExperiment)('advanced_layouts', true) && isLayoutColumn ? "calc((anchor(".concat(safeAnchorName, " right) + anchor(").concat(safeAnchorName, " left))/2 - ").concat(_consts2.DRAG_HANDLE_HEIGHT / 2, "px)") : "calc(anchor(".concat(safeAnchorName, " start) - ").concat(_styles.DRAG_HANDLE_WIDTH, "px - ").concat((0, _consts2.dragHandleGap)(nodeType, parentNodeType), "px)"),
910
933
  top: (0, _experiments.editorExperiment)('advanced_layouts', true) && isLayoutColumn ? "calc(anchor(".concat(safeAnchorName, " top) - ").concat(_styles.DRAG_HANDLE_WIDTH, "px)") : "calc(anchor(".concat(safeAnchorName, " start) + ").concat((0, _consts2.topPositionAdjustment)((0, _expValEquals.expValEquals)('platform_editor_native_anchor_with_dnd', 'isEnabled', true) ? $pos && $pos.nodeAfter && (0, _decorationsCommon.getNodeTypeWithLevel)($pos.nodeAfter) || nodeType : nodeType), "px)")
911
934
  }, bottom);
912
935
  }
913
- var height = (0, _experiments.editorExperiment)('platform_editor_controls', 'variant1') ? (0, _dragHandlePositions.getControlHeightCSSValue)((0, _dragHandlePositions.getNodeHeight)(dom, safeAnchorName, anchorRectCache) || 0, isSticky, isTopLevelNode, "".concat(_consts2.DRAG_HANDLE_HEIGHT), isLayoutColumn) : {};
936
+ var height = (0, _experiments.editorExperiment)('platform_editor_controls', 'variant1') ? (0, _dragHandlePositions.getControlHeightCSSValue)((0, _dragHandlePositions.getNodeHeight)(dom, safeAnchorName, anchorRectCache) || 0, isSticky, isTopLevelNodeValue, "".concat(_consts2.DRAG_HANDLE_HEIGHT), isLayoutColumn) : {};
914
937
  return _objectSpread({
915
938
  left: isEdgeCase ? "calc(".concat((dom === null || dom === void 0 ? void 0 : dom.offsetLeft) || 0, "px + ").concat((0, _dragHandlePositions.getLeftPosition)(dom, nodeType, innerContainer, isMacroInteractionUpdates, parentNodeType), ")") : (0, _dragHandlePositions.getLeftPosition)(dom, nodeType, innerContainer, isMacroInteractionUpdates, parentNodeType),
916
939
  top: (0, _dragHandlePositions.getTopPosition)(dom, nodeType)
917
940
  }, height);
918
- }, [anchorName, getPos, view, nodeType, blockCardWidth, macroInteractionUpdates, anchorRectCache, isTopLevelNode, isLayoutColumn]);
941
+ }, [anchorName, getPos, view, nodeType, blockCardWidth, macroInteractionUpdates, anchorRectCache, isTopLevelNodeValue, isLayoutColumn]);
919
942
  (0, _react.useEffect)(function () {
920
943
  if ((0, _experiments.editorExperiment)('platform_editor_block_control_optimise_render', true)) {
921
944
  return;
@@ -991,12 +1014,12 @@ var DragHandle = exports.DragHandle = function DragHandle(_ref3) {
991
1014
  }
992
1015
  var mSelect = api === null || api === void 0 || (_api$blockControls$sh4 = api.blockControls.sharedState.currentState()) === null || _api$blockControls$sh4 === void 0 ? void 0 : _api$blockControls$sh4.multiSelectDnD;
993
1016
  var $anchor = (mSelect === null || mSelect === void 0 ? void 0 : mSelect.anchor) !== undefined ? view.state.doc.resolve(mSelect === null || mSelect === void 0 ? void 0 : mSelect.anchor) : view.state.selection.$anchor;
994
- if (isShiftDown && (!isTopLevelNode || isTopLevelNode && $anchor.depth > _consts2.DRAG_HANDLE_MAX_SHIFT_CLICK_DEPTH)) {
1017
+ if (isShiftDown && (!isTopLevelNodeValue || isTopLevelNodeValue && $anchor.depth > _consts2.DRAG_HANDLE_MAX_SHIFT_CLICK_DEPTH)) {
995
1018
  setDragHandleDisabled(true);
996
1019
  } else {
997
1020
  setDragHandleDisabled(false);
998
1021
  }
999
- }, [api === null || api === void 0 ? void 0 : api.blockControls.sharedState, isMultiSelect, isShiftDown, isTopLevelNode, view]);
1022
+ }, [api === null || api === void 0 ? void 0 : api.blockControls.sharedState, isMultiSelect, isShiftDown, isTopLevelNodeValue, view]);
1000
1023
  var dragHandleMessage = (0, _expValEqualsNoExposure.expValEqualsNoExposure)('platform_editor_block_menu', 'isEnabled', true) ? formatMessage(_messages.blockControlsMessages.dragToMoveClickToOpen, {
1001
1024
  br: (0, _react2.jsx)("br", null)
1002
1025
  }) : formatMessage(_messages.blockControlsMessages.dragToMove);
@@ -1005,7 +1028,7 @@ var DragHandle = exports.DragHandle = function DragHandle(_ref3) {
1005
1028
  var dragHandleAriaLabel = (0, _expValEqualsNoExposure.expValEqualsNoExposure)('platform_editor_block_menu', 'isEnabled', true) ? formatMessage(_messages.blockControlsMessages.dragToMoveClickToOpen, {
1006
1029
  br: ' '
1007
1030
  }) : formatMessage(_messages.blockControlsMessages.dragToMove);
1008
- var helpDescriptors = isTopLevelNode ? [{
1031
+ var helpDescriptors = isTopLevelNodeValue ? [{
1009
1032
  description: dragHandleMessage
1010
1033
  }, {
1011
1034
  description: formatMessage(_messages.blockControlsMessages.moveUp),
@@ -1029,7 +1052,7 @@ var DragHandle = exports.DragHandle = function DragHandle(_ref3) {
1029
1052
  keymap: _keymaps.dragToMoveDown
1030
1053
  }];
1031
1054
  var isParentNodeOfTypeLayout;
1032
- if (!isTopLevelNode) {
1055
+ if (!isTopLevelNodeValue) {
1033
1056
  var pos = getPos();
1034
1057
  if (typeof pos === 'number') {
1035
1058
  var _$pos$parent;
@@ -1109,7 +1132,7 @@ var DragHandle = exports.DragHandle = function DragHandle(_ref3) {
1109
1132
  // eslint-disable-next-line @atlaskit/design-system/no-direct-use-of-web-platform-drag-and-drop
1110
1133
  ,
1111
1134
  onDragStart: handleIconDragStart
1112
- }, (0, _react2.jsx)(_dragHandleVertical.default, {
1135
+ }, (0, _expValEquals.expValEquals)('platform_editor_nested_drag_handle_icon', 'isEnabled', true) && !isTopLevelNodeValue ? (0, _react2.jsx)(_dragHandleNestedIcon.DragHandleNestedIcon, null) : (0, _react2.jsx)(_dragHandleVertical.default, {
1113
1136
  spacing: "spacious",
1114
1137
  label: "",
1115
1138
  size: "small"
@@ -1125,7 +1148,7 @@ var DragHandle = exports.DragHandle = function DragHandle(_ref3) {
1125
1148
  as: "span",
1126
1149
  testId: "block-ctrl-drag-handle-container"
1127
1150
  }, (0, _react2.jsx)("span", {
1128
- css: [tooltipContainerStyles, (0, _dragHandlePositions.shouldMaskNodeControls)(nodeType, isTopLevelNode) && ((0, _expValEquals.expValEquals)('platform_editor_table_sticky_header_improvements', 'cohort', 'test_with_overflow') && (0, _platformFeatureFlags.fg)('platform_editor_table_sticky_header_patch_6') ? tooltipContainerStylesImprovedStickyHeaderWithMask : tooltipContainerStylesStickyHeaderWithMask), !(0, _dragHandlePositions.shouldMaskNodeControls)(nodeType, isTopLevelNode) && tooltipContainerStylesStickyHeaderWithoutMask]
1151
+ css: [tooltipContainerStyles, (0, _dragHandlePositions.shouldMaskNodeControls)(nodeType, isTopLevelNodeValue) && ((0, _expValEquals.expValEquals)('platform_editor_table_sticky_header_improvements', 'cohort', 'test_with_overflow') && (0, _platformFeatureFlags.fg)('platform_editor_table_sticky_header_patch_6') ? tooltipContainerStylesImprovedStickyHeaderWithMask : tooltipContainerStylesStickyHeaderWithMask), !(0, _dragHandlePositions.shouldMaskNodeControls)(nodeType, isTopLevelNodeValue) && tooltipContainerStylesStickyHeaderWithoutMask]
1129
1152
  }, (0, _react2.jsx)(_tooltip.default, {
1130
1153
  content: (0, _react2.jsx)(_keymaps.TooltipContentWithMultipleShortcuts, {
1131
1154
  helpDescriptors: helpDescriptors
@@ -1139,7 +1162,7 @@ var DragHandle = exports.DragHandle = function DragHandle(_ref3) {
1139
1162
  });
1140
1163
  }
1141
1164
  }, (0, _react2.jsx)("span", {
1142
- css: [(0, _dragHandlePositions.shouldMaskNodeControls)(nodeType, isTopLevelNode) && buttonWrapperStyles, buttonWrapperStylesPatch]
1165
+ css: [(0, _dragHandlePositions.shouldMaskNodeControls)(nodeType, isTopLevelNodeValue) && buttonWrapperStyles, buttonWrapperStylesPatch]
1143
1166
  }, renderButton()))));
1144
1167
  };
1145
1168
  var stickyWithoutTooltip = function stickyWithoutTooltip() {
@@ -1151,9 +1174,9 @@ var DragHandle = exports.DragHandle = function DragHandle(_ref3) {
1151
1174
  as: "span",
1152
1175
  testId: "block-ctrl-drag-handle-container"
1153
1176
  }, (0, _react2.jsx)("span", {
1154
- css: [tooltipContainerStyles, (0, _dragHandlePositions.shouldMaskNodeControls)(nodeType, isTopLevelNode) && tooltipContainerStylesStickyHeaderWithMask, !(0, _dragHandlePositions.shouldMaskNodeControls)(nodeType, isTopLevelNode) && tooltipContainerStylesStickyHeaderWithoutMask]
1177
+ css: [tooltipContainerStyles, (0, _dragHandlePositions.shouldMaskNodeControls)(nodeType, isTopLevelNodeValue) && tooltipContainerStylesStickyHeaderWithMask, !(0, _dragHandlePositions.shouldMaskNodeControls)(nodeType, isTopLevelNodeValue) && tooltipContainerStylesStickyHeaderWithoutMask]
1155
1178
  }, (0, _react2.jsx)("span", {
1156
- css: [(0, _dragHandlePositions.shouldMaskNodeControls)(nodeType, isTopLevelNode) && buttonWrapperStyles, buttonWrapperStylesPatch]
1179
+ css: [(0, _dragHandlePositions.shouldMaskNodeControls)(nodeType, isTopLevelNodeValue) && buttonWrapperStyles, buttonWrapperStylesPatch]
1157
1180
  }, renderButton())));
1158
1181
  };
1159
1182
  var buttonWithTooltip = function buttonWithTooltip() {
@@ -1183,6 +1206,7 @@ var DragHandleWithVisibility = exports.DragHandleWithVisibility = function DragH
1183
1206
  anchorName = _ref10.anchorName,
1184
1207
  nodeType = _ref10.nodeType,
1185
1208
  handleOptions = _ref10.handleOptions,
1209
+ isTopLevelNode = _ref10.isTopLevelNode,
1186
1210
  anchorRectCache = _ref10.anchorRectCache;
1187
1211
  return (0, _react2.jsx)(_visibilityContainer.VisibilityContainer, {
1188
1212
  api: api
@@ -1194,6 +1218,7 @@ var DragHandleWithVisibility = exports.DragHandleWithVisibility = function DragH
1194
1218
  anchorName: anchorName,
1195
1219
  nodeType: nodeType,
1196
1220
  handleOptions: handleOptions,
1221
+ isTopLevelNode: isTopLevelNode,
1197
1222
  anchorRectCache: anchorRectCache
1198
1223
  }));
1199
1224
  };
@@ -134,7 +134,7 @@ export const blockControlsPlugin = ({
134
134
  });
135
135
  if ((menuTriggerBy === undefined || !!menuTriggerBy && menuTriggerBy === (options === null || options === void 0 ? void 0 : options.anchorName)) && currentUserIntent === 'blockMenuOpen') {
136
136
  const state = api === null || api === void 0 ? void 0 : api.blockControls.sharedState.currentState();
137
- if (state !== null && state !== void 0 && state.isSelectedViaDragHandle && fg('platform_editor_toolbar_aifc_user_intent_fix')) {
137
+ if (state !== null && state !== void 0 && state.isSelectedViaDragHandle) {
138
138
  var _api$userIntent4;
139
139
  api === null || api === void 0 ? void 0 : (_api$userIntent4 = api.userIntent) === null || _api$userIntent4 === void 0 ? void 0 : _api$userIntent4.commands.setCurrentUserIntent('dragHandleSelected')({
140
140
  tr
@@ -83,7 +83,7 @@ export const dragHandleDecoration = ({
83
83
  const getPos = () => {
84
84
  try {
85
85
  return getPosUnsafe();
86
- } catch (e) {
86
+ } catch {
87
87
  // Ignore errors from getPosUnsafe
88
88
  return undefined;
89
89
  }
@@ -1,5 +1,4 @@
1
1
  import { DRAG_HANDLE_SELECTOR } from '@atlaskit/editor-common/styles';
2
- import { fg } from '@atlaskit/platform-feature-flags';
3
2
  import { expValEquals } from '@atlaskit/tmp-editor-statsig/exp-val-equals';
4
3
  import { expValEqualsNoExposure } from '@atlaskit/tmp-editor-statsig/exp-val-equals-no-exposure';
5
4
  export const handleMouseDown = api => (view, event) => {
@@ -39,21 +38,19 @@ export const handleMouseDown = api => (view, event) => {
39
38
  * When block menu is enabled, reset intent back to 'default' as editor-plugin-block-menu sets the user intent to 'blockMenuOpen', and setting here
40
39
  * causes flickering as this runs before editor-plugin-block-menu.
41
40
  */
42
- if (fg('platform_editor_toolbar_aifc_user_intent_fix')) {
43
- if (expValEqualsNoExposure('platform_editor_block_menu', 'isEnabled', true)) {
44
- // if target is drag handle, block menu will be opened
45
- if (!isDragHandle) {
46
- var _api$userIntent;
47
- api === null || api === void 0 ? void 0 : (_api$userIntent = api.userIntent) === null || _api$userIntent === void 0 ? void 0 : _api$userIntent.commands.setCurrentUserIntent('default')({
48
- tr
49
- });
50
- }
51
- } else {
52
- var _api$userIntent2;
53
- (_api$userIntent2 = api.userIntent) === null || _api$userIntent2 === void 0 ? void 0 : _api$userIntent2.commands.setCurrentUserIntent(isDragHandle ? 'dragHandleSelected' : 'default')({
41
+ if (expValEqualsNoExposure('platform_editor_block_menu', 'isEnabled', true)) {
42
+ // if target is drag handle, block menu will be opened
43
+ if (!isDragHandle) {
44
+ var _api$userIntent;
45
+ api === null || api === void 0 ? void 0 : (_api$userIntent = api.userIntent) === null || _api$userIntent === void 0 ? void 0 : _api$userIntent.commands.setCurrentUserIntent('default')({
54
46
  tr
55
47
  });
56
48
  }
49
+ } else {
50
+ var _api$userIntent2;
51
+ (_api$userIntent2 = api.userIntent) === null || _api$userIntent2 === void 0 ? void 0 : _api$userIntent2.commands.setCurrentUserIntent(isDragHandle ? 'dragHandleSelected' : 'default')({
52
+ tr
53
+ });
57
54
  }
58
55
  return tr;
59
56
  });
@@ -99,7 +99,54 @@ export const mapPreservedSelection = (selection, tr) => {
99
99
  }
100
100
  return createPreservedSelection(tr.doc.resolve(from), tr.doc.resolve(to));
101
101
  };
102
+ const isInsideEmptyTextblock = $pos => $pos.parent.isTextblock && $pos.parent.content.size === 0;
103
+ const isAtEndOfParent = $pos => $pos.parentOffset === $pos.parent.content.size;
104
+ const isAtStartOfParent = $pos => $pos.parentOffset === 0;
102
105
 
106
+ /**
107
+ * Adjust selection bounds to exclude nodes where the selection only touches
108
+ * the edge position without selecting any content.
109
+ *
110
+ * Exception: Don't adjust if the selection is inside an empty textblock,
111
+ * as we want to include empty paragraphs in block operations.
112
+ *
113
+ * @param $from The resolved position of the start of the selection
114
+ * @param $to The resolved position of the end of the selection
115
+ * @returns Adjusted $from and $to positions
116
+ */
117
+ export const adjustSelectionBoundsForEdgePositions = ($from, $to) => {
118
+ const {
119
+ doc
120
+ } = $from;
121
+
122
+ // Walk $from forward while at end of ancestors
123
+ let adjustedFrom = $from;
124
+ if (!isInsideEmptyTextblock($from)) {
125
+ while (adjustedFrom.depth > 0 && isAtEndOfParent(adjustedFrom)) {
126
+ const nextPos = adjustedFrom.after();
127
+ if (nextPos > doc.content.size || nextPos === adjustedFrom.pos) {
128
+ break;
129
+ }
130
+ adjustedFrom = doc.resolve(nextPos);
131
+ }
132
+ }
133
+
134
+ // Walk $to backward while at start of ancestors
135
+ let adjustedTo = $to;
136
+ if (!isInsideEmptyTextblock($to)) {
137
+ while (adjustedTo.depth > 0 && isAtStartOfParent(adjustedTo)) {
138
+ const prevPos = adjustedTo.before();
139
+ if (prevPos < 0 || prevPos === adjustedTo.pos) {
140
+ break;
141
+ }
142
+ adjustedTo = doc.resolve(prevPos);
143
+ }
144
+ }
145
+ return {
146
+ $from: adjustedFrom,
147
+ $to: adjustedTo
148
+ };
149
+ };
103
150
  /**
104
151
  * Creates a preserved selection which is expanded to block boundaries.
105
152
  *
@@ -117,9 +164,17 @@ export const createPreservedSelection = ($from, $to) => {
117
164
  const {
118
165
  doc
119
166
  } = $from;
167
+ const isCollapsed = $from.pos === $to.pos;
168
+ const adjusted = isCollapsed ? {
169
+ $from,
170
+ $to
171
+ } : adjustSelectionBoundsForEdgePositions($from, $to);
172
+ if (!isCollapsed && adjusted.$from.pos >= adjusted.$to.pos) {
173
+ return undefined;
174
+ }
120
175
 
121
176
  // expand the selection range to block boundaries, so selection always includes whole nodes
122
- const expanded = expandToBlockRange($from, $to);
177
+ const expanded = expandToBlockRange(adjusted.$from, adjusted.$to);
123
178
 
124
179
  // stop preserving if selection becomes invalid
125
180
  if (expanded.$from.pos < 0 || expanded.$to.pos > doc.content.size || expanded.$from.pos >= expanded.$to.pos) {
@@ -0,0 +1,57 @@
1
+ /** @jsxRuntime classic */
2
+ /**
3
+ * @jsxRuntime classic
4
+ * @jsx jsx
5
+ */
6
+ // eslint-disable-next-line @atlaskit/ui-styling-standard/use-compiled -- Ignored via go/DSP-18766
7
+ import { css, jsx } from '@emotion/react';
8
+ const spanStyles = css({
9
+ display: 'inline-block',
10
+ boxSizing: 'border-box',
11
+ flexShrink: 0
12
+ });
13
+ const svgSizeStyles = css({
14
+ width: '24px',
15
+ height: '24px'
16
+ });
17
+ const svgStyles = css({
18
+ color: 'currentColor',
19
+ overflow: 'hidden',
20
+ pointerEvents: 'none',
21
+ verticalAlign: 'bottom'
22
+ });
23
+
24
+ /**
25
+ * Custom 3-dot vertical drag handle icon for nested nodes.
26
+ * Similar to DragHandleVerticalIcon but with only 3 dots instead of 6.
27
+ * Hardcoded to medium size with spacious spacing.
28
+ */
29
+ export const DragHandleNestedIcon = () => {
30
+ return jsx("span", {
31
+ "aria-hidden": true,
32
+ css: spanStyles
33
+ }, jsx("svg", {
34
+ width: 24,
35
+ height: 24,
36
+ viewBox: "-4 -4 24 24",
37
+ fill: "none",
38
+ xmlns: "http://www.w3.org/2000/svg",
39
+ role: "presentation",
40
+ css: [svgStyles, svgSizeStyles]
41
+ }, jsx("circle", {
42
+ cx: "8",
43
+ cy: "4",
44
+ r: "1.5",
45
+ fill: "currentColor"
46
+ }), jsx("circle", {
47
+ cx: "8",
48
+ cy: "8",
49
+ r: "1.5",
50
+ fill: "currentColor"
51
+ }), jsx("circle", {
52
+ cx: "8",
53
+ cy: "12",
54
+ r: "1.5",
55
+ fill: "currentColor"
56
+ })));
57
+ };