@tiptap/react
Advanced tools
Comparing version 2.0.0-beta.2 to 2.0.0-beta.3
@@ -6,2 +6,10 @@ # Change Log | ||
# [2.0.0-beta.3](https://github.com/ueberdosis/tiptap-next/compare/@tiptap/react@2.0.0-beta.2...@tiptap/react@2.0.0-beta.3) (2021-03-16) | ||
**Note:** Version bump only for package @tiptap/react | ||
# [2.0.0-beta.2](https://github.com/ueberdosis/tiptap-next/compare/@tiptap/react@2.0.0-beta.1...@tiptap/react@2.0.0-beta.2) (2021-03-09) | ||
@@ -8,0 +16,0 @@ |
import React from 'react'; | ||
import { Editor as CoreEditor } from '@tiptap/core'; | ||
import { EditorContentProps, EditorContentState } from './EditorContent'; | ||
export declare class Editor extends CoreEditor { | ||
contentComponent: React.Component | null; | ||
contentComponent: React.Component<EditorContentProps, EditorContentState> | null; | ||
} |
import React from 'react'; | ||
import { Editor } from './Editor'; | ||
declare type EditorContentProps = { | ||
import { ReactRenderer } from './ReactRenderer'; | ||
export interface EditorContentProps { | ||
editor: Editor | null; | ||
}; | ||
export declare class PureEditorContent extends React.Component<EditorContentProps, EditorContentProps> { | ||
} | ||
export interface EditorContentState { | ||
renderers: Map<string, ReactRenderer>; | ||
} | ||
export declare class PureEditorContent extends React.Component<EditorContentProps, EditorContentState> { | ||
editorContentRef: React.RefObject<any>; | ||
constructor(props: EditorContentProps); | ||
componentDidMount(): void; | ||
componentDidUpdate(): void; | ||
init(): void; | ||
componentWillUnmount(): void; | ||
render(): JSX.Element; | ||
} | ||
export declare const EditorContent: React.MemoExoticComponent<typeof PureEditorContent>; | ||
export {}; |
export * from '@tiptap/core'; | ||
export { Editor } from './Editor'; | ||
export * from './useEditor'; | ||
export * from './ReactRenderer'; | ||
export * from './ReactNodeViewRenderer'; | ||
export * from './EditorContent'; | ||
export * from './NodeViewWrapper'; | ||
export * from './NodeViewContent'; |
@@ -7,2 +7,3 @@ 'use strict'; | ||
var React = require('react'); | ||
var ReactDOM = require('react-dom'); | ||
@@ -12,2 +13,3 @@ function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; } | ||
var React__default = /*#__PURE__*/_interopDefaultLegacy(React); | ||
var ReactDOM__default = /*#__PURE__*/_interopDefaultLegacy(ReactDOM); | ||
@@ -39,2 +41,157 @@ class Editor extends core.Editor { | ||
function isClassComponent(Component) { | ||
return !!(typeof Component === 'function' | ||
&& Component.prototype | ||
&& Component.prototype.isReactComponent); | ||
} | ||
class ReactRenderer { | ||
constructor(component, { props = {}, editor }) { | ||
this.ref = null; | ||
this.id = Math.floor(Math.random() * 0xFFFFFFFF).toString(); | ||
this.component = component; | ||
this.editor = editor; | ||
this.props = props; | ||
this.element = document.createElement('div'); | ||
this.element.classList.add('react-renderer'); | ||
this.render(); | ||
} | ||
render() { | ||
var _a; | ||
const Component = this.component; | ||
const props = this.props; | ||
if (isClassComponent(Component)) { | ||
props.ref = (ref) => { | ||
this.ref = ref; | ||
}; | ||
} | ||
this.reactElement = React__default['default'].createElement(Component, Object.assign({}, props)); | ||
if ((_a = this.editor) === null || _a === void 0 ? void 0 : _a.contentComponent) { | ||
this.editor.contentComponent.setState({ | ||
renderers: this.editor.contentComponent.state.renderers.set(this.id, this), | ||
}); | ||
} | ||
} | ||
updateProps(props = {}) { | ||
this.props = { | ||
...this.props, | ||
...props, | ||
}; | ||
this.render(); | ||
} | ||
destroy() { | ||
var _a; | ||
if ((_a = this.editor) === null || _a === void 0 ? void 0 : _a.contentComponent) { | ||
const { renderers } = this.editor.contentComponent.state; | ||
renderers.delete(this.id); | ||
this.editor.contentComponent.setState({ | ||
renderers, | ||
}); | ||
} | ||
} | ||
} | ||
const ReactNodeViewContext = React.createContext({ | ||
isEditable: undefined, | ||
onDragStart: undefined, | ||
}); | ||
const useReactNodeView = () => React.useContext(ReactNodeViewContext); | ||
class ReactNodeView extends core.NodeView { | ||
mount() { | ||
const props = { | ||
editor: this.editor, | ||
node: this.node, | ||
decorations: this.decorations, | ||
selected: false, | ||
extension: this.extension, | ||
getPos: () => this.getPos(), | ||
updateAttributes: (attributes = {}) => this.updateAttributes(attributes), | ||
}; | ||
if (!this.component.displayName) { | ||
const capitalizeFirstChar = (string) => { | ||
return string.charAt(0).toUpperCase() + string.substring(1); | ||
}; | ||
// @ts-ignore | ||
this.component.displayName = capitalizeFirstChar(this.extension.config.name); | ||
} | ||
const ReactNodeViewProvider = componentProps => { | ||
const [isEditable, setIsEditable] = React.useState(this.editor.isEditable); | ||
const onDragStart = this.onDragStart.bind(this); | ||
const onViewUpdate = () => setIsEditable(this.editor.isEditable); | ||
const Component = this.component; | ||
React.useEffect(() => { | ||
this.editor.on('viewUpdate', onViewUpdate); | ||
return () => { | ||
this.editor.off('viewUpdate', onViewUpdate); | ||
}; | ||
}, []); | ||
return (React__default['default'].createElement(ReactNodeViewContext.Provider, { value: { onDragStart, isEditable } }, | ||
React__default['default'].createElement(Component, Object.assign({}, componentProps)))); | ||
}; | ||
ReactNodeViewProvider.displayName = 'ReactNodeView'; | ||
this.renderer = new ReactRenderer(ReactNodeViewProvider, { | ||
editor: this.editor, | ||
props, | ||
}); | ||
} | ||
get dom() { | ||
var _a; | ||
if (!((_a = this.renderer.element.firstElementChild) === null || _a === void 0 ? void 0 : _a.hasAttribute('data-node-view-wrapper'))) { | ||
throw Error('Please use the ReactViewWrapper component for your node view.'); | ||
} | ||
return this.renderer.element; | ||
} | ||
get contentDOM() { | ||
if (this.node.isLeaf) { | ||
return null; | ||
} | ||
const contentElement = this.dom.querySelector('[data-node-view-content]'); | ||
return contentElement || this.dom; | ||
} | ||
update(node, decorations) { | ||
if (typeof this.options.update === 'function') { | ||
return this.options.update(node, decorations); | ||
} | ||
if (node.type !== this.node.type) { | ||
return false; | ||
} | ||
if (node === this.node && this.decorations === decorations) { | ||
return true; | ||
} | ||
this.node = node; | ||
this.decorations = decorations; | ||
this.renderer.updateProps({ node, decorations }); | ||
return true; | ||
} | ||
selectNode() { | ||
this.renderer.updateProps({ | ||
selected: true, | ||
}); | ||
} | ||
deselectNode() { | ||
this.renderer.updateProps({ | ||
selected: false, | ||
}); | ||
} | ||
destroy() { | ||
this.renderer.destroy(); | ||
} | ||
} | ||
function ReactNodeViewRenderer(component, options) { | ||
return (props) => { | ||
// try to get the parent component | ||
// this is important for vue devtools to show the component hierarchy correctly | ||
// maybe it’s `undefined` because <editor-content> isn’t rendered yet | ||
if (!props.editor.contentComponent) { | ||
return {}; | ||
} | ||
return new ReactNodeView(component, props, options); | ||
}; | ||
} | ||
const Portals = ({ renderers }) => { | ||
return (React__default['default'].createElement(React__default['default'].Fragment, null, Array.from(renderers).map(([key, renderer]) => { | ||
return ReactDOM__default['default'].createPortal(renderer.reactElement, renderer.element, key); | ||
}))); | ||
}; | ||
class PureEditorContent extends React__default['default'].Component { | ||
@@ -45,8 +202,17 @@ constructor(props) { | ||
this.state = { | ||
editor: this.props.editor | ||
renderers: new Map(), | ||
}; | ||
} | ||
componentDidMount() { | ||
this.init(); | ||
} | ||
componentDidUpdate() { | ||
this.init(); | ||
} | ||
init() { | ||
const { editor } = this.props; | ||
if (editor && editor.options.element) { | ||
if (editor.contentComponent) { | ||
return; | ||
} | ||
const element = this.editorContentRef.current; | ||
@@ -58,10 +224,30 @@ element.appendChild(editor.options.element.firstChild); | ||
editor.contentComponent = this; | ||
// TODO: why setTimeout? | ||
setTimeout(() => { | ||
editor.createNodeViews(); | ||
}, 0); | ||
// TODO: alternative to setTimeout? | ||
setTimeout(() => editor.createNodeViews(), 0); | ||
} | ||
} | ||
componentWillUnmount() { | ||
const { editor } = this.props; | ||
if (!editor) { | ||
return; | ||
} | ||
if (!editor.isDestroyed) { | ||
editor.view.setProps({ | ||
nodeViews: {}, | ||
}); | ||
} | ||
editor.contentComponent = null; | ||
if (!editor.options.element.firstChild) { | ||
return; | ||
} | ||
const newElement = document.createElement('div'); | ||
newElement.appendChild(editor.options.element.firstChild); | ||
editor.setOptions({ | ||
element: newElement, | ||
}); | ||
} | ||
render() { | ||
return (React__default['default'].createElement("div", { ref: this.editorContentRef })); | ||
return (React__default['default'].createElement(React__default['default'].Fragment, null, | ||
React__default['default'].createElement("div", { ref: this.editorContentRef }), | ||
React__default['default'].createElement(Portals, { renderers: this.state.renderers }))); | ||
} | ||
@@ -71,5 +257,21 @@ } | ||
const NodeViewWrapper = props => { | ||
const { onDragStart } = useReactNodeView(); | ||
const Tag = props.as || 'div'; | ||
return (React__default['default'].createElement(Tag, { "data-node-view-wrapper": "", onDragStart: onDragStart, style: { whiteSpace: 'normal' } }, props.children)); | ||
}; | ||
const NodeViewContent = props => { | ||
const { isEditable } = useReactNodeView(); | ||
const Tag = props.as || 'div'; | ||
return (React__default['default'].createElement(Tag, { "data-node-view-content": "", contentEditable: isEditable, style: { whiteSpace: 'pre-wrap' } })); | ||
}; | ||
exports.Editor = Editor; | ||
exports.EditorContent = EditorContent; | ||
exports.NodeViewContent = NodeViewContent; | ||
exports.NodeViewWrapper = NodeViewWrapper; | ||
exports.PureEditorContent = PureEditorContent; | ||
exports.ReactNodeViewRenderer = ReactNodeViewRenderer; | ||
exports.ReactRenderer = ReactRenderer; | ||
exports.useEditor = useEditor; | ||
@@ -76,0 +278,0 @@ Object.keys(core).forEach(function (k) { |
@@ -1,4 +0,5 @@ | ||
import { Editor as Editor$1 } from '@tiptap/core'; | ||
import { Editor as Editor$1, NodeView } from '@tiptap/core'; | ||
export * from '@tiptap/core'; | ||
import React, { useState, useEffect } from 'react'; | ||
import React, { useState, useEffect, createContext, useContext } from 'react'; | ||
import ReactDOM from 'react-dom'; | ||
@@ -30,2 +31,157 @@ class Editor extends Editor$1 { | ||
function isClassComponent(Component) { | ||
return !!(typeof Component === 'function' | ||
&& Component.prototype | ||
&& Component.prototype.isReactComponent); | ||
} | ||
class ReactRenderer { | ||
constructor(component, { props = {}, editor }) { | ||
this.ref = null; | ||
this.id = Math.floor(Math.random() * 0xFFFFFFFF).toString(); | ||
this.component = component; | ||
this.editor = editor; | ||
this.props = props; | ||
this.element = document.createElement('div'); | ||
this.element.classList.add('react-renderer'); | ||
this.render(); | ||
} | ||
render() { | ||
var _a; | ||
const Component = this.component; | ||
const props = this.props; | ||
if (isClassComponent(Component)) { | ||
props.ref = (ref) => { | ||
this.ref = ref; | ||
}; | ||
} | ||
this.reactElement = React.createElement(Component, Object.assign({}, props)); | ||
if ((_a = this.editor) === null || _a === void 0 ? void 0 : _a.contentComponent) { | ||
this.editor.contentComponent.setState({ | ||
renderers: this.editor.contentComponent.state.renderers.set(this.id, this), | ||
}); | ||
} | ||
} | ||
updateProps(props = {}) { | ||
this.props = { | ||
...this.props, | ||
...props, | ||
}; | ||
this.render(); | ||
} | ||
destroy() { | ||
var _a; | ||
if ((_a = this.editor) === null || _a === void 0 ? void 0 : _a.contentComponent) { | ||
const { renderers } = this.editor.contentComponent.state; | ||
renderers.delete(this.id); | ||
this.editor.contentComponent.setState({ | ||
renderers, | ||
}); | ||
} | ||
} | ||
} | ||
const ReactNodeViewContext = createContext({ | ||
isEditable: undefined, | ||
onDragStart: undefined, | ||
}); | ||
const useReactNodeView = () => useContext(ReactNodeViewContext); | ||
class ReactNodeView extends NodeView { | ||
mount() { | ||
const props = { | ||
editor: this.editor, | ||
node: this.node, | ||
decorations: this.decorations, | ||
selected: false, | ||
extension: this.extension, | ||
getPos: () => this.getPos(), | ||
updateAttributes: (attributes = {}) => this.updateAttributes(attributes), | ||
}; | ||
if (!this.component.displayName) { | ||
const capitalizeFirstChar = (string) => { | ||
return string.charAt(0).toUpperCase() + string.substring(1); | ||
}; | ||
// @ts-ignore | ||
this.component.displayName = capitalizeFirstChar(this.extension.config.name); | ||
} | ||
const ReactNodeViewProvider = componentProps => { | ||
const [isEditable, setIsEditable] = useState(this.editor.isEditable); | ||
const onDragStart = this.onDragStart.bind(this); | ||
const onViewUpdate = () => setIsEditable(this.editor.isEditable); | ||
const Component = this.component; | ||
useEffect(() => { | ||
this.editor.on('viewUpdate', onViewUpdate); | ||
return () => { | ||
this.editor.off('viewUpdate', onViewUpdate); | ||
}; | ||
}, []); | ||
return (React.createElement(ReactNodeViewContext.Provider, { value: { onDragStart, isEditable } }, | ||
React.createElement(Component, Object.assign({}, componentProps)))); | ||
}; | ||
ReactNodeViewProvider.displayName = 'ReactNodeView'; | ||
this.renderer = new ReactRenderer(ReactNodeViewProvider, { | ||
editor: this.editor, | ||
props, | ||
}); | ||
} | ||
get dom() { | ||
var _a; | ||
if (!((_a = this.renderer.element.firstElementChild) === null || _a === void 0 ? void 0 : _a.hasAttribute('data-node-view-wrapper'))) { | ||
throw Error('Please use the ReactViewWrapper component for your node view.'); | ||
} | ||
return this.renderer.element; | ||
} | ||
get contentDOM() { | ||
if (this.node.isLeaf) { | ||
return null; | ||
} | ||
const contentElement = this.dom.querySelector('[data-node-view-content]'); | ||
return contentElement || this.dom; | ||
} | ||
update(node, decorations) { | ||
if (typeof this.options.update === 'function') { | ||
return this.options.update(node, decorations); | ||
} | ||
if (node.type !== this.node.type) { | ||
return false; | ||
} | ||
if (node === this.node && this.decorations === decorations) { | ||
return true; | ||
} | ||
this.node = node; | ||
this.decorations = decorations; | ||
this.renderer.updateProps({ node, decorations }); | ||
return true; | ||
} | ||
selectNode() { | ||
this.renderer.updateProps({ | ||
selected: true, | ||
}); | ||
} | ||
deselectNode() { | ||
this.renderer.updateProps({ | ||
selected: false, | ||
}); | ||
} | ||
destroy() { | ||
this.renderer.destroy(); | ||
} | ||
} | ||
function ReactNodeViewRenderer(component, options) { | ||
return (props) => { | ||
// try to get the parent component | ||
// this is important for vue devtools to show the component hierarchy correctly | ||
// maybe it’s `undefined` because <editor-content> isn’t rendered yet | ||
if (!props.editor.contentComponent) { | ||
return {}; | ||
} | ||
return new ReactNodeView(component, props, options); | ||
}; | ||
} | ||
const Portals = ({ renderers }) => { | ||
return (React.createElement(React.Fragment, null, Array.from(renderers).map(([key, renderer]) => { | ||
return ReactDOM.createPortal(renderer.reactElement, renderer.element, key); | ||
}))); | ||
}; | ||
class PureEditorContent extends React.Component { | ||
@@ -36,8 +192,17 @@ constructor(props) { | ||
this.state = { | ||
editor: this.props.editor | ||
renderers: new Map(), | ||
}; | ||
} | ||
componentDidMount() { | ||
this.init(); | ||
} | ||
componentDidUpdate() { | ||
this.init(); | ||
} | ||
init() { | ||
const { editor } = this.props; | ||
if (editor && editor.options.element) { | ||
if (editor.contentComponent) { | ||
return; | ||
} | ||
const element = this.editorContentRef.current; | ||
@@ -49,10 +214,30 @@ element.appendChild(editor.options.element.firstChild); | ||
editor.contentComponent = this; | ||
// TODO: why setTimeout? | ||
setTimeout(() => { | ||
editor.createNodeViews(); | ||
}, 0); | ||
// TODO: alternative to setTimeout? | ||
setTimeout(() => editor.createNodeViews(), 0); | ||
} | ||
} | ||
componentWillUnmount() { | ||
const { editor } = this.props; | ||
if (!editor) { | ||
return; | ||
} | ||
if (!editor.isDestroyed) { | ||
editor.view.setProps({ | ||
nodeViews: {}, | ||
}); | ||
} | ||
editor.contentComponent = null; | ||
if (!editor.options.element.firstChild) { | ||
return; | ||
} | ||
const newElement = document.createElement('div'); | ||
newElement.appendChild(editor.options.element.firstChild); | ||
editor.setOptions({ | ||
element: newElement, | ||
}); | ||
} | ||
render() { | ||
return (React.createElement("div", { ref: this.editorContentRef })); | ||
return (React.createElement(React.Fragment, null, | ||
React.createElement("div", { ref: this.editorContentRef }), | ||
React.createElement(Portals, { renderers: this.state.renderers }))); | ||
} | ||
@@ -62,3 +247,15 @@ } | ||
export { Editor, EditorContent, PureEditorContent, useEditor }; | ||
const NodeViewWrapper = props => { | ||
const { onDragStart } = useReactNodeView(); | ||
const Tag = props.as || 'div'; | ||
return (React.createElement(Tag, { "data-node-view-wrapper": "", onDragStart: onDragStart, style: { whiteSpace: 'normal' } }, props.children)); | ||
}; | ||
const NodeViewContent = props => { | ||
const { isEditable } = useReactNodeView(); | ||
const Tag = props.as || 'div'; | ||
return (React.createElement(Tag, { "data-node-view-content": "", contentEditable: isEditable, style: { whiteSpace: 'pre-wrap' } })); | ||
}; | ||
export { Editor, EditorContent, NodeViewContent, NodeViewWrapper, PureEditorContent, ReactNodeViewRenderer, ReactRenderer, useEditor }; | ||
//# sourceMappingURL=tiptap-react.esm.js.map |
(function (global, factory) { | ||
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('@tiptap/core'), require('react')) : | ||
typeof define === 'function' && define.amd ? define(['exports', '@tiptap/core', 'react'], factory) : | ||
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global['@tiptap/react'] = {}, global.core, global.React)); | ||
}(this, (function (exports, core, React) { 'use strict'; | ||
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('@tiptap/core'), require('react'), require('react-dom')) : | ||
typeof define === 'function' && define.amd ? define(['exports', '@tiptap/core', 'react', 'react-dom'], factory) : | ||
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global['@tiptap/react'] = {}, global.core, global.React, global.ReactDOM)); | ||
}(this, (function (exports, core, React, ReactDOM) { 'use strict'; | ||
@@ -10,2 +10,3 @@ function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; } | ||
var React__default = /*#__PURE__*/_interopDefaultLegacy(React); | ||
var ReactDOM__default = /*#__PURE__*/_interopDefaultLegacy(ReactDOM); | ||
@@ -37,2 +38,157 @@ class Editor extends core.Editor { | ||
function isClassComponent(Component) { | ||
return !!(typeof Component === 'function' | ||
&& Component.prototype | ||
&& Component.prototype.isReactComponent); | ||
} | ||
class ReactRenderer { | ||
constructor(component, { props = {}, editor }) { | ||
this.ref = null; | ||
this.id = Math.floor(Math.random() * 0xFFFFFFFF).toString(); | ||
this.component = component; | ||
this.editor = editor; | ||
this.props = props; | ||
this.element = document.createElement('div'); | ||
this.element.classList.add('react-renderer'); | ||
this.render(); | ||
} | ||
render() { | ||
var _a; | ||
const Component = this.component; | ||
const props = this.props; | ||
if (isClassComponent(Component)) { | ||
props.ref = (ref) => { | ||
this.ref = ref; | ||
}; | ||
} | ||
this.reactElement = React__default['default'].createElement(Component, Object.assign({}, props)); | ||
if ((_a = this.editor) === null || _a === void 0 ? void 0 : _a.contentComponent) { | ||
this.editor.contentComponent.setState({ | ||
renderers: this.editor.contentComponent.state.renderers.set(this.id, this), | ||
}); | ||
} | ||
} | ||
updateProps(props = {}) { | ||
this.props = { | ||
...this.props, | ||
...props, | ||
}; | ||
this.render(); | ||
} | ||
destroy() { | ||
var _a; | ||
if ((_a = this.editor) === null || _a === void 0 ? void 0 : _a.contentComponent) { | ||
const { renderers } = this.editor.contentComponent.state; | ||
renderers.delete(this.id); | ||
this.editor.contentComponent.setState({ | ||
renderers, | ||
}); | ||
} | ||
} | ||
} | ||
const ReactNodeViewContext = React.createContext({ | ||
isEditable: undefined, | ||
onDragStart: undefined, | ||
}); | ||
const useReactNodeView = () => React.useContext(ReactNodeViewContext); | ||
class ReactNodeView extends core.NodeView { | ||
mount() { | ||
const props = { | ||
editor: this.editor, | ||
node: this.node, | ||
decorations: this.decorations, | ||
selected: false, | ||
extension: this.extension, | ||
getPos: () => this.getPos(), | ||
updateAttributes: (attributes = {}) => this.updateAttributes(attributes), | ||
}; | ||
if (!this.component.displayName) { | ||
const capitalizeFirstChar = (string) => { | ||
return string.charAt(0).toUpperCase() + string.substring(1); | ||
}; | ||
// @ts-ignore | ||
this.component.displayName = capitalizeFirstChar(this.extension.config.name); | ||
} | ||
const ReactNodeViewProvider = componentProps => { | ||
const [isEditable, setIsEditable] = React.useState(this.editor.isEditable); | ||
const onDragStart = this.onDragStart.bind(this); | ||
const onViewUpdate = () => setIsEditable(this.editor.isEditable); | ||
const Component = this.component; | ||
React.useEffect(() => { | ||
this.editor.on('viewUpdate', onViewUpdate); | ||
return () => { | ||
this.editor.off('viewUpdate', onViewUpdate); | ||
}; | ||
}, []); | ||
return (React__default['default'].createElement(ReactNodeViewContext.Provider, { value: { onDragStart, isEditable } }, | ||
React__default['default'].createElement(Component, Object.assign({}, componentProps)))); | ||
}; | ||
ReactNodeViewProvider.displayName = 'ReactNodeView'; | ||
this.renderer = new ReactRenderer(ReactNodeViewProvider, { | ||
editor: this.editor, | ||
props, | ||
}); | ||
} | ||
get dom() { | ||
var _a; | ||
if (!((_a = this.renderer.element.firstElementChild) === null || _a === void 0 ? void 0 : _a.hasAttribute('data-node-view-wrapper'))) { | ||
throw Error('Please use the ReactViewWrapper component for your node view.'); | ||
} | ||
return this.renderer.element; | ||
} | ||
get contentDOM() { | ||
if (this.node.isLeaf) { | ||
return null; | ||
} | ||
const contentElement = this.dom.querySelector('[data-node-view-content]'); | ||
return contentElement || this.dom; | ||
} | ||
update(node, decorations) { | ||
if (typeof this.options.update === 'function') { | ||
return this.options.update(node, decorations); | ||
} | ||
if (node.type !== this.node.type) { | ||
return false; | ||
} | ||
if (node === this.node && this.decorations === decorations) { | ||
return true; | ||
} | ||
this.node = node; | ||
this.decorations = decorations; | ||
this.renderer.updateProps({ node, decorations }); | ||
return true; | ||
} | ||
selectNode() { | ||
this.renderer.updateProps({ | ||
selected: true, | ||
}); | ||
} | ||
deselectNode() { | ||
this.renderer.updateProps({ | ||
selected: false, | ||
}); | ||
} | ||
destroy() { | ||
this.renderer.destroy(); | ||
} | ||
} | ||
function ReactNodeViewRenderer(component, options) { | ||
return (props) => { | ||
// try to get the parent component | ||
// this is important for vue devtools to show the component hierarchy correctly | ||
// maybe it’s `undefined` because <editor-content> isn’t rendered yet | ||
if (!props.editor.contentComponent) { | ||
return {}; | ||
} | ||
return new ReactNodeView(component, props, options); | ||
}; | ||
} | ||
const Portals = ({ renderers }) => { | ||
return (React__default['default'].createElement(React__default['default'].Fragment, null, Array.from(renderers).map(([key, renderer]) => { | ||
return ReactDOM__default['default'].createPortal(renderer.reactElement, renderer.element, key); | ||
}))); | ||
}; | ||
class PureEditorContent extends React__default['default'].Component { | ||
@@ -43,8 +199,17 @@ constructor(props) { | ||
this.state = { | ||
editor: this.props.editor | ||
renderers: new Map(), | ||
}; | ||
} | ||
componentDidMount() { | ||
this.init(); | ||
} | ||
componentDidUpdate() { | ||
this.init(); | ||
} | ||
init() { | ||
const { editor } = this.props; | ||
if (editor && editor.options.element) { | ||
if (editor.contentComponent) { | ||
return; | ||
} | ||
const element = this.editorContentRef.current; | ||
@@ -56,10 +221,30 @@ element.appendChild(editor.options.element.firstChild); | ||
editor.contentComponent = this; | ||
// TODO: why setTimeout? | ||
setTimeout(() => { | ||
editor.createNodeViews(); | ||
}, 0); | ||
// TODO: alternative to setTimeout? | ||
setTimeout(() => editor.createNodeViews(), 0); | ||
} | ||
} | ||
componentWillUnmount() { | ||
const { editor } = this.props; | ||
if (!editor) { | ||
return; | ||
} | ||
if (!editor.isDestroyed) { | ||
editor.view.setProps({ | ||
nodeViews: {}, | ||
}); | ||
} | ||
editor.contentComponent = null; | ||
if (!editor.options.element.firstChild) { | ||
return; | ||
} | ||
const newElement = document.createElement('div'); | ||
newElement.appendChild(editor.options.element.firstChild); | ||
editor.setOptions({ | ||
element: newElement, | ||
}); | ||
} | ||
render() { | ||
return (React__default['default'].createElement("div", { ref: this.editorContentRef })); | ||
return (React__default['default'].createElement(React__default['default'].Fragment, null, | ||
React__default['default'].createElement("div", { ref: this.editorContentRef }), | ||
React__default['default'].createElement(Portals, { renderers: this.state.renderers }))); | ||
} | ||
@@ -69,5 +254,21 @@ } | ||
const NodeViewWrapper = props => { | ||
const { onDragStart } = useReactNodeView(); | ||
const Tag = props.as || 'div'; | ||
return (React__default['default'].createElement(Tag, { "data-node-view-wrapper": "", onDragStart: onDragStart, style: { whiteSpace: 'normal' } }, props.children)); | ||
}; | ||
const NodeViewContent = props => { | ||
const { isEditable } = useReactNodeView(); | ||
const Tag = props.as || 'div'; | ||
return (React__default['default'].createElement(Tag, { "data-node-view-content": "", contentEditable: isEditable, style: { whiteSpace: 'pre-wrap' } })); | ||
}; | ||
exports.Editor = Editor; | ||
exports.EditorContent = EditorContent; | ||
exports.NodeViewContent = NodeViewContent; | ||
exports.NodeViewWrapper = NodeViewWrapper; | ||
exports.PureEditorContent = PureEditorContent; | ||
exports.ReactNodeViewRenderer = ReactNodeViewRenderer; | ||
exports.ReactRenderer = ReactRenderer; | ||
exports.useEditor = useEditor; | ||
@@ -74,0 +275,0 @@ Object.keys(core).forEach(function (k) { |
{ | ||
"name": "@tiptap/react", | ||
"description": "React components for tiptap", | ||
"version": "2.0.0-beta.2", | ||
"version": "2.0.0-beta.3", | ||
"homepage": "https://tiptap.dev", | ||
@@ -35,3 +35,3 @@ "keywords": [ | ||
}, | ||
"gitHead": "bf357dd7cb3da87d494dd8843e90505bba9214ca" | ||
"gitHead": "7d740a5cd88daffa022e7004ebe3fdf9be8905da" | ||
} |
import React from 'react' | ||
import { Editor as CoreEditor } from '@tiptap/core' | ||
import { EditorContentProps, EditorContentState } from './EditorContent' | ||
export class Editor extends CoreEditor { | ||
public contentComponent: React.Component | null = null | ||
public contentComponent: React.Component<EditorContentProps, EditorContentState> | null = null | ||
} |
@@ -1,7 +0,8 @@ | ||
// @ts-nocheck | ||
export * from '@tiptap/core' | ||
export { Editor } from './Editor' | ||
export * from './useEditor' | ||
// export * from './ReactRenderer' | ||
// export * from './ReactNodeViewRenderer' | ||
export * from './ReactRenderer' | ||
export * from './ReactNodeViewRenderer' | ||
export * from './EditorContent' | ||
export * from './NodeViewWrapper' | ||
export * from './NodeViewContent' |
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
Long strings
Supply chain riskContains long string literals, which may be a sign of obfuscated or packed code.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
2248210
30
2808
11