@liveblocks/react-tiptap 2.16.0-toolbars1 → 2.16.0-toolbars2

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.d.mts CHANGED
@@ -121,7 +121,9 @@ interface FloatingToolbarProps extends Omit<ComponentProps<"div">, "children"> {
121
121
  before?: ToolbarSlot;
122
122
  after?: ToolbarSlot;
123
123
  }
124
- declare const FloatingToolbar: react.ForwardRefExoticComponent<Omit<FloatingToolbarProps, "ref"> & react.RefAttributes<HTMLDivElement>>;
124
+ declare const FloatingToolbar: react.ForwardRefExoticComponent<Omit<FloatingToolbarProps, "ref"> & react.RefAttributes<HTMLDivElement>> & {
125
+ External: react.ForwardRefExoticComponent<Omit<react.DetailedHTMLProps<react.HTMLAttributes<HTMLDivElement>, HTMLDivElement>, "ref"> & react.RefAttributes<HTMLDivElement>>;
126
+ };
125
127
 
126
128
  interface HistoryVersionPreviewProps extends ComponentPropsWithoutRef<"div"> {
127
129
  version: HistoryVersion;
package/dist/index.d.ts CHANGED
@@ -121,7 +121,9 @@ interface FloatingToolbarProps extends Omit<ComponentProps<"div">, "children"> {
121
121
  before?: ToolbarSlot;
122
122
  after?: ToolbarSlot;
123
123
  }
124
- declare const FloatingToolbar: react.ForwardRefExoticComponent<Omit<FloatingToolbarProps, "ref"> & react.RefAttributes<HTMLDivElement>>;
124
+ declare const FloatingToolbar: react.ForwardRefExoticComponent<Omit<FloatingToolbarProps, "ref"> & react.RefAttributes<HTMLDivElement>> & {
125
+ External: react.ForwardRefExoticComponent<Omit<react.DetailedHTMLProps<react.HTMLAttributes<HTMLDivElement>, HTMLDivElement>, "ref"> & react.RefAttributes<HTMLDivElement>>;
126
+ };
125
127
 
126
128
  interface HistoryVersionPreviewProps extends ComponentPropsWithoutRef<"div"> {
127
129
  version: HistoryVersion;
@@ -9,7 +9,7 @@ var react = require('react');
9
9
  var reactDom$1 = require('react-dom');
10
10
  var classnames = require('../classnames.js');
11
11
  var context = require('../context.js');
12
- var FloatingToolbarContext = require('./FloatingToolbarContext.js');
12
+ var shared = require('./shared.js');
13
13
  var Toolbar = require('./Toolbar.js');
14
14
 
15
15
  const FLOATING_TOOLBAR_COLLISION_PADDING = 10;
@@ -29,270 +29,295 @@ function DefaultFloatingToolbarContent({ editor }) {
29
29
  ]
30
30
  });
31
31
  }
32
- const FloatingToolbar = react.forwardRef(
33
- ({
34
- children = DefaultFloatingToolbarContent,
35
- before,
36
- after,
37
- position = "top",
38
- offset: sideOffset = 6,
39
- editor,
40
- onPointerDown,
41
- onFocus,
42
- onBlur,
43
- className,
44
- ...props
45
- }, forwardedRef) => {
46
- const toolbarRef = react.useRef(null);
47
- const [isPointerDown, setPointerDown] = react.useState(false);
48
- const [isFocused, setFocused] = react.useState(false);
49
- const [isManuallyClosed, setManuallyClosed] = react.useState(false);
50
- const isEditable = react$1.useEditorState({
32
+ const FloatingToolbar = Object.assign(
33
+ react.forwardRef(
34
+ ({
35
+ children = DefaultFloatingToolbarContent,
36
+ before,
37
+ after,
38
+ position = "top",
39
+ offset: sideOffset = 6,
51
40
  editor,
52
- equalityFn: Object.is,
53
- selector: (ctx) => ctx.editor?.isEditable ?? false
54
- }) ?? false;
55
- const hasSelectionRange = react$1.useEditorState({
56
- editor,
57
- equalityFn: Object.is,
58
- selector: (ctx) => {
59
- const editor2 = ctx.editor;
60
- if (!editor2) {
61
- return false;
62
- }
63
- const { doc, selection } = editor2.state;
64
- const { empty, ranges } = selection;
65
- const from = Math.min(...ranges.map((range) => range.$from.pos));
66
- const to = Math.max(...ranges.map((range) => range.$to.pos));
67
- if (empty) {
68
- return false;
41
+ onPointerDown,
42
+ onFocus,
43
+ onBlur,
44
+ className,
45
+ ...props
46
+ }, forwardedRef) => {
47
+ const toolbarRef = react.useRef(null);
48
+ const externalIds = _private.useInitial(() => /* @__PURE__ */ new Set());
49
+ const [isPointerDown, setPointerDown] = react.useState(false);
50
+ const [isFocused, setFocused] = react.useState(false);
51
+ const [isManuallyClosed, setManuallyClosed] = react.useState(false);
52
+ const isEditable = react$1.useEditorState({
53
+ editor,
54
+ equalityFn: Object.is,
55
+ selector: (ctx) => ctx.editor?.isEditable ?? false
56
+ }) ?? false;
57
+ const hasSelectionRange = react$1.useEditorState({
58
+ editor,
59
+ equalityFn: Object.is,
60
+ selector: (ctx) => {
61
+ const editor2 = ctx.editor;
62
+ if (!editor2) {
63
+ return false;
64
+ }
65
+ const { doc, selection } = editor2.state;
66
+ const { empty, ranges } = selection;
67
+ const from = Math.min(...ranges.map((range) => range.$from.pos));
68
+ const to = Math.max(...ranges.map((range) => range.$to.pos));
69
+ if (empty) {
70
+ return false;
71
+ }
72
+ return react$1.isTextSelection(selection) && doc.textBetween(from, to).length > 0;
69
73
  }
70
- return react$1.isTextSelection(selection) && doc.textBetween(from, to).length > 0;
71
- }
72
- }) ?? false;
73
- const isOpen = isFocused && !isPointerDown && hasSelectionRange && !isManuallyClosed;
74
- const [delayedIsOpen, setDelayedIsOpen] = react.useState(isOpen);
75
- const delayedIsOpenTimeoutRef = react.useRef();
76
- react.useEffect(() => {
77
- if (!editor) {
78
- return;
79
- }
80
- setManuallyClosed(false);
81
- const handleSelectionChange = () => {
82
- setManuallyClosed(false);
83
- };
84
- editor.on("selectionUpdate", handleSelectionChange);
85
- return () => {
86
- editor.off("selectionUpdate", handleSelectionChange);
87
- };
88
- }, [isFocused, hasSelectionRange, editor]);
89
- react.useEffect(() => {
90
- if (!editor) {
91
- return;
92
- }
93
- const handleFocus2 = () => {
94
- setFocused(true);
95
- };
96
- const handleBlur2 = (event) => {
97
- if (event.relatedTarget && toolbarRef.current?.contains(event.relatedTarget)) {
74
+ }) ?? false;
75
+ const isOpen = isFocused && !isPointerDown && hasSelectionRange && !isManuallyClosed;
76
+ const [delayedIsOpen, setDelayedIsOpen] = react.useState(isOpen);
77
+ const delayedIsOpenTimeoutRef = react.useRef();
78
+ react.useEffect(() => {
79
+ if (!editor) {
98
80
  return;
99
81
  }
100
- if (event.relatedTarget === editor.view.dom) {
82
+ setManuallyClosed(false);
83
+ const handleSelectionChange = () => {
84
+ setManuallyClosed(false);
85
+ };
86
+ editor.on("selectionUpdate", handleSelectionChange);
87
+ return () => {
88
+ editor.off("selectionUpdate", handleSelectionChange);
89
+ };
90
+ }, [isFocused, hasSelectionRange, editor]);
91
+ react.useEffect(() => {
92
+ if (!editor) {
101
93
  return;
102
94
  }
103
- setFocused(false);
104
- };
105
- editor.view.dom.addEventListener("focus", handleFocus2);
106
- editor.view.dom.addEventListener("blur", handleBlur2);
107
- return () => {
108
- editor.view.dom.removeEventListener("focus", handleFocus2);
109
- editor.view.dom.removeEventListener("blur", handleBlur2);
110
- };
111
- }, [editor]);
112
- const handleFocus = react.useCallback(
113
- (event) => {
114
- onFocus?.(event);
115
- if (!event.isDefaultPrevented()) {
95
+ const handleFocus2 = () => {
116
96
  setFocused(true);
117
- }
118
- },
119
- [onFocus]
120
- );
121
- const handleBlur = react.useCallback(
122
- (event) => {
123
- onBlur?.(event);
124
- if (!event.isDefaultPrevented()) {
97
+ };
98
+ const handleBlur2 = (event) => {
125
99
  if (event.relatedTarget && toolbarRef.current?.contains(event.relatedTarget)) {
126
100
  return;
127
101
  }
128
- if (event.relatedTarget === editor?.view.dom) {
102
+ if (event.relatedTarget === editor.view.dom) {
129
103
  return;
130
104
  }
105
+ for (const externalId of externalIds) {
106
+ if (document.getElementById(externalId)?.contains(event.relatedTarget)) {
107
+ return;
108
+ }
109
+ }
131
110
  setFocused(false);
111
+ };
112
+ editor.view.dom.addEventListener("focus", handleFocus2);
113
+ editor.view.dom.addEventListener("blur", handleBlur2);
114
+ return () => {
115
+ editor.view.dom.removeEventListener("focus", handleFocus2);
116
+ editor.view.dom.removeEventListener("blur", handleBlur2);
117
+ };
118
+ }, [editor, externalIds]);
119
+ const handleFocus = react.useCallback(
120
+ (event) => {
121
+ onFocus?.(event);
122
+ if (!event.isDefaultPrevented()) {
123
+ setFocused(true);
124
+ }
125
+ },
126
+ [onFocus]
127
+ );
128
+ const handleBlur = react.useCallback(
129
+ (event) => {
130
+ onBlur?.(event);
131
+ if (!event.isDefaultPrevented()) {
132
+ if (event.relatedTarget && toolbarRef.current?.contains(event.relatedTarget)) {
133
+ return;
134
+ }
135
+ if (event.relatedTarget === editor?.view.dom) {
136
+ return;
137
+ }
138
+ for (const externalId of externalIds) {
139
+ if (document.getElementById(externalId)?.contains(event.relatedTarget)) {
140
+ return;
141
+ }
142
+ }
143
+ setFocused(false);
144
+ }
145
+ },
146
+ [onBlur, editor, externalIds]
147
+ );
148
+ react.useEffect(() => {
149
+ if (isOpen) {
150
+ delayedIsOpenTimeoutRef.current = window.setTimeout(() => {
151
+ setDelayedIsOpen(true);
152
+ }, FLOATING_TOOLBAR_OPEN_DELAY);
153
+ } else {
154
+ setDelayedIsOpen(false);
132
155
  }
133
- },
134
- [onBlur, editor]
135
- );
136
- react.useEffect(() => {
137
- if (isOpen) {
138
- delayedIsOpenTimeoutRef.current = window.setTimeout(() => {
139
- setDelayedIsOpen(true);
140
- }, FLOATING_TOOLBAR_OPEN_DELAY);
141
- } else {
142
- setDelayedIsOpen(false);
143
- }
144
- return () => {
145
- window.clearTimeout(delayedIsOpenTimeoutRef.current);
146
- };
147
- }, [isOpen]);
148
- const floatingOptions = react.useMemo(() => {
149
- const detectOverflowOptions = {
150
- padding: FLOATING_TOOLBAR_COLLISION_PADDING
151
- };
152
- return {
153
- strategy: "fixed",
154
- placement: position,
155
- middleware: [
156
- reactDom.inline(detectOverflowOptions),
157
- reactDom.flip({ ...detectOverflowOptions, crossAxis: false }),
158
- reactDom.hide(detectOverflowOptions),
159
- reactDom.shift({
160
- ...detectOverflowOptions,
161
- limiter: reactDom.limitShift()
162
- }),
163
- reactDom.offset(sideOffset),
164
- reactDom.size(detectOverflowOptions)
165
- ],
166
- whileElementsMounted: (...args) => {
167
- return reactDom.autoUpdate(...args, {
168
- animationFrame: true
169
- });
156
+ return () => {
157
+ window.clearTimeout(delayedIsOpenTimeoutRef.current);
158
+ };
159
+ }, [isOpen]);
160
+ const floatingOptions = react.useMemo(() => {
161
+ const detectOverflowOptions = {
162
+ padding: FLOATING_TOOLBAR_COLLISION_PADDING
163
+ };
164
+ return {
165
+ strategy: "fixed",
166
+ placement: position,
167
+ middleware: [
168
+ reactDom.inline(detectOverflowOptions),
169
+ reactDom.flip({ ...detectOverflowOptions, crossAxis: false }),
170
+ reactDom.hide(detectOverflowOptions),
171
+ reactDom.shift({
172
+ ...detectOverflowOptions,
173
+ limiter: reactDom.limitShift()
174
+ }),
175
+ reactDom.offset(sideOffset),
176
+ reactDom.size(detectOverflowOptions)
177
+ ],
178
+ whileElementsMounted: (...args) => {
179
+ return reactDom.autoUpdate(...args, {
180
+ animationFrame: true
181
+ });
182
+ }
183
+ };
184
+ }, [position, sideOffset]);
185
+ const {
186
+ refs: { setReference, setFloating },
187
+ strategy,
188
+ x,
189
+ y,
190
+ isPositioned
191
+ } = reactDom.useFloating({
192
+ ...floatingOptions,
193
+ open: delayedIsOpen
194
+ });
195
+ const mergedRefs = _private.useRefs(forwardedRef, toolbarRef, setFloating);
196
+ const handlePointerDown = react.useCallback(
197
+ (event) => {
198
+ onPointerDown?.(event);
199
+ event.stopPropagation();
200
+ if (event.target === toolbarRef.current) {
201
+ event.preventDefault();
202
+ }
203
+ },
204
+ [onPointerDown]
205
+ );
206
+ react.useEffect(() => {
207
+ if (!editor || !isEditable) {
208
+ return;
170
209
  }
171
- };
172
- }, [position, sideOffset]);
173
- const {
174
- refs: { setReference, setFloating },
175
- strategy,
176
- x,
177
- y,
178
- isPositioned
179
- } = reactDom.useFloating({
180
- ...floatingOptions,
181
- open: delayedIsOpen
182
- });
183
- const mergedRefs = _private.useRefs(forwardedRef, toolbarRef, setFloating);
184
- const handlePointerDown = react.useCallback(
185
- (event) => {
186
- onPointerDown?.(event);
187
- event.stopPropagation();
188
- if (event.target === toolbarRef.current) {
189
- event.preventDefault();
210
+ const handlePointerDown2 = () => {
211
+ setPointerDown(true);
212
+ };
213
+ const handlePointerUp = () => {
214
+ setPointerDown(false);
215
+ };
216
+ document.addEventListener("pointerdown", handlePointerDown2);
217
+ document.addEventListener("pointercancel", handlePointerUp);
218
+ document.addEventListener("pointerup", handlePointerUp);
219
+ return () => {
220
+ document.removeEventListener("pointerdown", handlePointerDown2);
221
+ document.removeEventListener("pointercancel", handlePointerUp);
222
+ document.removeEventListener("pointerup", handlePointerUp);
223
+ };
224
+ }, [editor, isEditable]);
225
+ _private$1.useLayoutEffect(() => {
226
+ if (!editor || !delayedIsOpen) {
227
+ return;
190
228
  }
191
- },
192
- [onPointerDown]
193
- );
194
- react.useEffect(() => {
195
- if (!editor || !isEditable) {
196
- return;
197
- }
198
- const handlePointerDown2 = () => {
199
- setPointerDown(true);
200
- };
201
- const handlePointerUp = () => {
202
- setPointerDown(false);
203
- };
204
- document.addEventListener("pointerdown", handlePointerDown2);
205
- document.addEventListener("pointercancel", handlePointerUp);
206
- document.addEventListener("pointerup", handlePointerUp);
207
- return () => {
208
- document.removeEventListener("pointerdown", handlePointerDown2);
209
- document.removeEventListener("pointercancel", handlePointerUp);
210
- document.removeEventListener("pointerup", handlePointerUp);
211
- };
212
- }, [editor, isEditable]);
213
- _private$1.useLayoutEffect(() => {
214
- if (!editor || !delayedIsOpen) {
215
- return;
216
- }
217
- const updateSelectionReference = () => {
218
- const domSelection = window.getSelection();
219
- if (editor.state.selection.empty || !domSelection || !domSelection.rangeCount) {
220
- setReference(null);
221
- } else {
222
- const domRange = domSelection.getRangeAt(0);
223
- setReference(domRange);
229
+ const updateSelectionReference = () => {
230
+ const domSelection = window.getSelection();
231
+ if (editor.state.selection.empty || !domSelection || !domSelection.rangeCount) {
232
+ setReference(null);
233
+ } else {
234
+ const domRange = domSelection.getRangeAt(0);
235
+ setReference(domRange);
236
+ }
237
+ };
238
+ editor.on("transaction", updateSelectionReference);
239
+ updateSelectionReference();
240
+ return () => {
241
+ editor.off("transaction", updateSelectionReference);
242
+ };
243
+ }, [editor, delayedIsOpen, setReference]);
244
+ react.useEffect(() => {
245
+ if (!editor || !delayedIsOpen) {
246
+ return;
224
247
  }
225
- };
226
- editor.on("transaction", updateSelectionReference);
227
- updateSelectionReference();
228
- return () => {
229
- editor.off("transaction", updateSelectionReference);
230
- };
231
- }, [editor, delayedIsOpen, setReference]);
232
- react.useEffect(() => {
248
+ const handleKeyDown = (event) => {
249
+ if (event.target !== editor.view.dom && event.defaultPrevented) {
250
+ return;
251
+ }
252
+ if (event.key === "Escape") {
253
+ event.preventDefault();
254
+ event.stopPropagation();
255
+ editor.commands.focus();
256
+ setManuallyClosed(true);
257
+ }
258
+ };
259
+ editor.view.dom.addEventListener("keydown", handleKeyDown);
260
+ return () => {
261
+ editor.view.dom.removeEventListener("keydown", handleKeyDown);
262
+ };
263
+ }, [editor, delayedIsOpen]);
264
+ const close = react.useCallback(() => {
265
+ setManuallyClosed(true);
266
+ }, [setManuallyClosed]);
267
+ const registerExternal = react.useCallback(
268
+ (id) => {
269
+ externalIds.add(id);
270
+ return () => {
271
+ externalIds.delete(id);
272
+ };
273
+ },
274
+ [externalIds]
275
+ );
233
276
  if (!editor || !delayedIsOpen) {
234
- return;
277
+ return null;
235
278
  }
236
- const handleKeyDown = (event) => {
237
- if (event.target !== editor.view.dom && event.defaultPrevented) {
238
- return;
239
- }
240
- if (event.key === "Escape") {
241
- event.preventDefault();
242
- event.stopPropagation();
243
- editor.commands.focus();
244
- setManuallyClosed(true);
245
- }
246
- };
247
- editor.view.dom.addEventListener("keydown", handleKeyDown);
248
- return () => {
249
- editor.view.dom.removeEventListener("keydown", handleKeyDown);
250
- };
251
- }, [editor, delayedIsOpen]);
252
- const close = react.useCallback(() => {
253
- setManuallyClosed(true);
254
- }, [setManuallyClosed]);
255
- if (!editor || !delayedIsOpen) {
256
- return null;
257
- }
258
- const slotProps = { editor };
259
- return reactDom$1.createPortal(
260
- /* @__PURE__ */ jsxRuntime.jsx(_private.TooltipProvider, {
261
- children: /* @__PURE__ */ jsxRuntime.jsx(context.EditorProvider, {
262
- editor,
263
- children: /* @__PURE__ */ jsxRuntime.jsx(FloatingToolbarContext.FloatingToolbarContext.Provider, {
264
- value: { close },
265
- children: /* @__PURE__ */ jsxRuntime.jsxs("div", {
266
- role: "toolbar",
267
- "aria-label": "Floating toolbar",
268
- "aria-orientation": "horizontal",
269
- className: classnames.classNames(
270
- "lb-root lb-portal lb-elevation lb-tiptap-floating-toolbar lb-tiptap-toolbar",
271
- className
272
- ),
273
- ref: mergedRefs,
274
- style: {
275
- position: strategy,
276
- top: 0,
277
- left: 0,
278
- transform: isPositioned ? `translate3d(${Math.round(x)}px, ${Math.round(y)}px, 0)` : "translate3d(0, -200%, 0)",
279
- minWidth: "max-content"
280
- },
281
- onPointerDown: handlePointerDown,
282
- onFocus: handleFocus,
283
- onBlur: handleBlur,
284
- ...props,
285
- children: [
286
- Toolbar.applyToolbarSlot(before, slotProps),
287
- Toolbar.applyToolbarSlot(children, slotProps),
288
- Toolbar.applyToolbarSlot(after, slotProps)
289
- ]
279
+ const slotProps = { editor };
280
+ return reactDom$1.createPortal(
281
+ /* @__PURE__ */ jsxRuntime.jsx(_private.TooltipProvider, {
282
+ children: /* @__PURE__ */ jsxRuntime.jsx(context.EditorProvider, {
283
+ editor,
284
+ children: /* @__PURE__ */ jsxRuntime.jsx(shared.FloatingToolbarContext.Provider, {
285
+ value: { close, registerExternal },
286
+ children: /* @__PURE__ */ jsxRuntime.jsxs("div", {
287
+ role: "toolbar",
288
+ "aria-label": "Floating toolbar",
289
+ "aria-orientation": "horizontal",
290
+ className: classnames.classNames(
291
+ "lb-root lb-portal lb-elevation lb-tiptap-floating-toolbar lb-tiptap-toolbar",
292
+ className
293
+ ),
294
+ ref: mergedRefs,
295
+ style: {
296
+ position: strategy,
297
+ top: 0,
298
+ left: 0,
299
+ transform: isPositioned ? `translate3d(${Math.round(x)}px, ${Math.round(y)}px, 0)` : "translate3d(0, -200%, 0)",
300
+ minWidth: "max-content"
301
+ },
302
+ onPointerDown: handlePointerDown,
303
+ onFocus: handleFocus,
304
+ onBlur: handleBlur,
305
+ ...props,
306
+ children: [
307
+ Toolbar.applyToolbarSlot(before, slotProps),
308
+ Toolbar.applyToolbarSlot(children, slotProps),
309
+ Toolbar.applyToolbarSlot(after, slotProps)
310
+ ]
311
+ })
290
312
  })
291
313
  })
292
- })
293
- }),
294
- document.body
295
- );
314
+ }),
315
+ document.body
316
+ );
317
+ }
318
+ ),
319
+ {
320
+ External: shared.FloatingToolbarExternal
296
321
  }
297
322
  );
298
323