UNPKG

@atlaskit/editor-plugin-selection-marker

Version:

Selection marker plugin for @atlaskit/editor-core.

138 lines (137 loc) 8.65 kB
import React, { useEffect, useRef } from 'react'; import { useSharedPluginStateWithSelector } from '@atlaskit/editor-common/hooks'; import { useSharedPluginStateSelector } from '@atlaskit/editor-common/use-shared-plugin-state-selector'; import { expValEqualsNoExposure } from '@atlaskit/tmp-editor-statsig/exp-val-equals-no-exposure'; import { editorExperiment } from '@atlaskit/tmp-editor-statsig/experiments'; import { createPlugin, dispatchShouldHideDecorations, key } from './pm-plugins/main'; import { GlobalStylesWrapper } from './ui/global-styles'; export var selectionMarkerPlugin = function selectionMarkerPlugin(_ref) { var config = _ref.config, api = _ref.api; return { name: 'selectionMarker', pmPlugins: function pmPlugins() { return [{ name: 'selectionMarkerPmPlugin', plugin: function plugin() { return createPlugin(api); } }]; }, getSharedState: function getSharedState(editorState) { var _key$getState$forceHi, _key$getState, _key$getState2; if (!editorState) { return undefined; } return { isForcedHidden: (_key$getState$forceHi = (_key$getState = key.getState(editorState)) === null || _key$getState === void 0 ? void 0 : _key$getState.forceHide) !== null && _key$getState$forceHi !== void 0 ? _key$getState$forceHi : false, isMarkerActive: !((_key$getState2 = key.getState(editorState)) !== null && _key$getState2 !== void 0 && _key$getState2.shouldHideDecorations) }; }, actions: { // For now this is a very simple locking mechanism that only allows one // plugin to hide / release at a time. hideDecoration: function hideDecoration() { var _api$selectionMarker, _api$core; if (api !== null && api !== void 0 && (_api$selectionMarker = api.selectionMarker) !== null && _api$selectionMarker !== void 0 && (_api$selectionMarker = _api$selectionMarker.sharedState.currentState()) !== null && _api$selectionMarker !== void 0 && _api$selectionMarker.isForcedHidden) { return undefined; } var success = api === null || api === void 0 || (_api$core = api.core) === null || _api$core === void 0 ? void 0 : _api$core.actions.execute(function (_ref2) { var tr = _ref2.tr; return tr.setMeta(key, { forceHide: true }); }); if (!success) { return undefined; } return cleanupHiddenDecoration(api); }, queueHideDecoration: function queueHideDecoration(setCleanup) { var result = api === null || api === void 0 ? void 0 : api.selectionMarker.actions.hideDecoration(); if (result === undefined) { var _api$selectionMarker2; var cleanup = api === null || api === void 0 || (_api$selectionMarker2 = api.selectionMarker) === null || _api$selectionMarker2 === void 0 ? void 0 : _api$selectionMarker2.sharedState.onChange(function (_ref3) { var nextSharedState = _ref3.nextSharedState; if ((nextSharedState === null || nextSharedState === void 0 ? void 0 : nextSharedState.isForcedHidden) === false && (nextSharedState === null || nextSharedState === void 0 ? void 0 : nextSharedState.isMarkerActive) === false) { var _result = api === null || api === void 0 ? void 0 : api.selectionMarker.actions.hideDecoration(); setCleanup(_result); cleanup === null || cleanup === void 0 || cleanup(); } }); return cleanup; } setCleanup(result); return function () {}; } }, usePluginHook: function usePluginHook(_ref4) { var editorView = _ref4.editorView; var editorHasNotBeenFocused = useRef(true); useEffect(function () { // relatch when editorView changes (pretty good signal for reinit) editorHasNotBeenFocused.current = true; }, [editorView]); var _useSharedPluginState = useSharedPluginStateWithSelector(api, ['focus', 'typeAhead', 'editorDisabled', 'toolbar', 'decorations', 'userIntent'], function (states) { var _states$focusState, _states$typeAheadStat, _states$editorDisable, _states$toolbarState, _states$decorationsSt, _states$userIntentSta; return { hasFocus: (_states$focusState = states.focusState) === null || _states$focusState === void 0 ? void 0 : _states$focusState.hasFocus, isOpen: (_states$typeAheadStat = states.typeAheadState) === null || _states$typeAheadStat === void 0 ? void 0 : _states$typeAheadStat.isOpen, editorDisabled: (_states$editorDisable = states.editorDisabledState) === null || _states$editorDisable === void 0 ? void 0 : _states$editorDisable.editorDisabled, showToolbar: (_states$toolbarState = states.toolbarState) === null || _states$toolbarState === void 0 ? void 0 : _states$toolbarState.shouldShowToolbar, hasDangerDecorations: expValEqualsNoExposure('platform_editor_block_menu', 'isEnabled', true) ? (_states$decorationsSt = states.decorationsState) === null || _states$decorationsSt === void 0 ? void 0 : _states$decorationsSt.hasDangerDecorations : undefined, currentUserIntent: (_states$userIntentSta = states.userIntentState) === null || _states$userIntentSta === void 0 ? void 0 : _states$userIntentSta.currentUserIntent }; }), hasFocus = _useSharedPluginState.hasFocus, isOpen = _useSharedPluginState.isOpen, editorDisabled = _useSharedPluginState.editorDisabled, showToolbar = _useSharedPluginState.showToolbar, hasDangerDecorations = _useSharedPluginState.hasDangerDecorations, currentUserIntent = _useSharedPluginState.currentUserIntent; var isForcedHidden = useSharedPluginStateSelector(api, 'selectionMarker.isForcedHidden'); useEffect(function () { // On editor init we should use this latch to keep the marker hidden until // editor has received focus. This means editor will be initially hidden until // the first focus occurs, and after first focus the normal above rules will // apply if (hasFocus === true) { editorHasNotBeenFocused.current = false; } var isBlockMenuOpen = currentUserIntent === 'blockMenuOpen' && editorExperiment('platform_editor_block_menu', true); /** * There are a number of conditions we should not show the marker, * - Editor has not been focused: to keep the marker hidden until first focus if config is set * - Focus: to ensure it doesn't interrupt the normal cursor * - Typeahead Open: To ensure it doesn't show when we're typing in the typeahead * - Disabled: So that it behaves similar to the renderer in live pages/disabled * - Via the API: If another plugin has requested it to be hidden (force hidden). * - If danger styles is shown in decorationsPlugin, then we don't need to show the selection marker */ var shouldHide = (config === null || config === void 0 ? void 0 : config.hideCursorOnInit) && editorHasNotBeenFocused.current || hasFocus || (isOpen !== null && isOpen !== void 0 ? isOpen : false) || isForcedHidden || (editorDisabled !== null && editorDisabled !== void 0 ? editorDisabled : false) || (showToolbar !== null && showToolbar !== void 0 ? showToolbar : false) || !!hasDangerDecorations && editorExperiment('platform_editor_block_menu', true) || isBlockMenuOpen; requestAnimationFrame(function () { return dispatchShouldHideDecorations(editorView, shouldHide); }); }, [editorView, hasFocus, isOpen, isForcedHidden, editorDisabled, showToolbar, hasDangerDecorations, currentUserIntent]); }, contentComponent: function contentComponent() { return /*#__PURE__*/React.createElement(GlobalStylesWrapper, null); } }; }; function cleanupHiddenDecoration(api) { var hasRun = false; return function () { var _api$selectionMarker3; if (!hasRun && api !== null && api !== void 0 && (_api$selectionMarker3 = api.selectionMarker) !== null && _api$selectionMarker3 !== void 0 && (_api$selectionMarker3 = _api$selectionMarker3.sharedState.currentState()) !== null && _api$selectionMarker3 !== void 0 && _api$selectionMarker3.isForcedHidden) { var _api$core2; hasRun = true; return api === null || api === void 0 || (_api$core2 = api.core) === null || _api$core2 === void 0 ? void 0 : _api$core2.actions.execute(function (_ref5) { var tr = _ref5.tr; return tr.setMeta(key, { forceHide: false }); }); } }; }