summaryrefslogtreecommitdiff
path: root/preact/compat/src
diff options
context:
space:
mode:
authorSebastian <sebasjm@gmail.com>2021-08-23 16:46:06 -0300
committerSebastian <sebasjm@gmail.com>2021-08-23 16:48:30 -0300
commit38acabfa6089ab8ac469c12b5f55022fb96935e5 (patch)
tree453dbf70000cc5e338b06201af1eaca8343f8f73 /preact/compat/src
parentf26125e039143b92dc0d84e7775f508ab0cdcaa8 (diff)
downloadnode-vendor-38acabfa6089ab8ac469c12b5f55022fb96935e5.tar.gz
node-vendor-38acabfa6089ab8ac469c12b5f55022fb96935e5.tar.bz2
node-vendor-38acabfa6089ab8ac469c12b5f55022fb96935e5.zip
added web vendorsHEADmaster
Diffstat (limited to 'preact/compat/src')
-rw-r--r--preact/compat/src/Children.js21
-rw-r--r--preact/compat/src/PureComponent.js15
-rw-r--r--preact/compat/src/forwardRef.js51
-rw-r--r--preact/compat/src/index.d.ts140
-rw-r--r--preact/compat/src/index.js187
-rw-r--r--preact/compat/src/internal.d.ts47
-rw-r--r--preact/compat/src/memo.js34
-rw-r--r--preact/compat/src/portals.js80
-rw-r--r--preact/compat/src/render.js219
-rw-r--r--preact/compat/src/suspense-list.d.ts14
-rw-r--r--preact/compat/src/suspense-list.js126
-rw-r--r--preact/compat/src/suspense.d.ts15
-rw-r--r--preact/compat/src/suspense.js270
-rw-r--r--preact/compat/src/util.js28
14 files changed, 1247 insertions, 0 deletions
diff --git a/preact/compat/src/Children.js b/preact/compat/src/Children.js
new file mode 100644
index 0000000..0295d93
--- /dev/null
+++ b/preact/compat/src/Children.js
@@ -0,0 +1,21 @@
+import { toChildArray } from 'preact';
+
+const mapFn = (children, fn) => {
+ if (children == null) return null;
+ return toChildArray(toChildArray(children).map(fn));
+};
+
+// This API is completely unnecessary for Preact, so it's basically passthrough.
+export const Children = {
+ map: mapFn,
+ forEach: mapFn,
+ count(children) {
+ return children ? toChildArray(children).length : 0;
+ },
+ only(children) {
+ const normalized = toChildArray(children);
+ if (normalized.length !== 1) throw 'Children.only';
+ return normalized[0];
+ },
+ toArray: toChildArray
+};
diff --git a/preact/compat/src/PureComponent.js b/preact/compat/src/PureComponent.js
new file mode 100644
index 0000000..6396ce4
--- /dev/null
+++ b/preact/compat/src/PureComponent.js
@@ -0,0 +1,15 @@
+import { Component } from 'preact';
+import { shallowDiffers } from './util';
+
+/**
+ * Component class with a predefined `shouldComponentUpdate` implementation
+ */
+export function PureComponent(p) {
+ this.props = p;
+}
+PureComponent.prototype = new Component();
+// Some third-party libraries check if this property is present
+PureComponent.prototype.isPureReactComponent = true;
+PureComponent.prototype.shouldComponentUpdate = function(props, state) {
+ return shallowDiffers(this.props, props) || shallowDiffers(this.state, state);
+};
diff --git a/preact/compat/src/forwardRef.js b/preact/compat/src/forwardRef.js
new file mode 100644
index 0000000..39585cb
--- /dev/null
+++ b/preact/compat/src/forwardRef.js
@@ -0,0 +1,51 @@
+import { options } from 'preact';
+import { assign } from './util';
+
+let oldDiffHook = options._diff;
+options._diff = vnode => {
+ if (vnode.type && vnode.type._forwarded && vnode.ref) {
+ vnode.props.ref = vnode.ref;
+ vnode.ref = null;
+ }
+ if (oldDiffHook) oldDiffHook(vnode);
+};
+
+export const REACT_FORWARD_SYMBOL =
+ (typeof Symbol != 'undefined' &&
+ Symbol.for &&
+ Symbol.for('react.forward_ref')) ||
+ 0xf47;
+
+/**
+ * Pass ref down to a child. This is mainly used in libraries with HOCs that
+ * wrap components. Using `forwardRef` there is an easy way to get a reference
+ * of the wrapped component instead of one of the wrapper itself.
+ * @param {import('./index').ForwardFn} fn
+ * @returns {import('./internal').FunctionComponent}
+ */
+export function forwardRef(fn) {
+ // We always have ref in props.ref, except for
+ // mobx-react. It will call this function directly
+ // and always pass ref as the second argument.
+ function Forwarded(props, ref) {
+ let clone = assign({}, props);
+ delete clone.ref;
+ ref = props.ref || ref;
+ return fn(
+ clone,
+ !ref || (typeof ref === 'object' && !('current' in ref)) ? null : ref
+ );
+ }
+
+ // mobx-react checks for this being present
+ Forwarded.$$typeof = REACT_FORWARD_SYMBOL;
+ // mobx-react heavily relies on implementation details.
+ // It expects an object here with a `render` property,
+ // and prototype.render will fail. Without this
+ // mobx-react throws.
+ Forwarded.render = Forwarded;
+
+ Forwarded.prototype.isReactComponent = Forwarded._forwarded = true;
+ Forwarded.displayName = 'ForwardRef(' + (fn.displayName || fn.name) + ')';
+ return Forwarded;
+}
diff --git a/preact/compat/src/index.d.ts b/preact/compat/src/index.d.ts
new file mode 100644
index 0000000..82c1a4f
--- /dev/null
+++ b/preact/compat/src/index.d.ts
@@ -0,0 +1,140 @@
+import * as _hooks from '../../hooks';
+import * as preact from '../../src';
+import { JSXInternal } from '../../src/jsx';
+import * as _Suspense from './suspense';
+import * as _SuspenseList from './suspense-list';
+
+// export default React;
+export = React;
+export as namespace React;
+declare namespace React {
+ // Export JSX
+ export import JSX = JSXInternal;
+
+ // Hooks
+ export import CreateHandle = _hooks.CreateHandle;
+ export import EffectCallback = _hooks.EffectCallback;
+ export import Inputs = _hooks.Inputs;
+ export import PropRef = _hooks.PropRef;
+ export import Reducer = _hooks.Reducer;
+ export import Ref = _hooks.Ref;
+ export import StateUpdater = _hooks.StateUpdater;
+ export import useCallback = _hooks.useCallback;
+ export import useContext = _hooks.useContext;
+ export import useDebugValue = _hooks.useDebugValue;
+ export import useEffect = _hooks.useEffect;
+ export import useImperativeHandle = _hooks.useImperativeHandle;
+ export import useLayoutEffect = _hooks.useLayoutEffect;
+ export import useMemo = _hooks.useMemo;
+ export import useReducer = _hooks.useReducer;
+ export import useRef = _hooks.useRef;
+ export import useState = _hooks.useState;
+
+ // Preact Defaults
+ export import Component = preact.Component;
+ export import FunctionComponent = preact.FunctionComponent;
+ export import FC = preact.FunctionComponent;
+ export import createContext = preact.createContext;
+ export import createRef = preact.createRef;
+ export import Fragment = preact.Fragment;
+ export import createElement = preact.createElement;
+ export import cloneElement = preact.cloneElement;
+
+ // Suspense
+ export import Suspense = _Suspense.Suspense;
+ export import lazy = _Suspense.lazy;
+ export import SuspenseList = _SuspenseList.SuspenseList;
+
+ // Compat
+ export import StrictMode = preact.Fragment;
+ export const version: string;
+
+ export function createPortal(
+ vnode: preact.VNode,
+ container: Element
+ ): preact.VNode<any>;
+
+ export function render(
+ vnode: preact.VNode<any>,
+ parent: Element,
+ callback?: () => void
+ ): Component | null;
+
+ export function hydrate(
+ vnode: preact.VNode<any>,
+ parent: Element,
+ callback?: () => void
+ ): Component | null;
+
+ export function unmountComponentAtNode(
+ container: Element | Document | ShadowRoot | DocumentFragment
+ ): boolean;
+
+ export function createFactory(
+ type: preact.VNode<any>['type']
+ ): (
+ props?: any,
+ ...children: preact.ComponentChildren[]
+ ) => preact.VNode<any>;
+ export function isValidElement(element: any): boolean;
+ export function findDOMNode(component: preact.Component): Element | null;
+
+ export abstract class PureComponent<P = {}, S = {}> extends preact.Component<
+ P,
+ S
+ > {
+ isPureReactComponent: boolean;
+ }
+
+ export function memo<P = {}>(
+ component: preact.FunctionalComponent<P>,
+ comparer?: (prev: P, next: P) => boolean
+ ): preact.FunctionComponent<P>;
+ export function memo<C extends preact.FunctionalComponent<any>>(
+ component: C,
+ comparer?: (
+ prev: preact.ComponentProps<C>,
+ next: preact.ComponentProps<C>
+ ) => boolean
+ ): C;
+
+ export interface ForwardFn<P = {}, T = any> {
+ (props: P, ref: Ref<T>): preact.ComponentChild;
+ displayName?: string;
+ }
+
+ export function forwardRef<R, P = {}>(
+ fn: ForwardFn<P, R>
+ ): preact.FunctionalComponent<Omit<P, 'ref'> & { ref?: preact.RefObject<R> }>;
+
+ export function unstable_batchedUpdates(
+ callback: (arg?: any) => void,
+ arg?: any
+ ): void;
+
+ export const Children: {
+ map<T extends preact.ComponentChild, R>(
+ children: T | T[],
+ fn: (child: T, i: number) => R
+ ): R[];
+ forEach<T extends preact.ComponentChild>(
+ children: T | T[],
+ fn: (child: T, i: number) => void
+ ): void;
+ count: (children: preact.ComponentChildren) => number;
+ only: (children: preact.ComponentChildren) => preact.ComponentChild;
+ toArray: (children: preact.ComponentChildren) => preact.VNode<{}>[];
+ };
+
+ // scheduler
+ export const unstable_ImmediatePriority: number;
+ export const unstable_UserBlockingPriority: number;
+ export const unstable_NormalPriority: number;
+ export const unstable_LowPriority: number;
+ export const unstable_IdlePriority: number;
+ export function unstable_runWithPriority(
+ priority: number,
+ callback: () => void
+ ): void;
+ export const unstable_now: () => number;
+}
diff --git a/preact/compat/src/index.js b/preact/compat/src/index.js
new file mode 100644
index 0000000..c8f9a6c
--- /dev/null
+++ b/preact/compat/src/index.js
@@ -0,0 +1,187 @@
+import {
+ createElement,
+ render as preactRender,
+ cloneElement as preactCloneElement,
+ createRef,
+ Component,
+ createContext,
+ Fragment
+} from 'preact';
+import {
+ useState,
+ useReducer,
+ useEffect,
+ useLayoutEffect,
+ useRef,
+ useImperativeHandle,
+ useMemo,
+ useCallback,
+ useContext,
+ useDebugValue
+} from 'preact/hooks';
+import { PureComponent } from './PureComponent';
+import { memo } from './memo';
+import { forwardRef } from './forwardRef';
+import { Children } from './Children';
+import { Suspense, lazy } from './suspense';
+import { SuspenseList } from './suspense-list';
+import { createPortal } from './portals';
+import {
+ hydrate,
+ render,
+ REACT_ELEMENT_TYPE,
+ __SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED
+} from './render';
+
+const version = '17.0.2'; // trick libraries to think we are react
+
+/**
+ * Legacy version of createElement.
+ * @param {import('./internal').VNode["type"]} type The node name or Component constructor
+ */
+function createFactory(type) {
+ return createElement.bind(null, type);
+}
+
+/**
+ * Check if the passed element is a valid (p)react node.
+ * @param {*} element The element to check
+ * @returns {boolean}
+ */
+function isValidElement(element) {
+ return !!element && element.$$typeof === REACT_ELEMENT_TYPE;
+}
+
+/**
+ * Wrap `cloneElement` to abort if the passed element is not a valid element and apply
+ * all vnode normalizations.
+ * @param {import('./internal').VNode} element The vnode to clone
+ * @param {object} props Props to add when cloning
+ * @param {Array<import('./internal').ComponentChildren>} rest Optional component children
+ */
+function cloneElement(element) {
+ if (!isValidElement(element)) return element;
+ return preactCloneElement.apply(null, arguments);
+}
+
+/**
+ * Remove a component tree from the DOM, including state and event handlers.
+ * @param {import('./internal').PreactElement} container
+ * @returns {boolean}
+ */
+function unmountComponentAtNode(container) {
+ if (container._children) {
+ preactRender(null, container);
+ return true;
+ }
+ return false;
+}
+
+/**
+ * Get the matching DOM node for a component
+ * @param {import('./internal').Component} component
+ * @returns {import('./internal').PreactElement | null}
+ */
+function findDOMNode(component) {
+ return (
+ (component &&
+ (component.base || (component.nodeType === 1 && component))) ||
+ null
+ );
+}
+
+/**
+ * Deprecated way to control batched rendering inside the reconciler, but we
+ * already schedule in batches inside our rendering code
+ * @template Arg
+ * @param {(arg: Arg) => void} callback function that triggers the updated
+ * @param {Arg} [arg] Optional argument that can be passed to the callback
+ */
+// eslint-disable-next-line camelcase
+const unstable_batchedUpdates = (callback, arg) => callback(arg);
+
+/**
+ * In React, `flushSync` flushes the entire tree and forces a rerender. It's
+ * implmented here as a no-op.
+ * @template Arg
+ * @template Result
+ * @param {(arg: Arg) => Result} callback function that runs before the flush
+ * @param {Arg} [arg] Optional arugment that can be passed to the callback
+ * @returns
+ */
+const flushSync = (callback, arg) => callback(arg);
+
+/**
+ * Strict Mode is not implemented in Preact, so we provide a stand-in for it
+ * that just renders its children without imposing any restrictions.
+ */
+const StrictMode = Fragment;
+
+export * from 'preact/hooks';
+export {
+ version,
+ Children,
+ render,
+ hydrate,
+ unmountComponentAtNode,
+ createPortal,
+ createElement,
+ createContext,
+ createFactory,
+ cloneElement,
+ createRef,
+ Fragment,
+ isValidElement,
+ findDOMNode,
+ Component,
+ PureComponent,
+ memo,
+ forwardRef,
+ flushSync,
+ // eslint-disable-next-line camelcase
+ unstable_batchedUpdates,
+ StrictMode,
+ Suspense,
+ SuspenseList,
+ lazy,
+ __SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED
+};
+
+// React copies the named exports to the default one.
+export default {
+ useState,
+ useReducer,
+ useEffect,
+ useLayoutEffect,
+ useRef,
+ useImperativeHandle,
+ useMemo,
+ useCallback,
+ useContext,
+ useDebugValue,
+ version,
+ Children,
+ render,
+ hydrate,
+ unmountComponentAtNode,
+ createPortal,
+ createElement,
+ createContext,
+ createFactory,
+ cloneElement,
+ createRef,
+ Fragment,
+ isValidElement,
+ findDOMNode,
+ Component,
+ PureComponent,
+ memo,
+ forwardRef,
+ flushSync,
+ unstable_batchedUpdates,
+ StrictMode,
+ Suspense,
+ SuspenseList,
+ lazy,
+ __SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED
+};
diff --git a/preact/compat/src/internal.d.ts b/preact/compat/src/internal.d.ts
new file mode 100644
index 0000000..cb68ffa
--- /dev/null
+++ b/preact/compat/src/internal.d.ts
@@ -0,0 +1,47 @@
+import {
+ Component as PreactComponent,
+ VNode as PreactVNode,
+ FunctionComponent as PreactFunctionComponent
+} from '../../src/internal';
+import { SuspenseProps } from './suspense';
+
+export { ComponentChildren } from '../..';
+
+export { PreactElement } from '../../src/internal';
+
+export interface Component<P = {}, S = {}> extends PreactComponent<P, S> {
+ isReactComponent?: object;
+ isPureReactComponent?: true;
+ _patchedLifecycles?: true;
+
+ // Suspense internal properties
+ _childDidSuspend?(error: Promise<void>, suspendingVNode: VNode): void;
+ _suspended: (vnode: VNode) => (unsuspend: () => void) => void;
+ _onResolve?(): void;
+
+ // Portal internal properties
+ _temp: any;
+ _container: PreactElement;
+}
+
+export interface FunctionComponent<P = {}> extends PreactFunctionComponent<P> {
+ shouldComponentUpdate?(nextProps: Readonly<P>): boolean;
+ _forwarded?: boolean;
+ _patchedLifecycles?: true;
+}
+
+export interface VNode<T = any> extends PreactVNode<T> {
+ $$typeof?: symbol | string;
+ preactCompatNormalized?: boolean;
+}
+
+export interface SuspenseState {
+ _suspended?: null | VNode<any>;
+}
+
+export interface SuspenseComponent
+ extends PreactComponent<SuspenseProps, SuspenseState> {
+ _pendingSuspensionCount: number;
+ _suspenders: Component[];
+ _detachOnNextRender: null | VNode<any>;
+}
diff --git a/preact/compat/src/memo.js b/preact/compat/src/memo.js
new file mode 100644
index 0000000..e743199
--- /dev/null
+++ b/preact/compat/src/memo.js
@@ -0,0 +1,34 @@
+import { createElement } from 'preact';
+import { shallowDiffers } from './util';
+
+/**
+ * Memoize a component, so that it only updates when the props actually have
+ * changed. This was previously known as `React.pure`.
+ * @param {import('./internal').FunctionComponent} c functional component
+ * @param {(prev: object, next: object) => boolean} [comparer] Custom equality function
+ * @returns {import('./internal').FunctionComponent}
+ */
+export function memo(c, comparer) {
+ function shouldUpdate(nextProps) {
+ let ref = this.props.ref;
+ let updateRef = ref == nextProps.ref;
+ if (!updateRef && ref) {
+ ref.call ? ref(null) : (ref.current = null);
+ }
+
+ if (!comparer) {
+ return shallowDiffers(this.props, nextProps);
+ }
+
+ return !comparer(this.props, nextProps) || !updateRef;
+ }
+
+ function Memoed(props) {
+ this.shouldComponentUpdate = shouldUpdate;
+ return createElement(c, props);
+ }
+ Memoed.displayName = 'Memo(' + (c.displayName || c.name) + ')';
+ Memoed.prototype.isReactComponent = true;
+ Memoed._forwarded = true;
+ return Memoed;
+}
diff --git a/preact/compat/src/portals.js b/preact/compat/src/portals.js
new file mode 100644
index 0000000..cf9f8f7
--- /dev/null
+++ b/preact/compat/src/portals.js
@@ -0,0 +1,80 @@
+import { createElement, render } from 'preact';
+
+/**
+ * @param {import('../../src/index').RenderableProps<{ context: any }>} props
+ */
+function ContextProvider(props) {
+ this.getChildContext = () => props.context;
+ return props.children;
+}
+
+/**
+ * Portal component
+ * @this {import('./internal').Component}
+ * @param {object | null | undefined} props
+ *
+ * TODO: use createRoot() instead of fake root
+ */
+function Portal(props) {
+ const _this = this;
+ let container = props._container;
+
+ _this.componentWillUnmount = function() {
+ render(null, _this._temp);
+ _this._temp = null;
+ _this._container = null;
+ };
+
+ // When we change container we should clear our old container and
+ // indicate a new mount.
+ if (_this._container && _this._container !== container) {
+ _this.componentWillUnmount();
+ }
+
+ // When props.vnode is undefined/false/null we are dealing with some kind of
+ // conditional vnode. This should not trigger a render.
+ if (props._vnode) {
+ if (!_this._temp) {
+ _this._container = container;
+
+ // Create a fake DOM parent node that manages a subset of `container`'s children:
+ _this._temp = {
+ nodeType: 1,
+ parentNode: container,
+ childNodes: [],
+ appendChild(child) {
+ this.childNodes.push(child);
+ _this._container.appendChild(child);
+ },
+ insertBefore(child, before) {
+ this.childNodes.push(child);
+ _this._container.appendChild(child);
+ },
+ removeChild(child) {
+ this.childNodes.splice(this.childNodes.indexOf(child) >>> 1, 1);
+ _this._container.removeChild(child);
+ }
+ };
+ }
+
+ // Render our wrapping element into temp.
+ render(
+ createElement(ContextProvider, { context: _this.context }, props._vnode),
+ _this._temp
+ );
+ }
+ // When we come from a conditional render, on a mounted
+ // portal we should clear the DOM.
+ else if (_this._temp) {
+ _this.componentWillUnmount();
+ }
+}
+
+/**
+ * Create a `Portal` to continue rendering the vnode tree at a different DOM node
+ * @param {import('./internal').VNode} vnode The vnode to render
+ * @param {import('./internal').PreactElement} container The DOM node to continue rendering in to.
+ */
+export function createPortal(vnode, container) {
+ return createElement(Portal, { _vnode: vnode, _container: container });
+}
diff --git a/preact/compat/src/render.js b/preact/compat/src/render.js
new file mode 100644
index 0000000..2448d2d
--- /dev/null
+++ b/preact/compat/src/render.js
@@ -0,0 +1,219 @@
+import {
+ render as preactRender,
+ hydrate as preactHydrate,
+ options,
+ toChildArray,
+ Component
+} from 'preact';
+
+export const REACT_ELEMENT_TYPE =
+ (typeof Symbol != 'undefined' && Symbol.for && Symbol.for('react.element')) ||
+ 0xeac7;
+
+const 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]/;
+
+// Input types for which onchange should not be converted to oninput.
+// type="file|checkbox|radio", plus "range" in IE11.
+// (IE11 doesn't support Symbol, which we use here to turn `rad` into `ra` which matches "range")
+const onChangeInputType = type =>
+ (typeof Symbol != 'undefined' && typeof Symbol() == 'symbol'
+ ? /fil|che|rad/i
+ : /fil|che|ra/i
+ ).test(type);
+
+// Some libraries like `react-virtualized` explicitly check for this.
+Component.prototype.isReactComponent = {};
+
+// `UNSAFE_*` lifecycle hooks
+// Preact only ever invokes the unprefixed methods.
+// Here we provide a base "fallback" implementation that calls any defined UNSAFE_ prefixed method.
+// - If a component defines its own `componentDidMount()` (including via defineProperty), use that.
+// - If a component defines `UNSAFE_componentDidMount()`, `componentDidMount` is the alias getter/setter.
+// - If anything assigns to an `UNSAFE_*` property, the assignment is forwarded to the unprefixed property.
+// See https://github.com/preactjs/preact/issues/1941
+[
+ 'componentWillMount',
+ 'componentWillReceiveProps',
+ 'componentWillUpdate'
+].forEach(key => {
+ Object.defineProperty(Component.prototype, key, {
+ configurable: true,
+ get() {
+ return this['UNSAFE_' + key];
+ },
+ set(v) {
+ Object.defineProperty(this, key, {
+ configurable: true,
+ writable: true,
+ value: v
+ });
+ }
+ });
+});
+
+/**
+ * Proxy render() since React returns a Component reference.
+ * @param {import('./internal').VNode} vnode VNode tree to render
+ * @param {import('./internal').PreactElement} parent DOM node to render vnode tree into
+ * @param {() => void} [callback] Optional callback that will be called after rendering
+ * @returns {import('./internal').Component | null} The root component reference or null
+ */
+export function render(vnode, parent, callback) {
+ // React destroys any existing DOM nodes, see #1727
+ // ...but only on the first render, see #1828
+ if (parent._children == null) {
+ parent.textContent = '';
+ }
+
+ preactRender(vnode, parent);
+ if (typeof callback == 'function') callback();
+
+ return vnode ? vnode._component : null;
+}
+
+export function hydrate(vnode, parent, callback) {
+ preactHydrate(vnode, parent);
+ if (typeof callback == 'function') callback();
+
+ return vnode ? vnode._component : null;
+}
+
+let oldEventHook = options.event;
+options.event = e => {
+ if (oldEventHook) e = oldEventHook(e);
+ e.persist = empty;
+ e.isPropagationStopped = isPropagationStopped;
+ e.isDefaultPrevented = isDefaultPrevented;
+ return (e.nativeEvent = e);
+};
+
+function empty() {}
+
+function isPropagationStopped() {
+ return this.cancelBubble;
+}
+
+function isDefaultPrevented() {
+ return this.defaultPrevented;
+}
+
+let classNameDescriptor = {
+ configurable: true,
+ get() {
+ return this.class;
+ }
+};
+
+let oldVNodeHook = options.vnode;
+options.vnode = vnode => {
+ let type = vnode.type;
+ let props = vnode.props;
+ let normalizedProps = props;
+
+ // only normalize props on Element nodes
+ if (typeof type === 'string') {
+ normalizedProps = {};
+
+ for (let i in props) {
+ let value = props[i];
+
+ if (i === 'value' && 'defaultValue' in props && value == null) {
+ // Skip applying value if it is null/undefined and we already set
+ // a default value
+ continue;
+ } else if (
+ i === 'defaultValue' &&
+ 'value' in props &&
+ props.value == null
+ ) {
+ // `defaultValue` is treated as a fallback `value` when a value prop is present but null/undefined.
+ // `defaultValue` for Elements with no value prop is the same as the DOM defaultValue property.
+ i = 'value';
+ } else if (i === 'download' && value === true) {
+ // Calling `setAttribute` with a truthy value will lead to it being
+ // passed as a stringified value, e.g. `download="true"`. React
+ // converts it to an empty string instead, otherwise the attribute
+ // value will be used as the file name and the file will be called
+ // "true" upon downloading it.
+ value = '';
+ } else if (/ondoubleclick/i.test(i)) {
+ i = 'ondblclick';
+ } else if (
+ /^onchange(textarea|input)/i.test(i + type) &&
+ !onChangeInputType(props.type)
+ ) {
+ i = 'oninput';
+ } else if (/^on(Ani|Tra|Tou|BeforeInp)/.test(i)) {
+ i = i.toLowerCase();
+ } else if (CAMEL_PROPS.test(i)) {
+ i = i.replace(/[A-Z0-9]/, '-$&').toLowerCase();
+ } else if (value === null) {
+ value = undefined;
+ }
+
+ normalizedProps[i] = value;
+ }
+
+ // Add support for array select values: <select multiple value={[]} />
+ if (
+ type == 'select' &&
+ normalizedProps.multiple &&
+ Array.isArray(normalizedProps.value)
+ ) {
+ // forEach() always returns undefined, which we abuse here to unset the value prop.
+ normalizedProps.value = toChildArray(props.children).forEach(child => {
+ child.props.selected =
+ normalizedProps.value.indexOf(child.props.value) != -1;
+ });
+ }
+
+ // Adding support for defaultValue in select tag
+ if (type == 'select' && normalizedProps.defaultValue != null) {
+ normalizedProps.value = toChildArray(props.children).forEach(child => {
+ if (normalizedProps.multiple) {
+ child.props.selected =
+ normalizedProps.defaultValue.indexOf(child.props.value) != -1;
+ } else {
+ child.props.selected =
+ normalizedProps.defaultValue == child.props.value;
+ }
+ });
+ }
+
+ vnode.props = normalizedProps;
+ }
+
+ if (type && props.class != props.className) {
+ classNameDescriptor.enumerable = 'className' in props;
+ if (props.className != null) normalizedProps.class = props.className;
+ Object.defineProperty(normalizedProps, 'className', classNameDescriptor);
+ }
+
+ vnode.$$typeof = REACT_ELEMENT_TYPE;
+
+ if (oldVNodeHook) oldVNodeHook(vnode);
+};
+
+// Only needed for react-relay
+let currentComponent;
+const oldBeforeRender = options._render;
+options._render = function(vnode) {
+ if (oldBeforeRender) {
+ oldBeforeRender(vnode);
+ }
+ currentComponent = vnode._component;
+};
+
+// This is a very very private internal function for React it
+// is used to sort-of do runtime dependency injection. So far
+// only `react-relay` makes use of it. It uses it to read the
+// context value.
+export const __SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED = {
+ ReactCurrentDispatcher: {
+ current: {
+ readContext(context) {
+ return currentComponent._globalContext[context._id].props.value;
+ }
+ }
+ }
+};
diff --git a/preact/compat/src/suspense-list.d.ts b/preact/compat/src/suspense-list.d.ts
new file mode 100644
index 0000000..caa1eb6
--- /dev/null
+++ b/preact/compat/src/suspense-list.d.ts
@@ -0,0 +1,14 @@
+import { Component, ComponentChild, ComponentChildren } from '../../src';
+
+//
+// SuspenseList
+// -----------------------------------
+
+export interface SuspenseListProps {
+ children?: ComponentChildren;
+ revealOrder?: 'forwards' | 'backwards' | 'together';
+}
+
+export class SuspenseList extends Component<SuspenseListProps> {
+ render(): ComponentChild;
+}
diff --git a/preact/compat/src/suspense-list.js b/preact/compat/src/suspense-list.js
new file mode 100644
index 0000000..cf162cb
--- /dev/null
+++ b/preact/compat/src/suspense-list.js
@@ -0,0 +1,126 @@
+import { Component, toChildArray } from 'preact';
+import { suspended } from './suspense.js';
+
+// Indexes to linked list nodes (nodes are stored as arrays to save bytes).
+const SUSPENDED_COUNT = 0;
+const RESOLVED_COUNT = 1;
+const NEXT_NODE = 2;
+
+// Having custom inheritance instead of a class here saves a lot of bytes.
+export function SuspenseList() {
+ this._next = null;
+ this._map = null;
+}
+
+// Mark one of child's earlier suspensions as resolved.
+// Some pending callbacks may become callable due to this
+// (e.g. the last suspended descendant gets resolved when
+// revealOrder === 'together'). Process those callbacks as well.
+const resolve = (list, child, node) => {
+ if (++node[RESOLVED_COUNT] === node[SUSPENDED_COUNT]) {
+ // The number a child (or any of its descendants) has been suspended
+ // matches the number of times it's been resolved. Therefore we
+ // mark the child as completely resolved by deleting it from ._map.
+ // This is used to figure out when *all* children have been completely
+ // resolved when revealOrder is 'together'.
+ list._map.delete(child);
+ }
+
+ // If revealOrder is falsy then we can do an early exit, as the
+ // callbacks won't get queued in the node anyway.
+ // If revealOrder is 'together' then also do an early exit
+ // if all suspended descendants have not yet been resolved.
+ if (
+ !list.props.revealOrder ||
+ (list.props.revealOrder[0] === 't' && list._map.size)
+ ) {
+ return;
+ }
+
+ // Walk the currently suspended children in order, calling their
+ // stored callbacks on the way. Stop if we encounter a child that
+ // has not been completely resolved yet.
+ node = list._next;
+ while (node) {
+ while (node.length > 3) {
+ node.pop()();
+ }
+ if (node[RESOLVED_COUNT] < node[SUSPENDED_COUNT]) {
+ break;
+ }
+ list._next = node = node[NEXT_NODE];
+ }
+};
+
+// Things we do here to save some bytes but are not proper JS inheritance:
+// - call `new Component()` as the prototype
+// - do not set `Suspense.prototype.constructor` to `Suspense`
+SuspenseList.prototype = new Component();
+
+SuspenseList.prototype._suspended = function(child) {
+ const list = this;
+ const delegated = suspended(list._vnode);
+
+ let node = list._map.get(child);
+ node[SUSPENDED_COUNT]++;
+
+ return unsuspend => {
+ const wrappedUnsuspend = () => {
+ if (!list.props.revealOrder) {
+ // Special case the undefined (falsy) revealOrder, as there
+ // is no need to coordinate a specific order or unsuspends.
+ unsuspend();
+ } else {
+ node.push(unsuspend);
+ resolve(list, child, node);
+ }
+ };
+ if (delegated) {
+ delegated(wrappedUnsuspend);
+ } else {
+ wrappedUnsuspend();
+ }
+ };
+};
+
+SuspenseList.prototype.render = function(props) {
+ this._next = null;
+ this._map = new Map();
+
+ const children = toChildArray(props.children);
+ if (props.revealOrder && props.revealOrder[0] === 'b') {
+ // If order === 'backwards' (or, well, anything starting with a 'b')
+ // then flip the child list around so that the last child will be
+ // the first in the linked list.
+ children.reverse();
+ }
+ // Build the linked list. Iterate through the children in reverse order
+ // so that `_next` points to the first linked list node to be resolved.
+ for (let i = children.length; i--; ) {
+ // Create a new linked list node as an array of form:
+ // [suspended_count, resolved_count, next_node]
+ // where suspended_count and resolved_count are numeric counters for
+ // keeping track how many times a node has been suspended and resolved.
+ //
+ // Note that suspended_count starts from 1 instead of 0, so we can block
+ // processing callbacks until componentDidMount has been called. In a sense
+ // node is suspended at least until componentDidMount gets called!
+ //
+ // Pending callbacks are added to the end of the node:
+ // [suspended_count, resolved_count, next_node, callback_0, callback_1, ...]
+ this._map.set(children[i], (this._next = [1, 0, this._next]));
+ }
+ return props.children;
+};
+
+SuspenseList.prototype.componentDidUpdate = SuspenseList.prototype.componentDidMount = function() {
+ // Iterate through all children after mounting for two reasons:
+ // 1. As each node[SUSPENDED_COUNT] starts from 1, this iteration increases
+ // each node[RELEASED_COUNT] by 1, therefore balancing the counters.
+ // The nodes can now be completely consumed from the linked list.
+ // 2. Handle nodes that might have gotten resolved between render and
+ // componentDidMount.
+ this._map.forEach((node, child) => {
+ resolve(this, child, node);
+ });
+};
diff --git a/preact/compat/src/suspense.d.ts b/preact/compat/src/suspense.d.ts
new file mode 100644
index 0000000..9bd0e74
--- /dev/null
+++ b/preact/compat/src/suspense.d.ts
@@ -0,0 +1,15 @@
+import { Component, ComponentChild, ComponentChildren } from '../../src';
+
+//
+// Suspense/lazy
+// -----------------------------------
+export function lazy<T>(loader: () => Promise<{ default: T } | T>): T;
+
+export interface SuspenseProps {
+ children?: ComponentChildren;
+ fallback: ComponentChildren;
+}
+
+export class Suspense extends Component<SuspenseProps> {
+ render(): ComponentChild;
+}
diff --git a/preact/compat/src/suspense.js b/preact/compat/src/suspense.js
new file mode 100644
index 0000000..6244eaf
--- /dev/null
+++ b/preact/compat/src/suspense.js
@@ -0,0 +1,270 @@
+import { Component, createElement, options, Fragment } from 'preact';
+import { assign } from './util';
+
+const oldCatchError = options._catchError;
+options._catchError = function(error, newVNode, oldVNode) {
+ if (error.then) {
+ /** @type {import('./internal').Component} */
+ let component;
+ let vnode = newVNode;
+
+ for (; (vnode = vnode._parent); ) {
+ if ((component = vnode._component) && component._childDidSuspend) {
+ if (newVNode._dom == null) {
+ newVNode._dom = oldVNode._dom;
+ newVNode._children = oldVNode._children;
+ }
+ // Don't call oldCatchError if we found a Suspense
+ return component._childDidSuspend(error, newVNode);
+ }
+ }
+ }
+ oldCatchError(error, newVNode, oldVNode);
+};
+
+const oldUnmount = options.unmount;
+options.unmount = function(vnode) {
+ /** @type {import('./internal').Component} */
+ const component = vnode._component;
+ if (component && component._onResolve) {
+ component._onResolve();
+ }
+
+ // if the component is still hydrating
+ // most likely it is because the component is suspended
+ // we set the vnode.type as `null` so that it is not a typeof function
+ // so the unmount will remove the vnode._dom
+ if (component && vnode._hydrating === true) {
+ vnode.type = null;
+ }
+
+ if (oldUnmount) oldUnmount(vnode);
+};
+
+function detachedClone(vnode, detachedParent, parentDom) {
+ if (vnode) {
+ if (vnode._component && vnode._component.__hooks) {
+ vnode._component.__hooks._list.forEach(effect => {
+ if (typeof effect._cleanup == 'function') effect._cleanup();
+ });
+
+ vnode._component.__hooks = null;
+ }
+
+ vnode = assign({}, vnode);
+ if (vnode._component != null) {
+ if (vnode._component._parentDom === parentDom) {
+ vnode._component._parentDom = detachedParent;
+ }
+ vnode._component = null;
+ }
+
+ vnode._children =
+ vnode._children &&
+ vnode._children.map(child =>
+ detachedClone(child, detachedParent, parentDom)
+ );
+ }
+
+ return vnode;
+}
+
+function removeOriginal(vnode, detachedParent, originalParent) {
+ if (vnode) {
+ vnode._original = null;
+ vnode._children =
+ vnode._children &&
+ vnode._children.map(child =>
+ removeOriginal(child, detachedParent, originalParent)
+ );
+
+ if (vnode._component) {
+ if (vnode._component._parentDom === detachedParent) {
+ if (vnode._dom) {
+ originalParent.insertBefore(vnode._dom, vnode._nextDom);
+ }
+ vnode._component._force = true;
+ vnode._component._parentDom = originalParent;
+ }
+ }
+ }
+
+ return vnode;
+}
+
+// having custom inheritance instead of a class here saves a lot of bytes
+export function Suspense() {
+ // we do not call super here to golf some bytes...
+ this._pendingSuspensionCount = 0;
+ this._suspenders = null;
+ this._detachOnNextRender = null;
+}
+
+// Things we do here to save some bytes but are not proper JS inheritance:
+// - call `new Component()` as the prototype
+// - do not set `Suspense.prototype.constructor` to `Suspense`
+Suspense.prototype = new Component();
+
+/**
+ * @this {import('./internal').SuspenseComponent}
+ * @param {Promise} promise The thrown promise
+ * @param {import('./internal').VNode<any, any>} suspendingVNode The suspending component
+ */
+Suspense.prototype._childDidSuspend = function(promise, suspendingVNode) {
+ const suspendingComponent = suspendingVNode._component;
+
+ /** @type {import('./internal').SuspenseComponent} */
+ const c = this;
+
+ if (c._suspenders == null) {
+ c._suspenders = [];
+ }
+ c._suspenders.push(suspendingComponent);
+
+ const resolve = suspended(c._vnode);
+
+ let resolved = false;
+ const onResolved = () => {
+ if (resolved) return;
+
+ resolved = true;
+ suspendingComponent._onResolve = null;
+
+ if (resolve) {
+ resolve(onSuspensionComplete);
+ } else {
+ onSuspensionComplete();
+ }
+ };
+
+ suspendingComponent._onResolve = onResolved;
+
+ const onSuspensionComplete = () => {
+ if (!--c._pendingSuspensionCount) {
+ // If the suspension was during hydration we don't need to restore the
+ // suspended children into the _children array
+ if (c.state._suspended) {
+ const suspendedVNode = c.state._suspended;
+ c._vnode._children[0] = removeOriginal(
+ suspendedVNode,
+ suspendedVNode._component._parentDom,
+ suspendedVNode._component._originalParentDom
+ );
+ }
+
+ c.setState({ _suspended: (c._detachOnNextRender = null) });
+
+ let suspended;
+ while ((suspended = c._suspenders.pop())) {
+ suspended.forceUpdate();
+ }
+ }
+ };
+
+ /**
+ * We do not set `suspended: true` during hydration because we want the actual markup
+ * to remain on screen and hydrate it when the suspense actually gets resolved.
+ * While in non-hydration cases the usual fallback -> component flow would occour.
+ */
+ const wasHydrating = suspendingVNode._hydrating === true;
+ if (!c._pendingSuspensionCount++ && !wasHydrating) {
+ c.setState({ _suspended: (c._detachOnNextRender = c._vnode._children[0]) });
+ }
+ promise.then(onResolved, onResolved);
+};
+
+Suspense.prototype.componentWillUnmount = function() {
+ this._suspenders = [];
+};
+
+/**
+ * @this {import('./internal').SuspenseComponent}
+ * @param {import('./internal').SuspenseComponent["props"]} props
+ * @param {import('./internal').SuspenseState} state
+ */
+Suspense.prototype.render = function(props, state) {
+ if (this._detachOnNextRender) {
+ // When the Suspense's _vnode was created by a call to createVNode
+ // (i.e. due to a setState further up in the tree)
+ // it's _children prop is null, in this case we "forget" about the parked vnodes to detach
+ if (this._vnode._children) {
+ const detachedParent = document.createElement('div');
+ const detachedComponent = this._vnode._children[0]._component;
+ this._vnode._children[0] = detachedClone(
+ this._detachOnNextRender,
+ detachedParent,
+ (detachedComponent._originalParentDom = detachedComponent._parentDom)
+ );
+ }
+
+ this._detachOnNextRender = null;
+ }
+
+ // Wrap fallback tree in a VNode that prevents itself from being marked as aborting mid-hydration:
+ /** @type {import('./internal').VNode} */
+ const fallback =
+ state._suspended && createElement(Fragment, null, props.fallback);
+ if (fallback) fallback._hydrating = null;
+
+ return [
+ createElement(Fragment, null, state._suspended ? null : props.children),
+ fallback
+ ];
+};
+
+/**
+ * Checks and calls the parent component's _suspended method, passing in the
+ * suspended vnode. This is a way for a parent (e.g. SuspenseList) to get notified
+ * that one of its children/descendants suspended.
+ *
+ * The parent MAY return a callback. The callback will get called when the
+ * suspension resolves, notifying the parent of the fact.
+ * Moreover, the callback gets function `unsuspend` as a parameter. The resolved
+ * child descendant will not actually get unsuspended until `unsuspend` gets called.
+ * This is a way for the parent to delay unsuspending.
+ *
+ * If the parent does not return a callback then the resolved vnode
+ * gets unsuspended immediately when it resolves.
+ *
+ * @param {import('./internal').VNode} vnode
+ * @returns {((unsuspend: () => void) => void)?}
+ */
+export function suspended(vnode) {
+ /** @type {import('./internal').Component} */
+ let component = vnode._parent._component;
+ return component && component._suspended && component._suspended(vnode);
+}
+
+export function lazy(loader) {
+ let prom;
+ let component;
+ let error;
+
+ function Lazy(props) {
+ if (!prom) {
+ prom = loader();
+ prom.then(
+ exports => {
+ component = exports.default || exports;
+ },
+ e => {
+ error = e;
+ }
+ );
+ }
+
+ if (error) {
+ throw error;
+ }
+
+ if (!component) {
+ throw prom;
+ }
+
+ return createElement(component, props);
+ }
+
+ Lazy.displayName = 'Lazy';
+ Lazy._forwarded = true;
+ return Lazy;
+}
diff --git a/preact/compat/src/util.js b/preact/compat/src/util.js
new file mode 100644
index 0000000..fa12b09
--- /dev/null
+++ b/preact/compat/src/util.js
@@ -0,0 +1,28 @@
+/**
+ * Assign properties from `props` to `obj`
+ * @template O, P The obj and props types
+ * @param {O} obj The object to copy properties to
+ * @param {P} props The object to copy properties from
+ * @returns {O & P}
+ */
+export function assign(obj, props) {
+ for (let i in props) obj[i] = props[i];
+ return /** @type {O & P} */ (obj);
+}
+
+/**
+ * Check if two objects have a different shape
+ * @param {object} a
+ * @param {object} b
+ * @returns {boolean}
+ */
+export function shallowDiffers(a, b) {
+ for (let i in a) if (i !== '__source' && !(i in b)) return true;
+ for (let i in b) if (i !== '__source' && a[i] !== b[i]) return true;
+ return false;
+}
+
+export function removeNode(node) {
+ let parentNode = node.parentNode;
+ if (parentNode) parentNode.removeChild(node);
+}