forked from bitovi/react-to-web-component
-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathreact-to-can-webcomponent.js
130 lines (116 loc) · 3.58 KB
/
react-to-can-webcomponent.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
// TODO if ylem hooks branch is ever released, import use-observer from ylem instead.
import useObserver from "./lib/use-observer";
var reactComponentSymbol = Symbol.for("r2wc.reactComponent");
var renderSymbol = Symbol.for("r2wc.reactRender");
var shouldRenderSymbol = Symbol.for("r2wc.shouldRender");
var rootSymbol = Symbol.for("r2wc.root");
var define = {
// Creates a getter/setter that re-renders everytime a property is set.
expando: function(receiver, key, value) {
Object.defineProperty(receiver, key, {
enumerable: true,
get: function() {
return value;
},
set: function(newValue) {
value = newValue;
this[renderSymbol]();
return true;
}
});
receiver[renderSymbol]();
}
}
export default function(ReactComponent, React, ReactDOM) {
var renderAddedProperties = {isConnected: "isConnected" in HTMLElement.prototype};
var rendering = false;
// Create the web component "class"
var WebComponent = function() {
var self = Reflect.construct(HTMLElement, arguments, this.constructor);
return self;
};
// Make the class extend HTMLElement
var targetPrototype = Object.create(HTMLElement.prototype);
targetPrototype.constructor = WebComponent;
var ObservedComponent = function(props) {
useObserver(React);
return React.createElement(ReactComponent, props);
};
// But have that prototype be wrapped in a proxy.
var proxyPrototype = new Proxy(targetPrototype, {
has: function (target, key) {
return key in ReactComponent.propTypes ||
key in targetPrototype;
},
// when any undefined property is set, create a getter/setter that re-renders
set: function(target, key, value, receiver) {
if(rendering) {
renderAddedProperties[key] = true;
}
if (typeof key === "symbol" || renderAddedProperties[key] || key in target) {
return Reflect.set(target, key, value, receiver);
} else {
define.expando(receiver, key, value)
}
return true;
},
// makes sure the property looks writable
getOwnPropertyDescriptor: function(target, key){
var own = Reflect.getOwnPropertyDescriptor(target, key);
if(own) {
return own;
}
if(key in ReactComponent.propTypes) {
return { configurable: true, enumerable: true, writable: true, value: undefined };
}
}
});
WebComponent.prototype = proxyPrototype;
// Setup lifecycle methods
targetPrototype.connectedCallback = function() {
// Once connected, it will keep updating the innerHTML.
// We could add a render method to allow this as well.
this[shouldRenderSymbol] = true;
this[renderSymbol]();
};
targetPrototype.disconnectedCallback = function() {
this[shouldRenderSymbol] = false;
};
targetPrototype[renderSymbol] = function() {
if (this[shouldRenderSymbol] === true) {
var data = {};
Object.keys(this).forEach(function(key) {
if (renderAddedProperties[key] !== false) {
data[key] = this[key];
}
}, this);
rendering = true;
var element = React.createElement(ObservedComponent, data);
if ("createRoot" in ReactDOM) {
this[reactComponentSymbol] =
(
this[rootSymbol] ||
(this[rootSymbol] = ReactDOM.createRoot(
this
))
).render(element);
}
else if ("render" in ReactDOM) {
this[reactComponentSymbol] = ReactDOM.render(
element,
this
);
}
rendering = false;
}
};
// Handle attributes changing
if (ReactComponent.propTypes) {
WebComponent.observedAttributes = Object.keys(ReactComponent.propTypes);
targetPrototype.attributeChangedCallback = function(name, oldValue, newValue) {
// TODO: handle type conversion
this[name] = newValue;
};
}
return WebComponent;
}