@tiptap/react 3.17.1 → 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.cjs +327 -4
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +269 -1
- package/dist/index.d.ts +269 -1
- package/dist/index.js +318 -4
- package/dist/index.js.map +1 -1
- package/dist/menus/index.cjs +72 -17
- package/dist/menus/index.cjs.map +1 -1
- package/dist/menus/index.js +74 -19
- package/dist/menus/index.js.map +1 -1
- package/package.json +7 -7
- package/src/ReactNodeViewRenderer.tsx +29 -3
- package/src/Tiptap.tsx +355 -0
- package/src/index.ts +1 -0
- package/src/menus/BubbleMenu.tsx +49 -1
- package/src/menus/FloatingMenu.tsx +76 -20
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.
|
|
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
|