riot
Version:
Simple and elegant component-based UI library
156 lines (137 loc) • 6.84 kB
JavaScript
/* Riot v10.1.2, @license MIT */
import { isFunction, isObject } from '../dependencies/@riotjs/util/checks.js';
import { ON_BEFORE_UNMOUNT_KEY, PROPS_KEY, STATE_KEY, ROOT_ATTRIBUTES_KEY_SYMBOL, ROOT_KEY, TEMPLATE_KEY_SYMBOL, PARENT_KEY_SYMBOL, ON_UNMOUNTED_KEY, SHOULD_UPDATE_KEY, ON_BEFORE_UPDATE_KEY, IS_COMPONENT_UPDATING, ON_UPDATED_KEY, IS_PURE_SYMBOL, SLOTS_KEY, ON_BEFORE_MOUNT_KEY, ON_MOUNTED_KEY, IS_DIRECTIVE } from '../dependencies/@riotjs/util/constants.js';
import { DOMattributesToObject } from '../dependencies/@riotjs/util/dom.js';
import { autobindMethods } from '../dependencies/@riotjs/util/functions.js';
import { generatePropsFromAttributes } from '../dependencies/@riotjs/util/misc.js';
import { defineProperties, defineProperty } from '../dependencies/@riotjs/util/objects.js';
import { getRootComputedAttributeNames } from '../utils/get-root-computed-attribute-names.js';
import { addCssHook } from './add-css-hook.js';
import { bindDOMNodeToComponentInstance } from './bind-dom-node-to-component-instance.js';
import { computeComponentState } from './compute-component-state.js';
import { computeInitialProps } from './compute-initial-props.js';
import { runPlugins } from './run-plugins.js';
/**
* Component creation factory function that will enhance the user provided API
* @param {object} component - a component implementation previously defined
* @param {object} options - component options
* @param {Array} options.slots - component slots generated via riot compiler
* @param {Array} options.attributes - attribute expressions generated via riot compiler
* @param {object} options.props - component initial props
* @returns {Riot.Component} a riot component instance
*/
function manageComponentLifecycle(
component,
{ slots, attributes = [], props },
) {
return autobindMethods(
runPlugins(
defineProperties(
isObject(component) ? Object.create(component) : component,
{
mount(element, state = {}, parentScope) {
// any element mounted passing through this function can't be a pure component
defineProperty(element, IS_PURE_SYMBOL, false);
this[PARENT_KEY_SYMBOL] = parentScope;
defineProperty(
this,
PROPS_KEY,
Object.freeze({
...computeInitialProps(element, props),
...generatePropsFromAttributes(attributes, parentScope),
}),
);
this[STATE_KEY] = computeComponentState(this[STATE_KEY], state);
this[TEMPLATE_KEY_SYMBOL] = this.template.createDOM(element).clone();
// get the attribute names that don't belong to the props object
// this will avoid recursive props rendering https://github.com/riot/riot/issues/2994
this[ROOT_ATTRIBUTES_KEY_SYMBOL] = getRootComputedAttributeNames(
this[TEMPLATE_KEY_SYMBOL],
);
// link this object to the DOM node
bindDOMNodeToComponentInstance(element, this);
// add eventually the 'is' attribute
component.name && addCssHook(element, component.name);
// define the root element
defineProperty(this, ROOT_KEY, element);
// define the slots array
defineProperty(this, SLOTS_KEY, slots);
// before mount lifecycle event
this[ON_BEFORE_MOUNT_KEY](this[PROPS_KEY], this[STATE_KEY]);
// mount the template
this[TEMPLATE_KEY_SYMBOL].mount(element, this, parentScope);
this[ON_MOUNTED_KEY](this[PROPS_KEY], this[STATE_KEY]);
return this
},
update(state = {}, parentScope) {
if (parentScope) {
this[PARENT_KEY_SYMBOL] = parentScope;
}
// filter out the computed attributes from the root node
const staticRootAttributes = Array.from(
this[ROOT_KEY].attributes,
).filter(
({ name }) => !this[ROOT_ATTRIBUTES_KEY_SYMBOL].includes(name),
);
// evaluate the value of the static dom attributes
const domNodeAttributes = DOMattributesToObject({
attributes: staticRootAttributes,
});
// Avoid adding the riot "is" directives to the component props
// eslint-disable-next-line no-unused-vars
const { [IS_DIRECTIVE]: _, ...newProps } = {
...domNodeAttributes,
...generatePropsFromAttributes(
attributes,
this[PARENT_KEY_SYMBOL],
),
};
if (this[SHOULD_UPDATE_KEY](newProps, this[PROPS_KEY]) === false)
return
defineProperty(
this,
PROPS_KEY,
Object.freeze({
// only root components will merge their initial props with the new ones
// children components will just get them overridden see also https://github.com/riot/riot/issues/2978
...(parentScope ? null : this[PROPS_KEY]),
...newProps,
}),
);
this[STATE_KEY] = computeComponentState(this[STATE_KEY], state);
this[ON_BEFORE_UPDATE_KEY](this[PROPS_KEY], this[STATE_KEY]);
// avoiding recursive updates
// see also https://github.com/riot/riot/issues/2895
if (!this[IS_COMPONENT_UPDATING]) {
this[IS_COMPONENT_UPDATING] = true;
this[TEMPLATE_KEY_SYMBOL].update(this, this[PARENT_KEY_SYMBOL]);
}
this[ON_UPDATED_KEY](this[PROPS_KEY], this[STATE_KEY]);
this[IS_COMPONENT_UPDATING] = false;
return this
},
unmount(preserveRoot) {
this[ON_BEFORE_UNMOUNT_KEY](this[PROPS_KEY], this[STATE_KEY]);
// make sure that computed root attributes get removed if the root is preserved
// https://github.com/riot/riot/issues/3051
if (preserveRoot)
this[ROOT_ATTRIBUTES_KEY_SYMBOL].forEach((attribute) =>
this[ROOT_KEY].removeAttribute(attribute),
);
// if the preserveRoot is null the template html will be left untouched
// in that case the DOM cleanup will happen differently from a parent node
this[TEMPLATE_KEY_SYMBOL].unmount(
this,
this[PARENT_KEY_SYMBOL],
preserveRoot === null ? null : !preserveRoot,
);
this[ON_UNMOUNTED_KEY](this[PROPS_KEY], this[STATE_KEY]);
return this
},
},
),
),
Object.keys(component).filter((prop) => isFunction(component[prop])),
)
}
export { manageComponentLifecycle };