UNPKG

framework7

Version:

Full featured mobile HTML framework for building iOS & Android apps

319 lines (317 loc) 10.5 kB
/* eslint no-use-before-define: "off" */ /* eslint import/no-named-as-default: "off" */ import h from './snabbdom/h.js'; import customComponents from './custom-components.js'; import { isObject, eventNameToColonCase } from '../../shared/utils.js'; const SELF_CLOSING = 'area base br col command embed hr img input keygen link menuitem meta param source track wbr'.split(' '); const PROPS_ATTRS = 'hidden checked disabled readonly selected autofocus autoplay required multiple value indeterminate routeProps innerHTML'.split(' '); const BOOLEAN_PROPS = 'hidden checked disabled readonly selected autofocus autoplay required multiple readOnly indeterminate'.split(' '); const getTagName = treeNode => { return typeof treeNode.type === 'function' ? treeNode.type.name || 'CustomComponent' : treeNode.type; }; const toCamelCase = name => { return name.split('-').map((word, index) => { if (index === 0) return word.toLowerCase(); return word[0].toUpperCase() + word.substr(1); }).join(''); }; const propsFromAttrs = function () { const context = {}; for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) { args[_key] = arguments[_key]; } args.forEach(function (obj) { if (obj === void 0) { obj = {}; } Object.keys(obj).forEach(key => { context[toCamelCase(key)] = obj[key]; }); }); return context; }; const createCustomComponent = _ref => { let { f7, treeNode, vnode, data } = _ref; const component = typeof treeNode.type === 'function' ? treeNode.type : customComponents[treeNode.type]; f7.component.create(component, propsFromAttrs(data.attrs || {}, data.props || {}), { el: vnode.elm, children: treeNode.children }).then(c => { if (vnode.data && vnode.data.on && c && c.$el) { Object.keys(vnode.data.on).forEach(eventName => { c.$el.on(eventName, vnode.data.on[eventName]); }); } // eslint-disable-next-line vnode.elm.__component__ = c; }); }; const updateCustomComponent = vnode => { // eslint-disable-next-line const component = vnode && vnode.elm && vnode.elm.__component__; if (!component) return; const newProps = propsFromAttrs(vnode.data.attrs || {}, vnode.data.props || {}); component.children = vnode.data.treeNode.children; Object.assign(component.props, newProps); component.update(); }; const destroyCustomComponent = vnode => { // eslint-disable-next-line const component = vnode && vnode.elm && vnode.elm.__component__; if (component) { const { el, $el } = component; if (vnode.data && vnode.data.on && $el) { Object.keys(vnode.data.on).forEach(eventName => { $el.off(eventName, vnode.data.on[eventName]); }); } if (component.destroy) component.destroy(); if (el && el.parentNode) el.parentNode.removeChild(el); delete vnode.elm.__component__; // eslint-disable-line } }; const isCustomComponent = treeNodeType => { return typeof treeNodeType === 'function' || treeNodeType && treeNodeType.indexOf('-') > 0 && customComponents[treeNodeType]; }; function getHooks(treeNode, data, f7, initial, isRoot) { const hooks = {}; const insert = []; const destroy = []; const update = []; const postpatch = []; let isFakeElement = false; let tagName = getTagName(treeNode); if (data && data.attrs && data.attrs.component) { tagName = data.attrs.component; delete data.attrs.component; isFakeElement = true; } const isCustom = isCustomComponent(treeNode.type); if (isCustom) { insert.push(vnode => { if (vnode.sel !== tagName && !isFakeElement) return; createCustomComponent({ f7, treeNode, vnode, data }); }); destroy.push(vnode => { destroyCustomComponent(vnode); }); update.push((oldVnode, vnode) => { updateCustomComponent(vnode); }); } if (!isCustom) { if (!data || !data.attrs || !data.attrs.class) return hooks; const classNames = data.attrs.class; classNames.split(' ').forEach(className => { if (!initial) { insert.push(...f7.getVnodeHooks('insert', className)); } destroy.push(...f7.getVnodeHooks('destroy', className)); update.push(...f7.getVnodeHooks('update', className)); postpatch.push(...f7.getVnodeHooks('postpatch', className)); }); } if (isRoot && !initial) { postpatch.push((oldVnode, vnode) => { const vn = vnode || oldVnode; if (!vn) return; if (vn.data && vn.data.component) { vn.data.component.hook('onUpdated'); } }); } if (insert.length === 0 && destroy.length === 0 && update.length === 0 && postpatch.length === 0) { return hooks; } if (insert.length) { hooks.insert = vnode => { insert.forEach(f => f(vnode)); }; } if (destroy.length) { hooks.destroy = vnode => { destroy.forEach(f => f(vnode)); }; } if (update.length) { hooks.update = (oldVnode, vnode) => { update.forEach(f => f(oldVnode, vnode)); }; } if (postpatch.length) { hooks.postpatch = (oldVnode, vnode) => { postpatch.forEach(f => f(oldVnode, vnode)); }; } return hooks; } const getEventHandler = function (eventHandler, _temp) { let { stop, prevent, once } = _temp === void 0 ? {} : _temp; let fired = false; function handler() { const e = arguments.length <= 0 ? undefined : arguments[0]; if (once && fired) return; if (stop) e.stopPropagation(); if (prevent) e.preventDefault(); fired = true; eventHandler(...arguments); } return handler; }; const getData = (treeNode, component, f7, initial, isRoot) => { const data = { component, treeNode }; const tagName = getTagName(treeNode); Object.keys(treeNode.props).forEach(attrName => { const attrValue = treeNode.props[attrName]; if (typeof attrValue === 'undefined') return; if (PROPS_ATTRS.indexOf(attrName) >= 0) { // Props if (!data.props) data.props = {}; if (attrName === 'readonly') { // eslint-disable-next-line attrName = 'readOnly'; } if (attrName === 'routeProps') { // eslint-disable-next-line attrName = 'f7RouteProps'; } if (tagName === 'option' && attrName === 'value') { if (!data.attrs) data.attrs = {}; data.attrs.value = attrValue; } if (BOOLEAN_PROPS.indexOf(attrName) >= 0) { // eslint-disable-next-line data.props[attrName] = attrValue === false ? false : true; } else { data.props[attrName] = attrValue; } } else if (attrName === 'key') { // Key data.key = attrValue; } else if (attrName.indexOf('@') === 0 || attrName.indexOf('on') === 0 && attrName.length > 2) { // Events if (!data.on) data.on = {}; let eventName = attrName.indexOf('@') === 0 ? attrName.substr(1) : eventNameToColonCase(attrName.substr(2)); let stop = false; let prevent = false; let once = false; if (eventName.indexOf('.') >= 0) { eventName.split('.').forEach((eventNamePart, eventNameIndex) => { if (eventNameIndex === 0) eventName = eventNamePart;else { if (eventNamePart === 'stop') stop = true; if (eventNamePart === 'prevent') prevent = true; if (eventNamePart === 'once') once = true; } }); } data.on[eventName] = getEventHandler(attrValue, { stop, prevent, once }); } else if (attrName === 'style') { // Style if (typeof attrValue !== 'string') { data.style = attrValue; } else { if (!data.attrs) data.attrs = {}; data.attrs.style = attrValue; } } else { // Rest of attribures if (!data.attrs) data.attrs = {}; data.attrs[attrName] = attrValue; // ID -> Key if (attrName === 'id' && !data.key && !isRoot) { data.key = attrValue; } } }); const hooks = getHooks(treeNode, data, f7, initial, isRoot); hooks.prepatch = (oldVnode, vnode) => { if (!oldVnode || !vnode) return; if (oldVnode && oldVnode.data && oldVnode.data.props) { Object.keys(oldVnode.data.props).forEach(key => { if (BOOLEAN_PROPS.indexOf(key) < 0) return; if (!vnode.data) vnode.data = {}; if (!vnode.data.props) vnode.data.props = {}; if (oldVnode.data.props[key] === true && !(key in vnode.data.props)) { vnode.data.props[key] = false; } }); } }; data.hook = hooks; return data; }; const getChildren = (treeNode, component, f7, initial) => { if (treeNode && treeNode.type && SELF_CLOSING.indexOf(treeNode.type) >= 0) { return []; } const children = []; const nodes = treeNode.children; for (let i = 0; i < nodes.length; i += 1) { const childNode = nodes[i]; const child = treeNodeToVNode(childNode, component, f7, initial, false); if (Array.isArray(child)) { children.push(...child); } else if (child) { children.push(child); } } return children; }; const getSlots = (treeNode, component, f7, initial) => { const slotName = treeNode.props.name || 'default'; const slotNodes = (component.children || []).filter(childTreeNode => { let childSlotName = 'default'; if (childTreeNode.props) { childSlotName = childTreeNode.props.slot || 'default'; } return childSlotName === slotName; }); if (slotNodes.length === 0) { return getChildren(treeNode, component, f7, initial); } return slotNodes.map(subTreeNode => treeNodeToVNode(subTreeNode, component, f7, initial)); }; const isTreeNode = treeNode => { return isObject(treeNode) && 'props' in treeNode && 'type' in treeNode && 'children' in treeNode; }; const treeNodeToVNode = (treeNode, component, f7, initial, isRoot) => { if (!isTreeNode(treeNode)) { return String(treeNode); } if (treeNode.type === 'slot') { return getSlots(treeNode, component, f7, initial); } const data = getData(treeNode, component, f7, initial, isRoot); const children = isCustomComponent(treeNode.type) ? [] : getChildren(treeNode, component, f7, initial); return h(getTagName(treeNode), data, children); }; export default function vdom(tree, component, initial) { if (tree === void 0) { tree = {}; } return treeNodeToVNode(tree, component, component.f7, initial, true); }