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.
1 line
11 KiB
1 line
11 KiB
3 years ago
|
{"version":3,"file":"tiptap-extension-floating-menu.cjs","sources":["../src/floating-menu-plugin.ts","../src/floating-menu.ts"],"sourcesContent":["import { Editor, posToDOMRect } from '@tiptap/core'\nimport { EditorState, Plugin, PluginKey } from 'prosemirror-state'\nimport { EditorView } from 'prosemirror-view'\nimport tippy, { Instance, Props } from 'tippy.js'\n\nexport interface FloatingMenuPluginProps {\n pluginKey: PluginKey | string,\n editor: Editor,\n element: HTMLElement,\n tippyOptions?: Partial<Props>,\n shouldShow?: ((props: {\n editor: Editor,\n view: EditorView,\n state: EditorState,\n oldState?: EditorState,\n }) => boolean) | null,\n}\n\nexport type FloatingMenuViewProps = FloatingMenuPluginProps & {\n view: EditorView,\n}\n\nexport class FloatingMenuView {\n public editor: Editor\n\n public element: HTMLElement\n\n public view: EditorView\n\n public preventHide = false\n\n public tippy: Instance | undefined\n\n public tippyOptions?: Partial<Props>\n\n public shouldShow: Exclude<FloatingMenuPluginProps['shouldShow'], null> = ({ view, state }) => {\n const { selection } = state\n const { $anchor, empty } = selection\n const isRootDepth = $anchor.depth === 1\n const isEmptyTextBlock = $anchor.parent.isTextblock\n && !$anchor.parent.type.spec.code\n && !$anchor.parent.textContent\n\n if (\n !view.hasFocus()\n || !empty\n || !isRootDepth\n || !isEmptyTextBlock\n || !this.editor.isEditable\n ) {\n return false\n }\n\n return true\n }\n\n constructor({\n editor,\n element,\n view,\n tippyOptions = {},\n shouldShow,\n }: FloatingMenuViewProps) {\n this.editor = editor\n this.element = element\n this.view = view\n\n if (shouldShow) {\n this.shouldShow = shouldShow\n }\n\n this.element.addEventListener('mousedown', this.mousedownHandler, { capture: true })\n this.editor.on('focus', this.focusHandler)\n this.editor.on('blur', this.blurHandler)\n this.tippyOptions = tippyOptions\n // Detaches menu content from its current parent\n this.element.remove()\n this.element.style.visibility = 'visible'\n }\n\n mousedownHandler = () => {\n this.preventHide = true\n }\n\n focusHandler = () => {\n // we use `setTimeout` to make sure `selection` is already updated\n setTimeout(() => this.update(this.editor.view))\n }\n\n blurHandler = ({ event }: { event: FocusEvent }) => {\n if (this.preventHide) {\n this.preventHide = false\n\n return\n }\n\n if (\n event?.relatedTarget\n && this.element.parentNode?.contains(event.relatedTarget as Node)\n ) {\n return\n }\n\n this.hide()\n }\n\n tippyBlurHandler = (event : FocusEvent) => {\n this.blurHandler({ event })\n }\n\n createTooltip() {\n const { element: editorElement } = this.editor.options\n const editorIsAttached = !!editorElement.parentElement\n\n if (this.tippy || !editorIsAttached) {\n return\n }\n\n this.tippy = tippy(editorElement, {\n duration: 0,\n getReferenceClientRect: null,\n content: this.element,\n interactive: true,\n trigger: 'manual',\n placement: 'right',\n hideOnClick: 'toggle',\n ...this.tippyOptions,\n })\n\n // maybe we have to hide tippy on its own blur event as well\n if (this.tippy.popper.firstChild) {\n (this.tippy.popper.firstChild as HTMLElement).addEventListener('blur', this.tippyBlurHandler)\n }\n }\n\n update(view: EditorView, oldState?: EditorState) {\n const { state } = view\n const { doc, selection } = state\n const { from, to } = selection\n const isSame = oldState && oldState.doc.eq(doc) && oldState.selection.eq(selection)\n\n if (isSame) {\n return\n }\n\n this.createTooltip()\n\n const shouldShow = this.shouldShow?.({\n editor: this.editor,\n view,\n state,\n oldState,\n })\n\n if (!shouldShow) {\n this.hide()\n\n return\n }\n\n this.tippy?.setProps({\n getReferenceClientRect: thi
|