(function (exports) { 'use strict'; function filterOutModifiersFromData(dataList) { dataList = dataList.slice(); const modifiers = []; let elm; while ((elm = dataList[0]) && typeof elm === "string") { modifiers.push(dataList.shift()); } return { modifiers, data: dataList }; } const config = { // whether or not blockdom should normalize DOM whenever a block is created. // Normalizing dom mean removing empty text nodes (or containing only spaces) shouldNormalizeDom: true, // this is the main event handler. Every event handler registered with blockdom // will go through this function, giving it the data registered in the block // and the event mainEventHandler: (data, ev, currentTarget) => { if (typeof data === "function") { data(ev); } else if (Array.isArray(data)) { data = filterOutModifiersFromData(data).data; data[0](data[1], ev); } return false; }, }; // ----------------------------------------------------------------------------- // Toggler node // ----------------------------------------------------------------------------- class VToggler { constructor(key, child) { this.key = key; this.child = child; } mount(parent, afterNode) { this.parentEl = parent; this.child.mount(parent, afterNode); } moveBeforeDOMNode(node, parent) { this.child.moveBeforeDOMNode(node, parent); } moveBeforeVNode(other, afterNode) { this.moveBeforeDOMNode((other && other.firstNode()) || afterNode); } patch(other, withBeforeRemove) { if (this === other) { return; } let child1 = this.child; let child2 = other.child; if (this.key === other.key) { child1.patch(child2, withBeforeRemove); } else { child2.mount(this.parentEl, child1.firstNode()); if (withBeforeRemove) { child1.beforeRemove(); } child1.remove(); this.child = child2; this.key = other.key; } } beforeRemove() { this.child.beforeRemove(); } remove() { this.child.remove(); } firstNode() { return this.child.firstNode(); } toString() { return this.child.toString(); } } function toggler(key, child) { return new VToggler(key, child); } // Custom error class that wraps error that happen in the owl lifecycle class OwlError extends Error { } // Maps fibers to thrown errors const fibersInError = new WeakMap(); const nodeErrorHandlers = new WeakMap(); function _handleError(node, error) { if (!node) { return false; } const fiber = node.fiber; if (fiber) { fibersInError.set(fiber, error); } const errorHandlers = nodeErrorHandlers.get(node); if (errorHandlers) { let handled = false; // execute in the opposite order for (let i = errorHandlers.length - 1; i >= 0; i--) { try { errorHandlers[i](error); handled = true; break; } catch (e) { error = e; } } if (handled) { return true; } } return _handleError(node.parent, error); } function handleError(params) { let { error } = params; // Wrap error if it wasn't wrapped by wrapError (ie when not in dev mode) if (!(error instanceof OwlError)) { error = Object.assign(new OwlError(`An error occured in the owl lifecycle (see this Error's "cause" property)`), { cause: error }); } const node = "node" in params ? params.node : params.fiber.node; const fiber = "fiber" in params ? params.fiber : node.fiber; // resets the fibers on components if possible. This is important so that // new renderings can be properly included in the initial one, if any. let current = fiber; do { current.node.fiber = current; current = current.parent; } while (current); fibersInError.set(fiber.root, error); const handled = _handleError(node, error); if (!handled) { console.warn(`[Owl] Unhandled error. Destroying the root component`); try { node.app.destroy(); } catch (e) { console.error(e); } throw error; } } const { setAttribute: elemSetAttribute, removeAttribute } = Element.prototype; const tokenList = DOMTokenList.prototype; const tokenListAdd = tokenList.add; const tokenListRemove = tokenList.remove; const isArray = Array.isArray; const { split, trim } = String.prototype; const wordRegexp = /\s+/; /** * We regroup here all code related to updating attributes in a very loose sense: * attributes, properties and classs are all managed by the functions in this * file. */ function setAttribute(key, value) { switch (value) { case false: case undefined: removeAttribute.call(this, key); break; case true: elemSetAttribute.call(this, key, ""); break; default: elemSetAttribute.call(this, key, value); } } function createAttrUpdater(attr) { return function (value) { setAttribute.call(this, attr, value); }; } function attrsSetter(attrs) { if (isArray(attrs)) { setAttribute.call(this, attrs[0], attrs[1]); } else { for (let k in attrs) { setAttribute.call(this, k, attrs[k]); } } } function attrsUpdater(attrs, oldAttrs) { if (isArray(attrs)) { const name = attrs[0]; const val = attrs[1]; if (name === oldAttrs[0]) { if (val === oldAttrs[1]) { return; } setAttribute.call(this, name, val); } else { removeAttribute.call(this, oldAttrs[0]); setAttribute.call(this, name, val); } } else { for (let k in oldAttrs) { if (!(k in attrs)) { removeAttribute.call(this, k); } } for (let k in attrs) { const val = attrs[k]; if (val !== oldAttrs[k]) { setAttribute.call(this, k, val); } } } } function toClassObj(expr) { const result = {}; switch (typeof expr) { case "string": // we transform here a list of classes into an object: // 'hey you' becomes {hey: true, you: true} const str = trim.call(expr); if (!str) { return {}; } let words = split.call(str, wordRegexp); for (let i = 0, l = words.length; i < l; i++) { result[words[i]] = true; } return result; case "object": // this is already an object but we may need to split keys: // {'a': true, 'b c': true} should become {a: true, b: true, c: true} for (let key in expr) { const value = expr[key]; if (value) { key = trim.call(key); if (!key) { continue; } const words = split.call(key, wordRegexp); for (let word of words) { result[word] = value; } } } return result; case "undefined": return {}; case "number": return { [expr]: true }; default: return { [expr]: true }; } } function setClass(val) { val = val === "" ? {} : toClassObj(val); // add classes const cl = this.classList; for (let c in val) { tokenListAdd.call(cl, c); } } function updateClass(val, oldVal) { oldVal = oldVal === "" ? {} : toClassObj(oldVal); val = val === "" ? {} : toClassObj(val); const cl = this.classList; // remove classes for (let c in oldVal) { if (!(c in val)) { tokenListRemove.call(cl, c); } } // add classes for (let c in val) { if (!(c in oldVal)) { tokenListAdd.call(cl, c); } } } function makePropSetter(name) { return function setProp(value) { // support 0, fallback to empty string for other falsy values this[name] = value === 0 ? 0 : value ? value.valueOf() : ""; }; } function isProp(tag, key) { switch (tag) { case "input": return (key === "checked" || key === "indeterminate" || key === "value" || key === "readonly" || key === "disabled"); case "option": return key === "selected" || key === "disabled"; case "textarea": return key === "value" || key === "readonly" || key === "disabled"; case "select": return key === "value" || key === "disabled"; case "button": case "optgroup": return key === "disabled"; } return false; } function createEventHandler(rawEvent) { const eventName = rawEvent.split(".")[0]; const capture = rawEvent.includes(".capture"); if (rawEvent.includes(".synthetic")) { return createSyntheticHandler(eventName, capture); } else { return createElementHandler(eventName, capture); } } // Native listener let nextNativeEventId = 1; function createElementHandler(evName, capture = false) { let eventKey = `__event__${evName}_${nextNativeEventId++}`; if (capture) { eventKey = `${eventKey}_capture`; } function listener(ev) { const currentTarget = ev.currentTarget; if (!currentTarget || !currentTarget.ownerDocument.contains(currentTarget)) return; const data = currentTarget[eventKey]; if (!data) return; config.mainEventHandler(data, ev, currentTarget); } function setup(data) { this[eventKey] = data; this.addEventListener(evName, listener, { capture }); } function remove() { delete this[eventKey]; this.removeEventListener(evName, listener, { capture }); } function update(data) { this[eventKey] = data; } return { setup, update, remove }; } // Synthetic handler: a form of event delegation that allows placing only one // listener per event type. let nextSyntheticEventId = 1; function createSyntheticHandler(evName, capture = false) { let eventKey = `__event__synthetic_${evName}`; if (capture) { eventKey = `${eventKey}_capture`; } setupSyntheticEvent(evName, eventKey, capture); const currentId = nextSyntheticEventId++; function setup(data) { const _data = this[eventKey] || {}; _data[currentId] = data; this[eventKey] = _data; } function remove() { delete this[eventKey]; } return { setup, update: setup, remove }; } function nativeToSyntheticEvent(eventKey, event) { let dom = event.target; while (dom !== null) { const _data = dom[eventKey]; if (_data) { for (const data of Object.values(_data)) { const stopped = config.mainEventHandler(data, event, dom); if (stopped) return; } } dom = dom.parentNode; } } const CONFIGURED_SYNTHETIC_EVENTS = {}; function setupSyntheticEvent(evName, eventKey, capture = false) { if (CONFIGURED_SYNTHETIC_EVENTS[eventKey]) { return; } document.addEventListener(evName, (event) => nativeToSyntheticEvent(eventKey, event), { capture, }); CONFIGURED_SYNTHETIC_EVENTS[eventKey] = true; } const getDescriptor$3 = (o, p) => Object.getOwnPropertyDescriptor(o, p); const nodeProto$4 = Node.prototype; const nodeInsertBefore$3 = nodeProto$4.insertBefore; const nodeSetTextContent$1 = getDescriptor$3(nodeProto$4, "textContent").set; const nodeRemoveChild$3 = nodeProto$4.removeChild; // ----------------------------------------------------------------------------- // Multi NODE // ----------------------------------------------------------------------------- class VMulti { constructor(children) { this.children = children; } mount(parent, afterNode) { const children = this.children; const l = children.length; const anchors = new Array(l); for (let i = 0; i < l; i++) { let child = children[i]; if (child) { child.mount(parent, afterNode); } else { const childAnchor = document.createTextNode(""); anchors[i] = childAnchor; nodeInsertBefore$3.call(parent, childAnchor, afterNode); } } this.anchors = anchors; this.parentEl = parent; } moveBeforeDOMNode(node, parent = this.parentEl) { this.parentEl = parent; const children = this.children; const anchors = this.anchors; for (let i = 0, l = children.length; i < l; i++) { let child = children[i]; if (child) { child.moveBeforeDOMNode(node, parent); } else { const anchor = anchors[i]; nodeInsertBefore$3.call(parent, anchor, node); } } } moveBeforeVNode(other, afterNode) { if (other) { const next = other.children[0]; afterNode = (next ? next.firstNode() : other.anchors[0]) || null; } const children = this.children; const parent = this.parentEl; const anchors = this.anchors; for (let i = 0, l = children.length; i < l; i++) { let child = children[i]; if (child) { child.moveBeforeVNode(null, afterNode); } else { const anchor = anchors[i]; nodeInsertBefore$3.call(parent, anchor, afterNode); } } } patch(other, withBeforeRemove) { if (this === other) { return; } const children1 = this.children; const children2 = other.children; const anchors = this.anchors; const parentEl = this.parentEl; for (let i = 0, l = children1.length; i < l; i++) { const vn1 = children1[i]; const vn2 = children2[i]; if (vn1) { if (vn2) { vn1.patch(vn2, withBeforeRemove); } else { const afterNode = vn1.firstNode(); const anchor = document.createTextNode(""); anchors[i] = anchor; nodeInsertBefore$3.call(parentEl, anchor, afterNode); if (withBeforeRemove) { vn1.beforeRemove(); } vn1.remove(); children1[i] = undefined; } } else if (vn2) { children1[i] = vn2; const anchor = anchors[i]; vn2.mount(parentEl, anchor); nodeRemoveChild$3.call(parentEl, anchor); } } } beforeRemove() { const children = this.children; for (let i = 0, l = children.length; i < l; i++) { const child = children[i]; if (child) { child.beforeRemove(); } } } remove() { const parentEl = this.parentEl; if (this.isOnlyChild) { nodeSetTextContent$1.call(parentEl, ""); } else { const children = this.children; const anchors = this.anchors; for (let i = 0, l = children.length; i < l; i++) { const child = children[i]; if (child) { child.remove(); } else { nodeRemoveChild$3.call(parentEl, anchors[i]); } } } } firstNode() { const child = this.children[0]; return child ? child.firstNode() : this.anchors[0]; } toString() { return this.children.map((c) => (c ? c.toString() : "")).join(""); } } function multi(children) { return new VMulti(children); } const getDescriptor$2 = (o, p) => Object.getOwnPropertyDescriptor(o, p); const nodeProto$3 = Node.prototype; const characterDataProto$1 = CharacterData.prototype; const nodeInsertBefore$2 = nodeProto$3.insertBefore; const characterDataSetData$1 = getDescriptor$2(characterDataProto$1, "data").set; const nodeRemoveChild$2 = nodeProto$3.removeChild; class VSimpleNode { constructor(text) { this.text = text; } mountNode(node, parent, afterNode) { this.parentEl = parent; nodeInsertBefore$2.call(parent, node, afterNode); this.el = node; } moveBeforeDOMNode(node, parent = this.parentEl) { this.parentEl = parent; nodeInsertBefore$2.call(parent, this.el, node); } moveBeforeVNode(other, afterNode) { nodeInsertBefore$2.call(this.parentEl, this.el, other ? other.el : afterNode); } beforeRemove() { } remove() { nodeRemoveChild$2.call(this.parentEl, this.el); } firstNode() { return this.el; } toString() { return this.text; } } class VText$1 extends VSimpleNode { mount(parent, afterNode) { this.mountNode(document.createTextNode(toText(this.text)), parent, afterNode); } patch(other) { const text2 = other.text; if (this.text !== text2) { characterDataSetData$1.call(this.el, toText(text2)); this.text = text2; } } } class VComment extends VSimpleNode { mount(parent, afterNode) { this.mountNode(document.createComment(toText(this.text)), parent, afterNode); } patch() { } } function text(str) { return new VText$1(str); } function comment(str) { return new VComment(str); } function toText(value) { switch (typeof value) { case "string": return value; case "number": return String(value); case "boolean": return value ? "true" : "false"; default: return value || ""; } } const getDescriptor$1 = (o, p) => Object.getOwnPropertyDescriptor(o, p); const nodeProto$2 = Node.prototype; const elementProto = Element.prototype; const characterDataProto = CharacterData.prototype; const characterDataSetData = getDescriptor$1(characterDataProto, "data").set; const nodeGetFirstChild = getDescriptor$1(nodeProto$2, "firstChild").get; const nodeGetNextSibling = getDescriptor$1(nodeProto$2, "nextSibling").get; const NO_OP = () => { }; const cache$1 = {}; /** * Compiling blocks is a multi-step process: * * 1. build an IntermediateTree from the HTML element. This intermediate tree * is a binary tree structure that encode dynamic info sub nodes, and the * path required to reach them * 2. process the tree to build a block context, which is an object that aggregate * all dynamic info in a list, and also, all ref indexes. * 3. process the context to build appropriate builder/setter functions * 4. make a dynamic block class, which will efficiently collect references and * create/update dynamic locations/children * * @param str * @returns a new block type, that can build concrete blocks */ function createBlock(str) { if (str in cache$1) { return cache$1[str]; } // step 0: prepare html base element const doc = new DOMParser().parseFromString(`${str}`, "text/xml"); const node = doc.firstChild.firstChild; if (config.shouldNormalizeDom) { normalizeNode(node); } // step 1: prepare intermediate tree const tree = buildTree(node); // step 2: prepare block context const context = buildContext(tree); // step 3: build the final block class const template = tree.el; const Block = buildBlock(template, context); cache$1[str] = Block; return Block; } // ----------------------------------------------------------------------------- // Helper // ----------------------------------------------------------------------------- function normalizeNode(node) { if (node.nodeType === Node.TEXT_NODE) { if (!/\S/.test(node.textContent)) { node.remove(); return; } } if (node.nodeType === Node.ELEMENT_NODE) { if (node.tagName === "pre") { return; } } for (let i = node.childNodes.length - 1; i >= 0; --i) { normalizeNode(node.childNodes.item(i)); } } function buildTree(node, parent = null, domParentTree = null) { switch (node.nodeType) { case Node.ELEMENT_NODE: { // HTMLElement let currentNS = domParentTree && domParentTree.currentNS; const tagName = node.tagName; let el = undefined; const info = []; if (tagName.startsWith("block-text-")) { const index = parseInt(tagName.slice(11), 10); info.push({ type: "text", idx: index }); el = document.createTextNode(""); } if (tagName.startsWith("block-child-")) { if (!domParentTree.isRef) { addRef(domParentTree); } const index = parseInt(tagName.slice(12), 10); info.push({ type: "child", idx: index }); el = document.createTextNode(""); } const attrs = node.attributes; const ns = attrs.getNamedItem("block-ns"); if (ns) { attrs.removeNamedItem("block-ns"); currentNS = ns.value; } if (!el) { el = currentNS ? document.createElementNS(currentNS, tagName) : document.createElement(tagName); } if (el instanceof Element) { if (!domParentTree) { // some html elements may have side effects when setting their attributes. // For example, setting the src attribute of an will trigger a // request to get the corresponding image. This is something that we // don't want at compile time. We avoid that by putting the content of // the block in a