You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

162 lines
5.8 KiB

import { posToDOMRect, Extension } from '@tiptap/core';
import { Plugin, PluginKey } from 'prosemirror-state';
import tippy from 'tippy.js';
class FloatingMenuView {
constructor({ editor, element, view, tippyOptions = {}, shouldShow, }) {
this.preventHide = false;
this.shouldShow = ({ view, state }) => {
const { selection } = state;
const { $anchor, empty } = selection;
const isRootDepth = $anchor.depth === 1;
const isEmptyTextBlock = $anchor.parent.isTextblock
&& !$anchor.parent.type.spec.code
&& !$anchor.parent.textContent;
if (!view.hasFocus()
|| !empty
|| !isRootDepth
|| !isEmptyTextBlock
|| !this.editor.isEditable) {
return false;
}
return true;
};
this.mousedownHandler = () => {
this.preventHide = true;
};
this.focusHandler = () => {
// we use `setTimeout` to make sure `selection` is already updated
setTimeout(() => this.update(this.editor.view));
};
this.blurHandler = ({ event }) => {
var _a;
if (this.preventHide) {
this.preventHide = false;
return;
}
if ((event === null || event === void 0 ? void 0 : event.relatedTarget)
&& ((_a = this.element.parentNode) === null || _a === void 0 ? void 0 : _a.contains(event.relatedTarget))) {
return;
}
this.hide();
};
this.tippyBlurHandler = (event) => {
this.blurHandler({ event });
};
this.editor = editor;
this.element = element;
this.view = view;
if (shouldShow) {
this.shouldShow = shouldShow;
}
this.element.addEventListener('mousedown', this.mousedownHandler, { capture: true });
this.editor.on('focus', this.focusHandler);
this.editor.on('blur', this.blurHandler);
this.tippyOptions = tippyOptions;
// Detaches menu content from its current parent
this.element.remove();
this.element.style.visibility = 'visible';
}
createTooltip() {
const { element: editorElement } = this.editor.options;
const editorIsAttached = !!editorElement.parentElement;
if (this.tippy || !editorIsAttached) {
return;
}
this.tippy = tippy(editorElement, {
duration: 0,
getReferenceClientRect: null,
content: this.element,
interactive: true,
trigger: 'manual',
placement: 'right',
hideOnClick: 'toggle',
...this.tippyOptions,
});
// maybe we have to hide tippy on its own blur event as well
if (this.tippy.popper.firstChild) {
this.tippy.popper.firstChild.addEventListener('blur', this.tippyBlurHandler);
}
}
update(view, oldState) {
var _a, _b, _c;
const { state } = view;
const { doc, selection } = state;
const { from, to } = selection;
const isSame = oldState && oldState.doc.eq(doc) && oldState.selection.eq(selection);
if (isSame) {
return;
}
this.createTooltip();
const shouldShow = (_a = this.shouldShow) === null || _a === void 0 ? void 0 : _a.call(this, {
editor: this.editor,
view,
state,
oldState,
});
if (!shouldShow) {
this.hide();
return;
}
(_b = this.tippy) === null || _b === void 0 ? void 0 : _b.setProps({
getReferenceClientRect: ((_c = this.tippyOptions) === null || _c === void 0 ? void 0 : _c.getReferenceClientRect) || (() => posToDOMRect(view, from, to)),
});
this.show();
}
show() {
var _a;
(_a = this.tippy) === null || _a === void 0 ? void 0 : _a.show();
}
hide() {
var _a;
(_a = this.tippy) === null || _a === void 0 ? void 0 : _a.hide();
}
destroy() {
var _a, _b;
if ((_a = this.tippy) === null || _a === void 0 ? void 0 : _a.popper.firstChild) {
this.tippy.popper.firstChild.removeEventListener('blur', this.tippyBlurHandler);
}
(_b = this.tippy) === null || _b === void 0 ? void 0 : _b.destroy();
this.element.removeEventListener('mousedown', this.mousedownHandler, { capture: true });
this.editor.off('focus', this.focusHandler);
this.editor.off('blur', this.blurHandler);
}
}
const FloatingMenuPlugin = (options) => {
return new Plugin({
key: typeof options.pluginKey === 'string'
? new PluginKey(options.pluginKey)
: options.pluginKey,
view: view => new FloatingMenuView({ view, ...options }),
});
};
const FloatingMenu = Extension.create({
name: 'floatingMenu',
addOptions() {
return {
element: null,
tippyOptions: {},
pluginKey: 'floatingMenu',
shouldShow: null,
};
},
addProseMirrorPlugins() {
if (!this.options.element) {
return [];
}
return [
FloatingMenuPlugin({
pluginKey: this.options.pluginKey,
editor: this.editor,
element: this.options.element,
tippyOptions: this.options.tippyOptions,
shouldShow: this.options.shouldShow,
}),
];
},
});
export { FloatingMenu, FloatingMenuPlugin, FloatingMenuView, FloatingMenu as default };
//# sourceMappingURL=tiptap-extension-floating-menu.esm.js.map