Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

@tiptap/extension-bubble-menu

Package Overview
Dependencies
Maintainers
5
Versions
185
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@tiptap/extension-bubble-menu - npm Package Compare versions

Comparing version 2.5.8 to 3.0.0-next.0

171

dist/index.js

@@ -1,8 +0,48 @@

import { isTextSelection, isNodeSelection, posToDOMRect, Extension } from '@tiptap/core';
import { isTextSelection, posToDOMRect, Extension } from '@tiptap/core';
import { flip, shift, offset, arrow, size, autoPlacement, hide, inline, computePosition } from '@floating-ui/dom';
import { Plugin, PluginKey } from '@tiptap/pm/state';
import tippy from 'tippy.js';
class BubbleMenuView {
constructor({ editor, element, view, tippyOptions = {}, updateDelay = 250, shouldShow, }) {
get middlewares() {
const middlewares = [];
if (this.floatingUIOptions.flip) {
middlewares.push(flip(typeof this.floatingUIOptions.flip !== 'boolean' ? this.floatingUIOptions.flip : undefined));
}
if (this.floatingUIOptions.shift) {
middlewares.push(shift(typeof this.floatingUIOptions.shift !== 'boolean' ? this.floatingUIOptions.shift : undefined));
}
if (this.floatingUIOptions.offset) {
middlewares.push(offset(typeof this.floatingUIOptions.offset !== 'boolean' ? this.floatingUIOptions.offset : undefined));
}
if (this.floatingUIOptions.arrow) {
middlewares.push(arrow(this.floatingUIOptions.arrow));
}
if (this.floatingUIOptions.size) {
middlewares.push(size(typeof this.floatingUIOptions.size !== 'boolean' ? this.floatingUIOptions.size : undefined));
}
if (this.floatingUIOptions.autoPlacement) {
middlewares.push(autoPlacement(typeof this.floatingUIOptions.autoPlacement !== 'boolean' ? this.floatingUIOptions.autoPlacement : undefined));
}
if (this.floatingUIOptions.hide) {
middlewares.push(hide(typeof this.floatingUIOptions.hide !== 'boolean' ? this.floatingUIOptions.hide : undefined));
}
if (this.floatingUIOptions.inline) {
middlewares.push(inline(typeof this.floatingUIOptions.inline !== 'boolean' ? this.floatingUIOptions.inline : undefined));
}
return middlewares;
}
constructor({ editor, element, view, updateDelay = 250, resizeDelay = 60, shouldShow, options, }) {
this.preventHide = false;
this.floatingUIOptions = {
strategy: 'absolute',
placement: 'top',
offset: 8,
flip: {},
shift: {},
arrow: false,
size: false,
autoPlacement: false,
hide: false,
inline: false,
};
this.shouldShow = ({ view, state, from, to, }) => {

@@ -46,5 +86,2 @@ const { doc, selection } = state;

};
this.tippyBlurHandler = (event) => {
this.blurHandler({ event });
};
this.handleDebouncedUpdate = (view, oldState) => {

@@ -64,5 +101,3 @@ const selectionChanged = !(oldState === null || oldState === void 0 ? void 0 : oldState.selection.eq(view.state.selection));

this.updateHandler = (view, selectionChanged, docChanged, oldState) => {
var _a, _b, _c;
const { state, composing } = view;
const { selection } = state;
const { composing } = view;
const isSame = !selectionChanged && !docChanged;

@@ -72,15 +107,3 @@ if (composing || isSame) {

}
this.createTooltip();
// support for CellSelections
const { ranges } = selection;
const from = Math.min(...ranges.map(range => range.$from.pos));
const to = Math.max(...ranges.map(range => range.$to.pos));
const shouldShow = (_a = this.shouldShow) === null || _a === void 0 ? void 0 : _a.call(this, {
editor: this.editor,
view,
state,
oldState,
from,
to,
});
const shouldShow = this.getShouldShow(oldState);
if (!shouldShow) {

@@ -90,18 +113,3 @@ this.hide();

}
(_b = this.tippy) === null || _b === void 0 ? void 0 : _b.setProps({
getReferenceClientRect: ((_c = this.tippyOptions) === null || _c === void 0 ? void 0 : _c.getReferenceClientRect)
|| (() => {
if (isNodeSelection(state.selection)) {
let node = view.nodeDOM(from);
const nodeViewWrapper = node.dataset.nodeViewWrapper ? node : node.querySelector('[data-node-view-wrapper]');
if (nodeViewWrapper) {
node = nodeViewWrapper.firstChild;
}
if (node) {
return node.getBoundingClientRect();
}
}
return posToDOMRect(view, from, to);
}),
});
this.updatePosition();
this.show();

@@ -113,2 +121,7 @@ };

this.updateDelay = updateDelay;
this.resizeDelay = resizeDelay;
this.floatingUIOptions = {
...this.floatingUIOptions,
...options,
};
if (shouldShow) {

@@ -121,28 +134,27 @@ this.shouldShow = shouldShow;

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: 'top',
hideOnClick: 'toggle',
...this.tippyOptions,
window.addEventListener('resize', () => {
if (this.resizeDebounceTimer) {
clearTimeout(this.resizeDebounceTimer);
}
this.resizeDebounceTimer = window.setTimeout(() => {
this.updatePosition();
}, this.resizeDelay);
});
// 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);
this.update(view, view.state);
if (this.getShouldShow()) {
this.show();
}
}
updatePosition() {
const { selection } = this.editor.state;
const virtualElement = {
getBoundingClientRect: () => posToDOMRect(this.view, selection.from, selection.to),
};
computePosition(virtualElement, this.element, { placement: this.floatingUIOptions.placement, strategy: this.floatingUIOptions.strategy, middleware: this.middlewares }).then(({ x, y, strategy }) => {
this.element.style.width = 'max-content';
this.element.style.position = strategy;
this.element.style.left = `${x}px`;
this.element.style.top = `${y}px`;
});
}
update(view, oldState) {

@@ -159,16 +171,33 @@ const { state } = view;

}
show() {
getShouldShow(oldState) {
var _a;
(_a = this.tippy) === null || _a === void 0 ? void 0 : _a.show();
const { state } = this.view;
const { selection } = state;
const { ranges } = selection;
const from = Math.min(...ranges.map(range => range.$from.pos));
const to = Math.max(...ranges.map(range => range.$to.pos));
const shouldShow = (_a = this.shouldShow) === null || _a === void 0 ? void 0 : _a.call(this, {
editor: this.editor,
view: this.view,
state,
oldState,
from,
to,
});
return shouldShow;
}
show() {
this.element.style.visibility = 'visible';
this.element.style.opacity = '1';
// attach from body
document.body.appendChild(this.element);
}
hide() {
var _a;
(_a = this.tippy) === null || _a === void 0 ? void 0 : _a.hide();
this.element.style.visibility = 'hidden';
this.element.style.opacity = '0';
// remove from body
this.element.remove();
}
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.hide();
this.element.removeEventListener('mousedown', this.mousedownHandler, { capture: true });

@@ -196,3 +225,2 @@ this.view.dom.removeEventListener('dragstart', this.dragstartHandler);

element: null,
tippyOptions: {},
pluginKey: 'bubbleMenu',

@@ -212,3 +240,2 @@ updateDelay: undefined,

element: this.options.element,
tippyOptions: this.options.tippyOptions,
updateDelay: this.options.updateDelay,

@@ -215,0 +242,0 @@ shouldShow: this.options.shouldShow,

(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('@tiptap/core'), require('@tiptap/pm/state'), require('tippy.js')) :
typeof define === 'function' && define.amd ? define(['exports', '@tiptap/core', '@tiptap/pm/state', 'tippy.js'], factory) :
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global["@tiptap/extension-bubble-menu"] = {}, global.core, global.state, global.tippy));
})(this, (function (exports, core, state, tippy) { 'use strict';
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('@tiptap/core'), require('@floating-ui/dom'), require('@tiptap/pm/state')) :
typeof define === 'function' && define.amd ? define(['exports', '@tiptap/core', '@floating-ui/dom', '@tiptap/pm/state'], factory) :
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global["@tiptap/extension-bubble-menu"] = {}, global.core, global.dom, global.state));
})(this, (function (exports, core, dom, state) { 'use strict';
class BubbleMenuView {
constructor({ editor, element, view, tippyOptions = {}, updateDelay = 250, shouldShow, }) {
get middlewares() {
const middlewares = [];
if (this.floatingUIOptions.flip) {
middlewares.push(dom.flip(typeof this.floatingUIOptions.flip !== 'boolean' ? this.floatingUIOptions.flip : undefined));
}
if (this.floatingUIOptions.shift) {
middlewares.push(dom.shift(typeof this.floatingUIOptions.shift !== 'boolean' ? this.floatingUIOptions.shift : undefined));
}
if (this.floatingUIOptions.offset) {
middlewares.push(dom.offset(typeof this.floatingUIOptions.offset !== 'boolean' ? this.floatingUIOptions.offset : undefined));
}
if (this.floatingUIOptions.arrow) {
middlewares.push(dom.arrow(this.floatingUIOptions.arrow));
}
if (this.floatingUIOptions.size) {
middlewares.push(dom.size(typeof this.floatingUIOptions.size !== 'boolean' ? this.floatingUIOptions.size : undefined));
}
if (this.floatingUIOptions.autoPlacement) {
middlewares.push(dom.autoPlacement(typeof this.floatingUIOptions.autoPlacement !== 'boolean' ? this.floatingUIOptions.autoPlacement : undefined));
}
if (this.floatingUIOptions.hide) {
middlewares.push(dom.hide(typeof this.floatingUIOptions.hide !== 'boolean' ? this.floatingUIOptions.hide : undefined));
}
if (this.floatingUIOptions.inline) {
middlewares.push(dom.inline(typeof this.floatingUIOptions.inline !== 'boolean' ? this.floatingUIOptions.inline : undefined));
}
return middlewares;
}
constructor({ editor, element, view, updateDelay = 250, resizeDelay = 60, shouldShow, options, }) {
this.preventHide = false;
this.floatingUIOptions = {
strategy: 'absolute',
placement: 'top',
offset: 8,
flip: {},
shift: {},
arrow: false,
size: false,
autoPlacement: false,
hide: false,
inline: false,
};
this.shouldShow = ({ view, state, from, to, }) => {

@@ -48,5 +88,2 @@ const { doc, selection } = state;

};
this.tippyBlurHandler = (event) => {
this.blurHandler({ event });
};
this.handleDebouncedUpdate = (view, oldState) => {

@@ -66,5 +103,3 @@ const selectionChanged = !(oldState === null || oldState === void 0 ? void 0 : oldState.selection.eq(view.state.selection));

this.updateHandler = (view, selectionChanged, docChanged, oldState) => {
var _a, _b, _c;
const { state, composing } = view;
const { selection } = state;
const { composing } = view;
const isSame = !selectionChanged && !docChanged;

@@ -74,15 +109,3 @@ if (composing || isSame) {

}
this.createTooltip();
// support for CellSelections
const { ranges } = selection;
const from = Math.min(...ranges.map(range => range.$from.pos));
const to = Math.max(...ranges.map(range => range.$to.pos));
const shouldShow = (_a = this.shouldShow) === null || _a === void 0 ? void 0 : _a.call(this, {
editor: this.editor,
view,
state,
oldState,
from,
to,
});
const shouldShow = this.getShouldShow(oldState);
if (!shouldShow) {

@@ -92,18 +115,3 @@ this.hide();

}
(_b = this.tippy) === null || _b === void 0 ? void 0 : _b.setProps({
getReferenceClientRect: ((_c = this.tippyOptions) === null || _c === void 0 ? void 0 : _c.getReferenceClientRect)
|| (() => {
if (core.isNodeSelection(state.selection)) {
let node = view.nodeDOM(from);
const nodeViewWrapper = node.dataset.nodeViewWrapper ? node : node.querySelector('[data-node-view-wrapper]');
if (nodeViewWrapper) {
node = nodeViewWrapper.firstChild;
}
if (node) {
return node.getBoundingClientRect();
}
}
return core.posToDOMRect(view, from, to);
}),
});
this.updatePosition();
this.show();

@@ -115,2 +123,7 @@ };

this.updateDelay = updateDelay;
this.resizeDelay = resizeDelay;
this.floatingUIOptions = {
...this.floatingUIOptions,
...options,
};
if (shouldShow) {

@@ -123,28 +136,27 @@ this.shouldShow = shouldShow;

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: 'top',
hideOnClick: 'toggle',
...this.tippyOptions,
window.addEventListener('resize', () => {
if (this.resizeDebounceTimer) {
clearTimeout(this.resizeDebounceTimer);
}
this.resizeDebounceTimer = window.setTimeout(() => {
this.updatePosition();
}, this.resizeDelay);
});
// 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);
this.update(view, view.state);
if (this.getShouldShow()) {
this.show();
}
}
updatePosition() {
const { selection } = this.editor.state;
const virtualElement = {
getBoundingClientRect: () => core.posToDOMRect(this.view, selection.from, selection.to),
};
dom.computePosition(virtualElement, this.element, { placement: this.floatingUIOptions.placement, strategy: this.floatingUIOptions.strategy, middleware: this.middlewares }).then(({ x, y, strategy }) => {
this.element.style.width = 'max-content';
this.element.style.position = strategy;
this.element.style.left = `${x}px`;
this.element.style.top = `${y}px`;
});
}
update(view, oldState) {

@@ -161,16 +173,33 @@ const { state } = view;

}
show() {
getShouldShow(oldState) {
var _a;
(_a = this.tippy) === null || _a === void 0 ? void 0 : _a.show();
const { state } = this.view;
const { selection } = state;
const { ranges } = selection;
const from = Math.min(...ranges.map(range => range.$from.pos));
const to = Math.max(...ranges.map(range => range.$to.pos));
const shouldShow = (_a = this.shouldShow) === null || _a === void 0 ? void 0 : _a.call(this, {
editor: this.editor,
view: this.view,
state,
oldState,
from,
to,
});
return shouldShow;
}
show() {
this.element.style.visibility = 'visible';
this.element.style.opacity = '1';
// attach from body
document.body.appendChild(this.element);
}
hide() {
var _a;
(_a = this.tippy) === null || _a === void 0 ? void 0 : _a.hide();
this.element.style.visibility = 'hidden';
this.element.style.opacity = '0';
// remove from body
this.element.remove();
}
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.hide();
this.element.removeEventListener('mousedown', this.mousedownHandler, { capture: true });

@@ -198,3 +227,2 @@ this.view.dom.removeEventListener('dragstart', this.dragstartHandler);

element: null,
tippyOptions: {},
pluginKey: 'bubbleMenu',

@@ -214,3 +242,2 @@ updateDelay: undefined,

element: this.options.element,
tippyOptions: this.options.tippyOptions,
updateDelay: this.options.updateDelay,

@@ -217,0 +244,0 @@ shouldShow: this.options.shouldShow,

@@ -341,4 +341,4 @@ import { Plugin, Transaction } from '@tiptap/pm/state';

static create<O = any, S = any>(config?: Partial<ExtensionConfig<O, S>>): Extension<O, S>;
configure(options?: Partial<Options>): Extension<any, any>;
configure(options?: Partial<Options>): Extension<Options, Storage>;
extend<ExtendedOptions = Options, ExtendedStorage = Storage>(extendedConfig?: Partial<ExtensionConfig<ExtendedOptions, ExtendedStorage>>): Extension<ExtendedOptions, ExtendedStorage>;
}

@@ -445,3 +445,3 @@ import { DOMOutputSpec, Mark as ProseMirrorMark, MarkSpec, MarkType } from '@tiptap/pm/model';

static create<O = any, S = any>(config?: Partial<MarkConfig<O, S>>): Mark<O, S>;
configure(options?: Partial<Options>): Mark<any, any>;
configure(options?: Partial<Options>): Mark<Options, Storage>;
extend<ExtendedOptions = Options, ExtendedStorage = Storage>(extendedConfig?: Partial<MarkConfig<ExtendedOptions, ExtendedStorage>>): Mark<ExtendedOptions, ExtendedStorage>;

@@ -448,0 +448,0 @@ static handleExit({ editor, mark }: {

@@ -609,4 +609,4 @@ import { DOMOutputSpec, Node as ProseMirrorNode, NodeSpec, NodeType } from '@tiptap/pm/model';

static create<O = any, S = any>(config?: Partial<NodeConfig<O, S>>): Node<O, S>;
configure(options?: Partial<Options>): Node<any, any>;
configure(options?: Partial<Options>): Node<Options, Storage>;
extend<ExtendedOptions = Options, ExtendedStorage = Storage>(extendedConfig?: Partial<NodeConfig<ExtendedOptions, ExtendedStorage>>): Node<ExtendedOptions, ExtendedStorage>;
}

@@ -1,1 +0,1 @@

export declare const style = ".ProseMirror {\n position: relative;\n}\n\n.ProseMirror {\n word-wrap: break-word;\n white-space: pre-wrap;\n white-space: break-spaces;\n -webkit-font-variant-ligatures: none;\n font-variant-ligatures: none;\n font-feature-settings: \"liga\" 0; /* the above doesn't seem to work in Edge */\n}\n\n.ProseMirror [contenteditable=\"false\"] {\n white-space: normal;\n}\n\n.ProseMirror [contenteditable=\"false\"] [contenteditable=\"true\"] {\n white-space: pre-wrap;\n}\n\n.ProseMirror pre {\n white-space: pre-wrap;\n}\n\nimg.ProseMirror-separator {\n display: inline !important;\n border: none !important;\n margin: 0 !important;\n width: 1px !important;\n height: 1px !important;\n}\n\n.ProseMirror-gapcursor {\n display: none;\n pointer-events: none;\n position: absolute;\n margin: 0;\n}\n\n.ProseMirror-gapcursor:after {\n content: \"\";\n display: block;\n position: absolute;\n top: -2px;\n width: 20px;\n border-top: 1px solid black;\n animation: ProseMirror-cursor-blink 1.1s steps(2, start) infinite;\n}\n\n@keyframes ProseMirror-cursor-blink {\n to {\n visibility: hidden;\n }\n}\n\n.ProseMirror-hideselection *::selection {\n background: transparent;\n}\n\n.ProseMirror-hideselection *::-moz-selection {\n background: transparent;\n}\n\n.ProseMirror-hideselection * {\n caret-color: transparent;\n}\n\n.ProseMirror-focused .ProseMirror-gapcursor {\n display: block;\n}\n\n.tippy-box[data-animation=fade][data-state=hidden] {\n opacity: 0\n}";
export declare const style = ".ProseMirror {\n position: relative;\n}\n\n.ProseMirror {\n word-wrap: break-word;\n white-space: pre-wrap;\n white-space: break-spaces;\n -webkit-font-variant-ligatures: none;\n font-variant-ligatures: none;\n font-feature-settings: \"liga\" 0; /* the above doesn't seem to work in Edge */\n}\n\n.ProseMirror [contenteditable=\"false\"] {\n white-space: normal;\n}\n\n.ProseMirror [contenteditable=\"false\"] [contenteditable=\"true\"] {\n white-space: pre-wrap;\n}\n\n.ProseMirror pre {\n white-space: pre-wrap;\n}\n\nimg.ProseMirror-separator {\n display: inline !important;\n border: none !important;\n margin: 0 !important;\n width: 1px !important;\n height: 1px !important;\n}\n\n.ProseMirror-gapcursor {\n display: none;\n pointer-events: none;\n position: absolute;\n margin: 0;\n}\n\n.ProseMirror-gapcursor:after {\n content: \"\";\n display: block;\n position: absolute;\n top: -2px;\n width: 20px;\n border-top: 1px solid black;\n animation: ProseMirror-cursor-blink 1.1s steps(2, start) infinite;\n}\n\n@keyframes ProseMirror-cursor-blink {\n to {\n visibility: hidden;\n }\n}\n\n.ProseMirror-hideselection *::selection {\n background: transparent;\n}\n\n.ProseMirror-hideselection *::-moz-selection {\n background: transparent;\n}\n\n.ProseMirror-hideselection * {\n caret-color: transparent;\n}\n\n.ProseMirror-focused .ProseMirror-gapcursor {\n display: block;\n}";

@@ -0,5 +1,5 @@

import { type ArrowOptions, type AutoPlacementOptions, type FlipOptions, type HideOptions, type InlineOptions, type OffsetOptions, type Placement, type ShiftOptions, type SizeOptions, type Strategy } from '@floating-ui/dom';
import { Editor } from '@tiptap/core';
import { EditorState, Plugin, PluginKey } from '@tiptap/pm/state';
import { EditorView } from '@tiptap/pm/view';
import { Instance, Props } from 'tippy.js';
export interface BubbleMenuPluginProps {

@@ -23,7 +23,2 @@ /**

/**
* The options for the tippy.js instance.
* @see https://atomiks.github.io/tippyjs/v6/all-props/
*/
tippyOptions?: Partial<Props>;
/**
* The delay in milliseconds before the menu should be updated.

@@ -36,6 +31,13 @@ * This can be useful to prevent performance issues.

/**
* The delay in milliseconds before the menu position should be updated on window resize.
* This can be useful to prevent performance issues.
* @type {number}
* @default 60
*/
resizeDelay?: number;
/**
* A function that determines whether the menu should be shown or not.
* If this function returns `false`, the menu will be hidden, otherwise it will be shown.
*/
shouldShow?: ((props: {
shouldShow: ((props: {
editor: Editor;

@@ -48,2 +50,17 @@ view: EditorView;

}) => boolean) | null;
/**
* FloatingUI options.
*/
options?: {
strategy?: Strategy;
placement?: Placement;
offset?: OffsetOptions | boolean;
flip?: FlipOptions | boolean;
shift?: ShiftOptions | boolean;
arrow?: ArrowOptions | false;
size?: SizeOptions | boolean;
autoPlacement?: AutoPlacementOptions | boolean;
hide?: HideOptions | boolean;
inline?: InlineOptions | boolean;
};
}

@@ -58,8 +75,14 @@ export type BubbleMenuViewProps = BubbleMenuPluginProps & {

preventHide: boolean;
tippy: Instance | undefined;
tippyOptions?: Partial<Props>;
updateDelay: number;
resizeDelay: number;
private updateDebounceTimer;
private resizeDebounceTimer;
private floatingUIOptions;
shouldShow: Exclude<BubbleMenuPluginProps['shouldShow'], null>;
constructor({ editor, element, view, tippyOptions, updateDelay, shouldShow, }: BubbleMenuViewProps);
get middlewares(): {
name: string;
options?: any;
fn: (state: import("@floating-ui/dom").MiddlewareState) => import("@floating-ui/core").MiddlewareReturn | Promise<import("@floating-ui/core").MiddlewareReturn>;
}[];
constructor({ editor, element, view, updateDelay, resizeDelay, shouldShow, options, }: BubbleMenuViewProps);
mousedownHandler: () => void;

@@ -71,6 +94,6 @@ dragstartHandler: () => void;

}) => void;
tippyBlurHandler: (event: FocusEvent) => void;
createTooltip(): void;
updatePosition(): void;
update(view: EditorView, oldState?: EditorState): void;
handleDebouncedUpdate: (view: EditorView, oldState?: EditorState) => void;
getShouldShow(oldState?: EditorState): boolean;
updateHandler: (view: EditorView, selectionChanged: boolean, docChanged: boolean, oldState?: EditorState) => void;

@@ -77,0 +100,0 @@ show(): void;

{
"name": "@tiptap/extension-bubble-menu",
"description": "bubble-menu extension for tiptap",
"version": "2.5.8",
"version": "3.0.0-next.0",
"homepage": "https://tiptap.dev",

@@ -31,5 +31,2 @@ "keywords": [

],
"dependencies": {
"tippy.js": "^6.3.7"
},
"repository": {

@@ -42,8 +39,10 @@ "type": "git",

"devDependencies": {
"@tiptap/core": "^2.5.8",
"@tiptap/pm": "^2.5.8"
"@floating-ui/dom": "^1.0.0",
"@tiptap/core": "^3.0.0-next.0",
"@tiptap/pm": "^3.0.0-next.0"
},
"peerDependencies": {
"@tiptap/core": "^2.5.8",
"@tiptap/pm": "^2.5.8"
"@floating-ui/dom": "^1.0.0",
"@tiptap/core": "^3.0.0-next.0",
"@tiptap/pm": "^3.0.0-next.0"
},

@@ -50,0 +49,0 @@ "scripts": {

import {
Editor, isNodeSelection, isTextSelection, posToDOMRect,
type ArrowOptions,
type AutoPlacementOptions,
type FlipOptions,
type HideOptions,
type InlineOptions,
type Middleware, type OffsetOptions, type Placement, type ShiftOptions, type SizeOptions, type Strategy, arrow, autoPlacement, computePosition, flip, hide, inline, offset, shift,
size,
} from '@floating-ui/dom'
import {
Editor, isTextSelection, posToDOMRect,
} from '@tiptap/core'
import { EditorState, Plugin, PluginKey } from '@tiptap/pm/state'
import { EditorView } from '@tiptap/pm/view'
import tippy, { Instance, Props } from 'tippy.js'

@@ -29,8 +37,2 @@ export interface BubbleMenuPluginProps {

/**
* The options for the tippy.js instance.
* @see https://atomiks.github.io/tippyjs/v6/all-props/
*/
tippyOptions?: Partial<Props>
/**
* The delay in milliseconds before the menu should be updated.

@@ -44,6 +46,14 @@ * This can be useful to prevent performance issues.

/**
* The delay in milliseconds before the menu position should be updated on window resize.
* This can be useful to prevent performance issues.
* @type {number}
* @default 60
*/
resizeDelay?: number
/**
* A function that determines whether the menu should be shown or not.
* If this function returns `false`, the menu will be hidden, otherwise it will be shown.
*/
shouldShow?:
shouldShow:
| ((props: {

@@ -58,2 +68,18 @@ editor: Editor

| null
/**
* FloatingUI options.
*/
options?: {
strategy?: Strategy
placement?: Placement
offset?: OffsetOptions | boolean
flip?: FlipOptions | boolean
shift?: ShiftOptions | boolean
arrow?: ArrowOptions | false
size?: SizeOptions | boolean
autoPlacement?: AutoPlacementOptions | boolean
hide?: HideOptions | boolean
inline?: InlineOptions | boolean
}
}

@@ -74,10 +100,34 @@

public tippy: Instance | undefined
public updateDelay: number
public tippyOptions?: Partial<Props>
public resizeDelay: number
public updateDelay: number
private updateDebounceTimer: number | undefined
private resizeDebounceTimer: number | undefined
private floatingUIOptions: {
strategy: Strategy
placement: Placement
offset: OffsetOptions | boolean
flip: FlipOptions | boolean
shift: ShiftOptions | boolean
arrow: ArrowOptions | false
size: SizeOptions | boolean
autoPlacement: AutoPlacementOptions | boolean
hide: HideOptions | boolean
inline: InlineOptions | boolean
} = {
strategy: 'absolute',
placement: 'top',
offset: 8,
flip: {},
shift: {},
arrow: false,
size: false,
autoPlacement: false,
hide: false,
inline: false,
}
public shouldShow: Exclude<BubbleMenuPluginProps['shouldShow'], null> = ({

@@ -111,2 +161,40 @@ view,

get middlewares() {
const middlewares: Middleware[] = []
if (this.floatingUIOptions.flip) {
middlewares.push(flip(typeof this.floatingUIOptions.flip !== 'boolean' ? this.floatingUIOptions.flip : undefined))
}
if (this.floatingUIOptions.shift) {
middlewares.push(shift(typeof this.floatingUIOptions.shift !== 'boolean' ? this.floatingUIOptions.shift : undefined))
}
if (this.floatingUIOptions.offset) {
middlewares.push(offset(typeof this.floatingUIOptions.offset !== 'boolean' ? this.floatingUIOptions.offset : undefined))
}
if (this.floatingUIOptions.arrow) {
middlewares.push(arrow(this.floatingUIOptions.arrow))
}
if (this.floatingUIOptions.size) {
middlewares.push(size(typeof this.floatingUIOptions.size !== 'boolean' ? this.floatingUIOptions.size : undefined))
}
if (this.floatingUIOptions.autoPlacement) {
middlewares.push(autoPlacement(typeof this.floatingUIOptions.autoPlacement !== 'boolean' ? this.floatingUIOptions.autoPlacement : undefined))
}
if (this.floatingUIOptions.hide) {
middlewares.push(hide(typeof this.floatingUIOptions.hide !== 'boolean' ? this.floatingUIOptions.hide : undefined))
}
if (this.floatingUIOptions.inline) {
middlewares.push(inline(typeof this.floatingUIOptions.inline !== 'boolean' ? this.floatingUIOptions.inline : undefined))
}
return middlewares
}
constructor({

@@ -116,5 +204,6 @@ editor,

view,
tippyOptions = {},
updateDelay = 250,
resizeDelay = 60,
shouldShow,
options,
}: BubbleMenuViewProps) {

@@ -125,3 +214,9 @@ this.editor = editor

this.updateDelay = updateDelay
this.resizeDelay = resizeDelay
this.floatingUIOptions = {
...this.floatingUIOptions,
...options,
}
if (shouldShow) {

@@ -135,6 +230,17 @@ this.shouldShow = shouldShow

this.editor.on('blur', this.blurHandler)
this.tippyOptions = tippyOptions
// Detaches menu content from its current parent
this.element.remove()
this.element.style.visibility = 'visible'
window.addEventListener('resize', () => {
if (this.resizeDebounceTimer) {
clearTimeout(this.resizeDebounceTimer)
}
this.resizeDebounceTimer = window.setTimeout(() => {
this.updatePosition()
}, this.resizeDelay)
})
this.update(view, view.state)
if (this.getShouldShow()) {
this.show()
}
}

@@ -169,29 +275,15 @@

tippyBlurHandler = (event: FocusEvent) => {
this.blurHandler({ event })
}
updatePosition() {
const { selection } = this.editor.state
createTooltip() {
const { element: editorElement } = this.editor.options
const editorIsAttached = !!editorElement.parentElement
if (this.tippy || !editorIsAttached) {
return
const virtualElement = {
getBoundingClientRect: () => posToDOMRect(this.view, selection.from, selection.to),
}
this.tippy = tippy(editorElement, {
duration: 0,
getReferenceClientRect: null,
content: this.element,
interactive: true,
trigger: 'manual',
placement: 'top',
hideOnClick: 'toggle',
...this.tippyOptions,
computePosition(virtualElement, this.element, { placement: this.floatingUIOptions.placement, strategy: this.floatingUIOptions.strategy, middleware: this.middlewares }).then(({ x, y, strategy }) => {
this.element.style.width = 'max-content'
this.element.style.position = strategy
this.element.style.left = `${x}px`
this.element.style.top = `${y}px`
})
// maybe we have to hide tippy on its own blur event as well
if (this.tippy.popper.firstChild) {
(this.tippy.popper.firstChild as HTMLElement).addEventListener('blur', this.tippyBlurHandler)
}
}

@@ -231,15 +323,6 @@

updateHandler = (view: EditorView, selectionChanged: boolean, docChanged: boolean, oldState?: EditorState) => {
const { state, composing } = view
getShouldShow(oldState?: EditorState) {
const { state } = this.view
const { selection } = state
const isSame = !selectionChanged && !docChanged
if (composing || isSame) {
return
}
this.createTooltip()
// support for CellSelections
const { ranges } = selection

@@ -251,3 +334,3 @@ const from = Math.min(...ranges.map(range => range.$from.pos))

editor: this.editor,
view,
view: this.view,
state,

@@ -259,30 +342,23 @@ oldState,

if (!shouldShow) {
this.hide()
return shouldShow
}
updateHandler = (view: EditorView, selectionChanged: boolean, docChanged: boolean, oldState?: EditorState) => {
const { composing } = view
const isSame = !selectionChanged && !docChanged
if (composing || isSame) {
return
}
this.tippy?.setProps({
getReferenceClientRect:
this.tippyOptions?.getReferenceClientRect
|| (() => {
if (isNodeSelection(state.selection)) {
let node = view.nodeDOM(from) as HTMLElement
const shouldShow = this.getShouldShow(oldState)
const nodeViewWrapper = node.dataset.nodeViewWrapper ? node : node.querySelector('[data-node-view-wrapper]')
if (!shouldShow) {
this.hide()
if (nodeViewWrapper) {
node = nodeViewWrapper.firstChild as HTMLElement
}
return
}
if (node) {
return node.getBoundingClientRect()
}
}
return posToDOMRect(view, from, to)
}),
})
this.updatePosition()
this.show()

@@ -292,17 +368,17 @@ }

show() {
this.tippy?.show()
this.element.style.visibility = 'visible'
this.element.style.opacity = '1'
// attach from body
document.body.appendChild(this.element)
}
hide() {
this.tippy?.hide()
this.element.style.visibility = 'hidden'
this.element.style.opacity = '0'
// remove from body
this.element.remove()
}
destroy() {
if (this.tippy?.popper.firstChild) {
(this.tippy.popper.firstChild as HTMLElement).removeEventListener(
'blur',
this.tippyBlurHandler,
)
}
this.tippy?.destroy()
this.hide()
this.element.removeEventListener('mousedown', this.mousedownHandler, { capture: true })

@@ -309,0 +385,0 @@ this.view.dom.removeEventListener('dragstart', this.dragstartHandler)

@@ -24,3 +24,2 @@ import { Extension } from '@tiptap/core'

element: null,
tippyOptions: {},
pluginKey: 'bubbleMenu',

@@ -42,3 +41,2 @@ updateDelay: undefined,

element: this.options.element,
tippyOptions: this.options.tippyOptions,
updateDelay: this.options.updateDelay,

@@ -45,0 +43,0 @@ shouldShow: this.options.shouldShow,

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc