aboutsummaryrefslogtreecommitdiff
path: root/thirdparty/preact/src/vdom/component.js
diff options
context:
space:
mode:
Diffstat (limited to 'thirdparty/preact/src/vdom/component.js')
-rw-r--r--thirdparty/preact/src/vdom/component.js270
1 files changed, 270 insertions, 0 deletions
diff --git a/thirdparty/preact/src/vdom/component.js b/thirdparty/preact/src/vdom/component.js
new file mode 100644
index 000000000..bb2e4fa5d
--- /dev/null
+++ b/thirdparty/preact/src/vdom/component.js
@@ -0,0 +1,270 @@
+import { SYNC_RENDER, NO_RENDER, FORCE_RENDER, ASYNC_RENDER, ATTR_KEY } from '../constants';
+import options from '../options';
+import { isFunction, clone, extend } from '../util';
+import { enqueueRender } from '../render-queue';
+import { getNodeProps } from './index';
+import { diff, mounts, diffLevel, flushMounts, removeOrphanedChildren, recollectNodeTree } from './diff';
+import { isFunctionalComponent, buildFunctionalComponent } from './functional-component';
+import { createComponent, collectComponent } from './component-recycler';
+import { removeNode } from '../dom/index';
+
+
+
+/** Set a component's `props` (generally derived from JSX attributes).
+ * @param {Object} props
+ * @param {Object} [opts]
+ * @param {boolean} [opts.renderSync=false] If `true` and {@link options.syncComponentUpdates} is `true`, triggers synchronous rendering.
+ * @param {boolean} [opts.render=true] If `false`, no render will be triggered.
+ */
+export function setComponentProps(component, props, opts, context, mountAll) {
+ if (component._disable) return;
+ component._disable = true;
+
+ if ((component.__ref = props.ref)) delete props.ref;
+ if ((component.__key = props.key)) delete props.key;
+
+ if (!component.base || mountAll) {
+ if (component.componentWillMount) component.componentWillMount();
+ }
+ else if (component.componentWillReceiveProps) {
+ component.componentWillReceiveProps(props, context);
+ }
+
+ if (context && context!==component.context) {
+ if (!component.prevContext) component.prevContext = component.context;
+ component.context = context;
+ }
+
+ if (!component.prevProps) component.prevProps = component.props;
+ component.props = props;
+
+ component._disable = false;
+
+ if (opts!==NO_RENDER) {
+ if (opts===SYNC_RENDER || options.syncComponentUpdates!==false || !component.base) {
+ renderComponent(component, SYNC_RENDER, mountAll);
+ }
+ else {
+ enqueueRender(component);
+ }
+ }
+
+ if (component.__ref) component.__ref(component);
+}
+
+
+
+/** Render a Component, triggering necessary lifecycle events and taking High-Order Components into account.
+ * @param {Component} component
+ * @param {Object} [opts]
+ * @param {boolean} [opts.build=false] If `true`, component will build and store a DOM node if not already associated with one.
+ * @private
+ */
+export function renderComponent(component, opts, mountAll, isChild) {
+ if (component._disable) return;
+
+ let skip, rendered,
+ props = component.props,
+ state = component.state,
+ context = component.context,
+ previousProps = component.prevProps || props,
+ previousState = component.prevState || state,
+ previousContext = component.prevContext || context,
+ isUpdate = component.base,
+ nextBase = component.nextBase,
+ initialBase = isUpdate || nextBase,
+ initialChildComponent = component._component,
+ inst, cbase;
+
+ // if updating
+ if (isUpdate) {
+ component.props = previousProps;
+ component.state = previousState;
+ component.context = previousContext;
+ if (opts!==FORCE_RENDER
+ && component.shouldComponentUpdate
+ && component.shouldComponentUpdate(props, state, context) === false) {
+ skip = true;
+ }
+ else if (component.componentWillUpdate) {
+ component.componentWillUpdate(props, state, context);
+ }
+ component.props = props;
+ component.state = state;
+ component.context = context;
+ }
+
+ component.prevProps = component.prevState = component.prevContext = component.nextBase = null;
+ component._dirty = false;
+
+ if (!skip) {
+ if (component.render) rendered = component.render(props, state, context);
+
+ // context to pass to the child, can be updated via (grand-)parent component
+ if (component.getChildContext) {
+ context = extend(clone(context), component.getChildContext());
+ }
+
+ while (isFunctionalComponent(rendered)) {
+ rendered = buildFunctionalComponent(rendered, context);
+ }
+
+ let childComponent = rendered && rendered.nodeName,
+ toUnmount, base;
+
+ if (isFunction(childComponent)) {
+ // set up high order component link
+
+
+ inst = initialChildComponent;
+ let childProps = getNodeProps(rendered);
+
+ if (inst && inst.constructor===childComponent) {
+ setComponentProps(inst, childProps, SYNC_RENDER, context);
+ }
+ else {
+ toUnmount = inst;
+
+ inst = createComponent(childComponent, childProps, context);
+ inst.nextBase = inst.nextBase || nextBase;
+ inst._parentComponent = component;
+ component._component = inst;
+ setComponentProps(inst, childProps, NO_RENDER, context);
+ renderComponent(inst, SYNC_RENDER, mountAll, true);
+ }
+
+ base = inst.base;
+ }
+ else {
+ cbase = initialBase;
+
+ // destroy high order component link
+ toUnmount = initialChildComponent;
+ if (toUnmount) {
+ cbase = component._component = null;
+ }
+
+ if (initialBase || opts===SYNC_RENDER) {
+ if (cbase) cbase._component = null;
+ base = diff(cbase, rendered, context, mountAll || !isUpdate, initialBase && initialBase.parentNode, true);
+ }
+ }
+
+ if (initialBase && base!==initialBase && inst!==initialChildComponent) {
+ let baseParent = initialBase.parentNode;
+ if (baseParent && base!==baseParent) {
+ baseParent.replaceChild(base, initialBase);
+ }
+
+ if (!cbase && !toUnmount && component._parentComponent) {
+ initialBase._component = null;
+ recollectNodeTree(initialBase);
+ }
+ }
+
+ if (toUnmount) {
+ unmountComponent(toUnmount, base!==initialBase);
+ }
+
+ component.base = base;
+ if (base && !isChild) {
+ let componentRef = component,
+ t = component;
+ while ((t=t._parentComponent)) { componentRef = t; }
+ base._component = componentRef;
+ base._componentConstructor = componentRef.constructor;
+ }
+ }
+
+ if (!isUpdate || mountAll) {
+ mounts.unshift(component);
+ }
+ else if (!skip && component.componentDidUpdate) {
+ component.componentDidUpdate(previousProps, previousState, previousContext);
+ }
+
+ let cb = component._renderCallbacks, fn;
+ if (cb) while ( (fn = cb.pop()) ) fn.call(component);
+
+ if (!diffLevel && !isChild) flushMounts();
+}
+
+
+
+/** Apply the Component referenced by a VNode to the DOM.
+ * @param {Element} dom The DOM node to mutate
+ * @param {VNode} vnode A Component-referencing VNode
+ * @returns {Element} dom The created/mutated element
+ * @private
+ */
+export function buildComponentFromVNode(dom, vnode, context, mountAll) {
+ let c = dom && dom._component,
+ oldDom = dom,
+ isDirectOwner = c && dom._componentConstructor===vnode.nodeName,
+ isOwner = isDirectOwner,
+ props = getNodeProps(vnode);
+ while (c && !isOwner && (c=c._parentComponent)) {
+ isOwner = c.constructor===vnode.nodeName;
+ }
+
+ if (c && isOwner && (!mountAll || c._component)) {
+ setComponentProps(c, props, ASYNC_RENDER, context, mountAll);
+ dom = c.base;
+ }
+ else {
+ if (c && !isDirectOwner) {
+ unmountComponent(c, true);
+ dom = oldDom = null;
+ }
+
+ c = createComponent(vnode.nodeName, props, context);
+ if (dom && !c.nextBase) c.nextBase = dom;
+ setComponentProps(c, props, SYNC_RENDER, context, mountAll);
+ dom = c.base;
+
+ if (oldDom && dom!==oldDom) {
+ oldDom._component = null;
+ recollectNodeTree(oldDom);
+ }
+ }
+
+ return dom;
+}
+
+
+
+/** Remove a component from the DOM and recycle it.
+ * @param {Element} dom A DOM node from which to unmount the given Component
+ * @param {Component} component The Component instance to unmount
+ * @private
+ */
+export function unmountComponent(component, remove) {
+ // console.log(`${remove?'Removing':'Unmounting'} component: ${component.constructor.name}`);
+ let base = component.base;
+
+ component._disable = true;
+
+ if (component.componentWillUnmount) component.componentWillUnmount();
+
+ component.base = null;
+
+ // recursively tear down & recollect high-order component children:
+ let inner = component._component;
+ if (inner) {
+ unmountComponent(inner, remove);
+ }
+ else if (base) {
+ if (base[ATTR_KEY] && base[ATTR_KEY].ref) base[ATTR_KEY].ref(null);
+
+ component.nextBase = base;
+
+ if (remove) {
+ removeNode(base);
+ collectComponent(component);
+ }
+ removeOrphanedChildren(base.childNodes, !remove);
+ }
+
+ if (component.__ref) component.__ref(null);
+ if (component.componentDidUnmount) component.componentDidUnmount();
+}