holakit
Version:
Yet another design-driven UI component set.
1,103 lines (1,069 loc) • 98.6 kB
JavaScript
/*! Built with http://stenciljs.com */
(function(window,document,Context,namespace,hydratedCssClass,components,resourcesUrl){"use strict";
(function(s){s&&(resourcesUrl=s.getAttribute('data-resources-url'))})(document.querySelector("script[data-namespace='holakitcore']"));
/**
* SSR Attribute Names
*/
const SSR_VNODE_ID = 'ssrv';
const SSR_CHILD_ID = 'ssrc';
/**
* Default style mode id
*/ const DEFAULT_STYLE_MODE = '$';
/**
* Reusable empty obj/array
* Don't add values to these!!
*/ const EMPTY_OBJ = {};
/**
* Key Name to Key Code Map
*/ const KEY_CODE_MAP = {
'enter': 13,
'escape': 27,
'space': 32,
'tab': 9,
'left': 37,
'up': 38,
'right': 39,
'down': 40
};
function getScopeId(cmpMeta, mode) {
return 'sc-' + cmpMeta.tagNameMeta + (mode && mode !== DEFAULT_STYLE_MODE ? '-' + mode : '');
}
function getElementScopeId(scopeId, isHostElement) {
return scopeId + (isHostElement ? '-h' : '-s');
}
function initStyleTemplate(domApi, cmpMeta, encapsulation, style, styleMode, perf) {
if (style) {
false;
// we got a style mode for this component, let's create an id for this style
const styleModeId = cmpMeta.tagNameMeta + (styleMode || DEFAULT_STYLE_MODE);
if (!cmpMeta[styleModeId]) {
false;
{
// use <template> elements to clone styles
// create the template element which will hold the styles
// adding it to the dom via <template> so that we can
// clone this for each potential shadow root that will need these styles
// otherwise it'll be cloned and added to document.body.head
// but that's for the renderer to figure out later
const templateElm = domApi.$createElement('template');
// keep a reference to this template element within the
// Constructor using the style mode id as the key
cmpMeta[styleModeId] = templateElm;
// add the style text to the template element's innerHTML
true;
{
// hot module replacement enabled
// add a style id attribute, but only useful during dev
const styleContent = [ '<style', ` data-style-tag="${cmpMeta.tagNameMeta}"` ];
domApi.$setAttribute(templateElm, 'data-tmpl-style-tag', cmpMeta.tagNameMeta);
if (styleMode) {
styleContent.push(` data-style-mode="${styleMode}"`);
domApi.$setAttribute(templateElm, 'data-tmpl-style-mode', styleMode);
}
if (2 /* ScopedCss */ === encapsulation || 1 /* ShadowDom */ === encapsulation && !domApi.$supportsShadowDom) {
styleContent.push(' data-style-scoped="true"');
domApi.$setAttribute(templateElm, 'data-tmpl-style-scoped', 'true');
}
styleContent.push('>');
styleContent.push(style);
styleContent.push('</style>');
templateElm.innerHTML = styleContent.join('');
}
// add our new template element to the head
// so it can be cloned later
domApi.$appendChild(domApi.$doc.head, templateElm);
}
}
false;
}
}
function attachStyles(plt, domApi, cmpMeta, hostElm) {
// first see if we've got a style for a specific mode
// either this host element should use scoped css
// or it wants to use shadow dom but the browser doesn't support it
// create a scope id which is useful for scoped css
// and add the scope attribute to the host
// create the style id w/ the host element's mode
let styleId = cmpMeta.tagNameMeta + hostElm.mode;
let styleTemplate = cmpMeta[styleId];
const shouldScopeCss = 2 /* ScopedCss */ === cmpMeta.encapsulationMeta || 1 /* ShadowDom */ === cmpMeta.encapsulationMeta && !plt.domApi.$supportsShadowDom;
shouldScopeCss && (hostElm['s-sc'] = styleTemplate ? getScopeId(cmpMeta, hostElm.mode) : getScopeId(cmpMeta));
if (!styleTemplate) {
// doesn't look like there's a style template with the mode
// create the style id using the default style mode and try again
styleId = cmpMeta.tagNameMeta + DEFAULT_STYLE_MODE;
styleTemplate = cmpMeta[styleId];
}
if (styleTemplate) {
// cool, we found a style template element for this component
let styleContainerNode = domApi.$doc.head;
// if this browser supports shadow dom, then let's climb up
// the dom and see if we're within a shadow dom
if (true, domApi.$supportsShadowDom) if (1 /* ShadowDom */ === cmpMeta.encapsulationMeta)
// we already know we're in a shadow dom
// so shadow root is the container for these styles
styleContainerNode = hostElm.shadowRoot; else {
// climb up the dom and see if we're in a shadow dom
let root = hostElm;
while (root = domApi.$parentNode(root)) if (root.host && root.host.shadowRoot) {
// looks like we are in shadow dom, let's use
// this shadow root as the container for these styles
styleContainerNode = root.host.shadowRoot;
break;
}
}
// if this container element already has these styles
// then there's no need to apply them again
// create an object to keep track if we'ready applied this component style
let appliedStyles = plt.componentAppliedStyles.get(styleContainerNode);
appliedStyles || plt.componentAppliedStyles.set(styleContainerNode, appliedStyles = {});
// check if we haven't applied these styles to this container yet
if (!appliedStyles[styleId]) {
let styleElm;
false;
{
// this browser supports the <template> element
// and all its native content.cloneNode() goodness
// clone the template element to create a new <style> element
styleElm = styleTemplate.content.cloneNode(true);
// remember we don't need to do this again for this element
appliedStyles[styleId] = true;
// let's make sure we put the styles below the <style data-styles> element
// so any visibility css overrides the default
const dataStyles = styleContainerNode.querySelectorAll('[data-styles]');
domApi.$insertBefore(styleContainerNode, styleElm, dataStyles.length && dataStyles[dataStyles.length - 1].nextSibling || styleContainerNode.firstChild);
}
}
}
}
const isDef = v => null != v;
const toLowerCase = str => str.toLowerCase();
const dashToPascalCase = str => toLowerCase(str).split('-').map(segment => segment.charAt(0).toUpperCase() + segment.slice(1)).join('');
const noop = () => {};
function createDomApi(App, win, doc) {
// using the $ prefix so that closure is
// cool with property renaming each of these
if (!App.ael) {
App.ael = ((elm, eventName, cb, opts) => elm.addEventListener(eventName, cb, opts));
App.rel = ((elm, eventName, cb, opts) => elm.removeEventListener(eventName, cb, opts));
}
const unregisterListenerFns = new WeakMap();
false;
const domApi = {
$doc: doc,
$supportsShadowDom: !!doc.documentElement.attachShadow,
$supportsEventOptions: false,
$nodeType: node => node.nodeType,
$createElement: tagName => doc.createElement(tagName),
$createElementNS: (namespace, tagName) => doc.createElementNS(namespace, tagName),
$createTextNode: text => doc.createTextNode(text),
$createComment: data => doc.createComment(data),
$insertBefore: (parentNode, childNode, referenceNode) => parentNode.insertBefore(childNode, referenceNode),
// https://developer.mozilla.org/en-US/docs/Web/API/ChildNode/remove
// and it's polyfilled in es5 builds
$remove: node => node.remove(),
$appendChild: (parentNode, childNode) => parentNode.appendChild(childNode),
$addClass: (elm, cssClass) => {
true, false;
elm.classList.add(cssClass);
},
$childNodes: node => node.childNodes,
$parentNode: node => node.parentNode,
$nextSibling: node => node.nextSibling,
$previousSibling: node => node.previousSibling,
$tagName: elm => toLowerCase(elm.nodeName),
$getTextContent: node => node.textContent,
$setTextContent: (node, text) => node.textContent = text,
$getAttribute: (elm, key) => elm.getAttribute(key),
$setAttribute: (elm, key, val) => elm.setAttribute(key, val),
$setAttributeNS: (elm, namespaceURI, qualifiedName, val) => elm.setAttributeNS(namespaceURI, qualifiedName, val),
$removeAttribute: (elm, key) => elm.removeAttribute(key),
$hasAttribute: (elm, key) => elm.hasAttribute(key),
$getMode: elm => elm.getAttribute('mode') || (App.Context || {}).mode,
$elementRef: (elm, referenceName) => {
if ('child' === referenceName) return elm.firstElementChild;
if ('parent' === referenceName) return domApi.$parentElement(elm);
if ('body' === referenceName) return doc.body;
if ('document' === referenceName) return doc;
if ('window' === referenceName) return win;
return elm;
},
$addEventListener: (assignerElm, eventName, listenerCallback, useCapture, usePassive, attachTo, eventListenerOpts, splt) => {
// remember the original name before we possibly change it
const assignersEventName = eventName;
let attachToElm = assignerElm;
// get the existing unregister listeners for
// this element from the unregister listeners weakmap
let assignersUnregListeners = unregisterListenerFns.get(assignerElm);
assignersUnregListeners && assignersUnregListeners[assignersEventName] &&
// removed any existing listeners for this event for the assigner element
// this element already has this listener, so let's unregister it now
assignersUnregListeners[assignersEventName]();
if ('string' === typeof attachTo)
// attachTo is a string, and is probably something like
// "parent", "window", or "document"
// and the eventName would be like "mouseover" or "mousemove"
attachToElm = domApi.$elementRef(assignerElm, attachTo); else if ('object' === typeof attachTo)
// we were passed in an actual element to attach to
attachToElm = attachTo; else {
// depending on the event name, we could actually be attaching
// this element to something like the document or window
splt = eventName.split(':');
if (splt.length > 1) {
// document:mousemove
// parent:touchend
// body:keyup.enter
attachToElm = domApi.$elementRef(assignerElm, splt[0]);
eventName = splt[1];
}
}
if (!attachToElm)
// somehow we're referencing an element that doesn't exist
// let's not continue
return;
let eventListener = listenerCallback;
// test to see if we're looking for an exact keycode
splt = eventName.split('.');
if (splt.length > 1) {
// looks like this listener is also looking for a keycode
// keyup.enter
eventName = splt[0];
eventListener = (ev => {
// wrap the user's event listener with our own check to test
// if this keyboard event has the keycode they're looking for
ev.keyCode === KEY_CODE_MAP[splt[1]] && listenerCallback(ev);
});
}
// create the actual event listener options to use
// this browser may not support event options
eventListenerOpts = domApi.$supportsEventOptions ? {
capture: !!useCapture,
passive: !!usePassive
} : !!useCapture;
// ok, good to go, let's add the actual listener to the dom element
App.ael(attachToElm, eventName, eventListener, eventListenerOpts);
assignersUnregListeners ||
// we don't already have a collection, let's create it
unregisterListenerFns.set(assignerElm, assignersUnregListeners = {});
// add the unregister listener to this element's collection
assignersUnregListeners[assignersEventName] = (() => {
// looks like it's time to say goodbye
attachToElm && App.rel(attachToElm, eventName, eventListener, eventListenerOpts);
assignersUnregListeners[assignersEventName] = null;
});
},
$removeEventListener: (elm, eventName) => {
// get the unregister listener functions for this element
const assignersUnregListeners = unregisterListenerFns.get(elm);
assignersUnregListeners && (
// this element has unregister listeners
eventName ?
// passed in one specific event name to remove
assignersUnregListeners[eventName] && assignersUnregListeners[eventName]() :
// remove all event listeners
Object.keys(assignersUnregListeners).forEach(assignersEventName => {
assignersUnregListeners[assignersEventName] && assignersUnregListeners[assignersEventName]();
}));
},
$dispatchEvent: (elm, eventName, data) => {
// create and return the custom event, allows for cancel checks
const e = new win.CustomEvent(eventName, data);
elm && elm.dispatchEvent(e);
return e;
},
$parentElement: (elm, parentNode) =>
// if the parent node is a document fragment (shadow root)
// then use the "host" property on it
// otherwise use the parent node
(parentNode = domApi.$parentNode(elm)) && 11 /* DocumentFragment */ === domApi.$nodeType(parentNode) ? parentNode.host : parentNode
};
true;
win.location.search.indexOf('shadow=false') > 0 && (
// by adding ?shadow=false it'll force the slot polyfill
// only add this check when in dev mode
domApi.$supportsShadowDom = false);
true;
domApi.$attachShadow = ((elm, shadowRootInit) => elm.attachShadow(shadowRootInit));
true;
// test if this browser supports event options or not
try {
win.addEventListener('e', null, Object.defineProperty({}, 'passive', {
get: () => domApi.$supportsEventOptions = true
}));
} catch (e) {}
return domApi;
}
function updateAttribute(elm, memberName, newValue, isBooleanAttr = 'boolean' === typeof newValue) {
const isXlinkNs = memberName !== (memberName = memberName.replace(/^xlink\:?/, ''));
if (null == newValue || isBooleanAttr && (!newValue || 'false' === newValue)) isXlinkNs ? elm.removeAttributeNS(XLINK_NS$1, toLowerCase(memberName)) : elm.removeAttribute(memberName); else if ('function' !== typeof newValue) {
newValue = isBooleanAttr ? '' : newValue.toString();
isXlinkNs ? elm.setAttributeNS(XLINK_NS$1, toLowerCase(memberName), newValue) : elm.setAttribute(memberName, newValue);
}
}
const XLINK_NS$1 = 'http://www.w3.org/1999/xlink';
function setAccessor(plt, elm, memberName, oldValue, newValue, isSvg, isHostElement) {
if ('class' !== memberName || isSvg) if ('style' === memberName) {
// update style attribute, css properties and values
for (const prop in oldValue) newValue && null != newValue[prop] || (/-/.test(prop) ? elm.style.removeProperty(prop) : elm.style[prop] = '');
for (const prop in newValue) oldValue && newValue[prop] === oldValue[prop] || (/-/.test(prop) ? elm.style.setProperty(prop, newValue[prop]) : elm.style[prop] = newValue[prop]);
} else if ('o' !== memberName[0] || 'n' !== memberName[1] || !/[A-Z]/.test(memberName[2]) || memberName in elm) if ('list' !== memberName && 'type' !== memberName && !isSvg && (memberName in elm || -1 !== [ 'object', 'function' ].indexOf(typeof newValue) && null !== newValue) || false) {
// Properties
// - list and type are attributes that get applied as values on the element
// - all svgs get values as attributes not props
// - check if elm contains name or if the value is array, object, or function
const cmpMeta = plt.getComponentMeta(elm);
if (cmpMeta && cmpMeta.membersMeta && cmpMeta.membersMeta[memberName]) {
// we know for a fact that this element is a known component
// and this component has this member name as a property,
// let's set the known @Prop on this element
// set it directly as property on the element
setProperty(elm, memberName, newValue);
false;
} else if ('ref' !== memberName) {
// this member name is a property on this element, but it's not a component
// this is a native property like "value" or something
// also we can ignore the "ref" member name at this point
setProperty(elm, memberName, null == newValue ? '' : newValue);
null != newValue && false !== newValue || plt.domApi.$removeAttribute(elm, memberName);
}
} else null != newValue && 'key' !== memberName ?
// Element Attributes
updateAttribute(elm, memberName, newValue) : (isSvg || plt.domApi.$hasAttribute(elm, memberName) && (null == newValue || false === newValue)) &&
// remove svg attribute
plt.domApi.$removeAttribute(elm, memberName); else {
// Event Handlers
// so if the member name starts with "on" and the 3rd characters is
// a capital letter, and it's not already a member on the element,
// then we're assuming it's an event listener
// standard event
// the JSX attribute could have been "onMouseOver" and the
// member name "onmouseover" is on the element's prototype
// so let's add the listener "mouseover", which is all lowercased
memberName = toLowerCase(memberName) in elm ? toLowerCase(memberName.substring(2)) : toLowerCase(memberName[2]) + memberName.substring(3);
newValue ? newValue !== oldValue &&
// add listener
plt.domApi.$addEventListener(elm, memberName, newValue) :
// remove listener
plt.domApi.$removeEventListener(elm, memberName);
} else
// Class
if (oldValue !== newValue) {
const oldList = parseClassList(oldValue);
const newList = parseClassList(newValue);
// remove classes in oldList, not included in newList
const toRemove = oldList.filter(item => !newList.includes(item));
const classList = parseClassList(elm.className).filter(item => !toRemove.includes(item));
// add classes from newValue that are not in oldList or classList
const toAdd = newList.filter(item => !oldList.includes(item) && !classList.includes(item));
classList.push(...toAdd);
elm.className = classList.join(' ');
}
}
function parseClassList(value) {
return null == value || '' === value ? [] : value.trim().split(/\s+/);
}
/**
* Attempt to set a DOM property to the given value.
* IE & FF throw for certain property-value combinations.
*/ function setProperty(elm, name, value) {
try {
elm[name] = value;
} catch (e) {}
}
function updateElement(plt, oldVnode, newVnode, isSvgMode, memberName) {
// if the element passed in is a shadow root, which is a document fragment
// then we want to be adding attrs/props to the shadow root's "host" element
// if it's not a shadow root, then we add attrs/props to the same element
const elm = 11 /* DocumentFragment */ === newVnode.elm.nodeType && newVnode.elm.host ? newVnode.elm.host : newVnode.elm;
const oldVnodeAttrs = oldVnode && oldVnode.vattrs || EMPTY_OBJ;
const newVnodeAttrs = newVnode.vattrs || EMPTY_OBJ;
// remove attributes no longer present on the vnode by setting them to undefined
for (memberName in oldVnodeAttrs) newVnodeAttrs && null != newVnodeAttrs[memberName] || null == oldVnodeAttrs[memberName] || setAccessor(plt, elm, memberName, oldVnodeAttrs[memberName], void 0, isSvgMode, newVnode.ishost);
// add new & update changed attributes
for (memberName in newVnodeAttrs) memberName in oldVnodeAttrs && newVnodeAttrs[memberName] === ('value' === memberName || 'checked' === memberName ? elm[memberName] : oldVnodeAttrs[memberName]) || setAccessor(plt, elm, memberName, oldVnodeAttrs[memberName], newVnodeAttrs[memberName], isSvgMode, newVnode.ishost);
}
let isSvgMode = false;
function createRendererPatch(plt, domApi) {
// createRenderer() is only created once per app
// the patch() function which createRenderer() returned is the function
// which gets called numerous times by each component
// internal variables to be reused per patch() call
let useNativeShadowDom, scopeId, checkSlotFallbackVisibility, checkSlotRelocate, contentRef, hostTagName, hostElm;
function createElm(oldParentVNode, newParentVNode, childIndex, parentElm, i, elm, childNode, newVNode, oldVNode) {
newVNode = newParentVNode.vchildren[childIndex];
if (true, !useNativeShadowDom) {
// remember for later we need to check to relocate nodes
checkSlotRelocate = true;
if ('slot' === newVNode.vtag) {
scopeId &&
// scoped css needs to add its scoped id to the parent element
domApi.$addClass(parentElm, scopeId + '-s');
newVNode.vchildren ?
// slot element has fallback content
// still create an element that "mocks" the slot element
newVNode.isSlotFallback = true :
// slot element does not have fallback content
// create an html comment we'll use to always reference
// where actual slot content should sit next to
newVNode.isSlotReference = true;
}
}
if (isDef(newVNode.vtext))
// create text node
newVNode.elm = domApi.$createTextNode(newVNode.vtext); else if (true, newVNode.isSlotReference)
// create a slot reference html text node
newVNode.elm = domApi.$createTextNode(''); else {
// create element
elm = newVNode.elm = (true, isSvgMode || 'svg' === newVNode.vtag ? domApi.$createElementNS('http://www.w3.org/2000/svg', newVNode.vtag) : domApi.$createElement((true,
newVNode.isSlotFallback ? 'slot-fb' : newVNode.vtag)));
plt.isDefinedComponent(elm) && plt.isCmpReady.delete(hostElm);
true;
isSvgMode = 'svg' === newVNode.vtag || 'foreignObject' !== newVNode.vtag && isSvgMode;
// add css classes, attrs, props, listeners, etc.
updateElement(plt, null, newVNode, isSvgMode);
isDef(scopeId) && elm['s-si'] !== scopeId &&
// if there is a scopeId and this is the initial render
// then let's add the scopeId as an attribute
domApi.$addClass(elm, elm['s-si'] = scopeId);
false;
if (newVNode.vchildren) for (i = 0; i < newVNode.vchildren.length; ++i) {
// create the node
childNode = createElm(oldParentVNode, newVNode, i, elm);
// return node could have been null
if (childNode) {
false;
// append our new node
domApi.$appendChild(elm, childNode);
false;
}
}
(true, 'svg' === newVNode.vtag) && (
// Only reset the SVG context when we're exiting SVG element
isSvgMode = false);
}
true;
newVNode.elm['s-hn'] = hostTagName;
if (newVNode.isSlotFallback || newVNode.isSlotReference) {
// remember the content reference comment
newVNode.elm['s-sr'] = true;
// remember the content reference comment
newVNode.elm['s-cr'] = contentRef;
// remember the slot name, or empty string for default slot
newVNode.elm['s-sn'] = newVNode.vname || '';
// check if we've got an old vnode for this slot
oldVNode = oldParentVNode && oldParentVNode.vchildren && oldParentVNode.vchildren[childIndex];
oldVNode && oldVNode.vtag === newVNode.vtag && oldParentVNode.elm &&
// we've got an old slot vnode and the wrapper is being replaced
// so let's move the old slot content back to it's original location
putBackInOriginalLocation(oldParentVNode.elm);
}
return newVNode.elm;
}
function putBackInOriginalLocation(parentElm, recursive, i, childNode) {
plt.tmpDisconnected = true;
const oldSlotChildNodes = domApi.$childNodes(parentElm);
for (i = oldSlotChildNodes.length - 1; i >= 0; i--) {
childNode = oldSlotChildNodes[i];
if (childNode['s-hn'] !== hostTagName && childNode['s-ol']) {
// this child node in the old element is from another component
// remove this node from the old slot's parent
domApi.$remove(childNode);
// and relocate it back to it's original location
domApi.$insertBefore(parentReferenceNode(childNode), childNode, referenceNode(childNode));
// remove the old original location comment entirely
// later on the patch function will know what to do
// and move this to the correct spot in need be
domApi.$remove(childNode['s-ol']);
childNode['s-ol'] = null;
checkSlotRelocate = true;
}
recursive && putBackInOriginalLocation(childNode, recursive);
}
plt.tmpDisconnected = false;
}
function addVnodes(parentElm, before, parentVNode, vnodes, startIdx, endIdx, containerElm, childNode) {
const contentRef = parentElm['s-cr'];
containerElm = contentRef && domApi.$parentNode(contentRef) || parentElm;
containerElm.shadowRoot && domApi.$tagName(containerElm) === hostTagName && (containerElm = containerElm.shadowRoot);
for (;startIdx <= endIdx; ++startIdx) if (vnodes[startIdx]) {
childNode = isDef(vnodes[startIdx].vtext) ? domApi.$createTextNode(vnodes[startIdx].vtext) : createElm(null, parentVNode, startIdx, parentElm);
if (childNode) {
vnodes[startIdx].elm = childNode;
domApi.$insertBefore(containerElm, childNode, referenceNode(before));
}
}
}
function removeVnodes(vnodes, startIdx, endIdx, node) {
for (;startIdx <= endIdx; ++startIdx) if (isDef(vnodes[startIdx])) {
node = vnodes[startIdx].elm;
true;
// we're removing this element
// so it's possible we need to show slot fallback content now
checkSlotFallbackVisibility = true;
node['s-ol'] ?
// remove the original location comment
domApi.$remove(node['s-ol']) :
// it's possible that child nodes of the node
// that's being removed are slot nodes
putBackInOriginalLocation(node, true);
// remove the vnode's element from the dom
domApi.$remove(node);
}
}
function updateChildren(parentElm, oldCh, newVNode, newCh, idxInOld, i, node, elmToMove) {
let oldStartIdx = 0, newStartIdx = 0;
let oldEndIdx = oldCh.length - 1;
let oldStartVnode = oldCh[0];
let oldEndVnode = oldCh[oldEndIdx];
let newEndIdx = newCh.length - 1;
let newStartVnode = newCh[0];
let newEndVnode = newCh[newEndIdx];
while (oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx) if (null == oldStartVnode)
// Vnode might have been moved left
oldStartVnode = oldCh[++oldStartIdx]; else if (null == oldEndVnode) oldEndVnode = oldCh[--oldEndIdx]; else if (null == newStartVnode) newStartVnode = newCh[++newStartIdx]; else if (null == newEndVnode) newEndVnode = newCh[--newEndIdx]; else if (isSameVnode(oldStartVnode, newStartVnode)) {
patchVNode(oldStartVnode, newStartVnode);
oldStartVnode = oldCh[++oldStartIdx];
newStartVnode = newCh[++newStartIdx];
} else if (isSameVnode(oldEndVnode, newEndVnode)) {
patchVNode(oldEndVnode, newEndVnode);
oldEndVnode = oldCh[--oldEndIdx];
newEndVnode = newCh[--newEndIdx];
} else if (isSameVnode(oldStartVnode, newEndVnode)) {
// Vnode moved right
'slot' !== oldStartVnode.vtag && 'slot' !== newEndVnode.vtag || putBackInOriginalLocation(domApi.$parentNode(oldStartVnode.elm));
patchVNode(oldStartVnode, newEndVnode);
domApi.$insertBefore(parentElm, oldStartVnode.elm, domApi.$nextSibling(oldEndVnode.elm));
oldStartVnode = oldCh[++oldStartIdx];
newEndVnode = newCh[--newEndIdx];
} else if (isSameVnode(oldEndVnode, newStartVnode)) {
// Vnode moved left
'slot' !== oldStartVnode.vtag && 'slot' !== newEndVnode.vtag || putBackInOriginalLocation(domApi.$parentNode(oldEndVnode.elm));
patchVNode(oldEndVnode, newStartVnode);
domApi.$insertBefore(parentElm, oldEndVnode.elm, oldStartVnode.elm);
oldEndVnode = oldCh[--oldEndIdx];
newStartVnode = newCh[++newStartIdx];
} else {
// createKeyToOldIdx
idxInOld = null;
for (i = oldStartIdx; i <= oldEndIdx; ++i) if (oldCh[i] && isDef(oldCh[i].vkey) && oldCh[i].vkey === newStartVnode.vkey) {
idxInOld = i;
break;
}
if (isDef(idxInOld)) {
elmToMove = oldCh[idxInOld];
if (elmToMove.vtag !== newStartVnode.vtag) node = createElm(oldCh && oldCh[newStartIdx], newVNode, idxInOld, parentElm); else {
patchVNode(elmToMove, newStartVnode);
oldCh[idxInOld] = void 0;
node = elmToMove.elm;
}
newStartVnode = newCh[++newStartIdx];
} else {
// new element
node = createElm(oldCh && oldCh[newStartIdx], newVNode, newStartIdx, parentElm);
newStartVnode = newCh[++newStartIdx];
}
node && domApi.$insertBefore(parentReferenceNode(oldStartVnode.elm), node, referenceNode(oldStartVnode.elm));
}
oldStartIdx > oldEndIdx ? addVnodes(parentElm, null == newCh[newEndIdx + 1] ? null : newCh[newEndIdx + 1].elm, newVNode, newCh, newStartIdx, newEndIdx) : newStartIdx > newEndIdx && removeVnodes(oldCh, oldStartIdx, oldEndIdx);
}
function isSameVnode(vnode1, vnode2) {
// compare if two vnode to see if they're "technically" the same
// need to have the same element tag, and same key to be the same
if (vnode1.vtag === vnode2.vtag && vnode1.vkey === vnode2.vkey) {
true;
if ('slot' === vnode1.vtag) return vnode1.vname === vnode2.vname;
return true;
}
return false;
}
function referenceNode(node) {
true;
if (node && node['s-ol'])
// this node was relocated to a new location in the dom
// because of some other component's slot
// but we still have an html comment in place of where
// it's original location was according to it's original vdom
return node['s-ol'];
return node;
}
function parentReferenceNode(node) {
return domApi.$parentNode(node['s-ol'] ? node['s-ol'] : node);
}
function patchVNode(oldVNode, newVNode, defaultHolder) {
const elm = newVNode.elm = oldVNode.elm;
const oldChildren = oldVNode.vchildren;
const newChildren = newVNode.vchildren;
true;
// test if we're rendering an svg element, or still rendering nodes inside of one
// only add this to the when the compiler sees we're using an svg somewhere
isSvgMode = newVNode.elm && isDef(domApi.$parentElement(newVNode.elm)) && void 0 !== newVNode.elm.ownerSVGElement;
isSvgMode = 'svg' === newVNode.vtag || 'foreignObject' !== newVNode.vtag && isSvgMode;
if (isDef(newVNode.vtext)) true, (defaultHolder = elm['s-cr']) ?
// this element has slotted content
domApi.$setTextContent(domApi.$parentNode(defaultHolder), newVNode.vtext) : oldVNode.vtext !== newVNode.vtext &&
// update the text content for the text only vnode
// and also only if the text is different than before
domApi.$setTextContent(elm, newVNode.vtext); else {
// element node
'slot' !== newVNode.vtag &&
// either this is the first render of an element OR it's an update
// AND we already know it's possible it could have changed
// this updates the element's css classes, attrs, props, listeners, etc.
updateElement(plt, oldVNode, newVNode, isSvgMode);
if (isDef(oldChildren) && isDef(newChildren))
// looks like there's child vnodes for both the old and new vnodes
updateChildren(elm, oldChildren, newVNode, newChildren); else if (isDef(newChildren)) {
// no old child vnodes, but there are new child vnodes to add
isDef(oldVNode.vtext) &&
// the old vnode was text, so be sure to clear it out
domApi.$setTextContent(elm, '');
// add the new vnode children
addVnodes(elm, null, newVNode, newChildren, 0, newChildren.length - 1);
} else isDef(oldChildren) &&
// no new child vnodes, but there are old child vnodes to remove
removeVnodes(oldChildren, 0, oldChildren.length - 1);
}
true;
// reset svgMode when svg node is fully patched
isSvgMode && 'svg' === newVNode.vtag && (isSvgMode = false);
}
function updateFallbackSlotVisibility(elm, childNode, childNodes, i, ilen, j, slotNameAttr, nodeType) {
childNodes = domApi.$childNodes(elm);
for (i = 0, ilen = childNodes.length; i < ilen; i++) {
childNode = childNodes[i];
if (1 /* ElementNode */ === domApi.$nodeType(childNode)) {
if (childNode['s-sr']) {
// this is a slot fallback node
// get the slot name for this slot reference node
slotNameAttr = childNode['s-sn'];
// by default always show a fallback slot node
// then hide it if there are other slots in the light dom
childNode.hidden = false;
for (j = 0; j < ilen; j++) if (childNodes[j]['s-hn'] !== childNode['s-hn']) {
// this sibling node is from a different component
nodeType = domApi.$nodeType(childNodes[j]);
if ('' !== slotNameAttr) {
// this is a named fallback slot node
if (1 /* ElementNode */ === nodeType && slotNameAttr === domApi.$getAttribute(childNodes[j], 'slot')) {
childNode.hidden = true;
break;
}
} else
// this is a default fallback slot node
// any element or text node (with content)
// should hide the default fallback slot node
if (1 /* ElementNode */ === nodeType || 3 /* TextNode */ === nodeType && '' !== domApi.$getTextContent(childNodes[j]).trim()) {
childNode.hidden = true;
break;
}
}
}
// keep drilling down
updateFallbackSlotVisibility(childNode);
}
}
}
const relocateNodes = [];
function relocateSlotContent(elm, childNodes, childNode, node, i, ilen, j, hostContentNodes, slotNameAttr, nodeType) {
childNodes = domApi.$childNodes(elm);
for (i = 0, ilen = childNodes.length; i < ilen; i++) {
childNode = childNodes[i];
if (childNode['s-sr'] && (node = childNode['s-cr'])) {
// first got the content reference comment node
// then we got it's parent, which is where all the host content is in now
hostContentNodes = domApi.$childNodes(domApi.$parentNode(node));
slotNameAttr = childNode['s-sn'];
for (j = hostContentNodes.length - 1; j >= 0; j--) {
node = hostContentNodes[j];
if (!node['s-cn'] && !node['s-nr'] && node['s-hn'] !== childNode['s-hn']) {
// let's do some relocating to its new home
// but never relocate a content reference node
// that is suppose to always represent the original content location
nodeType = domApi.$nodeType(node);
if (((3 /* TextNode */ === nodeType || 8 /* CommentNode */ === nodeType) && '' === slotNameAttr || 1 /* ElementNode */ === nodeType && null === domApi.$getAttribute(node, 'slot') && '' === slotNameAttr || 1 /* ElementNode */ === nodeType && domApi.$getAttribute(node, 'slot') === slotNameAttr) && !relocateNodes.some(r => r.nodeToRelocate === node)) {
// made some changes to slots
// let's make sure we also double check
// fallbacks are correctly hidden or shown
checkSlotFallbackVisibility = true;
node['s-sn'] = slotNameAttr;
// add to our list of nodes to relocate
relocateNodes.push({
slotRefNode: childNode,
nodeToRelocate: node
});
}
}
}
}
1 /* ElementNode */ === domApi.$nodeType(childNode) && relocateSlotContent(childNode);
}
}
return function patch(hostElement, oldVNode, newVNode, useNativeShadowDomVal, encapsulation, ssrPatchId, i, relocateNode, orgLocationNode, refNode, parentNodeRef, insertBeforeNode) {
// patchVNode() is synchronous
// so it is safe to set these variables and internally
// the same patch() call will reference the same data
hostElm = hostElement;
hostTagName = domApi.$tagName(hostElm);
contentRef = hostElm['s-cr'];
useNativeShadowDom = useNativeShadowDomVal;
false;
true;
// get the scopeId
scopeId = hostElm['s-sc'];
// always reset
checkSlotRelocate = checkSlotFallbackVisibility = false;
// synchronous patch
patchVNode(oldVNode, newVNode);
false;
true;
if (checkSlotRelocate) {
relocateSlotContent(newVNode.elm);
for (i = 0; i < relocateNodes.length; i++) {
relocateNode = relocateNodes[i];
if (!relocateNode.nodeToRelocate['s-ol']) {
// add a reference node marking this node's original location
// keep a reference to this node for later lookups
orgLocationNode = domApi.$createTextNode('');
orgLocationNode['s-nr'] = relocateNode.nodeToRelocate;
domApi.$insertBefore(domApi.$parentNode(relocateNode.nodeToRelocate), relocateNode.nodeToRelocate['s-ol'] = orgLocationNode, relocateNode.nodeToRelocate);
}
}
// while we're moving nodes around existing nodes, temporarily disable
// the disconnectCallback from working
plt.tmpDisconnected = true;
for (i = 0; i < relocateNodes.length; i++) {
relocateNode = relocateNodes[i];
// by default we're just going to insert it directly
// after the slot reference node
parentNodeRef = domApi.$parentNode(relocateNode.slotRefNode);
insertBeforeNode = domApi.$nextSibling(relocateNode.slotRefNode);
orgLocationNode = relocateNode.nodeToRelocate['s-ol'];
while (orgLocationNode = domApi.$previousSibling(orgLocationNode)) if ((refNode = orgLocationNode['s-nr']) && refNode && refNode['s-sn'] === relocateNode.nodeToRelocate['s-sn'] && parentNodeRef === domApi.$parentNode(refNode) && (refNode = domApi.$nextSibling(refNode)) && refNode && !refNode['s-nr']) {
insertBeforeNode = refNode;
break;
}
if ((!insertBeforeNode && parentNodeRef !== domApi.$parentNode(relocateNode.nodeToRelocate) || domApi.$nextSibling(relocateNode.nodeToRelocate) !== insertBeforeNode) && relocateNode.nodeToRelocate !== insertBeforeNode) {
// remove the node from the dom
domApi.$remove(relocateNode.nodeToRelocate);
// add it back to the dom but in its new home
domApi.$insertBefore(parentNodeRef, relocateNode.nodeToRelocate, insertBeforeNode);
}
}
// done moving nodes around
// allow the disconnect callback to work again
plt.tmpDisconnected = false;
}
checkSlotFallbackVisibility && updateFallbackSlotVisibility(newVNode.elm);
// always reset
relocateNodes.length = 0;
// return our new vnode
return newVNode;
};
}
function callNodeRefs(vNode, isDestroy) {
if (vNode) {
vNode.vattrs && vNode.vattrs.ref && vNode.vattrs.ref(isDestroy ? null : vNode.elm);
vNode.vchildren && vNode.vchildren.forEach(vChild => {
callNodeRefs(vChild, isDestroy);
});
}
}
function createVNodesFromSsr(plt, domApi, rootElm) {
const allSsrElms = rootElm.querySelectorAll(`[${SSR_VNODE_ID}]`);
const ilen = allSsrElms.length;
let elm, ssrVNodeId, ssrVNode, i, j, jlen;
if (ilen > 0) {
plt.isCmpReady.set(rootElm, true);
for (i = 0; i < ilen; i++) {
elm = allSsrElms[i];
ssrVNodeId = domApi.$getAttribute(elm, SSR_VNODE_ID);
ssrVNode = {};
ssrVNode.vtag = domApi.$tagName(ssrVNode.elm = elm);
plt.vnodeMap.set(elm, ssrVNode);
for (j = 0, jlen = elm.childNodes.length; j < jlen; j++) addChildSsrVNodes(domApi, elm.childNodes[j], ssrVNode, ssrVNodeId, true);
}
}
}
function addChildSsrVNodes(domApi, node, parentVNode, ssrVNodeId, checkNestedElements) {
const nodeType = domApi.$nodeType(node);
let previousComment;
let childVNodeId, childVNodeSplt, childVNode;
if (checkNestedElements && 1 /* ElementNode */ === nodeType) {
childVNodeId = domApi.$getAttribute(node, SSR_CHILD_ID);
if (childVNodeId) {
// split the start comment's data with a period
childVNodeSplt = childVNodeId.split('.');
// ensure this this element is a child element of the ssr vnode
if (childVNodeSplt[0] === ssrVNodeId) {
// cool, this element is a child to the parent vnode
childVNode = {};
childVNode.vtag = domApi.$tagName(childVNode.elm = node);
// this is a new child vnode
// so ensure its parent vnode has the vchildren array
parentVNode.vchildren || (parentVNode.vchildren = []);
// add our child vnode to a specific index of the vnode's children
parentVNode.vchildren[childVNodeSplt[1]] = childVNode;
// this is now the new parent vnode for all the next child checks
parentVNode = childVNode;
// if there's a trailing period, then it means there aren't any
// more nested elements, but maybe nested text nodes
// either way, don't keep walking down the tree after this next call
checkNestedElements = '' !== childVNodeSplt[2];
}
}
// keep drilling down through the elements
for (let i = 0; i < node.childNodes.length; i++) addChildSsrVNodes(domApi, node.childNodes[i], parentVNode, ssrVNodeId, checkNestedElements);
} else if (3 /* TextNode */ === nodeType && (previousComment = node.previousSibling) && 8 /* CommentNode */ === domApi.$nodeType(previousComment)) {
// split the start comment's data with a period
childVNodeSplt = domApi.$getTextContent(previousComment).split('.');
// ensure this is an ssr text node start comment
// which should start with an "s" and delimited by periods
if ('s' === childVNodeSplt[0] && childVNodeSplt[1] === ssrVNodeId) {
// cool, this is a text node and it's got a start comment
childVNode = {
vtext: domApi.$getTextContent(node)
};
childVNode.elm = node;
// this is a new child vnode
// so ensure its parent vnode has the vchildren array
parentVNode.vchildren || (parentVNode.vchildren = []);
// add our child vnode to a specific index of the vnode's children
parentVNode.vchildren[childVNodeSplt[2]] = childVNode;
}
}
}
function createQueueClient(App, win) {
const now = () => win.performance.now();
const resolved = Promise.resolve();
const highPriority = [];
const domReads = [];
const domWrites = [];
const domWritesLow = [];
let congestion = 0;
let rafPending = false;
App.raf || (App.raf = win.requestAnimationFrame.bind(win));
function queueTask(queue) {
return cb => {
// queue dom reads
queue.push(cb);
if (!rafPending) {
rafPending = true;
App.raf(flush);
}
};
}
function consume(queue) {
for (let i = 0; i < queue.length; i++) try {
queue[i](now());
} catch (e) {
console.error(e);
}
queue.length = 0;
}
function consumeTimeout(queue, timeout) {
let i = 0;
let ts;
while (i < queue.length && (ts = now()) < timeout) try {
queue[i++](ts);
} catch (e) {
console.error(e);
}
i === queue.length ? queue.length = 0 : 0 !== i && queue.splice(0, i);
}
function flush() {
congestion++;
// always force a bunch of medium callbacks to run, but still have
// a throttle on how many can run in a certain time
// DOM READS!!!
consume(domReads);
const start = now() + 7 * Math.ceil(congestion * (1 / 22));
// DOM WRITES!!!
consumeTimeout(domWrites, start);
consumeTimeout(domWritesLow, start);
if (domWrites.length > 0) {
domWritesLow.push(...domWrites);
domWrites.length = 0;
}
(rafPending = domReads.length + domWrites.length + domWritesLow.length > 0) ?
// still more to do yet, but we've run out of time
// let's let this thing cool off and try again in the next tick
App.raf(flush) : congestion = 0;
}
return {
tick(cb) {
// queue high priority work to happen in next tick
// uses Promise.resolve() for next tick
highPriority.push(cb);
1 === highPriority.length && resolved.then(() => consume(highPriority));
},
read: queueTask(domReads),
write: queueTask(domWrites)
};
}
function initElementListeners(plt, elm) {
// so the element was just connected, which means it's in the DOM
// however, the component instance hasn't been created yet
// but what if an event it should be listening to get emitted right now??
// let's add our listeners right now to our element, and if it happens
// to receive events between now and the instance being created let's
// queue up all of the event data and fire it off on the instance when it's ready
const cmpMeta = plt.getComponentMeta(elm);
cmpMeta.listenersMeta &&
// we've got listens
cmpMeta.listenersMeta.forEach(listenMeta => {
// go through each listener
listenMeta.eventDisabled ||
// only add ones that are not already disabled
plt.domApi.$addEventListener(elm, listenMeta.eventName, createListenerCallback(plt, elm, listenMeta.eventMethodName), listenMeta.eventCapture, listenMeta.eventPassive);
});
}
function createListenerCallback(plt, elm, eventMethodName, val) {
// create the function that gets called when the element receives
// an event which it should be listening for
return ev => {
// get the instance if it exists
val = plt.instanceMap.get(elm);
if (val)
// instance is ready, let's call it's member method for this event
val[eventMethodName](ev); else {
// instance is not ready!!
// let's queue up this event data and replay it later
// when the instance is ready
val = plt.queuedEvents.get(elm) || [];
val.push(eventMethodName, ev);
plt.queuedEvents.set(elm, val);
}
};
}
function generateDevInspector(namespace, win, plt, components) {
const devInspector = win.devInspector = win.devInspector || {};
devInspector.apps = devInspector.apps || [];
devInspector.apps.push(generateDevInspectorApp(namespace, plt, components));
devInspector.getInstance || (devInspector.getInstance = (elm => {
return Promise.all(devInspector.apps.map(app => {
return app.getInstance(elm);
})).then(results => {
return results.find(instance => !!instance);
});
}));
devInspector.getComponents || (devInspector.getComponents = (() => {
const appsMetadata = [];
devInspector.apps.forEach(app => {
appsMetadata.push(app.getComponents());
});
return Promise.all(appsMetadata).then(appMetadata => {
const allMetadata = [];
appMetadata.forEach(metadata => {
metadata.forEach(m => {
allMetadata.push(m);
});
});
return allMetadata;
});
}));
return devInspector;
}
function generateDevInspectorApp(namespace, plt, components) {
const app = {
namespace: namespace,
getInstance: elm => {
if (elm && elm.tagName) return Promise.all([ getComponentMeta(plt, elm.tagName), getComponentInstance(plt, elm) ]).then(results => {
if (results[0] && results[1]) {
const cmp = {
meta: results[0],
instance: results[1]
};
return cmp;
}
return null;
});
return Promise.resolve(null);
},
getComponent: tagName => {
return getComponentMeta(plt, tagName);
},
getComponents: () => {
return Promise.all(components.map(cmp => {
return getComponentMeta(plt, cmp[0]);
})).then(metadata => {
return metadata.filter(m => m);
});
}
};
return app;
}
function getMembersMeta(properties) {
return Object.keys(properties).reduce((membersMap, memberKey) => {
const prop = properties[memberKey];
let category;
const member = {
name: memberKey
};
if (prop.state) {
category = 'states';
member.watchers = prop.watchCallbacks || [];
} else if (prop.elementRef) category = 'elements'; else if (prop.method) category = 'methods'; else {
category = 'props';
let type = 'any';
if (prop.type) {
type = prop.type;
'function' === typeof prop.type && (type = prop.type.name);
}
member.type = type.toLowerCase();
member.mutable = prop.mutable || false;
member.connect = prop.connect || '-';
member.context = prop.connect || '-';
member.watchers = prop.watchCallbacks || [];
}
membersMap[category].push(member);
return membersMap;
}, {
props: [],
states: [],
elements: [],
methods: []
});
}
function getComponentMeta(plt, tagName) {
const elm = {
nodeName: tagName
};
const internalMeta = plt.getComponentMeta(elm);
if (!internalMeta || !internalMeta.componentConstructor) return Promise.resolve(null);
const cmpCtr = internalMeta.componentConstructor;
const members = getMembersMeta(cmpCtr.properties || {});
const listeners = (internalMeta.listenersMeta || []).map(listenerMeta => {
return {
event: listenerMeta.eventName,
capture: listenerMeta.eventCapture,
disabled: listenerMeta.eventDisabled,
passive: listenerMeta.eventPassive,
method: listenerMeta.eventMethodName
};
});
const emmiters = cmpCtr.events || [];
const meta = Object.assign({
tag: cmpCtr.is,
bundle: internalMeta.bundleIds || 'unknown',
encapsulation: cmpCtr.encapsulation || 'none'
}, members, {
events: {
emmiters: emmiters,
listeners: listeners
}
});
return Promise.resolve(meta);
}
function getComponentInstance(plt, elm) {
return Promise.resolve(plt.instanceMap.get(elm));
}
/**
* Production h() function based on Preact by
* Jason Miller (@developit)
* Licensed under the MIT License
* https://github.com/developit/preact/blob/master/LICENSE
*
* Modified for Stencil's compiler and vdom
*/ const stack = [];
function h(nodeName, vnodeData) {
let child