diff --git a/owl.js b/owl.js
index 1621e595..b2ba3273 100644
--- a/owl.js
+++ b/owl.js
@@ -1,158 +1,2267 @@
(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);
+ }
+ moveBefore(other, afterNode) {
+ this.child.moveBefore(other ? other.child : null, 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);
+ }
+
+ 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 define here a simple event bus: it can
- * - emit events
- * - add/remove listeners.
- *
- * This is a useful pattern of communication in many cases. For OWL, each
- * components and stores are event buses.
+ * 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.
*/
- //------------------------------------------------------------------------------
- // EventBus
- //------------------------------------------------------------------------------
- class EventBus {
- constructor() {
- this.subscriptions = {};
+ 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);
}
- /**
- * Add a listener for the 'eventType' events.
- *
- * Note that the 'owner' of this event can be anything, but will more likely
- * be a component or a class. The idea is that the callback will be called with
- * the proper owner bound.
- *
- * Also, the owner should be kind of unique. This will be used to remove the
- * listener.
- */
- on(eventType, owner, callback) {
- if (!callback) {
- throw new Error("Missing callback");
- }
- if (!this.subscriptions[eventType]) {
- this.subscriptions[eventType] = [];
- }
- this.subscriptions[eventType].push({
- owner,
- callback,
- });
+ }
+ function createAttrUpdater(attr) {
+ return function (value) {
+ setAttribute.call(this, attr, value);
+ };
+ }
+ function attrsSetter(attrs) {
+ if (isArray(attrs)) {
+ setAttribute.call(this, attrs[0], attrs[1]);
}
- /**
- * Remove a listener
- */
- off(eventType, owner) {
- const subs = this.subscriptions[eventType];
- if (subs) {
- this.subscriptions[eventType] = subs.filter((s) => s.owner !== owner);
+ else {
+ for (let k in attrs) {
+ setAttribute.call(this, k, attrs[k]);
}
}
- /**
- * Emit an event of type 'eventType'. Any extra arguments will be passed to
- * the listeners callback.
- */
- trigger(eventType, ...args) {
- const subs = this.subscriptions[eventType] || [];
- for (let i = 0, iLen = subs.length; i < iLen; i++) {
- const sub = subs[i];
- sub.callback.call(sub.owner, ...args);
+ }
+ 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);
}
}
- /**
- * Remove all subscriptions.
- */
- clear() {
- this.subscriptions = {};
+ 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) {
+ const words = split.call(key, wordRegexp);
+ for (let word of words) {
+ result[word] = value;
+ }
+ }
+ }
+ return result;
+ case "undefined":
+ return {};
+ case "number":
+ return { [expr]: true };
+ default:
+ return { [expr]: true };
+ }
+ }
+ function setClass(val) {
+ val = val === "" ? {} : toClassObj(val);
+ // add classes
+ const cl = this.classList;
+ for (let c in val) {
+ tokenListAdd.call(cl, c);
+ }
+ }
+ function updateClass(val, oldVal) {
+ oldVal = oldVal === "" ? {} : toClassObj(oldVal);
+ val = val === "" ? {} : toClassObj(val);
+ const cl = this.classList;
+ // remove classes
+ for (let c in oldVal) {
+ if (!(c in val)) {
+ tokenListRemove.call(cl, c);
+ }
+ }
+ // add classes
+ for (let c in val) {
+ if (!(c in oldVal)) {
+ tokenListAdd.call(cl, c);
+ }
+ }
+ }
+ function makePropSetter(name) {
+ return function setProp(value) {
+ this[name] = value;
+ };
+ }
+ function isProp(tag, key) {
+ switch (tag) {
+ case "input":
+ return (key === "checked" ||
+ key === "indeterminate" ||
+ key === "value" ||
+ key === "readonly" ||
+ key === "disabled");
+ case "option":
+ return key === "selected" || key === "disabled";
+ case "textarea":
+ return key === "value" || key === "readonly" || key === "disabled";
+ case "select":
+ return key === "value" || key === "disabled";
+ case "button":
+ case "optgroup":
+ return key === "disabled";
+ }
+ return false;
+ }
+
+ function createEventHandler(rawEvent) {
+ const eventName = rawEvent.split(".")[0];
+ const capture = rawEvent.includes(".capture");
+ if (rawEvent.includes(".synthetic")) {
+ return createSyntheticHandler(eventName, capture);
+ }
+ else {
+ return createElementHandler(eventName, capture);
+ }
+ }
+ // Native listener
+ let nextNativeEventId = 1;
+ function createElementHandler(evName, capture = false) {
+ let eventKey = `__event__${evName}_${nextNativeEventId++}`;
+ if (capture) {
+ eventKey = `${eventKey}_capture`;
+ }
+ function listener(ev) {
+ const currentTarget = ev.currentTarget;
+ if (!currentTarget || !document.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 update(data) {
+ this[eventKey] = data;
+ }
+ return { setup, update };
+ }
+ // 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;
+ }
+ return { setup, update: setup };
+ }
+ 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;
+ }
+ moveBefore(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.moveBefore(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;
+ }
+ moveBefore(other, afterNode) {
+ const target = other ? other.el : afterNode;
+ nodeInsertBefore$2.call(this.parentEl, this.el, target);
+ }
+ 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$1 = () => { };
+ const cache$1 = {};
/**
- * Owl Observer
+ * Compiling blocks is a multi-step process:
*
- * This code contains the logic that allows Owl to observe and react to state
- * changes.
+ * 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
*
- * This is a Observer class that can observe any JS values. The way it works
- * can be summarized thusly:
- * - primitive values are not observed at all
- * - Objects and arrays are observed by replacing them with a Proxy
- * - each object/array metadata are tracked in a weakmap, and keep a revision
- * number
- *
- * Note that this code is loosely inspired by Vue.
+ * @param str
+ * @returns a new block type, that can build concrete blocks
*/
- //------------------------------------------------------------------------------
- // Observer
- //------------------------------------------------------------------------------
- class Observer {
- constructor() {
- this.rev = 1;
- this.allowMutations = true;
- this.weakMap = new WeakMap();
+ function createBlock(str) {
+ if (str in cache$1) {
+ return cache$1[str];
}
- notifyCB() { }
- observe(value, parent) {
- if (value === null ||
- typeof value !== "object" ||
- value instanceof Date ||
- value instanceof Promise) {
- // fun fact: typeof null === 'object'
- return value;
+ // 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;
}
- let metadata = this.weakMap.get(value) || this._observe(value, parent);
- return metadata.proxy;
}
- revNumber(value) {
- const metadata = this.weakMap.get(value);
- return metadata ? metadata.rev : 0;
+ if (node.nodeType === Node.ELEMENT_NODE) {
+ if (node.tagName === "pre") {
+ return;
+ }
}
- _observe(value, parent) {
- var self = this;
- const proxy = new Proxy(value, {
- get(target, k) {
- const targetValue = target[k];
- return self.observe(targetValue, value);
- },
- set(target, key, newVal) {
- const value = target[key];
- if (newVal !== value) {
- if (!self.allowMutations) {
- throw new Error(`Observed state cannot be changed here! (key: "${key}", val: "${newVal}")`);
+ 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) {
+ 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 === "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);
}
- self._updateRevNumber(target);
- target[key] = newVal;
- self.notifyCB();
}
- return true;
- },
- deleteProperty(target, key) {
- if (key in target) {
- delete target[key];
- self._updateRevNumber(target);
- self.notifyCB();
+ }
+ 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 });
}
- return true;
- },
- });
- const metadata = {
- value,
- proxy,
- rev: this.rev,
- parent,
+ 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 Error("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 "attribute": {
+ const refIdx = info.refIdx;
+ let updater;
+ let setter;
+ if (isProp(info.tag, info.name)) {
+ const setProp = makePropSetter(info.name);
+ setter = setProp;
+ updater = setProp;
+ }
+ else 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$1,
+ });
+ }
+ }
+ }
+ // -----------------------------------------------------------------------------
+ // 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);
+ }
+ }
};
- this.weakMap.set(value, metadata);
- this.weakMap.set(metadata.proxy, metadata);
- return metadata;
}
- _updateRevNumber(target) {
- this.rev++;
- let metadata = this.weakMap.get(target);
- let parent = target;
- do {
- metadata = this.weakMap.get(parent);
- metadata.rev++;
- } while ((parent = metadata.parent) && parent !== target);
+ 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;
+ }
+ moveBefore(other, afterNode) {
+ const target = other ? other.el : afterNode;
+ nodeInsertBefore.call(this.parentEl, this.el, target);
+ }
+ 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;
+ }
+ moveBefore(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].moveBefore(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, moveBefore: 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);
+ }
+ }
+ moveBefore(other, afterNode) {
+ const target = other ? other.content[0] : afterNode;
+ const parent = this.parentEl;
+ for (let elem of this.content) {
+ nodeInsertBefore.call(parent, elem, 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 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();
+ }
+
+ /**
+ * Apply default props (only top level).
+ *
+ * Note that this method does modify in place the props
+ */
+ function applyDefaultProps(props, ComponentClass) {
+ const defaultProps = ComponentClass.defaultProps;
+ if (defaultProps) {
+ for (let propName in defaultProps) {
+ if (props[propName] === undefined) {
+ props[propName] = defaultProps[propName];
+ }
+ }
+ }
+ }
+ //------------------------------------------------------------------------------
+ // Prop validation helper
+ //------------------------------------------------------------------------------
+ function getPropDescription(staticProps) {
+ if (staticProps instanceof Array) {
+ return Object.fromEntries(staticProps.map((p) => (p.endsWith("?") ? [p.slice(0, -1), false] : [p, true])));
+ }
+ return staticProps || { "*": true };
+ }
+ /**
+ * 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, parent) {
+ const ComponentClass = typeof name !== "string"
+ ? name
+ : parent.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;
+ }
+ applyDefaultProps(props, ComponentClass);
+ const defaultProps = ComponentClass.defaultProps || {};
+ let propsDef = getPropDescription(ComponentClass.props);
+ const allowAdditionalProps = "*" in propsDef;
+ for (let propName in propsDef) {
+ if (propName === "*") {
+ continue;
+ }
+ const propDef = propsDef[propName];
+ let isMandatory = !!propDef;
+ if (typeof propDef === "object" && "optional" in propDef) {
+ isMandatory = !propDef.optional;
+ }
+ if (isMandatory && propName in defaultProps) {
+ throw new Error(`A default value cannot be defined for a mandatory prop (name: '${propName}', component: ${ComponentClass.name})`);
+ }
+ if (props[propName] === undefined) {
+ if (isMandatory) {
+ throw new Error(`Missing props '${propName}' (component '${ComponentClass.name}')`);
+ }
+ else {
+ continue;
+ }
+ }
+ let isValid;
+ try {
+ isValid = isValidProp(props[propName], propDef);
+ }
+ catch (e) {
+ e.message = `Invalid prop '${propName}' in component ${ComponentClass.name} (${e.message})`;
+ throw e;
+ }
+ if (!isValid) {
+ throw new Error(`Invalid Prop '${propName}' in component '${ComponentClass.name}'`);
+ }
+ }
+ if (!allowAdditionalProps) {
+ for (let propName in props) {
+ if (!(propName in propsDef)) {
+ throw new Error(`Unknown prop '${propName}' given to component '${ComponentClass.name}'`);
+ }
+ }
+ }
+ }
+ /**
+ * Check if an invidual prop value matches its (static) prop definition
+ */
+ function isValidProp(prop, propDef) {
+ if (propDef === true) {
+ return true;
+ }
+ if (typeof propDef === "function") {
+ // Check if a value is constructed by some Constructor. Note that there is a
+ // slight abuse of language: we want to consider primitive values as well.
+ //
+ // So, even though 1 is not an instance of Number, we want to consider that
+ // it is valid.
+ if (typeof prop === "object") {
+ return prop instanceof propDef;
+ }
+ return typeof prop === propDef.name.toLowerCase();
+ }
+ else if (propDef instanceof Array) {
+ // If this code is executed, this means that we want to check if a prop
+ // matches at least one of its descriptor.
+ let result = false;
+ for (let i = 0, iLen = propDef.length; i < iLen; i++) {
+ result = result || isValidProp(prop, propDef[i]);
+ }
+ return result;
+ }
+ // propsDef is an object
+ if (propDef.optional && prop === undefined) {
+ return true;
+ }
+ let result = propDef.type ? isValidProp(prop, propDef.type) : true;
+ if (propDef.validate) {
+ result = result && propDef.validate(prop);
+ }
+ if (propDef.type === Array && propDef.element) {
+ for (let i = 0, iLen = prop.length; i < iLen; i++) {
+ result = result && isValidProp(prop[i], propDef.element);
+ }
+ }
+ if (propDef.type === Object && propDef.shape) {
+ const shape = propDef.shape;
+ for (let key in shape) {
+ result = result && isValidProp(prop[key], shape[key]);
+ }
+ if (result) {
+ for (let propName in prop) {
+ if (!(propName in shape)) {
+ throw new Error(`unknown prop '${propName}'`);
+ }
+ }
+ }
+ }
+ return result;
+ }
+
+ /**
+ * 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;
+ callback();
+ // 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
+ await Promise.resolve();
+ called = false;
+ }
+ };
+ }
+ function validateTarget(target) {
+ if (!(target instanceof HTMLElement)) {
+ throw new Error("Cannot mount component: the target is not a valid DOM element");
+ }
+ if (!document.body.contains(target)) {
+ throw new Error("Cannot mount a component on a detached dom node");
+ }
+ }
+ 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 Error("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);
+ }
+
+ /**
+ * 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 && ctx.props.slots) || {};
+ const { __render, __ctx, __scope } = slots[name] || {};
+ const slotScope = Object.create(__ctx || {});
+ if (__scope) {
+ slotScope[__scope] = extra || {};
+ }
+ const slotBDom = __render ? __render.call(__ctx.__owl__.component, slotScope, parent, key) : null;
+ if (defaultContent) {
+ let child1 = undefined;
+ let child2 = undefined;
+ if (slotBDom) {
+ child1 = dynamic ? toggler(name, slotBDom) : slotBDom;
+ }
+ else {
+ child2 = defaultContent.call(ctx.__owl__.component, ctx, parent, key);
+ }
+ return multi([child1, child2]);
+ }
+ return slotBDom || text("");
+ }
+ function capture(ctx) {
+ const component = ctx.__owl__.component;
+ const result = Object.create(component);
+ 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 Error("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$1(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, node) {
+ this.fn = fn;
+ this.ctx = capture(ctx);
+ this.node = node;
+ }
+ evaluate() {
+ return this.fn(this.ctx, this.node);
+ }
+ toString() {
+ return this.evaluate().toString();
+ }
+ }
+ /*
+ * Safely outputs `value` as a block depending on the nature of `value`
+ */
+ function safeOutput(value) {
+ if (!value) {
+ return value;
+ }
+ let safeKey;
+ let block;
+ 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 || typeof value === "string") {
+ safeKey = "string_unsafe";
+ block = text(value);
+ }
+ else {
+ // Assuming it is a block
+ safeKey = "block_safe";
+ block = value;
+ }
+ return toggler(safeKey, block);
+ }
+ let boundFunctions = new WeakMap();
+ function bind(ctx, fn) {
+ let component = ctx.__owl__.component;
+ let boundFnMap = boundFunctions.get(component);
+ if (!boundFnMap) {
+ boundFnMap = new WeakMap();
+ boundFunctions.set(component, boundFnMap);
+ }
+ let boundFn = boundFnMap.get(fn);
+ if (!boundFn) {
+ boundFn = fn.bind(component);
+ boundFnMap.set(fn, boundFn);
+ }
+ return boundFn;
+ }
+ function multiRefSetter(refs, name) {
+ let count = 0;
+ return (el) => {
+ if (el) {
+ count++;
+ if (count > 1) {
+ throw new Error("Cannot have 2 elements with same ref name at the same time");
+ }
+ }
+ if (count === 0 || el) {
+ refs[name] = el;
+ }
+ };
+ }
+ const UTILS = {
+ withDefault,
+ zero: Symbol("zero"),
+ isBoundary,
+ callSlot,
+ capture,
+ withKey,
+ prepareList,
+ setContextValue,
+ multiRefSetter,
+ shallowEqual: shallowEqual$1,
+ toNumber,
+ validateProps,
+ LazyValue,
+ safeOutput,
+ bind,
+ };
+
+ const mainEventHandler = (data, ev, currentTarget) => {
+ const { data: _data, modifiers } = filterOutModifiersFromData(data);
+ data = _data;
+ let stopped = false;
+ if (modifiers.length) {
+ let selfMode = false;
+ const isSelf = ev.target === currentTarget;
+ for (const mod of modifiers) {
+ switch (mod) {
+ case "self":
+ selfMode = true;
+ if (isSelf) {
+ continue;
+ }
+ else {
+ return stopped;
+ }
+ case "prevent":
+ if ((selfMode && isSelf) || !selfMode)
+ ev.preventDefault();
+ continue;
+ case "stop":
+ if ((selfMode && isSelf) || !selfMode)
+ ev.stopPropagation();
+ stopped = true;
+ continue;
+ }
+ }
+ }
+ // If handler is empty, the array slot 0 will also be empty, and data will not have the property 0
+ // We check this rather than data[0] being truthy (or typeof function) so that it crashes
+ // as expected when there is a handler expression that evaluates to a falsy value
+ if (Object.hasOwnProperty.call(data, 0)) {
+ const handler = data[0];
+ if (typeof handler !== "function") {
+ throw new Error(`Invalid handler (expected a function, received: '${handler}')`);
+ }
+ let node = data[1] ? data[1].__owl__ : null;
+ if (node ? node.status === 1 /* MOUNTED */ : true) {
+ handler.call(node ? node.component : null, ev);
+ }
+ }
+ return stopped;
+ };
+
+ // Maps fibers to thrown errors
+ const fibersInError = new WeakMap();
+ const nodeErrorHandlers = new WeakMap();
+ function _handleError(node, error, isFirstRound = false) {
+ if (!node) {
+ return false;
+ }
+ const fiber = node.fiber;
+ if (fiber) {
+ fibersInError.set(fiber, error);
+ }
+ const errorHandlers = nodeErrorHandlers.get(node);
+ if (errorHandlers) {
+ let stopped = false;
+ // execute in the opposite order
+ for (let i = errorHandlers.length - 1; i >= 0; i--) {
+ try {
+ errorHandlers[i](error);
+ stopped = true;
+ break;
+ }
+ catch (e) {
+ error = e;
+ }
+ }
+ if (stopped) {
+ if (isFirstRound && fiber && fiber.node.fiber) {
+ fiber.root.counter--;
+ }
+ return true;
+ }
+ }
+ return _handleError(node.parent, error);
+ }
+ function handleError(params) {
+ const error = params.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, true);
+ if (!handled) {
+ console.warn(`[Owl] Unhandled error. Destroying the root component`);
+ try {
+ node.app.destroy();
+ }
+ catch (e) {
+ console.error(e);
+ }
+ }
+ }
+
+ function makeChildFiber(node, parent) {
+ let current = node.fiber;
+ if (current) {
+ let root = parent.root;
+ cancelFibers(root, current.children);
+ current.root = null;
+ }
+ return new Fiber(node, parent);
+ }
+ function makeRootFiber(node) {
+ let current = node.fiber;
+ if (current) {
+ let root = current.root;
+ root.counter -= cancelFibers(root, current.children);
+ current.children = [];
+ root.counter++;
+ 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;
+ }
+ /**
+ * @returns number of not-yet rendered fibers cancelled
+ */
+ function cancelFibers(root, fibers) {
+ let result = 0;
+ for (let fiber of fibers) {
+ fiber.node.fiber = null;
+ fiber.root = root;
+ if (!fiber.bdom) {
+ result++;
+ }
+ result += cancelFibers(root, fiber.children);
+ }
+ return result;
+ }
+ class Fiber {
+ constructor(node, parent) {
+ this.bdom = null;
+ this.children = [];
+ this.appliedToDom = false;
+ this.node = node;
+ this.parent = parent;
+ if (parent) {
+ const root = parent.root;
+ root.counter++;
+ this.root = root;
+ parent.children.push(this);
+ }
+ else {
+ this.root = this;
+ }
+ }
+ }
+ 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;
+ handleError({ fiber: current || this, error: e });
+ }
+ }
+ }
+ 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.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) {
+ handleError({ fiber: current, error: e });
+ }
+ }
+ }
+
+ let currentNode = null;
+ function getCurrent() {
+ if (!currentNode) {
+ throw new Error("No active component (a hook function should only be called in 'setup')");
+ }
+ return currentNode;
+ }
+ function useComponent() {
+ return currentNode.component;
+ }
+ function component(name, props, key, ctx, parent) {
+ let node = ctx.children[key];
+ let isDynamic = typeof name !== "string";
+ if (node) {
+ if (node.status < 1 /* MOUNTED */) {
+ node.destroy();
+ node = undefined;
+ }
+ else if (node.status === 2 /* DESTROYED */) {
+ node = undefined;
+ }
+ }
+ if (isDynamic && node && node.component.constructor !== name) {
+ node = undefined;
+ }
+ const parentFiber = ctx.fiber;
+ if (node) {
+ node.updateAndRender(props, parentFiber);
+ }
+ else {
+ // new component
+ let C;
+ if (isDynamic) {
+ C = name;
+ }
+ else {
+ C = parent.constructor.components[name];
+ if (!C) {
+ throw new Error(`Cannot find the definition of component "${name}"`);
+ }
+ }
+ node = new ComponentNode(C, props, ctx.app, ctx);
+ ctx.children[key] = node;
+ const fiber = makeChildFiber(node, parentFiber);
+ node.initiateRender(fiber);
+ }
+ return node;
+ }
+ class ComponentNode {
+ constructor(C, props, app, parent) {
+ this.fiber = null;
+ this.bdom = null;
+ this.status = 0 /* NEW */;
+ 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 || null;
+ this.level = parent ? parent.level + 1 : 0;
+ applyDefaultProps(props, C);
+ const env = (parent && parent.childEnv) || app.env;
+ this.childEnv = env;
+ this.component = new C(props, env, this);
+ this.renderFn = app.getTemplate(C.template).bind(this.component, this.component, 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) {
+ handleError({ node: this, error: e });
+ return;
+ }
+ if (this.status === 0 /* NEW */ && this.fiber === fiber) {
+ this._render(fiber);
+ }
+ }
+ async render() {
+ let current = this.fiber;
+ if (current && current.root.locked) {
+ await Promise.resolve();
+ // situation may have changed after the microtask tick
+ current = this.fiber;
+ }
+ if (current && !current.bdom && !fibersInError.has(current)) {
+ return;
+ }
+ if (!this.bdom && !current) {
+ return;
+ }
+ const fiber = makeRootFiber(this);
+ 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)) {
+ this._render(fiber);
+ }
+ }
+ _render(fiber) {
+ try {
+ fiber.bdom = this.renderFn();
+ fiber.root.counter--;
+ }
+ catch (e) {
+ handleError({ node: this, error: e });
+ }
+ }
+ 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();
+ }
+ for (let cb of this.willDestroy) {
+ cb.call(component);
+ }
+ this.status = 2 /* DESTROYED */;
+ }
+ async updateAndRender(props, parentFiber) {
+ // update
+ const fiber = makeChildFiber(this, parentFiber);
+ this.fiber = fiber;
+ const component = this.component;
+ applyDefaultProps(props, component.constructor);
+ const prom = Promise.all(this.willUpdateProps.map((f) => f.call(component, props)));
+ await prom;
+ if (fiber !== this.fiber) {
+ return;
+ }
+ component.props = props;
+ this._render(fiber);
+ 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;
+ }
+ }
+ // ---------------------------------------------------------------------------
+ // 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.fiber = null;
+ }
+ moveBefore(other, afterNode) {
+ this.bdom.moveBefore(other ? other.bdom : null, afterNode);
+ }
+ patch() {
+ const hasChildren = Object.keys(this.children).length > 0;
+ this.bdom.patch(this.fiber.bdom, hasChildren);
+ if (hasChildren) {
+ this.cleanOutdatedChildren();
+ }
+ this.fiber.appliedToDom = true;
+ this.fiber = null;
+ }
+ beforeRemove() {
+ this._destroy();
+ }
+ remove() {
+ this.bdom.remove();
+ }
+ cleanOutdatedChildren() {
+ const children = this.children;
+ for (const key in children) {
+ const node = children[key];
+ const status = node.status;
+ if (status !== 1 /* MOUNTED */) {
+ delete children[key];
+ if (status !== 2 /* DESTROYED */) {
+ node.destroy();
+ }
+ }
+ }
+ }
+ }
+
+ // -----------------------------------------------------------------------------
+ // hooks
+ // -----------------------------------------------------------------------------
+ function onWillStart(fn) {
+ const node = getCurrent();
+ node.willStart.push(fn.bind(node.component));
+ }
+ function onWillUpdateProps(fn) {
+ const node = getCurrent();
+ node.willUpdateProps.push(fn.bind(node.component));
+ }
+ function onMounted(fn) {
+ const node = getCurrent();
+ node.mounted.push(fn.bind(node.component));
+ }
+ function onWillPatch(fn) {
+ const node = getCurrent();
+ node.willPatch.unshift(fn.bind(node.component));
+ }
+ function onPatched(fn) {
+ const node = getCurrent();
+ node.patched.push(fn.bind(node.component));
+ }
+ function onWillUnmount(fn) {
+ const node = getCurrent();
+ node.willUnmount.unshift(fn.bind(node.component));
+ }
+ function onWillDestroy(fn) {
+ const node = getCurrent();
+ node.willDestroy.push(fn.bind(node.component));
+ }
+ function onWillRender(fn) {
+ const node = getCurrent();
+ const renderFn = node.renderFn;
+ node.renderFn = () => {
+ fn.call(node.component);
+ return renderFn();
+ };
+ }
+ function onRendered(fn) {
+ const node = getCurrent();
+ const renderFn = node.renderFn;
+ node.renderFn = () => {
+ const result = renderFn();
+ fn.call(node.component);
+ 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));
}
/**
@@ -358,9 +2467,8 @@
* 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, scope) {
+ function compileExprToArray(expr) {
const localVars = new Set();
- scope = Object.create(scope);
const tokens = tokenize(expr);
let i = 0;
let stack = []; // to track last opening [ or {
@@ -385,7 +2493,7 @@
if (groupType === "LEFT_BRACE" &&
isLeftSeparator(prevToken) &&
isRightSeparator(nextToken)) {
- tokens.splice(i + 1, 0, { type: "COLON", value: ":" }, Object.assign({}, token));
+ tokens.splice(i + 1, 0, { type: "COLON", value: ":" }, { ...token });
nextToken = tokens[i + 1];
}
if (prevToken.type === "OPERATOR" && prevToken.value === ".") {
@@ -399,7 +2507,7 @@
}
}
if (token.type === "TEMPLATE_STRING") {
- token.value = token.replace((expr) => compileExpr(expr, scope));
+ token.value = token.replace((expr) => compileExpr(expr));
}
if (nextToken && nextToken.type === "OPERATOR" && nextToken.value === "=>") {
if (token.type === "RIGHT_PAREN") {
@@ -407,25 +2515,20 @@
while (j > 0 && tokens[j].type !== "LEFT_PAREN") {
if (tokens[j].type === "SYMBOL" && tokens[j].originalValue) {
tokens[j].value = tokens[j].originalValue;
- scope[tokens[j].value] = { id: tokens[j].value, expr: tokens[j].value };
- localVars.add(tokens[j].value);
+ localVars.add(tokens[j].value); //] = { id: tokens[j].value, expr: tokens[j].value };
}
j--;
}
}
else {
- scope[token.value] = { id: token.value, expr: token.value };
- localVars.add(token.value);
+ localVars.add(token.value); //] = { id: token.value, expr: token.value };
}
}
if (isVar) {
token.varName = token.value;
- if (token.value in scope && "id" in scope[token.value]) {
- token.value = scope[token.value].expr;
- }
- else {
+ if (!localVars.has(token.value)) {
token.originalValue = token.value;
- token.value = `scope['${token.value}']`;
+ token.value = `ctx['${token.value}']`;
}
}
i++;
@@ -433,1111 +2536,1853 @@
// 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" && localVars.has(token.value)) {
+ if (token.type === "SYMBOL" && token.varName && localVars.has(token.value)) {
+ token.originalValue = token.value;
+ token.value = `_${token.value}`;
token.isLocal = true;
}
}
return tokens;
}
- function compileExpr(expr, scope) {
- return compileExprToArray(expr, scope)
+ function compileExpr(expr) {
+ return compileExprToArray(expr)
.map((t) => t.value)
.join("");
+ }
+ const INTERP_REGEXP = /\{\{.*?\}\}/g;
+ const INTERP_GROUP_REGEXP = /\{\{.*?\}\}/g;
+ function interpolate(s) {
+ let matches = s.match(INTERP_REGEXP);
+ if (matches && matches[0].length === s.length) {
+ return `(${compileExpr(s.slice(2, -2))})`;
+ }
+ let r = s.replace(INTERP_GROUP_REGEXP, (s) => "${" + compileExpr(s.slice(2, -2)) + "}");
+ return "`" + r + "`";
}
- const INTERP_REGEXP = /\{\{.*?\}\}/g;
- //------------------------------------------------------------------------------
- // Compilation Context
- //------------------------------------------------------------------------------
- class CompilationContext {
+ // 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"]);
+ // -----------------------------------------------------------------------------
+ // 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;
+ }
+ static generateId(prefix) {
+ this.nextDataIds[prefix] = (this.nextDataIds[prefix] || 0) + 1;
+ return prefix + this.nextDataIds[prefix];
+ }
+ insertData(str, prefix = "d") {
+ const id = BlockDescription.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;
+ BlockDescription.nextDataIds = {};
+ 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) {
- this.code = [];
- this.variables = {};
- this.escaping = false;
- this.parentNode = null;
- this.parentTextNode = null;
- this.rootNode = null;
this.indentLevel = 0;
- this.shouldDefineParent = false;
- this.shouldDefineScope = false;
- this.protectedScopeNumber = 0;
- this.shouldDefineQWeb = false;
- this.shouldDefineUtils = false;
- this.shouldDefineRefs = false;
- this.shouldDefineResult = true;
- this.loopNumber = 0;
- this.inPreTag = false;
- this.allowMultipleRoots = false;
- this.hasParentWidget = false;
- this.hasKey0 = false;
- this.keyStack = [];
- this.rootContext = this;
- this.templateName = name || "noname";
- this.addLine("let h = this.h;");
+ this.loopLevel = 0;
+ this.code = [];
+ this.hasRoot = false;
+ this.hasCache = false;
+ this.hasRef = false;
+ // maps ref name to [id, expr]
+ this.refInfo = {};
+ this.shouldProtectScope = false;
+ this.name = name;
}
- generateID() {
- return CompilationContext.nextID++;
- }
- /**
- * This method generates a "template key", which is basically a unique key
- * which depends on the currently set keys, and on the iteration numbers (if
- * we are in a loop).
- *
- * Such a key is necessary when we need to associate an id to some element
- * generated by a template (for example, a component)
- */
- generateTemplateKey(prefix = "") {
- const id = this.generateID();
- if (this.loopNumber === 0 && !this.hasKey0) {
- return `'${prefix}__${id}__'`;
+ addLine(line, idx) {
+ const prefix = new Array(this.indentLevel + 2).join(" ");
+ if (idx === undefined) {
+ this.code.push(prefix + line);
}
- let key = `\`${prefix}__${id}__`;
- let start = this.hasKey0 ? 0 : 1;
- for (let i = start; i < this.loopNumber + 1; i++) {
- key += `\${key${i}}__`;
+ else {
+ this.code.splice(idx, 0, prefix + line);
}
- this.addLine(`let k${id} = ${key}\`;`);
- return `k${id}`;
}
generateCode() {
- if (this.shouldDefineResult) {
- this.code.unshift(" let result;");
- }
- if (this.shouldDefineScope) {
- this.code.unshift(" let scope = Object.create(context);");
- }
- if (this.shouldDefineRefs) {
- this.code.unshift(" context.__owl__.refs = context.__owl__.refs || {};");
- }
- if (this.shouldDefineParent) {
- if (this.hasParentWidget) {
- this.code.unshift(" let parent = extra.parent;");
- }
- else {
- this.code.unshift(" let parent = context;");
+ let result = [];
+ result.push(`function ${this.name}(ctx, node, key = "") {`);
+ if (this.hasRef) {
+ result.push(` const refs = ctx.__owl__.refs;`);
+ for (let name in this.refInfo) {
+ const [id, expr] = this.refInfo[name];
+ result.push(` const ${id} = ${expr};`);
}
}
- if (this.shouldDefineQWeb) {
- this.code.unshift(" let QWeb = this.constructor;");
+ if (this.shouldProtectScope) {
+ result.push(` ctx = Object.create(ctx);`);
+ result.push(` ctx[isBoundary] = 1`);
}
- if (this.shouldDefineUtils) {
- this.code.unshift(" let utils = this.constructor.utils;");
+ if (this.hasCache) {
+ result.push(` let cache = ctx.cache || {};`);
+ result.push(` let nextCache = ctx.cache = {};`);
}
- return this.code;
+ for (let line of this.code) {
+ result.push(line);
+ }
+ if (!this.hasRoot) {
+ result.push(`return text('');`);
+ }
+ result.push(`}`);
+ return result.join("\n ");
}
- withParent(node) {
- if (!this.allowMultipleRoots &&
- this === this.rootContext &&
- (this.parentNode || this.parentTextNode)) {
- throw new Error("A template should not have more than one root node");
+ }
+ const TRANSLATABLE_ATTRS = ["label", "title", "placeholder", "alt"];
+ const translationRE = /^(\s*)([\s\S]+?)(\s*)$/;
+ class CodeGenerator {
+ constructor(ast, options) {
+ this.blocks = [];
+ this.ids = {};
+ this.nextBlockId = 1;
+ this.isDebug = false;
+ this.targets = [];
+ this.target = new CodeTarget("template");
+ this.translatableAttributes = TRANSLATABLE_ATTRS;
+ this.staticCalls = [];
+ 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];
}
- if (!this.rootContext.rootNode) {
- this.rootContext.rootNode = node;
+ 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;
+ BlockDescription.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, component, comment } = bdom;`,
+ ];
+ if (this.helpers.size) {
+ mainCode.push(`let { ${[...this.helpers].join(", ")} } = helpers;`);
}
- if (!this.parentNode && this.rootContext.shouldDefineResult) {
- this.addLine(`result = vn${node};`);
+ if (this.templateName) {
+ mainCode.push(`// Template name: "${this.templateName}"`);
}
- return this.subContext("parentNode", node);
+ for (let { id, template } of this.staticCalls) {
+ mainCode.push(`const ${id} = getTemplate(${template});`);
+ }
+ // define all blocks
+ if (this.blocks.length) {
+ mainCode.push(``);
+ for (let block of this.blocks) {
+ if (block.dom) {
+ let xmlString = block.asXmlString();
+ 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;
}
- subContext(key, value) {
- const newContext = Object.create(this);
- newContext[key] = value;
- return newContext;
- }
- indent() {
- this.rootContext.indentLevel++;
- }
- dedent() {
- this.rootContext.indentLevel--;
+ compileInNewTarget(prefix, ast, ctx) {
+ const name = this.generateId(prefix);
+ const initialTarget = this.target;
+ const target = new CodeTarget(name);
+ this.targets.push(target);
+ this.target = target;
+ const subCtx = createContext(ctx);
+ this.compileAST(ast, subCtx);
+ this.target = initialTarget;
+ return name;
}
addLine(line) {
- const prefix = new Array(this.indentLevel + 2).join(" ");
- this.code.push(prefix + line);
- return this.code.length - 1;
+ this.target.addLine(line);
}
- addIf(condition) {
- this.addLine(`if (${condition}) {`);
- this.indent();
+ generateId(prefix = "") {
+ this.ids[prefix] = (this.ids[prefix] || 0) + 1;
+ return prefix + this.ids[prefix];
}
- addElse() {
- this.dedent();
- this.addLine("} else {");
- this.indent();
+ insertAnchor(block) {
+ const tag = `block-child-${block.children.length}`;
+ const anchor = xmlDoc.createElement(tag);
+ block.insert(anchor);
}
- closeIf() {
- this.dedent();
- this.addLine("}");
+ 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;
}
- getValue(val) {
- return val in this.variables ? this.getValue(this.variables[val]) : val;
+ insertBlock(expression, block, ctx) {
+ let blockExpr = block.generateExpr(expression);
+ const tKeyExpr = ctx.tKeyExpr;
+ if (block.parentVar) {
+ let keyArg = `key${this.target.loopLevel}`;
+ if (tKeyExpr) {
+ keyArg = `${tKeyExpr} + ${keyArg}`;
+ }
+ this.helpers.add("withKey");
+ this.addLine(`${block.parentVar}[${ctx.index}] = withKey(${blockExpr}, ${keyArg});`);
+ return;
+ }
+ if (tKeyExpr) {
+ blockExpr = `toggler(${tKeyExpr}, ${blockExpr})`;
+ }
+ if (block.isRoot && !ctx.preventRoot) {
+ this.addLine(`return ${blockExpr};`);
+ }
+ else {
+ this.addLine(`let ${block.varName} = ${blockExpr};`);
+ }
}
/**
- * Prepare an expression for being consumed at render time. Its main job
- * is to
- * - replace unknown variables by a lookup in the context
- * - replace already defined variables by their internal name
+ * 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
*/
- formatExpression(expr) {
- this.rootContext.shouldDefineScope = true;
- return compileExpr(expr, this.variables);
- }
- captureExpression(expr) {
- this.rootContext.shouldDefineScope = true;
- const argId = this.generateID();
- const tokens = compileExprToArray(expr, this.variables);
- const done = new Set();
+ captureExpression(expr, forceCapture = false) {
+ if (!forceCapture && !expr.includes("=>")) {
+ return compileExpr(expr);
+ }
+ const tokens = compileExprToArray(expr);
+ const mapping = new Map();
return tokens
- .map((tok, i) => {
- // "this" in captured expressions should be the current component
- if (tok.value === "this") {
- if (!done.has("this")) {
- done.add("this");
- this.addLine(`const this_${argId} = utils.getComponent(context);`);
+ .map((tok) => {
+ if (tok.varName && !tok.isLocal) {
+ if (!mapping.has(tok.varName)) {
+ const varId = this.generateId("v");
+ mapping.set(tok.varName, varId);
+ this.addLine(`const ${varId} = ${tok.value};`);
}
- tok.value = `this_${argId}`;
- }
- // Variables that should be looked up in the scope. isLocal is for arrow
- // function arguments that should stay untouched (eg "ev => ev" should
- // not become "const ev_1 = scope['ev']; ev_1 => ev_1")
- if (tok.varName &&
- !tok.isLocal &&
- // HACK: for backwards compatibility, we don't capture bare methods
- // this allows them to be called with the rendering context/scope
- // as their this value.
- (!tokens[i + 1] || tokens[i + 1].type !== "LEFT_PAREN")) {
- if (!done.has(tok.varName)) {
- done.add(tok.varName);
- this.addLine(`const ${tok.varName}_${argId} = ${tok.value};`);
- }
- tok.value = `${tok.varName}_${argId}`;
+ tok.value = mapping.get(tok.varName);
}
return tok.value;
})
.join("");
}
+ compileAST(ast, ctx) {
+ switch (ast.type) {
+ case 1 /* Comment */:
+ this.compileComment(ast, ctx);
+ break;
+ case 0 /* Text */:
+ this.compileText(ast, ctx);
+ break;
+ case 2 /* DomNode */:
+ this.compileTDomNode(ast, ctx);
+ break;
+ case 4 /* TEsc */:
+ this.compileTEsc(ast, ctx);
+ break;
+ case 8 /* TOut */:
+ this.compileTOut(ast, ctx);
+ break;
+ case 5 /* TIf */:
+ this.compileTIf(ast, ctx);
+ break;
+ case 9 /* TForEach */:
+ this.compileTForeach(ast, ctx);
+ break;
+ case 10 /* TKey */:
+ this.compileTKey(ast, ctx);
+ break;
+ case 3 /* Multi */:
+ this.compileMulti(ast, ctx);
+ break;
+ case 7 /* TCall */:
+ this.compileTCall(ast, ctx);
+ break;
+ case 15 /* TCallBlock */:
+ this.compileTCallBlock(ast, ctx);
+ break;
+ case 6 /* TSet */:
+ this.compileTSet(ast, ctx);
+ break;
+ case 11 /* TComponent */:
+ this.compileComponent(ast, ctx);
+ break;
+ case 12 /* TDebug */:
+ this.compileDebug(ast, ctx);
+ break;
+ case 13 /* TLog */:
+ this.compileLog(ast, ctx);
+ break;
+ case 14 /* TSlot */:
+ this.compileTSlot(ast, ctx);
+ break;
+ case 16 /* TTranslation */:
+ this.compileTTranslation(ast, ctx);
+ break;
+ case 17 /* TPortal */:
+ this.compileTPortal(ast, ctx);
+ }
+ }
+ compileDebug(ast, ctx) {
+ this.addLine(`debugger;`);
+ if (ast.content) {
+ this.compileAST(ast.content, ctx);
+ }
+ }
+ compileLog(ast, ctx) {
+ this.addLine(`console.log(${compileExpr(ast.expr)});`);
+ if (ast.content) {
+ this.compileAST(ast.content, ctx);
+ }
+ }
+ 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);
+ }
+ }
+ 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 (!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));
+ }
+ }
+ generateHandlerCode(rawEvent, handler) {
+ const modifiers = rawEvent
+ .split(".")
+ .slice(1)
+ .map((m) => {
+ if (!MODS.has(m)) {
+ throw new Error(`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 = this.generateId("tag");
+ this.addLine(`let ${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")) {
+ expr = compileExpr(ast.attrs[key]);
+ const idx = block.insertData(expr, "attr");
+ if (key === "t-att") {
+ attrs[`block-attributes`] = String(idx);
+ }
+ else {
+ attrName = key.slice(6);
+ 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";
+ }
+ }
+ // 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) {
+ this.target.hasRef = true;
+ const isDynamic = INTERP_REGEXP.test(ast.ref);
+ if (isDynamic) {
+ const str = ast.ref.replace(INTERP_REGEXP, (expr) => "${" + this.captureExpression(expr.slice(2, -2), true) + "}");
+ const idx = block.insertData(`(el) => refs[\`${str}\`] = el`, "ref");
+ attrs["block-ref"] = String(idx);
+ }
+ else {
+ let name = ast.ref;
+ if (name in this.target.refInfo) {
+ // ref has already been defined
+ this.helpers.add("multiRefSetter");
+ const info = this.target.refInfo[name];
+ const index = block.data.push(info[0]) - 1;
+ attrs["block-ref"] = String(index);
+ info[1] = `multiRefSetter(refs, \`${name}\`)`;
+ }
+ else {
+ let id = this.generateId("ref");
+ this.target.refInfo[name] = [id, `(el) => refs[\`${name}\`] = el`];
+ const index = block.data.push(id) - 1;
+ attrs["block-ref"] = String(index);
+ }
+ }
+ }
+ // t-model
+ let tModelSelectedExpr;
+ if (ast.model) {
+ const { hasDynamicChildren, baseExpr, expr, eventType, shouldNumberize, shouldTrim, targetAttr, specialInitTargetAttr, } = ast.model;
+ const baseExpression = compileExpr(baseExpr);
+ const bExprId = this.generateId("bExpr");
+ this.addLine(`const ${bExprId} = ${baseExpression};`);
+ const expression = compileExpr(expr);
+ const exprId = this.generateId("expr");
+ this.addLine(`const ${exprId} = ${expression};`);
+ const fullExpression = `${bExprId}[${exprId}]`;
+ let idx;
+ if (specialInitTargetAttr) {
+ idx = block.insertData(`${fullExpression} === '${attrs[targetAttr]}'`, "attr");
+ attrs[`block-attribute-${idx}`] = specialInitTargetAttr;
+ }
+ else if (hasDynamicChildren) {
+ const bValueId = this.generateId("bValue");
+ tModelSelectedExpr = `${bValueId}`;
+ this.addLine(`let ${tModelSelectedExpr} = ${fullExpression}`);
+ }
+ else {
+ idx = block.insertData(`${fullExpression}`, "attr");
+ attrs[`block-attribute-${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;
+ }
+ 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,
+ });
+ 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(`let ${current.varName} `)) {
+ code[i] = code[i].replace(`let ${current.varName}`, current.varName);
+ current = children.shift();
+ if (!current)
+ break;
+ }
+ }
+ this.target.addLine(`let ${block.children.map((c) => c.varName)};`, codeIdx);
+ }
+ }
+ }
+ 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);
+ }
+ }
+ compileTOut(ast, ctx) {
+ let { block } = ctx;
+ if (block) {
+ this.insertAnchor(block);
+ }
+ block = this.createBlock(block, "html", ctx);
+ this.helpers.add(ast.expr === "0" ? "zero" : "safeOutput");
+ let expr = ast.expr === "0" ? "ctx[zero]" : `safeOutput(${compileExpr(ast.expr)})`;
+ if (ast.body) {
+ const nextId = BlockDescription.nextBlockId;
+ const subCtx = createContext(ctx);
+ this.compileAST({ type: 3 /* Multi */, content: ast.body }, subCtx);
+ this.helpers.add("withDefault");
+ expr = `withDefault(${expr}, b${nextId})`;
+ }
+ this.insertBlock(`${expr}`, block, ctx);
+ }
+ compileTIf(ast, ctx, nextNode) {
+ let { block, forceNewBlock, index } = ctx;
+ let currentIndex = index;
+ 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.target.indentLevel++;
+ this.insertAnchor(block);
+ const subCtx = createContext(ctx, { block, index: currentIndex });
+ this.compileAST(ast.content, subCtx);
+ this.target.indentLevel--;
+ if (ast.tElif) {
+ for (let clause of ast.tElif) {
+ this.addLine(`} else if (${compileExpr(clause.condition)}) {`);
+ this.target.indentLevel++;
+ this.insertAnchor(block);
+ const subCtx = createContext(ctx, { block, index: currentIndex });
+ this.compileAST(clause.content, subCtx);
+ this.target.indentLevel--;
+ }
+ }
+ if (ast.tElse) {
+ this.addLine(`} else {`);
+ this.target.indentLevel++;
+ this.insertAnchor(block);
+ const subCtx = createContext(ctx, { block, index: currentIndex });
+ this.compileAST(ast.tElse, subCtx);
+ this.target.indentLevel--;
+ }
+ 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(`let ${current.varName} `)) {
+ code[i] = code[i].replace(`let ${current.varName}`, current.varName);
+ current = children.shift();
+ if (!current)
+ break;
+ }
+ }
+ this.target.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);
+ }
+ }
+ 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.addLine(`const [${keys}, ${vals}, ${l}, ${c}] = prepareList(${compileExpr(ast.collection)});`);
+ // Throw errors on duplicate keys in dev mode
+ if (this.dev) {
+ this.addLine(`const 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.addLine(`let key${this.target.loopLevel} = ${ast.key ? compileExpr(ast.key) : loopVar};`);
+ if (this.dev) {
+ // Throw error on duplicate keys in dev mode
+ this.addLine(`if (keys${block.id}.has(key${this.target.loopLevel})) { throw new Error(\`Got duplicate key in t-foreach: \${key${this.target.loopLevel}}\`)}`);
+ this.addLine(`keys${block.id}.add(key${this.target.loopLevel});`);
+ }
+ let id;
+ if (ast.memo) {
+ this.target.hasCache = true;
+ id = this.generateId();
+ this.addLine(`let memo${id} = ${compileExpr(ast.memo)}`);
+ this.addLine(`let 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);
+ }
+ compileTKey(ast, ctx) {
+ const tKeyExpr = this.generateId("tKey_");
+ this.addLine(`const ${tKeyExpr} = ${compileExpr(ast.expr)};`);
+ ctx = createContext(ctx, {
+ tKeyExpr,
+ block: ctx.block,
+ index: ctx.index,
+ });
+ 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;
+ if (n <= 1) {
+ for (let child of ast.content) {
+ this.compileAST(child, ctx);
+ }
+ return;
+ }
+ 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(`let ${current.varName} `)) {
+ code[i] = code[i].replace(`let ${current.varName}`, current.varName);
+ current = children.shift();
+ if (!current)
+ break;
+ }
+ }
+ this.target.addLine(`let ${block.children.map((c) => c.varName)};`, codeIdx);
+ }
+ }
+ const args = block.children.map((c) => c.varName).join(", ");
+ this.insertBlock(`multi([${args}])`, block, ctx);
+ }
+ }
+ compileTCall(ast, ctx) {
+ let { block, forceNewBlock } = ctx;
+ if (ast.body) {
+ this.addLine(`ctx = Object.create(ctx);`);
+ this.addLine(`ctx[isBoundary] = 1;`);
+ this.helpers.add("isBoundary");
+ const nextId = BlockDescription.nextBlockId;
+ const subCtx = createContext(ctx, { preventRoot: true });
+ this.compileAST({ type: 3 /* Multi */, content: ast.body }, subCtx);
+ if (nextId !== BlockDescription.nextBlockId) {
+ this.helpers.add("zero");
+ this.addLine(`ctx[zero] = b${nextId};`);
+ }
+ }
+ 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 = this.generateId("template");
+ this.addLine(`const ${templateVar} = ${subTemplate};`);
+ block = this.createBlock(block, "multi", ctx);
+ this.helpers.add("call");
+ this.insertBlock(`call(this, ${templateVar}, ctx, node, ${key})`, block, {
+ ...ctx,
+ forceNewBlock: !block,
+ });
+ }
+ else {
+ const id = this.generateId(`callTemplate_`);
+ this.helpers.add("getTemplate");
+ this.staticCalls.push({ id, template: subTemplate });
+ block = this.createBlock(block, "multi", ctx);
+ this.insertBlock(`${id}.call(this, ctx, node, ${key})`, block, {
+ ...ctx,
+ forceNewBlock: !block,
+ });
+ }
+ if (ast.body && !ctx.isLast) {
+ this.addLine(`ctx = ctx.__proto__;`);
+ }
+ }
+ 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 });
+ }
+ 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 value = `new LazyValue(${name}, ctx, node)`;
+ 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, "${ast.name}", ${value});`);
+ }
+ }
+ generateComponentKey() {
+ const parts = [this.generateId("__")];
+ for (let i = 0; i < this.target.loopLevel; i++) {
+ parts.push(`\${key${i + 1}}`);
+ }
+ return parts.join("__");
+ }
/**
- * Perform string interpolation on the given string. Note that if the whole
- * string is an expression, it simply returns it (formatted and enclosed in
- * parentheses).
- * For instance:
- * 'Hello {{x}}!' -> `Hello ${x}`
- * '{{x ? 'a': 'b'}}' -> (x ? 'a' : 'b')
+ * 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'])"
*/
- interpolate(s) {
- let matches = s.match(INTERP_REGEXP);
- if (matches && matches[0].length === s.length) {
- return `(${this.formatExpression(s.slice(2, -2))})`;
- }
- let r = s.replace(/\{\{.*?\}\}/g, (s) => "${" + this.formatExpression(s.slice(2, -2)) + "}");
- return "`" + r + "`";
- }
- startProtectScope(codeBlock) {
- const protectID = this.generateID();
- this.rootContext.protectedScopeNumber++;
- this.rootContext.shouldDefineScope = true;
- const scopeExpr = `Object.create(scope);`;
- this.addLine(`let _origScope${protectID} = scope;`);
- this.addLine(`scope = ${scopeExpr}`);
- if (!codeBlock) {
- this.addLine(`scope.__access_mode__ = 'ro';`);
- }
- return protectID;
- }
- stopProtectScope(protectID) {
- this.rootContext.protectedScopeNumber--;
- this.addLine(`scope = _origScope${protectID};`);
- }
- }
- CompilationContext.nextID = 1;
-
- //------------------------------------------------------------------------------
- // module/props.ts
- //------------------------------------------------------------------------------
- function updateProps(oldVnode, vnode) {
- var key, cur, old, elm = vnode.elm, oldProps = oldVnode.data.props, props = vnode.data.props;
- if (!oldProps && !props)
- return;
- if (oldProps === props)
- return;
- oldProps = oldProps || {};
- props = props || {};
- for (key in oldProps) {
- if (!props[key]) {
- delete elm[key];
- }
- }
- for (key in props) {
- cur = props[key];
- old = oldProps[key];
- if (old !== cur && (key !== "value" || elm[key] !== cur)) {
- elm[key] = cur;
- }
- }
- }
- const propsModule = {
- create: updateProps,
- update: updateProps,
- };
- //------------------------------------------------------------------------------
- // module/eventlisteners.ts
- //------------------------------------------------------------------------------
- function invokeHandler(handler, vnode, event) {
- if (typeof handler === "function") {
- // call function handler
- handler.call(vnode, event, vnode);
- }
- else if (typeof handler === "object") {
- // call handler with arguments
- if (typeof handler[0] === "function") {
- // special case for single argument for performance
- if (handler.length === 2) {
- handler[0].call(vnode, handler[1], event, vnode);
+ formatProp(name, value) {
+ value = this.captureExpression(value);
+ if (name.includes(".")) {
+ let [_name, suffix] = name.split(".");
+ if (suffix === "bind") {
+ this.helpers.add("bind");
+ name = _name;
+ value = `bind(ctx, ${value || undefined})`;
}
else {
- var args = handler.slice(1);
- args.push(event);
- args.push(vnode);
- handler[0].apply(vnode, args);
+ throw new Error("Invalid prop suffix");
}
}
- else {
- // call multiple handlers
- for (let i = 0, iLen = handler.length; i < iLen; i++) {
- invokeHandler(handler[i], vnode, event);
+ name = /^[a-z_]+$/i.test(name) ? name : `'${name}'`;
+ return `${name}: ${value || undefined}`;
+ }
+ formatPropObject(obj) {
+ const params = [];
+ for (const [n, v] of Object.entries(obj)) {
+ params.push(this.formatProp(n, v));
+ }
+ return params.join(", ");
+ }
+ compileComponent(ast, ctx) {
+ let { block } = ctx;
+ // props
+ const hasSlotsProp = "slots" in ast.props;
+ const props = [];
+ const propExpr = this.formatPropObject(ast.props);
+ if (propExpr) {
+ props.push(propExpr);
+ }
+ // slots
+ const hasSlot = !!Object.keys(ast.slots).length;
+ let slotDef = "";
+ if (hasSlot) {
+ let ctxStr = "ctx";
+ if (this.target.loopLevel || !this.hasSafeContext) {
+ ctxStr = this.generateId("ctx");
+ this.helpers.add("capture");
+ this.addLine(`const ${ctxStr} = capture(ctx);`);
}
- }
- }
- }
- function handleEvent(event, vnode) {
- var name = event.type, on = vnode.data.on;
- // call event handler(s) if exists
- if (on) {
- if (on[name]) {
- invokeHandler(on[name], vnode, event);
- }
- else if (on["!" + name]) {
- invokeHandler(on["!" + name], vnode, event);
- }
- }
- }
- function createListener() {
- return function handler(event) {
- handleEvent(event, handler.vnode);
- };
- }
- function updateEventListeners(oldVnode, vnode) {
- var oldOn = oldVnode.data.on, oldListener = oldVnode.listener, oldElm = oldVnode.elm, on = vnode && vnode.data.on, elm = (vnode && vnode.elm), name;
- // optimization for reused immutable handlers
- if (oldOn === on) {
- return;
- }
- // remove existing listeners which no longer used
- if (oldOn && oldListener) {
- // if element changed or deleted we remove all existing listeners unconditionally
- if (!on) {
- for (name in oldOn) {
- // remove listener if element was changed or existing listeners removed
- const capture = name.charAt(0) === "!";
- name = capture ? name.slice(1) : name;
- oldElm.removeEventListener(name, oldListener, capture);
- }
- }
- else {
- for (name in oldOn) {
- // remove listener if existing listener removed
- if (!on[name]) {
- const capture = name.charAt(0) === "!";
- name = capture ? name.slice(1) : name;
- oldElm.removeEventListener(name, oldListener, capture);
+ let slotStr = [];
+ for (let slotName in ast.slots) {
+ const slotAst = ast.slots[slotName].content;
+ const name = this.compileInNewTarget("slot", slotAst, ctx);
+ const params = [`__render: ${name}, __ctx: ${ctxStr}`];
+ const scope = ast.slots[slotName].scope;
+ if (scope) {
+ params.push(`__scope: "${scope}"`);
}
- }
- }
- }
- // add new listeners which has not already attached
- if (on) {
- // reuse existing listener or create new
- var listener = (vnode.listener = oldVnode.listener || createListener());
- // update vnode for listener
- listener.vnode = vnode;
- // if element changed or added we add all needed listeners unconditionally
- if (!oldOn) {
- for (name in on) {
- // add listener if element was changed or new listeners added
- const capture = name.charAt(0) === "!";
- name = capture ? name.slice(1) : name;
- elm.addEventListener(name, listener, capture);
- }
- }
- else {
- for (name in on) {
- // add listener if new listener added
- if (!oldOn[name]) {
- const capture = name.charAt(0) === "!";
- name = capture ? name.slice(1) : name;
- elm.addEventListener(name, listener, capture);
+ if (ast.slots[slotName].attrs) {
+ params.push(this.formatPropObject(ast.slots[slotName].attrs));
}
+ const slotInfo = `{${params.join(", ")}}`;
+ slotStr.push(`'${slotName}': ${slotInfo}`);
}
+ slotDef = `{${slotStr.join(", ")}}`;
}
- }
- }
- const eventListenersModule = {
- create: updateEventListeners,
- update: updateEventListeners,
- destroy: updateEventListeners,
- };
- //------------------------------------------------------------------------------
- // attributes.ts
- //------------------------------------------------------------------------------
- const xlinkNS = "http://www.w3.org/1999/xlink";
- const xmlNS = "http://www.w3.org/XML/1998/namespace";
- const colonChar = 58;
- const xChar = 120;
- function updateAttrs(oldVnode, vnode) {
- var key, elm = vnode.elm, oldAttrs = oldVnode.data.attrs, attrs = vnode.data.attrs;
- if (!oldAttrs && !attrs)
- return;
- if (oldAttrs === attrs)
- return;
- oldAttrs = oldAttrs || {};
- attrs = attrs || {};
- // update modified attributes, add new attributes
- for (key in attrs) {
- const cur = attrs[key];
- const old = oldAttrs[key];
- if (old !== cur) {
- if (cur === true) {
- elm.setAttribute(key, "");
- }
- else if (cur === false) {
- elm.removeAttribute(key);
+ if (slotDef && !(ast.dynamicProps || hasSlotsProp)) {
+ props.push(`slots: ${slotDef}`);
+ }
+ const propStr = `{${props.join(",")}}`;
+ let propString = propStr;
+ if (ast.dynamicProps) {
+ if (!props.length) {
+ propString = `Object.assign({}, ${compileExpr(ast.dynamicProps)})`;
}
else {
- if (key.charCodeAt(0) !== xChar) {
- elm.setAttribute(key, cur);
- }
- else if (key.charCodeAt(3) === colonChar) {
- // Assume xml namespace
- elm.setAttributeNS(xmlNS, key, cur);
- }
- else if (key.charCodeAt(5) === colonChar) {
- // Assume xlink namespace
- elm.setAttributeNS(xlinkNS, key, cur);
- }
- else {
- elm.setAttribute(key, cur);
- }
+ propString = `Object.assign({}, ${compileExpr(ast.dynamicProps)}, ${propStr})`;
}
}
- }
- // remove removed attributes
- // use `in` operator since the previous `for` iteration uses it (.i.e. add even attributes with undefined value)
- // the other option is to remove all attributes with value == undefined
- for (key in oldAttrs) {
- if (!(key in attrs)) {
- elm.removeAttribute(key);
+ let propVar;
+ if ((slotDef && (ast.dynamicProps || hasSlotsProp)) || this.dev) {
+ propVar = this.generateId("props");
+ this.addLine(`const ${propVar} = ${propString};`);
+ propString = propVar;
}
- }
- }
- const attrsModule = {
- create: updateAttrs,
- update: updateAttrs,
- };
- //------------------------------------------------------------------------------
- // class.ts
- //------------------------------------------------------------------------------
- function updateClass(oldVnode, vnode) {
- var cur, name, elm, oldClass = oldVnode.data.class, klass = vnode.data.class;
- if (!oldClass && !klass)
- return;
- if (oldClass === klass)
- return;
- oldClass = oldClass || {};
- klass = klass || {};
- elm = vnode.elm;
- for (name in oldClass) {
- if (name && !klass[name] && !Object.prototype.hasOwnProperty.call(klass, name)) {
- // was `true` and now not provided
- elm.classList.remove(name);
+ if (slotDef && (ast.dynamicProps || hasSlotsProp)) {
+ this.addLine(`${propVar}.slots = Object.assign(${slotDef}, ${propVar}.slots)`);
}
- }
- for (name in klass) {
- cur = klass[name];
- if (cur !== oldClass[name]) {
- elm.classList[cur ? "add" : "remove"](name);
- }
- }
- }
- const classModule = { create: updateClass, update: updateClass };
-
- /**
- * Owl VDOM
- *
- * This file contains an implementation of a virtual DOM, which is a system that
- * can generate in-memory representations of a DOM tree, compare them, and
- * eventually change a concrete DOM tree to match its representation, in an
- * hopefully efficient way.
- *
- * Note that this code is a fork of Snabbdom, slightly tweaked/optimized for our
- * needs (see https://github.com/snabbdom/snabbdom).
- *
- * The main exported values are:
- * - interface VNode
- * - h function (a helper function to generate a vnode)
- * - patch function (to apply a vnode to an actual DOM node)
- */
- function vnode(sel, data, children, text, elm) {
- let key = data === undefined ? undefined : data.key;
- return { sel, data, children, text, elm, key };
- }
- //------------------------------------------------------------------------------
- // snabbdom.ts
- //------------------------------------------------------------------------------
- function isUndef(s) {
- return s === undefined;
- }
- function isDef(s) {
- return s !== undefined;
- }
- const emptyNode = vnode("", {}, [], undefined, undefined);
- function sameVnode(vnode1, vnode2) {
- return vnode1.key === vnode2.key && vnode1.sel === vnode2.sel;
- }
- function isVnode(vnode) {
- return vnode.sel !== undefined;
- }
- function createKeyToOldIdx(children, beginIdx, endIdx) {
- let i, map = {}, key, ch;
- for (i = beginIdx; i <= endIdx; ++i) {
- ch = children[i];
- if (ch != null) {
- key = ch.key;
- if (key !== undefined)
- map[key] = i;
- }
- }
- return map;
- }
- const hooks$1 = ["create", "update", "remove", "destroy", "pre", "post"];
- function init(modules, domApi) {
- let i, j, cbs = {};
- const api = domApi !== undefined ? domApi : htmlDomApi;
- for (i = 0; i < hooks$1.length; ++i) {
- cbs[hooks$1[i]] = [];
- for (j = 0; j < modules.length; ++j) {
- const hook = modules[j][hooks$1[i]];
- if (hook !== undefined) {
- cbs[hooks$1[i]].push(hook);
- }
- }
- }
- function emptyNodeAt(elm) {
- const id = elm.id ? "#" + elm.id : "";
- const c = elm.className ? "." + elm.className.split(" ").join(".") : "";
- return vnode(api.tagName(elm).toLowerCase() + id + c, {}, [], undefined, elm);
- }
- function createRmCb(childElm, listeners) {
- return function rmCb() {
- if (--listeners === 0) {
- const parent = api.parentNode(childElm);
- api.removeChild(parent, childElm);
- }
- };
- }
- function createElm(vnode, insertedVnodeQueue) {
- let i, iLen, data = vnode.data;
- if (data !== undefined) {
- if (isDef((i = data.hook)) && isDef((i = i.init))) {
- i(vnode);
- data = vnode.data;
- }
- }
- let children = vnode.children, sel = vnode.sel;
- if (sel === "!") {
- if (isUndef(vnode.text)) {
- vnode.text = "";
- }
- vnode.elm = api.createComment(vnode.text);
- }
- else if (sel !== undefined) {
- const elm = vnode.elm ||
- (vnode.elm =
- isDef(data) && isDef((i = data.ns))
- ? api.createElementNS(i, sel)
- : api.createElement(sel));
- for (i = 0, iLen = cbs.create.length; i < iLen; ++i)
- cbs.create[i](emptyNode, vnode);
- if (array(children)) {
- for (i = 0, iLen = children.length; i < iLen; ++i) {
- const ch = children[i];
- if (ch != null) {
- api.appendChild(elm, createElm(ch, insertedVnodeQueue));
- }
- }
- }
- else if (primitive(vnode.text)) {
- api.appendChild(elm, api.createTextNode(vnode.text));
- }
- i = vnode.data.hook; // Reuse variable
- if (isDef(i)) {
- if (i.create)
- i.create(emptyNode, vnode);
- if (i.insert)
- insertedVnodeQueue.push(vnode);
- }
+ // cmap key
+ const key = this.generateComponentKey();
+ let expr;
+ if (ast.isDynamic) {
+ expr = this.generateId("Comp");
+ this.addLine(`let ${expr} = ${compileExpr(ast.name)};`);
}
else {
- vnode.elm = api.createTextNode(vnode.text);
+ expr = `\`${ast.name}\``;
}
- return vnode.elm;
- }
- function addVnodes(parentElm, before, vnodes, startIdx, endIdx, insertedVnodeQueue) {
- for (; startIdx <= endIdx; ++startIdx) {
- const ch = vnodes[startIdx];
- if (ch != null) {
- api.insertBefore(parentElm, createElm(ch, insertedVnodeQueue), before);
- }
+ if (this.dev) {
+ this.addLine(`helpers.validateProps(${expr}, ${propVar}, ctx);`);
}
- }
- function invokeDestroyHook(vnode) {
- let i, iLen, j, jLen, data = vnode.data;
- if (data !== undefined) {
- if (isDef((i = data.hook)) && isDef((i = i.destroy)))
- i(vnode);
- for (i = 0, iLen = cbs.destroy.length; i < iLen; ++i)
- cbs.destroy[i](vnode);
- if (vnode.children !== undefined) {
- for (j = 0, jLen = vnode.children.length; j < jLen; ++j) {
- i = vnode.children[j];
- if (i != null && typeof i !== "string") {
- invokeDestroyHook(i);
- }
- }
- }
+ if (block && (ctx.forceNewBlock === false || ctx.tKeyExpr)) {
+ // todo: check the forcenewblock condition
+ this.insertAnchor(block);
}
- }
- function removeVnodes(parentElm, vnodes, startIdx, endIdx) {
- for (; startIdx <= endIdx; ++startIdx) {
- let i, iLen, listeners, rm, ch = vnodes[startIdx];
- if (ch != null) {
- if (isDef(ch.sel)) {
- invokeDestroyHook(ch);
- listeners = cbs.remove.length + 1;
- rm = createRmCb(ch.elm, listeners);
- for (i = 0, iLen = cbs.remove.length; i < iLen; ++i)
- cbs.remove[i](ch, rm);
- if (isDef((i = ch.data)) && isDef((i = i.hook)) && isDef((i = i.remove))) {
- i(ch, rm);
- }
- else {
- rm();
- }
- }
- else {
- // Text node
- api.removeChild(parentElm, ch.elm);
- }
- }
+ let keyArg = `key + \`${key}\``;
+ if (ctx.tKeyExpr) {
+ keyArg = `${ctx.tKeyExpr} + ${keyArg}`;
}
+ const blockArgs = `${expr}, ${propString}, ${keyArg}, node, ctx`;
+ let blockExpr = `component(${blockArgs})`;
+ if (ast.isDynamic) {
+ blockExpr = `toggler(${expr}, ${blockExpr})`;
+ }
+ block = this.createBlock(block, "multi", ctx);
+ this.insertBlock(blockExpr, block, ctx);
}
- function updateChildren(parentElm, oldCh, newCh, insertedVnodeQueue) {
- let oldStartIdx = 0, newStartIdx = 0;
- let oldEndIdx = oldCh.length - 1;
- let oldStartVnode = oldCh[0];
- let oldEndVnode = oldCh[oldEndIdx];
- let newEndIdx = newCh.length - 1;
- let newStartVnode = newCh[0];
- let newEndVnode = newCh[newEndIdx];
- let oldKeyToIdx;
- let idxInOld;
- let elmToMove;
- let before;
- while (oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx) {
- if (oldStartVnode == null) {
- oldStartVnode = oldCh[++oldStartIdx]; // Vnode might have been moved left
- }
- else if (oldEndVnode == null) {
- oldEndVnode = oldCh[--oldEndIdx];
- }
- else if (newStartVnode == null) {
- newStartVnode = newCh[++newStartIdx];
- }
- else if (newEndVnode == null) {
- newEndVnode = newCh[--newEndIdx];
- }
- else if (sameVnode(oldStartVnode, newStartVnode)) {
- patchVnode(oldStartVnode, newStartVnode, insertedVnodeQueue);
- oldStartVnode = oldCh[++oldStartIdx];
- newStartVnode = newCh[++newStartIdx];
- }
- else if (sameVnode(oldEndVnode, newEndVnode)) {
- patchVnode(oldEndVnode, newEndVnode, insertedVnodeQueue);
- oldEndVnode = oldCh[--oldEndIdx];
- newEndVnode = newCh[--newEndIdx];
- }
- else if (sameVnode(oldStartVnode, newEndVnode)) {
- // Vnode moved right
- patchVnode(oldStartVnode, newEndVnode, insertedVnodeQueue);
- api.insertBefore(parentElm, oldStartVnode.elm, api.nextSibling(oldEndVnode.elm));
- oldStartVnode = oldCh[++oldStartIdx];
- newEndVnode = newCh[--newEndIdx];
- }
- else if (sameVnode(oldEndVnode, newStartVnode)) {
- // Vnode moved left
- patchVnode(oldEndVnode, newStartVnode, insertedVnodeQueue);
- api.insertBefore(parentElm, oldEndVnode.elm, oldStartVnode.elm);
- oldEndVnode = oldCh[--oldEndIdx];
- newStartVnode = newCh[++newStartIdx];
+ compileTSlot(ast, ctx) {
+ this.helpers.add("callSlot");
+ let { block } = ctx;
+ let blockString;
+ let slotName;
+ let dynamic = false;
+ if (ast.name.match(INTERP_REGEXP)) {
+ dynamic = true;
+ slotName = interpolate(ast.name);
+ }
+ else {
+ slotName = "'" + ast.name + "'";
+ }
+ const scope = ast.attrs ? `{${this.formatPropObject(ast.attrs)}}` : null;
+ if (ast.defaultContent) {
+ const name = this.compileInNewTarget("defaultContent", ast.defaultContent, ctx);
+ blockString = `callSlot(ctx, node, key, ${slotName}, ${dynamic}, ${scope}, ${name})`;
+ }
+ else {
+ if (dynamic) {
+ let name = this.generateId("slot");
+ this.addLine(`const ${name} = ${slotName};`);
+ blockString = `toggler(${name}, callSlot(ctx, node, key, ${name}), ${dynamic}, ${scope})`;
}
else {
- if (oldKeyToIdx === undefined) {
- oldKeyToIdx = createKeyToOldIdx(oldCh, oldStartIdx, oldEndIdx);
- }
- idxInOld = oldKeyToIdx[newStartVnode.key];
- if (isUndef(idxInOld)) {
- // New element
- api.insertBefore(parentElm, createElm(newStartVnode, insertedVnodeQueue), oldStartVnode.elm);
- newStartVnode = newCh[++newStartIdx];
- }
- else {
- elmToMove = oldCh[idxInOld];
- if (elmToMove.sel !== newStartVnode.sel) {
- api.insertBefore(parentElm, createElm(newStartVnode, insertedVnodeQueue), oldStartVnode.elm);
- }
- else {
- patchVnode(elmToMove, newStartVnode, insertedVnodeQueue);
- oldCh[idxInOld] = undefined;
- api.insertBefore(parentElm, elmToMove.elm, oldStartVnode.elm);
- }
- newStartVnode = newCh[++newStartIdx];
- }
+ blockString = `callSlot(ctx, node, key, ${slotName}, ${dynamic}, ${scope})`;
}
}
- if (oldStartIdx <= oldEndIdx || newStartIdx <= newEndIdx) {
- if (oldStartIdx > oldEndIdx) {
- before = newCh[newEndIdx + 1] == null ? null : newCh[newEndIdx + 1].elm;
- addVnodes(parentElm, before, newCh, newStartIdx, newEndIdx, insertedVnodeQueue);
- }
- else {
- removeVnodes(parentElm, oldCh, oldStartIdx, oldEndIdx);
- }
+ if (block) {
+ this.insertAnchor(block);
+ }
+ block = this.createBlock(block, "multi", ctx);
+ this.insertBlock(blockString, block, { ...ctx, forceNewBlock: false });
+ }
+ compileTTranslation(ast, ctx) {
+ if (ast.content) {
+ this.compileAST(ast.content, Object.assign({}, ctx, { translate: false }));
}
}
- function patchVnode(oldVnode, vnode, insertedVnodeQueue) {
- let i, iLen, hook;
- if (isDef((i = vnode.data)) && isDef((hook = i.hook)) && isDef((i = hook.prepatch))) {
- i(oldVnode, vnode);
+ compileTPortal(ast, ctx) {
+ this.helpers.add("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 = this.generateId("ctx");
+ this.helpers.add("capture");
+ this.addLine(`const ${ctxStr} = capture(ctx);`);
}
- const elm = (vnode.elm = oldVnode.elm);
- let oldCh = oldVnode.children;
- let ch = vnode.children;
- if (oldVnode === vnode)
- return;
- if (vnode.data !== undefined) {
- for (i = 0, iLen = cbs.update.length; i < iLen; ++i)
- cbs.update[i](oldVnode, vnode);
- i = vnode.data.hook;
- if (isDef(i) && isDef((i = i.update)))
- i(oldVnode, vnode);
- }
- if (isUndef(vnode.text)) {
- if (isDef(oldCh) && isDef(ch)) {
- if (oldCh !== ch)
- updateChildren(elm, oldCh, ch, insertedVnodeQueue);
- }
- else if (isDef(ch)) {
- if (isDef(oldVnode.text))
- api.setTextContent(elm, "");
- addVnodes(elm, null, ch, 0, ch.length - 1, insertedVnodeQueue);
- }
- else if (isDef(oldCh)) {
- removeVnodes(elm, oldCh, 0, oldCh.length - 1);
- }
- else if (isDef(oldVnode.text)) {
- api.setTextContent(elm, "");
- }
- }
- else if (oldVnode.text !== vnode.text) {
- if (isDef(oldCh)) {
- removeVnodes(elm, oldCh, 0, oldCh.length - 1);
- }
- api.setTextContent(elm, vnode.text);
- }
- if (isDef(hook) && isDef((i = hook.postpatch))) {
- i(oldVnode, vnode);
+ const blockString = `component(Portal, {target: ${ast.target},slots: {'default': {__render: ${name}, __ctx: ${ctxStr}}}}, key + \`${key}\`, node, ctx)`;
+ if (block) {
+ this.insertAnchor(block);
}
+ block = this.createBlock(block, "multi", ctx);
+ this.insertBlock(blockString, block, { ...ctx, forceNewBlock: false });
}
- return function patch(oldVnode, vnode) {
- let i, iLen, elm, parent;
- const insertedVnodeQueue = [];
- for (i = 0, iLen = cbs.pre.length; i < iLen; ++i)
- cbs.pre[i]();
- if (!isVnode(oldVnode)) {
- oldVnode = emptyNodeAt(oldVnode);
- }
- if (sameVnode(oldVnode, vnode)) {
- patchVnode(oldVnode, vnode, insertedVnodeQueue);
- }
- else {
- elm = oldVnode.elm;
- parent = api.parentNode(elm);
- createElm(vnode, insertedVnodeQueue);
- if (parent !== null) {
- api.insertBefore(parent, vnode.elm, api.nextSibling(elm));
- removeVnodes(parent, [oldVnode], 0, 0);
- }
- }
- for (i = 0, iLen = insertedVnodeQueue.length; i < iLen; ++i) {
- insertedVnodeQueue[i].data.hook.insert(insertedVnodeQueue[i]);
- }
- for (i = 0, iLen = cbs.post.length; i < iLen; ++i)
- cbs.post[i]();
- return vnode;
- };
- }
- //------------------------------------------------------------------------------
- // is.ts
- //------------------------------------------------------------------------------
- const array = Array.isArray;
- function primitive(s) {
- return typeof s === "string" || typeof s === "number";
- }
- function createElement(tagName) {
- return document.createElement(tagName);
- }
- function createElementNS(namespaceURI, qualifiedName) {
- return document.createElementNS(namespaceURI, qualifiedName);
- }
- function createTextNode(text) {
- return document.createTextNode(text);
- }
- function createComment(text) {
- return document.createComment(text);
- }
- function insertBefore(parentNode, newNode, referenceNode) {
- parentNode.insertBefore(newNode, referenceNode);
- }
- function removeChild(node, child) {
- node.removeChild(child);
- }
- function appendChild(node, child) {
- node.appendChild(child);
- }
- function parentNode(node) {
- return node.parentNode;
- }
- function nextSibling(node) {
- return node.nextSibling;
- }
- function tagName(elm) {
- return elm.tagName;
- }
- function setTextContent(node, text) {
- node.textContent = text;
- }
- const htmlDomApi = {
- createElement,
- createElementNS,
- createTextNode,
- createComment,
- insertBefore,
- removeChild,
- appendChild,
- parentNode,
- nextSibling,
- tagName,
- setTextContent,
- };
- function addNS(data, children, sel) {
- if (sel === "dummy") {
- // we do not need to add the namespace on dummy elements, they come from a
- // subcomponent, which will handle the namespace itself
- return;
- }
- data.ns = "http://www.w3.org/2000/svg";
- if (sel !== "foreignObject" && children !== undefined) {
- for (let i = 0, iLen = children.length; i < iLen; ++i) {
- const child = children[i];
- let childData = child.data;
- if (childData !== undefined) {
- addNS(childData, child.children, child.sel);
- }
- }
- }
- }
- function h(sel, b, c) {
- var data = {}, children, text, i, iLen;
- if (c !== undefined) {
- data = b;
- if (array(c)) {
- children = c;
- }
- else if (primitive(c)) {
- text = c;
- }
- else if (c && c.sel) {
- children = [c];
- }
- }
- else if (b !== undefined) {
- if (array(b)) {
- children = b;
- }
- else if (primitive(b)) {
- text = b;
- }
- else if (b && b.sel) {
- children = [b];
- }
- else {
- data = b;
- }
- }
- if (children !== undefined) {
- for (i = 0, iLen = children.length; i < iLen; ++i) {
- if (primitive(children[i]))
- children[i] = vnode(undefined, undefined, undefined, children[i], undefined);
- }
- }
- return vnode(sel, data, children, text, undefined);
}
- const patch = init([eventListenersModule, attrsModule, propsModule, classModule]);
-
- let localStorage = null;
- const browser = {
- setTimeout: window.setTimeout.bind(window),
- clearTimeout: window.clearTimeout.bind(window),
- setInterval: window.setInterval.bind(window),
- clearInterval: window.clearInterval.bind(window),
- requestAnimationFrame: window.requestAnimationFrame.bind(window),
- random: Math.random,
- Date: window.Date,
- fetch: (window.fetch || (() => { })).bind(window),
- get localStorage() {
- return localStorage || window.localStorage;
- },
- set localStorage(newLocalStorage) {
- localStorage = newLocalStorage;
- },
- };
-
- /**
- * Owl Utils
- *
- * We have here a small collection of utility functions:
- *
- * - whenReady
- * - loadJS
- * - loadFile
- * - escape
- * - debounce
- */
- function whenReady(fn) {
- return new Promise(function (resolve) {
- if (document.readyState !== "loading") {
- resolve();
- }
- else {
- document.addEventListener("DOMContentLoaded", resolve, false);
- }
- }).then(fn || function () { });
- }
- const loadedScripts = {};
- function loadJS(url) {
- if (url in loadedScripts) {
- return loadedScripts[url];
+ // -----------------------------------------------------------------------------
+ // AST Type definition
+ // -----------------------------------------------------------------------------
+ // -----------------------------------------------------------------------------
+ // Parser
+ // -----------------------------------------------------------------------------
+ const cache = new WeakMap();
+ function parse(xml) {
+ if (typeof xml === "string") {
+ const elem = parseXML$1(`${xml}`).firstChild;
+ return _parse(elem);
}
- const promise = new Promise(function (resolve, reject) {
- const script = document.createElement("script");
- script.type = "text/javascript";
- script.src = url;
- script.onload = function () {
- resolve();
- };
- script.onerror = function () {
- reject(`Error loading file '${url}'`);
- };
- const head = document.head || document.getElementsByTagName("head")[0];
- head.appendChild(script);
- });
- loadedScripts[url] = promise;
- return promise;
- }
- async function loadFile(url) {
- const result = await browser.fetch(url);
- if (!result.ok) {
- throw new Error("Error while fetching xml templates");
+ 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 await result.text();
+ return ast;
}
- function escape(str) {
- if (str === undefined) {
- return "";
- }
- if (typeof str === "number") {
- return String(str);
- }
- const p = document.createElement("p");
- p.textContent = str;
- return p.innerHTML;
+ function _parse(xml) {
+ normalizeXML(xml);
+ const ctx = { inPreTag: false, inSVG: false };
+ return parseNode(xml, ctx) || { type: 0 /* Text */, value: "" };
}
- /**
- * Returns a function, that, as long as it continues to be invoked, will not
- * be triggered. The function will be called after it stops being called for
- * N milliseconds. If `immediate` is passed, trigger the function on the
- * leading edge, instead of the trailing.
- *
- * Inspired by https://davidwalsh.name/javascript-debounce-function
- */
- function debounce(func, wait, immediate) {
- let timeout;
- return function () {
- const context = this;
- const args = arguments;
- function later() {
- timeout = null;
- if (!immediate) {
- func.apply(context, args);
- }
- }
- const callNow = immediate && !timeout;
- browser.clearTimeout(timeout);
- timeout = browser.setTimeout(later, wait);
- if (callNow) {
- func.apply(context, args);
- }
- };
- }
- function shallowEqual(p1, p2) {
- for (let k in p1) {
- if (p1[k] !== p2[k]) {
- return false;
- }
+ function parseNode(node, ctx) {
+ if (!(node instanceof Element)) {
+ return parseTextCommentNode(node, ctx);
}
- return true;
- }
-
- var _utils = /*#__PURE__*/Object.freeze({
- __proto__: null,
- whenReady: whenReady,
- loadJS: loadJS,
- loadFile: loadFile,
- escape: escape,
- debounce: debounce,
- shallowEqual: shallowEqual
- });
-
- //------------------------------------------------------------------------------
- // Const/global stuff/helpers
- //------------------------------------------------------------------------------
- const TRANSLATABLE_ATTRS = ["label", "title", "placeholder", "alt"];
+ 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]/;
const whitespaceRE = /\s+/g;
- const translationRE = /^(\s*)([\s\S]+?)(\s*)$/;
- const NODE_HOOKS_PARAMS = {
- create: "(_, n)",
- insert: "vn",
- remove: "(vn, rm)",
- destroy: "()",
- };
- function isComponent(obj) {
- return obj && obj.hasOwnProperty("__owl__");
- }
- class VDomArray extends Array {
- toString() {
- return vDomToString(this);
+ function parseTextCommentNode(node, ctx) {
+ if (node.nodeType === Node.TEXT_NODE) {
+ let value = node.textContent || "";
+ if (!ctx.inPreTag) {
+ if (lineBreakRE.test(value) && !value.trim()) {
+ return null;
+ }
+ value = value.replace(whitespaceRE, " ");
+ }
+ return { type: 0 /* Text */, value };
}
+ else if (node.nodeType === Node.COMMENT_NODE) {
+ return { type: 1 /* Comment */, value: node.textContent || "" };
+ }
+ return null;
}
- function vDomToString(vdom) {
- return vdom
- .map((vnode) => {
- if (vnode.sel) {
- const node = document.createElement(vnode.sel);
- const result = patch(node, vnode);
- return result.elm.outerHTML;
+ // -----------------------------------------------------------------------------
+ // 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;
+ }
+ 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();
+ const attrs = {};
+ const on = {};
+ let model = null;
+ for (let attr of nodeAttrsNames) {
+ const value = node.getAttribute(attr);
+ if (attr.startsWith("t-on")) {
+ if (attr === "t-on") {
+ throw new Error("Missing event name with t-on directive");
+ }
+ on[attr.slice(5)] = value;
+ }
+ else if (attr.startsWith("t-model")) {
+ if (!["input", "select", "textarea"].includes(tagName)) {
+ throw new Error("The t-model directive only works with ,