@atlaskit/editor-plugin-floating-toolbar
Version:
Floating toolbar plugin for @atlaskit/editor-core
157 lines (153 loc) • 7.14 kB
JavaScript
;
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
var _typeof = require("@babel/runtime/helpers/typeof");
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.EmojiPickerButton = void 0;
var _slicedToArray2 = _interopRequireDefault(require("@babel/runtime/helpers/slicedToArray"));
var _react = _interopRequireWildcard(require("react"));
var _react2 = require("@emotion/react");
var _new = require("@atlaskit/button/new");
var _hooks = require("@atlaskit/editor-common/hooks");
var _ui = require("@atlaskit/editor-common/ui");
var _uiReact = require("@atlaskit/editor-common/ui-react");
var _emoji = require("@atlaskit/emoji");
var _emojiAdd = _interopRequireDefault(require("@atlaskit/icon/core/emoji-add"));
var _tooltip = _interopRequireDefault(require("@atlaskit/tooltip"));
function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function _interopRequireWildcard(e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, default: e }; if (null === e || "object" != _typeof(e) && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (var _t in e) "default" !== _t && {}.hasOwnProperty.call(e, _t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, _t)) && (i.get || i.set) ? o(f, _t, i) : f[_t] = e[_t]); return f; })(e, t); }
/**
* @jsxRuntime classic
* @jsx jsx
*/
// eslint-disable-next-line @atlaskit/ui-styling-standard/use-compiled -- Ignored via go/DSP-18766
// eslint-disable-next-line @atlaskit/design-system/consistent-css-prop-usage
var emojiPickerButtonWrapperVisualRefresh = (0, _react2.css)({
position: 'relative',
// eslint-disable-next-line @atlaskit/ui-styling-standard/no-nested-selectors
button: {
// eslint-disable-next-line @atlaskit/ui-styling-standard/no-unsafe-selectors
'&:not([disabled])::after': {
border: 'none' // remove blue border when picker has been selected
}
}
});
var selector = function selector(states) {
var _states$emojiState;
return (_states$emojiState = states.emojiState) === null || _states$emojiState === void 0 ? void 0 : _states$emojiState.emojiProvider;
};
var EmojiPickerWithProvider = function EmojiPickerWithProvider(props) {
var emojiProvider = (0, _hooks.useSharedPluginStateWithSelector)(props.pluginInjectionApi, ['emoji'], selector);
var setOutsideClickTargetRef = (0, _react.useContext)(_uiReact.OutsideClickTargetRefContext);
if (!emojiProvider) {
return null;
}
return (0, _react2.jsx)(_emoji.EmojiPicker, {
emojiProvider: Promise.resolve(emojiProvider),
onSelection: props.updateEmoji,
onPickerRef: setOutsideClickTargetRef
});
};
// Note: These are based on the height and width of the emoji picker at the time
// of writing (2025-05-05). It is 100% prone to change but at least it's vaguely
// written down.
var EMOJI_PICKER_MAX_HEIGHT = 431;
var EMOJI_PICKER_MAX_WIDTH = 352;
var EmojiPickerWithListener = (0, _uiReact.withReactEditorViewOuterListeners)(EmojiPickerWithProvider);
var EmojiPickerButton = exports.EmojiPickerButton = function EmojiPickerButton(props) {
var buttonRef = _react.default.useRef(null);
var _React$useState = _react.default.useState(false),
_React$useState2 = (0, _slicedToArray2.default)(_React$useState, 2),
isPopupOpen = _React$useState2[0],
setIsPopupOpen = _React$useState2[1];
_react.default.useEffect(function () {
if (props.setDisableParentScroll) {
props.setDisableParentScroll(isPopupOpen);
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [isPopupOpen]);
var togglePopup = (0, _react.useCallback)(function () {
setIsPopupOpen(!isPopupOpen);
}, [setIsPopupOpen, isPopupOpen]);
var updateEmoji = function updateEmoji(emoji) {
setIsPopupOpen(false);
props.onChange && props.onChange(emoji);
requestAnimationFrame(function () {
var _props$editorView;
(_props$editorView = props.editorView) === null || _props$editorView === void 0 || _props$editorView.focus();
});
};
var isDetachedElement = (0, _react.useCallback)(function (el) {
return !document.body.contains(el);
}, []);
var handleEmojiClickOutside = (0, _react.useCallback)(function (e) {
// Ignore click events for detached elements.
// Workaround for CETI-240 - where two onClicks fire - one when the upload button is
// still in the document, and one once it's detached. Does not always occur, and
// may be a side effect of a react render optimisation
// Ignored via go/ees005
// eslint-disable-next-line @atlaskit/editor/no-as-casting
if (e && e.target && !isDetachedElement(e.target)) {
togglePopup();
}
}, [isDetachedElement, togglePopup]);
var handleEmojiPressEscape = (0, _react.useCallback)(function () {
var _buttonRef$current;
setIsPopupOpen(false);
(_buttonRef$current = buttonRef.current) === null || _buttonRef$current === void 0 || _buttonRef$current.focus();
}, [setIsPopupOpen, buttonRef]);
var renderPopup = function renderPopup() {
if (!buttonRef.current || !isPopupOpen) {
return;
}
return (0, _react2.jsx)(_ui.Popup, {
target: buttonRef.current
// Ignored via go/ees005
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
,
mountTo: props.setDisableParentScroll ? props.mountPoint : buttonRef.current.parentElement,
fitHeight: EMOJI_PICKER_MAX_HEIGHT,
fitWidth: EMOJI_PICKER_MAX_WIDTH
// eslint-disable-next-line @atlassian/perf-linting/no-unstable-inline-props -- Ignored via go/ees017 (to be fixed)
,
offset: [0, 10]
// Confluence inline comment editor has z-index: 500
// if the toolbar is scrollable, this will be mounted in the root editor
// we need an index of > 500 to display over it
,
zIndex: props.setDisableParentScroll ? 600 : undefined,
focusTrap: true,
preventOverflow: true,
boundariesElement: props.popupsBoundariesElement
}, (0, _react2.jsx)(EmojiPickerWithListener, {
handleEscapeKeydown: handleEmojiPressEscape,
handleClickOutside: handleEmojiClickOutside,
pluginInjectionApi: props.pluginInjectionApi,
updateEmoji: updateEmoji
}));
};
var title = props.title || '';
return (0, _react2.jsx)("div", {
css: emojiPickerButtonWrapperVisualRefresh
}, (0, _react2.jsx)(_tooltip.default, {
content: title,
position: "top"
}, (0, _react2.jsx)(_new.IconButton, {
appearance: "subtle",
key: props.idx,
onClick: togglePopup,
ref: buttonRef,
isSelected: props.isSelected,
label: title,
spacing: "compact"
// eslint-disable-next-line @atlassian/perf-linting/no-unstable-inline-props -- Ignored via go/ees017 (to be fixed)
,
icon: function icon() {
return (0, _react2.jsx)(_emojiAdd.default, {
color: "currentColor",
label: "emoji-picker-button",
spacing: "spacious"
});
}
})), renderPopup());
};