@atlaskit/editor-plugin-block-controls
Version:
Block controls plugin for @atlaskit/editor-core
916 lines (907 loc) • 65.2 kB
JavaScript
/**
* @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 { TextSelection } from '@atlaskit/editor-prosemirror/state';
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 { fg } from '@atlaskit/platform-feature-flags';
import { draggable } from '@atlaskit/pragmatic-drag-and-drop/element/adapter';
import { setCustomNativeDragPreview } from '@atlaskit/pragmatic-drag-and-drop/element/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 { expValEquals } from '@atlaskit/tmp-editor-statsig/exp-val-equals';
import { expValEqualsNoExposure } from '@atlaskit/tmp-editor-statsig/exp-val-equals-no-exposure';
import { editorExperiment } from '@atlaskit/tmp-editor-statsig/experiments';
import Tooltip from '@atlaskit/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 { alignAnchorHeadInDirectionOfPos, expandSelectionHeadToNodeAtPos } from '../pm-plugins/utils/selection';
import { 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 { refreshAnchorName } from './utils/anchor-name';
import { getAnchorAttrName } from './utils/dom-attr-name';
import { VisibilityContainer } from './visibility-container';
const iconWrapperStyles = xcss({
display: 'flex',
justifyContent: 'center',
alignItems: 'center'
});
const buttonWrapperStyles = 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) &': {
background: `linear-gradient(to bottom, ${"var(--ds-surface, #FFFFFF)"} 90%, transparent)`,
marginBottom: "var(--ds-space-negative-200, -16px)",
paddingBottom: "var(--ds-space-200, 16px)",
marginTop: "var(--ds-space-negative-400, -32px)",
paddingTop: `calc(${"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) &': {
background: `linear-gradient(to bottom, ${"var(--ds-surface, #FFFFFF)"} 90%, transparent)`,
marginBottom: "var(--ds-space-negative-200, -16px)",
paddingBottom: "var(--ds-space-200, 16px)",
marginTop: "var(--ds-space-negative-400, -32px)",
paddingTop: `calc(${"var(--ds-space-400, 32px)"} - 1px)`,
marginRight: "var(--ds-space-negative-150, -12px)",
paddingRight: "var(--ds-space-150, 12px)",
boxSizing: 'border-box'
}
});
const 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
const dragHandleColor = css({
color: "var(--ds-icon-subtle, #505258)"
});
const 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, #17171708)"
}
});
// 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
const 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)
});
const dragHandleButtonSmallScreenStyles = css({
// eslint-disable-next-line @atlaskit/ui-styling-standard/no-container-queries, @atlaskit/ui-styling-standard/no-imported-style-values, @atlaskit/ui-styling-standard/no-unsafe-values
[`@container editor-area (max-width: ${akEditorFullPageNarrowBreakout}px)`]: {
opacity: 0,
visibility: 'hidden'
}
});
const 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)"} solid ${"var(--ds-border-focused, #4688EC)"}`
},
'&:disabled': {
color: "var(--ds-icon-disabled, #080F214A)",
backgroundColor: 'transparent'
},
'&:hover:disabled': {
backgroundColor: "var(--ds-background-disabled, #17171708)"
}
});
const focusedStylesOld = css({
'&:focus': {
outline: `${"var(--ds-border-width-focused, 2px)"} solid ${"var(--ds-border-focused, #4688EC)"}`
}
});
const focusedStyles = css({
'&:focus-visible': {
outline: `${"var(--ds-border-width-focused, 2px)"} solid ${"var(--ds-border-focused, #4688EC)"}`
}
});
const keyboardFocusedDragHandleStyles = css({
outline: `${"var(--ds-border-width-focused, 2px)"} solid ${"var(--ds-border-focused, #4688EC)"}`
});
const dragHandleContainerStyles = xcss({
position: 'absolute',
boxSizing: 'border-box'
});
const tooltipContainerStyles = css({
// eslint-disable-next-line @atlaskit/ui-styling-standard/no-imported-style-values, @atlaskit/ui-styling-standard/no-unsafe-values
bottom: `-${STICKY_CONTROLS_TOP_MARGIN}px`,
position: 'sticky',
display: 'block',
zIndex: 100 // card = 100
});
const tooltipContainerStylesStickyHeaderWithMask = css({
// eslint-disable-next-line @atlaskit/ui-styling-standard/no-imported-style-values, @atlaskit/ui-styling-standard/no-unsafe-values
top: `${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'
}
});
const tooltipContainerStylesImprovedStickyHeaderWithMask = css({
// eslint-disable-next-line @atlaskit/ui-styling-standard/no-imported-style-values, @atlaskit/ui-styling-standard/no-unsafe-values
top: `${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: `${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: `${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: `${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: `${STICKY_CONTROLS_TOP_MARGIN_FOR_STICKY_HEADER}px`
}
});
const tooltipContainerStylesStickyHeaderWithoutMask = css({
// eslint-disable-next-line @atlaskit/ui-styling-standard/no-imported-style-values, @atlaskit/ui-styling-standard/no-unsafe-values
top: `${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
}
});
const dragHandleMultiLineSelectionFixFirefox = css({
'&::selection': {
backgroundColor: 'transparent'
}
});
const layoutColumnDragHandleStyles = css({
transform: 'rotate(90deg)'
});
const 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
const handleIconDragStart = e => {
const browser = getBrowserInfo();
if (!browser.chrome) {
return;
}
// prevent dragStart handler triggered by icon
e.stopPropagation();
const 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 ? void 0 : _e$target$closest.dispatchEvent(dragEvent);
}
};
const getNodeSpacingForPreview = node => {
if (!node) {
return spacingBetweenNodesForPreview['default'];
}
const nodeTypeName = node.type.name;
if (nodeTypeName === 'heading') {
return spacingBetweenNodesForPreview[`heading${node.attrs.level}`] || spacingBetweenNodesForPreview['default'];
}
return spacingBetweenNodesForPreview[nodeTypeName] || spacingBetweenNodesForPreview['default'];
};
const getNodeMargins = node => {
if (!node) {
return nodeMargins['default'];
}
const nodeTypeName = node.type.name;
if (nodeTypeName === 'heading') {
return nodeMargins[`heading${node.attrs.level}`] || nodeMargins['default'];
}
return nodeMargins[nodeTypeName] || nodeMargins['default'];
};
export const DragHandle = ({
view,
api,
formatMessage,
getPos,
anchorName,
nodeType,
handleOptions,
isTopLevelNode = true,
anchorRectCache
}) => {
var _api$core4;
const buttonRef = useRef(null);
const mouseDownRef = useRef(false);
const [dragHandleSelected, setDragHandleSelected] = useState(false);
const [dragHandleDisabled, setDragHandleDisabled] = useState(false);
const [blockCardWidth, setBlockCardWidth] = useState(768);
const [recalculatePosition, setRecalculatePosition] = useState(false);
const [positionStylesOld, setPositionStylesOld] = useState({
display: 'none'
});
const [isFocused, setIsFocused] = useState(Boolean(handleOptions === null || handleOptions === void 0 ? void 0 : handleOptions.isFocused));
const {
macroInteractionUpdates,
selection,
isShiftDown,
interactionState
} = useSharedPluginStateWithSelector(api, ['featureFlags', 'selection', 'blockControls', 'interaction'], states => {
var _states$featureFlagsS, _states$selectionStat, _states$blockControls, _states$interactionSt;
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
};
});
const start = getPos();
const isLayoutColumn = nodeType === 'layoutColumn';
const isMultiSelect = editorExperiment('platform_editor_element_drag_and_drop_multiselect', true);
// Dynamically calculate if node is top-level based on current position (gated by experiment)
const isTopLevelNodeDynamic = useMemo(() => {
if (!expValEquals('platform_editor_nested_drag_handle_icon', 'isEnabled', true)) {
return isTopLevelNode;
}
const pos = getPos();
if (typeof pos === 'number') {
const $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, isTopLevelNode]);
// Use the dynamic value when experiment is on, otherwise use the prop
// When cleaning up the experiment, you can safely remove the isTopLevelNode as an prop and
// just rely on the dynamic value (rename it to isTopLevelNode for simplicitiy)
const isTopLevelNodeValue = expValEquals('platform_editor_nested_drag_handle_icon', 'isEnabled', true) ? isTopLevelNodeDynamic : isTopLevelNode;
useEffect(() => {
if (editorExperiment('platform_editor_block_control_optimise_render', true)) {
return;
}
// blockCard/datasource width is rendered correctly after this decoraton does. We need to observe for changes.
if (nodeType === 'blockCard') {
const dom = view.dom.querySelector(`[${getAnchorAttrName()}="${anchorName}"]`);
const container = dom === null || dom === void 0 ? void 0 : dom.querySelector('.datasourceView-content-inner-wrap');
if (container) {
const resizeObserver = new ResizeObserver(entries => {
const width = entries[0].contentBoxSize[0].inlineSize;
setBlockCardWidth(width);
});
resizeObserver.observe(container);
return () => resizeObserver.unobserve(container);
}
}
}, [anchorName, nodeType, view.dom]);
useEffect(() => {
if (!expValEqualsNoExposure('platform_editor_selection_toolbar_block_handle', 'isEnabled', true)) {
return;
}
const unbind = bind(window, {
type: 'mouseUp',
listener: () => mouseDownRef.current = false
});
return () => unbind();
}, []);
const handleMouseDown = useCallback(() => {
mouseDownRef.current = true;
}, []);
const handleMouseUp = useCallback(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();
}
}, []);
const handleOnClickNew = useCallback(e => {
var _api$core;
api === null || api === void 0 ? void 0 : (_api$core = api.core) === null || _api$core === void 0 ? void 0 : _api$core.actions.execute(({
tr
}) => {
var _selectionPreservatio, _api$analytics, _resolvedStartPos$nod, _api$blockControls, _api$blockControls2;
const startPos = getPos();
if (startPos === undefined) {
return tr;
}
const resolvedStartPos = tr.doc.resolve(startPos);
const selection = ((_selectionPreservatio = selectionPreservationPluginKey.getState(view.state)) === null || _selectionPreservatio === void 0 ? void 0 : _selectionPreservatio.preservedSelection) || tr.selection;
api === null || api === void 0 ? void 0 : (_api$analytics = api.analytics) === null || _api$analytics === void 0 ? 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,
nodeType: ((_resolvedStartPos$nod = resolvedStartPos.nodeAfter) === null || _resolvedStartPos$nod === void 0 ? void 0 : _resolvedStartPos$nod.type.name) || ''
}
})(tr);
expandAndUpdateSelection({
tr,
selection,
startPos,
isShiftPressed: e.shiftKey,
nodeType,
api
});
api === null || api === void 0 ? void 0 : (_api$blockControls = api.blockControls) === null || _api$blockControls === void 0 ? void 0 : _api$blockControls.commands.startPreservingSelection()({
tr
});
api === null || api === void 0 ? void 0 : (_api$blockControls2 = api.blockControls) === null || _api$blockControls2 === void 0 ? void 0 : _api$blockControls2.commands.toggleBlockMenu({
anchorName,
openedViaKeyboard: false,
triggerByNode: editorExperiment('platform_synced_block', true) ? {
nodeType,
pos: startPos,
rootPos: tr.doc.resolve(startPos).before(1)
} : undefined
})({
tr
});
tr.setMeta('scrollIntoView', false);
return tr;
});
view.focus();
}, [api, view, getPos, nodeType, anchorName]);
const handleOnClick = useCallback(e => {
var _api$core2;
if (!isMultiSelect) {
setDragHandleSelected(!dragHandleSelected);
}
api === null || api === void 0 ? void 0 : (_api$core2 = api.core) === null || _api$core2 === void 0 ? void 0 : _api$core2.actions.execute(({
tr
}) => {
var _api$blockControls$sh, _api$analytics2;
const startPos = getPos();
if (startPos === undefined) {
return tr;
}
const mSelect = api === null || api === void 0 ? void 0 : (_api$blockControls$sh = api.blockControls.sharedState.currentState()) === null || _api$blockControls$sh === void 0 ? void 0 : _api$blockControls$sh.multiSelectDnD;
const $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;
if (!isMultiSelect || tr.selection.empty || !e.shiftKey) {
tr = selectNode(tr, startPos, nodeType, api);
} else if (isTopLevelNodeValue && $anchor.depth <= DRAG_HANDLE_MAX_SHIFT_CLICK_DEPTH && e.shiftKey && fg('platform_editor_elements_dnd_shift_click_select')) {
var _api$blockControls3;
const alignAnchorHeadToSel = alignAnchorHeadInDirectionOfPos(tr.selection, startPos);
const selectionWithExpandedHead = expandSelectionHeadToNodeAtPos(alignAnchorHeadToSel, startPos);
tr.setSelection(selectionWithExpandedHead);
api === null || api === void 0 ? void 0 : (_api$blockControls3 = api.blockControls) === null || _api$blockControls3 === void 0 ? void 0 : _api$blockControls3.commands.setMultiSelectPositions()({
tr
});
}
const resolvedMovingNode = tr.doc.resolve(startPos);
const maybeNode = resolvedMovingNode.nodeAfter;
tr.setMeta('scrollIntoView', false);
api === null || api === void 0 ? void 0 : (_api$analytics2 = api.analytics) === null || _api$analytics2 === void 0 ? void 0 : _api$analytics2.actions.attachAnalyticsEvent({
eventType: EVENT_TYPE.UI,
action: ACTION.CLICKED,
actionSubject: ACTION_SUBJECT.BUTTON,
actionSubjectId: ACTION_SUBJECT_ID.ELEMENT_DRAG_HANDLE,
attributes: {
nodeDepth: resolvedMovingNode.depth,
nodeType: (maybeNode === null || maybeNode === void 0 ? void 0 : maybeNode.type.name) || ''
}
})(tr);
return tr;
});
view.focus();
}, [isMultiSelect, api, view, dragHandleSelected, getPos, isTopLevelNodeValue, nodeType]);
const handleKeyDown = useCallback(e => {
// allow user to use spacebar to select the node
if (!e.repeat && e.key === ' ') {
var _api$core3;
const startPos = getPos();
api === null || api === void 0 ? void 0 : (_api$core3 = api.core) === null || _api$core3 === void 0 ? void 0 : _api$core3.actions.execute(({
tr
}) => {
if (startPos === undefined) {
return tr;
}
const node = tr.doc.nodeAt(startPos);
if (!node) {
return tr;
}
const $startPos = tr.doc.resolve(startPos + node.nodeSize);
const selection = new TextSelection($startPos);
tr.setSelection(selection);
!isMultiSelect && tr.setMeta(key, {
pos: startPos
});
return tr;
});
} else if (![e.altKey, e.ctrlKey, e.shiftKey].some(pressed => pressed)) {
// If not trying to press shortcut keys,
// return focus to editor to resume editing from caret position
view.focus();
}
}, [getPos, api === null || api === void 0 ? void 0 : (_api$core4 = api.core) === null || _api$core4 === void 0 ? void 0 : _api$core4.actions, isMultiSelect, view]);
const handleKeyDownNew = useCallback(e => {
// allow user to use spacebar to select the node
if (e.key === 'Enter' || !e.repeat && e.key === ' ') {
var _getDocument, _api$core5;
if (((_getDocument = getDocument()) === null || _getDocument === void 0 ? void 0 : _getDocument.activeElement) !== buttonRef.current) {
return;
}
e.preventDefault();
e.stopPropagation();
const startPos = getPos();
api === null || api === void 0 ? void 0 : (_api$core5 = api.core) === null || _api$core5 === void 0 ? void 0 : _api$core5.actions.execute(({
tr
}) => {
var _selectionPreservatio2, _api$blockControls4, _api$blockControls5, _api$userIntent;
if (startPos === undefined) {
return tr;
}
const selection = ((_selectionPreservatio2 = selectionPreservationPluginKey.getState(view.state)) === null || _selectionPreservatio2 === void 0 ? void 0 : _selectionPreservatio2.preservedSelection) || tr.selection;
expandAndUpdateSelection({
tr,
selection,
startPos,
isShiftPressed: e.shiftKey,
nodeType,
api
});
api === null || api === void 0 ? void 0 : (_api$blockControls4 = api.blockControls) === null || _api$blockControls4 === void 0 ? void 0 : _api$blockControls4.commands.startPreservingSelection()({
tr
});
const rootPos = editorExperiment('platform_synced_block', true) ? tr.doc.resolve(startPos).before(1) : undefined;
const triggerByNode = editorExperiment('platform_synced_block', true) ? {
nodeType,
pos: startPos,
rootPos
} : undefined;
api === null || api === void 0 ? void 0 : (_api$blockControls5 = api.blockControls) === null || _api$blockControls5 === void 0 ? void 0 : _api$blockControls5.commands.toggleBlockMenu({
anchorName,
triggerByNode,
openedViaKeyboard: true
})({
tr
});
api === null || api === void 0 ? void 0 : (_api$userIntent = api.userIntent) === null || _api$userIntent === void 0 ? void 0 : _api$userIntent.commands.setCurrentUserIntent('blockMenuOpen')({
tr
});
return tr;
});
view.focus();
} else if (![e.altKey, e.ctrlKey, e.shiftKey].some(pressed => 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(() => {
const element = buttonRef.current;
if (!element) {
return;
}
return draggable({
element,
getInitialData: () => ({
type: 'element',
start
}),
onGenerateDragPreview: ({
nativeSetDragImage
}) => {
var _api$blockControls$sh2;
if (isMultiSelect) {
var _api$core6;
api === null || api === void 0 ? void 0 : (_api$core6 = api.core) === null || _api$core6 === void 0 ? void 0 : _api$core6.actions.execute(({
tr
}) => {
const handlePos = getPos();
if (typeof handlePos !== 'number') {
return tr;
}
const newHandlePosCheck = isHandleCorrelatedToSelection(view.state, tr.selection, handlePos);
if (!tr.selection.empty && newHandlePosCheck) {
var _api$blockControls6;
api === null || api === void 0 ? void 0 : (_api$blockControls6 = api.blockControls) === null || _api$blockControls6 === void 0 ? void 0 : _api$blockControls6.commands.setMultiSelectPositions()({
tr
});
} else {
tr = selectNode(tr, handlePos, nodeType, api);
}
return tr;
});
}
const startPos = getPos();
const state = view.state;
const {
doc,
selection
} = state;
let sliceFrom = selection.from;
let sliceTo = selection.to;
const mSelect = api === null || api === void 0 ? void 0 : (_api$blockControls$sh2 = api.blockControls.sharedState.currentState()) === null || _api$blockControls$sh2 === void 0 ? void 0 : _api$blockControls$sh2.multiSelectDnD;
if (mSelect) {
const {
anchor,
head
} = mSelect;
sliceFrom = Math.min(anchor, head);
sliceTo = Math.max(anchor, head);
}
const expandedSlice = doc.slice(sliceFrom, sliceTo);
const isDraggingMultiLine = isMultiSelect && startPos !== undefined && startPos >= sliceFrom && startPos < sliceTo && expandedSlice.content.childCount > 1;
setCustomNativeDragPreview({
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
const domAtPos = view.domAtPos.bind(view);
let domElementsHeightBeforeHandle = 0;
const nodesStartPos = [];
const nodesEndPos = [];
let activeNodeMarginTop = 0;
for (let 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
const currentNodeElement = findDomRefAtPos(nodesStartPos[i], domAtPos);
const maybeCurrentNode = expandedSlice.content.maybeChild(i);
const 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
const maybeNextNode = expandedSlice.content.maybeChild(i);
activeNodeMarginTop = maybeNextNode ? getNodeMargins(maybeNextNode).top : 0;
break;
}
}
return {
x: 0,
y: domElementsHeightBeforeHandle + activeNodeMarginTop
};
}
},
render: ({
container
}) => {
const dom = view.dom.querySelector(`[${getAnchorAttrName()}="${anchorName}"]`);
if (!dom) {
return;
}
if (!isDraggingMultiLine) {
return dragPreview(container, {
dom,
nodeType
});
} else {
const domAtPos = view.domAtPos.bind(view);
const previewContent = [];
expandedSlice.content.descendants((node, pos) => {
// Get the dom element of the node
//eslint-disable-next-line @atlaskit/editor/no-as-casting
const nodeDomElement = findDomRefAtPos(sliceFrom + pos, domAtPos);
const 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
});
},
onDragStart() {
var _api$core7;
if (start === undefined) {
return;
}
api === null || api === void 0 ? void 0 : (_api$core7 = api.core) === null || _api$core7 === void 0 ? void 0 : _api$core7.actions.execute(({
tr
}) => {
var _api$blockControls$sh3, _api$blockControls7, _api$analytics3;
let nodeTypes, hasSelectedMultipleNodes;
const resolvedMovingNode = tr.doc.resolve(start);
const maybeNode = resolvedMovingNode.nodeAfter;
const mSelect = api === null || api === void 0 ? void 0 : (_api$blockControls$sh3 = api.blockControls.sharedState.currentState()) === null || _api$blockControls$sh3 === void 0 ? void 0 : _api$blockControls$sh3.multiSelectDnD;
if (mSelect) {
const 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 ? void 0 : (_api$blockControls7 = api.blockControls) === null || _api$blockControls7 === void 0 ? void 0 : _api$blockControls7.commands.setNodeDragged(getPos, anchorName, nodeType)({
tr
});
tr.setMeta('scrollIntoView', false);
api === null || api === void 0 ? void 0 : (_api$analytics3 = api.analytics) === null || _api$analytics3 === void 0 ? void 0 : _api$analytics3.actions.attachAnalyticsEvent({
eventType: EVENT_TYPE.UI,
action: ACTION.DRAGGED,
actionSubject: ACTION_SUBJECT.ELEMENT,
actionSubjectId: ACTION_SUBJECT_ID.ELEMENT_DRAG_HANDLE,
attributes: {
nodeDepth: resolvedMovingNode.depth,
nodeType: (maybeNode === null || maybeNode === void 0 ? void 0 : maybeNode.type.name) || '',
...(isMultiSelect && {
nodeTypes,
hasSelectedMultipleNodes
})
}
})(tr);
return tr;
});
view.focus();
}
});
}, [anchorName, api, getPos, isMultiSelect, nodeType, start, view]);
const positionStyles = useMemo(() => {
if (!editorExperiment('platform_editor_block_control_optimise_render', true)) {
return {};
}
// This is a no-op to allow recalculatePosition to be used as a dependency
if (recalculatePosition) {
setRecalculatePosition(recalculatePosition);
}
const pos = getPos();
const $pos = expValEquals('platform_editor_native_anchor_with_dnd', 'isEnabled', true) ? typeof pos === 'number' && view.state.doc.resolve(pos) : pos && view.state.doc.resolve(pos);
const parentPos = $pos && $pos.depth ? $pos.before() : undefined;
const node = parentPos !== undefined ? view.state.doc.nodeAt(parentPos) : undefined;
const parentNodeType = node === null || node === void 0 ? void 0 : node.type.name;
const supportsAnchor = CSS.supports('top', `anchor(${anchorName} start)`) && CSS.supports('left', `anchor(${anchorName} start)`);
const safeAnchorName = editorExperiment('platform_editor_controls', 'variant1') ? refreshAnchorName({
getPos,
view,
anchorName
}) : anchorName;
const dom = view.dom.querySelector(`[${getAnchorAttrName()}="${safeAnchorName}"]`);
const hasResizer = nodeType === 'table' || nodeType === 'mediaSingle';
const isExtension = nodeType === 'extension' || nodeType === 'bodiedExtension' || nodeType === 'multiBodiedExtension';
const isBlockCard = nodeType === 'blockCard';
const isEmbedCard = nodeType === 'embedCard';
const isMacroInteractionUpdates = macroInteractionUpdates && isExtension;
let 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');
}
}
const isEdgeCase = (hasResizer || isExtension || isEmbedCard || isBlockCard) && innerContainer;
const isSticky = shouldBeSticky(nodeType);
if (supportsAnchor) {
const bottom = editorExperiment('platform_editor_controls', 'variant1') ? getControlBottomCSSValue(safeAnchorName, isSticky, isTopLevelNodeValue, isLayoutColumn) : {};
return {
left: isEdgeCase ? `calc(anchor(${safeAnchorName} start) + ${getLeftPosition(dom, nodeType, innerContainer, isMacroInteractionUpdates, parentNodeType)})` : editorExperiment('advanced_layouts', true) && isLayoutColumn ? `calc((anchor(${safeAnchorName} right) + anchor(${safeAnchorName} left))/2 - ${DRAG_HANDLE_HEIGHT / 2}px)` : `calc(anchor(${safeAnchorName} start) - ${DRAG_HANDLE_WIDTH}px - ${dragHandleGap(nodeType, parentNodeType)}px)`,
top: editorExperiment('advanced_layouts', true) && isLayoutColumn ? `calc(anchor(${safeAnchorName} top) - ${DRAG_HANDLE_WIDTH}px)` : `calc(anchor(${safeAnchorName} start) + ${topPositionAdjustment(expValEquals('platform_editor_native_anchor_with_dnd', 'isEnabled', true) ? $pos && $pos.nodeAfter && getNodeTypeWithLevel($pos.nodeAfter) || nodeType : nodeType, (dom === null || dom === void 0 ? void 0 : dom.getAttribute('layout')) || '')}px)`,
...bottom
};
}
const height = editorExperiment('platform_editor_controls', 'variant1') ? getControlHeightCSSValue(getNodeHeight(dom, safeAnchorName, anchorRectCache) || 0, isSticky, isTopLevelNodeValue, `${DRAG_HANDLE_HEIGHT}`, isLayoutColumn) : {};
return {
left: isEdgeCase ? `calc(${(dom === null || dom === void 0 ? void 0 : dom.offsetLeft) || 0}px + ${getLeftPosition(dom, nodeType, innerContainer, isMacroInteractionUpdates, parentNodeType)})` : getLeftPosition(dom, nodeType, innerContainer, isMacroInteractionUpdates, parentNodeType),
top: getTopPosition(dom, nodeType),
...height
};
}, [anchorName, getPos, view, nodeType, macroInteractionUpdates, anchorRectCache, isTopLevelNodeValue, isLayoutColumn, recalculatePosition]);
const calculatePositionOld = useCallback(() => {
const pos = getPos();
const $pos = expValEquals('platform_editor_native_anchor_with_dnd', 'isEnabled', true) ? typeof pos === 'number' && view.state.doc.resolve(pos) : pos && view.state.doc.resolve(pos);
const parentPos = $pos && $pos.depth ? $pos.before() : undefined;
const node = parentPos !== undefined ? view.state.doc.nodeAt(parentPos) : undefined;
const parentNodeType = node === null || node === void 0 ? void 0 : node.type.name;
const supportsAnchor = CSS.supports('top', `anchor(${anchorName} start)`) && CSS.supports('left', `anchor(${anchorName} start)`);
const safeAnchorName = editorExperiment('platform_editor_controls', 'variant1') ? refreshAnchorName({
getPos,
view,
anchorName
}) : anchorName;
const dom = view.dom.querySelector(`[${getAnchorAttrName()}="${safeAnchorName}"]`);
const hasResizer = nodeType === 'table' || nodeType === 'mediaSingle';
const isExtension = nodeType === 'extension' || nodeType === 'bodiedExtension' || nodeType === 'multiBodiedExtension';
const isBlockCard = nodeType === 'blockCard' && !!blockCardWidth;
const isEmbedCard = nodeType === 'embedCard';
const isMacroInteractionUpdates = macroInteractionUpdates && isExtension;
let 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');
}
}
const isEdgeCase = (hasResizer || isExtension || isEmbedCard || isBlockCard) && innerContainer;
const isSticky = shouldBeSticky(nodeType);
if (supportsAnchor) {
const bottom = editorExperiment('platform_editor_controls', 'variant1') ? getControlBottomCSSValue(safeAnchorName, isSticky, isTopLevelNodeValue, isLayoutColumn) : {};
return {
left: isEdgeCase ? `calc(anchor(${safeAnchorName} start) + ${getLeftPosition(dom, nodeType, innerContainer, isMacroInteractionUpdates, parentNodeType)})` : editorExperiment('advanced_layouts', true) && isLayoutColumn ? `calc((anchor(${safeAnchorName} right) + anchor(${safeAnchorName} left))/2 - ${DRAG_HANDLE_HEIGHT / 2}px)` : `calc(anchor(${safeAnchorName} start) - ${DRAG_HANDLE_WIDTH}px - ${dragHandleGap(nodeType, parentNodeType)}px)`,
top: editorExperiment('advanced_layouts', true) && isLayoutColumn ? `calc(anchor(${safeAnchorName} top) - ${DRAG_HANDLE_WIDTH}px)` : `calc(anchor(${safeAnchorName} start) + ${topPositionAdjustment(expValEquals('platform_editor_native_anchor_with_dnd', 'isEnabled', true) ? $pos && $pos.nodeAfter && getNodeTypeWithLevel($pos.nodeAfter) || nodeType : nodeType, (dom === null || dom === void 0 ? void 0 : dom.getAttribute('layout')) || '')}px)`,
...bottom
};
}
const height = editorExperiment('platform_editor_controls', 'variant1') ? getControlHeightCSSValue(getNodeHeight(dom, safeAnchorName, anchorRectCache) || 0, isSticky, isTopLevelNodeValue, `${DRAG_HANDLE_HEIGHT}`, isLayoutColumn) : {};
return {
left: isEdgeCase ? `calc(${(dom === null || dom === void 0 ? void 0 : dom.offsetLeft) || 0}px + ${getLeftPosition(dom, nodeType, innerContainer, isMacroInteractionUpdates, parentNodeType)})` : getLeftPosition(dom, nodeType, innerContainer, isMacroInteractionUpdates, parentNodeType),
top: getTopPosition(dom, nodeType),
...height
};
}, [anchorName, getPos, view, nodeType, blockCardWidth, macroInteractionUpdates, anchorRectCache, isTopLevelNodeValue, isLayoutColumn]);
useEffect(() => {
if (editorExperiment('platform_editor_block_control_optimise_render', true)) {
return;
}
let cleanUpTransitionListener;
if (nodeType === 'extension' || nodeType === 'embedCard') {
const dom = view.dom.querySelector(`[${getAnchorAttrName()}="${anchorName}"]`);
if (!dom) {
return;
}
cleanUpTransitionListener = bind(dom, {
type: 'transitionend',
listener: () => {
setPositionStylesOld(calculatePositionOld());
}
});
}
const calcPos = requestAnimationFrame(() => {
setPositionStylesOld(calculatePositionOld());
});
return () => {
var _cleanUpTransitionLis