@servicenow/ui-renderer-snabbdom
Version:
Snabbdom renderer for UI Framework on Next Experience
279 lines (224 loc) • 10.6 kB
JavaScript
/**
* 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.
*/
;
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