src/core/manage-component-lifecycle.js
import {
ATTRIBUTES_KEY_SYMBOL,
IS_COMPONENT_UPDATING,
IS_PURE_SYMBOL,
ON_BEFORE_MOUNT_KEY,
ON_BEFORE_UNMOUNT_KEY,
ON_BEFORE_UPDATE_KEY,
ON_MOUNTED_KEY,
ON_UNMOUNTED_KEY,
ON_UPDATED_KEY,
PARENT_KEY_SYMBOL,
PROPS_KEY,
ROOT_KEY,
SHOULD_UPDATE_KEY,
SLOTS_KEY,
STATE_KEY,
TEMPLATE_KEY_SYMBOL,
autobindMethods,
defineProperties,
defineProperty,
evaluateAttributeExpressions,
isFunction,
isObject,
DOMattributesToObject,
} from '@riotjs/util'
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 { createAttributeBindings } from './create-attribute-bindings.js'
import { runPlugins } from './run-plugins.js'
import { IS_DIRECTIVE } from '@riotjs/util/constants'
import { getRootComputedAttributeNames } from '../utils/get-root-computed-attribute-names.js'
/**
* Component creation factory function that will enhance the user provided API
* @param {Object} component - a component implementation previously defined
* @param {Array} options.slots - component slots generated via riot compiler
* @param {Array} options.attributes - attribute expressions generated via riot compiler
* @returns {Riot.Component} a riot component instance
*/
export 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
this[ATTRIBUTES_KEY_SYMBOL] = createAttributeBindings(
element,
attributes,
).mount(parentScope)
defineProperty(
this,
PROPS_KEY,
Object.freeze({
...computeInitialProps(element, props),
...evaluateAttributeExpressions(
this[ATTRIBUTES_KEY_SYMBOL].expressions,
),
}),
)
this[STATE_KEY] = computeComponentState(this[STATE_KEY], state)
this[TEMPLATE_KEY_SYMBOL] = this.template.createDOM(element).clone()
// 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
this[ATTRIBUTES_KEY_SYMBOL].update(parentScope)
}
// get the attribute names that don't belong to the the props object
// this will avoid recursive props rendering https://github.com/riot/riot/issues/2994
const computedAttributeNames = getRootComputedAttributeNames(
this[TEMPLATE_KEY_SYMBOL],
)
// filter out the computed attributes from the root node
const staticRootAttributes = Array.from(
this[ROOT_KEY].attributes,
).filter(({ name }) => !computedAttributeNames.includes(name))
// evaluate the
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,
...evaluateAttributeExpressions(
this[ATTRIBUTES_KEY_SYMBOL].expressions,
),
}
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])
this[ATTRIBUTES_KEY_SYMBOL].unmount()
// 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])),
)
}