UNPKG

@servicenow/ui-renderer-snabbdom

Version:

Snabbdom renderer for UI Framework on Next Experience

122 lines (113 loc) 4.85 kB
/** * Copyright (c) 2020 ServiceNow, Inc. * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to * deal in the Software without restriction, including without limitation the * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or * sell copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ import { createInteractionId, setInteractionId, mark, types as metrics } from '@servicenow/ui-metrics'; import { errorTypes, locations, toLower, allLogTypes as logTypes } from '@servicenow/ui-internal'; import { patch } from './utils'; import { VNODE, INTERACTION_ID } from './symbols'; import createElementFromNode from './createElementFromNode'; import log from './log'; const noop = () => null; const helperNoop = Object.assign(noop, { dispatch: noop, updateState: noop, updateProperties: noop }); /** * Renders a view using Snabbdom * @method render * @param {HTMLElement} element Element to append rendered view to * @param {Function} handler Render function * @param {Object} [props = {}] Optional properties object * @param {Object } [helpers] Optional object containing helpful functions, such as dispatch, updateState, and updateProperties * @param {Function} [helpers.dispatch] Optional function for dispatching actions * @param {Function} [helpers.updateState] Optional function for updating component state * @param {Function} [helpers.updateProperties] Optional function for updating component properties * @param {String} [interactionId] Optional identifier used by ui-metrics for instrumentation * @returns {VNode} */ export default function onStateChange(element, handler, props = {}, helpers = helperNoop, interactionId = createInteractionId()) { // The renderer is sometimes used to render DOM to native elements, typically in test fixtures // or example code. Since we're not in a shadowRoot, we're not in a framework element, we // shouldn't mark as context is not complete, e.g. componentId, componentName, etc are missing // from `element.host`. const isShadowRoot = element instanceof ShadowRoot; try { const result = handler(props, helpers); const prev = element[VNODE] || createElementFromNode(element); const styles = []; const { children } = prev; const { length: childrenCount } = children; for (let i = 0; i < childrenCount; i++) { const child = children[i]; if (child && typeof child.sel === 'string' && (child.sel.startsWith('style') || child.sel.startsWith('link'))) styles.push(child); } const next = { ...prev, children: [].concat(result, styles) }; const { host = {} } = element; host[INTERACTION_ID] = interactionId; // eslint-disable-line no-param-reassign if (isShadowRoot) mark(host, interactionId, metrics.PATCH_START, { location: locations.VIEW }); const vnode = patch(prev, next); if (isShadowRoot) mark(host, interactionId, metrics.PATCH_END, { location: locations.VIEW }); element[VNODE] = vnode; // eslint-disable-line no-param-reassign delete host[INTERACTION_ID]; // eslint-disable-line no-param-reassign return vnode; } catch (e) { const { host = {} } = element; const metadata = setInteractionId({ internal: true }, interactionId); if (isShadowRoot) mark(host, interactionId, metrics.ERROR, { location: locations.VIEW }); helpers.dispatch(errorTypes.COMPONENT_ERROR_THROWN, { host, location: locations.VIEW, error: e, details: { boundaryError: true } }, metadata, true); const tagName = toLower(host.tagName); const componentId = host.getAttribute('component-id'); log(`An Error occurred in <${tagName} component-id="${componentId}"/>. COMPONENT_ERROR_THROWN action type is dispatched with error details.`, { error: e, level: logTypes.ERROR, host, origin: 'onStateChange' }); return createElementFromNode(element); } } //# sourceMappingURL=onStateChange.js.map