UNPKG

@servicenow/ui-renderer-snabbdom

Version:

Snabbdom renderer for UI Framework on Next Experience

279 lines (224 loc) 10.6 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. */ "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports["default"] = formatProps; var _uiConfig = require("@servicenow/ui-config"); var _uiMetrics = require("@servicenow/ui-metrics"); var _uiInternal = require("@servicenow/ui-internal"); var _constants = require("../constants"); var _log = _interopRequireDefault(require("../log")); var _symbols = require("../symbols"); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; } function _toConsumableArray(arr) { return _arrayWithoutHoles(arr) || _iterableToArray(arr) || _unsupportedIterableToArray(arr) || _nonIterableSpread(); } function _nonIterableSpread() { throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } function _arrayWithoutHoles(arr) { if (Array.isArray(arr)) return _arrayLikeToArray(arr); } function _toArray(arr) { return _arrayWithHoles(arr) || _iterableToArray(arr) || _unsupportedIterableToArray(arr) || _nonIterableRest(); } function _nonIterableRest() { throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } function _unsupportedIterableToArray(o, minLen) { if (!o) return; if (typeof o === "string") return _arrayLikeToArray(o, minLen); var n = Object.prototype.toString.call(o).slice(8, -1); if (n === "Object" && o.constructor) n = o.constructor.name; if (n === "Map" || n === "Set") return Array.from(o); if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); } function _arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len = arr.length; for (var i = 0, arr2 = new Array(len); i < len; i++) { arr2[i] = arr[i]; } return arr2; } function _iterableToArray(iter) { if (typeof Symbol !== "undefined" && iter[Symbol.iterator] != null || iter["@@iterator"] != null) return Array.from(iter); } function _arrayWithHoles(arr) { if (Array.isArray(arr)) return arr; } var MEMOIZE_SNABBDOM_PROP_PARTS = _uiConfig.configConstants.MEMOIZE_SNABBDOM_PROP_PARTS; var shouldMemoizePropParts = (0, _uiConfig.getConfigItem)(MEMOIZE_SNABBDOM_PROP_PARTS); var isNil = function isNil(value) { return value === null || value === undefined; }; // TODO: investigate the benifit and drawbacks of memoizing this. This does make it faster but at the cost of memory. var memoizedCamelCase = (0, _uiInternal.memoize)(_uiInternal.camelCase, { profileName: 'snabbdom-camelCase' }); var isCustomElement = function isCustomElement(tag) { return tag.indexOf('-') > 0; }; var memoizedIsCustomElement = (0, _uiInternal.memoize)(isCustomElement, { profileName: 'snabbdom-isCustomElement' }); function convertToLoadedEvent(value) { var _value = _toArray(value), fn = _value[0], args = _value.slice(1); return function (event, vnode) { fn.apply(vnode, [].concat(_toConsumableArray(args), [event, vnode])); }; } var getPropKeyParts = function getPropKeyParts(key) { var index = key.indexOf('-'); var prefix = index > 0 ? key.substring(0, index) : null; var name = prefix === _constants.ATTR_PREFIX || _constants.DATA_PREFIX || _constants.HOOK_PREFIX || _constants.EVENT_PREFIX ? key.slice(index + 1) : key; return { index: index, prefix: prefix, name: name }; }; var memoizedGetPropKeyParts = shouldMemoizePropParts ? (0, _uiInternal.memoize)(getPropKeyParts, { profileName: 'snabbdom-getPropKeyParts' }) : getPropKeyParts; /** * Move props into the proper module fields so they can properly be handled by modules registered with snabbdom. * @private * @method formatProps * @param tagName element name * @param {object} props Properties to format by prefixes * @returns {{hook: {}, ref: {}, now-aria-ref: {}, on: {}, style: {}, class: {}, attrs: {}, props: {}, dataset: {}}} */ function formatProps(tagName, props) { var result = { hook: {}, ref: null, 'now-aria-ref': null, on: {}, style: {}, "class": {}, attrs: {}, props: {} }; var isCustomTag = memoizedIsCustomElement(tagName); // this is the fastest way to iterate over an object's keys: // https://jsperf.com/object-keys-vs-for-in-with-closure/3 var keys = Object.keys(props); var _loop = function _loop(i) { var key = keys[i]; var value = props[key]; if (key === 'key') { result.key = value; return "continue"; } var _memoizedGetPropKeyPa = memoizedGetPropKeyParts(key), keyIndex = _memoizedGetPropKeyPa.index, prefix = _memoizedGetPropKeyPa.prefix, keyName = _memoizedGetPropKeyPa.name; if (keyIndex > 0) { if (prefix === _constants.ATTR_PREFIX) { result.attrs[keyName] = isNil(value) ? false : value; return "continue"; } if (prefix === _constants.ARIA_PREFIX) { result.attrs[key] = isNil(value) ? false : value; return "continue"; } if (prefix === _constants.DATA_PREFIX) { // Perf: Defer defining dataset on the resulting vnode as snabbdom will // otherwise do un-necessary reading from the DOM in the dataset module. if (!result.dataset) result.dataset = {}; result.dataset[keyName] = value; return "continue"; } if (prefix === _constants.HOOK_PREFIX && typeof value === 'function') { var name = keyName; result.hook[name] = function (vnode) { var host = vnode.elm && vnode.elm.getRootNode().host; for (var _len = arguments.length, rest = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) { rest[_key - 1] = arguments[_key]; } if (!host) { try { return value.apply(void 0, [vnode].concat(rest)); } catch (e) { (0, _log["default"])("An Error occured while executing hook-".concat(name), { error: e, level: _uiInternal.allLogTypes.ERROR, origin: 'formatProps' }); return; } } return (0, _uiInternal.sandbox)(function (host, vnode) { var interactionId = host[_symbols.INTERACTION_ID] || (0, _uiMetrics.createInteractionId)(); (0, _uiMetrics.mark)(host, interactionId, _uiMetrics.types.HOOK_START, { name: name, tagName: vnode.elm.tagName }); for (var _len2 = arguments.length, rest = new Array(_len2 > 2 ? _len2 - 2 : 0), _key2 = 2; _key2 < _len2; _key2++) { rest[_key2 - 2] = arguments[_key2]; } value.apply(void 0, [vnode].concat(rest)); (0, _uiMetrics.mark)(host, interactionId, _uiMetrics.types.HOOK_END, { name: name, tagName: vnode.elm.tagName }); }, { args: [host, vnode].concat(rest), dispatch: host.helpers, host: host, location: _uiInternal.locations.VIEW, details: { name: name }, log: _log["default"] }); }; return "continue"; } // Backwards compatibility for snabbdom's `loaded event` feature: // // on-click={[function handler() {}, a, b]} // // would automatically bind `a` and `b` as the first args to the handler: // // handler(a, b, event, vnode); // // This was supported in snabbdom@0.7 but removed in snabbdom@2.0 // https://github.com/snabbdom/snabbdom/pull/804 if (prefix === _constants.EVENT_PREFIX && Array.isArray(value) && typeof value[0] === 'function' && value.length > 1) { result.on[keyName] = convertToLoadedEvent(value); return "continue"; } if (_constants.MODULES.includes(prefix)) { result[prefix][keyName] = value; return "continue"; } } if (_constants.MODULES.includes(key)) { result[key] = value; return "continue"; } if (isCustomTag) { if (_constants.CUSTOM_ELEMENT_ATTRIBUTES.includes(key)) { result.attrs[key] = isNil(value) ? false : value; if (!result.key && key === 'component-id') result.key = value; } if (process.env.NODE_ENV !== 'production') { var lowerKey = memoizedCamelCase(key).toLowerCase(); if (_constants.NON_CUSTOM_ELEMENT_ATTRIBUTES.includes(lowerKey) && !_constants.NON_CUSTOM_ELEMENT_ATTRIBUTES.includes(key)) { (0, _log["default"])("For tag <".concat(tagName, ">: the property '").concat(key, "' matches the standard HTML5 attribute '").concat(lowerKey, "'. Please use standard HTML attributes & avoid props matching standard HTML attributes"), { level: _uiInternal.allLogTypes.WARN, tagName: tagName }); } } } else { if (_constants.NON_CUSTOM_ELEMENT_ATTRIBUTES.includes(key)) result.attrs[key] = isNil(value) ? false : value; } if (_constants.ACRONYM_PROPS.includes(key)) { result.props[key] = value; return "continue"; } result.props[memoizedCamelCase(key)] = value; }; for (var i = 0; i < keys.length; i++) { var _ret = _loop(i); if (_ret === "continue") continue; } return result; } //# sourceMappingURL=formatProps.js.map