UNPKG

@tiptap/react

Version:

React components for tiptap

531 lines (525 loc) 16.7 kB
"use strict"; var __create = Object.create; var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __getProtoOf = Object.getPrototypeOf; var __hasOwnProp = Object.prototype.hasOwnProperty; var __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); }; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps( // If the importer is in node compatibility mode or this is not an ESM // file that has been converted to a CommonJS file using a Babel- // compatible transform (i.e. "__esModule" has not been set), then set // "default" to the CommonJS "module.exports" for node compatibility. isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target, mod )); var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); // src/menus/index.ts var index_exports = {}; __export(index_exports, { BubbleMenu: () => BubbleMenu, FloatingMenu: () => FloatingMenu }); module.exports = __toCommonJS(index_exports); // src/menus/BubbleMenu.tsx var import_extension_bubble_menu = require("@tiptap/extension-bubble-menu"); var import_react2 = require("@tiptap/react"); var import_react3 = __toESM(require("react"), 1); var import_react_dom = require("react-dom"); // src/menus/getAutoPluginKey.ts var import_state = require("@tiptap/pm/state"); function getAutoPluginKey(pluginKey, defaultName) { return pluginKey != null ? pluginKey : new import_state.PluginKey(defaultName); } // src/menus/useMenuElementProps.ts var import_react = require("react"); var useIsomorphicLayoutEffect = typeof window !== "undefined" ? import_react.useLayoutEffect : import_react.useEffect; var PLUGIN_MANAGED_STYLE_PROPERTIES = /* @__PURE__ */ new Set([ "left", "opacity", "position", "top", "visibility", "width" ]); var UNITLESS_STYLE_PROPERTIES = /* @__PURE__ */ new Set([ "animationIterationCount", "aspectRatio", "borderImageOutset", "borderImageSlice", "borderImageWidth", "columnCount", "columns", "fillOpacity", "flex", "flexGrow", "flexShrink", "fontWeight", "gridArea", "gridColumn", "gridColumnEnd", "gridColumnStart", "gridRow", "gridRowEnd", "gridRowStart", "lineClamp", "lineHeight", "opacity", "order", "orphans", "scale", "stopOpacity", "strokeDasharray", "strokeDashoffset", "strokeMiterlimit", "strokeOpacity", "strokeWidth", "tabSize", "widows", "zIndex", "zoom" ]); var ATTRIBUTE_EXCLUSIONS = /* @__PURE__ */ new Set(["children", "className", "style"]); var DIRECT_PROPERTY_KEYS = /* @__PURE__ */ new Set(["tabIndex"]); var FORWARDED_ATTRIBUTE_KEYS = /* @__PURE__ */ new Set([ "accessKey", "autoCapitalize", "contentEditable", "contextMenu", "dir", "draggable", "enterKeyHint", "hidden", "id", "lang", "nonce", "role", "slot", "spellCheck", "tabIndex", "title", "translate" ]); var SPECIAL_EVENT_NAMES = { Blur: "focusout", DoubleClick: "dblclick", Focus: "focusin", MouseEnter: "mouseenter", MouseLeave: "mouseleave" }; function isEventProp(key, value) { return /^on[A-Z]/.test(key) && typeof value === "function"; } function toAttributeName(key) { if (key.startsWith("aria-") || key.startsWith("data-")) { return key; } return key; } function isForwardedAttributeKey(key) { return key.startsWith("aria-") || key.startsWith("data-") || FORWARDED_ATTRIBUTE_KEYS.has(key); } function toStylePropertyName(key) { if (key.startsWith("--")) { return key; } return key.replace(/[A-Z]/g, (match) => `-${match.toLowerCase()}`); } function toEventConfig(key) { var _a; const useCapture = key.endsWith("Capture"); const baseKey = useCapture ? key.slice(0, -7) : key; const reactEventName = baseKey.slice(2); const eventName = (_a = SPECIAL_EVENT_NAMES[reactEventName]) != null ? _a : reactEventName.toLowerCase(); return { eventName, options: useCapture ? { capture: true } : void 0 }; } function createSyntheticEvent(element, nativeEvent) { let defaultPrevented = nativeEvent.defaultPrevented; let propagationStopped = false; const syntheticEvent = Object.create(nativeEvent); Object.defineProperties(syntheticEvent, { nativeEvent: { value: nativeEvent }, currentTarget: { value: element }, target: { value: nativeEvent.target }, persist: { value: () => void 0 }, isDefaultPrevented: { value: () => defaultPrevented }, isPropagationStopped: { value: () => propagationStopped }, preventDefault: { value: () => { defaultPrevented = true; nativeEvent.preventDefault(); } }, stopPropagation: { value: () => { propagationStopped = true; nativeEvent.stopPropagation(); } } }); return syntheticEvent; } function isDirectPropertyKey(key) { return DIRECT_PROPERTY_KEYS.has(key); } function setDirectProperty(element, key, value) { if (key === "tabIndex") { element.tabIndex = Number(value); return; } ; element[key] = value; } function clearDirectProperty(element, key) { if (key === "tabIndex") { element.removeAttribute("tabindex"); return; } const propertyValue = element[key]; if (typeof propertyValue === "boolean") { ; element[key] = false; return; } if (typeof propertyValue === "number") { ; element[key] = 0; return; } ; element[key] = ""; } function toStyleValue(styleName, value) { if (typeof value !== "number" || value === 0 || styleName.startsWith("--") || UNITLESS_STYLE_PROPERTIES.has(styleName)) { return String(value); } return `${value}px`; } function removeStyleProperty(element, styleName) { if (PLUGIN_MANAGED_STYLE_PROPERTIES.has(styleName)) { return; } element.style.removeProperty(toStylePropertyName(styleName)); } function applyStyleProperty(element, styleName, value) { if (PLUGIN_MANAGED_STYLE_PROPERTIES.has(styleName)) { return; } element.style.setProperty(toStylePropertyName(styleName), toStyleValue(styleName, value)); } function syncAttributes(element, prevProps, nextProps) { const allKeys = /* @__PURE__ */ new Set([...Object.keys(prevProps), ...Object.keys(nextProps)]); allKeys.forEach((key) => { if (ATTRIBUTE_EXCLUSIONS.has(key) || !isForwardedAttributeKey(key) || isEventProp(key, prevProps[key]) || isEventProp(key, nextProps[key])) { return; } const prevValue = prevProps[key]; const nextValue = nextProps[key]; if (prevValue === nextValue) { return; } const attributeName = toAttributeName(key); if (nextValue == null || nextValue === false) { if (isDirectPropertyKey(key)) { clearDirectProperty(element, key); } element.removeAttribute(attributeName); return; } if (nextValue === true) { if (isDirectPropertyKey(key)) { setDirectProperty(element, key, true); } element.setAttribute(attributeName, ""); return; } if (isDirectPropertyKey(key)) { setDirectProperty(element, key, nextValue); return; } element.setAttribute(attributeName, String(nextValue)); }); } function syncClassName(element, prevClassName, nextClassName) { if (prevClassName === nextClassName) { return; } if (nextClassName) { element.className = nextClassName; return; } element.removeAttribute("class"); } function syncStyles(element, prevStyle, nextStyle) { const previousStyle = prevStyle != null ? prevStyle : {}; const currentStyle = nextStyle != null ? nextStyle : {}; const allStyleNames = /* @__PURE__ */ new Set([...Object.keys(previousStyle), ...Object.keys(currentStyle)]); allStyleNames.forEach((styleName) => { const prevValue = previousStyle[styleName]; const nextValue = currentStyle[styleName]; if (prevValue === nextValue) { return; } if (nextValue == null) { removeStyleProperty(element, styleName); return; } applyStyleProperty(element, styleName, nextValue); }); } function syncEventListeners(element, prevListeners, nextProps) { prevListeners.forEach(({ eventName, listener, options }) => { element.removeEventListener(eventName, listener, options); }); const nextListeners = []; Object.entries(nextProps).forEach(([key, value]) => { if (!isEventProp(key, value)) { return; } const { eventName, options } = toEventConfig(key); const listener = (event) => { value(createSyntheticEvent(element, event)); }; element.addEventListener(eventName, listener, options); nextListeners.push({ eventName, listener, options }); }); return nextListeners; } function useMenuElementProps(element, props) { const previousPropsRef = (0, import_react.useRef)({}); const listenersRef = (0, import_react.useRef)([]); useIsomorphicLayoutEffect(() => { const previousProps = previousPropsRef.current; syncClassName(element, previousProps.className, props.className); syncStyles(element, previousProps.style, props.style); syncAttributes(element, previousProps, props); listenersRef.current = syncEventListeners(element, listenersRef.current, props); previousPropsRef.current = props; return () => { listenersRef.current.forEach(({ eventName, listener, options }) => { element.removeEventListener(eventName, listener, options); }); listenersRef.current = []; }; }, [element, props]); } // src/menus/BubbleMenu.tsx var BubbleMenu = import_react3.default.forwardRef( ({ pluginKey, editor, updateDelay, resizeDelay, appendTo, shouldShow = null, getReferencedVirtualElement, options, children, ...restProps }, ref) => { const menuEl = (0, import_react3.useRef)(document.createElement("div")); const resolvedPluginKey = (0, import_react3.useRef)( getAutoPluginKey(pluginKey, "bubbleMenu") ).current; useMenuElementProps(menuEl.current, restProps); if (typeof ref === "function") { ref(menuEl.current); } else if (ref) { ref.current = menuEl.current; } const { editor: currentEditor } = (0, import_react2.useCurrentEditor)(); const pluginEditor = editor || currentEditor; const bubbleMenuPluginProps = { updateDelay, resizeDelay, appendTo, pluginKey: resolvedPluginKey, shouldShow, getReferencedVirtualElement, options }; const bubbleMenuPluginPropsRef = (0, import_react3.useRef)(bubbleMenuPluginProps); bubbleMenuPluginPropsRef.current = bubbleMenuPluginProps; const [pluginInitialized, setPluginInitialized] = (0, import_react3.useState)(false); const skipFirstUpdateRef = (0, import_react3.useRef)(true); (0, import_react3.useEffect)(() => { if (pluginEditor == null ? void 0 : pluginEditor.isDestroyed) { return; } if (!pluginEditor) { console.warn( "BubbleMenu component is not rendered inside of an editor component or does not have editor prop." ); return; } const bubbleMenuElement = menuEl.current; bubbleMenuElement.style.visibility = "hidden"; bubbleMenuElement.style.position = "absolute"; const plugin = (0, import_extension_bubble_menu.BubbleMenuPlugin)({ ...bubbleMenuPluginPropsRef.current, editor: pluginEditor, element: bubbleMenuElement }); pluginEditor.registerPlugin(plugin); const createdPluginKey = bubbleMenuPluginPropsRef.current.pluginKey; skipFirstUpdateRef.current = true; setPluginInitialized(true); return () => { setPluginInitialized(false); pluginEditor.unregisterPlugin(createdPluginKey); window.requestAnimationFrame(() => { if (bubbleMenuElement.parentNode) { bubbleMenuElement.parentNode.removeChild(bubbleMenuElement); } }); }; }, [pluginEditor]); (0, import_react3.useEffect)(() => { if (!pluginInitialized || !pluginEditor || pluginEditor.isDestroyed) { return; } if (skipFirstUpdateRef.current) { skipFirstUpdateRef.current = false; return; } pluginEditor.view.dispatch( pluginEditor.state.tr.setMeta(resolvedPluginKey, { type: "updateOptions", options: bubbleMenuPluginPropsRef.current }) ); }, [ pluginInitialized, pluginEditor, updateDelay, resizeDelay, shouldShow, options, appendTo, getReferencedVirtualElement, resolvedPluginKey ]); return (0, import_react_dom.createPortal)(children, menuEl.current); } ); // src/menus/FloatingMenu.tsx var import_extension_floating_menu = require("@tiptap/extension-floating-menu"); var import_react4 = require("@tiptap/react"); var import_react5 = __toESM(require("react"), 1); var import_react_dom2 = require("react-dom"); var FloatingMenu = import_react5.default.forwardRef( ({ pluginKey, editor, updateDelay, resizeDelay, appendTo, shouldShow = null, options, children, ...restProps }, ref) => { const menuEl = (0, import_react5.useRef)(document.createElement("div")); const resolvedPluginKey = (0, import_react5.useRef)( getAutoPluginKey(pluginKey, "floatingMenu") ).current; useMenuElementProps(menuEl.current, restProps); if (typeof ref === "function") { ref(menuEl.current); } else if (ref) { ref.current = menuEl.current; } const { editor: currentEditor } = (0, import_react4.useCurrentEditor)(); const pluginEditor = editor || currentEditor; const floatingMenuPluginProps = { updateDelay, resizeDelay, appendTo, pluginKey: resolvedPluginKey, shouldShow, options }; const floatingMenuPluginPropsRef = (0, import_react5.useRef)(floatingMenuPluginProps); floatingMenuPluginPropsRef.current = floatingMenuPluginProps; const [pluginInitialized, setPluginInitialized] = (0, import_react5.useState)(false); const skipFirstUpdateRef = (0, import_react5.useRef)(true); (0, import_react5.useEffect)(() => { if (pluginEditor == null ? void 0 : pluginEditor.isDestroyed) { return; } if (!pluginEditor) { console.warn( "FloatingMenu component is not rendered inside of an editor component or does not have editor prop." ); return; } const floatingMenuElement = menuEl.current; floatingMenuElement.style.visibility = "hidden"; floatingMenuElement.style.position = "absolute"; const plugin = (0, import_extension_floating_menu.FloatingMenuPlugin)({ ...floatingMenuPluginPropsRef.current, editor: pluginEditor, element: floatingMenuElement }); pluginEditor.registerPlugin(plugin); const createdPluginKey = floatingMenuPluginPropsRef.current.pluginKey; skipFirstUpdateRef.current = true; setPluginInitialized(true); return () => { setPluginInitialized(false); pluginEditor.unregisterPlugin(createdPluginKey); window.requestAnimationFrame(() => { if (floatingMenuElement.parentNode) { floatingMenuElement.parentNode.removeChild(floatingMenuElement); } }); }; }, [pluginEditor]); (0, import_react5.useEffect)(() => { if (!pluginInitialized || !pluginEditor || pluginEditor.isDestroyed) { return; } if (skipFirstUpdateRef.current) { skipFirstUpdateRef.current = false; return; } pluginEditor.view.dispatch( pluginEditor.state.tr.setMeta(resolvedPluginKey, { type: "updateOptions", options: floatingMenuPluginPropsRef.current }) ); }, [ pluginInitialized, pluginEditor, updateDelay, resizeDelay, shouldShow, options, appendTo, resolvedPluginKey ]); return (0, import_react_dom2.createPortal)(children, menuEl.current); } ); // Annotate the CommonJS export names for ESM import in node: 0 && (module.exports = { BubbleMenu, FloatingMenu }); //# sourceMappingURL=index.cjs.map