(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 { } 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)) { if (attrs[0] === "class") { setClass.call(this, attrs[1]); } else { setAttribute.call(this, attrs[0], attrs[1]); } } else { for (let k in attrs) { if (k === "class") { setClass.call(this, attrs[k]); } else { 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; } if (name === "class") { updateClass.call(this, val, oldAttrs[1]); } else { 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)) { if (k === "class") { updateClass.call(this, "", oldAttrs[k]); } else { removeAttribute.call(this, k); } } } for (let k in attrs) { const val = attrs[k]; if (val !== oldAttrs[k]) { if (k === "class") { updateClass.call(this, val, oldAttrs[k]); } else { 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); } } } /** * Creates a batched version of a callback so that all calls to it in the same * microtick will only call the original callback once. * * @param callback the callback to batch * @returns a batched version of the original callback */ function batched(callback) { let scheduled = false; return async (...args) => { if (!scheduled) { scheduled = true; await Promise.resolve(); scheduled = false; callback(...args); } }; } /** * Determine whether the given element is contained in its ownerDocument: * either directly or with a shadow root in between. */ function inOwnerDocument(el) { if (!el) { return false; } if (el.ownerDocument.contains(el)) { return true; } const rootNode = el.getRootNode(); return rootNode instanceof ShadowRoot && el.ownerDocument.contains(rootNode.host); } function validateTarget(target) { // Get the document and HTMLElement corresponding to the target to allow mounting in iframes const document = target && target.ownerDocument; if (document) { const HTMLElement = document.defaultView.HTMLElement; if (target instanceof HTMLElement || target instanceof ShadowRoot) { if (!document.body.contains(target instanceof HTMLElement ? target : target.host)) { throw new OwlError("Cannot mount a component on a detached dom node"); } return; } } throw new OwlError("Cannot mount component: the target is not a valid DOM element"); } class EventBus extends EventTarget { trigger(name, payload) { this.dispatchEvent(new CustomEvent(name, { detail: payload })); } } function whenReady(fn) { return new Promise(function (resolve) { if (document.readyState !== "loading") { resolve(true); } else { document.addEventListener("DOMContentLoaded", resolve, false); } }).then(fn || function () { }); } async function loadFile(url) { const result = await fetch(url); if (!result.ok) { throw new OwlError("Error while fetching xml templates"); } return await result.text(); } /* * This class just transports the fact that a string is safe * to be injected as HTML. Overriding a JS primitive is quite painful though * so we need to redfine toString and valueOf. */ class Markup extends String { } /* * Marks a value as safe, that is, a value that can be injected as HTML directly. * It should be used to wrap the value passed to a t-out directive to allow a raw rendering. */ function markup(value) { return new Markup(value); } 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 || !inOwnerDocument(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 = () => { }; 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() : ""; }; } 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(""); } currentNS || (currentNS = node.namespaceURI); 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