From 67407e3ec232bd12c6015e72f61fb7e1a2e5ab23 Mon Sep 17 00:00:00 2001 From: William Braeckman Date: Mon, 10 Mar 2025 14:51:45 +0100 Subject: [PATCH] [IMP] runbot: vendor owl and expose through odoo module --- runbot/static/libs/owl.js | 6223 +++++++++++++++++++++++++++++++ runbot/static/src/owl_module.js | 5 + 2 files changed, 6228 insertions(+) create mode 100644 runbot/static/libs/owl.js create mode 100644 runbot/static/src/owl_module.js diff --git a/runbot/static/libs/owl.js b/runbot/static/libs/owl.js new file mode 100644 index 00000000..a3b0eaba --- /dev/null +++ b/runbot/static/libs/owl.js @@ -0,0 +1,6223 @@ +(function (exports) { + 'use strict'; + + function filterOutModifiersFromData(dataList) { + dataList = dataList.slice(); + const modifiers = []; + let elm; + while ((elm = dataList[0]) && typeof elm === "string") { + modifiers.push(dataList.shift()); + } + return { modifiers, data: dataList }; + } + const config = { + // whether or not blockdom should normalize DOM whenever a block is created. + // Normalizing dom mean removing empty text nodes (or containing only spaces) + shouldNormalizeDom: true, + // this is the main event handler. Every event handler registered with blockdom + // will go through this function, giving it the data registered in the block + // and the event + mainEventHandler: (data, ev, currentTarget) => { + if (typeof data === "function") { + data(ev); + } + else if (Array.isArray(data)) { + data = filterOutModifiersFromData(data).data; + data[0](data[1], ev); + } + return false; + }, + }; + + // ----------------------------------------------------------------------------- + // Toggler node + // ----------------------------------------------------------------------------- + class VToggler { + constructor(key, child) { + this.key = key; + this.child = child; + } + mount(parent, afterNode) { + this.parentEl = parent; + this.child.mount(parent, afterNode); + } + moveBeforeDOMNode(node, parent) { + this.child.moveBeforeDOMNode(node, parent); + } + moveBeforeVNode(other, afterNode) { + this.moveBeforeDOMNode((other && other.firstNode()) || afterNode); + } + patch(other, withBeforeRemove) { + if (this === other) { + return; + } + let child1 = this.child; + let child2 = other.child; + if (this.key === other.key) { + child1.patch(child2, withBeforeRemove); + } + else { + child2.mount(this.parentEl, child1.firstNode()); + if (withBeforeRemove) { + child1.beforeRemove(); + } + child1.remove(); + this.child = child2; + this.key = other.key; + } + } + beforeRemove() { + this.child.beforeRemove(); + } + remove() { + this.child.remove(); + } + firstNode() { + return this.child.firstNode(); + } + toString() { + return this.child.toString(); + } + } + function toggler(key, child) { + return new VToggler(key, child); + } + + // Custom error class that wraps error that happen in the owl lifecycle + class OwlError extends Error { + } + + const { setAttribute: elemSetAttribute, removeAttribute } = Element.prototype; + const tokenList = DOMTokenList.prototype; + const tokenListAdd = tokenList.add; + const tokenListRemove = tokenList.remove; + const isArray = Array.isArray; + const { split, trim } = String.prototype; + const wordRegexp = /\s+/; + /** + * We regroup here all code related to updating attributes in a very loose sense: + * attributes, properties and classs are all managed by the functions in this + * file. + */ + function setAttribute(key, value) { + switch (value) { + case false: + case undefined: + removeAttribute.call(this, key); + break; + case true: + elemSetAttribute.call(this, key, ""); + break; + default: + elemSetAttribute.call(this, key, value); + } + } + function createAttrUpdater(attr) { + return function (value) { + setAttribute.call(this, attr, value); + }; + } + function attrsSetter(attrs) { + if (isArray(attrs)) { + if (attrs[0] === "class") { + setClass.call(this, attrs[1]); + } + else { + setAttribute.call(this, attrs[0], attrs[1]); + } + } + else { + for (let k in attrs) { + if (k === "class") { + setClass.call(this, attrs[k]); + } + else { + setAttribute.call(this, k, attrs[k]); + } + } + } + } + function attrsUpdater(attrs, oldAttrs) { + if (isArray(attrs)) { + const name = attrs[0]; + const val = attrs[1]; + if (name === oldAttrs[0]) { + if (val === oldAttrs[1]) { + return; + } + if (name === "class") { + updateClass.call(this, val, oldAttrs[1]); + } + else { + setAttribute.call(this, name, val); + } + } + else { + removeAttribute.call(this, oldAttrs[0]); + setAttribute.call(this, name, val); + } + } + else { + for (let k in oldAttrs) { + if (!(k in attrs)) { + if (k === "class") { + updateClass.call(this, "", oldAttrs[k]); + } + else { + removeAttribute.call(this, k); + } + } + } + for (let k in attrs) { + const val = attrs[k]; + if (val !== oldAttrs[k]) { + if (k === "class") { + updateClass.call(this, val, oldAttrs[k]); + } + else { + setAttribute.call(this, k, val); + } + } + } + } + } + function toClassObj(expr) { + const result = {}; + switch (typeof expr) { + case "string": + // we transform here a list of classes into an object: + // 'hey you' becomes {hey: true, you: true} + const str = trim.call(expr); + if (!str) { + return {}; + } + let words = split.call(str, wordRegexp); + for (let i = 0, l = words.length; i < l; i++) { + result[words[i]] = true; + } + return result; + case "object": + // this is already an object but we may need to split keys: + // {'a': true, 'b c': true} should become {a: true, b: true, c: true} + for (let key in expr) { + const value = expr[key]; + if (value) { + key = trim.call(key); + if (!key) { + continue; + } + const words = split.call(key, wordRegexp); + for (let word of words) { + result[word] = value; + } + } + } + return result; + case "undefined": + return {}; + case "number": + return { [expr]: true }; + default: + return { [expr]: true }; + } + } + function setClass(val) { + val = val === "" ? {} : toClassObj(val); + // add classes + const cl = this.classList; + for (let c in val) { + tokenListAdd.call(cl, c); + } + } + function updateClass(val, oldVal) { + oldVal = oldVal === "" ? {} : toClassObj(oldVal); + val = val === "" ? {} : toClassObj(val); + const cl = this.classList; + // remove classes + for (let c in oldVal) { + if (!(c in val)) { + tokenListRemove.call(cl, c); + } + } + // add classes + for (let c in val) { + if (!(c in oldVal)) { + tokenListAdd.call(cl, c); + } + } + } + + /** + * Creates a batched version of a callback so that all calls to it in the same + * microtick will only call the original callback once. + * + * @param callback the callback to batch + * @returns a batched version of the original callback + */ + function batched(callback) { + let scheduled = false; + return async (...args) => { + if (!scheduled) { + scheduled = true; + await Promise.resolve(); + scheduled = false; + callback(...args); + } + }; + } + /** + * Determine whether the given element is contained in its ownerDocument: + * either directly or with a shadow root in between. + */ + function inOwnerDocument(el) { + if (!el) { + return false; + } + if (el.ownerDocument.contains(el)) { + return true; + } + const rootNode = el.getRootNode(); + return rootNode instanceof ShadowRoot && el.ownerDocument.contains(rootNode.host); + } + function validateTarget(target) { + // Get the document and HTMLElement corresponding to the target to allow mounting in iframes + const document = target && target.ownerDocument; + if (document) { + const HTMLElement = document.defaultView.HTMLElement; + if (target instanceof HTMLElement || target instanceof ShadowRoot) { + if (!document.body.contains(target instanceof HTMLElement ? target : target.host)) { + throw new OwlError("Cannot mount a component on a detached dom node"); + } + return; + } + } + throw new OwlError("Cannot mount component: the target is not a valid DOM element"); + } + class EventBus extends EventTarget { + trigger(name, payload) { + this.dispatchEvent(new CustomEvent(name, { detail: payload })); + } + } + function whenReady(fn) { + return new Promise(function (resolve) { + if (document.readyState !== "loading") { + resolve(true); + } + else { + document.addEventListener("DOMContentLoaded", resolve, false); + } + }).then(fn || function () { }); + } + async function loadFile(url) { + const result = await fetch(url); + if (!result.ok) { + throw new OwlError("Error while fetching xml templates"); + } + return await result.text(); + } + /* + * This class just transports the fact that a string is safe + * to be injected as HTML. Overriding a JS primitive is quite painful though + * so we need to redfine toString and valueOf. + */ + class Markup extends String { + } + /* + * Marks a value as safe, that is, a value that can be injected as HTML directly. + * It should be used to wrap the value passed to a t-out directive to allow a raw rendering. + */ + function markup(value) { + return new Markup(value); + } + + function createEventHandler(rawEvent) { + const eventName = rawEvent.split(".")[0]; + const capture = rawEvent.includes(".capture"); + if (rawEvent.includes(".synthetic")) { + return createSyntheticHandler(eventName, capture); + } + else { + return createElementHandler(eventName, capture); + } + } + // Native listener + let nextNativeEventId = 1; + function createElementHandler(evName, capture = false) { + let eventKey = `__event__${evName}_${nextNativeEventId++}`; + if (capture) { + eventKey = `${eventKey}_capture`; + } + function listener(ev) { + const currentTarget = ev.currentTarget; + if (!currentTarget || !inOwnerDocument(currentTarget)) + return; + const data = currentTarget[eventKey]; + if (!data) + return; + config.mainEventHandler(data, ev, currentTarget); + } + function setup(data) { + this[eventKey] = data; + this.addEventListener(evName, listener, { capture }); + } + function remove() { + delete this[eventKey]; + this.removeEventListener(evName, listener, { capture }); + } + function update(data) { + this[eventKey] = data; + } + return { setup, update, remove }; + } + // Synthetic handler: a form of event delegation that allows placing only one + // listener per event type. + let nextSyntheticEventId = 1; + function createSyntheticHandler(evName, capture = false) { + let eventKey = `__event__synthetic_${evName}`; + if (capture) { + eventKey = `${eventKey}_capture`; + } + setupSyntheticEvent(evName, eventKey, capture); + const currentId = nextSyntheticEventId++; + function setup(data) { + const _data = this[eventKey] || {}; + _data[currentId] = data; + this[eventKey] = _data; + } + function remove() { + delete this[eventKey]; + } + return { setup, update: setup, remove }; + } + function nativeToSyntheticEvent(eventKey, event) { + let dom = event.target; + while (dom !== null) { + const _data = dom[eventKey]; + if (_data) { + for (const data of Object.values(_data)) { + const stopped = config.mainEventHandler(data, event, dom); + if (stopped) + return; + } + } + dom = dom.parentNode; + } + } + const CONFIGURED_SYNTHETIC_EVENTS = {}; + function setupSyntheticEvent(evName, eventKey, capture = false) { + if (CONFIGURED_SYNTHETIC_EVENTS[eventKey]) { + return; + } + document.addEventListener(evName, (event) => nativeToSyntheticEvent(eventKey, event), { + capture, + }); + CONFIGURED_SYNTHETIC_EVENTS[eventKey] = true; + } + + const getDescriptor$3 = (o, p) => Object.getOwnPropertyDescriptor(o, p); + const nodeProto$4 = Node.prototype; + const nodeInsertBefore$3 = nodeProto$4.insertBefore; + const nodeSetTextContent$1 = getDescriptor$3(nodeProto$4, "textContent").set; + const nodeRemoveChild$3 = nodeProto$4.removeChild; + // ----------------------------------------------------------------------------- + // Multi NODE + // ----------------------------------------------------------------------------- + class VMulti { + constructor(children) { + this.children = children; + } + mount(parent, afterNode) { + const children = this.children; + const l = children.length; + const anchors = new Array(l); + for (let i = 0; i < l; i++) { + let child = children[i]; + if (child) { + child.mount(parent, afterNode); + } + else { + const childAnchor = document.createTextNode(""); + anchors[i] = childAnchor; + nodeInsertBefore$3.call(parent, childAnchor, afterNode); + } + } + this.anchors = anchors; + this.parentEl = parent; + } + moveBeforeDOMNode(node, parent = this.parentEl) { + this.parentEl = parent; + const children = this.children; + const anchors = this.anchors; + for (let i = 0, l = children.length; i < l; i++) { + let child = children[i]; + if (child) { + child.moveBeforeDOMNode(node, parent); + } + else { + const anchor = anchors[i]; + nodeInsertBefore$3.call(parent, anchor, node); + } + } + } + moveBeforeVNode(other, afterNode) { + if (other) { + const next = other.children[0]; + afterNode = (next ? next.firstNode() : other.anchors[0]) || null; + } + const children = this.children; + const parent = this.parentEl; + const anchors = this.anchors; + for (let i = 0, l = children.length; i < l; i++) { + let child = children[i]; + if (child) { + child.moveBeforeVNode(null, afterNode); + } + else { + const anchor = anchors[i]; + nodeInsertBefore$3.call(parent, anchor, afterNode); + } + } + } + patch(other, withBeforeRemove) { + if (this === other) { + return; + } + const children1 = this.children; + const children2 = other.children; + const anchors = this.anchors; + const parentEl = this.parentEl; + for (let i = 0, l = children1.length; i < l; i++) { + const vn1 = children1[i]; + const vn2 = children2[i]; + if (vn1) { + if (vn2) { + vn1.patch(vn2, withBeforeRemove); + } + else { + const afterNode = vn1.firstNode(); + const anchor = document.createTextNode(""); + anchors[i] = anchor; + nodeInsertBefore$3.call(parentEl, anchor, afterNode); + if (withBeforeRemove) { + vn1.beforeRemove(); + } + vn1.remove(); + children1[i] = undefined; + } + } + else if (vn2) { + children1[i] = vn2; + const anchor = anchors[i]; + vn2.mount(parentEl, anchor); + nodeRemoveChild$3.call(parentEl, anchor); + } + } + } + beforeRemove() { + const children = this.children; + for (let i = 0, l = children.length; i < l; i++) { + const child = children[i]; + if (child) { + child.beforeRemove(); + } + } + } + remove() { + const parentEl = this.parentEl; + if (this.isOnlyChild) { + nodeSetTextContent$1.call(parentEl, ""); + } + else { + const children = this.children; + const anchors = this.anchors; + for (let i = 0, l = children.length; i < l; i++) { + const child = children[i]; + if (child) { + child.remove(); + } + else { + nodeRemoveChild$3.call(parentEl, anchors[i]); + } + } + } + } + firstNode() { + const child = this.children[0]; + return child ? child.firstNode() : this.anchors[0]; + } + toString() { + return this.children.map((c) => (c ? c.toString() : "")).join(""); + } + } + function multi(children) { + return new VMulti(children); + } + + const getDescriptor$2 = (o, p) => Object.getOwnPropertyDescriptor(o, p); + const nodeProto$3 = Node.prototype; + const characterDataProto$1 = CharacterData.prototype; + const nodeInsertBefore$2 = nodeProto$3.insertBefore; + const characterDataSetData$1 = getDescriptor$2(characterDataProto$1, "data").set; + const nodeRemoveChild$2 = nodeProto$3.removeChild; + class VSimpleNode { + constructor(text) { + this.text = text; + } + mountNode(node, parent, afterNode) { + this.parentEl = parent; + nodeInsertBefore$2.call(parent, node, afterNode); + this.el = node; + } + moveBeforeDOMNode(node, parent = this.parentEl) { + this.parentEl = parent; + nodeInsertBefore$2.call(parent, this.el, node); + } + moveBeforeVNode(other, afterNode) { + nodeInsertBefore$2.call(this.parentEl, this.el, other ? other.el : afterNode); + } + beforeRemove() { } + remove() { + nodeRemoveChild$2.call(this.parentEl, this.el); + } + firstNode() { + return this.el; + } + toString() { + return this.text; + } + } + class VText$1 extends VSimpleNode { + mount(parent, afterNode) { + this.mountNode(document.createTextNode(toText(this.text)), parent, afterNode); + } + patch(other) { + const text2 = other.text; + if (this.text !== text2) { + characterDataSetData$1.call(this.el, toText(text2)); + this.text = text2; + } + } + } + class VComment extends VSimpleNode { + mount(parent, afterNode) { + this.mountNode(document.createComment(toText(this.text)), parent, afterNode); + } + patch() { } + } + function text(str) { + return new VText$1(str); + } + function comment(str) { + return new VComment(str); + } + function toText(value) { + switch (typeof value) { + case "string": + return value; + case "number": + return String(value); + case "boolean": + return value ? "true" : "false"; + default: + return value || ""; + } + } + + const getDescriptor$1 = (o, p) => Object.getOwnPropertyDescriptor(o, p); + const nodeProto$2 = Node.prototype; + const elementProto = Element.prototype; + const characterDataProto = CharacterData.prototype; + const characterDataSetData = getDescriptor$1(characterDataProto, "data").set; + const nodeGetFirstChild = getDescriptor$1(nodeProto$2, "firstChild").get; + const nodeGetNextSibling = getDescriptor$1(nodeProto$2, "nextSibling").get; + const NO_OP = () => { }; + function makePropSetter(name) { + return function setProp(value) { + // support 0, fallback to empty string for other falsy values + this[name] = value === 0 ? 0 : value ? value.valueOf() : ""; + }; + } + const cache$1 = {}; + /** + * Compiling blocks is a multi-step process: + * + * 1. build an IntermediateTree from the HTML element. This intermediate tree + * is a binary tree structure that encode dynamic info sub nodes, and the + * path required to reach them + * 2. process the tree to build a block context, which is an object that aggregate + * all dynamic info in a list, and also, all ref indexes. + * 3. process the context to build appropriate builder/setter functions + * 4. make a dynamic block class, which will efficiently collect references and + * create/update dynamic locations/children + * + * @param str + * @returns a new block type, that can build concrete blocks + */ + function createBlock(str) { + if (str in cache$1) { + return cache$1[str]; + } + // step 0: prepare html base element + const doc = new DOMParser().parseFromString(`${str}`, "text/xml"); + const node = doc.firstChild.firstChild; + if (config.shouldNormalizeDom) { + normalizeNode(node); + } + // step 1: prepare intermediate tree + const tree = buildTree(node); + // step 2: prepare block context + const context = buildContext(tree); + // step 3: build the final block class + const template = tree.el; + const Block = buildBlock(template, context); + cache$1[str] = Block; + return Block; + } + // ----------------------------------------------------------------------------- + // Helper + // ----------------------------------------------------------------------------- + function normalizeNode(node) { + if (node.nodeType === Node.TEXT_NODE) { + if (!/\S/.test(node.textContent)) { + node.remove(); + return; + } + } + if (node.nodeType === Node.ELEMENT_NODE) { + if (node.tagName === "pre") { + return; + } + } + for (let i = node.childNodes.length - 1; i >= 0; --i) { + normalizeNode(node.childNodes.item(i)); + } + } + function buildTree(node, parent = null, domParentTree = null) { + switch (node.nodeType) { + case Node.ELEMENT_NODE: { + // HTMLElement + let currentNS = domParentTree && domParentTree.currentNS; + const tagName = node.tagName; + let el = undefined; + const info = []; + if (tagName.startsWith("block-text-")) { + const index = parseInt(tagName.slice(11), 10); + info.push({ type: "text", idx: index }); + el = document.createTextNode(""); + } + if (tagName.startsWith("block-child-")) { + if (!domParentTree.isRef) { + addRef(domParentTree); + } + const index = parseInt(tagName.slice(12), 10); + info.push({ type: "child", idx: index }); + el = document.createTextNode(""); + } + currentNS || (currentNS = node.namespaceURI); + if (!el) { + el = currentNS + ? document.createElementNS(currentNS, tagName) + : document.createElement(tagName); + } + if (el instanceof Element) { + if (!domParentTree) { + // some html elements may have side effects when setting their attributes. + // For example, setting the src attribute of an will trigger a + // request to get the corresponding image. This is something that we + // don't want at compile time. We avoid that by putting the content of + // the block in a