UNPKG

6.11 kBJavaScriptView Raw
1import {
2 render as preactRender,
3 hydrate as preactHydrate,
4 options,
5 toChildArray,
6 Component
7} from 'preact';
8
9export const REACT_ELEMENT_TYPE =
10 (typeof Symbol != 'undefined' && Symbol.for && Symbol.for('react.element')) ||
11 0xeac7;
12
13const CAMEL_PROPS = /^(?:accent|alignment|arabic|baseline|cap|clip(?!PathU)|color|fill|flood|font|glyph(?!R)|horiz|marker(?!H|W|U)|overline|paint|stop|strikethrough|stroke|text(?!L)|underline|unicode|units|v|vector|vert|word|writing|x(?!C))[A-Z]/;
14
15// Input types for which onchange should not be converted to oninput.
16// type="file|checkbox|radio", plus "range" in IE11.
17// (IE11 doesn't support Symbol, which we use here to turn `rad` into `ra` which matches "range")
18const ONCHANGE_INPUT_TYPES =
19 typeof Symbol != 'undefined' ? /fil|che|rad/i : /fil|che|ra/i;
20
21// Some libraries like `react-virtualized` explicitly check for this.
22Component.prototype.isReactComponent = {};
23
24// `UNSAFE_*` lifecycle hooks
25// Preact only ever invokes the unprefixed methods.
26// Here we provide a base "fallback" implementation that calls any defined UNSAFE_ prefixed method.
27// - If a component defines its own `componentDidMount()` (including via defineProperty), use that.
28// - If a component defines `UNSAFE_componentDidMount()`, `componentDidMount` is the alias getter/setter.
29// - If anything assigns to an `UNSAFE_*` property, the assignment is forwarded to the unprefixed property.
30// See https://github.com/preactjs/preact/issues/1941
31[
32 'componentWillMount',
33 'componentWillReceiveProps',
34 'componentWillUpdate'
35].forEach(key => {
36 Object.defineProperty(Component.prototype, key, {
37 configurable: true,
38 get() {
39 return this['UNSAFE_' + key];
40 },
41 set(v) {
42 Object.defineProperty(this, key, {
43 configurable: true,
44 writable: true,
45 value: v
46 });
47 }
48 });
49});
50
51/**
52 * Proxy render() since React returns a Component reference.
53 * @param {import('./internal').VNode} vnode VNode tree to render
54 * @param {import('./internal').PreactElement} parent DOM node to render vnode tree into
55 * @param {() => void} [callback] Optional callback that will be called after rendering
56 * @returns {import('./internal').Component | null} The root component reference or null
57 */
58export function render(vnode, parent, callback) {
59 // React destroys any existing DOM nodes, see #1727
60 // ...but only on the first render, see #1828
61 if (parent._children == null) {
62 parent.textContent = '';
63 }
64
65 preactRender(vnode, parent);
66 if (typeof callback == 'function') callback();
67
68 return vnode ? vnode._component : null;
69}
70
71export function hydrate(vnode, parent, callback) {
72 preactHydrate(vnode, parent);
73 if (typeof callback == 'function') callback();
74
75 return vnode ? vnode._component : null;
76}
77
78let oldEventHook = options.event;
79options.event = e => {
80 if (oldEventHook) e = oldEventHook(e);
81 e.persist = empty;
82 e.isPropagationStopped = isPropagationStopped;
83 e.isDefaultPrevented = isDefaultPrevented;
84 return (e.nativeEvent = e);
85};
86
87function empty() {}
88
89function isPropagationStopped() {
90 return this.cancelBubble;
91}
92
93function isDefaultPrevented() {
94 return this.defaultPrevented;
95}
96
97let classNameDescriptor = {
98 configurable: true,
99 get() {
100 return this.class;
101 }
102};
103
104let oldVNodeHook = options.vnode;
105options.vnode = vnode => {
106 let type = vnode.type;
107 let props = vnode.props;
108 let normalizedProps = props;
109
110 // only normalize props on Element nodes
111 if (typeof type === 'string') {
112 normalizedProps = {};
113
114 for (let i in props) {
115 let value = props[i];
116
117 if (i === 'defaultValue' && 'value' in props && props.value == null) {
118 // `defaultValue` is treated as a fallback `value` when a value prop is present but null/undefined.
119 // `defaultValue` for Elements with no value prop is the same as the DOM defaultValue property.
120 i = 'value';
121 } else if (i === 'download' && value === true) {
122 // Calling `setAttribute` with a truthy value will lead to it being
123 // passed as a stringified value, e.g. `download="true"`. React
124 // converts it to an empty string instead, otherwise the attribute
125 // value will be used as the file name and the file will be called
126 // "true" upon downloading it.
127 value = '';
128 } else if (/ondoubleclick/i.test(i)) {
129 i = 'ondblclick';
130 } else if (
131 /^onchange(textarea|input)/i.test(i + type) &&
132 !ONCHANGE_INPUT_TYPES.test(props.type)
133 ) {
134 i = 'oninput';
135 } else if (/^on(Ani|Tra|Tou|BeforeInp)/.test(i)) {
136 i = i.toLowerCase();
137 } else if (CAMEL_PROPS.test(i)) {
138 i = i.replace(/[A-Z0-9]/, '-$&').toLowerCase();
139 } else if (value === null) {
140 value = undefined;
141 }
142
143 normalizedProps[i] = value;
144 }
145
146 // Add support for array select values: <select multiple value={[]} />
147 if (
148 type == 'select' &&
149 normalizedProps.multiple &&
150 Array.isArray(normalizedProps.value)
151 ) {
152 // forEach() always returns undefined, which we abuse here to unset the value prop.
153 normalizedProps.value = toChildArray(props.children).forEach(child => {
154 child.props.selected =
155 normalizedProps.value.indexOf(child.props.value) != -1;
156 });
157 }
158
159 vnode.props = normalizedProps;
160 }
161
162 if (type && props.class != props.className) {
163 classNameDescriptor.enumerable = 'className' in props;
164 if (props.className != null) normalizedProps.class = props.className;
165 Object.defineProperty(normalizedProps, 'className', classNameDescriptor);
166 }
167
168 vnode.$$typeof = REACT_ELEMENT_TYPE;
169
170 if (oldVNodeHook) oldVNodeHook(vnode);
171};
172
173// Only needed for react-relay
174let currentComponent;
175const oldBeforeRender = options._render;
176options._render = function(vnode) {
177 if (oldBeforeRender) {
178 oldBeforeRender(vnode);
179 }
180 currentComponent = vnode._component;
181};
182
183// This is a very very private internal function for React it
184// is used to sort-of do runtime dependency injection. So far
185// only `react-relay` makes use of it. It uses it to read the
186// context value.
187export const __SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED = {
188 ReactCurrentDispatcher: {
189 current: {
190 readContext(context) {
191 return currentComponent._globalContext[context._id].props.value;
192 }
193 }
194 }
195};