@blockslides/react 0.1.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 ADDED
@@ -0,0 +1,1042 @@
1
+ // src/Context.tsx
2
+ import { createContext, useContext, useMemo } from "react";
3
+
4
+ // src/EditorContent.tsx
5
+ import React, { forwardRef } from "react";
6
+ import ReactDOM from "react-dom";
7
+ import { useSyncExternalStore } from "use-sync-external-store/shim/index.js";
8
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
9
+ var mergeRefs = (...refs) => {
10
+ return (node) => {
11
+ refs.forEach((ref) => {
12
+ if (typeof ref === "function") {
13
+ ref(node);
14
+ } else if (ref) {
15
+ ;
16
+ ref.current = node;
17
+ }
18
+ });
19
+ };
20
+ };
21
+ var Portals = ({ contentComponent }) => {
22
+ const renderers = useSyncExternalStore(
23
+ contentComponent.subscribe,
24
+ contentComponent.getSnapshot,
25
+ contentComponent.getServerSnapshot
26
+ );
27
+ return /* @__PURE__ */ jsx(Fragment, { children: Object.values(renderers) });
28
+ };
29
+ function getInstance() {
30
+ const subscribers = /* @__PURE__ */ new Set();
31
+ let renderers = {};
32
+ return {
33
+ /**
34
+ * Subscribe to the editor instance's changes.
35
+ */
36
+ subscribe(callback) {
37
+ subscribers.add(callback);
38
+ return () => {
39
+ subscribers.delete(callback);
40
+ };
41
+ },
42
+ getSnapshot() {
43
+ return renderers;
44
+ },
45
+ getServerSnapshot() {
46
+ return renderers;
47
+ },
48
+ /**
49
+ * Adds a new NodeView Renderer to the editor.
50
+ */
51
+ setRenderer(id, renderer) {
52
+ renderers = {
53
+ ...renderers,
54
+ [id]: ReactDOM.createPortal(renderer.reactElement, renderer.element, id)
55
+ };
56
+ subscribers.forEach((subscriber) => subscriber());
57
+ },
58
+ /**
59
+ * Removes a NodeView Renderer from the editor.
60
+ */
61
+ removeRenderer(id) {
62
+ const nextRenderers = { ...renderers };
63
+ delete nextRenderers[id];
64
+ renderers = nextRenderers;
65
+ subscribers.forEach((subscriber) => subscriber());
66
+ }
67
+ };
68
+ }
69
+ var PureEditorContent = class extends React.Component {
70
+ constructor(props) {
71
+ var _a;
72
+ super(props);
73
+ this.editorContentRef = React.createRef();
74
+ this.initialized = false;
75
+ this.state = {
76
+ hasContentComponentInitialized: Boolean((_a = props.editor) == null ? void 0 : _a.contentComponent)
77
+ };
78
+ }
79
+ componentDidMount() {
80
+ this.init();
81
+ }
82
+ componentDidUpdate() {
83
+ this.init();
84
+ }
85
+ init() {
86
+ const editor = this.props.editor;
87
+ if (editor && !editor.isDestroyed && editor.options.element) {
88
+ if (editor.contentComponent) {
89
+ return;
90
+ }
91
+ const element = this.editorContentRef.current;
92
+ element.append(editor.view.dom);
93
+ editor.setOptions({
94
+ element
95
+ });
96
+ editor.contentComponent = getInstance();
97
+ if (!this.state.hasContentComponentInitialized) {
98
+ this.unsubscribeToContentComponent = editor.contentComponent.subscribe(() => {
99
+ this.setState((prevState) => {
100
+ if (!prevState.hasContentComponentInitialized) {
101
+ return {
102
+ hasContentComponentInitialized: true
103
+ };
104
+ }
105
+ return prevState;
106
+ });
107
+ if (this.unsubscribeToContentComponent) {
108
+ this.unsubscribeToContentComponent();
109
+ }
110
+ });
111
+ }
112
+ editor.createNodeViews();
113
+ this.initialized = true;
114
+ }
115
+ }
116
+ componentWillUnmount() {
117
+ var _a;
118
+ const editor = this.props.editor;
119
+ if (!editor) {
120
+ return;
121
+ }
122
+ this.initialized = false;
123
+ if (!editor.isDestroyed) {
124
+ editor.view.setProps({
125
+ nodeViews: {}
126
+ });
127
+ }
128
+ if (this.unsubscribeToContentComponent) {
129
+ this.unsubscribeToContentComponent();
130
+ }
131
+ editor.contentComponent = null;
132
+ try {
133
+ if (!((_a = editor.view.dom) == null ? void 0 : _a.firstChild)) {
134
+ return;
135
+ }
136
+ const newElement = document.createElement("div");
137
+ newElement.append(editor.view.dom);
138
+ editor.setOptions({
139
+ element: newElement
140
+ });
141
+ } catch {
142
+ }
143
+ }
144
+ render() {
145
+ const { editor, innerRef, ...rest } = this.props;
146
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
147
+ /* @__PURE__ */ jsx("div", { ref: mergeRefs(innerRef, this.editorContentRef), ...rest }),
148
+ (editor == null ? void 0 : editor.contentComponent) && /* @__PURE__ */ jsx(Portals, { contentComponent: editor.contentComponent })
149
+ ] });
150
+ }
151
+ };
152
+ var EditorContentWithKey = forwardRef(
153
+ (props, ref) => {
154
+ const key = React.useMemo(() => {
155
+ return Math.floor(Math.random() * 4294967295).toString();
156
+ }, [props.editor]);
157
+ return React.createElement(PureEditorContent, {
158
+ key,
159
+ innerRef: ref,
160
+ ...props
161
+ });
162
+ }
163
+ );
164
+ var EditorContent = React.memo(EditorContentWithKey);
165
+
166
+ // src/useEditor.ts
167
+ import { Editor } from "@blockslides/core";
168
+ import { useDebugValue as useDebugValue2, useEffect as useEffect2, useRef, useState as useState2 } from "react";
169
+ import { useSyncExternalStore as useSyncExternalStore2 } from "use-sync-external-store/shim/index.js";
170
+
171
+ // src/useEditorState.ts
172
+ import deepEqual from "fast-deep-equal/es6/react.js";
173
+ import { useDebugValue, useEffect, useLayoutEffect, useState } from "react";
174
+ import { useSyncExternalStoreWithSelector } from "use-sync-external-store/shim/with-selector.js";
175
+ var useIsomorphicLayoutEffect = typeof window !== "undefined" ? useLayoutEffect : useEffect;
176
+ var EditorStateManager = class {
177
+ constructor(initialEditor) {
178
+ this.transactionNumber = 0;
179
+ this.lastTransactionNumber = 0;
180
+ this.subscribers = /* @__PURE__ */ new Set();
181
+ this.editor = initialEditor;
182
+ this.lastSnapshot = { editor: initialEditor, transactionNumber: 0 };
183
+ this.getSnapshot = this.getSnapshot.bind(this);
184
+ this.getServerSnapshot = this.getServerSnapshot.bind(this);
185
+ this.watch = this.watch.bind(this);
186
+ this.subscribe = this.subscribe.bind(this);
187
+ }
188
+ /**
189
+ * Get the current editor instance.
190
+ */
191
+ getSnapshot() {
192
+ if (this.transactionNumber === this.lastTransactionNumber) {
193
+ return this.lastSnapshot;
194
+ }
195
+ this.lastTransactionNumber = this.transactionNumber;
196
+ this.lastSnapshot = {
197
+ editor: this.editor,
198
+ transactionNumber: this.transactionNumber
199
+ };
200
+ return this.lastSnapshot;
201
+ }
202
+ /**
203
+ * Always disable the editor on the server-side.
204
+ */
205
+ getServerSnapshot() {
206
+ return { editor: null, transactionNumber: 0 };
207
+ }
208
+ /**
209
+ * Subscribe to the editor instance's changes.
210
+ */
211
+ subscribe(callback) {
212
+ this.subscribers.add(callback);
213
+ return () => {
214
+ this.subscribers.delete(callback);
215
+ };
216
+ }
217
+ /**
218
+ * Watch the editor instance for changes.
219
+ */
220
+ watch(nextEditor) {
221
+ this.editor = nextEditor;
222
+ if (this.editor) {
223
+ const fn = () => {
224
+ this.transactionNumber += 1;
225
+ this.subscribers.forEach((callback) => callback());
226
+ };
227
+ const currentEditor = this.editor;
228
+ currentEditor.on("transaction", fn);
229
+ return () => {
230
+ currentEditor.off("transaction", fn);
231
+ };
232
+ }
233
+ return void 0;
234
+ }
235
+ };
236
+ function useEditorState(options) {
237
+ var _a;
238
+ const [editorStateManager] = useState(
239
+ () => new EditorStateManager(options.editor)
240
+ );
241
+ const selectedState = useSyncExternalStoreWithSelector(
242
+ editorStateManager.subscribe,
243
+ editorStateManager.getSnapshot,
244
+ editorStateManager.getServerSnapshot,
245
+ options.selector,
246
+ (_a = options.equalityFn) != null ? _a : deepEqual
247
+ );
248
+ useIsomorphicLayoutEffect(() => {
249
+ return editorStateManager.watch(options.editor);
250
+ }, [options.editor, editorStateManager]);
251
+ useDebugValue(selectedState);
252
+ return selectedState;
253
+ }
254
+
255
+ // src/useEditor.ts
256
+ var isDev = process.env.NODE_ENV !== "production";
257
+ var isSSR = typeof window === "undefined";
258
+ var isNext = isSSR || Boolean(typeof window !== "undefined" && window.next);
259
+ var EditorInstanceManager = class _EditorInstanceManager {
260
+ constructor(options) {
261
+ /**
262
+ * The current editor instance.
263
+ */
264
+ this.editor = null;
265
+ /**
266
+ * The subscriptions to notify when the editor instance
267
+ * has been created or destroyed.
268
+ */
269
+ this.subscriptions = /* @__PURE__ */ new Set();
270
+ /**
271
+ * Whether the editor has been mounted.
272
+ */
273
+ this.isComponentMounted = false;
274
+ /**
275
+ * The most recent dependencies array.
276
+ */
277
+ this.previousDeps = null;
278
+ /**
279
+ * The unique instance ID. This is used to identify the editor instance. And will be re-generated for each new instance.
280
+ */
281
+ this.instanceId = "";
282
+ this.options = options;
283
+ this.subscriptions = /* @__PURE__ */ new Set();
284
+ this.setEditor(this.getInitialEditor());
285
+ this.scheduleDestroy();
286
+ this.getEditor = this.getEditor.bind(this);
287
+ this.getServerSnapshot = this.getServerSnapshot.bind(this);
288
+ this.subscribe = this.subscribe.bind(this);
289
+ this.refreshEditorInstance = this.refreshEditorInstance.bind(this);
290
+ this.scheduleDestroy = this.scheduleDestroy.bind(this);
291
+ this.onRender = this.onRender.bind(this);
292
+ this.createEditor = this.createEditor.bind(this);
293
+ }
294
+ setEditor(editor) {
295
+ this.editor = editor;
296
+ this.instanceId = Math.random().toString(36).slice(2, 9);
297
+ this.subscriptions.forEach((cb) => cb());
298
+ }
299
+ getInitialEditor() {
300
+ if (this.options.current.immediatelyRender === void 0) {
301
+ if (isSSR || isNext) {
302
+ if (isDev) {
303
+ throw new Error(
304
+ "Editor Error: SSR has been detected, please set `immediatelyRender` explicitly to `false` to avoid hydration mismatches."
305
+ );
306
+ }
307
+ return null;
308
+ }
309
+ return this.createEditor();
310
+ }
311
+ if (this.options.current.immediatelyRender && isSSR && isDev) {
312
+ throw new Error(
313
+ "Editor Error: SSR has been detected, and `immediatelyRender` has been set to `true` this is an unsupported configuration that may result in errors, explicitly set `immediatelyRender` to `false` to avoid hydration mismatches."
314
+ );
315
+ }
316
+ if (this.options.current.immediatelyRender) {
317
+ return this.createEditor();
318
+ }
319
+ return null;
320
+ }
321
+ /**
322
+ * Create a new editor instance. And attach event listeners.
323
+ */
324
+ createEditor() {
325
+ const optionsToApply = {
326
+ ...this.options.current,
327
+ // Always call the most recent version of the callback function by default
328
+ onBeforeCreate: (...args) => {
329
+ var _a, _b;
330
+ return (_b = (_a = this.options.current).onBeforeCreate) == null ? void 0 : _b.call(_a, ...args);
331
+ },
332
+ onBlur: (...args) => {
333
+ var _a, _b;
334
+ return (_b = (_a = this.options.current).onBlur) == null ? void 0 : _b.call(_a, ...args);
335
+ },
336
+ onCreate: (...args) => {
337
+ var _a, _b;
338
+ return (_b = (_a = this.options.current).onCreate) == null ? void 0 : _b.call(_a, ...args);
339
+ },
340
+ onDestroy: (...args) => {
341
+ var _a, _b;
342
+ return (_b = (_a = this.options.current).onDestroy) == null ? void 0 : _b.call(_a, ...args);
343
+ },
344
+ onFocus: (...args) => {
345
+ var _a, _b;
346
+ return (_b = (_a = this.options.current).onFocus) == null ? void 0 : _b.call(_a, ...args);
347
+ },
348
+ onSelectionUpdate: (...args) => {
349
+ var _a, _b;
350
+ return (_b = (_a = this.options.current).onSelectionUpdate) == null ? void 0 : _b.call(_a, ...args);
351
+ },
352
+ onTransaction: (...args) => {
353
+ var _a, _b;
354
+ return (_b = (_a = this.options.current).onTransaction) == null ? void 0 : _b.call(_a, ...args);
355
+ },
356
+ onUpdate: (...args) => {
357
+ var _a, _b;
358
+ return (_b = (_a = this.options.current).onUpdate) == null ? void 0 : _b.call(_a, ...args);
359
+ },
360
+ onContentError: (...args) => {
361
+ var _a, _b;
362
+ return (_b = (_a = this.options.current).onContentError) == null ? void 0 : _b.call(_a, ...args);
363
+ },
364
+ onDrop: (...args) => {
365
+ var _a, _b;
366
+ return (_b = (_a = this.options.current).onDrop) == null ? void 0 : _b.call(_a, ...args);
367
+ },
368
+ onPaste: (...args) => {
369
+ var _a, _b;
370
+ return (_b = (_a = this.options.current).onPaste) == null ? void 0 : _b.call(_a, ...args);
371
+ },
372
+ onDelete: (...args) => {
373
+ var _a, _b;
374
+ return (_b = (_a = this.options.current).onDelete) == null ? void 0 : _b.call(_a, ...args);
375
+ }
376
+ };
377
+ const editor = new Editor(optionsToApply);
378
+ return editor;
379
+ }
380
+ /**
381
+ * Get the current editor instance.
382
+ */
383
+ getEditor() {
384
+ return this.editor;
385
+ }
386
+ /**
387
+ * Always disable the editor on the server-side.
388
+ */
389
+ getServerSnapshot() {
390
+ return null;
391
+ }
392
+ /**
393
+ * Subscribe to the editor instance's changes.
394
+ */
395
+ subscribe(onStoreChange) {
396
+ this.subscriptions.add(onStoreChange);
397
+ return () => {
398
+ this.subscriptions.delete(onStoreChange);
399
+ };
400
+ }
401
+ static compareOptions(a, b) {
402
+ return Object.keys(a).every((key) => {
403
+ if ([
404
+ "onCreate",
405
+ "onBeforeCreate",
406
+ "onDestroy",
407
+ "onUpdate",
408
+ "onTransaction",
409
+ "onFocus",
410
+ "onBlur",
411
+ "onSelectionUpdate",
412
+ "onContentError",
413
+ "onDrop",
414
+ "onPaste"
415
+ ].includes(key)) {
416
+ return true;
417
+ }
418
+ if (key === "extensions" && a.extensions && b.extensions) {
419
+ if (a.extensions.length !== b.extensions.length) {
420
+ return false;
421
+ }
422
+ return a.extensions.every((extension, index) => {
423
+ var _a;
424
+ if (extension !== ((_a = b.extensions) == null ? void 0 : _a[index])) {
425
+ return false;
426
+ }
427
+ return true;
428
+ });
429
+ }
430
+ if (a[key] !== b[key]) {
431
+ return false;
432
+ }
433
+ return true;
434
+ });
435
+ }
436
+ /**
437
+ * On each render, we will create, update, or destroy the editor instance.
438
+ * @param deps The dependencies to watch for changes
439
+ * @returns A cleanup function
440
+ */
441
+ onRender(deps) {
442
+ return () => {
443
+ this.isComponentMounted = true;
444
+ clearTimeout(this.scheduledDestructionTimeout);
445
+ if (this.editor && !this.editor.isDestroyed && deps.length === 0) {
446
+ if (!_EditorInstanceManager.compareOptions(
447
+ this.options.current,
448
+ this.editor.options
449
+ )) {
450
+ this.editor.setOptions({
451
+ ...this.options.current,
452
+ editable: this.editor.isEditable
453
+ });
454
+ }
455
+ } else {
456
+ this.refreshEditorInstance(deps);
457
+ }
458
+ return () => {
459
+ this.isComponentMounted = false;
460
+ this.scheduleDestroy();
461
+ };
462
+ };
463
+ }
464
+ /**
465
+ * Recreate the editor instance if the dependencies have changed.
466
+ */
467
+ refreshEditorInstance(deps) {
468
+ if (this.editor && !this.editor.isDestroyed) {
469
+ if (this.previousDeps === null) {
470
+ this.previousDeps = deps;
471
+ return;
472
+ }
473
+ const depsAreEqual = this.previousDeps.length === deps.length && this.previousDeps.every((dep, index) => dep === deps[index]);
474
+ if (depsAreEqual) {
475
+ return;
476
+ }
477
+ }
478
+ if (this.editor && !this.editor.isDestroyed) {
479
+ this.editor.destroy();
480
+ }
481
+ this.setEditor(this.createEditor());
482
+ this.previousDeps = deps;
483
+ }
484
+ /**
485
+ * Schedule the destruction of the editor instance.
486
+ * This will only destroy the editor if it was not mounted on the next tick.
487
+ * This is to avoid destroying the editor instance when it's actually still mounted.
488
+ */
489
+ scheduleDestroy() {
490
+ const currentInstanceId = this.instanceId;
491
+ const currentEditor = this.editor;
492
+ this.scheduledDestructionTimeout = setTimeout(() => {
493
+ if (this.isComponentMounted && this.instanceId === currentInstanceId) {
494
+ if (currentEditor) {
495
+ currentEditor.setOptions(this.options.current);
496
+ }
497
+ return;
498
+ }
499
+ if (currentEditor && !currentEditor.isDestroyed) {
500
+ currentEditor.destroy();
501
+ if (this.instanceId === currentInstanceId) {
502
+ this.setEditor(null);
503
+ }
504
+ }
505
+ }, 1);
506
+ }
507
+ };
508
+ function useEditor(options = {}, deps = []) {
509
+ const mostRecentOptions = useRef(options);
510
+ mostRecentOptions.current = options;
511
+ const [instanceManager] = useState2(
512
+ () => new EditorInstanceManager(mostRecentOptions)
513
+ );
514
+ const editor = useSyncExternalStore2(
515
+ instanceManager.subscribe,
516
+ instanceManager.getEditor,
517
+ instanceManager.getServerSnapshot
518
+ );
519
+ useDebugValue2(editor);
520
+ useEffect2(instanceManager.onRender(deps));
521
+ useEditorState({
522
+ editor,
523
+ selector: ({ transactionNumber }) => {
524
+ if (options.shouldRerenderOnTransaction === false || options.shouldRerenderOnTransaction === void 0) {
525
+ return null;
526
+ }
527
+ if (options.immediatelyRender && transactionNumber === 0) {
528
+ return 0;
529
+ }
530
+ return transactionNumber + 1;
531
+ }
532
+ });
533
+ return editor;
534
+ }
535
+
536
+ // src/Context.tsx
537
+ import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
538
+ var EditorContext = createContext({
539
+ editor: null
540
+ });
541
+ var EditorConsumer = EditorContext.Consumer;
542
+ var useCurrentEditor = () => useContext(EditorContext);
543
+ function EditorProvider({
544
+ children,
545
+ slotAfter,
546
+ slotBefore,
547
+ editorContainerProps = {},
548
+ ...editorOptions
549
+ }) {
550
+ const editor = useEditor(editorOptions);
551
+ const contextValue = useMemo(() => ({ editor }), [editor]);
552
+ if (!editor) {
553
+ return null;
554
+ }
555
+ return /* @__PURE__ */ jsxs2(EditorContext.Provider, { value: contextValue, children: [
556
+ slotBefore,
557
+ /* @__PURE__ */ jsx2(EditorConsumer, { children: ({ editor: currentEditor }) => /* @__PURE__ */ jsx2(EditorContent, { editor: currentEditor, ...editorContainerProps }) }),
558
+ children,
559
+ slotAfter
560
+ ] });
561
+ }
562
+
563
+ // src/useReactNodeView.ts
564
+ import { createContext as createContext2, createElement, useContext as useContext2 } from "react";
565
+ var ReactNodeViewContext = createContext2({
566
+ onDragStart: () => {
567
+ },
568
+ nodeViewContentChildren: void 0,
569
+ nodeViewContentRef: () => {
570
+ }
571
+ });
572
+ var ReactNodeViewContentProvider = ({ children, content }) => {
573
+ return createElement(ReactNodeViewContext.Provider, { value: { nodeViewContentChildren: content } }, children);
574
+ };
575
+ var useReactNodeView = () => useContext2(ReactNodeViewContext);
576
+
577
+ // src/NodeViewContent.tsx
578
+ import { jsx as jsx3 } from "react/jsx-runtime";
579
+ function NodeViewContent({
580
+ as: Tag = "div",
581
+ ...props
582
+ }) {
583
+ const { nodeViewContentRef, nodeViewContentChildren } = useReactNodeView();
584
+ return (
585
+ // @ts-ignore
586
+ /* @__PURE__ */ jsx3(
587
+ Tag,
588
+ {
589
+ ...props,
590
+ ref: nodeViewContentRef,
591
+ "data-node-view-content": "",
592
+ style: {
593
+ whiteSpace: "pre-wrap",
594
+ ...props.style
595
+ },
596
+ children: nodeViewContentChildren
597
+ }
598
+ )
599
+ );
600
+ }
601
+
602
+ // src/NodeViewWrapper.tsx
603
+ import React3 from "react";
604
+ import { jsx as jsx4 } from "react/jsx-runtime";
605
+ var NodeViewWrapper = React3.forwardRef((props, ref) => {
606
+ const { onDragStart } = useReactNodeView();
607
+ const Tag = props.as || "div";
608
+ return (
609
+ // @ts-ignore
610
+ /* @__PURE__ */ jsx4(
611
+ Tag,
612
+ {
613
+ ...props,
614
+ ref,
615
+ "data-node-view-wrapper": "",
616
+ onDragStart,
617
+ style: {
618
+ whiteSpace: "normal",
619
+ ...props.style
620
+ }
621
+ }
622
+ )
623
+ );
624
+ });
625
+
626
+ // src/ReactMarkViewRenderer.tsx
627
+ import { MarkView } from "@blockslides/core";
628
+ import React4 from "react";
629
+
630
+ // src/ReactRenderer.tsx
631
+ import { version as reactVersion } from "react";
632
+ import { flushSync } from "react-dom";
633
+ import { jsx as jsx5 } from "react/jsx-runtime";
634
+ function isClassComponent(Component) {
635
+ return !!(typeof Component === "function" && Component.prototype && Component.prototype.isReactComponent);
636
+ }
637
+ function isForwardRefComponent(Component) {
638
+ return !!(typeof Component === "object" && Component.$$typeof && (Component.$$typeof.toString() === "Symbol(react.forward_ref)" || Component.$$typeof.description === "react.forward_ref"));
639
+ }
640
+ function isMemoComponent(Component) {
641
+ return !!(typeof Component === "object" && Component.$$typeof && (Component.$$typeof.toString() === "Symbol(react.memo)" || Component.$$typeof.description === "react.memo"));
642
+ }
643
+ function canReceiveRef(Component) {
644
+ if (isClassComponent(Component)) {
645
+ return true;
646
+ }
647
+ if (isForwardRefComponent(Component)) {
648
+ return true;
649
+ }
650
+ if (isMemoComponent(Component)) {
651
+ const wrappedComponent = Component.type;
652
+ if (wrappedComponent) {
653
+ return isClassComponent(wrappedComponent) || isForwardRefComponent(wrappedComponent);
654
+ }
655
+ }
656
+ return false;
657
+ }
658
+ function isReact19Plus() {
659
+ try {
660
+ if (reactVersion) {
661
+ const majorVersion = parseInt(reactVersion.split(".")[0], 10);
662
+ return majorVersion >= 19;
663
+ }
664
+ } catch {
665
+ }
666
+ return false;
667
+ }
668
+ var ReactRenderer = class {
669
+ /**
670
+ * Immediately creates element and renders the provided React component.
671
+ */
672
+ constructor(component, { editor, props = {}, as = "div", className = "" }) {
673
+ this.ref = null;
674
+ this.id = Math.floor(Math.random() * 4294967295).toString();
675
+ this.component = component;
676
+ this.editor = editor;
677
+ this.props = props;
678
+ this.element = document.createElement(as);
679
+ this.element.classList.add("react-renderer");
680
+ if (className) {
681
+ this.element.classList.add(...className.split(" "));
682
+ }
683
+ if (this.editor.isInitialized) {
684
+ flushSync(() => {
685
+ this.render();
686
+ });
687
+ } else {
688
+ queueMicrotask(() => {
689
+ this.render();
690
+ });
691
+ }
692
+ }
693
+ /**
694
+ * Render the React component.
695
+ */
696
+ render() {
697
+ var _a;
698
+ const Component = this.component;
699
+ const props = this.props;
700
+ const editor = this.editor;
701
+ const isReact19 = isReact19Plus();
702
+ const componentCanReceiveRef = canReceiveRef(Component);
703
+ const elementProps = { ...props };
704
+ if (elementProps.ref && !(isReact19 || componentCanReceiveRef)) {
705
+ delete elementProps.ref;
706
+ }
707
+ if (!elementProps.ref && (isReact19 || componentCanReceiveRef)) {
708
+ elementProps.ref = (ref) => {
709
+ this.ref = ref;
710
+ };
711
+ }
712
+ this.reactElement = /* @__PURE__ */ jsx5(Component, { ...elementProps });
713
+ (_a = editor == null ? void 0 : editor.contentComponent) == null ? void 0 : _a.setRenderer(this.id, this);
714
+ }
715
+ /**
716
+ * Re-renders the React component with new props.
717
+ */
718
+ updateProps(props = {}) {
719
+ this.props = {
720
+ ...this.props,
721
+ ...props
722
+ };
723
+ this.render();
724
+ }
725
+ /**
726
+ * Destroy the React component.
727
+ */
728
+ destroy() {
729
+ var _a;
730
+ const editor = this.editor;
731
+ (_a = editor == null ? void 0 : editor.contentComponent) == null ? void 0 : _a.removeRenderer(this.id);
732
+ try {
733
+ if (this.element && this.element.parentNode) {
734
+ this.element.parentNode.removeChild(this.element);
735
+ }
736
+ } catch {
737
+ }
738
+ }
739
+ /**
740
+ * Update the attributes of the element that holds the React component.
741
+ */
742
+ updateAttributes(attributes) {
743
+ Object.keys(attributes).forEach((key) => {
744
+ this.element.setAttribute(key, attributes[key]);
745
+ });
746
+ }
747
+ };
748
+
749
+ // src/ReactMarkViewRenderer.tsx
750
+ import { jsx as jsx6 } from "react/jsx-runtime";
751
+ var ReactMarkViewContext = React4.createContext({
752
+ markViewContentRef: () => {
753
+ }
754
+ });
755
+ var MarkViewContent = (props) => {
756
+ const { as: Tag = "span", ...rest } = props;
757
+ const { markViewContentRef } = React4.useContext(ReactMarkViewContext);
758
+ return (
759
+ // @ts-ignore
760
+ /* @__PURE__ */ jsx6(Tag, { ...rest, ref: markViewContentRef, "data-mark-view-content": "" })
761
+ );
762
+ };
763
+ var ReactMarkView = class extends MarkView {
764
+ constructor(component, props, options) {
765
+ super(component, props, options);
766
+ const { as = "span", attrs, className = "" } = options || {};
767
+ const componentProps = { ...props, updateAttributes: this.updateAttributes.bind(this) };
768
+ this.contentDOMElement = document.createElement("span");
769
+ const markViewContentRef = (el) => {
770
+ if (el && !el.contains(this.contentDOMElement)) {
771
+ el.appendChild(this.contentDOMElement);
772
+ }
773
+ };
774
+ const context = {
775
+ markViewContentRef
776
+ };
777
+ const ReactMarkViewProvider = React4.memo((componentProps2) => {
778
+ return /* @__PURE__ */ jsx6(ReactMarkViewContext.Provider, { value: context, children: React4.createElement(component, componentProps2) });
779
+ });
780
+ ReactMarkViewProvider.displayName = "ReactMarkView";
781
+ this.renderer = new ReactRenderer(ReactMarkViewProvider, {
782
+ editor: props.editor,
783
+ props: componentProps,
784
+ as,
785
+ className: `mark-${props.mark.type.name} ${className}`.trim()
786
+ });
787
+ if (attrs) {
788
+ this.renderer.updateAttributes(attrs);
789
+ }
790
+ }
791
+ get dom() {
792
+ return this.renderer.element;
793
+ }
794
+ get contentDOM() {
795
+ return this.contentDOMElement;
796
+ }
797
+ };
798
+ function ReactMarkViewRenderer(component, options = {}) {
799
+ return (props) => new ReactMarkView(component, props, options);
800
+ }
801
+
802
+ // src/ReactNodeViewRenderer.tsx
803
+ import { getRenderedAttributes, NodeView } from "@blockslides/core";
804
+ import { createElement as createElement2, createRef, memo } from "react";
805
+ import { jsx as jsx7 } from "react/jsx-runtime";
806
+ var ReactNodeView = class extends NodeView {
807
+ constructor(component, props, options) {
808
+ super(component, props, options);
809
+ if (!this.node.isLeaf) {
810
+ if (this.options.contentDOMElementTag) {
811
+ this.contentDOMElement = document.createElement(this.options.contentDOMElementTag);
812
+ } else {
813
+ this.contentDOMElement = document.createElement(this.node.isInline ? "span" : "div");
814
+ }
815
+ this.contentDOMElement.dataset.nodeViewContentReact = "";
816
+ this.contentDOMElement.dataset.nodeViewWrapper = "";
817
+ this.contentDOMElement.style.whiteSpace = "inherit";
818
+ const contentTarget = this.dom.querySelector("[data-node-view-content]");
819
+ if (!contentTarget) {
820
+ return;
821
+ }
822
+ contentTarget.appendChild(this.contentDOMElement);
823
+ }
824
+ }
825
+ /**
826
+ * Setup the React component.
827
+ * Called on initialization.
828
+ */
829
+ mount() {
830
+ const props = {
831
+ editor: this.editor,
832
+ node: this.node,
833
+ decorations: this.decorations,
834
+ innerDecorations: this.innerDecorations,
835
+ view: this.view,
836
+ selected: false,
837
+ extension: this.extension,
838
+ HTMLAttributes: this.HTMLAttributes,
839
+ getPos: () => this.getPos(),
840
+ updateAttributes: (attributes = {}) => this.updateAttributes(attributes),
841
+ deleteNode: () => this.deleteNode(),
842
+ ref: createRef()
843
+ };
844
+ if (!this.component.displayName) {
845
+ const capitalizeFirstChar = (string) => {
846
+ return string.charAt(0).toUpperCase() + string.substring(1);
847
+ };
848
+ this.component.displayName = capitalizeFirstChar(this.extension.name);
849
+ }
850
+ const onDragStart = this.onDragStart.bind(this);
851
+ const nodeViewContentRef = (element) => {
852
+ if (element && this.contentDOMElement && element.firstChild !== this.contentDOMElement) {
853
+ if (element.hasAttribute("data-node-view-wrapper")) {
854
+ element.removeAttribute("data-node-view-wrapper");
855
+ }
856
+ element.appendChild(this.contentDOMElement);
857
+ }
858
+ };
859
+ const context = { onDragStart, nodeViewContentRef };
860
+ const Component = this.component;
861
+ const ReactNodeViewProvider = memo((componentProps) => {
862
+ return /* @__PURE__ */ jsx7(ReactNodeViewContext.Provider, { value: context, children: createElement2(Component, componentProps) });
863
+ });
864
+ ReactNodeViewProvider.displayName = "ReactNodeView";
865
+ let as = this.node.isInline ? "span" : "div";
866
+ if (this.options.as) {
867
+ as = this.options.as;
868
+ }
869
+ const { className = "" } = this.options;
870
+ this.handleSelectionUpdate = this.handleSelectionUpdate.bind(this);
871
+ this.renderer = new ReactRenderer(ReactNodeViewProvider, {
872
+ editor: this.editor,
873
+ props,
874
+ as,
875
+ className: `node-${this.node.type.name} ${className}`.trim()
876
+ });
877
+ this.editor.on("selectionUpdate", this.handleSelectionUpdate);
878
+ this.updateElementAttributes();
879
+ }
880
+ /**
881
+ * Return the DOM element.
882
+ * This is the element that will be used to display the node view.
883
+ */
884
+ get dom() {
885
+ var _a;
886
+ if (this.renderer.element.firstElementChild && !((_a = this.renderer.element.firstElementChild) == null ? void 0 : _a.hasAttribute("data-node-view-wrapper"))) {
887
+ throw Error("Please use the NodeViewWrapper component for your node view.");
888
+ }
889
+ return this.renderer.element;
890
+ }
891
+ /**
892
+ * Return the content DOM element.
893
+ * This is the element that will be used to display the rich-text content of the node.
894
+ */
895
+ get contentDOM() {
896
+ if (this.node.isLeaf) {
897
+ return null;
898
+ }
899
+ return this.contentDOMElement;
900
+ }
901
+ /**
902
+ * On editor selection update, check if the node is selected.
903
+ * If it is, call `selectNode`, otherwise call `deselectNode`.
904
+ */
905
+ handleSelectionUpdate() {
906
+ const { from, to } = this.editor.state.selection;
907
+ const pos = this.getPos();
908
+ if (typeof pos !== "number") {
909
+ return;
910
+ }
911
+ if (from <= pos && to >= pos + this.node.nodeSize) {
912
+ if (this.renderer.props.selected) {
913
+ return;
914
+ }
915
+ this.selectNode();
916
+ } else {
917
+ if (!this.renderer.props.selected) {
918
+ return;
919
+ }
920
+ this.deselectNode();
921
+ }
922
+ }
923
+ /**
924
+ * On update, update the React component.
925
+ * To prevent unnecessary updates, the `update` option can be used.
926
+ */
927
+ update(node, decorations, innerDecorations) {
928
+ const rerenderComponent = (props) => {
929
+ this.renderer.updateProps(props);
930
+ if (typeof this.options.attrs === "function") {
931
+ this.updateElementAttributes();
932
+ }
933
+ };
934
+ if (node.type !== this.node.type) {
935
+ return false;
936
+ }
937
+ if (typeof this.options.update === "function") {
938
+ const oldNode = this.node;
939
+ const oldDecorations = this.decorations;
940
+ const oldInnerDecorations = this.innerDecorations;
941
+ this.node = node;
942
+ this.decorations = decorations;
943
+ this.innerDecorations = innerDecorations;
944
+ return this.options.update({
945
+ oldNode,
946
+ oldDecorations,
947
+ newNode: node,
948
+ newDecorations: decorations,
949
+ oldInnerDecorations,
950
+ innerDecorations,
951
+ updateProps: () => rerenderComponent({ node, decorations, innerDecorations })
952
+ });
953
+ }
954
+ if (node === this.node && this.decorations === decorations && this.innerDecorations === innerDecorations) {
955
+ return true;
956
+ }
957
+ this.node = node;
958
+ this.decorations = decorations;
959
+ this.innerDecorations = innerDecorations;
960
+ rerenderComponent({ node, decorations, innerDecorations });
961
+ return true;
962
+ }
963
+ /**
964
+ * Select the node.
965
+ * Add the `selected` prop and the `ProseMirror-selectednode` class.
966
+ */
967
+ selectNode() {
968
+ this.renderer.updateProps({
969
+ selected: true
970
+ });
971
+ this.renderer.element.classList.add("ProseMirror-selectednode");
972
+ }
973
+ /**
974
+ * Deselect the node.
975
+ * Remove the `selected` prop and the `ProseMirror-selectednode` class.
976
+ */
977
+ deselectNode() {
978
+ this.renderer.updateProps({
979
+ selected: false
980
+ });
981
+ this.renderer.element.classList.remove("ProseMirror-selectednode");
982
+ }
983
+ /**
984
+ * Destroy the React component instance.
985
+ */
986
+ destroy() {
987
+ this.renderer.destroy();
988
+ this.editor.off("selectionUpdate", this.handleSelectionUpdate);
989
+ this.contentDOMElement = null;
990
+ }
991
+ /**
992
+ * Update the attributes of the top-level element that holds the React component.
993
+ * Applying the attributes defined in the `attrs` option.
994
+ */
995
+ updateElementAttributes() {
996
+ if (this.options.attrs) {
997
+ let attrsObj = {};
998
+ if (typeof this.options.attrs === "function") {
999
+ const extensionAttributes = this.editor.extensionManager.attributes;
1000
+ const HTMLAttributes = getRenderedAttributes(this.node, extensionAttributes);
1001
+ attrsObj = this.options.attrs({ node: this.node, HTMLAttributes });
1002
+ } else {
1003
+ attrsObj = this.options.attrs;
1004
+ }
1005
+ this.renderer.updateAttributes(attrsObj);
1006
+ }
1007
+ }
1008
+ };
1009
+ function ReactNodeViewRenderer(component, options) {
1010
+ return (props) => {
1011
+ if (!props.editor.contentComponent) {
1012
+ return {};
1013
+ }
1014
+ return new ReactNodeView(component, props, options);
1015
+ };
1016
+ }
1017
+
1018
+ // src/index.ts
1019
+ export * from "@blockslides/core";
1020
+ export {
1021
+ EditorConsumer,
1022
+ EditorContent,
1023
+ EditorContext,
1024
+ EditorProvider,
1025
+ MarkViewContent,
1026
+ NodeViewContent,
1027
+ NodeViewWrapper,
1028
+ PureEditorContent,
1029
+ ReactMarkView,
1030
+ ReactMarkViewContext,
1031
+ ReactMarkViewRenderer,
1032
+ ReactNodeView,
1033
+ ReactNodeViewContentProvider,
1034
+ ReactNodeViewContext,
1035
+ ReactNodeViewRenderer,
1036
+ ReactRenderer,
1037
+ useCurrentEditor,
1038
+ useEditor,
1039
+ useEditorState,
1040
+ useReactNodeView
1041
+ };
1042
+ //# sourceMappingURL=index.js.map