UNPKG

@atlaskit/editor-plugin-block-controls

Version:

Block controls plugin for @atlaskit/editor-core

1,214 lines 64.7 kB
import _toConsumableArray from "@babel/runtime/helpers/toConsumableArray";
import _slicedToArray from "@babel/runtime/helpers/slicedToArray";
import _defineProperty from "@babel/runtime/helpers/defineProperty";
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; }
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; }
/**
 * @jsxRuntime classic
 * @jsx jsx
 */
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';

// eslint-disable-next-line @atlaskit/ui-styling-standard/use-compiled, @typescript-eslint/consistent-type-imports
import { css, jsx } from '@emotion/react';
import { bind } from 'bind-event-listener';
import { getDocument } from '@atlaskit/browser-apis';
import { ACTION, ACTION_SUBJECT, ACTION_SUBJECT_ID, EVENT_TYPE } from '@atlaskit/editor-common/analytics';
import { getBrowserInfo } from '@atlaskit/editor-common/browser';
import { useSharedPluginStateWithSelector } from '@atlaskit/editor-common/hooks';
import { dragToMoveDown, dragToMoveLeft, dragToMoveRight, dragToMoveUp, getAriaKeyshortcuts, TooltipContentWithMultipleShortcuts } from '@atlaskit/editor-common/keymaps';
import { blockControlsMessages } from '@atlaskit/editor-common/messages';
import { DRAG_HANDLE_WIDTH, tableControlsSpacing } from '@atlaskit/editor-common/styles';
import { findDomRefAtPos } from '@atlaskit/editor-prosemirror/utils';
import { akEditorFullPageNarrowBreakout, akEditorTableToolbarSize, relativeSizeToBaseFontSize } from '@atlaskit/editor-shared-styles/consts';
import DragHandleVerticalIcon from '@atlaskit/icon/core/drag-handle-vertical';
import { isExperimentEnabled } from '@atlaskit/platform-feature-experiments/is-experiment-enabled';
import { fg } from '@atlaskit/platform-feature-flags/fg';
import { draggable } from '@atlaskit/pragmatic-drag-and-drop/adapter/element-adapter';
import { setCustomNativeDragPreview } from '@atlaskit/pragmatic-drag-and-drop/utils/set-custom-native-drag-preview';
// eslint-disable-next-line @atlaskit/design-system/no-emotion-primitives -- to be migrated to @atlaskit/primitives/compiled – go/akcss
import { Box, xcss } from '@atlaskit/primitives';
import { editorExperiment } from '@atlaskit/tmp-editor-statsig/editor-experiment';
import { expValEquals } from '@atlaskit/tmp-editor-statsig/exp-val-equals';
import { expValEqualsNoExposure } from '@atlaskit/tmp-editor-statsig/exp-val-equals-no-exposure';
import Tooltip from '@atlaskit/tooltip/Tooltip';
import { getNodeTypeWithLevel } from '../pm-plugins/decorations-common';
import { key } from '../pm-plugins/main';
import { selectionPreservationPluginKey } from '../pm-plugins/selection-preservation/plugin-key';
import { getMultiSelectAnalyticsAttributes } from '../pm-plugins/utils/analytics';
import { getControlBottomCSSValue, getControlHeightCSSValue, getLeftPosition, getNodeHeight, getTopPosition, shouldBeSticky, shouldMaskNodeControls } from '../pm-plugins/utils/drag-handle-positions';
import { expandAndUpdateSelection } from '../pm-plugins/utils/expand-and-update-selection';
import { isHandleCorrelatedToSelection, selectNode } from '../pm-plugins/utils/getSelection';
import { ACTIVE_DRAG_HANDLE_ATTR, ACTIVE_DRAG_HANDLE_FALLBACK_ANCHOR_NAME, DRAG_HANDLE_BORDER_RADIUS, DRAG_HANDLE_HEIGHT, DRAG_HANDLE_MAX_SHIFT_CLICK_DEPTH, DRAG_HANDLE_ZINDEX, dragHandleGap, nodeMargins, spacingBetweenNodesForPreview, STICKY_CONTROLS_TOP_MARGIN, STICKY_CONTROLS_TOP_MARGIN_FOR_STICKY_HEADER, topPositionAdjustment } from './consts';
import { DragHandleNestedIcon } from './drag-handle-nested-icon';
import { dragPreview } from './drag-preview';
import { shouldUseNestedDragHandleIcon } from './should-use-nested-drag-handle-icon';
import { refreshAnchorName } from './utils/anchor-name';
import { getAnchorAttrName } from './utils/dom-attr-name';
import { VisibilityContainer } from './visibility-container';
var iconWrapperStyles = xcss({
  display: 'flex',
  justifyContent: 'center',
  alignItems: 'center'
});
var buttonWrapperStylesNoBackground = css({
  display: 'flex',
  justifyContent: 'center',
  alignItems: 'center',
  // eslint-disable-next-line @atlaskit/ui-styling-standard/no-nested-selectors, @atlaskit/ui-styling-standard/no-unsafe-selectors
  '[data-blocks-drag-handle-container]:has(+ [data-prosemirror-node-name="table"] .pm-table-with-controls tr.sticky) &': {
    marginBottom: "var(--ds-space-negative-200, -16px)",
    paddingBottom: "var(--ds-space-200, 16px)",
    marginTop: "var(--ds-space-negative-400, -32px)",
    paddingTop: "calc(".concat("var(--ds-space-400, 32px)", " - 1px)"),
    marginRight: "var(--ds-space-negative-150, -12px)",
    paddingRight: "var(--ds-space-150, 12px)",
    boxSizing: 'border-box'
  },
  // eslint-disable-next-line @atlaskit/ui-styling-standard/no-nested-selectors, @atlaskit/ui-styling-standard/no-unsafe-selectors
  '[data-prosemirror-mark-name="breakout"]:has([data-blocks-drag-handle-container]):has(+ [data-prosemirror-node-name="table"] .pm-table-with-controls tr.sticky) &': {
    marginBottom: "var(--ds-space-negative-200, -16px)",
    paddingBottom: "var(--ds-space-200, 16px)",
    marginTop: "var(--ds-space-negative-400, -32px)",
    paddingTop: "calc(".concat("var(--ds-space-400, 32px)", " - 1px)"),
    marginRight: "var(--ds-space-negative-150, -12px)",
    paddingRight: "var(--ds-space-150, 12px)",
    boxSizing: 'border-box'
  }
});
var buttonWrapperStylesPatch = css({
  // eslint-disable-next-line @atlaskit/ui-styling-standard/no-nested-selectors, @atlaskit/ui-styling-standard/no-unsafe-selectors
  '[data-blocks-drag-handle-container]:has(+ [data-prosemirror-node-name="table"] .pm-table-with-controls [data-number-column="true"] tr.sticky) &': {
    // eslint-disable-next-line @atlaskit/ui-styling-standard/no-imported-style-values, @atlaskit/ui-styling-standard/no-unsafe-values
    marginRight: -akEditorTableToolbarSize,
    // eslint-disable-next-line @atlaskit/ui-styling-standard/no-imported-style-values, @atlaskit/ui-styling-standard/no-unsafe-values
    paddingRight: akEditorTableToolbarSize
  },
  // eslint-disable-next-line @atlaskit/ui-styling-standard/no-nested-selectors, @atlaskit/ui-styling-standard/no-unsafe-selectors
  '[data-prosemirror-mark-name="breakout"]:has([data-blocks-drag-handle-container]):has(+ [data-prosemirror-node-name="table"] .pm-table-with-controls [data-number-column="true"] tr.sticky) &': {
    // eslint-disable-next-line @atlaskit/ui-styling-standard/no-imported-style-values, @atlaskit/ui-styling-standard/no-unsafe-values
    marginRight: -akEditorTableToolbarSize,
    // eslint-disable-next-line @atlaskit/ui-styling-standard/no-imported-style-values, @atlaskit/ui-styling-standard/no-unsafe-values
    paddingRight: akEditorTableToolbarSize
  }
});

// update color to match quick insert button for new editor controls
var dragHandleColor = css({
  color: "var(--ds-icon-subtle, #505258)"
});
var dragHandleButtonStyles = css({
  display: 'flex',
  boxSizing: 'border-box',
  flexDirection: 'column',
  justifyContent: 'center',
  alignItems: 'center',
  // eslint-disable-next-line @atlaskit/ui-styling-standard/no-imported-style-values, @atlaskit/ui-styling-standard/no-unsafe-values -- Ignored via go/DSP-18766
  height: DRAG_HANDLE_HEIGHT,
  // eslint-disable-next-line @atlaskit/ui-styling-standard/no-imported-style-values, @atlaskit/ui-styling-standard/no-unsafe-values -- Ignored via go/DSP-18766
  width: DRAG_HANDLE_WIDTH,
  border: 'none',
  background: 'transparent',
  // eslint-disable-next-line @atlaskit/ui-styling-standard/no-imported-style-values, @atlaskit/ui-styling-standard/no-unsafe-values -- Ignored via go/DSP-18766
  borderRadius: DRAG_HANDLE_BORDER_RADIUS,
  // when platform_editor_controls is enabled, the drag handle color is overridden. Update color here when experiment is cleaned up.
  color: "var(--ds-icon, #292A2E)",
  cursor: 'grab',
  // eslint-disable-next-line @atlaskit/ui-styling-standard/no-imported-style-values, @atlaskit/ui-styling-standard/no-unsafe-values -- Ignored via go/DSP-18766
  zIndex: DRAG_HANDLE_ZINDEX,
  outline: 'none',
  '&:hover': {
    backgroundColor: "var(--ds-background-neutral-subtle-hovered, #0515240F)"
  },
  '&:active': {
    backgroundColor: "var(--ds-background-neutral-subtle-pressed, #0B120E24)"
  },
  '&:disabled': {
    color: "var(--ds-icon-disabled, #080F214A)",
    backgroundColor: 'transparent'
  },
  '&:hover:disabled': {
    backgroundColor: "var(--ds-background-disabled, #0515240F)"
  }
});

// Calculate scaled dimensions based on the base font size using CSS calc()
// Default font size is 16px, scale proportionally
// Standard: 16px -> 24h x 12w, Dense: 13px -> 18h x 9w, Jira: 14px -> 21h x 12w
var dragHandleButtonScaledStyles = css({
  // eslint-disable-next-line @atlaskit/ui-styling-standard/no-imported-style-values, @atlaskit/ui-styling-standard/no-unsafe-values
  height: relativeSizeToBaseFontSize(DRAG_HANDLE_HEIGHT),
  // eslint-disable-next-line @atlaskit/ui-styling-standard/no-imported-style-values, @atlaskit/ui-styling-standard/no-unsafe-values
  width: relativeSizeToBaseFontSize(DRAG_HANDLE_WIDTH)
});
var dragHandleButtonSmallScreenStyles = css(_defineProperty({}, "@container editor-area (max-width: ".concat(akEditorFullPageNarrowBreakout, "px)"), {
  opacity: 0,
  visibility: 'hidden'
}));
var dragHandleButtonStylesOld = css({
  position: 'absolute',
  paddingTop: "var(--ds-space-025, 2px)",
  paddingBottom: "var(--ds-space-025, 2px)",
  paddingLeft: '0',
  paddingRight: '0',
  boxSizing: 'border-box',
  display: 'flex',
  flexDirection: 'column',
  justifyContent: 'center',
  alignItems: 'center',
  // eslint-disable-next-line @atlaskit/ui-styling-standard/no-imported-style-values, @atlaskit/ui-styling-standard/no-unsafe-values -- Ignored via go/DSP-18766
  height: DRAG_HANDLE_HEIGHT,
  // eslint-disable-next-line @atlaskit/ui-styling-standard/no-imported-style-values, @atlaskit/ui-styling-standard/no-unsafe-values -- Ignored via go/DSP-18766
  width: DRAG_HANDLE_WIDTH,
  border: 'none',
  background: 'transparent',
  // eslint-disable-next-line @atlaskit/ui-styling-standard/no-imported-style-values, @atlaskit/ui-styling-standard/no-unsafe-values -- Ignored via go/DSP-18766
  borderRadius: DRAG_HANDLE_BORDER_RADIUS,
  // when platform_editor_controls is enabled, the drag handle color is overridden. Update color here when experiment is cleaned up.
  color: "var(--ds-icon, #292A2E)",
  cursor: 'grab',
  // eslint-disable-next-line @atlaskit/ui-styling-standard/no-imported-style-values, @atlaskit/ui-styling-standard/no-unsafe-values -- Ignored via go/DSP-18766
  zIndex: DRAG_HANDLE_ZINDEX,
  outline: 'none',
  '&:hover': {
    backgroundColor: "var(--ds-background-neutral-subtle-hovered, #0515240F)"
  },
  '&:active': {
    backgroundColor: "var(--ds-background-neutral-subtle-pressed, #0B120E24)"
  },
  '&:focus': {
    outline: "var(--ds-border-width-focused, 2px)".concat(" solid ", "var(--ds-border-focused, #4688EC)")
  },
  '&:disabled': {
    color: "var(--ds-icon-disabled, #080F214A)",
    backgroundColor: 'transparent'
  },
  '&:hover:disabled': {
    backgroundColor: "var(--ds-background-disabled, #0515240F)"
  }
});
var focusedStyles = css({
  '&:focus-visible': {
    outline: "var(--ds-border-width-focused, 2px)".concat(" solid ", "var(--ds-border-focused, #4688EC)")
  }
});
var keyboardFocusedDragHandleStyles = css({
  outline: "var(--ds-border-width-focused, 2px)".concat(" solid ", "var(--ds-border-focused, #4688EC)")
});
var dragHandleContainerStyles = xcss({
  position: 'absolute',
  boxSizing: 'border-box'
});
var tooltipContainerStyles = css({
  // eslint-disable-next-line @atlaskit/ui-styling-standard/no-imported-style-values, @atlaskit/ui-styling-standard/no-unsafe-values
  bottom: "-".concat(STICKY_CONTROLS_TOP_MARGIN, "px"),
  position: 'sticky',
  display: 'block',
  zIndex: 100 // card = 100
});
var tooltipContainerStylesStickyHeaderWithMask = css({
  // eslint-disable-next-line @atlaskit/ui-styling-standard/no-imported-style-values, @atlaskit/ui-styling-standard/no-unsafe-values
  top: "".concat(STICKY_CONTROLS_TOP_MARGIN, "px"),
  // eslint-disable-next-line @atlaskit/ui-styling-standard/no-nested-selectors, @atlaskit/ui-styling-standard/no-unsafe-selectors
  '[data-blocks-drag-handle-container]:has(+ [data-prosemirror-node-name="table"] .pm-table-with-controls tr.sticky) &': {
    top: '0'
  },
  // eslint-disable-next-line @atlaskit/ui-styling-standard/no-nested-selectors, @atlaskit/ui-styling-standard/no-unsafe-selectors
  '[data-prosemirror-mark-name="breakout"]:has([data-blocks-drag-handle-container]):has(+ [data-prosemirror-node-name="table"] .pm-table-with-controls tr.sticky) &': {
    top: '0'
  }
});
var tooltipContainerStylesImprovedStickyHeaderWithMask = css({
  // eslint-disable-next-line @atlaskit/ui-styling-standard/no-imported-style-values, @atlaskit/ui-styling-standard/no-unsafe-values
  top: "".concat(STICKY_CONTROLS_TOP_MARGIN, "px"),
  // eslint-disable-next-line @atlaskit/ui-styling-standard/no-nested-selectors, @atlaskit/ui-styling-standard/no-unsafe-selectors
  '[data-blocks-drag-handle-container]:has(+ [data-prosemirror-node-name="table"] .pm-table-with-controls tr.sticky) &': {
    top: '0'
  },
  // eslint-disable-next-line @atlaskit/ui-styling-standard/no-nested-selectors, @atlaskit/ui-styling-standard/no-unsafe-selectors
  '[data-prosemirror-mark-name="breakout"]:has([data-blocks-drag-handle-container]):has(+ [data-prosemirror-node-name="table"] .pm-table-with-controls tr.sticky) &': {
    top: '0'
  },
  // eslint-disable-next-line @atlaskit/ui-styling-standard/no-nested-selectors, @atlaskit/ui-styling-standard/no-unsafe-selectors
  '[data-blocks-drag-handle-container]:has(+ [data-prosemirror-mark-name="fragment"] >[data-prosemirror-node-name="table"] .pm-table-with-controls tr.sticky) &': {
    // eslint-disable-next-line @atlaskit/ui-styling-standard/no-imported-style-values, @atlaskit/ui-styling-standard/no-unsafe-values
    top: tableControlsSpacing
  },
  // eslint-disable-next-line @atlaskit/ui-styling-standard/no-nested-selectors, @atlaskit/ui-styling-standard/no-unsafe-selectors
  '[data-prosemirror-mark-name="breakout"]:has([data-blocks-drag-handle-container]):has(+ [data-prosemirror-mark-name="fragment"] >[data-prosemirror-node-name="table"] .pm-table-with-controls tr.sticky) &': {
    // eslint-disable-next-line @atlaskit/ui-styling-standard/no-imported-style-values, @atlaskit/ui-styling-standard/no-unsafe-values
    top: tableControlsSpacing
  },
  // eslint-disable-next-line @atlaskit/ui-styling-standard/no-nested-selectors, @atlaskit/ui-styling-standard/no-unsafe-selectors
  '[data-blocks-drag-handle-container]:has(+ [data-prosemirror-node-name="table"] tr.pm-table-row-native-sticky.pm-table-row-native-sticky-active) &': {
    // eslint-disable-next-line @atlaskit/ui-styling-standard/no-imported-style-values, @atlaskit/ui-styling-standard/no-unsafe-values
    top: "".concat(STICKY_CONTROLS_TOP_MARGIN_FOR_STICKY_HEADER, "px")
  },
  // eslint-disable-next-line @atlaskit/ui-styling-standard/no-nested-selectors, @atlaskit/ui-styling-standard/no-unsafe-selectors
  '[data-prosemirror-mark-name="breakout"]:has([data-blocks-drag-handle-container]):has(+ [data-prosemirror-node-name="table"] tr.pm-table-row-native-sticky.pm-table-row-native-sticky-active) &': {
    // eslint-disable-next-line @atlaskit/ui-styling-standard/no-imported-style-values, @atlaskit/ui-styling-standard/no-unsafe-values
    top: "".concat(STICKY_CONTROLS_TOP_MARGIN_FOR_STICKY_HEADER, "px")
  },
  // eslint-disable-next-line @atlaskit/ui-styling-standard/no-nested-selectors, @atlaskit/ui-styling-standard/no-unsafe-selectors
  '[data-blocks-drag-handle-container]:has(+ [data-prosemirror-mark-name="fragment"] >[data-prosemirror-node-name="table"] tr.pm-table-row-native-sticky.pm-table-row-native-sticky-active) &': {
    // eslint-disable-next-line @atlaskit/ui-styling-standard/no-imported-style-values, @atlaskit/ui-styling-standard/no-unsafe-values
    top: "".concat(STICKY_CONTROLS_TOP_MARGIN_FOR_STICKY_HEADER, "px")
  },
  // eslint-disable-next-line @atlaskit/ui-styling-standard/no-nested-selectors, @atlaskit/ui-styling-standard/no-unsafe-selectors
  '[data-prosemirror-mark-name="breakout"]:has([data-blocks-drag-handle-container]):has(+ [data-prosemirror-mark-name="fragment"] > [data-prosemirror-node-name="table"] tr.pm-table-row-native-sticky.pm-table-row-native-sticky-active) &': {
    // eslint-disable-next-line @atlaskit/ui-styling-standard/no-imported-style-values, @atlaskit/ui-styling-standard/no-unsafe-values
    top: "".concat(STICKY_CONTROLS_TOP_MARGIN_FOR_STICKY_HEADER, "px")
  }
});
var tooltipContainerStylesStickyHeaderWithoutMask = css({
  // eslint-disable-next-line @atlaskit/ui-styling-standard/no-imported-style-values, @atlaskit/ui-styling-standard/no-unsafe-values
  top: "".concat(STICKY_CONTROLS_TOP_MARGIN, "px"),
  // eslint-disable-next-line @atlaskit/ui-styling-standard/no-nested-selectors, @atlaskit/ui-styling-standard/no-unsafe-selectors
  '[data-blocks-drag-handle-container]:has(+ [data-prosemirror-node-name="table"] .pm-table-with-controls tr.sticky) &': {
    // eslint-disable-next-line @atlaskit/ui-styling-standard/no-imported-style-values, @atlaskit/ui-styling-standard/no-unsafe-values
    top: tableControlsSpacing
  },
  // eslint-disable-next-line @atlaskit/ui-styling-standard/no-nested-selectors, @atlaskit/ui-styling-standard/no-unsafe-selectors
  '[data-prosemirror-mark-name="breakout"]:has([data-blocks-drag-handle-container]):has(+ [data-prosemirror-node-name="table"] .pm-table-with-controls tr.sticky) &': {
    // eslint-disable-next-line @atlaskit/ui-styling-standard/no-imported-style-values, @atlaskit/ui-styling-standard/no-unsafe-values
    top: tableControlsSpacing
  }
});
var dragHandleMultiLineSelectionFixFirefox = css({
  '&::selection': {
    backgroundColor: 'transparent'
  }
});
var layoutColumnDragHandleStyles = css({
  transform: 'rotate(90deg)'
});
var selectedStyles = css({
  backgroundColor: "var(--ds-background-selected, #E9F2FE)",
  color: "var(--ds-icon-selected, #1868DB)"
});

// [Chrome only] When selection contains multiple nodes and then drag a drag handle that is within the selection range,
// icon span receives dragStart event, instead of button, and since it is not registered as a draggable element
// with pragmatic DnD and pragmatic DnD is not triggered
var handleIconDragStart = function handleIconDragStart(e) {
  var browser = getBrowserInfo();
  if (!browser.chrome) {
    return;
  }
  // prevent dragStart handler triggered by icon
  e.stopPropagation();
  var dragEvent = new DragEvent('dragstart', {
    bubbles: true,
    cancelable: true,
    dataTransfer: e.dataTransfer
  });
  if (e.target instanceof HTMLElement) {
    var _e$target$closest;
    // re-dispatch drag event on button so that pragmatic DnD can be triggered properly
    (_e$target$closest = e.target.closest('button')) === null || _e$target$closest === void 0 || _e$target$closest.dispatchEvent(dragEvent);
  }
};
var getNodeSpacingForPreview = function getNodeSpacingForPreview(node) {
  if (!node) {
    return spacingBetweenNodesForPreview['default'];
  }
  var nodeTypeName = node.type.name;
  if (nodeTypeName === 'heading') {
    return spacingBetweenNodesForPreview["heading".concat(node.attrs.level)] || spacingBetweenNodesForPreview['default'];
  }
  return spacingBetweenNodesForPreview[nodeTypeName] || spacingBetweenNodesForPreview['default'];
};
var getNodeMargins = function getNodeMargins(node) {
  if (!node) {
    return nodeMargins['default'];
  }
  var nodeTypeName = node.type.name;
  if (nodeTypeName === 'heading') {
    return nodeMargins["heading".concat(node.attrs.level)] || nodeMargins['default'];
  }
  return nodeMargins[nodeTypeName] || nodeMargins['default'];
};

// Kill switch OFF: omit `isOpen` so the reducer toggles per clicked column. ON: keep
// `isOpen: true` for legacy always-open. Centralised so all dispatch sites stay in sync.
var buildToggleLayoutColumnMenuMeta = function buildToggleLayoutColumnMenuMeta(anchorPos, openedViaKeyboard) {
  return _objectSpread(_objectSpread({
    anchorPos: anchorPos
  }, fg('platform_editor_layout_column_menu_kill_switch_1') ? {
    isOpen: true
  } : {}), {}, {
    openedViaKeyboard: openedViaKeyboard
  });
};
var getDragHandleAnchorReference = function getDragHandleAnchorReference(_ref) {
  var edge = _ref.edge,
    safeAnchorName = _ref.safeAnchorName;
  if (expValEquals('platform_editor_controls_reliable_anchor', 'isEnabled', true)) {
    return "anchor(".concat(safeAnchorName, " ").concat(edge, ", anchor(").concat(ACTIVE_DRAG_HANDLE_FALLBACK_ANCHOR_NAME, " ").concat(edge, "))");
  }
  return "anchor(".concat(safeAnchorName, " ").concat(edge, ")");
};
export var DragHandle = function DragHandle(_ref2) {
  var _api$blockControls7;
  var view = _ref2.view,
    api = _ref2.api,
    formatMessage = _ref2.formatMessage,
    getPos = _ref2.getPos,
    anchorName = _ref2.anchorName,
    nodeType = _ref2.nodeType,
    handleOptions = _ref2.handleOptions,
    anchorRectCache = _ref2.anchorRectCache;
  var buttonRef = useRef(null);
  var mouseDownRef = useRef(false);
  var _useState = useState(false),
    _useState2 = _slicedToArray(_useState, 2),
    dragHandleSelected = _useState2[0],
    setDragHandleSelected = _useState2[1];
  var _useState3 = useState(false),
    _useState4 = _slicedToArray(_useState3, 2),
    dragHandleDisabled = _useState4[0],
    setDragHandleDisabled = _useState4[1];
  var _useState5 = useState(768),
    _useState6 = _slicedToArray(_useState5, 2),
    blockCardWidth = _useState6[0],
    setBlockCardWidth = _useState6[1];
  var _useState7 = useState({
      display: 'none'
    }),
    _useState8 = _slicedToArray(_useState7, 2),
    positionStylesOld = _useState8[0],
    setPositionStylesOld = _useState8[1];
  // Tracks whether the initial position calculation has been performed at least once.
  // The reliable-anchor early-return optimisation must not fire before the first calculation,
  // otherwise positionStylesOld stays as { display: 'none' } and the handle is never shown.
  var hasCalculatedInitialPosition = useRef(false);
  var _useState9 = useState(Boolean(handleOptions === null || handleOptions === void 0 ? void 0 : handleOptions.isFocused)),
    _useState0 = _slicedToArray(_useState9, 2),
    isFocused = _useState0[0],
    setIsFocused = _useState0[1];
  var _useSharedPluginState = useSharedPluginStateWithSelector(api, ['featureFlags', 'selection', 'blockControls', 'interaction', 'userIntent'], function (states) {
      var _states$featureFlagsS, _states$selectionStat, _states$blockControls, _states$interactionSt, _states$userIntentSta;
      return {
        macroInteractionUpdates: (_states$featureFlagsS = states.featureFlagsState) === null || _states$featureFlagsS === void 0 ? void 0 : _states$featureFlagsS.macroInteractionUpdates,
        selection: (_states$selectionStat = states.selectionState) === null || _states$selectionStat === void 0 ? void 0 : _states$selectionStat.selection,
        isShiftDown: (_states$blockControls = states.blockControlsState) === null || _states$blockControls === void 0 ? void 0 : _states$blockControls.isShiftDown,
        interactionState: (_states$interactionSt = states.interactionState) === null || _states$interactionSt === void 0 ? void 0 : _states$interactionSt.interactionState,
        currentUserIntent: (_states$userIntentSta = states.userIntentState) === null || _states$userIntentSta === void 0 ? void 0 : _states$userIntentSta.currentUserIntent
      };
    }),
    macroInteractionUpdates = _useSharedPluginState.macroInteractionUpdates,
    selection = _useSharedPluginState.selection,
    isShiftDown = _useSharedPluginState.isShiftDown,
    interactionState = _useSharedPluginState.interactionState,
    currentUserIntent = _useSharedPluginState.currentUserIntent;
  var start = getPos();
  var isLayoutColumn = nodeType === 'layoutColumn';

  // Dynamically calculate if node is top-level based on current position
  var isTopLevelNodeValue = useMemo(function () {
    var pos = getPos();
    if (typeof pos === 'number') {
      var $pos = view.state.doc.resolve(pos);
      return ($pos === null || $pos === void 0 ? void 0 : $pos.parent.type.name) === 'doc';
    }
    return true;
  }, [getPos, view.state.doc]);
  useEffect(function () {
    // blockCard/datasource width is rendered correctly after this decoraton does. We need to observe for changes.
    if (nodeType === 'blockCard') {
      var dom = view.dom.querySelector("[".concat(getAnchorAttrName(), "=\"").concat(anchorName, "\"]"));
      var container = dom === null || dom === void 0 ? void 0 : dom.querySelector('.datasourceView-content-inner-wrap');
      if (container) {
        var resizeObserver = new ResizeObserver(function (entries) {
          var width = entries[0].contentBoxSize[0].inlineSize;
          setBlockCardWidth(width);
        });
        resizeObserver.observe(container);
        return function () {
          return resizeObserver.unobserve(container);
        };
      }
    }
  }, [anchorName, nodeType, view.dom]);
  useEffect(function () {
    if (!expValEqualsNoExposure('platform_editor_selection_toolbar_block_handle', 'isEnabled', true)) {
      return;
    }
    var unbind = bind(window, {
      type: 'mouseUp',
      listener: function listener() {
        return mouseDownRef.current = false;
      }
    });
    return function () {
      return unbind();
    };
  }, []);
  var handleMouseDown = useCallback(function () {
    mouseDownRef.current = true;
  }, []);
  var handleMouseUp = useCallback(function (e) {
    // Stop propagation so that for drag handles in nested scenarios the click is captured
    // and doesn't propagate to the edge of the element and trigger a node selection
    // on the parent element
    if (!expValEqualsNoExposure('platform_editor_selection_toolbar_block_handle', 'isEnabled', true)) {
      e.stopPropagation();
    }

    // Fixes bug where selection toolbar is blocked when mouse is released on drag handle
    if (mouseDownRef.current) {
      e.stopPropagation();
    }
  }, []);
  var handleOnClickNew = useCallback(function (e) {
    var _api$core;
    api === null || api === void 0 || (_api$core = api.core) === null || _api$core === void 0 || _api$core.actions.execute(function (_ref3) {
      var _selectionPreservatio, _api$analytics, _resolvedStartPos$nod, _api$blockControls, _api$blockControls2;
      var tr = _ref3.tr;
      var startPos = getPos();
      if (startPos === undefined) {
        return tr;
      }
      if (nodeType === 'layoutColumn' && expValEquals('platform_editor_layout_column_menu', 'isEnabled', true)) {
        tr.setMeta('toggleLayoutColumnMenu', buildToggleLayoutColumnMenuMeta(startPos, false));
      }
      var resolvedStartPos = tr.doc.resolve(startPos);
      var selection = ((_selectionPreservatio = selectionPreservationPluginKey.getState(view.state)) === null || _selectionPreservatio === void 0 ? void 0 : _selectionPreservatio.preservedSelection) || tr.selection;
      api === null || api === void 0 || (_api$analytics = api.analytics) === null || _api$analytics === void 0 || _api$analytics.actions.attachAnalyticsEvent({
        eventType: EVENT_TYPE.UI,
        action: ACTION.CLICKED,
        actionSubject: ACTION_SUBJECT.BUTTON,
        actionSubjectId: ACTION_SUBJECT_ID.ELEMENT_DRAG_HANDLE,
        attributes: {
          nodeDepth: resolvedStartPos.depth,
          nodeTypes: ((_resolvedStartPos$nod = resolvedStartPos.nodeAfter) === null || _resolvedStartPos$nod === void 0 ? void 0 : _resolvedStartPos$nod.type.name) || ''
        }
      })(tr);
      expandAndUpdateSelection({
        tr: tr,
        selection: selection,
        startPos: startPos,
        isShiftPressed: e.shiftKey,
        nodeType: nodeType,
        api: api
      });
      api === null || api === void 0 || (_api$blockControls = api.blockControls) === null || _api$blockControls === void 0 || _api$blockControls.commands.startPreservingSelection()({
        tr: tr
      });
      api === null || api === void 0 || (_api$blockControls2 = api.blockControls) === null || _api$blockControls2 === void 0 || _api$blockControls2.commands.toggleBlockMenu({
        anchorName: anchorName,
        openedViaKeyboard: false,
        triggerByNode: {
          nodeType: nodeType,
          pos: startPos,
          rootPos: tr.doc.resolve(startPos).before(1)
        }
      })({
        tr: tr
      });
      tr.setMeta('scrollIntoView', false);
      return tr;
    });
    view.focus();
  }, [api, view, getPos, nodeType, anchorName]);
  var handleKeyDownNew = useCallback(function (e) {
    // allow user to use spacebar to select the node
    if (e.key === 'Enter' || !e.repeat && e.key === ' ') {
      var _getDocument, _api$core2;
      if (((_getDocument = getDocument()) === null || _getDocument === void 0 ? void 0 : _getDocument.activeElement) !== buttonRef.current) {
        return;
      }
      e.preventDefault();
      e.stopPropagation();
      var startPos = getPos();
      api === null || api === void 0 || (_api$core2 = api.core) === null || _api$core2 === void 0 || _api$core2.actions.execute(function (_ref4) {
        var _selectionPreservatio2, _api$blockControls3, _api$blockControls4, _api$userIntent;
        var tr = _ref4.tr;
        if (startPos === undefined) {
          return tr;
        }
        var selection = ((_selectionPreservatio2 = selectionPreservationPluginKey.getState(view.state)) === null || _selectionPreservatio2 === void 0 ? void 0 : _selectionPreservatio2.preservedSelection) || tr.selection;
        expandAndUpdateSelection({
          tr: tr,
          selection: selection,
          startPos: startPos,
          isShiftPressed: e.shiftKey,
          nodeType: nodeType,
          api: api
        });
        api === null || api === void 0 || (_api$blockControls3 = api.blockControls) === null || _api$blockControls3 === void 0 || _api$blockControls3.commands.startPreservingSelection()({
          tr: tr
        });
        if (nodeType === 'layoutColumn' && expValEquals('platform_editor_layout_column_menu', 'isEnabled', true)) {
          tr.setMeta('toggleLayoutColumnMenu', buildToggleLayoutColumnMenuMeta(startPos, true));
        }
        var triggerByNode = {
          nodeType: nodeType,
          pos: startPos,
          rootPos: tr.doc.resolve(startPos).before(1)
        };
        api === null || api === void 0 || (_api$blockControls4 = api.blockControls) === null || _api$blockControls4 === void 0 || _api$blockControls4.commands.toggleBlockMenu({
          anchorName: anchorName,
          triggerByNode: triggerByNode,
          openedViaKeyboard: true
        })({
          tr: tr
        });
        api === null || api === void 0 || (_api$userIntent = api.userIntent) === null || _api$userIntent === void 0 || _api$userIntent.commands.setCurrentUserIntent('blockMenuOpen')({
          tr: tr
        });
        return tr;
      });
      view.focus();
    } else if (![e.altKey, e.ctrlKey, e.shiftKey].some(function (pressed) {
      return pressed;
    })) {
      // If not trying to press shortcut keys,
      // return focus to editor to resume editing from caret position
      view.focus();
    }
  }, [getPos, api, nodeType, anchorName, view]);
  useEffect(function () {
    var element = buttonRef.current;
    if (!element) {
      return;
    }
    return draggable({
      element: element,
      getInitialData: function getInitialData() {
        return {
          type: 'element',
          start: start
        };
      },
      onGenerateDragPreview: function onGenerateDragPreview(_ref5) {
        var _api$core3, _api$blockControls$sh;
        var nativeSetDragImage = _ref5.nativeSetDragImage;
        api === null || api === void 0 || (_api$core3 = api.core) === null || _api$core3 === void 0 || _api$core3.actions.execute(function (_ref6) {
          var tr = _ref6.tr;
          var handlePos = getPos();
          if (typeof handlePos !== 'number') {
            return tr;
          }
          var newHandlePosCheck = isHandleCorrelatedToSelection(view.state, tr.selection, handlePos);
          if (!tr.selection.empty && newHandlePosCheck) {
            var _api$blockControls5;
            api === null || api === void 0 || (_api$blockControls5 = api.blockControls) === null || _api$blockControls5 === void 0 || _api$blockControls5.commands.setMultiSelectPositions()({
              tr: tr
            });
          } else {
            tr = selectNode(tr, handlePos, nodeType, api);
          }
          return tr;
        });
        var startPos = getPos();
        var state = view.state;
        var doc = state.doc,
          selection = state.selection;
        var sliceFrom = selection.from;
        var sliceTo = selection.to;
        var mSelect = api === null || api === void 0 || (_api$blockControls$sh = api.blockControls.sharedState.currentState()) === null || _api$blockControls$sh === void 0 ? void 0 : _api$blockControls$sh.multiSelectDnD;
        if (mSelect) {
          var anchor = mSelect.anchor,
            head = mSelect.head;
          sliceFrom = Math.min(anchor, head);
          sliceTo = Math.max(anchor, head);
        }
        var expandedSlice = doc.slice(sliceFrom, sliceTo);
        var isDraggingMultiLine = startPos !== undefined && startPos >= sliceFrom && startPos < sliceTo && expandedSlice.content.childCount > 1;
        setCustomNativeDragPreview({
          getOffset: function getOffset() {
            if (!isDraggingMultiLine) {
              return {
                x: 0,
                y: 0
              };
            } else {
              // Calculate the offset of the preview container,
              // So when drag multiple nodes, the preview align with the position of the selected nodes
              var domAtPos = view.domAtPos.bind(view);
              var domElementsHeightBeforeHandle = 0;
              var nodesStartPos = [];
              var nodesEndPos = [];
              var activeNodeMarginTop = 0;
              for (var i = 0; i < expandedSlice.content.childCount; i++) {
                if (i === 0) {
                  var _expandedSlice$conten;
                  nodesStartPos[i] = sliceFrom;
                  nodesEndPos[i] = sliceFrom + (((_expandedSlice$conten = expandedSlice.content.maybeChild(i)) === null || _expandedSlice$conten === void 0 ? void 0 : _expandedSlice$conten.nodeSize) || 0);
                } else {
                  var _expandedSlice$conten2;
                  nodesStartPos[i] = nodesEndPos[i - 1];
                  nodesEndPos[i] = nodesStartPos[i] + (((_expandedSlice$conten2 = expandedSlice.content.maybeChild(i)) === null || _expandedSlice$conten2 === void 0 ? void 0 : _expandedSlice$conten2.nodeSize) || 0);
                }

                // when the node is before the handle, calculate the height of the node
                if (nodesEndPos[i] <= startPos) {
                  // eslint-disable-next-line @atlaskit/editor/no-as-casting
                  var currentNodeElement = findDomRefAtPos(nodesStartPos[i], domAtPos);
                  var maybeCurrentNode = expandedSlice.content.maybeChild(i);
                  var currentNodeSpacing = maybeCurrentNode ? getNodeMargins(maybeCurrentNode).top + getNodeMargins(maybeCurrentNode).bottom : 0;
                  domElementsHeightBeforeHandle = domElementsHeightBeforeHandle + currentNodeElement.offsetHeight + currentNodeSpacing;
                } else {
                  // when the node is after the handle, calculate the top margin of the active node
                  var maybeNextNode = expandedSlice.content.maybeChild(i);
                  activeNodeMarginTop = maybeNextNode ? getNodeMargins(maybeNextNode).top : 0;
                  break;
                }
              }
              return {
                x: 0,
                y: domElementsHeightBeforeHandle + activeNodeMarginTop
              };
            }
          },
          render: function render(_ref7) {
            var container = _ref7.container;
            var dom = view.dom.querySelector("[".concat(getAnchorAttrName(), "=\"").concat(anchorName, "\"]"));
            if (!dom) {
              return;
            }
            if (!isDraggingMultiLine) {
              return dragPreview(container, {
                dom: dom,
                nodeType: nodeType
              });
            } else {
              var domAtPos = view.domAtPos.bind(view);
              var previewContent = [];
              expandedSlice.content.descendants(function (node, pos) {
                // Get the dom element of the node
                //eslint-disable-next-line @atlaskit/editor/no-as-casting
                var nodeDomElement = findDomRefAtPos(sliceFrom + pos, domAtPos);
                var currentNodeSpacing = getNodeSpacingForPreview(node);
                previewContent.push({
                  dom: nodeDomElement,
                  nodeType: node.type.name,
                  nodeSpacing: currentNodeSpacing
                });
                return false; // Only iterate through the first level of nodes
              });
              return dragPreview(container, previewContent);
            }
          },
          nativeSetDragImage: nativeSetDragImage
        });
      },
      onDragStart: function onDragStart() {
        var _api$core4;
        if (start === undefined) {
          return;
        }
        api === null || api === void 0 || (_api$core4 = api.core) === null || _api$core4 === void 0 || _api$core4.actions.execute(function (_ref8) {
          var _api$blockControls$sh2, _api$blockControls6, _api$analytics2;
          var tr = _ref8.tr;
          var nodeTypes, hasSelectedMultipleNodes;
          var resolvedMovingNode = tr.doc.resolve(start);
          var maybeNode = resolvedMovingNode.nodeAfter;
          var mSelect = api === null || api === void 0 || (_api$blockControls$sh2 = api.blockControls.sharedState.currentState()) === null || _api$blockControls$sh2 === void 0 ? void 0 : _api$blockControls$sh2.multiSelectDnD;
          if (mSelect) {
            var attributes = getMultiSelectAnalyticsAttributes(tr, mSelect.anchor, mSelect.head);
            nodeTypes = attributes.nodeTypes;
            hasSelectedMultipleNodes = attributes.hasSelectedMultipleNodes;
          } else {
            nodeTypes = maybeNode === null || maybeNode === void 0 ? void 0 : maybeNode.type.name;
            hasSelectedMultipleNodes = false;
          }
          api === null || api === void 0 || (_api$blockControls6 = api.blockControls) === null || _api$blockControls6 === void 0 || _api$blockControls6.commands.setNodeDragged(getPos, anchorName, nodeType)({
            tr: tr
          });
          tr.setMeta('scrollIntoView', false);
          api === null || api === void 0 || (_api$analytics2 = api.analytics) === null || _api$analytics2 === void 0 || _api$analytics2.actions.attachAnalyticsEvent({
            eventType: EVENT_TYPE.UI,
            action: ACTION.DRAGGED,
            actionSubject: ACTION_SUBJECT.ELEMENT,
            actionSubjectId: ACTION_SUBJECT_ID.ELEMENT_DRAG_HANDLE,
            attributes: {
              nodeDepth: resolvedMovingNode.depth,
              nodeTypes: nodeTypes || '',
              hasSelectedMultipleNodes: hasSelectedMultipleNodes
            }
          })(tr);
          return tr;
        });
        view.focus();
      }
    });
  }, [anchorName, api, getPos, nodeType, start, view]);
  var calculatePositionOld = useCallback(function () {
    var _$pos$nodeAfter;
    var pos = getPos();
    var $pos = typeof pos === 'number' ? view.state.doc.resolve(pos) : undefined;
    var parentPos = $pos && $pos.depth ? $pos.before() : undefined;
    var node = parentPos !== undefined ? view.state.doc.nodeAt(parentPos) : undefined;
    var nodeTypeWithLevel = $pos !== null && $pos !== void 0 && (_$pos$nodeAfter = $pos.nodeAfter) !== null && _$pos$nodeAfter !== void 0 && _$pos$nodeAfter.isBlock ? getNodeTypeWithLevel($pos.nodeAfter) : nodeType;
    var parentNodeType = node === null || node === void 0 ? void 0 : node.type.name;
    var supportsAnchor = CSS.supports('top', "anchor(".concat(anchorName, " start)")) && CSS.supports('left', "anchor(".concat(anchorName, " start)"));
    var safeAnchorName = editorExperiment('platform_editor_controls', 'variant1') ? refreshAnchorName({
      getPos: getPos,
      view: view,
      anchorName: anchorName
    }) : anchorName;
    var dom = view.dom.querySelector("[".concat(getAnchorAttrName(), "=\"").concat(safeAnchorName, "\"]"));

    // Defence-in-depth guard: since the node decoration sets data-active-drag-handle on the
    // active node, we check for it directly. This is a cheap DOM attribute read (no reflow,
    // no style recalculation) and hides the control if the decoration hasn't been applied yet.
    if (expValEquals('platform_editor_controls_reliable_anchor', 'isEnabled', true) && !(dom !== null && dom !== void 0 && dom.hasAttribute(ACTIVE_DRAG_HANDLE_ATTR))) {
      return {
        display: 'none'
      };
    }
    var hasResizer = nodeType === 'table' || nodeType === 'mediaSingle';
    var isExtension = nodeType === 'extension' || nodeType === 'bodiedExtension' || nodeType === 'multiBodiedExtension' && expValEquals('confluence_native_tabs_experiment', 'isEnabled', true);
    var isBlockCard = nodeType === 'blockCard' && !!blockCardWidth;
    var isEmbedCard = nodeType === 'embedCard';
    var isMacroInteractionUpdates = macroInteractionUpdates && isExtension;
    var innerContainer = null;
    if (dom) {
      if (isEmbedCard) {
        innerContainer = dom.querySelector('.rich-media-item');
      } else if (hasResizer) {
        innerContainer = dom.querySelector('.resizer-item');
      } else if (isExtension) {
        innerContainer = dom.querySelector('.extension-container[data-layout]');
      } else if (isBlockCard) {
        //specific to datasource blockCard
        innerContainer = dom.querySelector('.datasourceView-content-inner-wrap');
      }
    }
    var isEdgeCase = (hasResizer || isExtension || isEmbedCard || isBlockCard) && innerContainer;
    var isSticky = shouldBeSticky(nodeType);
    if (supportsAnchor) {
      var anchorStart = getDragHandleAnchorReference({
        edge: 'start',
        safeAnchorName: safeAnchorName
      });
      var anchorTop = getDragHandleAnchorReference({
        edge: 'top',
        safeAnchorName: safeAnchorName
      });
      var anchorLeft = getDragHandleAnchorReference({
        edge: 'left',
        safeAnchorName: safeAnchorName
      });
      var anchorRight = getDragHandleAnchorReference({
        edge: 'right',
        safeAnchorName: safeAnchorName
      });
      var bottom = editorExperiment('platform_editor_controls', 'variant1') ? getControlBottomCSSValue(safeAnchorName, isSticky, isTopLevelNodeValue, isLayoutColumn, ACTIVE_DRAG_HANDLE_FALLBACK_ANCHOR_NAME) : {};
      return _objectSpread({
        left: isEdgeCase ? "calc(".concat(anchorStart, " + ").concat(getLeftPosition(dom, nodeType, innerContainer, isMacroInteractionUpdates, parentNodeType), ")") : editorExperiment('advanced_layouts', true) && isLayoutColumn ? "calc((".concat(anchorRight, " + ").concat(anchorLeft, ")/2 - ").concat(DRAG_HANDLE_HEIGHT / 2, "px)") : "calc(".concat(anchorStart, " - ").concat(DRAG_HANDLE_WIDTH, "px - ").concat(dragHandleGap(nodeType, parentNodeType), "px)"),
        top: editorExperiment('advanced_layouts', true) && isLayoutColumn ? "calc(".concat(anchorTop, " - ").concat(DRAG_HANDLE_WIDTH, "px)") : "calc(".concat(anchorStart, " + ").concat(topPositionAdjustment(nodeTypeWithLevel, (dom === null || dom === void 0 ? void 0 : dom.getAttribute('layout')) || ''), "px)")
      }, bottom);
    }
    var height = editorExperiment('platform_editor_controls', 'variant1') ? getControlHeightCSSValue(getNodeHeight(dom, safeAnchorName, anchorRectCache) || 0, isSticky, isTopLevelNodeValue, "".concat(DRAG_HANDLE_HEIGHT), isLayoutColumn) : {};
    return _objectSpread({
      left: isEdgeCase ? "calc(".concat((dom === null || dom === void 0 ? void 0 : dom.offsetLeft) || 0, "px + ").concat(getLeftPosition(dom, nodeType, innerContainer, isMacroInteractionUpdates, parentNodeType), ")") : getLeftPosition(dom, nodeType, innerContainer, isMacroInteractionUpdates, parentNodeType),
      top: getTopPosition(dom, nodeTypeWithLevel)
    }, height);
  }, [anchorName, getPos, view, nodeType, blockCardWidth, macroInteractionUpdates, anchorRectCache, isTopLevelNodeValue, isLayoutColumn]);
  var isReliableAnchorEnabled = expValEquals('platform_editor_controls_reliable_anchor', 'isEnabled', true);
  var docDepForReliableAnchor = isReliableAnchorEnabled ? view.state.doc : undefined;

  // Effect 1 (reliable-anchor ON): fires when non-doc deps change (e.g. blockCardWidth,
  // anchorRectCache, macroInteractionUpdates, isTopLevelNodeValue, isLayoutColumn via
  // calculatePositionOld) — always recalculates without the doc guard, so non-doc position
  // changes are never incorrectly skipped.
  useEffect(function () {
    if (!isReliableAnchorEnabled) {
      return;
    }
    var cleanUpTransitionListener;
    if (nodeType === 'extension' || nodeType === 'embedCard') {
      var dom = view.dom.querySelector("[".concat(getAnchorAttrName(), "=\"").concat(anchorName, "\"]"));
      if (!dom) {
        return;
      }
      cleanUpTransitionListener = bind(dom, {
        type: 'transitionend',
        listener: function listener() {
          setPositionStylesOld(calculatePositionOld());
        }
      });
    }
    var calcPos = requestAnimationFrame(function () {
      setPositionStylesOld(calculatePositionOld());
      hasCalculatedInitialPosition.current = true;
    });
    return function () {
      var _cleanUpTransitionLis;
      cancelAnimationFrame(calcPos);
      (_cleanUpTransitionLis = cleanUpTransitionListener) === null || _cleanUpTransitionLis === void 0 || _cleanUpTransitionLis();
    };
  }, [isReliableAnchorEnabled, calculatePositionOld, view.dom, anchorName, nodeType]);

  // Effect 2 (reliable-anchor ON): fires when the doc changes — carries the DOM-attribute
  // guard to skip recalc when a drag handle is active (pure keystroke during drag).
  // Effect (reliable-anchor OFF): single combined effect, original behaviour.
  useEffect(function () {
    if (isReliableAnchorEnabled) {
      // Doc-change only effect: apply the DOM-attribute guard.
      // React's dep comparison already ensures this only runs when docDepForReliableAnchor
      // (i.e. view.state.doc) changed, so no manual ref tracking is needed.
      if (!hasCalculatedInitialPosition.current) {
        return;
      }
      var _calcPos = requestAnimationFrame(function () {
        var dom = view.dom.querySelector("[".concat(getAnchorAttrName(), "=\"").concat(anchorName, "\"]"));
        if (dom !== null && dom !== void 0 && dom.hasAttribute(ACTIVE_DRAG_HANDLE_ATTR)) {
          return;
        }
        setPositionStylesOld(calculatePositionOld());
      });
      return function () {
        cancelAnimationFrame(_calcPos);
      };
    }

    // reliable-anchor OFF: original single-effect behaviour (no guard).
    var cleanUpTransitionListener;
    if (nodeType === 'extension' || nodeType === 'embedCard') {
      var dom = view.dom.querySelector("[".concat(getAnchorAttrName(), "=\"").concat(anchorName, "\"]"));
      if (!dom) {
        return;
      }
      cleanUpTransitionListener = bind(dom, {
        type: 'transitionend',
        listener: function listener() {
          setPositionStylesOld(calculatePositionOld());
        }
      });
    }
    var calcPos = requestAnimationFrame(function () {
      setPositionStylesOld(calculatePositionOld());
      hasCalculatedInitialPosition.current = true;
    });
    return function () {
      var _cleanUpTransitionLis2;
      cancelAnimationFrame(calcPos);
      (_cleanUpTransitionLis2 = cleanUpTransitionListener) === null || _cleanUpTransitionLis2 === void 0 || _cleanUpTransitionLis2();
    };
  }, [isReliableAnchorEnabled, calculatePositionOld, view.dom, anchorName, nodeType, docDepForReliableAnchor]);
  var isHandleShown = positionStylesOld.display !== 'none';
  useEffect(function () {
    if (isExperimentEnabled('platform_editor_react19_migration')) {
      return;
    }
    if (handleOptions !== null && handleOptions !== void 0 && handleOptions.isFocused && buttonRef.current) {
      var id = requestAnimationFrame(function () {
        var _buttonRef$current;
        (_buttonRef$current = buttonRef.current) === null || _buttonRef$current === void 0 || _buttonRef$current.focus();
      });
      return function () {
        cancelAnimationFrame(id);
        view.focus();
      };
    }
  }, [buttonRef, handleOptions === null || handleOptions === void 0 ? void 0 : handleOptions.isFocused, view]);
  useEffect(function () {
    if (!isExperimentEnabled('platform_editor_react19_migration')) {
      return;
    }
    if (!(handleOptions !== null && handleOptions !== void 0 && handleOptions.isFocused && isHandleShown && buttonRef.current)) {
      return;
    }

    // isHandleShown means the handle has been positioned, but an ancestor can still be
    // visibility:hidden or display:none for a frame, in which case focus() is a silent no-op.
    // Focus now, and if activeElement shows it did not land, retry on the next few frames.
    var rafId;
    var attempts = 0;
    var MAX_ATTEMPTS = 5;
    var _focusHandle = function focusHandle() {
      var _getDocument2;
      var button = buttonRef.current;
      if (!button) {
        return;
      }
      button.focus();
      // getDocument() is the same document handleKeyDownNew reads activeElement from, so
      // this confirms Space/Enter will act on the handle rather than silently no-op.
      if (((_getDocument2 = getDocument()) === null || _getDocument2 === void 0 ? void 0 : _getDocument2.activeElement) === button || attempts >= MAX_ATTEMPTS) {
        return;
      }
      attempts++;
      rafId = requestAnimationFrame(_focusHandle);
    };
    _focusHandle();
    return function () {
      if (rafId !== undefined) {
        cancelAnimationFrame(rafId);
      }
      view.focus();
    };
  }, [buttonRef, handleOptions === null || handleOptions === void 0 ? void 0 : handleOptions.isFocused, view, isHandleShown]);
  useEffect(function () {
    if (typeof start !== 'number' || !selection) {
      return;
    }
    setDragHandleSelected(isHandleCorrelatedToSelection(view.state, selection, start));
  }, [start, selection, view]);
  useEffect(function () {
    var _api$blockControls$sh3;
    if (isShiftDown === undefined || view.state.selection.empty || !fg('platform_editor_elements_dnd_shift_click_select')) {
      return;
    }
    var mSelect = api === null || api === void 0 || (_api$blockControls$sh3 = api.blockControls.sharedState.currentState()) === null || _api$blockControls$sh3 === void 0 ? void 0 : _api$blockControls$sh3.multiSelectDnD;
    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;
    var isLayoutColumnMenuEnabled = expValEquals('platform_editor_layout_column_menu', 'isEnabled', true);
    if (isShiftDown && !(isLayoutColumnMenuEnabled && isLayoutColumn) && (!isTopLevelNodeValue || isTopLevelNodeValue && $anchor.depth > DRAG_HANDLE_MAX_SHIFT_CLICK_DEPTH)) {
      setDragHandleDisabled(true);
    } else {
      setDragHandleDisabled(false);
    }
  }, [api === null || api === void 0 || (_api$blockControls7 = api.blockControls) === null || _api$blockControls7 === void 0 ? void 0 : _api$blockControls7.sharedState, isLayoutColumn, isShiftDown, isTopLevelNodeValue, view]);
  var dragHandleMessage = formatMessage(blockControlsMessages.dragToMoveClickToOpen, {
    br: jsx("br", null)
  });

  // Create a string version for aria-label
  var dragHandleAriaLabel = formatMessage(blockControlsMessages.dragToMoveClickToOpen, {
    br: ' '
  });
  var helpDescriptors = isTopLevelNodeValue ? [{
    description: dragHandleMessage
  }, {
    description: formatMessage(blockControlsMessages.moveUp),
    keymap: dragToMoveUp
  }, {
    description: formatMessage(blockControlsMessages.moveDown),
    keymap: dragToMoveDown
  }, {
    description: formatMessage(blockControlsMessages.moveLeft),
    keymap: dragToMoveLeft
  }, {
    description: formatMessage(blockControlsMessages.moveRight),
    keymap: dragToMoveRight
  }] : [{
    description: dragHandleMessage
  }, {
    description: formatMessage(blockControlsMessages.moveUp),
    keymap: dragToMoveUp
  }, {
    description: formatMessage(blockControlsMessages.moveDown),
    keymap: dragToMoveDown
  }];
  var isParentNodeOfTypeLayout;
  if (!isTopLevelNodeValue) {
    var pos = getPos();
    if (typeof pos === 'number') {
      var _$pos$parent;
      var $pos = view.state.doc.resolve(pos);
      isParentNodeOfTypeLayout = ($pos === null || $pos === void 0 || (_$pos$parent = $pos.parent) === null || _$pos$parent === void 0 ? void 0 : _$pos$parent.type.name) === 'layoutColumn';
    }
    if (isParentNodeOfTypeLayout) {
      helpDescriptors = [].concat(_toConsumableArray(helpDescriptors), [{
        description: formatMessage(blockControlsMessages.moveLeft),
        keymap: dragToMoveLeft
      }, {
        description: formatMessage(blockControlsMessages.moveRight),
        keymap: dragToMoveRight
      }]);
    }
  }

  // When advanced layout is on, layout column drag handle show only show 'Drag to move', no shortcuts
  if (editorExperiment('advanced_layouts', true) && nodeType === 'layoutColumn') {
    helpDescriptors = [{
      description: formatMessage(blockControlsMessages.dragToRearrange)
    }, {
      description: formatMessage(blockControlsMessages.moveUp),
      keymap: dragToMoveUp
    }, {
      description: formatMessage(blockControlsMessages.moveDown),
      keymap: dragToMoveDown
    }, {
      description: formatMessage(blockControlsMessages.moveLeft),
      keymap: dragToMoveLeft
    }, {
      description: formatMessage(blockControlsMessages.moveRight),
      keymap: dragToMoveRight
    }];
  }
  if (editorExperiment('platform_editor_controls', 'variant1')) {
    helpDescriptors = [{
      description: dragHandleMessage
    }];
  }
  var message = helpDescriptors.map(function (descriptor) {
    return descriptor.keymap ? [descriptor.description, getAriaKeyshortcuts(descriptor.keymap)] : [descriptor.description];
  }).join('. ');
  var handleOnDrop = function handleOnDrop(event) {
    event.stopPropagation();
  };
  var hasHadInteraction = interactionState !== 'hasNotHadInteraction';
  var browser = getBrowserInfo();
  var renderButton = function renderButton() {
    return (
      // eslint-disable-next-line @atlaskit/design-system/no-html-button
      jsx("button", {
        type: "button",
        css: [editorExperiment('platform_editor_controls', 'variant1') ? dragHandleButtonStyles : dragHandleButtonStylesOld, editorExperiment('platform_editor_controls', 'variant1') && dragHandleColor,
        // ED-26266: Fixed the drag handle highlight when selecting multiple line in Firefox
        // See https://product-fabric.atlassian.net/browse/ED-26266
        browser.gecko && dragHandleMultiLineSelectionFixFirefox, editorExperiment('advanced_layouts', true) && isLayoutColumn && layoutColumnDragHandleStyles, dragHandleSelected && hasHadInteraction && selectedStyles, editorExperiment('platform_editor_preview_panel_responsiveness', true) && editorExperiment('platform_editor_controls', 'control') && dragHandleButtonSmallScreenStyles, isFocused && keyboardFocusedDragHandleStyles, focusedStyles, dragHandleButtonScaledStyles],
        ref: buttonRef
        // eslint-disable-next-line @atlaskit/ui-styling-standard/enforce-style-prop -- Ignored via go/DSP-18766
        ,
        style: !editorExperiment('platform_editor_controls', 'variant1') ? positionStylesOld : {},
        onMouseDown: expValEqualsNoExposure('platform_editor_selection_toolbar_block_handle', 'isEnabled', true) ? handleMouseDown : undefined,
        onMouseUp: handleMouseUp,
        onClick: handleOnClickNew,
        onKeyDown: handleKeyDownNew
        // eslint-disable-next-line @atlaskit/design-system/no-direct-use-of-web-platform-drag-and-drop
        ,
        onDrop: handleOnDrop,
        disabled: dragHandleDisabled,
        "data-editor-block-ctrl-drag-handle": true,
        "data-blocks-drag-handle": fg('confluence_remix_button_right_side_block_fg') || undefined,
        "data-testid": "block-ctrl-drag-handle",
        "aria-label": dragHandleAriaLabel,
        onBlur: function onBlur() {
          setIsFocused(false);
          var pos = getPos();
          if (pos !== undefined) {
            var _api$core5;
            api === null || api === void 0 || (_api$core5 = api.core) === null || _api$core5 === void 0 || _api$core5.actions.execute(function (_ref9) {
              var tr = _ref9.tr;
              tr.setMeta(key, {
                activeNode: {
                  pos: pos,
                  anchorName: anchorName,
                  nodeType: nodeType,
                  handleOptions: {
                    isFocused: false
                  }
                }
              });
              return tr;
            });
          }
        }
      }, jsx(Box, {
        xcss: iconWrapperStyles
        // eslint-disable-next-line @atlaskit/design-system/no-direct-use-of-web-platform-drag-and-drop
        ,
        onDragStart: handleIconDragStart
      }, shouldUseNestedDragHandleIcon(isTopLevelNodeValue, isLayoutColumn) ? jsx(DragHandleNestedIcon, null) : jsx(DragHandleVerticalIcon, {
        spacing: "spacious",
        label: "",
        size: "small"
      })))
    );
  };
  var stickyWithTooltip = function stickyWithTooltip() {
    return jsx(Box
    // eslint-disable-next-line @atlaskit/ui-styling-standard/enforce-style-prop
    , {
      style: positionStylesOld
      // eslint-disable-next-line @atlassian/perf-linting/no-unstable-inline-props -- Ignored via go/ees017 (to be fixed)
      ,
      xcss: [dragHandleContainerStyles],
      as: "span",
      testId: "block-ctrl-drag-handle-container"
    }, jsx("span", {
      css: [tooltipContainerStyles, shouldMaskNodeControls(nodeType, isTopLevelNodeValue) && (expValEquals('platform_editor_table_sticky_header_improvements', 'cohort', 'test_with_overflow') ? tooltipContainerStylesImprovedStickyHeaderWithMask : tooltipContainerStylesStickyHeaderWithMask), !shouldMaskNodeControls(nodeType, isTopLevelNodeValue) && tooltipContainerStylesStickyHeaderWithoutMask]
    }, jsx(Tooltip, {
      content: tooltipContent,
      ignoreTooltipPointerEvents: true,
      position: 'top',
      tag: fg('platform-dst-top-layer-tooltip') ? 'span' : 'div'
      // eslint-disable-next-line @atlassian/perf-linting/no-unstable-inline-props -- Ignored via go/ees017 (to be fixed)
      ,
      onShow: function onShow() {
        var _api$accessibilityUti;
        api === null || api === void 0 || (_api$accessibilityUti = api.accessibilityUtils) === null || _api$accessibilityUti === void 0 || _api$accessibilityUti.actions.ariaNotify(message, {
          priority: 'important'
        });
      }
    }, jsx("span", {
      css: [shouldMaskNodeControls(nodeType, isTopLevelNodeValue) && buttonWrapperStylesNoBackground, buttonWrapperStylesPatch]
    }, renderButton()))));
  };
  var stickyWithoutTooltip = function stickyWithoutTooltip() {
    return jsx(Box
    // eslint-disable-next-line @atlaskit/ui-styling-standard/enforce-style-prop
    , {
      style: positionStylesOld
      // eslint-disable-next-line @atlassian/perf-linting/no-unstable-inline-props -- Ignored via go/ees017 (to be fixed)
      ,
      xcss: [dragHandleContainerStyles],
      as: "span",
      testId: "block-ctrl-drag-handle-container"
    }, jsx("span", {
      css: [tooltipContainerStyles, shouldMaskNodeControls(nodeType, isTopLevelNodeValue) && tooltipContainerStylesStickyHeaderWithMask, !shouldMaskNodeControls(nodeType, isTopLevelNodeValue) && tooltipContainerStylesStickyHeaderWithoutMask]
    }, jsx("span", {
      css: [shouldMaskNodeControls(nodeType, isTopLevelNodeValue) && buttonWrapperStylesNoBackground, buttonWrapperStylesPatch]
    }, renderButton())));
  };
  var buttonWithTooltip = function buttonWithTooltip() {
    return jsx(Tooltip, {
      content: tooltipContent,
      ignoreTooltipPointerEvents: true
      // eslint-disable-next-line @atlassian/perf-linting/no-unstable-inline-props -- Ignored via go/ees017 (to be fixed)
      ,
      onShow: function onShow() {
        var _api$accessibilityUti2;
        api === null || api === void 0 || (_api$accessibilityUti2 = api.accessibilityUtils) === null || _api$accessibilityUti2 === void 0 || _api$accessibilityUti2.actions.ariaNotify(message, {
          priority: 'important'
        });
      }
    }, renderButton());
  };
  var tooltipContent = isLayoutColumn && expValEquals('platform_editor_layout_column_menu', 'isEnabled', true) && currentUserIntent === 'layoutColumnMenuPopupOpen' ? null : jsx(TooltipContentWithMultipleShortcuts, {
    helpDescriptors: helpDescriptors
  });
  var isTooltip = !dragHandleDisabled;
  var stickyRender = isTooltip ? stickyWithTooltip() : stickyWithoutTooltip();
  var render = isTooltip ? buttonWithTooltip() : renderButton();
  return editorExperiment('platform_editor_controls', 'variant1') ? stickyRender : render;
};
export var DragHandleWithVisibility = function DragHandleWithVisibility(_ref0) {
  var view = _ref0.view,
    api = _ref0.api,
    formatMessage = _ref0.formatMessage,
    getPos = _ref0.getPos,
    anchorName = _ref0.anchorName,
    nodeType = _ref0.nodeType,
    handleOptions = _ref0.handleOptions,
    isTopLevelNode = _ref0.isTopLevelNode,
    anchorRectCache = _ref0.anchorRectCache;
  var rightSideControlsEnabled = useSharedPluginStateWithSelector(api, ['blockControls'], function (states) {
    var _states$blockControls2, _states$blockControls3;
    return {
      rightSideControlsEnabled: (_states$blockControls2 = (_states$blockControls3 = states.blockControlsState) === null || _states$blockControls3 === void 0 ? void 0 : _states$blockControls3.rightSideControlsEnabled) !== null && _states$blockControls2 !== void 0 ? _states$blockControls2 : false
    };
  }).rightSideControlsEnabled;
  // Layout column drag handles sit at the top-centre of each column, not on a left/right edge.
  // Don't restrict by hoverSide for layout columns — the drag handle should always be visible
  // when hovering anywhere over the column, regardless of which side of the layoutSection the
  // column is on. (The right-side remix button is a separate node decoration and is unaffected.)
  var isLayoutColumn = nodeType === 'layoutColumn';

  // Skip the right-side controlSide restriction for non-top-level nodes. The restriction exists
  // to avoid showing the drag handle and remix button simultaneously — but remix only applies to
  // top-level nodes, so non-top-level nodes should never be restricted.
  // Gated behind platform_editor_controls_reliable_anchor.
  var skipControlSideRestriction = expValEquals('platform_editor_controls_reliable_anchor', 'isEnabled', true) && isTopLevelNode === false;
  return jsx(VisibilityContainer, {
    api: api,
    controlSide: !isLayoutColumn && !skipControlSideRestriction && rightSideControlsEnabled ? 'left' : undefined,
    forceVisibleOnMouseOut: !!(handleOptions !== null && handleOptions !== void 0 && handleOptions.isFocused),
    shouldUseDisplayContents: isLayoutColumn && fg('platform-dst-top-layer-tooltip')
  }, jsx(DragHandle, {
    view: view,
    api: api,
    formatMessage: formatMessage,
    getPos: getPos,
    anchorName: anchorName,
    nodeType: nodeType,
    handleOptions: handleOptions,
    anchorRectCache: anchorRectCache
  }));
};