@textbus/adapter-react 4.0.0-alpha.6 → 4.0.0-alpha.60

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,28 +1,80 @@
1
- import { useState, useEffect, createElement } from 'react';
2
- import { Subject } from '@tanbo/stream';
3
- import { makeError, VElement, VTextNode, replaceEmpty } from '@textbus/core';
1
+ import { makeError, VElement, VTextNode } from '@textbus/core';
4
2
  import { DomAdapter } from '@textbus/platform-browser';
3
+ import { createElement, useState, useEffect } from 'react';
5
4
 
6
- // hack start
7
- // 在 composition 输入时,浏览器会默认删除占位节点(<br>),这会导致 react 在 diff 时报错
8
- const oldRemoveChild = Node.prototype.removeChild;
9
- Node.prototype.removeChild = function (child) {
10
- if (child) {
11
- if (!child.parentNode) {
12
- return child;
13
- }
14
- }
15
- return oldRemoveChild.call(this, child);
16
- };
17
- // hack end
18
5
  const adapterError = makeError('ReactAdapter');
19
- /**
20
- * Textbus 桥接 React 渲染能力适配器,用于在 React 项目中渲染 Textbus 数据
21
- */
22
- class Adapter extends DomAdapter {
6
+ class ReactAdapter extends DomAdapter {
23
7
  constructor(components, mount) {
24
- super(mount);
25
- this.onViewUpdated = new Subject();
8
+ super({
9
+ createCompositionNode(compositionState, updateNativeCompositionNode) {
10
+ return new VElement('span', {
11
+ style: {
12
+ textDecoration: 'underline'
13
+ },
14
+ ref: (node) => {
15
+ updateNativeCompositionNode(node);
16
+ }
17
+ }, [
18
+ new VTextNode(compositionState.text)
19
+ ]);
20
+ },
21
+ getParentNode(node) {
22
+ return node.parentNode;
23
+ },
24
+ getChildNodes(parentElement) {
25
+ return Array.from(parentElement.childNodes);
26
+ },
27
+ isNativeElementNode(node) {
28
+ return node instanceof HTMLElement;
29
+ },
30
+ getChildByIndex(parentElement, index) {
31
+ return parentElement.childNodes[index];
32
+ },
33
+ getAndUpdateSlotRootNativeElement(vEle, update) {
34
+ const currentRef = vEle.attrs.get('ref');
35
+ if (currentRef) {
36
+ vEle.attrs.set('ref', (v) => {
37
+ update(v);
38
+ if (typeof currentRef === 'function') {
39
+ currentRef(v);
40
+ }
41
+ else if (!currentRef.current) {
42
+ currentRef.current = v;
43
+ }
44
+ });
45
+ }
46
+ else {
47
+ vEle.attrs.set('ref', update);
48
+ }
49
+ },
50
+ componentRender: (component) => {
51
+ const comp = this.components[component.name] || this.components['*'];
52
+ if (comp) {
53
+ component.changeMarker.rendered();
54
+ return createElement(comp, {
55
+ key: component.id,
56
+ component
57
+ });
58
+ }
59
+ throw adapterError(`cannot found view component \`${component.name}\`!`);
60
+ },
61
+ vElementToViewElement(vNode, children) {
62
+ const props = Object.assign({}, (Array.from(vNode.attrs).reduce((a, b) => {
63
+ a[b[0]] = b[1];
64
+ return a;
65
+ }, {})));
66
+ if (vNode.classes.size) {
67
+ props.className = Array.from(vNode.classes).join(' ');
68
+ }
69
+ if (vNode.styles) {
70
+ props.style = Array.from(vNode.styles).reduce((a, b) => {
71
+ a[b[0]] = b[1];
72
+ return a;
73
+ }, {});
74
+ }
75
+ return createElement(vNode.tagName, props, ...children);
76
+ }
77
+ }, mount);
26
78
  this.components = {};
27
79
  Object.keys(components).forEach(key => {
28
80
  this.components[key] = (props) => {
@@ -41,7 +93,10 @@ class Adapter extends DomAdapter {
41
93
  useEffect(() => {
42
94
  this.onViewUpdated.next();
43
95
  }, [updateKey]);
44
- return components[key]({
96
+ component.__slots__.forEach(i => this.renderedSlotCache.delete(i));
97
+ component.__slots__.length = 0;
98
+ this.componentRendingStack.push(component);
99
+ const vNode = components[key]({
45
100
  component,
46
101
  rootRef: (rootNode) => {
47
102
  if (rootNode) {
@@ -52,78 +107,23 @@ class Adapter extends DomAdapter {
52
107
  }
53
108
  }
54
109
  });
110
+ useEffect(() => {
111
+ const context = this.componentRendingStack[this.componentRendingStack.length - 1];
112
+ if (context === component) {
113
+ this.componentRendingStack.pop();
114
+ }
115
+ if (!this.componentRootElementCaches.get(component)) {
116
+ // eslint-disable-next-line max-len
117
+ throw adapterError(`Component \`${component.name}\` is not bound to rootRef, you must bind rootRef to the root element node of the component view.`);
118
+ }
119
+ });
120
+ return vNode;
55
121
  };
56
122
  });
57
123
  }
58
- componentRender(component) {
59
- const comp = this.components[component.name] || this.components['*'];
60
- if (comp) {
61
- component.changeMarker.rendered();
62
- return createElement(comp, {
63
- key: component.id,
64
- component
65
- });
66
- }
67
- throw adapterError(`cannot found view component \`${component.name}\`!`);
68
- }
69
- slotRender(slot, slotHostRender, renderEnv) {
70
- const vElement = slot.toTree(slotHostRender, renderEnv);
71
- this.slotRootVElementCaches.set(slot, vElement);
72
- const vNodeToJSX = (vNode) => {
73
- const children = [];
74
- for (let i = 0; i < vNode.children.length; i++) {
75
- const child = vNode.children[i];
76
- if (child instanceof VElement) {
77
- children.push(vNodeToJSX(child));
78
- }
79
- else if (child instanceof VTextNode) {
80
- children.push(replaceEmpty(child.textContent));
81
- }
82
- else {
83
- children.push(this.componentRender(child));
84
- }
85
- }
86
- const props = Object.assign({}, (Array.from(vNode.attrs).reduce((a, b) => {
87
- a[b[0]] = b[1];
88
- return a;
89
- }, {})));
90
- if (vNode.classes.size) {
91
- props.className = Array.from(vNode.classes).join(' ');
92
- }
93
- if (vNode.styles) {
94
- props.style = Array.from(vNode.styles).reduce((a, b) => {
95
- a[b[0]] = b[1];
96
- return a;
97
- }, {});
98
- }
99
- return createElement(vNode.tagName, props, ...children);
100
- };
101
- const refFn = (nativeNode) => {
102
- if (!nativeNode) {
103
- this.slotRootNativeElementCaches.remove(nativeNode);
104
- }
105
- else {
106
- this.slotRootNativeElementCaches.set(slot, nativeNode);
107
- }
108
- };
109
- const currentRef = vElement.attrs.get('ref');
110
- if (currentRef) {
111
- vElement.attrs.set('ref', (v) => {
112
- refFn(v);
113
- if (typeof currentRef === 'function') {
114
- currentRef(v);
115
- }
116
- else if (!currentRef.current) {
117
- currentRef.current = v;
118
- }
119
- });
120
- }
121
- else {
122
- vElement.attrs.set('ref', refFn);
123
- }
124
- slot.changeMarker.rendered();
125
- return vNodeToJSX(vElement);
124
+ copy() {
125
+ document.execCommand('copy');
126
126
  }
127
127
  }
128
128
 
129
- export { Adapter };
129
+ export { ReactAdapter };
package/bundles/index.js CHANGED
@@ -1,30 +1,82 @@
1
1
  'use strict';
2
2
 
3
- var react = require('react');
4
- var stream = require('@tanbo/stream');
5
3
  var core = require('@textbus/core');
6
4
  var platformBrowser = require('@textbus/platform-browser');
5
+ var react = require('react');
7
6
 
8
- // hack start
9
- // 在 composition 输入时,浏览器会默认删除占位节点(<br>),这会导致 react 在 diff 时报错
10
- const oldRemoveChild = Node.prototype.removeChild;
11
- Node.prototype.removeChild = function (child) {
12
- if (child) {
13
- if (!child.parentNode) {
14
- return child;
15
- }
16
- }
17
- return oldRemoveChild.call(this, child);
18
- };
19
- // hack end
20
7
  const adapterError = core.makeError('ReactAdapter');
21
- /**
22
- * Textbus 桥接 React 渲染能力适配器,用于在 React 项目中渲染 Textbus 数据
23
- */
24
- class Adapter extends platformBrowser.DomAdapter {
8
+ class ReactAdapter extends platformBrowser.DomAdapter {
25
9
  constructor(components, mount) {
26
- super(mount);
27
- this.onViewUpdated = new stream.Subject();
10
+ super({
11
+ createCompositionNode(compositionState, updateNativeCompositionNode) {
12
+ return new core.VElement('span', {
13
+ style: {
14
+ textDecoration: 'underline'
15
+ },
16
+ ref: (node) => {
17
+ updateNativeCompositionNode(node);
18
+ }
19
+ }, [
20
+ new core.VTextNode(compositionState.text)
21
+ ]);
22
+ },
23
+ getParentNode(node) {
24
+ return node.parentNode;
25
+ },
26
+ getChildNodes(parentElement) {
27
+ return Array.from(parentElement.childNodes);
28
+ },
29
+ isNativeElementNode(node) {
30
+ return node instanceof HTMLElement;
31
+ },
32
+ getChildByIndex(parentElement, index) {
33
+ return parentElement.childNodes[index];
34
+ },
35
+ getAndUpdateSlotRootNativeElement(vEle, update) {
36
+ const currentRef = vEle.attrs.get('ref');
37
+ if (currentRef) {
38
+ vEle.attrs.set('ref', (v) => {
39
+ update(v);
40
+ if (typeof currentRef === 'function') {
41
+ currentRef(v);
42
+ }
43
+ else if (!currentRef.current) {
44
+ currentRef.current = v;
45
+ }
46
+ });
47
+ }
48
+ else {
49
+ vEle.attrs.set('ref', update);
50
+ }
51
+ },
52
+ componentRender: (component) => {
53
+ const comp = this.components[component.name] || this.components['*'];
54
+ if (comp) {
55
+ component.changeMarker.rendered();
56
+ return react.createElement(comp, {
57
+ key: component.id,
58
+ component
59
+ });
60
+ }
61
+ throw adapterError(`cannot found view component \`${component.name}\`!`);
62
+ },
63
+ vElementToViewElement(vNode, children) {
64
+ const props = Object.assign({}, (Array.from(vNode.attrs).reduce((a, b) => {
65
+ a[b[0]] = b[1];
66
+ return a;
67
+ }, {})));
68
+ if (vNode.classes.size) {
69
+ props.className = Array.from(vNode.classes).join(' ');
70
+ }
71
+ if (vNode.styles) {
72
+ props.style = Array.from(vNode.styles).reduce((a, b) => {
73
+ a[b[0]] = b[1];
74
+ return a;
75
+ }, {});
76
+ }
77
+ return react.createElement(vNode.tagName, props, ...children);
78
+ }
79
+ }, mount);
28
80
  this.components = {};
29
81
  Object.keys(components).forEach(key => {
30
82
  this.components[key] = (props) => {
@@ -43,7 +95,10 @@ class Adapter extends platformBrowser.DomAdapter {
43
95
  react.useEffect(() => {
44
96
  this.onViewUpdated.next();
45
97
  }, [updateKey]);
46
- return components[key]({
98
+ component.__slots__.forEach(i => this.renderedSlotCache.delete(i));
99
+ component.__slots__.length = 0;
100
+ this.componentRendingStack.push(component);
101
+ const vNode = components[key]({
47
102
  component,
48
103
  rootRef: (rootNode) => {
49
104
  if (rootNode) {
@@ -54,78 +109,23 @@ class Adapter extends platformBrowser.DomAdapter {
54
109
  }
55
110
  }
56
111
  });
112
+ react.useEffect(() => {
113
+ const context = this.componentRendingStack[this.componentRendingStack.length - 1];
114
+ if (context === component) {
115
+ this.componentRendingStack.pop();
116
+ }
117
+ if (!this.componentRootElementCaches.get(component)) {
118
+ // eslint-disable-next-line max-len
119
+ throw adapterError(`Component \`${component.name}\` is not bound to rootRef, you must bind rootRef to the root element node of the component view.`);
120
+ }
121
+ });
122
+ return vNode;
57
123
  };
58
124
  });
59
125
  }
60
- componentRender(component) {
61
- const comp = this.components[component.name] || this.components['*'];
62
- if (comp) {
63
- component.changeMarker.rendered();
64
- return react.createElement(comp, {
65
- key: component.id,
66
- component
67
- });
68
- }
69
- throw adapterError(`cannot found view component \`${component.name}\`!`);
70
- }
71
- slotRender(slot, slotHostRender, renderEnv) {
72
- const vElement = slot.toTree(slotHostRender, renderEnv);
73
- this.slotRootVElementCaches.set(slot, vElement);
74
- const vNodeToJSX = (vNode) => {
75
- const children = [];
76
- for (let i = 0; i < vNode.children.length; i++) {
77
- const child = vNode.children[i];
78
- if (child instanceof core.VElement) {
79
- children.push(vNodeToJSX(child));
80
- }
81
- else if (child instanceof core.VTextNode) {
82
- children.push(core.replaceEmpty(child.textContent));
83
- }
84
- else {
85
- children.push(this.componentRender(child));
86
- }
87
- }
88
- const props = Object.assign({}, (Array.from(vNode.attrs).reduce((a, b) => {
89
- a[b[0]] = b[1];
90
- return a;
91
- }, {})));
92
- if (vNode.classes.size) {
93
- props.className = Array.from(vNode.classes).join(' ');
94
- }
95
- if (vNode.styles) {
96
- props.style = Array.from(vNode.styles).reduce((a, b) => {
97
- a[b[0]] = b[1];
98
- return a;
99
- }, {});
100
- }
101
- return react.createElement(vNode.tagName, props, ...children);
102
- };
103
- const refFn = (nativeNode) => {
104
- if (!nativeNode) {
105
- this.slotRootNativeElementCaches.remove(nativeNode);
106
- }
107
- else {
108
- this.slotRootNativeElementCaches.set(slot, nativeNode);
109
- }
110
- };
111
- const currentRef = vElement.attrs.get('ref');
112
- if (currentRef) {
113
- vElement.attrs.set('ref', (v) => {
114
- refFn(v);
115
- if (typeof currentRef === 'function') {
116
- currentRef(v);
117
- }
118
- else if (!currentRef.current) {
119
- currentRef.current = v;
120
- }
121
- });
122
- }
123
- else {
124
- vElement.attrs.set('ref', refFn);
125
- }
126
- slot.changeMarker.rendered();
127
- return vNodeToJSX(vElement);
126
+ copy() {
127
+ document.execCommand('copy');
128
128
  }
129
129
  }
130
130
 
131
- exports.Adapter = Adapter;
131
+ exports.ReactAdapter = ReactAdapter;
@@ -1 +1 @@
1
- export * from './adapter';
1
+ export * from './react-adapter';
@@ -0,0 +1,15 @@
1
+ import { Component, ViewMount } from '@textbus/core';
2
+ import { DomAdapter } from '@textbus/platform-browser';
3
+ import { JSX } from 'react';
4
+ export interface ViewComponentProps<T extends Component> {
5
+ component: T;
6
+ rootRef: ((rootNode: HTMLElement) => void);
7
+ }
8
+ export interface ReactAdapterComponents {
9
+ [key: string]: (props: ViewComponentProps<any>) => JSX.Element;
10
+ }
11
+ export declare class ReactAdapter extends DomAdapter<JSX.Element, JSX.Element> {
12
+ private components;
13
+ constructor(components: ReactAdapterComponents, mount: ViewMount<JSX.Element, HTMLElement>);
14
+ copy(): void;
15
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@textbus/adapter-react",
3
- "version": "4.0.0-alpha.6",
3
+ "version": "4.0.0-alpha.60",
4
4
  "description": "Textbus is a rich text editor and framework that is highly customizable and extensible to achieve rich wysiwyg effects.",
5
5
  "main": "./bundles/index.js",
6
6
  "module": "./bundles/index.esm.js",
@@ -25,10 +25,10 @@
25
25
  "typescript editor"
26
26
  ],
27
27
  "dependencies": {
28
- "@tanbo/stream": "^1.2.0",
29
- "@textbus/core": "^4.0.0-alpha.6",
30
- "@textbus/platform-browser": "^4.0.0-alpha.6",
31
- "react": "^18.2.0"
28
+ "@tanbo/stream": "^1.2.4",
29
+ "@textbus/core": "^4.0.0-alpha.60",
30
+ "@textbus/platform-browser": "^4.0.0-alpha.60",
31
+ "react": "^17.0.0 || ^18.0.0"
32
32
  },
33
33
  "devDependencies": {
34
34
  "@rollup/plugin-commonjs": "^23.0.2",
@@ -49,5 +49,5 @@
49
49
  "bugs": {
50
50
  "url": "https://github.com/textbus/textbus.git/issues"
51
51
  },
52
- "gitHead": "47394e77de252fdfd9193e1620177f944976c1a9"
52
+ "gitHead": "bae80846bc8c26754af661e35dea236db237aeeb"
53
53
  }
@@ -1,21 +0,0 @@
1
- import { JSX } from 'react';
2
- import { Subject } from '@tanbo/stream';
3
- import { Component, ComponentInstance, ExtractComponentInstanceType, Slot, VElement, VTextNode } from '@textbus/core';
4
- import { DomAdapter } from '@textbus/platform-browser';
5
- export interface ViewComponentProps<T extends Component = Component> {
6
- component: ExtractComponentInstanceType<T>;
7
- rootRef: ((rootNode: HTMLElement) => void);
8
- }
9
- export interface ReactAdapterComponents {
10
- [key: string]: (props: ViewComponentProps) => JSX.Element;
11
- }
12
- /**
13
- * Textbus 桥接 React 渲染能力适配器,用于在 React 项目中渲染 Textbus 数据
14
- */
15
- export declare class Adapter extends DomAdapter<JSX.Element, JSX.Element> {
16
- onViewUpdated: Subject<void>;
17
- private components;
18
- constructor(components: ReactAdapterComponents, mount: (host: HTMLElement, root: JSX.Element) => (void | (() => void)));
19
- componentRender(component: ComponentInstance): JSX.Element;
20
- slotRender(slot: Slot, slotHostRender: (children: Array<VElement | VTextNode | ComponentInstance>) => VElement, renderEnv?: any): JSX.Element;
21
- }