UNPKG

@atlaskit/editor-plugin-block-controls

Version:

Block controls plugin for @atlaskit/editor-core

204 lines (198 loc) 9.9 kB
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; } import { bind } from 'bind-event-listener'; import { SafePlugin } from '@atlaskit/editor-common/safe-plugin'; import { PluginKey } from '@atlaskit/editor-prosemirror/state'; import { editorExperiment } from '@atlaskit/tmp-editor-statsig/experiments'; import { RIGHT_MARGIN_ROVO_GAP_PX } from './constants'; import { handleKeyDown } from './handle-key-down'; import { handleMouseEnter, handleMouseLeave, handleMouseMove } from './handle-mouse-move'; /** Elements that extend the editor hover area (block controls, right-edge button, etc.) */ var BLOCK_CONTROLS_HOVER_AREA_SELECTOR = '[data-blocks-right-edge-button-container], [data-blocks-drag-handle-container], [data-testid="block-ctrl-drag-handle"], [data-testid="block-ctrl-drag-handle-container"], [data-testid="block-ctrl-decorator-widget"], [data-testid="block-ctrl-quick-insert-button"]'; var MOUSE_LEAVE_DEBOUNCE_MS = 200; /** ClickAreaBlock overlay that wraps the editor content and covers the right margin. */ var CLICK_AREA_SELECTOR = '[data-editor-click-wrapper]'; var isMovingToBlockControlsArea = function isMovingToBlockControlsArea(target) { return target instanceof Element && !!target.closest(BLOCK_CONTROLS_HOVER_AREA_SELECTOR); }; /** * The right margin is covered by the ClickAreaBlock overlay, which sits outside .ak-editor-content-area. * Hovering there should still surface the right-side Remix button, so keep controls alive — but only on * the right (past the content's right edge), and not in the far-right Rovo gap. The left gutter must * dismiss like the experiment-off path, so it is treated as inactive. */ var isOverActiveClickArea = function isOverActiveClickArea(target, clientX) { var _clickArea$querySelec, _target$ownerDocument, _target$ownerDocument2; if (!(target instanceof Element)) { return false; } var clickArea = target.closest(CLICK_AREA_SELECTOR); if (!clickArea) { return false; } var contentRight = (_clickArea$querySelec = clickArea.querySelector('.ak-editor-content-area')) === null || _clickArea$querySelec === void 0 ? void 0 : _clickArea$querySelec.getBoundingClientRect().right; if (contentRight !== undefined && clientX <= contentRight) { return false; } var innerWidth = (_target$ownerDocument = (_target$ownerDocument2 = target.ownerDocument.defaultView) === null || _target$ownerDocument2 === void 0 ? void 0 : _target$ownerDocument2.innerWidth) !== null && _target$ownerDocument !== void 0 ? _target$ownerDocument : Number.POSITIVE_INFINITY; return clientX <= innerWidth - RIGHT_MARGIN_ROVO_GAP_PX; }; export var interactionTrackingPluginKey = new PluginKey('interactionTrackingPlugin'); export var createInteractionTrackingPlugin = function createInteractionTrackingPlugin() { var rightSideControlsEnabled = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : false; var api = arguments.length > 1 ? arguments[1] : undefined; return new SafePlugin({ key: interactionTrackingPluginKey, state: { init: function init() { var state = { isEditing: false }; if (editorExperiment('platform_editor_controls', 'variant1')) { state.isMouseOut = false; } return state; }, apply: function apply(tr, pluginState) { var meta = tr.getMeta(interactionTrackingPluginKey); var newState = {}; switch (meta === null || meta === void 0 ? void 0 : meta.type) { case 'startEditing': newState.isEditing = true; break; case 'stopEditing': newState.isEditing = false; break; case 'mouseLeave': newState.isMouseOut = true; newState.hoverSide = undefined; break; case 'mouseEnter': newState.isMouseOut = false; break; case 'setHoverSide': newState.hoverSide = meta.side; break; case 'clearHoverSide': newState.hoverSide = undefined; break; } return _objectSpread(_objectSpread({}, pluginState), newState); } }, props: { handleKeyDown: handleKeyDown, handleDOMEvents: { mousemove: function mousemove(view, event) { return handleMouseMove(view, event, rightSideControlsEnabled, api); } } }, view: editorExperiment('platform_editor_controls', 'variant1') ? function (view) { var editorContentArea = view.dom.closest('.ak-editor-content-area'); // rightSideControlsEnabled is the single source of truth (confluence_remix_button_right_side_block_fg from preset) var unbindMouseEnter; var unbindMouseLeave; var unbindDocumentMouseMove; var mouseLeaveTimeoutId = null; var lastMousePosition = { x: 0, y: 0 }; // The active right margin only counts as "still hovering" when our experiment is on; // otherwise leaving the content area (e.g. exiting left) must dismiss as on master. var marginHoverEnabled = editorExperiment('remix_button_right_margin_hover', true); var scheduleMouseLeave = function scheduleMouseLeave(event) { if (mouseLeaveTimeoutId) { clearTimeout(mouseLeaveTimeoutId); mouseLeaveTimeoutId = null; } // Keep controls visible when moving to block controls (or, with the experiment on, // the active right margin — the Rovo gap is excluded so controls still clear there). if (rightSideControlsEnabled && (isMovingToBlockControlsArea(event.relatedTarget) || marginHoverEnabled && isOverActiveClickArea(event.relatedTarget, event.clientX))) { return; } mouseLeaveTimeoutId = setTimeout(function () { mouseLeaveTimeoutId = null; // Re-check after the debounce: keep controls if the cursor landed on block controls // (or, with the experiment on, the active right margin). if (rightSideControlsEnabled && typeof document !== 'undefined') { var el = document.elementFromPoint(lastMousePosition.x, lastMousePosition.y); if (el && (isMovingToBlockControlsArea(el) || marginHoverEnabled && isOverActiveClickArea(el, lastMousePosition.x))) { return; } } handleMouseLeave(view, rightSideControlsEnabled); }, MOUSE_LEAVE_DEBOUNCE_MS); }; var cancelScheduledMouseLeave = function cancelScheduledMouseLeave() { if (mouseLeaveTimeoutId) { clearTimeout(mouseLeaveTimeoutId); mouseLeaveTimeoutId = null; } }; if (editorContentArea) { if (rightSideControlsEnabled && typeof document !== 'undefined') { unbindDocumentMouseMove = bind(document, { type: 'mousemove', listener: function listener(event) { lastMousePosition = { x: event.clientX, y: event.clientY }; // Catches block controls in portals that handleDOMEvents.mousemove misses. // The right-margin overlay is only relevant with the experiment on. var overClickArea = marginHoverEnabled && event.target instanceof Element && !!event.target.closest(CLICK_AREA_SELECTOR); if (editorContentArea.contains(event.target) || isMovingToBlockControlsArea(event.target) || overClickArea) { handleMouseMove(view, event, rightSideControlsEnabled, api); } }, options: { passive: true } }); } unbindMouseEnter = bind(editorContentArea, { type: 'mouseenter', listener: function listener() { if (rightSideControlsEnabled) { cancelScheduledMouseLeave(); } handleMouseEnter(view); } }); unbindMouseLeave = bind(editorContentArea, { type: 'mouseleave', listener: function listener(event) { var e = event; lastMousePosition = { x: e.clientX, y: e.clientY }; if (rightSideControlsEnabled) { scheduleMouseLeave(e); } else { handleMouseLeave(view, false); } } }); } return { destroy: function destroy() { var _unbindMouseEnter, _unbindMouseLeave; if (rightSideControlsEnabled) { var _unbindDocumentMouseM; cancelScheduledMouseLeave(); (_unbindDocumentMouseM = unbindDocumentMouseMove) === null || _unbindDocumentMouseM === void 0 || _unbindDocumentMouseM(); } (_unbindMouseEnter = unbindMouseEnter) === null || _unbindMouseEnter === void 0 || _unbindMouseEnter(); (_unbindMouseLeave = unbindMouseLeave) === null || _unbindMouseLeave === void 0 || _unbindMouseLeave(); } }; } : undefined }); }; export var getInteractionTrackingState = function getInteractionTrackingState(state) { return interactionTrackingPluginKey.getState(state); };