mancha 0.6.2 → 0.6.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/core.d.ts CHANGED
@@ -12,6 +12,7 @@ export declare abstract class IRenderer extends ReactiveProxyStore {
12
12
  protected readonly expressionCache: Map<string, Function>;
13
13
  protected readonly evalCallbacks: Map<string, EvalListener[]>;
14
14
  readonly _skipNodes: Set<Node>;
15
+ readonly _customElements: Map<string, Node>;
15
16
  abstract parseHTML(content: string, params?: ParserParams): DocumentFragment;
16
17
  abstract serializeHTML(root: DocumentFragment | Node): string;
17
18
  debug(flag: boolean): this;
@@ -29,7 +30,7 @@ export declare abstract class IRenderer extends ReactiveProxyStore {
29
30
  watchExpr(expr: string, args: {
30
31
  [key: string]: any;
31
32
  }, callback: EvalListener): Promise<void>;
32
- preprocessNode(root: Document | DocumentFragment | Node, params?: RenderParams): Promise<void>;
33
- renderNode(root: Document | DocumentFragment | Node, params?: RenderParams): Promise<Document | DocumentFragment | Node>;
33
+ preprocessNode<T extends Document | DocumentFragment | Node>(root: T, params?: RenderParams): Promise<T>;
34
+ renderNode<T extends Document | DocumentFragment | Node>(root: T, params?: RenderParams): Promise<T>;
34
35
  mount(root: Document | DocumentFragment | Node, params?: RenderParams): Promise<void>;
35
36
  }
package/dist/core.js CHANGED
@@ -7,7 +7,7 @@ export function* traverse(root, skip = new Set()) {
7
7
  // Also yield the root node.
8
8
  yield root;
9
9
  while (frontier.length) {
10
- const node = frontier.pop();
10
+ const node = frontier.shift();
11
11
  if (!explored.has(node)) {
12
12
  explored.add(node);
13
13
  yield node;
@@ -43,6 +43,7 @@ export class IRenderer extends ReactiveProxyStore {
43
43
  expressionCache = new Map();
44
44
  evalCallbacks = new Map();
45
45
  _skipNodes = new Set();
46
+ _customElements = new Map();
46
47
  debug(flag) {
47
48
  this.debugging = flag;
48
49
  return this;
@@ -80,6 +81,8 @@ export class IRenderer extends ReactiveProxyStore {
80
81
  }
81
82
  clone() {
82
83
  const instance = new this.constructor(Object.fromEntries(this.store.entries()));
84
+ // Custom elements are shared across all instances.
85
+ instance._customElements = this._customElements;
83
86
  return instance.debug(this.debugging);
84
87
  }
85
88
  log(...args) {
@@ -145,9 +148,15 @@ export class IRenderer extends ReactiveProxyStore {
145
148
  await RendererPlugins.resolveIncludes.call(this, node, params);
146
149
  // Resolve all the relative paths in the node.
147
150
  await RendererPlugins.rebaseRelativePaths.call(this, node, params);
151
+ // Register all the custom elements in the node.
152
+ await RendererPlugins.registerCustomElements.call(this, node, params);
153
+ // Resolve all the custom elements in the node.
154
+ await RendererPlugins.resolveCustomElements.call(this, node, params);
148
155
  });
149
156
  // Wait for all the rendering operations to complete.
150
157
  await Promise.all(promises.generator());
158
+ // Return the input node, which should now be fully preprocessed.
159
+ return root;
151
160
  }
152
161
  async renderNode(root, params) {
153
162
  // Iterate over all the nodes and apply appropriate handlers.
package/dist/dome.d.ts CHANGED
@@ -12,6 +12,8 @@ export declare function attributeNameToCamelCase(name: string): string;
12
12
  export declare function getAttribute(elem: __Element, name: string): string | null;
13
13
  export declare function setAttribute(elem: __Element, name: string, value: string): void;
14
14
  export declare function removeAttribute(elem: __Element, name: string): void;
15
+ export declare function cloneAttribute(elemFrom: __Element, elemDest: __Element, name: string): void;
16
+ export declare function firstElementChild(elem: __Element): __Element | null;
15
17
  export declare function replaceWith(original: __ChildNode, ...replacement: __Node[]): void;
16
18
  export declare function appendChild(parent: __Node, node: __Node): __Node;
17
19
  export declare function removeChild(parent: __Node, node: __Node): __Node;
package/dist/dome.js CHANGED
@@ -29,6 +29,23 @@ export function removeAttribute(elem, name) {
29
29
  else
30
30
  elem.removeAttribute?.(name);
31
31
  }
32
+ export function cloneAttribute(elemFrom, elemDest, name) {
33
+ if (elemFrom instanceof _Element && elemDest instanceof _Element) {
34
+ elemDest.attribs[name] = elemFrom.attribs[name];
35
+ }
36
+ else {
37
+ const attr = elemFrom?.getAttributeNode?.(name);
38
+ elemDest?.setAttributeNode?.(attr?.cloneNode(true));
39
+ }
40
+ }
41
+ export function firstElementChild(elem) {
42
+ if (elem instanceof _Element) {
43
+ return elem.children.find((child) => child instanceof _Element);
44
+ }
45
+ else {
46
+ return elem.firstElementChild;
47
+ }
48
+ }
32
49
  export function replaceWith(original, ...replacement) {
33
50
  if (hasFunction(original, "replaceWith")) {
34
51
  return original.replaceWith(...replacement);
@@ -3,6 +3,7 @@ export declare class Iterator<T> {
3
3
  constructor(iter: Iterable<T>);
4
4
  filter(fn: (val: T) => boolean): Iterator<T>;
5
5
  map<S>(fn: (val: T) => S): Iterator<S>;
6
+ find(fn: (val: T) => boolean): T | undefined;
6
7
  array(): T[];
7
8
  generator(): Iterable<T>;
8
9
  static filterGenerator<T>(fn: (val: T) => boolean, iter: Iterable<T>): Iterable<T>;
package/dist/iterator.js CHANGED
@@ -9,6 +9,13 @@ export class Iterator {
9
9
  map(fn) {
10
10
  return new Iterator(Iterator.mapGenerator(fn, this.iterable));
11
11
  }
12
+ find(fn) {
13
+ for (const val of this.iterable) {
14
+ if (fn(val))
15
+ return val;
16
+ }
17
+ return undefined;
18
+ }
12
19
  array() {
13
20
  return Array.from(this.iterable);
14
21
  }
package/dist/mancha.js CHANGED
@@ -1 +1 @@
1
- (()=>{"use strict";class t{timeouts=new Map;debounce(t,e){return new Promise(((s,r)=>{const n=this.timeouts.get(e);n&&clearTimeout(n),this.timeouts.set(e,setTimeout((()=>{try{s(e()),this.timeouts.delete(e)}catch(t){r(t)}}),t))}))}}function e(t,r,n=!0){if(null==t||function(t){return t instanceof s||t.__is_proxy__}(t))return t;if(n)for(const s in t)t.hasOwnProperty(s)&&"object"==typeof t[s]&&null!=t[s]&&(t[s]=e(t[s],r));return new Proxy(t,{deleteProperty:(t,e)=>e in t&&(delete t[e],r(),!0),set:(t,s,i,a)=>{n&&"object"==typeof i&&(i=e(i,r));const o=Reflect.set(t,s,i,a);return r(),o},get:(t,e,s)=>"__is_proxy__"===e||Reflect.get(t,e,s)})}class s extends t{value=null;listeners=[];constructor(t=null,...e){super(),this.value=this.wrapObjValue(t),e.forEach((t=>this.watch(t)))}static from(t,...e){return t instanceof s?(e.forEach(t.watch),t):new s(t,...e)}wrapObjValue(t){return null===t||"object"!=typeof t?t:e(t,(()=>this.trigger()))}get(){return this.value}async set(t){if(this.value!==t){const e=this.value;this.value=this.wrapObjValue(t),await this.trigger(e)}}watch(t){this.listeners.push(t)}unwatch(t){this.listeners=this.listeners.filter((e=>e!==t))}trigger(t=null){const e=this.listeners.slice();return this.debounce(10,(()=>Promise.all(e.map((e=>e(this.value,t)))).then((()=>{}))))}}class r extends t{store=new Map;debouncedListeners=new Map;lock=Promise.resolve();constructor(t){super();for(const[e,r]of Object.entries(t||{}))this.store.set(e,s.from(this.wrapFnValue(r)))}wrapFnValue(t){return t&&"function"==typeof t?(...e)=>t.call(n(this),...e):t}get $(){return n(this)}entries(){return this.store.entries()}get(t){return this.store.get(t)?.get()}async set(t,e){this.store.has(t)?await this.store.get(t).set(this.wrapFnValue(e)):this.store.set(t,s.from(this.wrapFnValue(e)))}del(t){return this.store.delete(t)}has(t){return this.store.has(t)}async update(t){await Promise.all(Object.entries(t).map((([t,e])=>this.set(t,e))))}watch(t,e){t=Array.isArray(t)?t:[t];const s=()=>e(...t.map((t=>this.store.get(t).get()))),r=()=>this.debounce(10,s);t.forEach((t=>this.store.get(t).watch(r))),this.debouncedListeners.set(e,r)}unwatch(t,e){(t=Array.isArray(t)?t:[t]).forEach((t=>this.store.get(t).unwatch(this.debouncedListeners.get(e)))),this.debouncedListeners.delete(e)}async trigger(t){t=Array.isArray(t)?t:[t],await Promise.all(t.map((t=>this.store.get(t).trigger())))}async trace(t){const e=new Set,s=n(this,((t,s)=>{"get"===t&&e.add(s)}));return[await t.call(s),Array.from(e)]}async computed(t,e){const[s,r]=await this.trace(e);this.watch(r,(async()=>this.set(t,await e.call(n(this))))),this.set(t,s)}}function n(t,e=(()=>{})){const s=Array.from(t.entries()).map((([t])=>t)),r=Object.fromEntries(s.map((t=>[t,void 0])));return new Proxy(Object.assign({},t,r),{get:(s,r,n)=>"string"==typeof r&&t.has(r)?(e("get",r),t.get(r)):"get"===r?s=>(e("get",s),t.get(s)):Reflect.get(t,r,n),set:(s,r,n,i)=>("string"!=typeof r||r in t?Reflect.set(t,r,n,i):(e("set",r,n),t.set(r,n)),!0)})}class i{iterable;constructor(t){this.iterable=t}filter(t){return new i(i.filterGenerator(t,this.iterable))}map(t){return new i(i.mapGenerator(t,this.iterable))}array(){return Array.from(this.iterable)}*generator(){for(const t of this.iterable)yield t}static*filterGenerator(t,e){for(const s of e)t(s)&&(yield s)}static*mapGenerator(t,e){for(const s of e)yield t(s)}static equals(t,e){const s=t[Symbol.iterator](),r=e[Symbol.iterator]();let n=s.next(),i=r.next();for(;!n.done&&!i.done;){if(n.value!==i.value)return!1;n=s.next(),i=r.next()}return n.done===i.done}}var a,o;(o=a||(a={})).Root="root",o.Text="text",o.Directive="directive",o.Comment="comment",o.Script="script",o.Style="style",o.Tag="tag",o.CDATA="cdata",o.Doctype="doctype",a.Root,a.Text,a.Directive,a.Comment,a.Script,a.Style,a.Tag,a.CDATA,a.Doctype;class c{constructor(){this.parent=null,this.prev=null,this.next=null,this.startIndex=null,this.endIndex=null}get parentNode(){return this.parent}set parentNode(t){this.parent=t}get previousSibling(){return this.prev}set previousSibling(t){this.prev=t}get nextSibling(){return this.next}set nextSibling(t){this.next=t}cloneNode(t=!1){return w(this,t)}}class l extends c{constructor(t){super(),this.data=t}get nodeValue(){return this.data}set nodeValue(t){this.data=t}}class h extends l{constructor(){super(...arguments),this.type=a.Text}get nodeType(){return 3}}class u extends l{constructor(){super(...arguments),this.type=a.Comment}get nodeType(){return 8}}class d extends l{constructor(t,e){super(e),this.name=t,this.type=a.Directive}get nodeType(){return 1}}class p extends c{constructor(t){super(),this.children=t}get firstChild(){var t;return null!==(t=this.children[0])&&void 0!==t?t:null}get lastChild(){return this.children.length>0?this.children[this.children.length-1]:null}get childNodes(){return this.children}set childNodes(t){this.children=t}}class f extends p{constructor(){super(...arguments),this.type=a.CDATA}get nodeType(){return 4}}class m extends p{constructor(){super(...arguments),this.type=a.Root}get nodeType(){return 9}}class g extends p{constructor(t,e,s=[],r=("script"===t?a.Script:"style"===t?a.Style:a.Tag)){super(s),this.name=t,this.attribs=e,this.type=r}get nodeType(){return 1}get tagName(){return this.name}set tagName(t){this.name=t}get attributes(){return Object.keys(this.attribs).map((t=>{var e,s;return{name:t,value:this.attribs[t],namespace:null===(e=this["x-attribsNamespace"])||void 0===e?void 0:e[t],prefix:null===(s=this["x-attribsPrefix"])||void 0===s?void 0:s[t]}}))}}function w(t,e=!1){let s;if(function(t){return t.type===a.Text}(t))s=new h(t.data);else if(function(t){return t.type===a.Comment}(t))s=new u(t.data);else if(function(t){return(e=t).type===a.Tag||e.type===a.Script||e.type===a.Style;var e}(t)){const r=e?y(t.children):[],n=new g(t.name,{...t.attribs},r);r.forEach((t=>t.parent=n)),null!=t.namespace&&(n.namespace=t.namespace),t["x-attribsNamespace"]&&(n["x-attribsNamespace"]={...t["x-attribsNamespace"]}),t["x-attribsPrefix"]&&(n["x-attribsPrefix"]={...t["x-attribsPrefix"]}),s=n}else if(function(t){return t.type===a.CDATA}(t)){const r=e?y(t.children):[],n=new f(r);r.forEach((t=>t.parent=n)),s=n}else if(function(t){return t.type===a.Root}(t)){const r=e?y(t.children):[],n=new m(r);r.forEach((t=>t.parent=n)),t["x-mode"]&&(n["x-mode"]=t["x-mode"]),s=n}else{if(!function(t){return t.type===a.Directive}(t))throw new Error(`Not implemented yet: ${t.type}`);{const e=new d(t.name,t.data);null!=t["x-name"]&&(e["x-name"]=t["x-name"],e["x-publicId"]=t["x-publicId"],e["x-systemId"]=t["x-systemId"]),s=e}}return s.startIndex=t.startIndex,s.endIndex=t.endIndex,null!=t.sourceCodeLocation&&(s.sourceCodeLocation=t.sourceCodeLocation),s}function y(t){const e=t.map((t=>w(t,!0)));for(let t=1;t<e.length;t++)e[t].prev=e[t-1],e[t-1].next=e[t];return e}function b(t,e){return"function"==typeof t?.[e]}function x(t,e){return t instanceof g?t.attribs?.[e]:t.getAttribute?.(e)}function v(t,e,s){t instanceof g?t.attribs[e]=s:t.setAttribute?.(e,s)}function A(t,e){t instanceof g?delete t.attribs[e]:t.removeAttribute?.(e)}function N(t,...e){if(b(t,"replaceWith"))return t.replaceWith(...e);{const s=t,r=s.parentNode,n=Array.from(r.childNodes).indexOf(s);e.forEach((t=>t.parentNode=r)),r.childNodes=[].concat(Array.from(r.childNodes).slice(0,n)).concat(e).concat(Array.from(r.childNodes).slice(n+1))}}function $(t,e){return b(e,"appendChild")?t.appendChild(e):(t.childNodes.push(e),e.parentNode=t,e)}function E(t,e){if(b(e,"removeChild"))return t.removeChild(e);{const s=e;return t.childNodes=t.children.filter((t=>t!==s)),s}}function k(t,e,s){return s?b(t,"insertBefore")?t.insertBefore(e,s):(N(s,e,s),e):$(t,e)}window.htmlparser2;const C=new Set([":bind",":bind-events",":data",":for",":show","@watch","$html"]);var S;function*T(t,e=new Set){const s=new Set,r=Array.from(t.childNodes).filter((t=>!e.has(t)));for(yield t;r.length;){const t=r.pop();s.has(t)||(s.add(t),yield t),t.childNodes&&Array.from(t.childNodes).filter((t=>!e.has(t))).forEach((t=>r.push(t)))}}function _(t){return t.includes("/")?t.split("/").slice(0,-1).join("/"):""}function P(t){return!(t.includes("://")||t.startsWith("/")||t.startsWith("#")||t.startsWith("data:"))}!function(t){t.resolveIncludes=async function(t,e){const s=t;if("include"!==s.tagName?.toLocaleLowerCase())return;this.log("<include> tag found in:\n",t),this.log("<include> params:",e);const r=x(s,"src");if(!r)throw new Error(`"src" attribute missing from ${t}.`);const n=e=>{const r=e.firstChild;for(const t of Array.from(s.attributes))r&&"src"!==t.name&&v(r,t.name,t.value);N(t,...Array.from(e.childNodes))},i={...e,root:!1,maxdepth:e?.maxdepth-1};if(0===i.maxdepth)throw new Error("Maximum recursion depth reached.");if(r.includes("://")||r.startsWith("//"))this.log("Including remote file from absolute path:",r),await this.preprocessRemote(r,i).then(n);else if(e?.dirpath?.includes("://")||e?.dirpath?.startsWith("//")){const t=e.dirpath&&"."!==e.dirpath?`${e.dirpath}/${r}`:r;this.log("Including remote file from relative path:",t),await this.preprocessRemote(t,i).then(n)}else if("/"===r.charAt(0))this.log("Including local file from absolute path:",r),await this.preprocessLocal(r,i).then(n);else{const t=e?.dirpath&&"."!==e?.dirpath?`${e?.dirpath}/${r}`:r;this.log("Including local file from relative path:",t),await this.preprocessLocal(t,i).then(n)}},t.rebaseRelativePaths=async function(t,e){const s=t,r=s.tagName?.toLowerCase();if(!e?.dirpath)return;const n=x(s,"src"),i=x(s,"href"),a=x(s,"data"),o=n||i||a;o&&(o&&P(o)&&this.log("Rebasing relative path as:",e.dirpath,"/",o),"img"===r&&n&&P(n)?v(s,"src",`${e.dirpath}/${n}`):"a"===r&&i&&P(i)||"link"===r&&i&&P(i)?v(s,"href",`${e.dirpath}/${i}`):"script"===r&&n&&P(n)||"source"===r&&n&&P(n)||"audio"===r&&n&&P(n)||"video"===r&&n&&P(n)||"track"===r&&n&&P(n)||"iframe"===r&&n&&P(n)?v(s,"src",`${e.dirpath}/${n}`):"object"===r&&a&&P(a)?v(s,"data",`${e.dirpath}/${a}`):"input"===r&&n&&P(n)?v(s,"src",`${e.dirpath}/${n}`):("area"===r&&i&&P(i)||"base"===r&&i&&P(i))&&v(s,"href",`${e.dirpath}/${i}`))},t.resolveTextNodeExpressions=async function(t,e){if(3!==t.nodeType)return;const s=function(t){return t instanceof c?t.data:t.nodeValue}(t)||"";this.log("Processing node content value:\n",s);const r=new RegExp(/{{ ([^}]+) }}/gm),n=Array.from(s.matchAll(r)).map((t=>t[1])),i=async()=>{let e=s;for(const s of n){const[r]=await this.eval(s,{$elem:t});e=e.replace(`{{ ${s} }}`,String(r))}!function(t,e){t instanceof c?t.data=e:t.nodeValue=e}(t,e)};await Promise.all(n.map((e=>this.watchExpr(e,{$elem:t},i))))},t.resolveDataAttribute=async function(t,e){if(this._skipNodes.has(t))return;const s=t,r=x(s,":data");if(r){this.log(":data attribute found in:\n",t),A(s,":data");const n=this.clone();t.renderer=n;const[i]=await n.eval(r,{$elem:t});await n.update(i);for(const e of T(t,this._skipNodes))this._skipNodes.add(e);await n.mount(t,e)}},t.resolveWatchAttribute=async function(t,e){if(this._skipNodes.has(t))return;const s=t,r=x(s,"@watch");r&&(this.log("@watch attribute found in:\n",t),A(s,"@watch"),await this.watchExpr(r,{$elem:t},(()=>{})))},t.resolveTextAttributes=async function(t,e){if(this._skipNodes.has(t))return;const s=t,r=x(s,"$text");r&&(this.log("$text attribute found in:\n",t),A(s,"$text"),await this.watchExpr(r,{$elem:t},(e=>function(t,e){t instanceof g?t.children=[new h(e)]:t.textContent=e}(t,e))))},t.resolveHtmlAttribute=async function(t,e){if(this._skipNodes.has(t))return;const s=t,r=x(s,"$html");if(r){this.log("$html attribute found in:\n",t),A(s,"$html");const n=this.clone();await this.watchExpr(r,{$elem:t},(async t=>{const r=await n.preprocessString(t,e);await n.renderNode(r,e),function(t,...e){b(t,"replaceChildren")?t.replaceChildren(...e):(t.childNodes=e,e.forEach((e=>e.parentNode=t)))}(s,r)}))}},t.resolvePropAttributes=async function(t,e){if(this._skipNodes.has(t))return;const s=t;for(const e of Array.from(s.attributes||[]))if(e.name.startsWith("$")&&!C.has(e.name)){this.log(e.name,"attribute found in:\n",t),A(s,e.name);const r=e.name.slice(1).replace(/-./g,(t=>t[1].toUpperCase()));await this.watchExpr(e.value,{$elem:t},(e=>t[r]=e))}},t.resolveAttrAttributes=async function(t,e){if(this._skipNodes.has(t))return;const s=t;for(const e of Array.from(s.attributes||[]))if(e.name.startsWith(":")&&!C.has(e.name)){this.log(e.name,"attribute found in:\n",t),A(s,e.name);const r=e.name.slice(1);await this.watchExpr(e.value,{$elem:t},(t=>v(s,r,t)))}},t.resolveEventAttributes=async function(t,e){if(this._skipNodes.has(t))return;const s=t;for(const e of Array.from(s.attributes||[]))e.name.startsWith("@")&&!C.has(e.name)&&(this.log(e.name,"attribute found in:\n",t),A(s,e.name),t.addEventListener?.(e.name.substring(1),(s=>{this.eval(e.value,{$elem:t,$event:s})})))},t.resolveForAttribute=async function(t,e){if(this._skipNodes.has(t))return;const s=t,r=x(s,":for")?.trim();if(r){this.log(":for attribute found in:\n",t),A(s,":for");for(const e of T(t,this._skipNodes))this._skipNodes.add(e);const n=t.parentNode,i=function(t,e){return e?e.createElement(t):new g(t,{})}("template",t.ownerDocument);k(n,i,t),E(n,t),$(i,t),this.log(":for template:\n",i);const a=r.split(" in ",2);if(2!==a.length)throw new Error(`Invalid :for format: \`${r}\`. Expected "{key} in {expression}".`);const o=[],[c,l]=a;await this.watchExpr(l,{$elem:t},(s=>(this.log(":for list items:",s),this.lock=this.lock.then((()=>new Promise((async r=>{if(o.splice(0,o.length).forEach((t=>{E(n,t),this._skipNodes.delete(t)})),!Array.isArray(s))return console.error(`Expression did not yield a list: \`${l}\` => \`${s}\``),r();for(const r of s){const s=this.clone();await s.set(c,r);const n=t.cloneNode(!0);o.push(n),this._skipNodes.add(n),await s.mount(n,e),this.log("Rendered list child:\n",n,n.outerHTML)}const a=i.nextSibling;for(const t of o)k(n,t,a);r()})))).catch((t=>{throw console.error(t),new Error(t)})).then(),this.lock)))}},t.resolveBindAttribute=async function(t,e){if(this._skipNodes.has(t))return;const s=t,r=x(s,":bind");if(r){this.log(":bind attribute found in:\n",t);const e=["change","input"],n=x(s,":bind-events")?.split(",")||e;A(s,":bind"),A(s,":bind-events");const i="checkbox"===x(s,"type")?"checked":"value",a=`$elem.${i} = ${r}`;await this.watchExpr(a,{$elem:t},(t=>s[i]=t));const o=`${r} = $elem.${i}`;for(const e of n)t.addEventListener(e,(()=>this.eval(o,{$elem:t})))}},t.resolveShowAttribute=async function(t,e){if(this._skipNodes.has(t))return;const s=t,r=x(s,":show");if(r){this.log(":show attribute found in:\n",t),A(s,":show");const e="none"===s.style?.display?"":s.style?.display??x(s,"style")?.split(";")?.find((t=>"display"===t.split(":")[0]))?.split(":")?.at(1)?.trim();await this.watchExpr(r,{$elem:t},(t=>{s.style?s.style.display=t?e:"none":v(s,"style",`display: ${t?e:"none"};`)}))}}}(S||(S={}));class L extends r{debugging=!1;dirpath="";evalkeys=["$elem","$event"];expressionCache=new Map;evalCallbacks=new Map;_skipNodes=new Set;debug(t){return this.debugging=t,this}async fetchRemote(t,e){return fetch(t,{cache:e?.cache??"default"}).then((t=>t.text()))}async fetchLocal(t,e){return this.fetchRemote(t,e)}async preprocessString(t,e){this.log("Preprocessing string content with params:\n",e);const s=this.parseHTML(t,e);return await this.preprocessNode(s,e),s}async preprocessLocal(t,e){const s=await this.fetchLocal(t,e);return this.preprocessString(s,{...e,dirpath:_(t),root:e?.root??!t.endsWith(".tpl.html")})}async preprocessRemote(t,e){const s={};e?.cache&&(s.cache=e.cache);const r=await fetch(t,s).then((t=>t.text()));return this.preprocessString(r,{...e,dirpath:_(t),root:e?.root??!t.endsWith(".tpl.html")})}clone(){return new this.constructor(Object.fromEntries(this.store.entries())).debug(this.debugging)}log(...t){this.debugging&&console.debug(...t)}cachedExpressionFunction(t){return this.expressionCache.has(t)||this.expressionCache.set(t,function(t,e=[]){return new Function(...e,`with (this) { return (async () => (${t}))(); }`)}(t,this.evalkeys)),this.expressionCache.get(t)}async eval(t,e={}){if(this.store.has(t))return[this.get(t),[t]];{const s=this.cachedExpressionFunction(t),r=this.evalkeys.map((t=>e[t]));if(Object.keys(e).some((t=>!this.evalkeys.includes(t))))throw new Error(`Invalid argument key, must be one of: ${this.evalkeys.join(", ")}`);const[n,i]=await this.trace((async function(){return s.call(this,...r)}));return this.log(`eval \`${t}\` => `,n,`[ ${i.join(", ")} ]`),[n,i]}}watchExpr(t,e,s){if(this.evalCallbacks.has(t))return this.evalCallbacks.get(t)?.push(s),this.eval(t,e).then((([t,e])=>s(t,e)));this.evalCallbacks.set(t,[s]);const r=[],n=async()=>{const[s,i]=await this.eval(t,e),a=this.evalCallbacks.get(t)||[];await Promise.all(a.map((t=>t(s,i)))),r.length>0&&this.unwatch(r,n),r.splice(0,r.length,...i),this.watch(i,n)};return n()}async preprocessNode(t,e){e=Object.assign({dirpath:this.dirpath,maxdepth:10},e);const s=new i(T(t,this._skipNodes)).map((async t=>{this.log("Preprocessing node:\n",t),await S.resolveIncludes.call(this,t,e),await S.rebaseRelativePaths.call(this,t,e)}));await Promise.all(s.generator())}async renderNode(t,e){for(const s of T(t,this._skipNodes))this.log("Rendering node:\n",s),await S.resolveDataAttribute.call(this,s,e),await S.resolveForAttribute.call(this,s,e),await S.resolveTextAttributes.call(this,s,e),await S.resolveHtmlAttribute.call(this,s,e),await S.resolveShowAttribute.call(this,s,e),await S.resolveWatchAttribute.call(this,s,e),await S.resolveBindAttribute.call(this,s,e),await S.resolvePropAttributes.call(this,s,e),await S.resolveAttrAttributes.call(this,s,e),await S.resolveEventAttributes.call(this,s,e),await S.resolveTextNodeExpressions.call(this,s,e);return t}async mount(t,e){await this.preprocessNode(t,e),await this.renderNode(t,e)}}const R=new class extends L{dirpath=_(self.location.href);parseHTML(t,e={root:!1}){if(e.root)return(new DOMParser).parseFromString(t,"text/html");{const e=document.createRange();return e.selectNodeContents(document.body),e.createContextualFragment(t)}}serializeHTML(t){return(new XMLSerializer).serializeToString(t).replace(/\s?xmlns="[^"]+"/gm,"")}preprocessLocal(t,e){return this.preprocessRemote(t,e)}};self.Mancha=R;const j=self.document?.currentScript;if(self.document?.currentScript?.hasAttribute("init")){const t=j?.hasAttribute("debug"),e=j?.getAttribute("cache"),s=j?.getAttribute("target")?.split(",")||["body"];window.addEventListener("load",(()=>{s.map((async s=>{const r=self.document.querySelector(s);await R.debug(t).mount(r,{cache:e})}))}))}})();
1
+ (()=>{"use strict";class t{timeouts=new Map;debounce(t,e){return new Promise(((s,r)=>{const n=this.timeouts.get(e);n&&clearTimeout(n),this.timeouts.set(e,setTimeout((()=>{try{s(e()),this.timeouts.delete(e)}catch(t){r(t)}}),t))}))}}function e(t,r,n=!0){if(null==t||function(t){return t instanceof s||t.__is_proxy__}(t)||t instanceof Promise)return t;if(n)for(const s in t)t.hasOwnProperty(s)&&"object"==typeof t[s]&&null!=t[s]&&(t[s]=e(t[s],r));return new Proxy(t,{deleteProperty:(t,e)=>e in t&&(delete t[e],r(),!0),set:(t,s,i,a)=>{n&&"object"==typeof i&&(i=e(i,r));const o=Reflect.set(t,s,i,a);return r(),o},get:(t,e,s)=>"__is_proxy__"===e||Reflect.get(t,e,s)})}class s extends t{value=null;listeners=[];constructor(t=null,...e){super(),this.value=this.wrapObjValue(t),e.forEach((t=>this.watch(t)))}static from(t,...e){return t instanceof s?(e.forEach(t.watch),t):new s(t,...e)}wrapObjValue(t){return null===t||"object"!=typeof t?t:e(t,(()=>this.trigger()))}get(){return this.value}async set(t){if(this.value!==t){const e=this.value;this.value=this.wrapObjValue(t),await this.trigger(e)}}watch(t){this.listeners.push(t)}unwatch(t){this.listeners=this.listeners.filter((e=>e!==t))}trigger(t=null){const e=this.listeners.slice();return this.debounce(10,(()=>Promise.all(e.map((e=>e(this.value,t)))).then((()=>{}))))}}class r extends t{store=new Map;debouncedListeners=new Map;lock=Promise.resolve();constructor(t){super();for(const[e,r]of Object.entries(t||{}))this.store.set(e,s.from(this.wrapFnValue(r)))}wrapFnValue(t){return t&&"function"==typeof t?(...e)=>t.call(n(this),...e):t}get $(){return n(this)}entries(){return this.store.entries()}get(t){return this.store.get(t)?.get()}async set(t,e){this.store.has(t)?await this.store.get(t).set(this.wrapFnValue(e)):this.store.set(t,s.from(this.wrapFnValue(e)))}del(t){return this.store.delete(t)}has(t){return this.store.has(t)}async update(t){await Promise.all(Object.entries(t).map((([t,e])=>this.set(t,e))))}watch(t,e){t=Array.isArray(t)?t:[t];const s=()=>e(...t.map((t=>this.store.get(t).get()))),r=()=>this.debounce(10,s);t.forEach((t=>this.store.get(t).watch(r))),this.debouncedListeners.set(e,r)}unwatch(t,e){(t=Array.isArray(t)?t:[t]).forEach((t=>this.store.get(t).unwatch(this.debouncedListeners.get(e)))),this.debouncedListeners.delete(e)}async trigger(t){t=Array.isArray(t)?t:[t],await Promise.all(t.map((t=>this.store.get(t).trigger())))}async trace(t){const e=new Set,s=n(this,((t,s)=>{"get"===t&&e.add(s)}));return[await t.call(s),Array.from(e)]}async computed(t,e){const[s,r]=await this.trace(e);this.watch(r,(async()=>this.set(t,await e.call(n(this))))),this.set(t,s)}}function n(t,e=(()=>{})){const s=Array.from(t.entries()).map((([t])=>t)),r=Object.fromEntries(s.map((t=>[t,void 0])));return new Proxy(Object.assign({},t,r),{get:(s,r,n)=>"string"==typeof r&&t.has(r)?(e("get",r),t.get(r)):"get"===r?s=>(e("get",s),t.get(s)):Reflect.get(t,r,n),set:(s,r,n,i)=>("string"!=typeof r||r in t?Reflect.set(t,r,n,i):(e("set",r,n),t.set(r,n)),!0)})}class i{iterable;constructor(t){this.iterable=t}filter(t){return new i(i.filterGenerator(t,this.iterable))}map(t){return new i(i.mapGenerator(t,this.iterable))}find(t){for(const e of this.iterable)if(t(e))return e}array(){return Array.from(this.iterable)}*generator(){for(const t of this.iterable)yield t}static*filterGenerator(t,e){for(const s of e)t(s)&&(yield s)}static*mapGenerator(t,e){for(const s of e)yield t(s)}static equals(t,e){const s=t[Symbol.iterator](),r=e[Symbol.iterator]();let n=s.next(),i=r.next();for(;!n.done&&!i.done;){if(n.value!==i.value)return!1;n=s.next(),i=r.next()}return n.done===i.done}}var a,o;(o=a||(a={})).Root="root",o.Text="text",o.Directive="directive",o.Comment="comment",o.Script="script",o.Style="style",o.Tag="tag",o.CDATA="cdata",o.Doctype="doctype",a.Root,a.Text,a.Directive,a.Comment,a.Script,a.Style,a.Tag,a.CDATA,a.Doctype;class c{constructor(){this.parent=null,this.prev=null,this.next=null,this.startIndex=null,this.endIndex=null}get parentNode(){return this.parent}set parentNode(t){this.parent=t}get previousSibling(){return this.prev}set previousSibling(t){this.prev=t}get nextSibling(){return this.next}set nextSibling(t){this.next=t}cloneNode(t=!1){return w(this,t)}}class l extends c{constructor(t){super(),this.data=t}get nodeValue(){return this.data}set nodeValue(t){this.data=t}}class h extends l{constructor(){super(...arguments),this.type=a.Text}get nodeType(){return 3}}class u extends l{constructor(){super(...arguments),this.type=a.Comment}get nodeType(){return 8}}class d extends l{constructor(t,e){super(e),this.name=t,this.type=a.Directive}get nodeType(){return 1}}class p extends c{constructor(t){super(),this.children=t}get firstChild(){var t;return null!==(t=this.children[0])&&void 0!==t?t:null}get lastChild(){return this.children.length>0?this.children[this.children.length-1]:null}get childNodes(){return this.children}set childNodes(t){this.children=t}}class f extends p{constructor(){super(...arguments),this.type=a.CDATA}get nodeType(){return 4}}class m extends p{constructor(){super(...arguments),this.type=a.Root}get nodeType(){return 9}}class g extends p{constructor(t,e,s=[],r=("script"===t?a.Script:"style"===t?a.Style:a.Tag)){super(s),this.name=t,this.attribs=e,this.type=r}get nodeType(){return 1}get tagName(){return this.name}set tagName(t){this.name=t}get attributes(){return Object.keys(this.attribs).map((t=>{var e,s;return{name:t,value:this.attribs[t],namespace:null===(e=this["x-attribsNamespace"])||void 0===e?void 0:e[t],prefix:null===(s=this["x-attribsPrefix"])||void 0===s?void 0:s[t]}}))}}function w(t,e=!1){let s;if(function(t){return t.type===a.Text}(t))s=new h(t.data);else if(function(t){return t.type===a.Comment}(t))s=new u(t.data);else if(function(t){return(e=t).type===a.Tag||e.type===a.Script||e.type===a.Style;var e}(t)){const r=e?y(t.children):[],n=new g(t.name,{...t.attribs},r);r.forEach((t=>t.parent=n)),null!=t.namespace&&(n.namespace=t.namespace),t["x-attribsNamespace"]&&(n["x-attribsNamespace"]={...t["x-attribsNamespace"]}),t["x-attribsPrefix"]&&(n["x-attribsPrefix"]={...t["x-attribsPrefix"]}),s=n}else if(function(t){return t.type===a.CDATA}(t)){const r=e?y(t.children):[],n=new f(r);r.forEach((t=>t.parent=n)),s=n}else if(function(t){return t.type===a.Root}(t)){const r=e?y(t.children):[],n=new m(r);r.forEach((t=>t.parent=n)),t["x-mode"]&&(n["x-mode"]=t["x-mode"]),s=n}else{if(!function(t){return t.type===a.Directive}(t))throw new Error(`Not implemented yet: ${t.type}`);{const e=new d(t.name,t.data);null!=t["x-name"]&&(e["x-name"]=t["x-name"],e["x-publicId"]=t["x-publicId"],e["x-systemId"]=t["x-systemId"]),s=e}}return s.startIndex=t.startIndex,s.endIndex=t.endIndex,null!=t.sourceCodeLocation&&(s.sourceCodeLocation=t.sourceCodeLocation),s}function y(t){const e=t.map((t=>w(t,!0)));for(let t=1;t<e.length;t++)e[t].prev=e[t-1],e[t-1].next=e[t];return e}function b(t,e){return"function"==typeof t?.[e]}function v(t,e){return t instanceof g?t.attribs?.[e]:t.getAttribute?.(e)}function x(t,e,s){t instanceof g?t.attribs[e]=s:t.setAttribute?.(e,s)}function N(t,e){t instanceof g?delete t.attribs[e]:t.removeAttribute?.(e)}function A(t,e,s){if(t instanceof g&&e instanceof g)e.attribs[s]=t.attribs[s];else{const r=t?.getAttributeNode?.(s);e?.setAttributeNode?.(r?.cloneNode(!0))}}function $(t,...e){if(b(t,"replaceWith"))return t.replaceWith(...e);{const s=t,r=s.parentNode,n=Array.from(r.childNodes).indexOf(s);e.forEach((t=>t.parentNode=r)),r.childNodes=[].concat(Array.from(r.childNodes).slice(0,n)).concat(e).concat(Array.from(r.childNodes).slice(n+1))}}function E(t,e){return b(e,"appendChild")?t.appendChild(e):(t.childNodes.push(e),e.parentNode=t,e)}function C(t,e){if(b(e,"removeChild"))return t.removeChild(e);{const s=e;return t.childNodes=t.children.filter((t=>t!==s)),s}}function k(t,e,s){return s?b(t,"insertBefore")?t.insertBefore(e,s):($(s,e,s),e):E(t,e)}window.htmlparser2;const _=new Set([":bind",":bind-events",":data",":for",":show","@watch","$html"]);var S;function*T(t,e=new Set){const s=new Set,r=Array.from(t.childNodes).filter((t=>!e.has(t)));for(yield t;r.length;){const t=r.shift();s.has(t)||(s.add(t),yield t),t.childNodes&&Array.from(t.childNodes).filter((t=>!e.has(t))).forEach((t=>r.push(t)))}}function L(t){return t.includes("/")?t.split("/").slice(0,-1).join("/"):""}function P(t){return!(t.includes("://")||t.startsWith("/")||t.startsWith("#")||t.startsWith("data:"))}!function(t){t.resolveIncludes=async function(t,e){const s=t;if("include"!==s.tagName?.toLocaleLowerCase())return;this.log("<include> tag found in:\n",t),this.log("<include> params:",e);const r=v(s,"src");if(!r)throw new Error(`"src" attribute missing from ${t}.`);const n=e=>{const r=e.firstChild;for(const t of Array.from(s.attributes))r&&"src"!==t.name&&A(s,r,t.name);$(t,...e.childNodes)},i={...e,root:!1,maxdepth:e?.maxdepth-1};if(0===i.maxdepth)throw new Error("Maximum recursion depth reached.");if(r.includes("://")||r.startsWith("//"))this.log("Including remote file from absolute path:",r),await this.preprocessRemote(r,i).then(n);else if(e?.dirpath?.includes("://")||e?.dirpath?.startsWith("//")){const t=r.startsWith("/")?r:`${e.dirpath}/${r}`;this.log("Including remote file from relative path:",t),await this.preprocessRemote(t,i).then(n)}else if("/"===r.charAt(0))this.log("Including local file from absolute path:",r),await this.preprocessLocal(r,i).then(n);else{const t=e?.dirpath&&"."!==e?.dirpath?`${e?.dirpath}/${r}`:r;this.log("Including local file from relative path:",t),await this.preprocessLocal(t,i).then(n)}},t.rebaseRelativePaths=async function(t,e){const s=t,r=s.tagName?.toLowerCase();if(!e?.dirpath)return;const n=v(s,"src"),i=v(s,"href"),a=v(s,"data"),o=n||i||a;o&&(o&&P(o)&&this.log("Rebasing relative path as:",e.dirpath,"/",o),"img"===r&&n&&P(n)?x(s,"src",`${e.dirpath}/${n}`):"a"===r&&i&&P(i)||"link"===r&&i&&P(i)?x(s,"href",`${e.dirpath}/${i}`):"script"===r&&n&&P(n)||"source"===r&&n&&P(n)||"audio"===r&&n&&P(n)||"video"===r&&n&&P(n)||"track"===r&&n&&P(n)||"iframe"===r&&n&&P(n)?x(s,"src",`${e.dirpath}/${n}`):"object"===r&&a&&P(a)?x(s,"data",`${e.dirpath}/${a}`):"input"===r&&n&&P(n)?x(s,"src",`${e.dirpath}/${n}`):("area"===r&&i&&P(i)||"base"===r&&i&&P(i))&&x(s,"href",`${e.dirpath}/${i}`))},t.registerCustomElements=async function(t,e){const s=t;if("template"===s.tagName?.toLowerCase()&&v(s,"is")){const t=v(s,"is")?.toLowerCase();this._customElements.has(t)||(this.log(`Registering custom element: ${t}\n`,s),this._customElements.set(t,s.cloneNode(!0)),C(s.parentNode,s))}},t.resolveCustomElements=async function(t,e){const s=t,r=s.tagName?.toLowerCase();if(this._customElements.has(r)){this.log(`Processing custom element: ${r}\n`,s);const e=this._customElements.get(r),n=(e.content||e).cloneNode(!0),a=function(t){return t instanceof g?t.children.find((t=>t instanceof g)):t.firstElementChild}(n);for(const t of Array.from(s.attributes))a&&A(s,a,t.name);const o=new i(T(n)).find((t=>"slot"===t.tagName?.toLowerCase()));o&&$(o,...s.childNodes),$(t,...n.childNodes)}},t.resolveTextNodeExpressions=async function(t,e){if(3!==t.nodeType)return;const s=function(t){return t instanceof c?t.data:t.nodeValue}(t)||"";this.log("Processing node content value:\n",s);const r=new RegExp(/{{ ([^}]+) }}/gm),n=Array.from(s.matchAll(r)).map((t=>t[1])),i=async()=>{let e=s;for(const s of n){const[r]=await this.eval(s,{$elem:t});e=e.replace(`{{ ${s} }}`,String(r))}!function(t,e){t instanceof c?t.data=e:t.nodeValue=e}(t,e)};await Promise.all(n.map((e=>this.watchExpr(e,{$elem:t},i))))},t.resolveDataAttribute=async function(t,e){if(this._skipNodes.has(t))return;const s=t,r=v(s,":data");if(r){this.log(":data attribute found in:\n",t),N(s,":data");const n=this.clone();t.renderer=n;const[i]=await n.eval(r,{$elem:t});await n.update(i);for(const e of T(t,this._skipNodes))this._skipNodes.add(e);await n.mount(t,e)}},t.resolveWatchAttribute=async function(t,e){if(this._skipNodes.has(t))return;const s=t,r=v(s,"@watch");r&&(this.log("@watch attribute found in:\n",t),N(s,"@watch"),await this.watchExpr(r,{$elem:t},(()=>{})))},t.resolveTextAttributes=async function(t,e){if(this._skipNodes.has(t))return;const s=t,r=v(s,"$text");r&&(this.log("$text attribute found in:\n",t),N(s,"$text"),await this.watchExpr(r,{$elem:t},(e=>function(t,e){t instanceof g?t.children=[new h(e)]:t.textContent=e}(t,e))))},t.resolveHtmlAttribute=async function(t,e){if(this._skipNodes.has(t))return;const s=t,r=v(s,"$html");if(r){this.log("$html attribute found in:\n",t),N(s,"$html");const n=this.clone();await this.watchExpr(r,{$elem:t},(async t=>{const r=await n.preprocessString(t,e);await n.renderNode(r,e),function(t,...e){b(t,"replaceChildren")?t.replaceChildren(...e):(t.childNodes=e,e.forEach((e=>e.parentNode=t)))}(s,r)}))}},t.resolvePropAttributes=async function(t,e){if(this._skipNodes.has(t))return;const s=t;for(const e of Array.from(s.attributes||[]))if(e.name.startsWith("$")&&!_.has(e.name)){this.log(e.name,"attribute found in:\n",t),N(s,e.name);const r=e.name.slice(1).replace(/-./g,(t=>t[1].toUpperCase()));await this.watchExpr(e.value,{$elem:t},(e=>t[r]=e))}},t.resolveAttrAttributes=async function(t,e){if(this._skipNodes.has(t))return;const s=t;for(const e of Array.from(s.attributes||[]))if(e.name.startsWith(":")&&!_.has(e.name)){this.log(e.name,"attribute found in:\n",t),N(s,e.name);const r=e.name.slice(1);await this.watchExpr(e.value,{$elem:t},(t=>x(s,r,t)))}},t.resolveEventAttributes=async function(t,e){if(this._skipNodes.has(t))return;const s=t;for(const e of Array.from(s.attributes||[]))e.name.startsWith("@")&&!_.has(e.name)&&(this.log(e.name,"attribute found in:\n",t),N(s,e.name),t.addEventListener?.(e.name.substring(1),(s=>{this.eval(e.value,{$elem:t,$event:s})})))},t.resolveForAttribute=async function(t,e){if(this._skipNodes.has(t))return;const s=t,r=v(s,":for")?.trim();if(r){this.log(":for attribute found in:\n",t),N(s,":for");for(const e of T(t,this._skipNodes))this._skipNodes.add(e);const n=t.parentNode,i=function(t,e){return e?e.createElement(t):new g(t,{})}("template",t.ownerDocument);k(n,i,t),C(n,t),E(i,t),this.log(":for template:\n",i);const a=r.split(" in ",2);if(2!==a.length)throw new Error(`Invalid :for format: \`${r}\`. Expected "{key} in {expression}".`);const o=[],[c,l]=a;await this.watchExpr(l,{$elem:t},(s=>(this.log(":for list items:",s),this.lock=this.lock.then((()=>new Promise((async r=>{if(o.splice(0,o.length).forEach((t=>{C(n,t),this._skipNodes.delete(t)})),!Array.isArray(s))return console.error(`Expression did not yield a list: \`${l}\` => \`${s}\``),r();for(const r of s){const s=this.clone();await s.set(c,r);const n=t.cloneNode(!0);o.push(n),this._skipNodes.add(n),await s.mount(n,e),this.log("Rendered list child:\n",n,n.outerHTML)}const a=i.nextSibling;for(const t of o)k(n,t,a);r()})))).catch((t=>{throw console.error(t),new Error(t)})).then(),this.lock)))}},t.resolveBindAttribute=async function(t,e){if(this._skipNodes.has(t))return;const s=t,r=v(s,":bind");if(r){this.log(":bind attribute found in:\n",t);const e=["change","input"],n=v(s,":bind-events")?.split(",")||e;N(s,":bind"),N(s,":bind-events");const i="checkbox"===v(s,"type")?"checked":"value",a=`$elem.${i} = ${r}`;await this.watchExpr(a,{$elem:t},(t=>s[i]=t));const o=`${r} = $elem.${i}`;for(const e of n)t.addEventListener(e,(()=>this.eval(o,{$elem:t})))}},t.resolveShowAttribute=async function(t,e){if(this._skipNodes.has(t))return;const s=t,r=v(s,":show");if(r){this.log(":show attribute found in:\n",t),N(s,":show");const e="none"===s.style?.display?"":s.style?.display??v(s,"style")?.split(";")?.find((t=>"display"===t.split(":")[0]))?.split(":")?.at(1)?.trim();await this.watchExpr(r,{$elem:t},(t=>{s.style?s.style.display=t?e:"none":x(s,"style",`display: ${t?e:"none"};`)}))}}}(S||(S={}));class R extends r{debugging=!1;dirpath="";evalkeys=["$elem","$event"];expressionCache=new Map;evalCallbacks=new Map;_skipNodes=new Set;_customElements=new Map;debug(t){return this.debugging=t,this}async fetchRemote(t,e){return fetch(t,{cache:e?.cache??"default"}).then((t=>t.text()))}async fetchLocal(t,e){return this.fetchRemote(t,e)}async preprocessString(t,e){this.log("Preprocessing string content with params:\n",e);const s=this.parseHTML(t,e);return await this.preprocessNode(s,e),s}async preprocessLocal(t,e){const s=await this.fetchLocal(t,e);return this.preprocessString(s,{...e,dirpath:L(t),root:e?.root??!t.endsWith(".tpl.html")})}async preprocessRemote(t,e){const s={};e?.cache&&(s.cache=e.cache);const r=await fetch(t,s).then((t=>t.text()));return this.preprocessString(r,{...e,dirpath:L(t),root:e?.root??!t.endsWith(".tpl.html")})}clone(){const t=new this.constructor(Object.fromEntries(this.store.entries()));return t._customElements=this._customElements,t.debug(this.debugging)}log(...t){this.debugging&&console.debug(...t)}cachedExpressionFunction(t){return this.expressionCache.has(t)||this.expressionCache.set(t,function(t,e=[]){return new Function(...e,`with (this) { return (async () => (${t}))(); }`)}(t,this.evalkeys)),this.expressionCache.get(t)}async eval(t,e={}){if(this.store.has(t))return[this.get(t),[t]];{const s=this.cachedExpressionFunction(t),r=this.evalkeys.map((t=>e[t]));if(Object.keys(e).some((t=>!this.evalkeys.includes(t))))throw new Error(`Invalid argument key, must be one of: ${this.evalkeys.join(", ")}`);const[n,i]=await this.trace((async function(){return s.call(this,...r)}));return this.log(`eval \`${t}\` => `,n,`[ ${i.join(", ")} ]`),[n,i]}}watchExpr(t,e,s){if(this.evalCallbacks.has(t))return this.evalCallbacks.get(t)?.push(s),this.eval(t,e).then((([t,e])=>s(t,e)));this.evalCallbacks.set(t,[s]);const r=[],n=async()=>{const[s,i]=await this.eval(t,e),a=this.evalCallbacks.get(t)||[];await Promise.all(a.map((t=>t(s,i)))),r.length>0&&this.unwatch(r,n),r.splice(0,r.length,...i),this.watch(i,n)};return n()}async preprocessNode(t,e){e=Object.assign({dirpath:this.dirpath,maxdepth:10},e);const s=new i(T(t,this._skipNodes)).map((async t=>{this.log("Preprocessing node:\n",t),await S.resolveIncludes.call(this,t,e),await S.rebaseRelativePaths.call(this,t,e),await S.registerCustomElements.call(this,t,e),await S.resolveCustomElements.call(this,t,e)}));return await Promise.all(s.generator()),t}async renderNode(t,e){for(const s of T(t,this._skipNodes))this.log("Rendering node:\n",s),await S.resolveDataAttribute.call(this,s,e),await S.resolveForAttribute.call(this,s,e),await S.resolveTextAttributes.call(this,s,e),await S.resolveHtmlAttribute.call(this,s,e),await S.resolveShowAttribute.call(this,s,e),await S.resolveWatchAttribute.call(this,s,e),await S.resolveBindAttribute.call(this,s,e),await S.resolvePropAttributes.call(this,s,e),await S.resolveAttrAttributes.call(this,s,e),await S.resolveEventAttributes.call(this,s,e),await S.resolveTextNodeExpressions.call(this,s,e);return t}async mount(t,e){await this.preprocessNode(t,e),await this.renderNode(t,e)}}const j=new class extends R{dirpath=L(self.location.href);parseHTML(t,e={root:!1}){if(e.root)return(new DOMParser).parseFromString(t,"text/html");{const e=document.createRange();return e.selectNodeContents(document.body),e.createContextualFragment(t)}}serializeHTML(t){return(new XMLSerializer).serializeToString(t).replace(/\s?xmlns="[^"]+"/gm,"")}preprocessLocal(t,e){return this.preprocessRemote(t,e)}};self.Mancha=j;const I=self.document?.currentScript;if(self.document?.currentScript?.hasAttribute("init")){const t=I?.hasAttribute("debug"),e=I?.getAttribute("cache"),s=I?.getAttribute("target")?.split(",")||["body"];window.addEventListener("load",(()=>{s.map((async s=>{const r=self.document.querySelector(s);await j.debug(t).mount(r,{cache:e})}))}))}})();
package/dist/plugins.js CHANGED
@@ -1,5 +1,6 @@
1
- import { appendChild, attributeNameToCamelCase, createElement, getAttribute, getNodeValue, insertBefore, removeAttribute, removeChild, replaceChildren, replaceWith, setAttribute, setNodeValue, setTextContent, } from "./dome.js";
1
+ import { appendChild, attributeNameToCamelCase, cloneAttribute, createElement, firstElementChild, getAttribute, getNodeValue, insertBefore, removeAttribute, removeChild, replaceChildren, replaceWith, setAttribute, setNodeValue, setTextContent, } from "./dome.js";
2
2
  import { isRelativePath, traverse } from "./core.js";
3
+ import { Iterator } from "./iterator.js";
3
4
  const KW_ATTRIBUTES = new Set([
4
5
  ":bind",
5
6
  ":bind-events",
@@ -30,10 +31,10 @@ export var RendererPlugins;
30
31
  const child = fragment.firstChild;
31
32
  for (const attr of Array.from(elem.attributes)) {
32
33
  if (child && attr.name !== "src")
33
- setAttribute(child, attr.name, attr.value);
34
+ cloneAttribute(elem, child, attr.name);
34
35
  }
35
36
  // Replace the include tag with the contents of the included file.
36
- replaceWith(node, ...Array.from(fragment.childNodes));
37
+ replaceWith(node, ...fragment.childNodes);
37
38
  };
38
39
  // Compute the subparameters being passed down to the included file.
39
40
  const subparameters = { ...params, root: false, maxdepth: params?.maxdepth - 1 };
@@ -46,7 +47,7 @@ export var RendererPlugins;
46
47
  // Case 2: Relative remote path.
47
48
  }
48
49
  else if (params?.dirpath?.includes("://") || params?.dirpath?.startsWith("//")) {
49
- const relpath = params.dirpath && params.dirpath !== "." ? `${params.dirpath}/${src}` : src;
50
+ const relpath = src.startsWith("/") ? src : `${params.dirpath}/${src}`;
50
51
  this.log("Including remote file from relative path:", relpath);
51
52
  await this.preprocessRemote(relpath, subparameters).then(handler);
52
53
  // Case 3: Local absolute path.
@@ -119,6 +120,40 @@ export var RendererPlugins;
119
120
  setAttribute(elem, "href", `${params.dirpath}/${href}`);
120
121
  }
121
122
  };
123
+ RendererPlugins.registerCustomElements = async function (node, params) {
124
+ const elem = node;
125
+ if (elem.tagName?.toLowerCase() === "template" && getAttribute(elem, "is")) {
126
+ const tagName = getAttribute(elem, "is")?.toLowerCase();
127
+ if (!this._customElements.has(tagName)) {
128
+ this.log(`Registering custom element: ${tagName}\n`, elem);
129
+ this._customElements.set(tagName, elem.cloneNode(true));
130
+ // Remove the node from the DOM.
131
+ removeChild(elem.parentNode, elem);
132
+ }
133
+ }
134
+ };
135
+ RendererPlugins.resolveCustomElements = async function (node, params) {
136
+ const elem = node;
137
+ const tagName = elem.tagName?.toLowerCase();
138
+ if (this._customElements.has(tagName)) {
139
+ this.log(`Processing custom element: ${tagName}\n`, elem);
140
+ const template = this._customElements.get(tagName);
141
+ const clone = (template.content || template).cloneNode(true);
142
+ // Add whatever attributes the custom element tag had to the first child.
143
+ const child = firstElementChild(clone);
144
+ for (const attr of Array.from(elem.attributes)) {
145
+ if (child)
146
+ cloneAttribute(elem, child, attr.name);
147
+ }
148
+ // If there's a <slot> element, replace it with the contents of the custom element.
149
+ const iter = new Iterator(traverse(clone));
150
+ const slot = iter.find((x) => x.tagName?.toLowerCase() === "slot");
151
+ if (slot)
152
+ replaceWith(slot, ...elem.childNodes);
153
+ // Replace the custom element tag with the contents of the template.
154
+ replaceWith(node, ...clone.childNodes);
155
+ }
156
+ };
122
157
  RendererPlugins.resolveTextNodeExpressions = async function (node, params) {
123
158
  if (node.nodeType !== 3)
124
159
  return;
package/dist/reactive.js CHANGED
@@ -23,8 +23,8 @@ function isProxified(object) {
23
23
  /** Default debouncer time in millis. */
24
24
  export const REACTIVE_DEBOUNCE_MILLIS = 10;
25
25
  export function proxifyObject(object, callback, deep = true) {
26
- // If this object is already a proxy, return it as-is.
27
- if (object == null || isProxified(object))
26
+ // If this object is already a proxy or a Promise, return it as-is.
27
+ if (object == null || isProxified(object) || object instanceof Promise)
28
28
  return object;
29
29
  // First, proxify any existing properties if deep = true.
30
30
  if (deep) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mancha",
3
- "version": "0.6.2",
3
+ "version": "0.6.4",
4
4
  "description": "Javscript HTML rendering engine",
5
5
  "main": "dist/index.js",
6
6
  "type": "module",