@tiptap/react 3.17.0 → 3.18.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -815,6 +815,7 @@ var ReactNodeView = class extends NodeView {
815
815
  * The requestAnimationFrame ID used for selection updates.
816
816
  */
817
817
  this.selectionRafId = null;
818
+ this.cachedExtensionWithSyncedStorage = null;
818
819
  if (!this.node.isLeaf) {
819
820
  if (this.options.contentDOMElementTag) {
820
821
  this.contentDOMElement = document.createElement(this.options.contentDOMElementTag);
@@ -831,6 +832,27 @@ var ReactNodeView = class extends NodeView {
831
832
  contentTarget.appendChild(this.contentDOMElement);
832
833
  }
833
834
  }
835
+ /**
836
+ * Returns a proxy of the extension that redirects storage access to the editor's mutable storage.
837
+ * This preserves the original prototype chain (instanceof checks, methods like configure/extend work).
838
+ * Cached to avoid proxy creation on every update.
839
+ */
840
+ get extensionWithSyncedStorage() {
841
+ if (!this.cachedExtensionWithSyncedStorage) {
842
+ const editor = this.editor;
843
+ const extension = this.extension;
844
+ this.cachedExtensionWithSyncedStorage = new Proxy(extension, {
845
+ get(target, prop, receiver) {
846
+ var _a;
847
+ if (prop === "storage") {
848
+ return (_a = editor.storage[extension.name]) != null ? _a : {};
849
+ }
850
+ return Reflect.get(target, prop, receiver);
851
+ }
852
+ });
853
+ }
854
+ return this.cachedExtensionWithSyncedStorage;
855
+ }
834
856
  /**
835
857
  * Setup the React component.
836
858
  * Called on initialization.
@@ -843,7 +865,7 @@ var ReactNodeView = class extends NodeView {
843
865
  innerDecorations: this.innerDecorations,
844
866
  view: this.view,
845
867
  selected: false,
846
- extension: this.extension,
868
+ extension: this.extensionWithSyncedStorage,
847
869
  HTMLAttributes: this.HTMLAttributes,
848
870
  getPos: () => this.getPos(),
849
871
  updateAttributes: (attributes = {}) => this.updateAttributes(attributes),
@@ -964,7 +986,7 @@ var ReactNodeView = class extends NodeView {
964
986
  newDecorations: decorations,
965
987
  oldInnerDecorations,
966
988
  innerDecorations,
967
- updateProps: () => rerenderComponent({ node, decorations, innerDecorations })
989
+ updateProps: () => rerenderComponent({ node, decorations, innerDecorations, extension: this.extensionWithSyncedStorage })
968
990
  });
969
991
  }
970
992
  if (node === this.node && this.decorations === decorations && this.innerDecorations === innerDecorations) {
@@ -973,7 +995,7 @@ var ReactNodeView = class extends NodeView {
973
995
  this.node = node;
974
996
  this.decorations = decorations;
975
997
  this.innerDecorations = innerDecorations;
976
- rerenderComponent({ node, decorations, innerDecorations });
998
+ rerenderComponent({ node, decorations, innerDecorations, extension: this.extensionWithSyncedStorage });
977
999
  return true;
978
1000
  }
979
1001
  /**
@@ -1035,6 +1057,289 @@ function ReactNodeViewRenderer(component, options) {
1035
1057
  };
1036
1058
  }
1037
1059
 
1060
+ // src/Tiptap.tsx
1061
+ import { createContext as createContext3, useContext as useContext3, useEffect as useEffect5, useMemo as useMemo2, useState as useState5 } from "react";
1062
+
1063
+ // src/menus/BubbleMenu.tsx
1064
+ import { BubbleMenuPlugin } from "@tiptap/extension-bubble-menu";
1065
+ import { useCurrentEditor as useCurrentEditor2 } from "@tiptap/react";
1066
+ import React5, { useEffect as useEffect3, useRef as useRef2, useState as useState3 } from "react";
1067
+ import { createPortal } from "react-dom";
1068
+ import { jsx as jsx8 } from "react/jsx-runtime";
1069
+ var BubbleMenu = React5.forwardRef(
1070
+ ({
1071
+ pluginKey = "bubbleMenu",
1072
+ editor,
1073
+ updateDelay,
1074
+ resizeDelay,
1075
+ appendTo,
1076
+ shouldShow = null,
1077
+ getReferencedVirtualElement,
1078
+ options,
1079
+ children,
1080
+ ...restProps
1081
+ }, ref) => {
1082
+ const menuEl = useRef2(document.createElement("div"));
1083
+ if (typeof ref === "function") {
1084
+ ref(menuEl.current);
1085
+ } else if (ref) {
1086
+ ref.current = menuEl.current;
1087
+ }
1088
+ const { editor: currentEditor } = useCurrentEditor2();
1089
+ const pluginEditor = editor || currentEditor;
1090
+ const bubbleMenuPluginProps = {
1091
+ updateDelay,
1092
+ resizeDelay,
1093
+ appendTo,
1094
+ pluginKey,
1095
+ shouldShow,
1096
+ getReferencedVirtualElement,
1097
+ options
1098
+ };
1099
+ const bubbleMenuPluginPropsRef = useRef2(bubbleMenuPluginProps);
1100
+ bubbleMenuPluginPropsRef.current = bubbleMenuPluginProps;
1101
+ const [pluginInitialized, setPluginInitialized] = useState3(false);
1102
+ const skipFirstUpdateRef = useRef2(true);
1103
+ useEffect3(() => {
1104
+ if (pluginEditor == null ? void 0 : pluginEditor.isDestroyed) {
1105
+ return;
1106
+ }
1107
+ if (!pluginEditor) {
1108
+ console.warn("BubbleMenu component is not rendered inside of an editor component or does not have editor prop.");
1109
+ return;
1110
+ }
1111
+ const bubbleMenuElement = menuEl.current;
1112
+ bubbleMenuElement.style.visibility = "hidden";
1113
+ bubbleMenuElement.style.position = "absolute";
1114
+ const plugin = BubbleMenuPlugin({
1115
+ ...bubbleMenuPluginPropsRef.current,
1116
+ editor: pluginEditor,
1117
+ element: bubbleMenuElement
1118
+ });
1119
+ pluginEditor.registerPlugin(plugin);
1120
+ const createdPluginKey = bubbleMenuPluginPropsRef.current.pluginKey;
1121
+ skipFirstUpdateRef.current = true;
1122
+ setPluginInitialized(true);
1123
+ return () => {
1124
+ setPluginInitialized(false);
1125
+ pluginEditor.unregisterPlugin(createdPluginKey);
1126
+ window.requestAnimationFrame(() => {
1127
+ if (bubbleMenuElement.parentNode) {
1128
+ bubbleMenuElement.parentNode.removeChild(bubbleMenuElement);
1129
+ }
1130
+ });
1131
+ };
1132
+ }, [pluginEditor]);
1133
+ useEffect3(() => {
1134
+ if (!pluginInitialized || !pluginEditor || pluginEditor.isDestroyed) {
1135
+ return;
1136
+ }
1137
+ if (skipFirstUpdateRef.current) {
1138
+ skipFirstUpdateRef.current = false;
1139
+ return;
1140
+ }
1141
+ pluginEditor.view.dispatch(
1142
+ pluginEditor.state.tr.setMeta("bubbleMenu", {
1143
+ type: "updateOptions",
1144
+ options: bubbleMenuPluginPropsRef.current
1145
+ })
1146
+ );
1147
+ }, [
1148
+ pluginInitialized,
1149
+ pluginEditor,
1150
+ updateDelay,
1151
+ resizeDelay,
1152
+ shouldShow,
1153
+ options,
1154
+ appendTo,
1155
+ getReferencedVirtualElement
1156
+ ]);
1157
+ return createPortal(/* @__PURE__ */ jsx8("div", { ...restProps, children }), menuEl.current);
1158
+ }
1159
+ );
1160
+
1161
+ // src/menus/FloatingMenu.tsx
1162
+ import { FloatingMenuPlugin } from "@tiptap/extension-floating-menu";
1163
+ import { useCurrentEditor as useCurrentEditor3 } from "@tiptap/react";
1164
+ import React6, { useEffect as useEffect4, useRef as useRef3, useState as useState4 } from "react";
1165
+ import { createPortal as createPortal2 } from "react-dom";
1166
+ import { jsx as jsx9 } from "react/jsx-runtime";
1167
+ var FloatingMenu = React6.forwardRef(
1168
+ ({
1169
+ pluginKey = "floatingMenu",
1170
+ editor,
1171
+ updateDelay,
1172
+ resizeDelay,
1173
+ appendTo,
1174
+ shouldShow = null,
1175
+ options,
1176
+ children,
1177
+ ...restProps
1178
+ }, ref) => {
1179
+ const menuEl = useRef3(document.createElement("div"));
1180
+ if (typeof ref === "function") {
1181
+ ref(menuEl.current);
1182
+ } else if (ref) {
1183
+ ref.current = menuEl.current;
1184
+ }
1185
+ const { editor: currentEditor } = useCurrentEditor3();
1186
+ const pluginEditor = editor || currentEditor;
1187
+ const floatingMenuPluginProps = {
1188
+ updateDelay,
1189
+ resizeDelay,
1190
+ appendTo,
1191
+ pluginKey,
1192
+ shouldShow,
1193
+ options
1194
+ };
1195
+ const floatingMenuPluginPropsRef = useRef3(floatingMenuPluginProps);
1196
+ floatingMenuPluginPropsRef.current = floatingMenuPluginProps;
1197
+ const [pluginInitialized, setPluginInitialized] = useState4(false);
1198
+ const skipFirstUpdateRef = useRef3(true);
1199
+ useEffect4(() => {
1200
+ if (pluginEditor == null ? void 0 : pluginEditor.isDestroyed) {
1201
+ return;
1202
+ }
1203
+ if (!pluginEditor) {
1204
+ console.warn(
1205
+ "FloatingMenu component is not rendered inside of an editor component or does not have editor prop."
1206
+ );
1207
+ return;
1208
+ }
1209
+ const floatingMenuElement = menuEl.current;
1210
+ floatingMenuElement.style.visibility = "hidden";
1211
+ floatingMenuElement.style.position = "absolute";
1212
+ const plugin = FloatingMenuPlugin({
1213
+ ...floatingMenuPluginPropsRef.current,
1214
+ editor: pluginEditor,
1215
+ element: floatingMenuElement
1216
+ });
1217
+ pluginEditor.registerPlugin(plugin);
1218
+ const createdPluginKey = floatingMenuPluginPropsRef.current.pluginKey;
1219
+ skipFirstUpdateRef.current = true;
1220
+ setPluginInitialized(true);
1221
+ return () => {
1222
+ setPluginInitialized(false);
1223
+ pluginEditor.unregisterPlugin(createdPluginKey);
1224
+ window.requestAnimationFrame(() => {
1225
+ if (floatingMenuElement.parentNode) {
1226
+ floatingMenuElement.parentNode.removeChild(floatingMenuElement);
1227
+ }
1228
+ });
1229
+ };
1230
+ }, [pluginEditor]);
1231
+ useEffect4(() => {
1232
+ if (!pluginInitialized || !pluginEditor || pluginEditor.isDestroyed) {
1233
+ return;
1234
+ }
1235
+ if (skipFirstUpdateRef.current) {
1236
+ skipFirstUpdateRef.current = false;
1237
+ return;
1238
+ }
1239
+ pluginEditor.view.dispatch(
1240
+ pluginEditor.state.tr.setMeta("floatingMenu", {
1241
+ type: "updateOptions",
1242
+ options: floatingMenuPluginPropsRef.current
1243
+ })
1244
+ );
1245
+ }, [pluginInitialized, pluginEditor, updateDelay, resizeDelay, shouldShow, options, appendTo]);
1246
+ return createPortal2(/* @__PURE__ */ jsx9("div", { ...restProps, children }), menuEl.current);
1247
+ }
1248
+ );
1249
+
1250
+ // src/Tiptap.tsx
1251
+ import { jsx as jsx10 } from "react/jsx-runtime";
1252
+ var TiptapContext = createContext3({
1253
+ editor: null,
1254
+ isReady: false
1255
+ });
1256
+ TiptapContext.displayName = "TiptapContext";
1257
+ var useTiptap = () => useContext3(TiptapContext);
1258
+ function useTiptapState(selector, equalityFn) {
1259
+ const { editor } = useTiptap();
1260
+ return useEditorState({
1261
+ editor,
1262
+ selector,
1263
+ equalityFn
1264
+ });
1265
+ }
1266
+ function TiptapWrapper({ instance, children }) {
1267
+ var _a;
1268
+ const [isReady, setIsReady] = useState5((_a = instance == null ? void 0 : instance.isInitialized) != null ? _a : false);
1269
+ useEffect5(() => {
1270
+ if (!instance) {
1271
+ setIsReady(false);
1272
+ return;
1273
+ }
1274
+ if (instance.isInitialized) {
1275
+ setIsReady(true);
1276
+ return;
1277
+ }
1278
+ const handleCreate = () => {
1279
+ setIsReady(true);
1280
+ };
1281
+ instance.on("create", handleCreate);
1282
+ return () => {
1283
+ instance.off("create", handleCreate);
1284
+ };
1285
+ }, [instance]);
1286
+ const tiptapContextValue = useMemo2(() => ({ editor: instance, isReady }), [instance, isReady]);
1287
+ const legacyContextValue = useMemo2(() => ({ editor: instance }), [instance]);
1288
+ return /* @__PURE__ */ jsx10(EditorContext.Provider, { value: legacyContextValue, children: /* @__PURE__ */ jsx10(TiptapContext.Provider, { value: tiptapContextValue, children }) });
1289
+ }
1290
+ TiptapWrapper.displayName = "Tiptap";
1291
+ function TiptapContent({ ...rest }) {
1292
+ const { editor } = useTiptap();
1293
+ return /* @__PURE__ */ jsx10(EditorContent, { editor, ...rest });
1294
+ }
1295
+ TiptapContent.displayName = "Tiptap.Content";
1296
+ function TiptapLoading({ children }) {
1297
+ const { isReady } = useTiptap();
1298
+ if (isReady) {
1299
+ return null;
1300
+ }
1301
+ return children;
1302
+ }
1303
+ TiptapLoading.displayName = "Tiptap.Loading";
1304
+ function TiptapBubbleMenu({ children, ...rest }) {
1305
+ const { editor } = useTiptap();
1306
+ if (!editor) {
1307
+ return null;
1308
+ }
1309
+ return /* @__PURE__ */ jsx10(BubbleMenu, { editor, ...rest, children });
1310
+ }
1311
+ TiptapBubbleMenu.displayName = "Tiptap.BubbleMenu";
1312
+ function TiptapFloatingMenu({ children, ...rest }) {
1313
+ const { editor } = useTiptap();
1314
+ if (!editor) {
1315
+ return null;
1316
+ }
1317
+ return /* @__PURE__ */ jsx10(FloatingMenu, { ...rest, editor, children });
1318
+ }
1319
+ TiptapFloatingMenu.displayName = "Tiptap.FloatingMenu";
1320
+ var Tiptap = Object.assign(TiptapWrapper, {
1321
+ /**
1322
+ * The Tiptap Content component that renders the EditorContent with the editor instance from the context.
1323
+ * @see TiptapContent
1324
+ */
1325
+ Content: TiptapContent,
1326
+ /**
1327
+ * The Tiptap Loading component that renders its children only when the editor is not ready.
1328
+ * @see TiptapLoading
1329
+ */
1330
+ Loading: TiptapLoading,
1331
+ /**
1332
+ * The Tiptap BubbleMenu component that wraps the BubbleMenu from Tiptap and provides the editor instance from the context.
1333
+ * @see TiptapBubbleMenu
1334
+ */
1335
+ BubbleMenu: TiptapBubbleMenu,
1336
+ /**
1337
+ * The Tiptap FloatingMenu component that wraps the FloatingMenu from Tiptap and provides the editor instance from the context.
1338
+ * @see TiptapFloatingMenu
1339
+ */
1340
+ FloatingMenu: TiptapFloatingMenu
1341
+ });
1342
+
1038
1343
  // src/index.ts
1039
1344
  export * from "@tiptap/core";
1040
1345
  export {
@@ -1054,9 +1359,18 @@ export {
1054
1359
  ReactNodeViewContext,
1055
1360
  ReactNodeViewRenderer,
1056
1361
  ReactRenderer,
1362
+ Tiptap,
1363
+ TiptapBubbleMenu,
1364
+ TiptapContent,
1365
+ TiptapContext,
1366
+ TiptapFloatingMenu,
1367
+ TiptapLoading,
1368
+ TiptapWrapper,
1057
1369
  useCurrentEditor,
1058
1370
  useEditor,
1059
1371
  useEditorState,
1060
- useReactNodeView
1372
+ useReactNodeView,
1373
+ useTiptap,
1374
+ useTiptapState
1061
1375
  };
1062
1376
  //# sourceMappingURL=index.js.map