diff --git a/Activity/activity1.css b/Activity/activity1.css
new file mode 100644
index 0000000..38443a6
--- /dev/null
+++ b/Activity/activity1.css
@@ -0,0 +1,13 @@
+input[type=text] {
+ width: 100%;
+ padding: 12px 20px;
+ margin: 8px 0;
+ box-sizing: border-box;
+ border: 2px solid blueviolet;
+ border-radius: 4px;
+
+ }
+ #result{
+ color:red;
+ }
+
\ No newline at end of file
diff --git a/Activity/activity1.html b/Activity/activity1.html
new file mode 100644
index 0000000..4f878d0
--- /dev/null
+++ b/Activity/activity1.html
@@ -0,0 +1,46 @@
+
+
+ Activity 1
+
+
+
+
+
+
+
+
+
diff --git a/Activity2/fetch_products.css b/Activity2/fetch_products.css
new file mode 100644
index 0000000..1d1ec9d
--- /dev/null
+++ b/Activity2/fetch_products.css
@@ -0,0 +1,14 @@
+#products {
+ display: grid;
+ grid-template-columns: auto auto auto auto;
+ gap: 1px;
+ padding: 2px;
+ }
+
+ #products > div {
+ background-color: rgba(255, 255, 255, 0.8);
+ text-align: center;
+ padding: 2px;
+ font-size: 15px;
+ }
+
\ No newline at end of file
diff --git a/Activity2/fetch_products.html b/Activity2/fetch_products.html
new file mode 100644
index 0000000..485ad7a
--- /dev/null
+++ b/Activity2/fetch_products.html
@@ -0,0 +1,14 @@
+
+
+
+ Fetch API
+
+
+
+
+
+ Data
+
+
+
+
diff --git a/Activity2/fetch_products.js b/Activity2/fetch_products.js
new file mode 100644
index 0000000..68adde3
--- /dev/null
+++ b/Activity2/fetch_products.js
@@ -0,0 +1,34 @@
+async function getProducts(){
+ const url= "https://fakestoreapi.com/products"
+
+ try{
+ let response = await fetch(url);
+ return await response.json();
+ }catch(error){
+ console.log(error)
+ }
+}
+async function renderProducts() {
+ let products = await getProducts();
+ let html = '';
+ let i=0
+ products.forEach(product => {
+ if(i<10){
+ let htmlSegment = `
+
${product.id} ${product.title}
+
+
+
+
Price:${product.price}
+
Description:${product.description}
+
`;
+
+ html += htmlSegment;
+ i++;
+ }
+ });
+
+ document.getElementById("products").innerHTML = html;
+ }
+
+renderProducts();
diff --git a/Day-2_Activity1/activity_day2.html b/Day-2_Activity1/activity_day2.html
new file mode 100644
index 0000000..424a3a2
--- /dev/null
+++ b/Day-2_Activity1/activity_day2.html
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Day-2_Activity1/activity_day2.js b/Day-2_Activity1/activity_day2.js
new file mode 100644
index 0000000..26a4520
--- /dev/null
+++ b/Day-2_Activity1/activity_day2.js
@@ -0,0 +1,73 @@
+
+const {Component,mount,xml,useState,useEnv,reactive}=owl
+
+const finalData=()=>{
+ const counter=useEnv();
+ return useState(counter.store)
+}
+
+class dataList{
+ count=0;
+ updateCount(){ this.count++; }
+ getCount(){ return this.count; }
+}
+
+
+class Second extends Component{
+ static template=xml`
+
+ Please Guess the number:
+
+
+ No. of Trials:
+
+ `;
+ static props=["x"]
+
+ setup(){
+ this.counter=finalData();
+ }
+ check(evt){
+ this.counter.updateCount();
+
+ let num=evt.target.value;
+ let x=this.props.x.x;
+ let result=document.getElementById("result")
+ if(num x){
+ result.innerHTML="HIGHER THAN GUESS";
+ }
+ else{
+ result.innerHTML="CORRECT GUESS";
+ result.style.color='green'
+ }
+ document.getElementById("trials").innerHTML=this.counter.getCount();
+
+ }
+
+
+}
+
+class Root extends Component{
+ static template=xml`Computer Guess:
+
+
+ `;
+ setup(){
+ let x = Math.floor((Math.random() * 10) + 1);
+ console.log(x)
+ this.data=useState({x:x})
+
+ }
+
+ static components={Second};
+
+}
+const createData=()=>{
+ return reactive(new dataList);
+}
+
+const env={store:createData()}
+mount(Root,document.body,{dev:true,env});
\ No newline at end of file
diff --git a/Day-2_Activity1/owl.js b/Day-2_Activity1/owl.js
new file mode 100644
index 0000000..b07d8c8
--- /dev/null
+++ b/Day-2_Activity1/owl.js
@@ -0,0 +1,5938 @@
+(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 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 = () => { };
+ 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("");
+ }
+ 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 element
+ const fragment = document.createElement("template").content;
+ fragment.appendChild(el);
+ }
+ for (let i = 0; i < attrs.length; i++) {
+ const attrName = attrs[i].name;
+ const attrValue = attrs[i].value;
+ if (attrName.startsWith("block-handler-")) {
+ const idx = parseInt(attrName.slice(14), 10);
+ info.push({
+ type: "handler",
+ idx,
+ event: attrValue,
+ });
+ }
+ else if (attrName.startsWith("block-attribute-")) {
+ const idx = parseInt(attrName.slice(16), 10);
+ info.push({
+ type: "attribute",
+ idx,
+ name: attrValue,
+ tag: tagName,
+ });
+ }
+ else if (attrName.startsWith("block-property-")) {
+ const idx = parseInt(attrName.slice(15), 10);
+ info.push({
+ type: "property",
+ idx,
+ name: attrValue,
+ tag: tagName,
+ });
+ }
+ else if (attrName === "block-attributes") {
+ info.push({
+ type: "attributes",
+ idx: parseInt(attrValue, 10),
+ });
+ }
+ else if (attrName === "block-ref") {
+ info.push({
+ type: "ref",
+ idx: parseInt(attrValue, 10),
+ });
+ }
+ else {
+ el.setAttribute(attrs[i].name, attrValue);
+ }
+ }
+ }
+ const tree = {
+ parent,
+ firstChild: null,
+ nextSibling: null,
+ el,
+ info,
+ refN: 0,
+ currentNS,
+ };
+ if (node.firstChild) {
+ const childNode = node.childNodes[0];
+ if (node.childNodes.length === 1 &&
+ childNode.nodeType === Node.ELEMENT_NODE &&
+ childNode.tagName.startsWith("block-child-")) {
+ const tagName = childNode.tagName;
+ const index = parseInt(tagName.slice(12), 10);
+ info.push({ idx: index, type: "child", isOnlyChild: true });
+ }
+ else {
+ tree.firstChild = buildTree(node.firstChild, tree, tree);
+ el.appendChild(tree.firstChild.el);
+ let curNode = node.firstChild;
+ let curTree = tree.firstChild;
+ while ((curNode = curNode.nextSibling)) {
+ curTree.nextSibling = buildTree(curNode, curTree, tree);
+ el.appendChild(curTree.nextSibling.el);
+ curTree = curTree.nextSibling;
+ }
+ }
+ }
+ if (tree.info.length) {
+ addRef(tree);
+ }
+ return tree;
+ }
+ case Node.TEXT_NODE:
+ case Node.COMMENT_NODE: {
+ // text node or comment node
+ const el = node.nodeType === Node.TEXT_NODE
+ ? document.createTextNode(node.textContent)
+ : document.createComment(node.textContent);
+ return {
+ parent: parent,
+ firstChild: null,
+ nextSibling: null,
+ el,
+ info: [],
+ refN: 0,
+ currentNS: null,
+ };
+ }
+ }
+ throw new OwlError("boom");
+ }
+ function addRef(tree) {
+ tree.isRef = true;
+ do {
+ tree.refN++;
+ } while ((tree = tree.parent));
+ }
+ function parentTree(tree) {
+ let parent = tree.parent;
+ while (parent && parent.nextSibling === tree) {
+ tree = parent;
+ parent = parent.parent;
+ }
+ return parent;
+ }
+ function buildContext(tree, ctx, fromIdx) {
+ if (!ctx) {
+ const children = new Array(tree.info.filter((v) => v.type === "child").length);
+ ctx = { collectors: [], locations: [], children, cbRefs: [], refN: tree.refN, refList: [] };
+ fromIdx = 0;
+ }
+ if (tree.refN) {
+ const initialIdx = fromIdx;
+ const isRef = tree.isRef;
+ const firstChild = tree.firstChild ? tree.firstChild.refN : 0;
+ const nextSibling = tree.nextSibling ? tree.nextSibling.refN : 0;
+ //node
+ if (isRef) {
+ for (let info of tree.info) {
+ info.refIdx = initialIdx;
+ }
+ tree.refIdx = initialIdx;
+ updateCtx(ctx, tree);
+ fromIdx++;
+ }
+ // right
+ if (nextSibling) {
+ const idx = fromIdx + firstChild;
+ ctx.collectors.push({ idx, prevIdx: initialIdx, getVal: nodeGetNextSibling });
+ buildContext(tree.nextSibling, ctx, idx);
+ }
+ // left
+ if (firstChild) {
+ ctx.collectors.push({ idx: fromIdx, prevIdx: initialIdx, getVal: nodeGetFirstChild });
+ buildContext(tree.firstChild, ctx, fromIdx);
+ }
+ }
+ return ctx;
+ }
+ function updateCtx(ctx, tree) {
+ for (let info of tree.info) {
+ switch (info.type) {
+ case "text":
+ ctx.locations.push({
+ idx: info.idx,
+ refIdx: info.refIdx,
+ setData: setText,
+ updateData: setText,
+ });
+ break;
+ case "child":
+ if (info.isOnlyChild) {
+ // tree is the parentnode here
+ ctx.children[info.idx] = {
+ parentRefIdx: info.refIdx,
+ isOnlyChild: true,
+ };
+ }
+ else {
+ // tree is the anchor text node
+ ctx.children[info.idx] = {
+ parentRefIdx: parentTree(tree).refIdx,
+ afterRefIdx: info.refIdx,
+ };
+ }
+ break;
+ case "property": {
+ const refIdx = info.refIdx;
+ const setProp = makePropSetter(info.name);
+ ctx.locations.push({
+ idx: info.idx,
+ refIdx,
+ setData: setProp,
+ updateData: setProp,
+ });
+ break;
+ }
+ case "attribute": {
+ const refIdx = info.refIdx;
+ let updater;
+ let setter;
+ if (info.name === "class") {
+ setter = setClass;
+ updater = updateClass;
+ }
+ else {
+ setter = createAttrUpdater(info.name);
+ updater = setter;
+ }
+ ctx.locations.push({
+ idx: info.idx,
+ refIdx,
+ setData: setter,
+ updateData: updater,
+ });
+ break;
+ }
+ case "attributes":
+ ctx.locations.push({
+ idx: info.idx,
+ refIdx: info.refIdx,
+ setData: attrsSetter,
+ updateData: attrsUpdater,
+ });
+ break;
+ case "handler": {
+ const { setup, update } = createEventHandler(info.event);
+ ctx.locations.push({
+ idx: info.idx,
+ refIdx: info.refIdx,
+ setData: setup,
+ updateData: update,
+ });
+ break;
+ }
+ case "ref":
+ const index = ctx.cbRefs.push(info.idx) - 1;
+ ctx.locations.push({
+ idx: info.idx,
+ refIdx: info.refIdx,
+ setData: makeRefSetter(index, ctx.refList),
+ updateData: NO_OP,
+ });
+ }
+ }
+ }
+ // -----------------------------------------------------------------------------
+ // building the concrete block class
+ // -----------------------------------------------------------------------------
+ function buildBlock(template, ctx) {
+ let B = createBlockClass(template, ctx);
+ if (ctx.cbRefs.length) {
+ const cbRefs = ctx.cbRefs;
+ const refList = ctx.refList;
+ let cbRefsNumber = cbRefs.length;
+ B = class extends B {
+ mount(parent, afterNode) {
+ refList.push(new Array(cbRefsNumber));
+ super.mount(parent, afterNode);
+ for (let cbRef of refList.pop()) {
+ cbRef();
+ }
+ }
+ remove() {
+ super.remove();
+ for (let cbRef of cbRefs) {
+ let fn = this.data[cbRef];
+ fn(null);
+ }
+ }
+ };
+ }
+ if (ctx.children.length) {
+ B = class extends B {
+ constructor(data, children) {
+ super(data);
+ this.children = children;
+ }
+ };
+ B.prototype.beforeRemove = VMulti.prototype.beforeRemove;
+ return (data, children = []) => new B(data, children);
+ }
+ return (data) => new B(data);
+ }
+ function createBlockClass(template, ctx) {
+ const { refN, collectors, children } = ctx;
+ const colN = collectors.length;
+ ctx.locations.sort((a, b) => a.idx - b.idx);
+ const locations = ctx.locations.map((loc) => ({
+ refIdx: loc.refIdx,
+ setData: loc.setData,
+ updateData: loc.updateData,
+ }));
+ const locN = locations.length;
+ const childN = children.length;
+ const childrenLocs = children;
+ const isDynamic = refN > 0;
+ // these values are defined here to make them faster to lookup in the class
+ // block scope
+ const nodeCloneNode = nodeProto$2.cloneNode;
+ const nodeInsertBefore = nodeProto$2.insertBefore;
+ const elementRemove = elementProto.remove;
+ class Block {
+ constructor(data) {
+ this.data = data;
+ }
+ beforeRemove() { }
+ remove() {
+ elementRemove.call(this.el);
+ }
+ firstNode() {
+ return this.el;
+ }
+ moveBeforeDOMNode(node, parent = this.parentEl) {
+ this.parentEl = parent;
+ nodeInsertBefore.call(parent, this.el, node);
+ }
+ moveBeforeVNode(other, afterNode) {
+ nodeInsertBefore.call(this.parentEl, this.el, other ? other.el : afterNode);
+ }
+ toString() {
+ const div = document.createElement("div");
+ this.mount(div, null);
+ return div.innerHTML;
+ }
+ mount(parent, afterNode) {
+ const el = nodeCloneNode.call(template, true);
+ nodeInsertBefore.call(parent, el, afterNode);
+ this.el = el;
+ this.parentEl = parent;
+ }
+ patch(other, withBeforeRemove) { }
+ }
+ if (isDynamic) {
+ Block.prototype.mount = function mount(parent, afterNode) {
+ const el = nodeCloneNode.call(template, true);
+ // collecting references
+ const refs = new Array(refN);
+ this.refs = refs;
+ refs[0] = el;
+ for (let i = 0; i < colN; i++) {
+ const w = collectors[i];
+ refs[w.idx] = w.getVal.call(refs[w.prevIdx]);
+ }
+ // applying data to all update points
+ if (locN) {
+ const data = this.data;
+ for (let i = 0; i < locN; i++) {
+ const loc = locations[i];
+ loc.setData.call(refs[loc.refIdx], data[i]);
+ }
+ }
+ nodeInsertBefore.call(parent, el, afterNode);
+ // preparing all children
+ if (childN) {
+ const children = this.children;
+ for (let i = 0; i < childN; i++) {
+ const child = children[i];
+ if (child) {
+ const loc = childrenLocs[i];
+ const afterNode = loc.afterRefIdx ? refs[loc.afterRefIdx] : null;
+ child.isOnlyChild = loc.isOnlyChild;
+ child.mount(refs[loc.parentRefIdx], afterNode);
+ }
+ }
+ }
+ this.el = el;
+ this.parentEl = parent;
+ };
+ Block.prototype.patch = function patch(other, withBeforeRemove) {
+ if (this === other) {
+ return;
+ }
+ const refs = this.refs;
+ // update texts/attributes/
+ if (locN) {
+ const data1 = this.data;
+ const data2 = other.data;
+ for (let i = 0; i < locN; i++) {
+ const val1 = data1[i];
+ const val2 = data2[i];
+ if (val1 !== val2) {
+ const loc = locations[i];
+ loc.updateData.call(refs[loc.refIdx], val2, val1);
+ }
+ }
+ this.data = data2;
+ }
+ // update children
+ if (childN) {
+ let children1 = this.children;
+ const children2 = other.children;
+ for (let i = 0; i < childN; i++) {
+ const child1 = children1[i];
+ const child2 = children2[i];
+ if (child1) {
+ if (child2) {
+ child1.patch(child2, withBeforeRemove);
+ }
+ else {
+ if (withBeforeRemove) {
+ child1.beforeRemove();
+ }
+ child1.remove();
+ children1[i] = undefined;
+ }
+ }
+ else if (child2) {
+ const loc = childrenLocs[i];
+ const afterNode = loc.afterRefIdx ? refs[loc.afterRefIdx] : null;
+ child2.mount(refs[loc.parentRefIdx], afterNode);
+ children1[i] = child2;
+ }
+ }
+ }
+ };
+ }
+ return Block;
+ }
+ function setText(value) {
+ characterDataSetData.call(this, toText(value));
+ }
+ function makeRefSetter(index, refs) {
+ return function setRef(fn) {
+ refs[refs.length - 1][index] = () => fn(this);
+ };
+ }
+
+ const getDescriptor = (o, p) => Object.getOwnPropertyDescriptor(o, p);
+ const nodeProto$1 = Node.prototype;
+ const nodeInsertBefore$1 = nodeProto$1.insertBefore;
+ const nodeAppendChild = nodeProto$1.appendChild;
+ const nodeRemoveChild$1 = nodeProto$1.removeChild;
+ const nodeSetTextContent = getDescriptor(nodeProto$1, "textContent").set;
+ // -----------------------------------------------------------------------------
+ // List Node
+ // -----------------------------------------------------------------------------
+ class VList {
+ constructor(children) {
+ this.children = children;
+ }
+ mount(parent, afterNode) {
+ const children = this.children;
+ const _anchor = document.createTextNode("");
+ this.anchor = _anchor;
+ nodeInsertBefore$1.call(parent, _anchor, afterNode);
+ const l = children.length;
+ if (l) {
+ const mount = children[0].mount;
+ for (let i = 0; i < l; i++) {
+ mount.call(children[i], parent, _anchor);
+ }
+ }
+ this.parentEl = parent;
+ }
+ moveBeforeDOMNode(node, parent = this.parentEl) {
+ this.parentEl = parent;
+ const children = this.children;
+ for (let i = 0, l = children.length; i < l; i++) {
+ children[i].moveBeforeDOMNode(node, parent);
+ }
+ parent.insertBefore(this.anchor, node);
+ }
+ moveBeforeVNode(other, afterNode) {
+ if (other) {
+ const next = other.children[0];
+ afterNode = (next ? next.firstNode() : other.anchor) || null;
+ }
+ const children = this.children;
+ for (let i = 0, l = children.length; i < l; i++) {
+ children[i].moveBeforeVNode(null, afterNode);
+ }
+ this.parentEl.insertBefore(this.anchor, afterNode);
+ }
+ patch(other, withBeforeRemove) {
+ if (this === other) {
+ return;
+ }
+ const ch1 = this.children;
+ const ch2 = other.children;
+ if (ch2.length === 0 && ch1.length === 0) {
+ return;
+ }
+ this.children = ch2;
+ const proto = ch2[0] || ch1[0];
+ const { mount: cMount, patch: cPatch, remove: cRemove, beforeRemove, moveBeforeVNode: cMoveBefore, firstNode: cFirstNode, } = proto;
+ const _anchor = this.anchor;
+ const isOnlyChild = this.isOnlyChild;
+ const parent = this.parentEl;
+ // fast path: no new child => only remove
+ if (ch2.length === 0 && isOnlyChild) {
+ if (withBeforeRemove) {
+ for (let i = 0, l = ch1.length; i < l; i++) {
+ beforeRemove.call(ch1[i]);
+ }
+ }
+ nodeSetTextContent.call(parent, "");
+ nodeAppendChild.call(parent, _anchor);
+ return;
+ }
+ let startIdx1 = 0;
+ let startIdx2 = 0;
+ let startVn1 = ch1[0];
+ let startVn2 = ch2[0];
+ let endIdx1 = ch1.length - 1;
+ let endIdx2 = ch2.length - 1;
+ let endVn1 = ch1[endIdx1];
+ let endVn2 = ch2[endIdx2];
+ let mapping = undefined;
+ while (startIdx1 <= endIdx1 && startIdx2 <= endIdx2) {
+ // -------------------------------------------------------------------
+ if (startVn1 === null) {
+ startVn1 = ch1[++startIdx1];
+ continue;
+ }
+ // -------------------------------------------------------------------
+ if (endVn1 === null) {
+ endVn1 = ch1[--endIdx1];
+ continue;
+ }
+ // -------------------------------------------------------------------
+ let startKey1 = startVn1.key;
+ let startKey2 = startVn2.key;
+ if (startKey1 === startKey2) {
+ cPatch.call(startVn1, startVn2, withBeforeRemove);
+ ch2[startIdx2] = startVn1;
+ startVn1 = ch1[++startIdx1];
+ startVn2 = ch2[++startIdx2];
+ continue;
+ }
+ // -------------------------------------------------------------------
+ let endKey1 = endVn1.key;
+ let endKey2 = endVn2.key;
+ if (endKey1 === endKey2) {
+ cPatch.call(endVn1, endVn2, withBeforeRemove);
+ ch2[endIdx2] = endVn1;
+ endVn1 = ch1[--endIdx1];
+ endVn2 = ch2[--endIdx2];
+ continue;
+ }
+ // -------------------------------------------------------------------
+ if (startKey1 === endKey2) {
+ // bnode moved right
+ cPatch.call(startVn1, endVn2, withBeforeRemove);
+ ch2[endIdx2] = startVn1;
+ const nextChild = ch2[endIdx2 + 1];
+ cMoveBefore.call(startVn1, nextChild, _anchor);
+ startVn1 = ch1[++startIdx1];
+ endVn2 = ch2[--endIdx2];
+ continue;
+ }
+ // -------------------------------------------------------------------
+ if (endKey1 === startKey2) {
+ // bnode moved left
+ cPatch.call(endVn1, startVn2, withBeforeRemove);
+ ch2[startIdx2] = endVn1;
+ const nextChild = ch1[startIdx1];
+ cMoveBefore.call(endVn1, nextChild, _anchor);
+ endVn1 = ch1[--endIdx1];
+ startVn2 = ch2[++startIdx2];
+ continue;
+ }
+ // -------------------------------------------------------------------
+ mapping = mapping || createMapping(ch1, startIdx1, endIdx1);
+ let idxInOld = mapping[startKey2];
+ if (idxInOld === undefined) {
+ cMount.call(startVn2, parent, cFirstNode.call(startVn1) || null);
+ }
+ else {
+ const elmToMove = ch1[idxInOld];
+ cMoveBefore.call(elmToMove, startVn1, null);
+ cPatch.call(elmToMove, startVn2, withBeforeRemove);
+ ch2[startIdx2] = elmToMove;
+ ch1[idxInOld] = null;
+ }
+ startVn2 = ch2[++startIdx2];
+ }
+ // ---------------------------------------------------------------------
+ if (startIdx1 <= endIdx1 || startIdx2 <= endIdx2) {
+ if (startIdx1 > endIdx1) {
+ const nextChild = ch2[endIdx2 + 1];
+ const anchor = nextChild ? cFirstNode.call(nextChild) || null : _anchor;
+ for (let i = startIdx2; i <= endIdx2; i++) {
+ cMount.call(ch2[i], parent, anchor);
+ }
+ }
+ else {
+ for (let i = startIdx1; i <= endIdx1; i++) {
+ let ch = ch1[i];
+ if (ch) {
+ if (withBeforeRemove) {
+ beforeRemove.call(ch);
+ }
+ cRemove.call(ch);
+ }
+ }
+ }
+ }
+ }
+ beforeRemove() {
+ const children = this.children;
+ const l = children.length;
+ if (l) {
+ const beforeRemove = children[0].beforeRemove;
+ for (let i = 0; i < l; i++) {
+ beforeRemove.call(children[i]);
+ }
+ }
+ }
+ remove() {
+ const { parentEl, anchor } = this;
+ if (this.isOnlyChild) {
+ nodeSetTextContent.call(parentEl, "");
+ }
+ else {
+ const children = this.children;
+ const l = children.length;
+ if (l) {
+ const remove = children[0].remove;
+ for (let i = 0; i < l; i++) {
+ remove.call(children[i]);
+ }
+ }
+ nodeRemoveChild$1.call(parentEl, anchor);
+ }
+ }
+ firstNode() {
+ const child = this.children[0];
+ return child ? child.firstNode() : undefined;
+ }
+ toString() {
+ return this.children.map((c) => c.toString()).join("");
+ }
+ }
+ function list(children) {
+ return new VList(children);
+ }
+ function createMapping(ch1, startIdx1, endIdx2) {
+ let mapping = {};
+ for (let i = startIdx1; i <= endIdx2; i++) {
+ mapping[ch1[i].key] = i;
+ }
+ return mapping;
+ }
+
+ const nodeProto = Node.prototype;
+ const nodeInsertBefore = nodeProto.insertBefore;
+ const nodeRemoveChild = nodeProto.removeChild;
+ class VHtml {
+ constructor(html) {
+ this.content = [];
+ this.html = html;
+ }
+ mount(parent, afterNode) {
+ this.parentEl = parent;
+ const template = document.createElement("template");
+ template.innerHTML = this.html;
+ this.content = [...template.content.childNodes];
+ for (let elem of this.content) {
+ nodeInsertBefore.call(parent, elem, afterNode);
+ }
+ if (!this.content.length) {
+ const textNode = document.createTextNode("");
+ this.content.push(textNode);
+ nodeInsertBefore.call(parent, textNode, afterNode);
+ }
+ }
+ moveBeforeDOMNode(node, parent = this.parentEl) {
+ this.parentEl = parent;
+ for (let elem of this.content) {
+ nodeInsertBefore.call(parent, elem, node);
+ }
+ }
+ moveBeforeVNode(other, afterNode) {
+ const target = other ? other.content[0] : afterNode;
+ this.moveBeforeDOMNode(target);
+ }
+ patch(other) {
+ if (this === other) {
+ return;
+ }
+ const html2 = other.html;
+ if (this.html !== html2) {
+ const parent = this.parentEl;
+ // insert new html in front of current
+ const afterNode = this.content[0];
+ const template = document.createElement("template");
+ template.innerHTML = html2;
+ const content = [...template.content.childNodes];
+ for (let elem of content) {
+ nodeInsertBefore.call(parent, elem, afterNode);
+ }
+ if (!content.length) {
+ const textNode = document.createTextNode("");
+ content.push(textNode);
+ nodeInsertBefore.call(parent, textNode, afterNode);
+ }
+ // remove current content
+ this.remove();
+ this.content = content;
+ this.html = other.html;
+ }
+ }
+ beforeRemove() { }
+ remove() {
+ const parent = this.parentEl;
+ for (let elem of this.content) {
+ nodeRemoveChild.call(parent, elem);
+ }
+ }
+ firstNode() {
+ return this.content[0];
+ }
+ toString() {
+ return this.html;
+ }
+ }
+ function html(str) {
+ return new VHtml(str);
+ }
+
+ function createCatcher(eventsSpec) {
+ const n = Object.keys(eventsSpec).length;
+ class VCatcher {
+ constructor(child, handlers) {
+ this.handlerFns = [];
+ this.afterNode = null;
+ this.child = child;
+ this.handlerData = handlers;
+ }
+ mount(parent, afterNode) {
+ this.parentEl = parent;
+ this.child.mount(parent, afterNode);
+ this.afterNode = document.createTextNode("");
+ parent.insertBefore(this.afterNode, afterNode);
+ this.wrapHandlerData();
+ for (let name in eventsSpec) {
+ const index = eventsSpec[name];
+ const handler = createEventHandler(name);
+ this.handlerFns[index] = handler;
+ handler.setup.call(parent, this.handlerData[index]);
+ }
+ }
+ wrapHandlerData() {
+ for (let i = 0; i < n; i++) {
+ let handler = this.handlerData[i];
+ // handler = [...mods, fn, comp], so we need to replace second to last elem
+ let idx = handler.length - 2;
+ let origFn = handler[idx];
+ const self = this;
+ handler[idx] = function (ev) {
+ const target = ev.target;
+ let currentNode = self.child.firstNode();
+ const afterNode = self.afterNode;
+ while (currentNode && currentNode !== afterNode) {
+ if (currentNode.contains(target)) {
+ return origFn.call(this, ev);
+ }
+ currentNode = currentNode.nextSibling;
+ }
+ };
+ }
+ }
+ moveBeforeDOMNode(node, parent = this.parentEl) {
+ this.parentEl = parent;
+ this.child.moveBeforeDOMNode(node, parent);
+ parent.insertBefore(this.afterNode, node);
+ }
+ moveBeforeVNode(other, afterNode) {
+ if (other) {
+ // check this with @ged-odoo for use in foreach
+ afterNode = other.firstNode() || afterNode;
+ }
+ this.child.moveBeforeVNode(other ? other.child : null, afterNode);
+ this.parentEl.insertBefore(this.afterNode, afterNode);
+ }
+ patch(other, withBeforeRemove) {
+ if (this === other) {
+ return;
+ }
+ this.handlerData = other.handlerData;
+ this.wrapHandlerData();
+ for (let i = 0; i < n; i++) {
+ this.handlerFns[i].update.call(this.parentEl, this.handlerData[i]);
+ }
+ this.child.patch(other.child, withBeforeRemove);
+ }
+ beforeRemove() {
+ this.child.beforeRemove();
+ }
+ remove() {
+ for (let i = 0; i < n; i++) {
+ this.handlerFns[i].remove.call(this.parentEl);
+ }
+ this.child.remove();
+ this.afterNode.remove();
+ }
+ firstNode() {
+ return this.child.firstNode();
+ }
+ toString() {
+ return this.child.toString();
+ }
+ }
+ return function (child, handlers) {
+ return new VCatcher(child, handlers);
+ };
+ }
+
+ function mount$1(vnode, fixture, afterNode = null) {
+ vnode.mount(fixture, afterNode);
+ }
+ function patch(vnode1, vnode2, withBeforeRemove = false) {
+ vnode1.patch(vnode2, withBeforeRemove);
+ }
+ function remove(vnode, withBeforeRemove = false) {
+ if (withBeforeRemove) {
+ vnode.beforeRemove();
+ }
+ vnode.remove();
+ }
+
+ function makeChildFiber(node, parent) {
+ let current = node.fiber;
+ if (current) {
+ cancelFibers(current.children);
+ current.root = null;
+ }
+ return new Fiber(node, parent);
+ }
+ function makeRootFiber(node) {
+ let current = node.fiber;
+ if (current) {
+ let root = current.root;
+ // lock root fiber because canceling children fibers may destroy components,
+ // which means any arbitrary code can be run in onWillDestroy, which may
+ // trigger new renderings
+ root.locked = true;
+ root.setCounter(root.counter + 1 - cancelFibers(current.children));
+ root.locked = false;
+ current.children = [];
+ current.childrenMap = {};
+ current.bdom = null;
+ if (fibersInError.has(current)) {
+ fibersInError.delete(current);
+ fibersInError.delete(root);
+ current.appliedToDom = false;
+ }
+ return current;
+ }
+ const fiber = new RootFiber(node, null);
+ if (node.willPatch.length) {
+ fiber.willPatch.push(fiber);
+ }
+ if (node.patched.length) {
+ fiber.patched.push(fiber);
+ }
+ return fiber;
+ }
+ function throwOnRender() {
+ throw new OwlError("Attempted to render cancelled fiber");
+ }
+ /**
+ * @returns number of not-yet rendered fibers cancelled
+ */
+ function cancelFibers(fibers) {
+ let result = 0;
+ for (let fiber of fibers) {
+ let node = fiber.node;
+ fiber.render = throwOnRender;
+ if (node.status === 0 /* NEW */) {
+ node.destroy();
+ delete node.parent.children[node.parentKey];
+ }
+ node.fiber = null;
+ if (fiber.bdom) {
+ // if fiber has been rendered, this means that the component props have
+ // been updated. however, this fiber will not be patched to the dom, so
+ // it could happen that the next render compare the current props with
+ // the same props, and skip the render completely. With the next line,
+ // we kindly request the component code to force a render, so it works as
+ // expected.
+ node.forceNextRender = true;
+ }
+ else {
+ result++;
+ }
+ result += cancelFibers(fiber.children);
+ }
+ return result;
+ }
+ class Fiber {
+ constructor(node, parent) {
+ this.bdom = null;
+ this.children = [];
+ this.appliedToDom = false;
+ this.deep = false;
+ this.childrenMap = {};
+ this.node = node;
+ this.parent = parent;
+ if (parent) {
+ this.deep = parent.deep;
+ const root = parent.root;
+ root.setCounter(root.counter + 1);
+ this.root = root;
+ parent.children.push(this);
+ }
+ else {
+ this.root = this;
+ }
+ }
+ render() {
+ // if some parent has a fiber => register in followup
+ let prev = this.root.node;
+ let scheduler = prev.app.scheduler;
+ let current = prev.parent;
+ while (current) {
+ if (current.fiber) {
+ let root = current.fiber.root;
+ if (root.counter === 0 && prev.parentKey in current.fiber.childrenMap) {
+ current = root.node;
+ }
+ else {
+ scheduler.delayedRenders.push(this);
+ return;
+ }
+ }
+ prev = current;
+ current = current.parent;
+ }
+ // there are no current rendering from above => we can render
+ this._render();
+ }
+ _render() {
+ const node = this.node;
+ const root = this.root;
+ if (root) {
+ try {
+ this.bdom = true;
+ this.bdom = node.renderFn();
+ }
+ catch (e) {
+ node.app.handleError({ node, error: e });
+ }
+ root.setCounter(root.counter - 1);
+ }
+ }
+ }
+ class RootFiber extends Fiber {
+ constructor() {
+ super(...arguments);
+ this.counter = 1;
+ // only add stuff in this if they have registered some hooks
+ this.willPatch = [];
+ this.patched = [];
+ this.mounted = [];
+ // A fiber is typically locked when it is completing and the patch has not, or is being applied.
+ // i.e.: render triggered in onWillUnmount or in willPatch will be delayed
+ this.locked = false;
+ }
+ complete() {
+ const node = this.node;
+ this.locked = true;
+ let current = undefined;
+ try {
+ // Step 1: calling all willPatch lifecycle hooks
+ for (current of this.willPatch) {
+ // because of the asynchronous nature of the rendering, some parts of the
+ // UI may have been rendered, then deleted in a followup rendering, and we
+ // do not want to call onWillPatch in that case.
+ let node = current.node;
+ if (node.fiber === current) {
+ const component = node.component;
+ for (let cb of node.willPatch) {
+ cb.call(component);
+ }
+ }
+ }
+ current = undefined;
+ // Step 2: patching the dom
+ node._patch();
+ this.locked = false;
+ // Step 4: calling all mounted lifecycle hooks
+ let mountedFibers = this.mounted;
+ while ((current = mountedFibers.pop())) {
+ current = current;
+ if (current.appliedToDom) {
+ for (let cb of current.node.mounted) {
+ cb();
+ }
+ }
+ }
+ // Step 5: calling all patched hooks
+ let patchedFibers = this.patched;
+ while ((current = patchedFibers.pop())) {
+ current = current;
+ if (current.appliedToDom) {
+ for (let cb of current.node.patched) {
+ cb();
+ }
+ }
+ }
+ }
+ catch (e) {
+ this.locked = false;
+ node.app.handleError({ fiber: current || this, error: e });
+ }
+ }
+ setCounter(newValue) {
+ this.counter = newValue;
+ if (newValue === 0) {
+ this.node.app.scheduler.flush();
+ }
+ }
+ }
+ class MountFiber extends RootFiber {
+ constructor(node, target, options = {}) {
+ super(node, null);
+ this.target = target;
+ this.position = options.position || "last-child";
+ }
+ complete() {
+ let current = this;
+ try {
+ const node = this.node;
+ node.children = this.childrenMap;
+ node.app.constructor.validateTarget(this.target);
+ if (node.bdom) {
+ // this is a complicated situation: if we mount a fiber with an existing
+ // bdom, this means that this same fiber was already completed, mounted,
+ // but a crash occurred in some mounted hook. Then, it was handled and
+ // the new rendering is being applied.
+ node.updateDom();
+ }
+ else {
+ node.bdom = this.bdom;
+ if (this.position === "last-child" || this.target.childNodes.length === 0) {
+ mount$1(node.bdom, this.target);
+ }
+ else {
+ const firstChild = this.target.childNodes[0];
+ mount$1(node.bdom, this.target, firstChild);
+ }
+ }
+ // unregistering the fiber before mounted since it can do another render
+ // and that the current rendering is obviously completed
+ node.fiber = null;
+ node.status = 1 /* MOUNTED */;
+ this.appliedToDom = true;
+ let mountedFibers = this.mounted;
+ while ((current = mountedFibers.pop())) {
+ if (current.appliedToDom) {
+ for (let cb of current.node.mounted) {
+ cb();
+ }
+ }
+ }
+ }
+ catch (e) {
+ this.node.app.handleError({ fiber: current, error: e });
+ }
+ }
+ }
+
+ // Special key to subscribe to, to be notified of key creation/deletion
+ const KEYCHANGES = Symbol("Key changes");
+ // Used to specify the absence of a callback, can be used as WeakMap key but
+ // should only be used as a sentinel value and never called.
+ const NO_CALLBACK = () => {
+ throw new Error("Called NO_CALLBACK. Owl is broken, please report this to the maintainers.");
+ };
+ const objectToString = Object.prototype.toString;
+ const objectHasOwnProperty = Object.prototype.hasOwnProperty;
+ const SUPPORTED_RAW_TYPES = new Set(["Object", "Array", "Set", "Map", "WeakMap"]);
+ const COLLECTION_RAWTYPES = new Set(["Set", "Map", "WeakMap"]);
+ /**
+ * extract "RawType" from strings like "[object RawType]" => this lets us ignore
+ * many native objects such as Promise (whose toString is [object Promise])
+ * or Date ([object Date]), while also supporting collections without using
+ * instanceof in a loop
+ *
+ * @param obj the object to check
+ * @returns the raw type of the object
+ */
+ function rawType(obj) {
+ return objectToString.call(toRaw(obj)).slice(8, -1);
+ }
+ /**
+ * Checks whether a given value can be made into a reactive object.
+ *
+ * @param value the value to check
+ * @returns whether the value can be made reactive
+ */
+ function canBeMadeReactive(value) {
+ if (typeof value !== "object") {
+ return false;
+ }
+ return SUPPORTED_RAW_TYPES.has(rawType(value));
+ }
+ /**
+ * Creates a reactive from the given object/callback if possible and returns it,
+ * returns the original object otherwise.
+ *
+ * @param value the value make reactive
+ * @returns a reactive for the given object when possible, the original otherwise
+ */
+ function possiblyReactive(val, cb) {
+ return canBeMadeReactive(val) ? reactive(val, cb) : val;
+ }
+ const skipped = new WeakSet();
+ /**
+ * Mark an object or array so that it is ignored by the reactivity system
+ *
+ * @param value the value to mark
+ * @returns the object itself
+ */
+ function markRaw(value) {
+ skipped.add(value);
+ return value;
+ }
+ /**
+ * Given a reactive objet, return the raw (non reactive) underlying object
+ *
+ * @param value a reactive value
+ * @returns the underlying value
+ */
+ function toRaw(value) {
+ return targets.has(value) ? targets.get(value) : value;
+ }
+ const targetToKeysToCallbacks = new WeakMap();
+ /**
+ * Observes a given key on a target with an callback. The callback will be
+ * called when the given key changes on the target.
+ *
+ * @param target the target whose key should be observed
+ * @param key the key to observe (or Symbol(KEYCHANGES) for key creation
+ * or deletion)
+ * @param callback the function to call when the key changes
+ */
+ function observeTargetKey(target, key, callback) {
+ if (callback === NO_CALLBACK) {
+ return;
+ }
+ if (!targetToKeysToCallbacks.get(target)) {
+ targetToKeysToCallbacks.set(target, new Map());
+ }
+ const keyToCallbacks = targetToKeysToCallbacks.get(target);
+ if (!keyToCallbacks.get(key)) {
+ keyToCallbacks.set(key, new Set());
+ }
+ keyToCallbacks.get(key).add(callback);
+ if (!callbacksToTargets.has(callback)) {
+ callbacksToTargets.set(callback, new Set());
+ }
+ callbacksToTargets.get(callback).add(target);
+ }
+ /**
+ * Notify Reactives that are observing a given target that a key has changed on
+ * the target.
+ *
+ * @param target target whose Reactives should be notified that the target was
+ * changed.
+ * @param key the key that changed (or Symbol `KEYCHANGES` if a key was created
+ * or deleted)
+ */
+ function notifyReactives(target, key) {
+ const keyToCallbacks = targetToKeysToCallbacks.get(target);
+ if (!keyToCallbacks) {
+ return;
+ }
+ const callbacks = keyToCallbacks.get(key);
+ if (!callbacks) {
+ return;
+ }
+ // Loop on copy because clearReactivesForCallback will modify the set in place
+ for (const callback of [...callbacks]) {
+ clearReactivesForCallback(callback);
+ callback();
+ }
+ }
+ const callbacksToTargets = new WeakMap();
+ /**
+ * Clears all subscriptions of the Reactives associated with a given callback.
+ *
+ * @param callback the callback for which the reactives need to be cleared
+ */
+ function clearReactivesForCallback(callback) {
+ const targetsToClear = callbacksToTargets.get(callback);
+ if (!targetsToClear) {
+ return;
+ }
+ for (const target of targetsToClear) {
+ const observedKeys = targetToKeysToCallbacks.get(target);
+ if (!observedKeys) {
+ continue;
+ }
+ for (const [key, callbacks] of observedKeys.entries()) {
+ callbacks.delete(callback);
+ if (!callbacks.size) {
+ observedKeys.delete(key);
+ }
+ }
+ }
+ targetsToClear.clear();
+ }
+ function getSubscriptions(callback) {
+ const targets = callbacksToTargets.get(callback) || [];
+ return [...targets].map((target) => {
+ const keysToCallbacks = targetToKeysToCallbacks.get(target);
+ let keys = [];
+ if (keysToCallbacks) {
+ for (const [key, cbs] of keysToCallbacks) {
+ if (cbs.has(callback)) {
+ keys.push(key);
+ }
+ }
+ }
+ return { target, keys };
+ });
+ }
+ // Maps reactive objects to the underlying target
+ const targets = new WeakMap();
+ const reactiveCache = new WeakMap();
+ /**
+ * Creates a reactive proxy for an object. Reading data on the reactive object
+ * subscribes to changes to the data. Writing data on the object will cause the
+ * notify callback to be called if there are suscriptions to that data. Nested
+ * objects and arrays are automatically made reactive as well.
+ *
+ * Whenever you are notified of a change, all subscriptions are cleared, and if
+ * you would like to be notified of any further changes, you should go read
+ * the underlying data again. We assume that if you don't go read it again after
+ * being notified, it means that you are no longer interested in that data.
+ *
+ * Subscriptions:
+ * + Reading a property on an object will subscribe you to changes in the value
+ * of that property.
+ * + Accessing an object's keys (eg with Object.keys or with `for..in`) will
+ * subscribe you to the creation/deletion of keys. Checking the presence of a
+ * key on the object with 'in' has the same effect.
+ * - getOwnPropertyDescriptor does not currently subscribe you to the property.
+ * This is a choice that was made because changing a key's value will trigger
+ * this trap and we do not want to subscribe by writes. This also means that
+ * Object.hasOwnProperty doesn't subscribe as it goes through this trap.
+ *
+ * @param target the object for which to create a reactive proxy
+ * @param callback the function to call when an observed property of the
+ * reactive has changed
+ * @returns a proxy that tracks changes to it
+ */
+ function reactive(target, callback = NO_CALLBACK) {
+ if (!canBeMadeReactive(target)) {
+ throw new OwlError(`Cannot make the given value reactive`);
+ }
+ if (skipped.has(target)) {
+ return target;
+ }
+ if (targets.has(target)) {
+ // target is reactive, create a reactive on the underlying object instead
+ return reactive(targets.get(target), callback);
+ }
+ if (!reactiveCache.has(target)) {
+ reactiveCache.set(target, new WeakMap());
+ }
+ const reactivesForTarget = reactiveCache.get(target);
+ if (!reactivesForTarget.has(callback)) {
+ const targetRawType = rawType(target);
+ const handler = COLLECTION_RAWTYPES.has(targetRawType)
+ ? collectionsProxyHandler(target, callback, targetRawType)
+ : basicProxyHandler(callback);
+ const proxy = new Proxy(target, handler);
+ reactivesForTarget.set(callback, proxy);
+ targets.set(proxy, target);
+ }
+ return reactivesForTarget.get(callback);
+ }
+ /**
+ * Creates a basic proxy handler for regular objects and arrays.
+ *
+ * @param callback @see reactive
+ * @returns a proxy handler object
+ */
+ function basicProxyHandler(callback) {
+ return {
+ get(target, key, receiver) {
+ // non-writable non-configurable properties cannot be made reactive
+ const desc = Object.getOwnPropertyDescriptor(target, key);
+ if (desc && !desc.writable && !desc.configurable) {
+ return Reflect.get(target, key, receiver);
+ }
+ observeTargetKey(target, key, callback);
+ return possiblyReactive(Reflect.get(target, key, receiver), callback);
+ },
+ set(target, key, value, receiver) {
+ const hadKey = objectHasOwnProperty.call(target, key);
+ const originalValue = Reflect.get(target, key, receiver);
+ const ret = Reflect.set(target, key, value, receiver);
+ if (!hadKey && objectHasOwnProperty.call(target, key)) {
+ notifyReactives(target, KEYCHANGES);
+ }
+ // While Array length may trigger the set trap, it's not actually set by this
+ // method but is updated behind the scenes, and the trap is not called with the
+ // new value. We disable the "same-value-optimization" for it because of that.
+ if (originalValue !== Reflect.get(target, key, receiver) ||
+ (key === "length" && Array.isArray(target))) {
+ notifyReactives(target, key);
+ }
+ return ret;
+ },
+ deleteProperty(target, key) {
+ const ret = Reflect.deleteProperty(target, key);
+ // TODO: only notify when something was actually deleted
+ notifyReactives(target, KEYCHANGES);
+ notifyReactives(target, key);
+ return ret;
+ },
+ ownKeys(target) {
+ observeTargetKey(target, KEYCHANGES, callback);
+ return Reflect.ownKeys(target);
+ },
+ has(target, key) {
+ // TODO: this observes all key changes instead of only the presence of the argument key
+ // observing the key itself would observe value changes instead of presence changes
+ // so we may need a finer grained system to distinguish observing value vs presence.
+ observeTargetKey(target, KEYCHANGES, callback);
+ return Reflect.has(target, key);
+ },
+ };
+ }
+ /**
+ * Creates a function that will observe the key that is passed to it when called
+ * and delegates to the underlying method.
+ *
+ * @param methodName name of the method to delegate to
+ * @param target @see reactive
+ * @param callback @see reactive
+ */
+ function makeKeyObserver(methodName, target, callback) {
+ return (key) => {
+ key = toRaw(key);
+ observeTargetKey(target, key, callback);
+ return possiblyReactive(target[methodName](key), callback);
+ };
+ }
+ /**
+ * Creates an iterable that will delegate to the underlying iteration method and
+ * observe keys as necessary.
+ *
+ * @param methodName name of the method to delegate to
+ * @param target @see reactive
+ * @param callback @see reactive
+ */
+ function makeIteratorObserver(methodName, target, callback) {
+ return function* () {
+ observeTargetKey(target, KEYCHANGES, callback);
+ const keys = target.keys();
+ for (const item of target[methodName]()) {
+ const key = keys.next().value;
+ observeTargetKey(target, key, callback);
+ yield possiblyReactive(item, callback);
+ }
+ };
+ }
+ /**
+ * Creates a forEach function that will delegate to forEach on the underlying
+ * collection while observing key changes, and keys as they're iterated over,
+ * and making the passed keys/values reactive.
+ *
+ * @param target @see reactive
+ * @param callback @see reactive
+ */
+ function makeForEachObserver(target, callback) {
+ return function forEach(forEachCb, thisArg) {
+ observeTargetKey(target, KEYCHANGES, callback);
+ target.forEach(function (val, key, targetObj) {
+ observeTargetKey(target, key, callback);
+ forEachCb.call(thisArg, possiblyReactive(val, callback), possiblyReactive(key, callback), possiblyReactive(targetObj, callback));
+ }, thisArg);
+ };
+ }
+ /**
+ * Creates a function that will delegate to an underlying method, and check if
+ * that method has modified the presence or value of a key, and notify the
+ * reactives appropriately.
+ *
+ * @param setterName name of the method to delegate to
+ * @param getterName name of the method which should be used to retrieve the
+ * value before calling the delegate method for comparison purposes
+ * @param target @see reactive
+ */
+ function delegateAndNotify(setterName, getterName, target) {
+ return (key, value) => {
+ key = toRaw(key);
+ const hadKey = target.has(key);
+ const originalValue = target[getterName](key);
+ const ret = target[setterName](key, value);
+ const hasKey = target.has(key);
+ if (hadKey !== hasKey) {
+ notifyReactives(target, KEYCHANGES);
+ }
+ if (originalValue !== value) {
+ notifyReactives(target, key);
+ }
+ return ret;
+ };
+ }
+ /**
+ * Creates a function that will clear the underlying collection and notify that
+ * the keys of the collection have changed.
+ *
+ * @param target @see reactive
+ */
+ function makeClearNotifier(target) {
+ return () => {
+ const allKeys = [...target.keys()];
+ target.clear();
+ notifyReactives(target, KEYCHANGES);
+ for (const key of allKeys) {
+ notifyReactives(target, key);
+ }
+ };
+ }
+ /**
+ * Maps raw type of an object to an object containing functions that can be used
+ * to build an appropritate proxy handler for that raw type. Eg: when making a
+ * reactive set, calling the has method should mark the key that is being
+ * retrieved as observed, and calling the add or delete method should notify the
+ * reactives that the key which is being added or deleted has been modified.
+ */
+ const rawTypeToFuncHandlers = {
+ Set: (target, callback) => ({
+ has: makeKeyObserver("has", target, callback),
+ add: delegateAndNotify("add", "has", target),
+ delete: delegateAndNotify("delete", "has", target),
+ keys: makeIteratorObserver("keys", target, callback),
+ values: makeIteratorObserver("values", target, callback),
+ entries: makeIteratorObserver("entries", target, callback),
+ [Symbol.iterator]: makeIteratorObserver(Symbol.iterator, target, callback),
+ forEach: makeForEachObserver(target, callback),
+ clear: makeClearNotifier(target),
+ get size() {
+ observeTargetKey(target, KEYCHANGES, callback);
+ return target.size;
+ },
+ }),
+ Map: (target, callback) => ({
+ has: makeKeyObserver("has", target, callback),
+ get: makeKeyObserver("get", target, callback),
+ set: delegateAndNotify("set", "get", target),
+ delete: delegateAndNotify("delete", "has", target),
+ keys: makeIteratorObserver("keys", target, callback),
+ values: makeIteratorObserver("values", target, callback),
+ entries: makeIteratorObserver("entries", target, callback),
+ [Symbol.iterator]: makeIteratorObserver(Symbol.iterator, target, callback),
+ forEach: makeForEachObserver(target, callback),
+ clear: makeClearNotifier(target),
+ get size() {
+ observeTargetKey(target, KEYCHANGES, callback);
+ return target.size;
+ },
+ }),
+ WeakMap: (target, callback) => ({
+ has: makeKeyObserver("has", target, callback),
+ get: makeKeyObserver("get", target, callback),
+ set: delegateAndNotify("set", "get", target),
+ delete: delegateAndNotify("delete", "has", target),
+ }),
+ };
+ /**
+ * Creates a proxy handler for collections (Set/Map/WeakMap)
+ *
+ * @param callback @see reactive
+ * @param target @see reactive
+ * @returns a proxy handler object
+ */
+ function collectionsProxyHandler(target, callback, targetRawType) {
+ // TODO: if performance is an issue we can create the special handlers lazily when each
+ // property is read.
+ const specialHandlers = rawTypeToFuncHandlers[targetRawType](target, callback);
+ return Object.assign(basicProxyHandler(callback), {
+ // FIXME: probably broken when part of prototype chain since we ignore the receiver
+ get(target, key) {
+ if (objectHasOwnProperty.call(specialHandlers, key)) {
+ return specialHandlers[key];
+ }
+ observeTargetKey(target, key, callback);
+ return possiblyReactive(target[key], callback);
+ },
+ });
+ }
+
+ /**
+ * 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 called = false;
+ return async () => {
+ // This await blocks all calls to the callback here, then releases them sequentially
+ // in the next microtick. This line decides the granularity of the batch.
+ await Promise.resolve();
+ if (!called) {
+ called = true;
+ // wait for all calls in this microtick to fall through before resetting "called"
+ // so that only the first call to the batched function calls the original callback.
+ // Schedule this before calling the callback so that calls to the batched function
+ // within the callback will proceed only after resetting called to false, and have
+ // a chance to execute the callback again
+ Promise.resolve().then(() => (called = false));
+ callback();
+ }
+ };
+ }
+ 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) {
+ if (!document.body.contains(target)) {
+ 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);
+ }
+
+ let currentNode = null;
+ function getCurrent() {
+ if (!currentNode) {
+ throw new OwlError("No active component (a hook function should only be called in 'setup')");
+ }
+ return currentNode;
+ }
+ function useComponent() {
+ return currentNode.component;
+ }
+ /**
+ * Apply default props (only top level).
+ */
+ function applyDefaultProps(props, defaultProps) {
+ for (let propName in defaultProps) {
+ if (props[propName] === undefined) {
+ props[propName] = defaultProps[propName];
+ }
+ }
+ }
+ // -----------------------------------------------------------------------------
+ // Integration with reactivity system (useState)
+ // -----------------------------------------------------------------------------
+ const batchedRenderFunctions = new WeakMap();
+ /**
+ * Creates a reactive object that will be observed by the current component.
+ * Reading data from the returned object (eg during rendering) will cause the
+ * component to subscribe to that data and be rerendered when it changes.
+ *
+ * @param state the state to observe
+ * @returns a reactive object that will cause the component to re-render on
+ * relevant changes
+ * @see reactive
+ */
+ function useState(state) {
+ const node = getCurrent();
+ let render = batchedRenderFunctions.get(node);
+ if (!render) {
+ render = batched(node.render.bind(node, false));
+ batchedRenderFunctions.set(node, render);
+ // manual implementation of onWillDestroy to break cyclic dependency
+ node.willDestroy.push(clearReactivesForCallback.bind(null, render));
+ }
+ return reactive(state, render);
+ }
+ class ComponentNode {
+ constructor(C, props, app, parent, parentKey) {
+ this.fiber = null;
+ this.bdom = null;
+ this.status = 0 /* NEW */;
+ this.forceNextRender = false;
+ this.children = Object.create(null);
+ this.refs = {};
+ this.willStart = [];
+ this.willUpdateProps = [];
+ this.willUnmount = [];
+ this.mounted = [];
+ this.willPatch = [];
+ this.patched = [];
+ this.willDestroy = [];
+ currentNode = this;
+ this.app = app;
+ this.parent = parent;
+ this.props = props;
+ this.parentKey = parentKey;
+ const defaultProps = C.defaultProps;
+ props = Object.assign({}, props);
+ if (defaultProps) {
+ applyDefaultProps(props, defaultProps);
+ }
+ const env = (parent && parent.childEnv) || app.env;
+ this.childEnv = env;
+ for (const key in props) {
+ const prop = props[key];
+ if (prop && typeof prop === "object" && targets.has(prop)) {
+ props[key] = useState(prop);
+ }
+ }
+ this.component = new C(props, env, this);
+ const ctx = Object.assign(Object.create(this.component), { this: this.component });
+ this.renderFn = app.getTemplate(C.template).bind(this.component, ctx, this);
+ this.component.setup();
+ currentNode = null;
+ }
+ mountComponent(target, options) {
+ const fiber = new MountFiber(this, target, options);
+ this.app.scheduler.addFiber(fiber);
+ this.initiateRender(fiber);
+ }
+ async initiateRender(fiber) {
+ this.fiber = fiber;
+ if (this.mounted.length) {
+ fiber.root.mounted.push(fiber);
+ }
+ const component = this.component;
+ try {
+ await Promise.all(this.willStart.map((f) => f.call(component)));
+ }
+ catch (e) {
+ this.app.handleError({ node: this, error: e });
+ return;
+ }
+ if (this.status === 0 /* NEW */ && this.fiber === fiber) {
+ fiber.render();
+ }
+ }
+ async render(deep) {
+ let current = this.fiber;
+ if (current && (current.root.locked || current.bdom === true)) {
+ await Promise.resolve();
+ // situation may have changed after the microtask tick
+ current = this.fiber;
+ }
+ if (current) {
+ if (!current.bdom && !fibersInError.has(current)) {
+ if (deep) {
+ // we want the render from this point on to be with deep=true
+ current.deep = deep;
+ }
+ return;
+ }
+ // if current rendering was with deep=true, we want this one to be the same
+ deep = deep || current.deep;
+ }
+ else if (!this.bdom) {
+ return;
+ }
+ const fiber = makeRootFiber(this);
+ fiber.deep = deep;
+ this.fiber = fiber;
+ this.app.scheduler.addFiber(fiber);
+ await Promise.resolve();
+ if (this.status === 2 /* DESTROYED */) {
+ return;
+ }
+ // We only want to actually render the component if the following two
+ // conditions are true:
+ // * this.fiber: it could be null, in which case the render has been cancelled
+ // * (current || !fiber.parent): if current is not null, this means that the
+ // render function was called when a render was already occurring. In this
+ // case, the pending rendering was cancelled, and the fiber needs to be
+ // rendered to complete the work. If current is null, we check that the
+ // fiber has no parent. If that is the case, the fiber was downgraded from
+ // a root fiber to a child fiber in the previous microtick, because it was
+ // embedded in a rendering coming from above, so the fiber will be rendered
+ // in the next microtick anyway, so we should not render it again.
+ if (this.fiber === fiber && (current || !fiber.parent)) {
+ fiber.render();
+ }
+ }
+ destroy() {
+ let shouldRemove = this.status === 1 /* MOUNTED */;
+ this._destroy();
+ if (shouldRemove) {
+ this.bdom.remove();
+ }
+ }
+ _destroy() {
+ const component = this.component;
+ if (this.status === 1 /* MOUNTED */) {
+ for (let cb of this.willUnmount) {
+ cb.call(component);
+ }
+ }
+ for (let child of Object.values(this.children)) {
+ child._destroy();
+ }
+ if (this.willDestroy.length) {
+ try {
+ for (let cb of this.willDestroy) {
+ cb.call(component);
+ }
+ }
+ catch (e) {
+ this.app.handleError({ error: e, node: this });
+ }
+ }
+ this.status = 2 /* DESTROYED */;
+ }
+ async updateAndRender(props, parentFiber) {
+ const rawProps = props;
+ props = Object.assign({}, props);
+ // update
+ const fiber = makeChildFiber(this, parentFiber);
+ this.fiber = fiber;
+ const component = this.component;
+ const defaultProps = component.constructor.defaultProps;
+ if (defaultProps) {
+ applyDefaultProps(props, defaultProps);
+ }
+ currentNode = this;
+ for (const key in props) {
+ const prop = props[key];
+ if (prop && typeof prop === "object" && targets.has(prop)) {
+ props[key] = useState(prop);
+ }
+ }
+ currentNode = null;
+ const prom = Promise.all(this.willUpdateProps.map((f) => f.call(component, props)));
+ await prom;
+ if (fiber !== this.fiber) {
+ return;
+ }
+ component.props = props;
+ this.props = rawProps;
+ fiber.render();
+ const parentRoot = parentFiber.root;
+ if (this.willPatch.length) {
+ parentRoot.willPatch.push(fiber);
+ }
+ if (this.patched.length) {
+ parentRoot.patched.push(fiber);
+ }
+ }
+ /**
+ * Finds a child that has dom that is not yet updated, and update it. This
+ * method is meant to be used only in the context of repatching the dom after
+ * a mounted hook failed and was handled.
+ */
+ updateDom() {
+ if (!this.fiber) {
+ return;
+ }
+ if (this.bdom === this.fiber.bdom) {
+ // If the error was handled by some child component, we need to find it to
+ // apply its change
+ for (let k in this.children) {
+ const child = this.children[k];
+ child.updateDom();
+ }
+ }
+ else {
+ // if we get here, this is the component that handled the error and rerendered
+ // itself, so we can simply patch the dom
+ this.bdom.patch(this.fiber.bdom, false);
+ this.fiber.appliedToDom = true;
+ this.fiber = null;
+ }
+ }
+ /**
+ * Sets a ref to a given HTMLElement.
+ *
+ * @param name the name of the ref to set
+ * @param el the HTMLElement to set the ref to. The ref is not set if the el
+ * is null, but useRef will not return elements that are not in the DOM
+ */
+ setRef(name, el) {
+ if (el) {
+ this.refs[name] = el;
+ }
+ }
+ // ---------------------------------------------------------------------------
+ // Block DOM methods
+ // ---------------------------------------------------------------------------
+ firstNode() {
+ const bdom = this.bdom;
+ return bdom ? bdom.firstNode() : undefined;
+ }
+ mount(parent, anchor) {
+ const bdom = this.fiber.bdom;
+ this.bdom = bdom;
+ bdom.mount(parent, anchor);
+ this.status = 1 /* MOUNTED */;
+ this.fiber.appliedToDom = true;
+ this.children = this.fiber.childrenMap;
+ this.fiber = null;
+ }
+ moveBeforeDOMNode(node, parent) {
+ this.bdom.moveBeforeDOMNode(node, parent);
+ }
+ moveBeforeVNode(other, afterNode) {
+ this.bdom.moveBeforeVNode(other ? other.bdom : null, afterNode);
+ }
+ patch() {
+ if (this.fiber && this.fiber.parent) {
+ // we only patch here renderings coming from above. renderings initiated
+ // by the component will be patched independently in the appropriate
+ // fiber.complete
+ this._patch();
+ }
+ }
+ _patch() {
+ let hasChildren = false;
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
+ for (let _k in this.children) {
+ hasChildren = true;
+ break;
+ }
+ const fiber = this.fiber;
+ this.children = fiber.childrenMap;
+ this.bdom.patch(fiber.bdom, hasChildren);
+ fiber.appliedToDom = true;
+ this.fiber = null;
+ }
+ beforeRemove() {
+ this._destroy();
+ }
+ remove() {
+ this.bdom.remove();
+ }
+ // ---------------------------------------------------------------------------
+ // Some debug helpers
+ // ---------------------------------------------------------------------------
+ get name() {
+ return this.component.constructor.name;
+ }
+ get subscriptions() {
+ const render = batchedRenderFunctions.get(this);
+ return render ? getSubscriptions(render) : [];
+ }
+ }
+
+ const TIMEOUT = Symbol("timeout");
+ function wrapError(fn, hookName) {
+ const error = new OwlError(`The following error occurred in ${hookName}: `);
+ const timeoutError = new OwlError(`${hookName}'s promise hasn't resolved after 3 seconds`);
+ const node = getCurrent();
+ return (...args) => {
+ const onError = (cause) => {
+ error.cause = cause;
+ if (cause instanceof Error) {
+ error.message += `"${cause.message}"`;
+ }
+ else {
+ error.message = `Something that is not an Error was thrown in ${hookName} (see this Error's "cause" property)`;
+ }
+ throw error;
+ };
+ try {
+ const result = fn(...args);
+ if (result instanceof Promise) {
+ if (hookName === "onWillStart" || hookName === "onWillUpdateProps") {
+ const fiber = node.fiber;
+ Promise.race([
+ result.catch(() => { }),
+ new Promise((resolve) => setTimeout(() => resolve(TIMEOUT), 3000)),
+ ]).then((res) => {
+ if (res === TIMEOUT && node.fiber === fiber) {
+ console.warn(timeoutError);
+ }
+ });
+ }
+ return result.catch(onError);
+ }
+ return result;
+ }
+ catch (cause) {
+ onError(cause);
+ }
+ };
+ }
+ // -----------------------------------------------------------------------------
+ // hooks
+ // -----------------------------------------------------------------------------
+ function onWillStart(fn) {
+ const node = getCurrent();
+ const decorate = node.app.dev ? wrapError : (fn) => fn;
+ node.willStart.push(decorate(fn.bind(node.component), "onWillStart"));
+ }
+ function onWillUpdateProps(fn) {
+ const node = getCurrent();
+ const decorate = node.app.dev ? wrapError : (fn) => fn;
+ node.willUpdateProps.push(decorate(fn.bind(node.component), "onWillUpdateProps"));
+ }
+ function onMounted(fn) {
+ const node = getCurrent();
+ const decorate = node.app.dev ? wrapError : (fn) => fn;
+ node.mounted.push(decorate(fn.bind(node.component), "onMounted"));
+ }
+ function onWillPatch(fn) {
+ const node = getCurrent();
+ const decorate = node.app.dev ? wrapError : (fn) => fn;
+ node.willPatch.unshift(decorate(fn.bind(node.component), "onWillPatch"));
+ }
+ function onPatched(fn) {
+ const node = getCurrent();
+ const decorate = node.app.dev ? wrapError : (fn) => fn;
+ node.patched.push(decorate(fn.bind(node.component), "onPatched"));
+ }
+ function onWillUnmount(fn) {
+ const node = getCurrent();
+ const decorate = node.app.dev ? wrapError : (fn) => fn;
+ node.willUnmount.unshift(decorate(fn.bind(node.component), "onWillUnmount"));
+ }
+ function onWillDestroy(fn) {
+ const node = getCurrent();
+ const decorate = node.app.dev ? wrapError : (fn) => fn;
+ node.willDestroy.push(decorate(fn.bind(node.component), "onWillDestroy"));
+ }
+ function onWillRender(fn) {
+ const node = getCurrent();
+ const renderFn = node.renderFn;
+ const decorate = node.app.dev ? wrapError : (fn) => fn;
+ fn = decorate(fn.bind(node.component), "onWillRender");
+ node.renderFn = () => {
+ fn();
+ return renderFn();
+ };
+ }
+ function onRendered(fn) {
+ const node = getCurrent();
+ const renderFn = node.renderFn;
+ const decorate = node.app.dev ? wrapError : (fn) => fn;
+ fn = decorate(fn.bind(node.component), "onRendered");
+ node.renderFn = () => {
+ const result = renderFn();
+ fn();
+ return result;
+ };
+ }
+ function onError(callback) {
+ const node = getCurrent();
+ let handlers = nodeErrorHandlers.get(node);
+ if (!handlers) {
+ handlers = [];
+ nodeErrorHandlers.set(node, handlers);
+ }
+ handlers.push(callback.bind(node.component));
+ }
+
+ class Component {
+ constructor(props, env, node) {
+ this.props = props;
+ this.env = env;
+ this.__owl__ = node;
+ }
+ setup() { }
+ render(deep = false) {
+ this.__owl__.render(deep === true);
+ }
+ }
+ Component.template = "";
+
+ const VText = text("").constructor;
+ class VPortal extends VText {
+ constructor(selector, content) {
+ super("");
+ this.target = null;
+ this.selector = selector;
+ this.content = content;
+ }
+ mount(parent, anchor) {
+ super.mount(parent, anchor);
+ this.target = document.querySelector(this.selector);
+ if (this.target) {
+ this.content.mount(this.target, null);
+ }
+ else {
+ this.content.mount(parent, anchor);
+ }
+ }
+ beforeRemove() {
+ this.content.beforeRemove();
+ }
+ remove() {
+ if (this.content) {
+ super.remove();
+ this.content.remove();
+ this.content = null;
+ }
+ }
+ patch(other) {
+ super.patch(other);
+ if (this.content) {
+ this.content.patch(other.content, true);
+ }
+ else {
+ this.content = other.content;
+ this.content.mount(this.target, null);
+ }
+ }
+ }
+ /**
+ * kind of similar to , but it wraps it around a VPortal
+ */
+ function portalTemplate(app, bdom, helpers) {
+ let { callSlot } = helpers;
+ return function template(ctx, node, key = "") {
+ return new VPortal(ctx.props.target, callSlot(ctx, node, key, "default", false, null));
+ };
+ }
+ class Portal extends Component {
+ setup() {
+ const node = this.__owl__;
+ onMounted(() => {
+ const portal = node.bdom;
+ if (!portal.target) {
+ const target = document.querySelector(this.props.target);
+ if (target) {
+ portal.content.moveBeforeDOMNode(target.firstChild, target);
+ }
+ else {
+ throw new OwlError("invalid portal target");
+ }
+ }
+ });
+ onWillUnmount(() => {
+ const portal = node.bdom;
+ portal.remove();
+ });
+ }
+ }
+ Portal.template = "__portal__";
+ Portal.props = {
+ target: {
+ type: String,
+ },
+ slots: true,
+ };
+
+ // -----------------------------------------------------------------------------
+ // helpers
+ // -----------------------------------------------------------------------------
+ const isUnionType = (t) => Array.isArray(t);
+ const isBaseType = (t) => typeof t !== "object";
+ const isValueType = (t) => typeof t === "object" && t && "value" in t;
+ function isOptional(t) {
+ return typeof t === "object" && "optional" in t ? t.optional || false : false;
+ }
+ function describeType(type) {
+ return type === "*" || type === true ? "value" : type.name.toLowerCase();
+ }
+ function describe(info) {
+ if (isBaseType(info)) {
+ return describeType(info);
+ }
+ else if (isUnionType(info)) {
+ return info.map(describe).join(" or ");
+ }
+ else if (isValueType(info)) {
+ return String(info.value);
+ }
+ if ("element" in info) {
+ return `list of ${describe({ type: info.element, optional: false })}s`;
+ }
+ if ("shape" in info) {
+ return `object`;
+ }
+ return describe(info.type || "*");
+ }
+ function toSchema(spec) {
+ return Object.fromEntries(spec.map((e) => e.endsWith("?") ? [e.slice(0, -1), { optional: true }] : [e, { type: "*", optional: false }]));
+ }
+ /**
+ * Main validate function
+ */
+ function validate(obj, spec) {
+ let errors = validateSchema(obj, spec);
+ if (errors.length) {
+ throw new OwlError("Invalid object: " + errors.join(", "));
+ }
+ }
+ /**
+ * Helper validate function, to get the list of errors. useful if one want to
+ * manipulate the errors without parsing an error object
+ */
+ function validateSchema(obj, schema) {
+ if (Array.isArray(schema)) {
+ schema = toSchema(schema);
+ }
+ obj = toRaw(obj);
+ let errors = [];
+ // check if each value in obj has correct shape
+ for (let key in obj) {
+ if (key in schema) {
+ let result = validateType(key, obj[key], schema[key]);
+ if (result) {
+ errors.push(result);
+ }
+ }
+ else if (!("*" in schema)) {
+ errors.push(`unknown key '${key}'`);
+ }
+ }
+ // check that all specified keys are defined in obj
+ for (let key in schema) {
+ const spec = schema[key];
+ if (key !== "*" && !isOptional(spec) && !(key in obj)) {
+ const isObj = typeof spec === "object" && !Array.isArray(spec);
+ const isAny = spec === "*" || (isObj && "type" in spec ? spec.type === "*" : isObj);
+ let detail = isAny ? "" : ` (should be a ${describe(spec)})`;
+ errors.push(`'${key}' is missing${detail}`);
+ }
+ }
+ return errors;
+ }
+ function validateBaseType(key, value, type) {
+ if (typeof type === "function") {
+ if (typeof value === "object") {
+ if (!(value instanceof type)) {
+ return `'${key}' is not a ${describeType(type)}`;
+ }
+ }
+ else if (typeof value !== type.name.toLowerCase()) {
+ return `'${key}' is not a ${describeType(type)}`;
+ }
+ }
+ return null;
+ }
+ function validateArrayType(key, value, descr) {
+ if (!Array.isArray(value)) {
+ return `'${key}' is not a list of ${describe(descr)}s`;
+ }
+ for (let i = 0; i < value.length; i++) {
+ const error = validateType(`${key}[${i}]`, value[i], descr);
+ if (error) {
+ return error;
+ }
+ }
+ return null;
+ }
+ function validateType(key, value, descr) {
+ if (value === undefined) {
+ return isOptional(descr) ? null : `'${key}' is undefined (should be a ${describe(descr)})`;
+ }
+ else if (isBaseType(descr)) {
+ return validateBaseType(key, value, descr);
+ }
+ else if (isValueType(descr)) {
+ return value === descr.value ? null : `'${key}' is not equal to '${descr.value}'`;
+ }
+ else if (isUnionType(descr)) {
+ let validDescr = descr.find((p) => !validateType(key, value, p));
+ return validDescr ? null : `'${key}' is not a ${describe(descr)}`;
+ }
+ let result = null;
+ if ("element" in descr) {
+ result = validateArrayType(key, value, descr.element);
+ }
+ else if ("shape" in descr && !result) {
+ if (typeof value !== "object" || Array.isArray(value)) {
+ result = `'${key}' is not an object`;
+ }
+ else {
+ const errors = validateSchema(value, descr.shape);
+ if (errors.length) {
+ result = `'${key}' has not the correct shape (${errors.join(", ")})`;
+ }
+ }
+ }
+ if ("type" in descr && !result) {
+ result = validateType(key, value, descr.type);
+ }
+ if ("validate" in descr && !result) {
+ result = !descr.validate(value) ? `'${key}' is not valid` : null;
+ }
+ return result;
+ }
+
+ const ObjectCreate = Object.create;
+ /**
+ * This file contains utility functions that will be injected in each template,
+ * to perform various useful tasks in the compiled code.
+ */
+ function withDefault(value, defaultValue) {
+ return value === undefined || value === null || value === false ? defaultValue : value;
+ }
+ function callSlot(ctx, parent, key, name, dynamic, extra, defaultContent) {
+ key = key + "__slot_" + name;
+ const slots = ctx.props.slots || {};
+ const { __render, __ctx, __scope } = slots[name] || {};
+ const slotScope = ObjectCreate(__ctx || {});
+ if (__scope) {
+ slotScope[__scope] = extra;
+ }
+ const slotBDom = __render ? __render(slotScope, parent, key) : null;
+ if (defaultContent) {
+ let child1 = undefined;
+ let child2 = undefined;
+ if (slotBDom) {
+ child1 = dynamic ? toggler(name, slotBDom) : slotBDom;
+ }
+ else {
+ child2 = defaultContent(ctx, parent, key);
+ }
+ return multi([child1, child2]);
+ }
+ return slotBDom || text("");
+ }
+ function capture(ctx) {
+ const result = ObjectCreate(ctx);
+ for (let k in ctx) {
+ result[k] = ctx[k];
+ }
+ return result;
+ }
+ function withKey(elem, k) {
+ elem.key = k;
+ return elem;
+ }
+ function prepareList(collection) {
+ let keys;
+ let values;
+ if (Array.isArray(collection)) {
+ keys = collection;
+ values = collection;
+ }
+ else if (collection) {
+ values = Object.keys(collection);
+ keys = Object.values(collection);
+ }
+ else {
+ throw new OwlError("Invalid loop expression");
+ }
+ const n = values.length;
+ return [keys, values, n, new Array(n)];
+ }
+ const isBoundary = Symbol("isBoundary");
+ function setContextValue(ctx, key, value) {
+ const ctx0 = ctx;
+ while (!ctx.hasOwnProperty(key) && !ctx.hasOwnProperty(isBoundary)) {
+ const newCtx = ctx.__proto__;
+ if (!newCtx) {
+ ctx = ctx0;
+ break;
+ }
+ ctx = newCtx;
+ }
+ ctx[key] = value;
+ }
+ function toNumber(val) {
+ const n = parseFloat(val);
+ return isNaN(n) ? val : n;
+ }
+ function shallowEqual(l1, l2) {
+ for (let i = 0, l = l1.length; i < l; i++) {
+ if (l1[i] !== l2[i]) {
+ return false;
+ }
+ }
+ return true;
+ }
+ class LazyValue {
+ constructor(fn, ctx, component, node, key) {
+ this.fn = fn;
+ this.ctx = capture(ctx);
+ this.component = component;
+ this.node = node;
+ this.key = key;
+ }
+ evaluate() {
+ return this.fn.call(this.component, this.ctx, this.node, this.key);
+ }
+ toString() {
+ return this.evaluate().toString();
+ }
+ }
+ /*
+ * Safely outputs `value` as a block depending on the nature of `value`
+ */
+ function safeOutput(value, defaultValue) {
+ if (value === undefined) {
+ return defaultValue ? toggler("default", defaultValue) : toggler("undefined", text(""));
+ }
+ let safeKey;
+ let block;
+ switch (typeof value) {
+ case "object":
+ if (value instanceof Markup) {
+ safeKey = `string_safe`;
+ block = html(value);
+ }
+ else if (value instanceof LazyValue) {
+ safeKey = `lazy_value`;
+ block = value.evaluate();
+ }
+ else if (value instanceof String) {
+ safeKey = "string_unsafe";
+ block = text(value);
+ }
+ else {
+ // Assuming it is a block
+ safeKey = "block_safe";
+ block = value;
+ }
+ break;
+ case "string":
+ safeKey = "string_unsafe";
+ block = text(value);
+ break;
+ default:
+ safeKey = "string_unsafe";
+ block = text(String(value));
+ }
+ return toggler(safeKey, block);
+ }
+ /**
+ * Validate the component props (or next props) against the (static) props
+ * description. This is potentially an expensive operation: it may needs to
+ * visit recursively the props and all the children to check if they are valid.
+ * This is why it is only done in 'dev' mode.
+ */
+ function validateProps(name, props, comp) {
+ const ComponentClass = typeof name !== "string"
+ ? name
+ : comp.constructor.components[name];
+ if (!ComponentClass) {
+ // this is an error, wrong component. We silently return here instead so the
+ // error is triggered by the usual path ('component' function)
+ return;
+ }
+ const schema = ComponentClass.props;
+ if (!schema) {
+ if (comp.__owl__.app.warnIfNoStaticProps) {
+ console.warn(`Component '${ComponentClass.name}' does not have a static props description`);
+ }
+ return;
+ }
+ const defaultProps = ComponentClass.defaultProps;
+ if (defaultProps) {
+ let isMandatory = (name) => Array.isArray(schema)
+ ? schema.includes(name)
+ : name in schema && !("*" in schema) && !isOptional(schema[name]);
+ for (let p in defaultProps) {
+ if (isMandatory(p)) {
+ throw new OwlError(`A default value cannot be defined for a mandatory prop (name: '${p}', component: ${ComponentClass.name})`);
+ }
+ }
+ }
+ const errors = validateSchema(props, schema);
+ if (errors.length) {
+ throw new OwlError(`Invalid props for component '${ComponentClass.name}': ` + errors.join(", "));
+ }
+ }
+ function makeRefWrapper(node) {
+ let refNames = new Set();
+ return (name, fn) => {
+ if (refNames.has(name)) {
+ throw new OwlError(`Cannot set the same ref more than once in the same component, ref "${name}" was set multiple times in ${node.name}`);
+ }
+ refNames.add(name);
+ return fn;
+ };
+ }
+ const helpers = {
+ withDefault,
+ zero: Symbol("zero"),
+ isBoundary,
+ callSlot,
+ capture,
+ withKey,
+ prepareList,
+ setContextValue,
+ shallowEqual,
+ toNumber,
+ validateProps,
+ LazyValue,
+ safeOutput,
+ createCatcher,
+ markRaw,
+ OwlError,
+ makeRefWrapper,
+ };
+
+ const bdom = { text, createBlock, list, multi, html, toggler, comment };
+ function parseXML$1(xml) {
+ const parser = new DOMParser();
+ const doc = parser.parseFromString(xml, "text/xml");
+ if (doc.getElementsByTagName("parsererror").length) {
+ let msg = "Invalid XML in template.";
+ const parsererrorText = doc.getElementsByTagName("parsererror")[0].textContent;
+ if (parsererrorText) {
+ msg += "\nThe parser has produced the following error message:\n" + parsererrorText;
+ const re = /\d+/g;
+ const firstMatch = re.exec(parsererrorText);
+ if (firstMatch) {
+ const lineNumber = Number(firstMatch[0]);
+ const line = xml.split("\n")[lineNumber - 1];
+ const secondMatch = re.exec(parsererrorText);
+ if (line && secondMatch) {
+ const columnIndex = Number(secondMatch[0]) - 1;
+ if (line[columnIndex]) {
+ msg +=
+ `\nThe error might be located at xml line ${lineNumber} column ${columnIndex}\n` +
+ `${line}\n${"-".repeat(columnIndex - 1)}^`;
+ }
+ }
+ }
+ }
+ throw new OwlError(msg);
+ }
+ return doc;
+ }
+ class TemplateSet {
+ constructor(config = {}) {
+ this.rawTemplates = Object.create(globalTemplates);
+ this.templates = {};
+ this.Portal = Portal;
+ this.dev = config.dev || false;
+ this.translateFn = config.translateFn;
+ this.translatableAttributes = config.translatableAttributes;
+ if (config.templates) {
+ this.addTemplates(config.templates);
+ }
+ }
+ static registerTemplate(name, fn) {
+ globalTemplates[name] = fn;
+ }
+ addTemplate(name, template) {
+ if (name in this.rawTemplates) {
+ const rawTemplate = this.rawTemplates[name];
+ const currentAsString = typeof rawTemplate === "string"
+ ? rawTemplate
+ : rawTemplate instanceof Element
+ ? rawTemplate.outerHTML
+ : rawTemplate.toString();
+ const newAsString = typeof template === "string" ? template : template.outerHTML;
+ if (currentAsString === newAsString) {
+ return;
+ }
+ throw new OwlError(`Template ${name} already defined with different content`);
+ }
+ this.rawTemplates[name] = template;
+ }
+ addTemplates(xml) {
+ if (!xml) {
+ // empty string
+ return;
+ }
+ xml = xml instanceof Document ? xml : parseXML$1(xml);
+ for (const template of xml.querySelectorAll("[t-name]")) {
+ const name = template.getAttribute("t-name");
+ this.addTemplate(name, template);
+ }
+ }
+ getTemplate(name) {
+ if (!(name in this.templates)) {
+ const rawTemplate = this.rawTemplates[name];
+ if (rawTemplate === undefined) {
+ let extraInfo = "";
+ try {
+ const componentName = getCurrent().component.constructor.name;
+ extraInfo = ` (for component "${componentName}")`;
+ }
+ catch { }
+ throw new OwlError(`Missing template: "${name}"${extraInfo}`);
+ }
+ const isFn = typeof rawTemplate === "function" && !(rawTemplate instanceof Element);
+ const templateFn = isFn ? rawTemplate : this._compileTemplate(name, rawTemplate);
+ // first add a function to lazily get the template, in case there is a
+ // recursive call to the template name
+ const templates = this.templates;
+ this.templates[name] = function (context, parent) {
+ return templates[name].call(this, context, parent);
+ };
+ const template = templateFn(this, bdom, helpers);
+ this.templates[name] = template;
+ }
+ return this.templates[name];
+ }
+ _compileTemplate(name, template) {
+ throw new OwlError(`Unable to compile a template. Please use owl full build instead`);
+ }
+ callTemplate(owner, subTemplate, ctx, parent, key) {
+ const template = this.getTemplate(subTemplate);
+ return toggler(subTemplate, template.call(owner, ctx, parent, key));
+ }
+ }
+ // -----------------------------------------------------------------------------
+ // xml tag helper
+ // -----------------------------------------------------------------------------
+ const globalTemplates = {};
+ function xml(...args) {
+ const name = `__template__${xml.nextId++}`;
+ const value = String.raw(...args);
+ globalTemplates[name] = value;
+ return name;
+ }
+ xml.nextId = 1;
+ TemplateSet.registerTemplate("__portal__", portalTemplate);
+
+ /**
+ * Owl QWeb Expression Parser
+ *
+ * Owl needs in various contexts to be able to understand the structure of a
+ * string representing a javascript expression. The usual goal is to be able
+ * to rewrite some variables. For example, if a template has
+ *
+ * ```xml
+ * ...
+ * ```
+ *
+ * this needs to be translated in something like this:
+ *
+ * ```js
+ * if (context["computeSomething"]({val: context["state"].val})) { ... }
+ * ```
+ *
+ * This file contains the implementation of an extremely naive tokenizer/parser
+ * and evaluator for javascript expressions. The supported grammar is basically
+ * only expressive enough to understand the shape of objects, of arrays, and
+ * various operators.
+ */
+ //------------------------------------------------------------------------------
+ // Misc types, constants and helpers
+ //------------------------------------------------------------------------------
+ const RESERVED_WORDS = "true,false,NaN,null,undefined,debugger,console,window,in,instanceof,new,function,return,eval,void,Math,RegExp,Array,Object,Date".split(",");
+ const WORD_REPLACEMENT = Object.assign(Object.create(null), {
+ and: "&&",
+ or: "||",
+ gt: ">",
+ gte: ">=",
+ lt: "<",
+ lte: "<=",
+ });
+ const STATIC_TOKEN_MAP = Object.assign(Object.create(null), {
+ "{": "LEFT_BRACE",
+ "}": "RIGHT_BRACE",
+ "[": "LEFT_BRACKET",
+ "]": "RIGHT_BRACKET",
+ ":": "COLON",
+ ",": "COMMA",
+ "(": "LEFT_PAREN",
+ ")": "RIGHT_PAREN",
+ });
+ // note that the space after typeof is relevant. It makes sure that the formatted
+ // expression has a space after typeof. Currently we don't support delete and void
+ const OPERATORS = "...,.,===,==,+,!==,!=,!,||,&&,>=,>,<=,<,?,-,*,/,%,typeof ,=>,=,;,in ,new ,|,&,^,~".split(",");
+ let tokenizeString = function (expr) {
+ let s = expr[0];
+ let start = s;
+ if (s !== "'" && s !== '"' && s !== "`") {
+ return false;
+ }
+ let i = 1;
+ let cur;
+ while (expr[i] && expr[i] !== start) {
+ cur = expr[i];
+ s += cur;
+ if (cur === "\\") {
+ i++;
+ cur = expr[i];
+ if (!cur) {
+ throw new OwlError("Invalid expression");
+ }
+ s += cur;
+ }
+ i++;
+ }
+ if (expr[i] !== start) {
+ throw new OwlError("Invalid expression");
+ }
+ s += start;
+ if (start === "`") {
+ return {
+ type: "TEMPLATE_STRING",
+ value: s,
+ replace(replacer) {
+ return s.replace(/\$\{(.*?)\}/g, (match, group) => {
+ return "${" + replacer(group) + "}";
+ });
+ },
+ };
+ }
+ return { type: "VALUE", value: s };
+ };
+ let tokenizeNumber = function (expr) {
+ let s = expr[0];
+ if (s && s.match(/[0-9]/)) {
+ let i = 1;
+ while (expr[i] && expr[i].match(/[0-9]|\./)) {
+ s += expr[i];
+ i++;
+ }
+ return { type: "VALUE", value: s };
+ }
+ else {
+ return false;
+ }
+ };
+ let tokenizeSymbol = function (expr) {
+ let s = expr[0];
+ if (s && s.match(/[a-zA-Z_\$]/)) {
+ let i = 1;
+ while (expr[i] && expr[i].match(/\w/)) {
+ s += expr[i];
+ i++;
+ }
+ if (s in WORD_REPLACEMENT) {
+ return { type: "OPERATOR", value: WORD_REPLACEMENT[s], size: s.length };
+ }
+ return { type: "SYMBOL", value: s };
+ }
+ else {
+ return false;
+ }
+ };
+ const tokenizeStatic = function (expr) {
+ const char = expr[0];
+ if (char && char in STATIC_TOKEN_MAP) {
+ return { type: STATIC_TOKEN_MAP[char], value: char };
+ }
+ return false;
+ };
+ const tokenizeOperator = function (expr) {
+ for (let op of OPERATORS) {
+ if (expr.startsWith(op)) {
+ return { type: "OPERATOR", value: op };
+ }
+ }
+ return false;
+ };
+ const TOKENIZERS = [
+ tokenizeString,
+ tokenizeNumber,
+ tokenizeOperator,
+ tokenizeSymbol,
+ tokenizeStatic,
+ ];
+ /**
+ * Convert a javascript expression (as a string) into a list of tokens. For
+ * example: `tokenize("1 + b")` will return:
+ * ```js
+ * [
+ * {type: "VALUE", value: "1"},
+ * {type: "OPERATOR", value: "+"},
+ * {type: "SYMBOL", value: "b"}
+ * ]
+ * ```
+ */
+ function tokenize(expr) {
+ const result = [];
+ let token = true;
+ let error;
+ let current = expr;
+ try {
+ while (token) {
+ current = current.trim();
+ if (current) {
+ for (let tokenizer of TOKENIZERS) {
+ token = tokenizer(current);
+ if (token) {
+ result.push(token);
+ current = current.slice(token.size || token.value.length);
+ break;
+ }
+ }
+ }
+ else {
+ token = false;
+ }
+ }
+ }
+ catch (e) {
+ error = e; // Silence all errors and throw a generic error below
+ }
+ if (current.length || error) {
+ throw new OwlError(`Tokenizer error: could not tokenize \`${expr}\``);
+ }
+ return result;
+ }
+ //------------------------------------------------------------------------------
+ // Expression "evaluator"
+ //------------------------------------------------------------------------------
+ const isLeftSeparator = (token) => token && (token.type === "LEFT_BRACE" || token.type === "COMMA");
+ const isRightSeparator = (token) => token && (token.type === "RIGHT_BRACE" || token.type === "COMMA");
+ /**
+ * This is the main function exported by this file. This is the code that will
+ * process an expression (given as a string) and returns another expression with
+ * proper lookups in the context.
+ *
+ * Usually, this kind of code would be very simple to do if we had an AST (so,
+ * if we had a javascript parser), since then, we would only need to find the
+ * variables and replace them. However, a parser is more complicated, and there
+ * are no standard builtin parser API.
+ *
+ * Since this method is applied to simple javasript expressions, and the work to
+ * be done is actually quite simple, we actually can get away with not using a
+ * parser, which helps with the code size.
+ *
+ * Here is the heuristic used by this method to determine if a token is a
+ * variable:
+ * - by default, all symbols are considered a variable
+ * - unless the previous token is a dot (in that case, this is a property: `a.b`)
+ * - or if the previous token is a left brace or a comma, and the next token is
+ * a colon (in that case, this is an object key: `{a: b}`)
+ *
+ * Some specific code is also required to support arrow functions. If we detect
+ * the arrow operator, then we add the current (or some previous tokens) token to
+ * the list of variables so it does not get replaced by a lookup in the context
+ */
+ function compileExprToArray(expr) {
+ const localVars = new Set();
+ const tokens = tokenize(expr);
+ let i = 0;
+ let stack = []; // to track last opening [ or {
+ while (i < tokens.length) {
+ let token = tokens[i];
+ let prevToken = tokens[i - 1];
+ let nextToken = tokens[i + 1];
+ let groupType = stack[stack.length - 1];
+ switch (token.type) {
+ case "LEFT_BRACE":
+ case "LEFT_BRACKET":
+ stack.push(token.type);
+ break;
+ case "RIGHT_BRACE":
+ case "RIGHT_BRACKET":
+ stack.pop();
+ }
+ let isVar = token.type === "SYMBOL" && !RESERVED_WORDS.includes(token.value);
+ if (token.type === "SYMBOL" && !RESERVED_WORDS.includes(token.value)) {
+ if (prevToken) {
+ // normalize missing tokens: {a} should be equivalent to {a:a}
+ if (groupType === "LEFT_BRACE" &&
+ isLeftSeparator(prevToken) &&
+ isRightSeparator(nextToken)) {
+ tokens.splice(i + 1, 0, { type: "COLON", value: ":" }, { ...token });
+ nextToken = tokens[i + 1];
+ }
+ if (prevToken.type === "OPERATOR" && prevToken.value === ".") {
+ isVar = false;
+ }
+ else if (prevToken.type === "LEFT_BRACE" || prevToken.type === "COMMA") {
+ if (nextToken && nextToken.type === "COLON") {
+ isVar = false;
+ }
+ }
+ }
+ }
+ if (token.type === "TEMPLATE_STRING") {
+ token.value = token.replace((expr) => compileExpr(expr));
+ }
+ if (nextToken && nextToken.type === "OPERATOR" && nextToken.value === "=>") {
+ if (token.type === "RIGHT_PAREN") {
+ let j = i - 1;
+ while (j > 0 && tokens[j].type !== "LEFT_PAREN") {
+ if (tokens[j].type === "SYMBOL" && tokens[j].originalValue) {
+ tokens[j].value = tokens[j].originalValue;
+ localVars.add(tokens[j].value); //] = { id: tokens[j].value, expr: tokens[j].value };
+ }
+ j--;
+ }
+ }
+ else {
+ localVars.add(token.value); //] = { id: token.value, expr: token.value };
+ }
+ }
+ if (isVar) {
+ token.varName = token.value;
+ if (!localVars.has(token.value)) {
+ token.originalValue = token.value;
+ token.value = `ctx['${token.value}']`;
+ }
+ }
+ i++;
+ }
+ // Mark all variables that have been used locally.
+ // This assumes the expression has only one scope (incorrect but "good enough for now")
+ for (const token of tokens) {
+ if (token.type === "SYMBOL" && token.varName && localVars.has(token.value)) {
+ token.originalValue = token.value;
+ token.value = `_${token.value}`;
+ token.isLocal = true;
+ }
+ }
+ return tokens;
+ }
+ // Leading spaces are trimmed during tokenization, so they need to be added back for some values
+ const paddedValues = new Map([["in ", " in "]]);
+ function compileExpr(expr) {
+ return compileExprToArray(expr)
+ .map((t) => paddedValues.get(t.value) || t.value)
+ .join("");
+ }
+ const INTERP_REGEXP = /\{\{.*?\}\}|\#\{.*?\}/g;
+ function replaceDynamicParts(s, replacer) {
+ let matches = s.match(INTERP_REGEXP);
+ if (matches && matches[0].length === s.length) {
+ return `(${replacer(s.slice(2, matches[0][0] === "{" ? -2 : -1))})`;
+ }
+ let r = s.replace(INTERP_REGEXP, (s) => "${" + replacer(s.slice(2, s[0] === "{" ? -2 : -1)) + "}");
+ return "`" + r + "`";
+ }
+ function interpolate(s) {
+ return replaceDynamicParts(s, compileExpr);
+ }
+
+ const whitespaceRE = /\s+/g;
+ // using a non-html document so that HTML serializes as XML instead
+ // of HTML (as we will parse it as xml later)
+ const xmlDoc = document.implementation.createDocument(null, null, null);
+ const MODS = new Set(["stop", "capture", "prevent", "self", "synthetic"]);
+ let nextDataIds = {};
+ function generateId(prefix = "") {
+ nextDataIds[prefix] = (nextDataIds[prefix] || 0) + 1;
+ return prefix + nextDataIds[prefix];
+ }
+ function isProp(tag, key) {
+ switch (tag) {
+ case "input":
+ return (key === "checked" ||
+ key === "indeterminate" ||
+ key === "value" ||
+ key === "readonly" ||
+ key === "readOnly" ||
+ key === "disabled");
+ case "option":
+ return key === "selected" || key === "disabled";
+ case "textarea":
+ return key === "value" || key === "readonly" || key === "readOnly" || key === "disabled";
+ case "select":
+ return key === "value" || key === "disabled";
+ case "button":
+ case "optgroup":
+ return key === "disabled";
+ }
+ return false;
+ }
+ // -----------------------------------------------------------------------------
+ // BlockDescription
+ // -----------------------------------------------------------------------------
+ class BlockDescription {
+ constructor(target, type) {
+ this.dynamicTagName = null;
+ this.isRoot = false;
+ this.hasDynamicChildren = false;
+ this.children = [];
+ this.data = [];
+ this.childNumber = 0;
+ this.parentVar = "";
+ this.id = BlockDescription.nextBlockId++;
+ this.varName = "b" + this.id;
+ this.blockName = "block" + this.id;
+ this.target = target;
+ this.type = type;
+ }
+ insertData(str, prefix = "d") {
+ const id = generateId(prefix);
+ this.target.addLine(`let ${id} = ${str};`);
+ return this.data.push(id) - 1;
+ }
+ insert(dom) {
+ if (this.currentDom) {
+ this.currentDom.appendChild(dom);
+ }
+ else {
+ this.dom = dom;
+ }
+ }
+ generateExpr(expr) {
+ if (this.type === "block") {
+ const hasChildren = this.children.length;
+ let params = this.data.length ? `[${this.data.join(", ")}]` : hasChildren ? "[]" : "";
+ if (hasChildren) {
+ params += ", [" + this.children.map((c) => c.varName).join(", ") + "]";
+ }
+ if (this.dynamicTagName) {
+ return `toggler(${this.dynamicTagName}, ${this.blockName}(${this.dynamicTagName})(${params}))`;
+ }
+ return `${this.blockName}(${params})`;
+ }
+ else if (this.type === "list") {
+ return `list(c_block${this.id})`;
+ }
+ return expr;
+ }
+ asXmlString() {
+ // Can't use outerHTML on text/comment nodes
+ // append dom to any element and use innerHTML instead
+ const t = xmlDoc.createElement("t");
+ t.appendChild(this.dom);
+ return t.innerHTML;
+ }
+ }
+ BlockDescription.nextBlockId = 1;
+ function createContext(parentCtx, params) {
+ return Object.assign({
+ block: null,
+ index: 0,
+ forceNewBlock: true,
+ translate: parentCtx.translate,
+ tKeyExpr: null,
+ nameSpace: parentCtx.nameSpace,
+ tModelSelectedExpr: parentCtx.tModelSelectedExpr,
+ }, params);
+ }
+ class CodeTarget {
+ constructor(name, on) {
+ this.indentLevel = 0;
+ this.loopLevel = 0;
+ this.code = [];
+ this.hasRoot = false;
+ this.hasCache = false;
+ this.shouldProtectScope = false;
+ this.hasRefWrapper = false;
+ this.name = name;
+ this.on = on || null;
+ }
+ addLine(line, idx) {
+ const prefix = new Array(this.indentLevel + 2).join(" ");
+ if (idx === undefined) {
+ this.code.push(prefix + line);
+ }
+ else {
+ this.code.splice(idx, 0, prefix + line);
+ }
+ }
+ generateCode() {
+ let result = [];
+ result.push(`function ${this.name}(ctx, node, key = "") {`);
+ if (this.shouldProtectScope) {
+ result.push(` ctx = Object.create(ctx);`);
+ result.push(` ctx[isBoundary] = 1`);
+ }
+ if (this.hasRefWrapper) {
+ result.push(` let refWrapper = makeRefWrapper(this.__owl__);`);
+ }
+ if (this.hasCache) {
+ result.push(` let cache = ctx.cache || {};`);
+ result.push(` let nextCache = ctx.cache = {};`);
+ }
+ for (let line of this.code) {
+ result.push(line);
+ }
+ if (!this.hasRoot) {
+ result.push(`return text('');`);
+ }
+ result.push(`}`);
+ return result.join("\n ");
+ }
+ currentKey(ctx) {
+ let key = this.loopLevel ? `key${this.loopLevel}` : "key";
+ if (ctx.tKeyExpr) {
+ key = `${ctx.tKeyExpr} + ${key}`;
+ }
+ return key;
+ }
+ }
+ const TRANSLATABLE_ATTRS = ["label", "title", "placeholder", "alt"];
+ const translationRE = /^(\s*)([\s\S]+?)(\s*)$/;
+ class CodeGenerator {
+ constructor(ast, options) {
+ this.blocks = [];
+ this.nextBlockId = 1;
+ this.isDebug = false;
+ this.targets = [];
+ this.target = new CodeTarget("template");
+ this.translatableAttributes = TRANSLATABLE_ATTRS;
+ this.staticDefs = [];
+ this.slotNames = new Set();
+ this.helpers = new Set();
+ this.translateFn = options.translateFn || ((s) => s);
+ if (options.translatableAttributes) {
+ const attrs = new Set(TRANSLATABLE_ATTRS);
+ for (let attr of options.translatableAttributes) {
+ if (attr.startsWith("-")) {
+ attrs.delete(attr.slice(1));
+ }
+ else {
+ attrs.add(attr);
+ }
+ }
+ this.translatableAttributes = [...attrs];
+ }
+ this.hasSafeContext = options.hasSafeContext || false;
+ this.dev = options.dev || false;
+ this.ast = ast;
+ this.templateName = options.name;
+ }
+ generateCode() {
+ const ast = this.ast;
+ this.isDebug = ast.type === 12 /* TDebug */;
+ BlockDescription.nextBlockId = 1;
+ nextDataIds = {};
+ this.compileAST(ast, {
+ block: null,
+ index: 0,
+ forceNewBlock: false,
+ isLast: true,
+ translate: true,
+ tKeyExpr: null,
+ });
+ // define blocks and utility functions
+ let mainCode = [` let { text, createBlock, list, multi, html, toggler, comment } = bdom;`];
+ if (this.helpers.size) {
+ mainCode.push(`let { ${[...this.helpers].join(", ")} } = helpers;`);
+ }
+ if (this.templateName) {
+ mainCode.push(`// Template name: "${this.templateName}"`);
+ }
+ for (let { id, expr } of this.staticDefs) {
+ mainCode.push(`const ${id} = ${expr};`);
+ }
+ // define all blocks
+ if (this.blocks.length) {
+ mainCode.push(``);
+ for (let block of this.blocks) {
+ if (block.dom) {
+ let xmlString = block.asXmlString();
+ xmlString = xmlString.replace(/\\/g, "\\\\").replace(/`/g, "\\`");
+ if (block.dynamicTagName) {
+ xmlString = xmlString.replace(/^<\w+/, `<\${tag || '${block.dom.nodeName}'}`);
+ xmlString = xmlString.replace(/\w+>$/, `\${tag || '${block.dom.nodeName}'}>`);
+ mainCode.push(`let ${block.blockName} = tag => createBlock(\`${xmlString}\`);`);
+ }
+ else {
+ mainCode.push(`let ${block.blockName} = createBlock(\`${xmlString}\`);`);
+ }
+ }
+ }
+ }
+ // define all slots/defaultcontent function
+ if (this.targets.length) {
+ for (let fn of this.targets) {
+ mainCode.push("");
+ mainCode = mainCode.concat(fn.generateCode());
+ }
+ }
+ // generate main code
+ mainCode.push("");
+ mainCode = mainCode.concat("return " + this.target.generateCode());
+ const code = mainCode.join("\n ");
+ if (this.isDebug) {
+ const msg = `[Owl Debug]\n${code}`;
+ console.log(msg);
+ }
+ return code;
+ }
+ compileInNewTarget(prefix, ast, ctx, on) {
+ const name = generateId(prefix);
+ const initialTarget = this.target;
+ const target = new CodeTarget(name, on);
+ this.targets.push(target);
+ this.target = target;
+ this.compileAST(ast, createContext(ctx));
+ this.target = initialTarget;
+ return name;
+ }
+ addLine(line, idx) {
+ this.target.addLine(line, idx);
+ }
+ define(varName, expr) {
+ this.addLine(`const ${varName} = ${expr};`);
+ }
+ insertAnchor(block, index = block.children.length) {
+ const tag = `block-child-${index}`;
+ const anchor = xmlDoc.createElement(tag);
+ block.insert(anchor);
+ }
+ createBlock(parentBlock, type, ctx) {
+ const hasRoot = this.target.hasRoot;
+ const block = new BlockDescription(this.target, type);
+ if (!hasRoot && !ctx.preventRoot) {
+ this.target.hasRoot = true;
+ block.isRoot = true;
+ }
+ if (parentBlock) {
+ parentBlock.children.push(block);
+ if (parentBlock.type === "list") {
+ block.parentVar = `c_block${parentBlock.id}`;
+ }
+ }
+ return block;
+ }
+ insertBlock(expression, block, ctx) {
+ let blockExpr = block.generateExpr(expression);
+ if (block.parentVar) {
+ let key = this.target.currentKey(ctx);
+ this.helpers.add("withKey");
+ this.addLine(`${block.parentVar}[${ctx.index}] = withKey(${blockExpr}, ${key});`);
+ return;
+ }
+ if (ctx.tKeyExpr) {
+ blockExpr = `toggler(${ctx.tKeyExpr}, ${blockExpr})`;
+ }
+ if (block.isRoot && !ctx.preventRoot) {
+ if (this.target.on) {
+ blockExpr = this.wrapWithEventCatcher(blockExpr, this.target.on);
+ }
+ this.addLine(`return ${blockExpr};`);
+ }
+ else {
+ this.define(block.varName, blockExpr);
+ }
+ }
+ /**
+ * Captures variables that are used inside of an expression. This is useful
+ * because in compiled code, almost all variables are accessed through the ctx
+ * object. In the case of functions, that lookup in the context can be delayed
+ * which can cause issues if the value has changed since the function was
+ * defined.
+ *
+ * @param expr the expression to capture
+ * @param forceCapture whether the expression should capture its scope even if
+ * it doesn't contain a function. Useful when the expression will be used as
+ * a function body.
+ * @returns a new expression that uses the captured values
+ */
+ captureExpression(expr, forceCapture = false) {
+ if (!forceCapture && !expr.includes("=>")) {
+ return compileExpr(expr);
+ }
+ const tokens = compileExprToArray(expr);
+ const mapping = new Map();
+ return tokens
+ .map((tok) => {
+ if (tok.varName && !tok.isLocal) {
+ if (!mapping.has(tok.varName)) {
+ const varId = generateId("v");
+ mapping.set(tok.varName, varId);
+ this.define(varId, tok.value);
+ }
+ tok.value = mapping.get(tok.varName);
+ }
+ return tok.value;
+ })
+ .join("");
+ }
+ /**
+ * @returns the newly created block name, if any
+ */
+ compileAST(ast, ctx) {
+ switch (ast.type) {
+ case 1 /* Comment */:
+ return this.compileComment(ast, ctx);
+ case 0 /* Text */:
+ return this.compileText(ast, ctx);
+ case 2 /* DomNode */:
+ return this.compileTDomNode(ast, ctx);
+ case 4 /* TEsc */:
+ return this.compileTEsc(ast, ctx);
+ case 8 /* TOut */:
+ return this.compileTOut(ast, ctx);
+ case 5 /* TIf */:
+ return this.compileTIf(ast, ctx);
+ case 9 /* TForEach */:
+ return this.compileTForeach(ast, ctx);
+ case 10 /* TKey */:
+ return this.compileTKey(ast, ctx);
+ case 3 /* Multi */:
+ return this.compileMulti(ast, ctx);
+ case 7 /* TCall */:
+ return this.compileTCall(ast, ctx);
+ case 15 /* TCallBlock */:
+ return this.compileTCallBlock(ast, ctx);
+ case 6 /* TSet */:
+ return this.compileTSet(ast, ctx);
+ case 11 /* TComponent */:
+ return this.compileComponent(ast, ctx);
+ case 12 /* TDebug */:
+ return this.compileDebug(ast, ctx);
+ case 13 /* TLog */:
+ return this.compileLog(ast, ctx);
+ case 14 /* TSlot */:
+ return this.compileTSlot(ast, ctx);
+ case 16 /* TTranslation */:
+ return this.compileTTranslation(ast, ctx);
+ case 17 /* TPortal */:
+ return this.compileTPortal(ast, ctx);
+ }
+ }
+ compileDebug(ast, ctx) {
+ this.addLine(`debugger;`);
+ if (ast.content) {
+ return this.compileAST(ast.content, ctx);
+ }
+ return null;
+ }
+ compileLog(ast, ctx) {
+ this.addLine(`console.log(${compileExpr(ast.expr)});`);
+ if (ast.content) {
+ return this.compileAST(ast.content, ctx);
+ }
+ return null;
+ }
+ compileComment(ast, ctx) {
+ let { block, forceNewBlock } = ctx;
+ const isNewBlock = !block || forceNewBlock;
+ if (isNewBlock) {
+ block = this.createBlock(block, "comment", ctx);
+ this.insertBlock(`comment(\`${ast.value}\`)`, block, {
+ ...ctx,
+ forceNewBlock: forceNewBlock && !block,
+ });
+ }
+ else {
+ const text = xmlDoc.createComment(ast.value);
+ block.insert(text);
+ }
+ return block.varName;
+ }
+ compileText(ast, ctx) {
+ let { block, forceNewBlock } = ctx;
+ let value = ast.value;
+ if (value && ctx.translate !== false) {
+ const match = translationRE.exec(value);
+ value = match[1] + this.translateFn(match[2]) + match[3];
+ }
+ if (!ctx.inPreTag) {
+ value = value.replace(whitespaceRE, " ");
+ }
+ if (!block || forceNewBlock) {
+ block = this.createBlock(block, "text", ctx);
+ this.insertBlock(`text(\`${value}\`)`, block, {
+ ...ctx,
+ forceNewBlock: forceNewBlock && !block,
+ });
+ }
+ else {
+ const createFn = ast.type === 0 /* Text */ ? xmlDoc.createTextNode : xmlDoc.createComment;
+ block.insert(createFn.call(xmlDoc, value));
+ }
+ return block.varName;
+ }
+ generateHandlerCode(rawEvent, handler) {
+ const modifiers = rawEvent
+ .split(".")
+ .slice(1)
+ .map((m) => {
+ if (!MODS.has(m)) {
+ throw new OwlError(`Unknown event modifier: '${m}'`);
+ }
+ return `"${m}"`;
+ });
+ let modifiersCode = "";
+ if (modifiers.length) {
+ modifiersCode = `${modifiers.join(",")}, `;
+ }
+ return `[${modifiersCode}${this.captureExpression(handler)}, ctx]`;
+ }
+ compileTDomNode(ast, ctx) {
+ let { block, forceNewBlock } = ctx;
+ const isNewBlock = !block || forceNewBlock || ast.dynamicTag !== null || ast.ns;
+ let codeIdx = this.target.code.length;
+ if (isNewBlock) {
+ if ((ast.dynamicTag || ctx.tKeyExpr || ast.ns) && ctx.block) {
+ this.insertAnchor(ctx.block);
+ }
+ block = this.createBlock(block, "block", ctx);
+ this.blocks.push(block);
+ if (ast.dynamicTag) {
+ const tagExpr = generateId("tag");
+ this.define(tagExpr, compileExpr(ast.dynamicTag));
+ block.dynamicTagName = tagExpr;
+ }
+ }
+ // attributes
+ const attrs = {};
+ const nameSpace = ast.ns || ctx.nameSpace;
+ if (nameSpace && isNewBlock) {
+ // specific namespace uri
+ attrs["block-ns"] = nameSpace;
+ }
+ for (let key in ast.attrs) {
+ let expr, attrName;
+ if (key.startsWith("t-attf")) {
+ expr = interpolate(ast.attrs[key]);
+ const idx = block.insertData(expr, "attr");
+ attrName = key.slice(7);
+ attrs["block-attribute-" + idx] = attrName;
+ }
+ else if (key.startsWith("t-att")) {
+ attrName = key === "t-att" ? null : key.slice(6);
+ expr = compileExpr(ast.attrs[key]);
+ if (attrName && isProp(ast.tag, attrName)) {
+ if (attrName === "readonly") {
+ // the property has a different name than the attribute
+ attrName = "readOnly";
+ }
+ // we force a new string or new boolean to bypass the equality check in blockdom when patching same value
+ if (attrName === "value") {
+ // When the expression is falsy (except 0), fall back to an empty string
+ expr = `new String((${expr}) === 0 ? 0 : ((${expr}) || ""))`;
+ }
+ else {
+ expr = `new Boolean(${expr})`;
+ }
+ const idx = block.insertData(expr, "prop");
+ attrs[`block-property-${idx}`] = attrName;
+ }
+ else {
+ const idx = block.insertData(expr, "attr");
+ if (key === "t-att") {
+ attrs[`block-attributes`] = String(idx);
+ }
+ else {
+ attrs[`block-attribute-${idx}`] = attrName;
+ }
+ }
+ }
+ else if (this.translatableAttributes.includes(key)) {
+ attrs[key] = this.translateFn(ast.attrs[key]);
+ }
+ else {
+ expr = `"${ast.attrs[key]}"`;
+ attrName = key;
+ attrs[key] = ast.attrs[key];
+ }
+ if (attrName === "value" && ctx.tModelSelectedExpr) {
+ let selectedId = block.insertData(`${ctx.tModelSelectedExpr} === ${expr}`, "attr");
+ attrs[`block-attribute-${selectedId}`] = "selected";
+ }
+ }
+ // t-model
+ let tModelSelectedExpr;
+ if (ast.model) {
+ const { hasDynamicChildren, baseExpr, expr, eventType, shouldNumberize, shouldTrim, targetAttr, specialInitTargetAttr, } = ast.model;
+ const baseExpression = compileExpr(baseExpr);
+ const bExprId = generateId("bExpr");
+ this.define(bExprId, baseExpression);
+ const expression = compileExpr(expr);
+ const exprId = generateId("expr");
+ this.define(exprId, expression);
+ const fullExpression = `${bExprId}[${exprId}]`;
+ let idx;
+ if (specialInitTargetAttr) {
+ let targetExpr = targetAttr in attrs && `'${attrs[targetAttr]}'`;
+ if (!targetExpr && ast.attrs) {
+ // look at the dynamic attribute counterpart
+ const dynamicTgExpr = ast.attrs[`t-att-${targetAttr}`];
+ if (dynamicTgExpr) {
+ targetExpr = compileExpr(dynamicTgExpr);
+ }
+ }
+ idx = block.insertData(`${fullExpression} === ${targetExpr}`, "prop");
+ attrs[`block-property-${idx}`] = specialInitTargetAttr;
+ }
+ else if (hasDynamicChildren) {
+ const bValueId = generateId("bValue");
+ tModelSelectedExpr = `${bValueId}`;
+ this.define(tModelSelectedExpr, fullExpression);
+ }
+ else {
+ idx = block.insertData(`${fullExpression}`, "prop");
+ attrs[`block-property-${idx}`] = targetAttr;
+ }
+ this.helpers.add("toNumber");
+ let valueCode = `ev.target.${targetAttr}`;
+ valueCode = shouldTrim ? `${valueCode}.trim()` : valueCode;
+ valueCode = shouldNumberize ? `toNumber(${valueCode})` : valueCode;
+ const handler = `[(ev) => { ${fullExpression} = ${valueCode}; }]`;
+ idx = block.insertData(handler, "hdlr");
+ attrs[`block-handler-${idx}`] = eventType;
+ }
+ // event handlers
+ for (let ev in ast.on) {
+ const name = this.generateHandlerCode(ev, ast.on[ev]);
+ const idx = block.insertData(name, "hdlr");
+ attrs[`block-handler-${idx}`] = ev;
+ }
+ // t-ref
+ if (ast.ref) {
+ if (this.dev) {
+ this.helpers.add("makeRefWrapper");
+ this.target.hasRefWrapper = true;
+ }
+ const isDynamic = INTERP_REGEXP.test(ast.ref);
+ let name = `\`${ast.ref}\``;
+ if (isDynamic) {
+ name = replaceDynamicParts(ast.ref, (expr) => this.captureExpression(expr, true));
+ }
+ let setRefStr = `(el) => this.__owl__.setRef((${name}), el)`;
+ if (this.dev) {
+ setRefStr = `refWrapper(${name}, ${setRefStr})`;
+ }
+ const idx = block.insertData(setRefStr, "ref");
+ attrs["block-ref"] = String(idx);
+ }
+ const dom = xmlDoc.createElement(ast.tag);
+ for (const [attr, val] of Object.entries(attrs)) {
+ if (!(attr === "class" && val === "")) {
+ dom.setAttribute(attr, val);
+ }
+ }
+ block.insert(dom);
+ if (ast.content.length) {
+ const initialDom = block.currentDom;
+ block.currentDom = dom;
+ const children = ast.content;
+ for (let i = 0; i < children.length; i++) {
+ const child = ast.content[i];
+ const subCtx = createContext(ctx, {
+ block,
+ index: block.childNumber,
+ forceNewBlock: false,
+ isLast: ctx.isLast && i === children.length - 1,
+ tKeyExpr: ctx.tKeyExpr,
+ nameSpace,
+ tModelSelectedExpr,
+ inPreTag: ctx.inPreTag || ast.tag === "pre",
+ });
+ this.compileAST(child, subCtx);
+ }
+ block.currentDom = initialDom;
+ }
+ if (isNewBlock) {
+ this.insertBlock(`${block.blockName}(ddd)`, block, ctx);
+ // may need to rewrite code!
+ if (block.children.length && block.hasDynamicChildren) {
+ const code = this.target.code;
+ const children = block.children.slice();
+ let current = children.shift();
+ for (let i = codeIdx; i < code.length; i++) {
+ if (code[i].trimStart().startsWith(`const ${current.varName} `)) {
+ code[i] = code[i].replace(`const ${current.varName}`, current.varName);
+ current = children.shift();
+ if (!current)
+ break;
+ }
+ }
+ this.addLine(`let ${block.children.map((c) => c.varName)};`, codeIdx);
+ }
+ }
+ return block.varName;
+ }
+ compileTEsc(ast, ctx) {
+ let { block, forceNewBlock } = ctx;
+ let expr;
+ if (ast.expr === "0") {
+ this.helpers.add("zero");
+ expr = `ctx[zero]`;
+ }
+ else {
+ expr = compileExpr(ast.expr);
+ if (ast.defaultValue) {
+ this.helpers.add("withDefault");
+ expr = `withDefault(${expr}, \`${ast.defaultValue}\`)`;
+ }
+ }
+ if (!block || forceNewBlock) {
+ block = this.createBlock(block, "text", ctx);
+ this.insertBlock(`text(${expr})`, block, { ...ctx, forceNewBlock: forceNewBlock && !block });
+ }
+ else {
+ const idx = block.insertData(expr, "txt");
+ const text = xmlDoc.createElement(`block-text-${idx}`);
+ block.insert(text);
+ }
+ return block.varName;
+ }
+ compileTOut(ast, ctx) {
+ let { block } = ctx;
+ if (block) {
+ this.insertAnchor(block);
+ }
+ block = this.createBlock(block, "html", ctx);
+ let blockStr;
+ if (ast.expr === "0") {
+ this.helpers.add("zero");
+ blockStr = `ctx[zero]`;
+ }
+ else if (ast.body) {
+ let bodyValue = null;
+ bodyValue = BlockDescription.nextBlockId;
+ const subCtx = createContext(ctx);
+ this.compileAST({ type: 3 /* Multi */, content: ast.body }, subCtx);
+ this.helpers.add("safeOutput");
+ blockStr = `safeOutput(${compileExpr(ast.expr)}, b${bodyValue})`;
+ }
+ else {
+ this.helpers.add("safeOutput");
+ blockStr = `safeOutput(${compileExpr(ast.expr)})`;
+ }
+ this.insertBlock(blockStr, block, ctx);
+ return block.varName;
+ }
+ compileTIfBranch(content, block, ctx) {
+ this.target.indentLevel++;
+ let childN = block.children.length;
+ this.compileAST(content, createContext(ctx, { block, index: ctx.index }));
+ if (block.children.length > childN) {
+ // we have some content => need to insert an anchor at correct index
+ this.insertAnchor(block, childN);
+ }
+ this.target.indentLevel--;
+ }
+ compileTIf(ast, ctx, nextNode) {
+ let { block, forceNewBlock } = ctx;
+ const codeIdx = this.target.code.length;
+ const isNewBlock = !block || (block.type !== "multi" && forceNewBlock);
+ if (block) {
+ block.hasDynamicChildren = true;
+ }
+ if (!block || (block.type !== "multi" && forceNewBlock)) {
+ block = this.createBlock(block, "multi", ctx);
+ }
+ this.addLine(`if (${compileExpr(ast.condition)}) {`);
+ this.compileTIfBranch(ast.content, block, ctx);
+ if (ast.tElif) {
+ for (let clause of ast.tElif) {
+ this.addLine(`} else if (${compileExpr(clause.condition)}) {`);
+ this.compileTIfBranch(clause.content, block, ctx);
+ }
+ }
+ if (ast.tElse) {
+ this.addLine(`} else {`);
+ this.compileTIfBranch(ast.tElse, block, ctx);
+ }
+ this.addLine("}");
+ if (isNewBlock) {
+ // note: this part is duplicated from end of compiledomnode:
+ if (block.children.length) {
+ const code = this.target.code;
+ const children = block.children.slice();
+ let current = children.shift();
+ for (let i = codeIdx; i < code.length; i++) {
+ if (code[i].trimStart().startsWith(`const ${current.varName} `)) {
+ code[i] = code[i].replace(`const ${current.varName}`, current.varName);
+ current = children.shift();
+ if (!current)
+ break;
+ }
+ }
+ this.addLine(`let ${block.children.map((c) => c.varName)};`, codeIdx);
+ }
+ // note: this part is duplicated from end of compilemulti:
+ const args = block.children.map((c) => c.varName).join(", ");
+ this.insertBlock(`multi([${args}])`, block, ctx);
+ }
+ return block.varName;
+ }
+ compileTForeach(ast, ctx) {
+ let { block } = ctx;
+ if (block) {
+ this.insertAnchor(block);
+ }
+ block = this.createBlock(block, "list", ctx);
+ this.target.loopLevel++;
+ const loopVar = `i${this.target.loopLevel}`;
+ this.addLine(`ctx = Object.create(ctx);`);
+ const vals = `v_block${block.id}`;
+ const keys = `k_block${block.id}`;
+ const l = `l_block${block.id}`;
+ const c = `c_block${block.id}`;
+ this.helpers.add("prepareList");
+ this.define(`[${keys}, ${vals}, ${l}, ${c}]`, `prepareList(${compileExpr(ast.collection)});`);
+ // Throw errors on duplicate keys in dev mode
+ if (this.dev) {
+ this.define(`keys${block.id}`, `new Set()`);
+ }
+ this.addLine(`for (let ${loopVar} = 0; ${loopVar} < ${l}; ${loopVar}++) {`);
+ this.target.indentLevel++;
+ this.addLine(`ctx[\`${ast.elem}\`] = ${vals}[${loopVar}];`);
+ if (!ast.hasNoFirst) {
+ this.addLine(`ctx[\`${ast.elem}_first\`] = ${loopVar} === 0;`);
+ }
+ if (!ast.hasNoLast) {
+ this.addLine(`ctx[\`${ast.elem}_last\`] = ${loopVar} === ${vals}.length - 1;`);
+ }
+ if (!ast.hasNoIndex) {
+ this.addLine(`ctx[\`${ast.elem}_index\`] = ${loopVar};`);
+ }
+ if (!ast.hasNoValue) {
+ this.addLine(`ctx[\`${ast.elem}_value\`] = ${keys}[${loopVar}];`);
+ }
+ this.define(`key${this.target.loopLevel}`, ast.key ? compileExpr(ast.key) : loopVar);
+ if (this.dev) {
+ // Throw error on duplicate keys in dev mode
+ this.helpers.add("OwlError");
+ this.addLine(`if (keys${block.id}.has(String(key${this.target.loopLevel}))) { throw new OwlError(\`Got duplicate key in t-foreach: \${key${this.target.loopLevel}}\`)}`);
+ this.addLine(`keys${block.id}.add(String(key${this.target.loopLevel}));`);
+ }
+ let id;
+ if (ast.memo) {
+ this.target.hasCache = true;
+ id = generateId();
+ this.define(`memo${id}`, compileExpr(ast.memo));
+ this.define(`vnode${id}`, `cache[key${this.target.loopLevel}];`);
+ this.addLine(`if (vnode${id}) {`);
+ this.target.indentLevel++;
+ this.addLine(`if (shallowEqual(vnode${id}.memo, memo${id})) {`);
+ this.target.indentLevel++;
+ this.addLine(`${c}[${loopVar}] = vnode${id};`);
+ this.addLine(`nextCache[key${this.target.loopLevel}] = vnode${id};`);
+ this.addLine(`continue;`);
+ this.target.indentLevel--;
+ this.addLine("}");
+ this.target.indentLevel--;
+ this.addLine("}");
+ }
+ const subCtx = createContext(ctx, { block, index: loopVar });
+ this.compileAST(ast.body, subCtx);
+ if (ast.memo) {
+ this.addLine(`nextCache[key${this.target.loopLevel}] = Object.assign(${c}[${loopVar}], {memo: memo${id}});`);
+ }
+ this.target.indentLevel--;
+ this.target.loopLevel--;
+ this.addLine(`}`);
+ if (!ctx.isLast) {
+ this.addLine(`ctx = ctx.__proto__;`);
+ }
+ this.insertBlock("l", block, ctx);
+ return block.varName;
+ }
+ compileTKey(ast, ctx) {
+ const tKeyExpr = generateId("tKey_");
+ this.define(tKeyExpr, compileExpr(ast.expr));
+ ctx = createContext(ctx, {
+ tKeyExpr,
+ block: ctx.block,
+ index: ctx.index,
+ });
+ return this.compileAST(ast.content, ctx);
+ }
+ compileMulti(ast, ctx) {
+ let { block, forceNewBlock } = ctx;
+ const isNewBlock = !block || forceNewBlock;
+ let codeIdx = this.target.code.length;
+ if (isNewBlock) {
+ const n = ast.content.filter((c) => c.type !== 6 /* TSet */).length;
+ let result = null;
+ if (n <= 1) {
+ for (let child of ast.content) {
+ const blockName = this.compileAST(child, ctx);
+ result = result || blockName;
+ }
+ return result;
+ }
+ block = this.createBlock(block, "multi", ctx);
+ }
+ let index = 0;
+ for (let i = 0, l = ast.content.length; i < l; i++) {
+ const child = ast.content[i];
+ const isTSet = child.type === 6 /* TSet */;
+ const subCtx = createContext(ctx, {
+ block,
+ index,
+ forceNewBlock: !isTSet,
+ preventRoot: ctx.preventRoot,
+ isLast: ctx.isLast && i === l - 1,
+ });
+ this.compileAST(child, subCtx);
+ if (!isTSet) {
+ index++;
+ }
+ }
+ if (isNewBlock) {
+ if (block.hasDynamicChildren) {
+ if (block.children.length) {
+ const code = this.target.code;
+ const children = block.children.slice();
+ let current = children.shift();
+ for (let i = codeIdx; i < code.length; i++) {
+ if (code[i].trimStart().startsWith(`const ${current.varName} `)) {
+ code[i] = code[i].replace(`const ${current.varName}`, current.varName);
+ current = children.shift();
+ if (!current)
+ break;
+ }
+ }
+ this.addLine(`let ${block.children.map((c) => c.varName)};`, codeIdx);
+ }
+ }
+ const args = block.children.map((c) => c.varName).join(", ");
+ this.insertBlock(`multi([${args}])`, block, ctx);
+ }
+ return block.varName;
+ }
+ compileTCall(ast, ctx) {
+ let { block, forceNewBlock } = ctx;
+ let ctxVar = ctx.ctxVar || "ctx";
+ if (ast.context) {
+ ctxVar = generateId("ctx");
+ this.addLine(`let ${ctxVar} = ${compileExpr(ast.context)};`);
+ }
+ if (ast.body) {
+ this.addLine(`${ctxVar} = Object.create(${ctxVar});`);
+ this.addLine(`${ctxVar}[isBoundary] = 1;`);
+ this.helpers.add("isBoundary");
+ const subCtx = createContext(ctx, { preventRoot: true, ctxVar });
+ const bl = this.compileMulti({ type: 3 /* Multi */, content: ast.body }, subCtx);
+ if (bl) {
+ this.helpers.add("zero");
+ this.addLine(`${ctxVar}[zero] = ${bl};`);
+ }
+ }
+ const isDynamic = INTERP_REGEXP.test(ast.name);
+ const subTemplate = isDynamic ? interpolate(ast.name) : "`" + ast.name + "`";
+ if (block) {
+ if (!forceNewBlock) {
+ this.insertAnchor(block);
+ }
+ }
+ const key = `key + \`${this.generateComponentKey()}\``;
+ if (isDynamic) {
+ const templateVar = generateId("template");
+ if (!this.staticDefs.find((d) => d.id === "call")) {
+ this.staticDefs.push({ id: "call", expr: `app.callTemplate.bind(app)` });
+ }
+ this.define(templateVar, subTemplate);
+ block = this.createBlock(block, "multi", ctx);
+ this.insertBlock(`call(this, ${templateVar}, ${ctxVar}, node, ${key})`, block, {
+ ...ctx,
+ forceNewBlock: !block,
+ });
+ }
+ else {
+ const id = generateId(`callTemplate_`);
+ this.staticDefs.push({ id, expr: `app.getTemplate(${subTemplate})` });
+ block = this.createBlock(block, "multi", ctx);
+ this.insertBlock(`${id}.call(this, ${ctxVar}, node, ${key})`, block, {
+ ...ctx,
+ forceNewBlock: !block,
+ });
+ }
+ if (ast.body && !ctx.isLast) {
+ this.addLine(`${ctxVar} = ${ctxVar}.__proto__;`);
+ }
+ return block.varName;
+ }
+ compileTCallBlock(ast, ctx) {
+ let { block, forceNewBlock } = ctx;
+ if (block) {
+ if (!forceNewBlock) {
+ this.insertAnchor(block);
+ }
+ }
+ block = this.createBlock(block, "multi", ctx);
+ this.insertBlock(compileExpr(ast.name), block, { ...ctx, forceNewBlock: !block });
+ return block.varName;
+ }
+ compileTSet(ast, ctx) {
+ this.target.shouldProtectScope = true;
+ this.helpers.add("isBoundary").add("withDefault");
+ const expr = ast.value ? compileExpr(ast.value || "") : "null";
+ if (ast.body) {
+ this.helpers.add("LazyValue");
+ const bodyAst = { type: 3 /* Multi */, content: ast.body };
+ const name = this.compileInNewTarget("value", bodyAst, ctx);
+ let key = this.target.currentKey(ctx);
+ let value = `new LazyValue(${name}, ctx, this, node, ${key})`;
+ value = ast.value ? (value ? `withDefault(${expr}, ${value})` : expr) : value;
+ this.addLine(`ctx[\`${ast.name}\`] = ${value};`);
+ }
+ else {
+ let value;
+ if (ast.defaultValue) {
+ if (ast.value) {
+ value = `withDefault(${expr}, \`${ast.defaultValue}\`)`;
+ }
+ else {
+ value = `\`${ast.defaultValue}\``;
+ }
+ }
+ else {
+ value = expr;
+ }
+ this.helpers.add("setContextValue");
+ this.addLine(`setContextValue(${ctx.ctxVar || "ctx"}, "${ast.name}", ${value});`);
+ }
+ return null;
+ }
+ generateComponentKey() {
+ const parts = [generateId("__")];
+ for (let i = 0; i < this.target.loopLevel; i++) {
+ parts.push(`\${key${i + 1}}`);
+ }
+ return parts.join("__");
+ }
+ /**
+ * Formats a prop name and value into a string suitable to be inserted in the
+ * generated code. For example:
+ *
+ * Name Value Result
+ * ---------------------------------------------------------
+ * "number" "state" "number: ctx['state']"
+ * "something" "" "something: undefined"
+ * "some-prop" "state" "'some-prop': ctx['state']"
+ * "onClick.bind" "onClick" "onClick: bind(ctx, ctx['onClick'])"
+ */
+ formatProp(name, value) {
+ value = this.captureExpression(value);
+ if (name.includes(".")) {
+ let [_name, suffix] = name.split(".");
+ name = _name;
+ switch (suffix) {
+ case "bind":
+ value = `(${value}).bind(this)`;
+ break;
+ case "alike":
+ break;
+ default:
+ throw new OwlError("Invalid prop suffix");
+ }
+ }
+ name = /^[a-z_]+$/i.test(name) ? name : `'${name}'`;
+ return `${name}: ${value || undefined}`;
+ }
+ formatPropObject(obj) {
+ return Object.entries(obj).map(([k, v]) => this.formatProp(k, v));
+ }
+ getPropString(props, dynProps) {
+ let propString = `{${props.join(",")}}`;
+ if (dynProps) {
+ propString = `Object.assign({}, ${compileExpr(dynProps)}${props.length ? ", " + propString : ""})`;
+ }
+ return propString;
+ }
+ compileComponent(ast, ctx) {
+ let { block } = ctx;
+ // props
+ const hasSlotsProp = "slots" in (ast.props || {});
+ const props = ast.props ? this.formatPropObject(ast.props) : [];
+ // slots
+ let slotDef = "";
+ if (ast.slots) {
+ let ctxStr = "ctx";
+ if (this.target.loopLevel || !this.hasSafeContext) {
+ ctxStr = generateId("ctx");
+ this.helpers.add("capture");
+ this.define(ctxStr, `capture(ctx)`);
+ }
+ let slotStr = [];
+ for (let slotName in ast.slots) {
+ const slotAst = ast.slots[slotName];
+ const params = [];
+ if (slotAst.content) {
+ const name = this.compileInNewTarget("slot", slotAst.content, ctx, slotAst.on);
+ params.push(`__render: ${name}.bind(this), __ctx: ${ctxStr}`);
+ }
+ const scope = ast.slots[slotName].scope;
+ if (scope) {
+ params.push(`__scope: "${scope}"`);
+ }
+ if (ast.slots[slotName].attrs) {
+ params.push(...this.formatPropObject(ast.slots[slotName].attrs));
+ }
+ const slotInfo = `{${params.join(", ")}}`;
+ slotStr.push(`'${slotName}': ${slotInfo}`);
+ }
+ slotDef = `{${slotStr.join(", ")}}`;
+ }
+ if (slotDef && !(ast.dynamicProps || hasSlotsProp)) {
+ this.helpers.add("markRaw");
+ props.push(`slots: markRaw(${slotDef})`);
+ }
+ let propString = this.getPropString(props, ast.dynamicProps);
+ let propVar;
+ if ((slotDef && (ast.dynamicProps || hasSlotsProp)) || this.dev) {
+ propVar = generateId("props");
+ this.define(propVar, propString);
+ propString = propVar;
+ }
+ if (slotDef && (ast.dynamicProps || hasSlotsProp)) {
+ this.helpers.add("markRaw");
+ this.addLine(`${propVar}.slots = markRaw(Object.assign(${slotDef}, ${propVar}.slots))`);
+ }
+ // cmap key
+ const key = this.generateComponentKey();
+ let expr;
+ if (ast.isDynamic) {
+ expr = generateId("Comp");
+ this.define(expr, compileExpr(ast.name));
+ }
+ else {
+ expr = `\`${ast.name}\``;
+ }
+ if (this.dev) {
+ this.addLine(`helpers.validateProps(${expr}, ${propVar}, this);`);
+ }
+ if (block && (ctx.forceNewBlock === false || ctx.tKeyExpr)) {
+ // todo: check the forcenewblock condition
+ this.insertAnchor(block);
+ }
+ let keyArg = `key + \`${key}\``;
+ if (ctx.tKeyExpr) {
+ keyArg = `${ctx.tKeyExpr} + ${keyArg}`;
+ }
+ let id = generateId("comp");
+ const propList = [];
+ for (let p in ast.props || {}) {
+ let [name, suffix] = p.split(".");
+ if (!suffix) {
+ propList.push(`"${name}"`);
+ }
+ }
+ this.staticDefs.push({
+ id,
+ expr: `app.createComponent(${ast.isDynamic ? null : expr}, ${!ast.isDynamic}, ${!!ast.slots}, ${!!ast.dynamicProps}, [${propList}])`,
+ });
+ if (ast.isDynamic) {
+ // If the component class changes, this can cause delayed renders to go
+ // through if the key doesn't change. Use the component name for now.
+ // This means that two component classes with the same name isn't supported
+ // in t-component. We can generate a unique id per class later if needed.
+ keyArg = `(${expr}).name + ${keyArg}`;
+ }
+ let blockExpr = `${id}(${propString}, ${keyArg}, node, this, ${ast.isDynamic ? expr : null})`;
+ if (ast.isDynamic) {
+ blockExpr = `toggler(${expr}, ${blockExpr})`;
+ }
+ // event handling
+ if (ast.on) {
+ blockExpr = this.wrapWithEventCatcher(blockExpr, ast.on);
+ }
+ block = this.createBlock(block, "multi", ctx);
+ this.insertBlock(blockExpr, block, ctx);
+ return block.varName;
+ }
+ wrapWithEventCatcher(expr, on) {
+ this.helpers.add("createCatcher");
+ let name = generateId("catcher");
+ let spec = {};
+ let handlers = [];
+ for (let ev in on) {
+ let handlerId = generateId("hdlr");
+ let idx = handlers.push(handlerId) - 1;
+ spec[ev] = idx;
+ const handler = this.generateHandlerCode(ev, on[ev]);
+ this.define(handlerId, handler);
+ }
+ this.staticDefs.push({ id: name, expr: `createCatcher(${JSON.stringify(spec)})` });
+ return `${name}(${expr}, [${handlers.join(",")}])`;
+ }
+ compileTSlot(ast, ctx) {
+ this.helpers.add("callSlot");
+ let { block } = ctx;
+ let blockString;
+ let slotName;
+ let dynamic = false;
+ let isMultiple = false;
+ if (ast.name.match(INTERP_REGEXP)) {
+ dynamic = true;
+ isMultiple = true;
+ slotName = interpolate(ast.name);
+ }
+ else {
+ slotName = "'" + ast.name + "'";
+ isMultiple = isMultiple || this.slotNames.has(ast.name);
+ this.slotNames.add(ast.name);
+ }
+ const dynProps = ast.attrs ? ast.attrs["t-props"] : null;
+ if (ast.attrs) {
+ delete ast.attrs["t-props"];
+ }
+ let key = this.target.loopLevel ? `key${this.target.loopLevel}` : "key";
+ if (isMultiple) {
+ key = `${key} + \`${this.generateComponentKey()}\``;
+ }
+ const props = ast.attrs ? this.formatPropObject(ast.attrs) : [];
+ const scope = this.getPropString(props, dynProps);
+ if (ast.defaultContent) {
+ const name = this.compileInNewTarget("defaultContent", ast.defaultContent, ctx);
+ blockString = `callSlot(ctx, node, ${key}, ${slotName}, ${dynamic}, ${scope}, ${name}.bind(this))`;
+ }
+ else {
+ if (dynamic) {
+ let name = generateId("slot");
+ this.define(name, slotName);
+ blockString = `toggler(${name}, callSlot(ctx, node, ${key}, ${name}, ${dynamic}, ${scope}))`;
+ }
+ else {
+ blockString = `callSlot(ctx, node, ${key}, ${slotName}, ${dynamic}, ${scope})`;
+ }
+ }
+ // event handling
+ if (ast.on) {
+ blockString = this.wrapWithEventCatcher(blockString, ast.on);
+ }
+ if (block) {
+ this.insertAnchor(block);
+ }
+ block = this.createBlock(block, "multi", ctx);
+ this.insertBlock(blockString, block, { ...ctx, forceNewBlock: false });
+ return block.varName;
+ }
+ compileTTranslation(ast, ctx) {
+ if (ast.content) {
+ return this.compileAST(ast.content, Object.assign({}, ctx, { translate: false }));
+ }
+ return null;
+ }
+ compileTPortal(ast, ctx) {
+ if (!this.staticDefs.find((d) => d.id === "Portal")) {
+ this.staticDefs.push({ id: "Portal", expr: `app.Portal` });
+ }
+ let { block } = ctx;
+ const name = this.compileInNewTarget("slot", ast.content, ctx);
+ const key = this.generateComponentKey();
+ let ctxStr = "ctx";
+ if (this.target.loopLevel || !this.hasSafeContext) {
+ ctxStr = generateId("ctx");
+ this.helpers.add("capture");
+ this.define(ctxStr, `capture(ctx)`);
+ }
+ let id = generateId("comp");
+ this.staticDefs.push({
+ id,
+ expr: `app.createComponent(null, false, true, false, false)`,
+ });
+ const target = compileExpr(ast.target);
+ const blockString = `${id}({target: ${target},slots: {'default': {__render: ${name}.bind(this), __ctx: ${ctxStr}}}}, key + \`${key}\`, node, ctx, Portal)`;
+ if (block) {
+ this.insertAnchor(block);
+ }
+ block = this.createBlock(block, "multi", ctx);
+ this.insertBlock(blockString, block, { ...ctx, forceNewBlock: false });
+ return block.varName;
+ }
+ }
+
+ // -----------------------------------------------------------------------------
+ // Parser
+ // -----------------------------------------------------------------------------
+ const cache = new WeakMap();
+ function parse(xml) {
+ if (typeof xml === "string") {
+ const elem = parseXML(`${xml}`).firstChild;
+ return _parse(elem);
+ }
+ let ast = cache.get(xml);
+ if (!ast) {
+ // we clone here the xml to prevent modifying it in place
+ ast = _parse(xml.cloneNode(true));
+ cache.set(xml, ast);
+ }
+ return ast;
+ }
+ function _parse(xml) {
+ normalizeXML(xml);
+ const ctx = { inPreTag: false, inSVG: false };
+ return parseNode(xml, ctx) || { type: 0 /* Text */, value: "" };
+ }
+ function parseNode(node, ctx) {
+ if (!(node instanceof Element)) {
+ return parseTextCommentNode(node, ctx);
+ }
+ return (parseTDebugLog(node, ctx) ||
+ parseTForEach(node, ctx) ||
+ parseTIf(node, ctx) ||
+ parseTPortal(node, ctx) ||
+ parseTCall(node, ctx) ||
+ parseTCallBlock(node) ||
+ parseTEscNode(node, ctx) ||
+ parseTKey(node, ctx) ||
+ parseTTranslation(node, ctx) ||
+ parseTSlot(node, ctx) ||
+ parseTOutNode(node, ctx) ||
+ parseComponent(node, ctx) ||
+ parseDOMNode(node, ctx) ||
+ parseTSetNode(node, ctx) ||
+ parseTNode(node, ctx));
+ }
+ // -----------------------------------------------------------------------------
+ // tag
+ // -----------------------------------------------------------------------------
+ function parseTNode(node, ctx) {
+ if (node.tagName !== "t") {
+ return null;
+ }
+ return parseChildNodes(node, ctx);
+ }
+ // -----------------------------------------------------------------------------
+ // Text and Comment Nodes
+ // -----------------------------------------------------------------------------
+ const lineBreakRE = /[\r\n]/;
+ function parseTextCommentNode(node, ctx) {
+ if (node.nodeType === Node.TEXT_NODE) {
+ let value = node.textContent || "";
+ if (!ctx.inPreTag && lineBreakRE.test(value) && !value.trim()) {
+ return null;
+ }
+ return { type: 0 /* Text */, value };
+ }
+ else if (node.nodeType === Node.COMMENT_NODE) {
+ return { type: 1 /* Comment */, value: node.textContent || "" };
+ }
+ return null;
+ }
+ // -----------------------------------------------------------------------------
+ // debugging
+ // -----------------------------------------------------------------------------
+ function parseTDebugLog(node, ctx) {
+ if (node.hasAttribute("t-debug")) {
+ node.removeAttribute("t-debug");
+ return {
+ type: 12 /* TDebug */,
+ content: parseNode(node, ctx),
+ };
+ }
+ if (node.hasAttribute("t-log")) {
+ const expr = node.getAttribute("t-log");
+ node.removeAttribute("t-log");
+ return {
+ type: 13 /* TLog */,
+ expr,
+ content: parseNode(node, ctx),
+ };
+ }
+ return null;
+ }
+ // -----------------------------------------------------------------------------
+ // Regular dom node
+ // -----------------------------------------------------------------------------
+ const hasDotAtTheEnd = /\.[\w_]+\s*$/;
+ const hasBracketsAtTheEnd = /\[[^\[]+\]\s*$/;
+ const ROOT_SVG_TAGS = new Set(["svg", "g", "path"]);
+ function parseDOMNode(node, ctx) {
+ const { tagName } = node;
+ const dynamicTag = node.getAttribute("t-tag");
+ node.removeAttribute("t-tag");
+ if (tagName === "t" && !dynamicTag) {
+ return null;
+ }
+ if (tagName.startsWith("block-")) {
+ throw new OwlError(`Invalid tag name: '${tagName}'`);
+ }
+ ctx = Object.assign({}, ctx);
+ if (tagName === "pre") {
+ ctx.inPreTag = true;
+ }
+ const shouldAddSVGNS = ROOT_SVG_TAGS.has(tagName) && !ctx.inSVG;
+ ctx.inSVG = ctx.inSVG || shouldAddSVGNS;
+ const ns = shouldAddSVGNS ? "http://www.w3.org/2000/svg" : null;
+ const ref = node.getAttribute("t-ref");
+ node.removeAttribute("t-ref");
+ const nodeAttrsNames = node.getAttributeNames();
+ let attrs = null;
+ let on = null;
+ let model = null;
+ for (let attr of nodeAttrsNames) {
+ const value = node.getAttribute(attr);
+ if (attr.startsWith("t-on")) {
+ if (attr === "t-on") {
+ throw new OwlError("Missing event name with t-on directive");
+ }
+ on = on || {};
+ on[attr.slice(5)] = value;
+ }
+ else if (attr.startsWith("t-model")) {
+ if (!["input", "select", "textarea"].includes(tagName)) {
+ throw new OwlError("The t-model directive only works with ,