rasti 4.0.0-alpha.6 → 4.0.0-alpha.7

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/README.md CHANGED
@@ -1,7 +1,7 @@
1
1
  <p align="center">
2
2
  <picture>
3
- <source media="(prefers-color-scheme: dark)" srcset="https://cdn.jsdelivr.net/gh/8tentaculos/rasti@v4.0.0-alpha.6/docs/logo-dark.svg">
4
- <img alt="Rasti.js" src="https://cdn.jsdelivr.net/gh/8tentaculos/rasti@v4.0.0-alpha.6/docs/logo.svg" height="120">
3
+ <source media="(prefers-color-scheme: dark)" srcset="https://cdn.jsdelivr.net/gh/8tentaculos/rasti@v4.0.0-alpha.7/docs/logo-dark.svg">
4
+ <img alt="Rasti.js" src="https://cdn.jsdelivr.net/gh/8tentaculos/rasti@v4.0.0-alpha.7/docs/logo.svg" height="120">
5
5
  </picture>
6
6
  </p>
7
7
 
package/dist/rasti.js CHANGED
@@ -1551,7 +1551,7 @@
1551
1551
  element.after(divider);
1552
1552
  divider.after(fragment.firstChild);
1553
1553
  handleComponents();
1554
- element.remove();
1554
+ if (element.nextSibling === divider) element.remove();
1555
1555
  divider.remove();
1556
1556
  }
1557
1557
  }
@@ -2030,17 +2030,23 @@
2030
2030
  const PH = Component.PLACEHOLDER('(\\d+)');
2031
2031
  const attributes = [];
2032
2032
  // Parse attributes string with support for placeholders in both names and values.
2033
- const regExp = new RegExp(`(${PH}|[\\w-]+)(?:=(["']?)(?:${PH}|((?:.?(?!["']?\\s+(?:\\S+)=|\\s*/?[>"']))+.))\\3)?`, 'g');
2033
+ const regExp = new RegExp(`(?:${PH}|([\\w-]+))(?:=(["']?)(?:${PH}|((?:.?(?!["']?\\s+(?:\\S+)=|\\s*/?[>"']))+.))?\\3)?`, 'g');
2034
2034
 
2035
2035
  let attributeMatch;
2036
2036
  while ((attributeMatch = regExp.exec(attributesStr)) !== null) {
2037
- const [, attribute, attributeIdx, quotes, valueIdx, value] = attributeMatch;
2037
+ const [, attributeIdx, attribute, quotes, valueIdx, value] = attributeMatch;
2038
2038
 
2039
- const attr = typeof attributeIdx !== 'undefined' ? expressions[parseInt(attributeIdx, 10)] : attribute;
2040
- const val = typeof valueIdx !== 'undefined' ? expressions[parseInt(valueIdx, 10)] : value;
2039
+ const hasQuotes = !!quotes;
2040
+
2041
+ let attr = typeof attributeIdx !== 'undefined' ? expressions[parseInt(attributeIdx, 10)] : attribute;
2042
+ let val = typeof valueIdx !== 'undefined' ? expressions[parseInt(valueIdx, 10)] : value;
2043
+
2044
+ if (hasQuotes && typeof val === 'undefined') {
2045
+ val = '';
2046
+ }
2041
2047
 
2042
2048
  if (typeof val !== 'undefined') {
2043
- attributes.push([attr, val, !!quotes]);
2049
+ attributes.push([attr, val, hasQuotes]);
2044
2050
  } else {
2045
2051
  attributes.push([attr]);
2046
2052
  }
@@ -2491,17 +2497,17 @@
2491
2497
  // Parse the rendered content and get the fragment.
2492
2498
  const fragment = parseHTML(rendered);
2493
2499
 
2494
- const handleComponents = () => {
2500
+ const handleComponents = (parent) => () => {
2495
2501
  // Add recycled components to children and move them to the new template in the fragment.
2496
- recycledChildren.forEach(recycled => recycle(recycled, this.el));
2502
+ recycledChildren.forEach(recycled => recycle(recycled, parent));
2497
2503
  // Add new children. Hydrate them and update the interpolation.
2498
- nextChildren.forEach(child => this.addChild(child).hydrate(this.el));
2504
+ nextChildren.forEach(child => this.addChild(child).hydrate(parent));
2499
2505
  };
2500
2506
 
2501
2507
  if (this.isContainer()) {
2502
- interpolation.updateElement(this.el, fragment, handleComponents);
2508
+ interpolation.updateElement(this.el, fragment, handleComponents(this.el.parentNode));
2503
2509
  } else {
2504
- interpolation.update(fragment, handleComponents);
2510
+ interpolation.update(fragment, handleComponents(this.el));
2505
2511
  }
2506
2512
  });
2507
2513
  // Destroy unused children.
@@ -2612,16 +2618,49 @@
2612
2618
  }
2613
2619
 
2614
2620
  /**
2615
- * Mount the component into the dom.
2616
- * It instantiate the Component view using options,
2617
- * appends its element into the DOM (if `el` is provided).
2618
- * The `onMount` lifecycle method will be called after the component is added to the DOM.
2619
- * And returns the view instance.
2621
+ * Mount the component into the DOM.
2622
+ * Creates a new component instance with the provided options and optionally mounts it into the DOM.
2623
+ *
2624
+ * **Mounting modes:**
2625
+ * - **Normal mount** (default): Renders the component as HTML and appends it to the provided element. Use this for client-side rendering.
2626
+ * - **Hydration mode**: Assumes the DOM already contains the component's HTML (from server-side rendering).
2627
+ *
2628
+ * If `el` is not provided, the component is instantiated but not mounted (the same as using `new Component(options)`). You can mount it later by calling `render()` and appending the element (`this.el`) to the DOM.
2629
+ *
2620
2630
  * @static
2621
- * @param {object} [options] The view options.
2622
- * @param {node} [el] Dom element to append the view element.
2623
- * @param {boolean} [hydrate] If true, the view will hydrate existing DOM.
2631
+ * @param {object} [options={}] The component options. These will be passed to the constructor and can include
2632
+ * `model`, `state`, `props`, lifecycle methods, and any other component-specific options.
2633
+ * @param {node} [el] The DOM element where the component will be mounted. If provided, the component will be
2634
+ * rendered and appended to this element. If not provided, the component is created but not mounted.
2635
+ * @param {boolean} [hydrate=false] If `true`, enables hydration mode for server-side rendering. The component will
2636
+ * assume the DOM already contains its HTML structure and will only hydrate it.
2637
+ * If `false` (default), the component will be rendered from scratch and appended to `el`.
2624
2638
  * @return {Component} The component instance.
2639
+ * @example
2640
+ * import { Component, Model } from 'rasti';
2641
+ *
2642
+ * const Button = Component.create`
2643
+ * <button class="${({ props }) => props.className}">
2644
+ * ${({ props }) => props.label}
2645
+ * </button>
2646
+ * `;
2647
+ *
2648
+ * // Normal mount: render and append to DOM.
2649
+ * const button = Button.mount({
2650
+ * label: 'Click me'
2651
+ * }, document.body);
2652
+ *
2653
+ * // Create without mounting (mount later).
2654
+ * const button2 = Button.mount({ className : 'secondary', label : 'Save' });
2655
+ * // Later, render and append it to the DOM.
2656
+ * document.body.appendChild(button2.render().el);
2657
+ *
2658
+ * // Hydration mode: hydrate existing server-rendered HTML
2659
+ * // Assuming document.body already contains the HTML structure of the button.
2660
+ * const hydratedButton = Button.mount({
2661
+ * className : 'primary',
2662
+ * label : 'Click me'
2663
+ * }, document.body, true);
2625
2664
  */
2626
2665
  static mount(options, el, hydrate) {
2627
2666
  // Instantiate component.
@@ -2629,14 +2668,14 @@
2629
2668
  // If `el` is passed, mount component.
2630
2669
  if (el) {
2631
2670
  if (hydrate) {
2632
- // Generate subcomponents.
2671
+ // Hydrate existing DOM, only generate subcomponents calling `toString`.
2633
2672
  component.toString();
2634
- // Hydrate existing DOM.
2635
- component.hydrate(el);
2636
2673
  } else {
2637
- // Append element to the DOM.
2638
- el.append(component.render().el);
2674
+ // Render the component and append it to the provided element.
2675
+ el.append(parseHTML(component));
2639
2676
  }
2677
+ // Hydrate in both cases.
2678
+ component.hydrate(el);
2640
2679
  }
2641
2680
  // Return component instance.
2642
2681
  return component;
package/dist/rasti.min.js CHANGED
@@ -1 +1 @@
1
- !function(t,e){"object"==typeof exports&&"undefined"!=typeof module?e(exports):"function"==typeof define&&define.amd?define(["exports"],e):e((t="undefined"!=typeof globalThis?globalThis:t||self).Rasti={})}(this,(function(t){"use strict";function e(t){if("function"!=typeof t)throw new TypeError("Listener must be a function")}class s{on(t,s){return e(s),this.listeners||(this.listeners={}),this.listeners[t]||(this.listeners[t]=[]),this.listeners[t].push(s),()=>this.off(t,s)}once(t,s){e(s);const i=(...e)=>{s(...e),this.off(t,i)};return this.on(t,i)}off(t,e){this.listeners&&(t?this.listeners[t]&&(e?(this.listeners[t]=this.listeners[t].filter((t=>t!==e)),this.listeners[t].length||delete this.listeners[t]):delete this.listeners[t],Object.keys(this.listeners).length||delete this.listeners):delete this.listeners)}emit(t,...e){this.listeners&&this.listeners[t]&&this.listeners[t].slice().forEach((t=>t(...e)))}listenTo(t,e,s){return t.on(e,s),this.listeningTo||(this.listeningTo=[]),this.listeningTo.push({emitter:t,type:e,listener:s}),()=>this.stopListening(t,e,s)}listenToOnce(t,s,i){e(i);const n=(...e)=>{i(...e),this.stopListening(t,s,n)};return this.listenTo(t,s,n)}stopListening(t,e,s){this.listeningTo&&(this.listeningTo=this.listeningTo.filter((i=>!(!t||t===i.emitter&&!e||t===i.emitter&&e===i.type&&!s||t===i.emitter&&e===i.type&&s===i.listener)||(i.emitter.off(i.type,i.listener),!1))),this.listeningTo.length||delete this.listeningTo)}}const i=(t,e,...s)=>"function"!=typeof t?t:t.apply(e,s);class n extends s{constructor(){super(),this.preinitialize.apply(this,arguments),this.attributes=Object.assign({},i(this.defaults,this),this.parse.apply(this,arguments)),this.previous={},Object.keys(this.attributes).forEach(this.defineAttribute.bind(this))}preinitialize(){}defineAttribute(t){Object.defineProperty(this,`${this.constructor.attributePrefix}${t}`,{get:()=>this.get(t),set:e=>{this.set(t,e)}})}get(t){return this.attributes[t]}set(t,e,...s){let i,n;"object"==typeof t?(i=t,n=[e,...s]):(i={[t]:e},n=s);const r=this._changing;this._changing=!0;const h={};r||(this.previous=Object.assign({},this.attributes)),Object.keys(i).forEach((t=>{i[t]!==this.attributes[t]&&(h[t]=i[t],this.attributes[t]=i[t])}));const o=Object.keys(h);if(o.length&&(this._pending=["change",this,h,...n]),o.forEach((t=>{this.emit(`change:${t}`,this,i[t],...n)})),r)return this;for(;this._pending;){const t=this._pending;this._pending=null,this.emit.apply(this,t)}return this._pending=null,this._changing=!1,this}parse(t){return t}toJSON(){return Object.assign({},this.attributes)}}n.attributePrefix="";const r=["el","tag","attributes","events","model","template","onDestroy"];class h extends s{constructor(t={}){super(),this.preinitialize.apply(this,arguments),this.delegatedEventListeners=[],this.children=[],this.destroyQueue=[],this.viewOptions=[],r.forEach((e=>{e in t&&(this[e]=t[e],this.viewOptions.push(e))})),this.ensureUid(),this.ensureElement()}preinitialize(){}$(t){return this.el.querySelector(t)}$$(t){return this.el.querySelectorAll(t)}destroy(){return this.destroyChildren(),this.undelegateEvents(),this.stopListening(),this.off(),this.destroyQueue.forEach((t=>t())),this.destroyQueue=[],this.onDestroy.apply(this,arguments),this.destroyed=!0,this}onDestroy(){}addChild(t){return this.children.push(t),t}destroyChildren(){this.children.forEach((t=>t.destroy())),this.children=[]}ensureUid(){this.uid||(this.uid="r"+ ++h.uid)}ensureElement(){if(this.el)this.el=i(this.el,this);else{const t=i(this.tag,this),e=i(this.attributes,this);this.el=this.createElement(t,e)}this.delegateEvents()}createElement(t="div",e={}){let s=document.createElement(t);return Object.keys(e).forEach((t=>s.setAttribute(t,e[t]))),s}removeElement(){return this.el.remove(),this}delegateEvents(t){if(t||(t=i(this.events,this)),!t)return this;this.delegatedEventListeners.length&&this.undelegateEvents();let s={};return Object.keys(t).forEach((i=>{const n=i.split(" "),r=n.shift(),h=n.join(" ");let o=t[i];"string"==typeof o&&(o=this[o]),e(o),s[r]||(s[r]=[]),s[r].push({selector:h,listener:o})})),Object.keys(s).forEach((t=>{const e=e=>{s[t].forEach((({selector:t,listener:s})=>{if(!t)return void s.call(this,e,this,this.el);let i=e.target;for(;i&&i!==this.el;)i.matches&&i.matches(t)&&s.call(this,e,this,i),i=i.parentElement}))};this.delegatedEventListeners.push({type:t,listener:e}),this.el.addEventListener(t,e)})),this}undelegateEvents(){return this.delegatedEventListeners.forEach((({type:t,listener:e})=>{this.el.removeEventListener(t,e)})),this.delegatedEventListeners=[],this}render(){return this.template&&(this.el.innerHTML=this.template(this.model)),this}static sanitize(t){return`${t}`.replace(/[&<>"']/g,(t=>({"&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;","'":"&#039;"}[t])))}}h.uid=0;class o{constructor(t){this.value=t}toString(){return this.value}}class a{constructor(t){this.items=t}}class l{constructor(){this.listeners=[],this.types=new Set,this.previousSize=0}addListener(t,e){return this.types.add(e),this.listeners.push(t),this.listeners.length-1}reset(){this.listeners=[],this.previousSize=this.types.size}hasPendingTypes(){return this.types.size>this.previousSize}}class c{constructor(){}reset(){this.paused=0,this.previous=this.tracked||new Map,this.tracked=new Map,this.positionStack=[0]}push(){this.positionStack.push(0)}pop(){this.positionStack.pop()}increment(){this.positionStack[this.positionStack.length-1]++}pause(){this.paused++}resume(){this.paused--}getPath(){return this.positionStack.join("-")}track(t){return 0===this.paused&&this.tracked.set(this.getPath(),t),t}findRecyclable(t){const e=this.previous.get(this.getPath());return e&&e.constructor===t&&!e.key?e:null}}const u=["value","checked","selected"];let d=class{constructor(t){this.getSelector=t.getSelector,this.getAttributes=t.getAttributes,this.previousAttributes={}}hydrate(t){this.ref=t.querySelector(this.getSelector())}update(){const t=this.getAttributes(),{remove:e,add:s}=function(t,e={}){const s={},i=[];return Object.keys(t).forEach((i=>{let n=t[i];n!==e[i]&&(!0===n?s[i]="":!1!==n&&(null==n&&(n=""),s[i]=n))})),Object.keys(e).forEach((s=>{(!(s in t)||e[s]!==t[s]&&!1===t[s])&&i.push(s)})),{add:s,remove:i}}(t,this.previousAttributes);this.previousAttributes=t,e.forEach((t=>{this.ref.removeAttribute(t),u.includes(t)&&t in this.ref&&(this.ref[t]="value"===t&&"")})),Object.keys(s).forEach((t=>{const e=s[t];this.ref.setAttribute(t,e),u.includes(t)&&t in this.ref&&(this.ref[t]="value"===t?e:!1!==e&&"false"!==e)}))}};const p=["value","checked","selected"];function f(t,e){t.nodeType===e.nodeType?t.nodeType!==Node.TEXT_NODE?t.tagName===e.tagName?(((t,e)=>{const s=e.attributes,i=t.attributes,n=new Set;for(let e=0,i=s.length;e<i;e++){const{name:i,value:r}=s[e];n.add(i),t.getAttribute(i)!==r&&t.setAttribute(i,r)}for(let e=i.length-1;e>=0;e--){const{name:s}=i[e];n.has(s)||t.removeAttribute(s)}for(let s=0,i=p.length;s<i;s++){const i=p[s];i in t&&t[i]!==e[i]&&(t[i]=e[i])}})(t,e),((t,e)=>{const s=t.childNodes,i=e.childNodes,n=s.length;if(n!==i.length)return!1;for(let t=0;t<n;t++)if(!s[t].isEqualNode(i[t]))return!1;return!0})(t,e)||((t,e)=>{const s=Array.from(e.childNodes);t.replaceChildren(...s)})(t,e)):t.replaceWith(e):t.nodeValue!==e.nodeValue&&(t.nodeValue=e.nodeValue):t.replaceWith(e)}function g(t,e,s=(()=>!1),i){let n=i||t.firstChild;for(;n;){if(n.nodeType===Node.COMMENT_NODE&&n.data.trim()===e)return n;if(n.nodeType!==Node.ELEMENT_NODE||s(n)||!n.firstChild){for(;n&&!n.nextSibling;)if(n=n.parentNode,!n||n===t)return null;n&&(n=n.nextSibling)}else n=n.firstChild}return null}class E{constructor(t){this.getStart=t.getStart,this.getEnd=t.getEnd,this.expression=t.expression,this.shouldSkipFind=t.shouldSkipFind,this.shouldSkipSync=t.shouldSkipSync,this.previousItems=[]}hydrate(t){const e=g(t,this.getStart(),this.shouldSkipFind),s=g(t,this.getEnd(),this.shouldSkipFind,e);this.ref=[e,s]}update(t,e){let s;const[i,n]=this.ref,r=i.nextSibling,h=r===n,o=!h&&r.nextSibling===n;if(h?n.before(t):!o||1!==t.children.length||this.shouldSkipSync(r)||this.shouldSkipSync(t.firstChild)?(s=document.createComment(""),n.before(s),n.before(t)):f(r,t.firstChild),e(),s){if(this.ref[0].nextSibling===s)s.remove();else{const t=document.createRange();t.setStartAfter(this.ref[0]),t.setEndAfter(s),t.deleteContents()}}}updateElement(t,e,s){const i=document.createComment("");t.after(i),i.after(e.firstChild),s(),t.remove(),i.remove()}}const m=t=>t.reduce(((t,e)=>(Array.isArray(e)?t.push(...m(e)):t.push(e),t)),[]);function y(t){const e=document.createElement("template");return e.innerHTML=`${t}`.trim(),e.content}function T(t){const e=[];return Object.keys(t).forEach((s=>{let i=t[s];!0===i?e.push(s):!1!==i&&(null==i&&(i=""),e.push(`${s}="${i}"`))})),e.join(" ")}const b=(t,e)=>i(t,e,e),v=t=>!!(t&&t.dataset&&t.dataset[N.DATASET_ELEMENT]&&t.dataset[N.DATASET_ELEMENT].endsWith("-1")),A=t=>!!(t&&t.dataset&&t.dataset[N.DATASET_ELEMENT])||!!t.querySelector(`[${N.ATTRIBUTE_ELEMENT}]`),S=(t,e)=>t.reduce(((t,s,i)=>(t.push(s),void 0!==e[i]&&t.push(N.PLACEHOLDER(i)),t)),[]).join(""),R=(t,e)=>{const s=N.PLACEHOLDER("(\\d+)"),i=t.match(new RegExp(`^${s}$`));if(i)return[e[parseInt(i[1],10)]];const n=new RegExp(`${s}`,"g"),r=[];let h,o=0;for(;null!==(h=n.exec(t));){const s=t.slice(o,h.index);r.push(N.markAsSafeHTML(s),e[parseInt(h[1],10)]),o=h.index+h[0].length}return r.push(N.markAsSafeHTML(t.slice(o))),r},L=(t,e)=>t.reduce(((t,s)=>{const i=e(s[0]);if(1===s.length)"object"==typeof i?t=Object.assign(t,i):"string"==typeof i&&(t[i]=!0);else{const n=s[2]?e(s[1]):s[1];t[i]=n}return t}),{}),$=(t,e,s)=>{const i={};return Object.keys(t).forEach((n=>{const r=n.match(/on(([A-Z]{1}[a-z]+)+)/);if(r&&r[1]){const h=r[1].toLowerCase(),o=t[n];if(o){const t=e.addListener(o,h);i[s(h)]=t}}else i[n]=t[n]})),i},C=(t,e,s=!1)=>{const i=N.PLACEHOLDER("(\\d+)"),n=new Map;return s||(t=t.replace(new RegExp(i,"g"),((t,s)=>{const i=e[s];if(i&&i.prototype instanceof N){if(n.has(i))return n.get(i);n.set(i,t)}return t}))),t.replace(new RegExp(`<(${i})([^>]*)>([\\s\\S]*?)</\\1>|<(${i})([^>]*)/>`,"g"),((t,s,i,n,r,h,o,l)=>{let c,u,d;if(s?(c=e[i],u=n):(c=void 0!==o?e[o]:h,u=l),!(c.prototype instanceof N))return t;if(s){const t=C(r,e,!0),s=O(t,e);d=R(s,e)}const p=_(u,e);return e.push((function(){const t=L(p,(t=>b(t,this)));return d&&(t.renderChildren=()=>new a(d.map((t=>b(t,this))))),c.mount(t)})),N.PLACEHOLDER(e.length-1)}))},M=(t,e)=>{const s=N.PLACEHOLDER("(?:\\d+)");return t.replace(new RegExp(`<(${s}|[a-z]+[1-6]?)(?:\\s*)((?:"[^"]*"|'[^']*'|[^>])*)(/?>)`,"gi"),e)},k=(t,e,s)=>{const n=N.PLACEHOLDER("(?:\\d+)");if(t.match(new RegExp(`^\\s*${n}\\s*$`)))return t;if(!t.match(new RegExp(`^\\s*<([a-z]+[1-6]?|${n})([^>]*)>([\\s\\S]*?)</(\\1|${n})>\\s*$|^\\s*<([a-z]+[1-6]?|${n})([^>]*)/>\\s*$`)))throw new SyntaxError(`Template must have a single root element or be a container component: "${t.trim()}"`);let r=0;return M(t,((t,h,o,a)=>{const l=0===r,c=++r;if(!l&&!o.match(new RegExp(n)))return t;const u=_(o,e),d=t=>`${t}-${c}`,p=s.length;s.push({getSelector:function(){return`[${N.ATTRIBUTE_ELEMENT}="${d(this.uid)}"]`},getAttributes:function(){const t=$(L(u,(t=>b(t,this))),this.eventsManager,(t=>N.ATTRIBUTE_EVENT(t,this.uid)));return l&&this.attributes&&Object.assign(t,i(this.attributes,this)),t[N.ATTRIBUTE_ELEMENT]=d(this.uid),t}}),e.push((function(){const t=this.template.elements[p],e=t.getAttributes.call(this);return t.previousAttributes=e,N.markAsSafeHTML(T(e))}));return`<${h} ${N.PLACEHOLDER(e.length-1)}${a}`}))},O=(t,e)=>{const s=N.PLACEHOLDER("(?:\\d+)");return M(t,((t,i,n,r)=>{if(!n.match(new RegExp(s)))return t;const h=_(n,e),o=function(){return $(L(h,(t=>b(t,this))),this.eventsManager,(t=>N.ATTRIBUTE_EVENT(t,this.uid)))};e.push((function(){const t=o.call(this);return N.markAsSafeHTML(T(t))}));return`<${i} ${N.PLACEHOLDER(e.length-1)}${r}`}))},x=(t,e,s)=>{const i=N.PLACEHOLDER("(\\d+)");let n=0;return t.replace(new RegExp(i,"g"),(function(i,r,h){const o=t.substring(0,h);if(o.lastIndexOf("<")>o.lastIndexOf(">"))return i;const a=++n;const l=s.length;return s.push({getStart:function(){return N.MARKER_START(`${this.uid}-${a}`)},getEnd:function(){return N.MARKER_END(`${this.uid}-${a}`)},expression:e[r]}),e.push((function(){return this.template.interpolations[l]})),N.PLACEHOLDER(e.length-1)}))},_=(t,e)=>{const s=N.PLACEHOLDER("(\\d+)"),i=[],n=new RegExp(`(${s}|[\\w-]+)(?:=(["']?)(?:${s}|((?:.?(?!["']?\\s+(?:\\S+)=|\\s*/?[>"']))+.))\\3)?`,"g");let r;for(;null!==(r=n.exec(t));){const[,t,s,n,h,o]=r,a=void 0!==s?e[parseInt(s,10)]:t,l=void 0!==h?e[parseInt(h,10)]:o;void 0!==l?i.push([a,l,!!n]):i.push([a])}return i},w=["key","state","onCreate","onChange","onHydrate","onRecycle","onUpdate"];class N extends h{constructor(t={}){super(...arguments),this.componentOptions=[],w.forEach((e=>{e in t&&(this[e]=t[e],this.componentOptions.push(e))}));const e={};Object.keys(t).forEach((s=>{this.viewOptions.includes(s)||this.componentOptions.includes(s)||(e[s]=t[s])})),this.props=new n(e),this.options=t,this.partial=this.partial.bind(this),this.onChange=this.onChange.bind(this),this.onCreate.apply(this,arguments)}events(){const t={};return this.eventsManager.types.forEach((s=>{const i=N.ATTRIBUTE_EVENT(s,this.uid),n=function(t,s,n){const r=n.getAttribute(i);if(r){let i=this.eventsManager.listeners[parseInt(r,10)];"string"==typeof i&&(i=this[i]),e(i),i.call(this,t,s,n)}};t[`${s} [${i}]`]=n,t[s]=n})),t}ensureElement(){this.eventsManager=new l,this.pathManager=new c,this.template=i(this.template,this),this.el&&(this.el=i(this.el,this),this.toString(),this.hydrate(this.el.parentNode))}isContainer(){return 0===this.template.elements.length&&1===this.template.interpolations.length}subscribe(t,e="change",s=this.onChange){return t.on&&this.listenTo(t,e,s),this}hydrate(t){return["model","state","props"].forEach((t=>{this[t]&&this.subscribe(this[t])})),this.isContainer()?(this.children[0].hydrate(t),this.el=this.children[0].el):(this.template.elements.forEach(((e,s)=>{0===s?(e.hydrate(t),this.el?e.ref=this.el:this.el=e.ref):e.hydrate(this.el)})),this.delegateEvents(),this.template.interpolations.forEach((t=>t.hydrate(this.el))),this.children.forEach((t=>t.hydrate(this.el)))),this.onHydrate.call(this),this}recycle(t,e){if(t){!function(t,e){if(Element.prototype.moveBefore)t.parentNode.moveBefore(e,t),t.remove();else{const s=document.activeElement;t.before(e),t.remove(),s&&s!==document.activeElement&&e.contains(s)&&s.focus()}}(g(t,N.MARKER_RECYCLED(this.uid),v),this.el)}return e&&this.props.set(e),this.onRecycle.call(this),this}getRecycledMarker(){return`\x3c!--${N.MARKER_RECYCLED(this.uid)}--\x3e`}partial(t,...e){const s=R(O(C(S(t,e).trim(),e),e),e).map((t=>b(t,this)));return new a(s)}renderTemplatePart(t,e,s=[]){const i=b(t,this),n=t=>{if(null==t||!1===t||!0===t)return"";if(t instanceof o)return s.push(t),t;if(t instanceof N)return s.push(t),e(t);if(t instanceof a){if(1===t.items.length)return n(t.items[0]);this.pathManager.push();const e=t.items.map((t=>(this.pathManager.increment(),n(t)))).join("");return this.pathManager.pop(),e}if(Array.isArray(t)){this.pathManager.pause();const e=m(t).map(n).join("");return this.pathManager.resume(),e}if(t instanceof E){this.pathManager.increment();const e=this.isContainer()?"":`\x3c!--${t.getStart()}--\x3e`,i=this.isContainer()?"":`\x3c!--${t.getEnd()}--\x3e`,r=n(b(t.expression,this));return t.previousItems=s,`${e}${r}${i}`}const i=N.sanitize(t);return s.push(i),i};return`${n(i)}`}toString(){this.destroyChildren(),this.eventsManager.reset(),this.pathManager.reset();const t=t=>(this.pathManager.track(t),this.addChild(t));return this.template.parts.map((e=>this.renderTemplatePart(e,t))).join("")}render(){if(this.destroyed)return this;if(!this.el){const t=y(this);return this.hydrate(t),this}this.eventsManager.reset(),this.pathManager.reset(),this.template.elements.forEach((t=>t.update()));const t=this.children;return this.children=[],this.template.interpolations.forEach((e=>{const s=[],i=[];this.pathManager.increment();const n=e.previousItems,r=[],h=this.renderTemplatePart(e.expression,(e=>{let n=e,r=null;return r=e.key?t.find((t=>t.key===e.key)):this.pathManager.findRecyclable(e.constructor),r?(n=r.getRecycledMarker(),i.push([r,e]),r.key||this.pathManager.track(r)):(s.push(e),this.pathManager.track(e)),n}),r);e.previousItems=r;const o=([t,e],s)=>{this.addChild(t).recycle(s,e.props.toJSON()),e.destroy()};if(1===n.length&&n[0]instanceof N&&1===r.length&&r[0]instanceof N&&1===i.length)return void o(i[0],null);const a=y(h),l=()=>{i.forEach((t=>o(t,this.el))),s.forEach((t=>this.addChild(t).hydrate(this.el)))};this.isContainer()?e.updateElement(this.el,a,l):e.update(a,l)})),t.forEach((t=>{this.children.indexOf(t)<0&&t.destroy()})),this.isContainer()?this.el=this.children[0].el:this.eventsManager.hasPendingTypes()&&this.delegateEvents(),this.onUpdate.call(this),this}onCreate(){}onChange(){this.render()}onHydrate(){}onRecycle(){}onUpdate(){}onDestroy(){}static markAsSafeHTML(t){return new o(t)}static extend(t){const e=this;class s extends e{}return Object.assign(s.prototype,"function"==typeof t?t(e.prototype):t),s}static mount(t,e,s){const i=new this(t);return e&&(s?(i.toString(),i.hydrate(e)):e.append(i.render().el)),i}static create(t,...e){"function"==typeof t&&(e=[t],t=["",""]);const s=[],i=[],n=R(x(k(C(S(t,e).trim(),e),e,s),e,i),e);return this.extend({template(){return{elements:s.map((t=>new d({getSelector:t.getSelector.bind(this),getAttributes:t.getAttributes.bind(this)}))),interpolations:i.map((t=>new E({getStart:t.getStart.bind(this),getEnd:t.getEnd.bind(this),expression:t.expression,shouldSkipFind:v,shouldSkipSync:A}))),parts:n}}})}}N.ATTRIBUTE_ELEMENT="data-rasti-el",N.ATTRIBUTE_EVENT=(t,e)=>`data-rasti-on-${t}-${e}`,N.DATASET_ELEMENT="rastiEl",N.PLACEHOLDER=t=>`__RASTI_PH_${t}__`,N.MARKER_RECYCLED=t=>`rasti-r-${t}`,N.MARKER_START=t=>`rasti-s-${t}`,N.MARKER_END=t=>`rasti-e-${t}`;var j=N.create`<div></div>`;t.Component=j,t.Emitter=s,t.Model=n,t.View=h}));
1
+ !function(t,e){"object"==typeof exports&&"undefined"!=typeof module?e(exports):"function"==typeof define&&define.amd?define(["exports"],e):e((t="undefined"!=typeof globalThis?globalThis:t||self).Rasti={})}(this,(function(t){"use strict";function e(t){if("function"!=typeof t)throw new TypeError("Listener must be a function")}class s{on(t,s){return e(s),this.listeners||(this.listeners={}),this.listeners[t]||(this.listeners[t]=[]),this.listeners[t].push(s),()=>this.off(t,s)}once(t,s){e(s);const i=(...e)=>{s(...e),this.off(t,i)};return this.on(t,i)}off(t,e){this.listeners&&(t?this.listeners[t]&&(e?(this.listeners[t]=this.listeners[t].filter((t=>t!==e)),this.listeners[t].length||delete this.listeners[t]):delete this.listeners[t],Object.keys(this.listeners).length||delete this.listeners):delete this.listeners)}emit(t,...e){this.listeners&&this.listeners[t]&&this.listeners[t].slice().forEach((t=>t(...e)))}listenTo(t,e,s){return t.on(e,s),this.listeningTo||(this.listeningTo=[]),this.listeningTo.push({emitter:t,type:e,listener:s}),()=>this.stopListening(t,e,s)}listenToOnce(t,s,i){e(i);const n=(...e)=>{i(...e),this.stopListening(t,s,n)};return this.listenTo(t,s,n)}stopListening(t,e,s){this.listeningTo&&(this.listeningTo=this.listeningTo.filter((i=>!(!t||t===i.emitter&&!e||t===i.emitter&&e===i.type&&!s||t===i.emitter&&e===i.type&&s===i.listener)||(i.emitter.off(i.type,i.listener),!1))),this.listeningTo.length||delete this.listeningTo)}}const i=(t,e,...s)=>"function"!=typeof t?t:t.apply(e,s);class n extends s{constructor(){super(),this.preinitialize.apply(this,arguments),this.attributes=Object.assign({},i(this.defaults,this),this.parse.apply(this,arguments)),this.previous={},Object.keys(this.attributes).forEach(this.defineAttribute.bind(this))}preinitialize(){}defineAttribute(t){Object.defineProperty(this,`${this.constructor.attributePrefix}${t}`,{get:()=>this.get(t),set:e=>{this.set(t,e)}})}get(t){return this.attributes[t]}set(t,e,...s){let i,n;"object"==typeof t?(i=t,n=[e,...s]):(i={[t]:e},n=s);const r=this._changing;this._changing=!0;const h={};r||(this.previous=Object.assign({},this.attributes)),Object.keys(i).forEach((t=>{i[t]!==this.attributes[t]&&(h[t]=i[t],this.attributes[t]=i[t])}));const o=Object.keys(h);if(o.length&&(this._pending=["change",this,h,...n]),o.forEach((t=>{this.emit(`change:${t}`,this,i[t],...n)})),r)return this;for(;this._pending;){const t=this._pending;this._pending=null,this.emit.apply(this,t)}return this._pending=null,this._changing=!1,this}parse(t){return t}toJSON(){return Object.assign({},this.attributes)}}n.attributePrefix="";const r=["el","tag","attributes","events","model","template","onDestroy"];class h extends s{constructor(t={}){super(),this.preinitialize.apply(this,arguments),this.delegatedEventListeners=[],this.children=[],this.destroyQueue=[],this.viewOptions=[],r.forEach((e=>{e in t&&(this[e]=t[e],this.viewOptions.push(e))})),this.ensureUid(),this.ensureElement()}preinitialize(){}$(t){return this.el.querySelector(t)}$$(t){return this.el.querySelectorAll(t)}destroy(){return this.destroyChildren(),this.undelegateEvents(),this.stopListening(),this.off(),this.destroyQueue.forEach((t=>t())),this.destroyQueue=[],this.onDestroy.apply(this,arguments),this.destroyed=!0,this}onDestroy(){}addChild(t){return this.children.push(t),t}destroyChildren(){this.children.forEach((t=>t.destroy())),this.children=[]}ensureUid(){this.uid||(this.uid="r"+ ++h.uid)}ensureElement(){if(this.el)this.el=i(this.el,this);else{const t=i(this.tag,this),e=i(this.attributes,this);this.el=this.createElement(t,e)}this.delegateEvents()}createElement(t="div",e={}){let s=document.createElement(t);return Object.keys(e).forEach((t=>s.setAttribute(t,e[t]))),s}removeElement(){return this.el.remove(),this}delegateEvents(t){if(t||(t=i(this.events,this)),!t)return this;this.delegatedEventListeners.length&&this.undelegateEvents();let s={};return Object.keys(t).forEach((i=>{const n=i.split(" "),r=n.shift(),h=n.join(" ");let o=t[i];"string"==typeof o&&(o=this[o]),e(o),s[r]||(s[r]=[]),s[r].push({selector:h,listener:o})})),Object.keys(s).forEach((t=>{const e=e=>{s[t].forEach((({selector:t,listener:s})=>{if(!t)return void s.call(this,e,this,this.el);let i=e.target;for(;i&&i!==this.el;)i.matches&&i.matches(t)&&s.call(this,e,this,i),i=i.parentElement}))};this.delegatedEventListeners.push({type:t,listener:e}),this.el.addEventListener(t,e)})),this}undelegateEvents(){return this.delegatedEventListeners.forEach((({type:t,listener:e})=>{this.el.removeEventListener(t,e)})),this.delegatedEventListeners=[],this}render(){return this.template&&(this.el.innerHTML=this.template(this.model)),this}static sanitize(t){return`${t}`.replace(/[&<>"']/g,(t=>({"&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;","'":"&#039;"}[t])))}}h.uid=0;class o{constructor(t){this.value=t}toString(){return this.value}}class a{constructor(t){this.items=t}}class l{constructor(){this.listeners=[],this.types=new Set,this.previousSize=0}addListener(t,e){return this.types.add(e),this.listeners.push(t),this.listeners.length-1}reset(){this.listeners=[],this.previousSize=this.types.size}hasPendingTypes(){return this.types.size>this.previousSize}}class c{constructor(){}reset(){this.paused=0,this.previous=this.tracked||new Map,this.tracked=new Map,this.positionStack=[0]}push(){this.positionStack.push(0)}pop(){this.positionStack.pop()}increment(){this.positionStack[this.positionStack.length-1]++}pause(){this.paused++}resume(){this.paused--}getPath(){return this.positionStack.join("-")}track(t){return 0===this.paused&&this.tracked.set(this.getPath(),t),t}findRecyclable(t){const e=this.previous.get(this.getPath());return e&&e.constructor===t&&!e.key?e:null}}const u=["value","checked","selected"];let d=class{constructor(t){this.getSelector=t.getSelector,this.getAttributes=t.getAttributes,this.previousAttributes={}}hydrate(t){this.ref=t.querySelector(this.getSelector())}update(){const t=this.getAttributes(),{remove:e,add:s}=function(t,e={}){const s={},i=[];return Object.keys(t).forEach((i=>{let n=t[i];n!==e[i]&&(!0===n?s[i]="":!1!==n&&(null==n&&(n=""),s[i]=n))})),Object.keys(e).forEach((s=>{(!(s in t)||e[s]!==t[s]&&!1===t[s])&&i.push(s)})),{add:s,remove:i}}(t,this.previousAttributes);this.previousAttributes=t,e.forEach((t=>{this.ref.removeAttribute(t),u.includes(t)&&t in this.ref&&(this.ref[t]="value"===t&&"")})),Object.keys(s).forEach((t=>{const e=s[t];this.ref.setAttribute(t,e),u.includes(t)&&t in this.ref&&(this.ref[t]="value"===t?e:!1!==e&&"false"!==e)}))}};const p=["value","checked","selected"];function f(t,e){t.nodeType===e.nodeType?t.nodeType!==Node.TEXT_NODE?t.tagName===e.tagName?(((t,e)=>{const s=e.attributes,i=t.attributes,n=new Set;for(let e=0,i=s.length;e<i;e++){const{name:i,value:r}=s[e];n.add(i),t.getAttribute(i)!==r&&t.setAttribute(i,r)}for(let e=i.length-1;e>=0;e--){const{name:s}=i[e];n.has(s)||t.removeAttribute(s)}for(let s=0,i=p.length;s<i;s++){const i=p[s];i in t&&t[i]!==e[i]&&(t[i]=e[i])}})(t,e),((t,e)=>{const s=t.childNodes,i=e.childNodes,n=s.length;if(n!==i.length)return!1;for(let t=0;t<n;t++)if(!s[t].isEqualNode(i[t]))return!1;return!0})(t,e)||((t,e)=>{const s=Array.from(e.childNodes);t.replaceChildren(...s)})(t,e)):t.replaceWith(e):t.nodeValue!==e.nodeValue&&(t.nodeValue=e.nodeValue):t.replaceWith(e)}function g(t,e,s=(()=>!1),i){let n=i||t.firstChild;for(;n;){if(n.nodeType===Node.COMMENT_NODE&&n.data.trim()===e)return n;if(n.nodeType!==Node.ELEMENT_NODE||s(n)||!n.firstChild){for(;n&&!n.nextSibling;)if(n=n.parentNode,!n||n===t)return null;n&&(n=n.nextSibling)}else n=n.firstChild}return null}class E{constructor(t){this.getStart=t.getStart,this.getEnd=t.getEnd,this.expression=t.expression,this.shouldSkipFind=t.shouldSkipFind,this.shouldSkipSync=t.shouldSkipSync,this.previousItems=[]}hydrate(t){const e=g(t,this.getStart(),this.shouldSkipFind),s=g(t,this.getEnd(),this.shouldSkipFind,e);this.ref=[e,s]}update(t,e){let s;const[i,n]=this.ref,r=i.nextSibling,h=r===n,o=!h&&r.nextSibling===n;if(h?n.before(t):!o||1!==t.children.length||this.shouldSkipSync(r)||this.shouldSkipSync(t.firstChild)?(s=document.createComment(""),n.before(s),n.before(t)):f(r,t.firstChild),e(),s){if(this.ref[0].nextSibling===s)s.remove();else{const t=document.createRange();t.setStartAfter(this.ref[0]),t.setEndAfter(s),t.deleteContents()}}}updateElement(t,e,s){const i=document.createComment("");t.after(i),i.after(e.firstChild),s(),t.nextSibling===i&&t.remove(),i.remove()}}const m=t=>t.reduce(((t,e)=>(Array.isArray(e)?t.push(...m(e)):t.push(e),t)),[]);function y(t){const e=document.createElement("template");return e.innerHTML=`${t}`.trim(),e.content}function b(t){const e=[];return Object.keys(t).forEach((s=>{let i=t[s];!0===i?e.push(s):!1!==i&&(null==i&&(i=""),e.push(`${s}="${i}"`))})),e.join(" ")}const T=(t,e)=>i(t,e,e),v=t=>!!(t&&t.dataset&&t.dataset[N.DATASET_ELEMENT]&&t.dataset[N.DATASET_ELEMENT].endsWith("-1")),A=t=>!!(t&&t.dataset&&t.dataset[N.DATASET_ELEMENT])||!!t.querySelector(`[${N.ATTRIBUTE_ELEMENT}]`),S=(t,e)=>t.reduce(((t,s,i)=>(t.push(s),void 0!==e[i]&&t.push(N.PLACEHOLDER(i)),t)),[]).join(""),R=(t,e)=>{const s=N.PLACEHOLDER("(\\d+)"),i=t.match(new RegExp(`^${s}$`));if(i)return[e[parseInt(i[1],10)]];const n=new RegExp(`${s}`,"g"),r=[];let h,o=0;for(;null!==(h=n.exec(t));){const s=t.slice(o,h.index);r.push(N.markAsSafeHTML(s),e[parseInt(h[1],10)]),o=h.index+h[0].length}return r.push(N.markAsSafeHTML(t.slice(o))),r},L=(t,e)=>t.reduce(((t,s)=>{const i=e(s[0]);if(1===s.length)"object"==typeof i?t=Object.assign(t,i):"string"==typeof i&&(t[i]=!0);else{const n=s[2]?e(s[1]):s[1];t[i]=n}return t}),{}),$=(t,e,s)=>{const i={};return Object.keys(t).forEach((n=>{const r=n.match(/on(([A-Z]{1}[a-z]+)+)/);if(r&&r[1]){const h=r[1].toLowerCase(),o=t[n];if(o){const t=e.addListener(o,h);i[s(h)]=t}}else i[n]=t[n]})),i},C=(t,e,s=!1)=>{const i=N.PLACEHOLDER("(\\d+)"),n=new Map;return s||(t=t.replace(new RegExp(i,"g"),((t,s)=>{const i=e[s];if(i&&i.prototype instanceof N){if(n.has(i))return n.get(i);n.set(i,t)}return t}))),t.replace(new RegExp(`<(${i})([^>]*)>([\\s\\S]*?)</\\1>|<(${i})([^>]*)/>`,"g"),((t,s,i,n,r,h,o,l)=>{let c,u,d;if(s?(c=e[i],u=n):(c=void 0!==o?e[o]:h,u=l),!(c.prototype instanceof N))return t;if(s){const t=C(r,e,!0),s=x(t,e);d=R(s,e)}const p=_(u,e);return e.push((function(){const t=L(p,(t=>T(t,this)));return d&&(t.renderChildren=()=>new a(d.map((t=>T(t,this))))),c.mount(t)})),N.PLACEHOLDER(e.length-1)}))},M=(t,e)=>{const s=N.PLACEHOLDER("(?:\\d+)");return t.replace(new RegExp(`<(${s}|[a-z]+[1-6]?)(?:\\s*)((?:"[^"]*"|'[^']*'|[^>])*)(/?>)`,"gi"),e)},k=(t,e,s)=>{const n=N.PLACEHOLDER("(?:\\d+)");if(t.match(new RegExp(`^\\s*${n}\\s*$`)))return t;if(!t.match(new RegExp(`^\\s*<([a-z]+[1-6]?|${n})([^>]*)>([\\s\\S]*?)</(\\1|${n})>\\s*$|^\\s*<([a-z]+[1-6]?|${n})([^>]*)/>\\s*$`)))throw new SyntaxError(`Template must have a single root element or be a container component: "${t.trim()}"`);let r=0;return M(t,((t,h,o,a)=>{const l=0===r,c=++r;if(!l&&!o.match(new RegExp(n)))return t;const u=_(o,e),d=t=>`${t}-${c}`,p=s.length;s.push({getSelector:function(){return`[${N.ATTRIBUTE_ELEMENT}="${d(this.uid)}"]`},getAttributes:function(){const t=$(L(u,(t=>T(t,this))),this.eventsManager,(t=>N.ATTRIBUTE_EVENT(t,this.uid)));return l&&this.attributes&&Object.assign(t,i(this.attributes,this)),t[N.ATTRIBUTE_ELEMENT]=d(this.uid),t}}),e.push((function(){const t=this.template.elements[p],e=t.getAttributes.call(this);return t.previousAttributes=e,N.markAsSafeHTML(b(e))}));return`<${h} ${N.PLACEHOLDER(e.length-1)}${a}`}))},x=(t,e)=>{const s=N.PLACEHOLDER("(?:\\d+)");return M(t,((t,i,n,r)=>{if(!n.match(new RegExp(s)))return t;const h=_(n,e),o=function(){return $(L(h,(t=>T(t,this))),this.eventsManager,(t=>N.ATTRIBUTE_EVENT(t,this.uid)))};e.push((function(){const t=o.call(this);return N.markAsSafeHTML(b(t))}));return`<${i} ${N.PLACEHOLDER(e.length-1)}${r}`}))},O=(t,e,s)=>{const i=N.PLACEHOLDER("(\\d+)");let n=0;return t.replace(new RegExp(i,"g"),(function(i,r,h){const o=t.substring(0,h);if(o.lastIndexOf("<")>o.lastIndexOf(">"))return i;const a=++n;const l=s.length;return s.push({getStart:function(){return N.MARKER_START(`${this.uid}-${a}`)},getEnd:function(){return N.MARKER_END(`${this.uid}-${a}`)},expression:e[r]}),e.push((function(){return this.template.interpolations[l]})),N.PLACEHOLDER(e.length-1)}))},_=(t,e)=>{const s=N.PLACEHOLDER("(\\d+)"),i=[],n=new RegExp(`(?:${s}|([\\w-]+))(?:=(["']?)(?:${s}|((?:.?(?!["']?\\s+(?:\\S+)=|\\s*/?[>"']))+.))?\\3)?`,"g");let r;for(;null!==(r=n.exec(t));){const[,t,s,n,h,o]=r,a=!!n;let l=void 0!==t?e[parseInt(t,10)]:s,c=void 0!==h?e[parseInt(h,10)]:o;a&&void 0===c&&(c=""),void 0!==c?i.push([l,c,a]):i.push([l])}return i},w=["key","state","onCreate","onChange","onHydrate","onRecycle","onUpdate"];class N extends h{constructor(t={}){super(...arguments),this.componentOptions=[],w.forEach((e=>{e in t&&(this[e]=t[e],this.componentOptions.push(e))}));const e={};Object.keys(t).forEach((s=>{this.viewOptions.includes(s)||this.componentOptions.includes(s)||(e[s]=t[s])})),this.props=new n(e),this.options=t,this.partial=this.partial.bind(this),this.onChange=this.onChange.bind(this),this.onCreate.apply(this,arguments)}events(){const t={};return this.eventsManager.types.forEach((s=>{const i=N.ATTRIBUTE_EVENT(s,this.uid),n=function(t,s,n){const r=n.getAttribute(i);if(r){let i=this.eventsManager.listeners[parseInt(r,10)];"string"==typeof i&&(i=this[i]),e(i),i.call(this,t,s,n)}};t[`${s} [${i}]`]=n,t[s]=n})),t}ensureElement(){this.eventsManager=new l,this.pathManager=new c,this.template=i(this.template,this),this.el&&(this.el=i(this.el,this),this.toString(),this.hydrate(this.el.parentNode))}isContainer(){return 0===this.template.elements.length&&1===this.template.interpolations.length}subscribe(t,e="change",s=this.onChange){return t.on&&this.listenTo(t,e,s),this}hydrate(t){return["model","state","props"].forEach((t=>{this[t]&&this.subscribe(this[t])})),this.isContainer()?(this.children[0].hydrate(t),this.el=this.children[0].el):(this.template.elements.forEach(((e,s)=>{0===s?(e.hydrate(t),this.el?e.ref=this.el:this.el=e.ref):e.hydrate(this.el)})),this.delegateEvents(),this.template.interpolations.forEach((t=>t.hydrate(this.el))),this.children.forEach((t=>t.hydrate(this.el)))),this.onHydrate.call(this),this}recycle(t,e){if(t){!function(t,e){if(Element.prototype.moveBefore)t.parentNode.moveBefore(e,t),t.remove();else{const s=document.activeElement;t.before(e),t.remove(),s&&s!==document.activeElement&&e.contains(s)&&s.focus()}}(g(t,N.MARKER_RECYCLED(this.uid),v),this.el)}return e&&this.props.set(e),this.onRecycle.call(this),this}getRecycledMarker(){return`\x3c!--${N.MARKER_RECYCLED(this.uid)}--\x3e`}partial(t,...e){const s=R(x(C(S(t,e).trim(),e),e),e).map((t=>T(t,this)));return new a(s)}renderTemplatePart(t,e,s=[]){const i=T(t,this),n=t=>{if(null==t||!1===t||!0===t)return"";if(t instanceof o)return s.push(t),t;if(t instanceof N)return s.push(t),e(t);if(t instanceof a){if(1===t.items.length)return n(t.items[0]);this.pathManager.push();const e=t.items.map((t=>(this.pathManager.increment(),n(t)))).join("");return this.pathManager.pop(),e}if(Array.isArray(t)){this.pathManager.pause();const e=m(t).map(n).join("");return this.pathManager.resume(),e}if(t instanceof E){this.pathManager.increment();const e=this.isContainer()?"":`\x3c!--${t.getStart()}--\x3e`,i=this.isContainer()?"":`\x3c!--${t.getEnd()}--\x3e`,r=n(T(t.expression,this));return t.previousItems=s,`${e}${r}${i}`}const i=N.sanitize(t);return s.push(i),i};return`${n(i)}`}toString(){this.destroyChildren(),this.eventsManager.reset(),this.pathManager.reset();const t=t=>(this.pathManager.track(t),this.addChild(t));return this.template.parts.map((e=>this.renderTemplatePart(e,t))).join("")}render(){if(this.destroyed)return this;if(!this.el){const t=y(this);return this.hydrate(t),this}this.eventsManager.reset(),this.pathManager.reset(),this.template.elements.forEach((t=>t.update()));const t=this.children;return this.children=[],this.template.interpolations.forEach((e=>{const s=[],i=[];this.pathManager.increment();const n=e.previousItems,r=[],h=this.renderTemplatePart(e.expression,(e=>{let n=e,r=null;return r=e.key?t.find((t=>t.key===e.key)):this.pathManager.findRecyclable(e.constructor),r?(n=r.getRecycledMarker(),i.push([r,e]),r.key||this.pathManager.track(r)):(s.push(e),this.pathManager.track(e)),n}),r);e.previousItems=r;const o=([t,e],s)=>{this.addChild(t).recycle(s,e.props.toJSON()),e.destroy()};if(1===n.length&&n[0]instanceof N&&1===r.length&&r[0]instanceof N&&1===i.length)return void o(i[0],null);const a=y(h),l=t=>()=>{i.forEach((e=>o(e,t))),s.forEach((e=>this.addChild(e).hydrate(t)))};this.isContainer()?e.updateElement(this.el,a,l(this.el.parentNode)):e.update(a,l(this.el))})),t.forEach((t=>{this.children.indexOf(t)<0&&t.destroy()})),this.isContainer()?this.el=this.children[0].el:this.eventsManager.hasPendingTypes()&&this.delegateEvents(),this.onUpdate.call(this),this}onCreate(){}onChange(){this.render()}onHydrate(){}onRecycle(){}onUpdate(){}onDestroy(){}static markAsSafeHTML(t){return new o(t)}static extend(t){const e=this;class s extends e{}return Object.assign(s.prototype,"function"==typeof t?t(e.prototype):t),s}static mount(t,e,s){const i=new this(t);return e&&(s?i.toString():e.append(y(i)),i.hydrate(e)),i}static create(t,...e){"function"==typeof t&&(e=[t],t=["",""]);const s=[],i=[],n=R(O(k(C(S(t,e).trim(),e),e,s),e,i),e);return this.extend({template(){return{elements:s.map((t=>new d({getSelector:t.getSelector.bind(this),getAttributes:t.getAttributes.bind(this)}))),interpolations:i.map((t=>new E({getStart:t.getStart.bind(this),getEnd:t.getEnd.bind(this),expression:t.expression,shouldSkipFind:v,shouldSkipSync:A}))),parts:n}}})}}N.ATTRIBUTE_ELEMENT="data-rasti-el",N.ATTRIBUTE_EVENT=(t,e)=>`data-rasti-on-${t}-${e}`,N.DATASET_ELEMENT="rastiEl",N.PLACEHOLDER=t=>`__RASTI_PH_${t}__`,N.MARKER_RECYCLED=t=>`rasti-r-${t}`,N.MARKER_START=t=>`rasti-s-${t}`,N.MARKER_END=t=>`rasti-e-${t}`;var j=N.create`<div></div>`;t.Component=j,t.Emitter=s,t.Model=n,t.View=h}));
package/es/Component.js CHANGED
@@ -418,17 +418,23 @@ const parseAttributes = (attributesStr, expressions) => {
418
418
  const PH = Component.PLACEHOLDER('(\\d+)');
419
419
  const attributes = [];
420
420
  // Parse attributes string with support for placeholders in both names and values.
421
- const regExp = new RegExp(`(${PH}|[\\w-]+)(?:=(["']?)(?:${PH}|((?:.?(?!["']?\\s+(?:\\S+)=|\\s*/?[>"']))+.))\\3)?`, 'g');
421
+ const regExp = new RegExp(`(?:${PH}|([\\w-]+))(?:=(["']?)(?:${PH}|((?:.?(?!["']?\\s+(?:\\S+)=|\\s*/?[>"']))+.))?\\3)?`, 'g');
422
422
 
423
423
  let attributeMatch;
424
424
  while ((attributeMatch = regExp.exec(attributesStr)) !== null) {
425
- const [, attribute, attributeIdx, quotes, valueIdx, value] = attributeMatch;
425
+ const [, attributeIdx, attribute, quotes, valueIdx, value] = attributeMatch;
426
426
 
427
- const attr = typeof attributeIdx !== 'undefined' ? expressions[parseInt(attributeIdx, 10)] : attribute;
428
- const val = typeof valueIdx !== 'undefined' ? expressions[parseInt(valueIdx, 10)] : value;
427
+ const hasQuotes = !!quotes;
428
+
429
+ let attr = typeof attributeIdx !== 'undefined' ? expressions[parseInt(attributeIdx, 10)] : attribute;
430
+ let val = typeof valueIdx !== 'undefined' ? expressions[parseInt(valueIdx, 10)] : value;
431
+
432
+ if (hasQuotes && typeof val === 'undefined') {
433
+ val = '';
434
+ }
429
435
 
430
436
  if (typeof val !== 'undefined') {
431
- attributes.push([attr, val, !!quotes]);
437
+ attributes.push([attr, val, hasQuotes]);
432
438
  } else {
433
439
  attributes.push([attr]);
434
440
  }
@@ -879,17 +885,17 @@ class Component extends View {
879
885
  // Parse the rendered content and get the fragment.
880
886
  const fragment = parseHTML(rendered);
881
887
 
882
- const handleComponents = () => {
888
+ const handleComponents = (parent) => () => {
883
889
  // Add recycled components to children and move them to the new template in the fragment.
884
- recycledChildren.forEach(recycled => recycle(recycled, this.el));
890
+ recycledChildren.forEach(recycled => recycle(recycled, parent));
885
891
  // Add new children. Hydrate them and update the interpolation.
886
- nextChildren.forEach(child => this.addChild(child).hydrate(this.el));
892
+ nextChildren.forEach(child => this.addChild(child).hydrate(parent));
887
893
  };
888
894
 
889
895
  if (this.isContainer()) {
890
- interpolation.updateElement(this.el, fragment, handleComponents);
896
+ interpolation.updateElement(this.el, fragment, handleComponents(this.el.parentNode));
891
897
  } else {
892
- interpolation.update(fragment, handleComponents);
898
+ interpolation.update(fragment, handleComponents(this.el));
893
899
  }
894
900
  });
895
901
  // Destroy unused children.
@@ -1000,16 +1006,49 @@ class Component extends View {
1000
1006
  }
1001
1007
 
1002
1008
  /**
1003
- * Mount the component into the dom.
1004
- * It instantiate the Component view using options,
1005
- * appends its element into the DOM (if `el` is provided).
1006
- * The `onMount` lifecycle method will be called after the component is added to the DOM.
1007
- * And returns the view instance.
1009
+ * Mount the component into the DOM.
1010
+ * Creates a new component instance with the provided options and optionally mounts it into the DOM.
1011
+ *
1012
+ * **Mounting modes:**
1013
+ * - **Normal mount** (default): Renders the component as HTML and appends it to the provided element. Use this for client-side rendering.
1014
+ * - **Hydration mode**: Assumes the DOM already contains the component's HTML (from server-side rendering).
1015
+ *
1016
+ * If `el` is not provided, the component is instantiated but not mounted (the same as using `new Component(options)`). You can mount it later by calling `render()` and appending the element (`this.el`) to the DOM.
1017
+ *
1008
1018
  * @static
1009
- * @param {object} [options] The view options.
1010
- * @param {node} [el] Dom element to append the view element.
1011
- * @param {boolean} [hydrate] If true, the view will hydrate existing DOM.
1019
+ * @param {object} [options={}] The component options. These will be passed to the constructor and can include
1020
+ * `model`, `state`, `props`, lifecycle methods, and any other component-specific options.
1021
+ * @param {node} [el] The DOM element where the component will be mounted. If provided, the component will be
1022
+ * rendered and appended to this element. If not provided, the component is created but not mounted.
1023
+ * @param {boolean} [hydrate=false] If `true`, enables hydration mode for server-side rendering. The component will
1024
+ * assume the DOM already contains its HTML structure and will only hydrate it.
1025
+ * If `false` (default), the component will be rendered from scratch and appended to `el`.
1012
1026
  * @return {Component} The component instance.
1027
+ * @example
1028
+ * import { Component, Model } from 'rasti';
1029
+ *
1030
+ * const Button = Component.create`
1031
+ * <button class="${({ props }) => props.className}">
1032
+ * ${({ props }) => props.label}
1033
+ * </button>
1034
+ * `;
1035
+ *
1036
+ * // Normal mount: render and append to DOM.
1037
+ * const button = Button.mount({
1038
+ * label: 'Click me'
1039
+ * }, document.body);
1040
+ *
1041
+ * // Create without mounting (mount later).
1042
+ * const button2 = Button.mount({ className : 'secondary', label : 'Save' });
1043
+ * // Later, render and append it to the DOM.
1044
+ * document.body.appendChild(button2.render().el);
1045
+ *
1046
+ * // Hydration mode: hydrate existing server-rendered HTML
1047
+ * // Assuming document.body already contains the HTML structure of the button.
1048
+ * const hydratedButton = Button.mount({
1049
+ * className : 'primary',
1050
+ * label : 'Click me'
1051
+ * }, document.body, true);
1013
1052
  */
1014
1053
  static mount(options, el, hydrate) {
1015
1054
  // Instantiate component.
@@ -1017,14 +1056,14 @@ class Component extends View {
1017
1056
  // If `el` is passed, mount component.
1018
1057
  if (el) {
1019
1058
  if (hydrate) {
1020
- // Generate subcomponents.
1059
+ // Hydrate existing DOM, only generate subcomponents calling `toString`.
1021
1060
  component.toString();
1022
- // Hydrate existing DOM.
1023
- component.hydrate(el);
1024
1061
  } else {
1025
- // Append element to the DOM.
1026
- el.append(component.render().el);
1062
+ // Render the component and append it to the provided element.
1063
+ el.append(parseHTML(component));
1027
1064
  }
1065
+ // Hydrate in both cases.
1066
+ component.hydrate(el);
1028
1067
  }
1029
1068
  // Return component instance.
1030
1069
  return component;
@@ -102,7 +102,7 @@ class Interpolation {
102
102
  element.after(divider);
103
103
  divider.after(fragment.firstChild);
104
104
  handleComponents();
105
- element.remove();
105
+ if (element.nextSibling === divider) element.remove();
106
106
  divider.remove();
107
107
  }
108
108
  }
package/lib/Component.cjs CHANGED
@@ -420,17 +420,23 @@ const parseAttributes = (attributesStr, expressions) => {
420
420
  const PH = Component.PLACEHOLDER('(\\d+)');
421
421
  const attributes = [];
422
422
  // Parse attributes string with support for placeholders in both names and values.
423
- const regExp = new RegExp(`(${PH}|[\\w-]+)(?:=(["']?)(?:${PH}|((?:.?(?!["']?\\s+(?:\\S+)=|\\s*/?[>"']))+.))\\3)?`, 'g');
423
+ const regExp = new RegExp(`(?:${PH}|([\\w-]+))(?:=(["']?)(?:${PH}|((?:.?(?!["']?\\s+(?:\\S+)=|\\s*/?[>"']))+.))?\\3)?`, 'g');
424
424
 
425
425
  let attributeMatch;
426
426
  while ((attributeMatch = regExp.exec(attributesStr)) !== null) {
427
- const [, attribute, attributeIdx, quotes, valueIdx, value] = attributeMatch;
427
+ const [, attributeIdx, attribute, quotes, valueIdx, value] = attributeMatch;
428
428
 
429
- const attr = typeof attributeIdx !== 'undefined' ? expressions[parseInt(attributeIdx, 10)] : attribute;
430
- const val = typeof valueIdx !== 'undefined' ? expressions[parseInt(valueIdx, 10)] : value;
429
+ const hasQuotes = !!quotes;
430
+
431
+ let attr = typeof attributeIdx !== 'undefined' ? expressions[parseInt(attributeIdx, 10)] : attribute;
432
+ let val = typeof valueIdx !== 'undefined' ? expressions[parseInt(valueIdx, 10)] : value;
433
+
434
+ if (hasQuotes && typeof val === 'undefined') {
435
+ val = '';
436
+ }
431
437
 
432
438
  if (typeof val !== 'undefined') {
433
- attributes.push([attr, val, !!quotes]);
439
+ attributes.push([attr, val, hasQuotes]);
434
440
  } else {
435
441
  attributes.push([attr]);
436
442
  }
@@ -881,17 +887,17 @@ class Component extends View {
881
887
  // Parse the rendered content and get the fragment.
882
888
  const fragment = utils_parseHTML(rendered);
883
889
 
884
- const handleComponents = () => {
890
+ const handleComponents = (parent) => () => {
885
891
  // Add recycled components to children and move them to the new template in the fragment.
886
- recycledChildren.forEach(recycled => recycle(recycled, this.el));
892
+ recycledChildren.forEach(recycled => recycle(recycled, parent));
887
893
  // Add new children. Hydrate them and update the interpolation.
888
- nextChildren.forEach(child => this.addChild(child).hydrate(this.el));
894
+ nextChildren.forEach(child => this.addChild(child).hydrate(parent));
889
895
  };
890
896
 
891
897
  if (this.isContainer()) {
892
- interpolation.updateElement(this.el, fragment, handleComponents);
898
+ interpolation.updateElement(this.el, fragment, handleComponents(this.el.parentNode));
893
899
  } else {
894
- interpolation.update(fragment, handleComponents);
900
+ interpolation.update(fragment, handleComponents(this.el));
895
901
  }
896
902
  });
897
903
  // Destroy unused children.
@@ -1002,16 +1008,49 @@ class Component extends View {
1002
1008
  }
1003
1009
 
1004
1010
  /**
1005
- * Mount the component into the dom.
1006
- * It instantiate the Component view using options,
1007
- * appends its element into the DOM (if `el` is provided).
1008
- * The `onMount` lifecycle method will be called after the component is added to the DOM.
1009
- * And returns the view instance.
1011
+ * Mount the component into the DOM.
1012
+ * Creates a new component instance with the provided options and optionally mounts it into the DOM.
1013
+ *
1014
+ * **Mounting modes:**
1015
+ * - **Normal mount** (default): Renders the component as HTML and appends it to the provided element. Use this for client-side rendering.
1016
+ * - **Hydration mode**: Assumes the DOM already contains the component's HTML (from server-side rendering).
1017
+ *
1018
+ * If `el` is not provided, the component is instantiated but not mounted (the same as using `new Component(options)`). You can mount it later by calling `render()` and appending the element (`this.el`) to the DOM.
1019
+ *
1010
1020
  * @static
1011
- * @param {object} [options] The view options.
1012
- * @param {node} [el] Dom element to append the view element.
1013
- * @param {boolean} [hydrate] If true, the view will hydrate existing DOM.
1021
+ * @param {object} [options={}] The component options. These will be passed to the constructor and can include
1022
+ * `model`, `state`, `props`, lifecycle methods, and any other component-specific options.
1023
+ * @param {node} [el] The DOM element where the component will be mounted. If provided, the component will be
1024
+ * rendered and appended to this element. If not provided, the component is created but not mounted.
1025
+ * @param {boolean} [hydrate=false] If `true`, enables hydration mode for server-side rendering. The component will
1026
+ * assume the DOM already contains its HTML structure and will only hydrate it.
1027
+ * If `false` (default), the component will be rendered from scratch and appended to `el`.
1014
1028
  * @return {Component} The component instance.
1029
+ * @example
1030
+ * import { Component, Model } from 'rasti';
1031
+ *
1032
+ * const Button = Component.create`
1033
+ * <button class="${({ props }) => props.className}">
1034
+ * ${({ props }) => props.label}
1035
+ * </button>
1036
+ * `;
1037
+ *
1038
+ * // Normal mount: render and append to DOM.
1039
+ * const button = Button.mount({
1040
+ * label: 'Click me'
1041
+ * }, document.body);
1042
+ *
1043
+ * // Create without mounting (mount later).
1044
+ * const button2 = Button.mount({ className : 'secondary', label : 'Save' });
1045
+ * // Later, render and append it to the DOM.
1046
+ * document.body.appendChild(button2.render().el);
1047
+ *
1048
+ * // Hydration mode: hydrate existing server-rendered HTML
1049
+ * // Assuming document.body already contains the HTML structure of the button.
1050
+ * const hydratedButton = Button.mount({
1051
+ * className : 'primary',
1052
+ * label : 'Click me'
1053
+ * }, document.body, true);
1015
1054
  */
1016
1055
  static mount(options, el, hydrate) {
1017
1056
  // Instantiate component.
@@ -1019,14 +1058,14 @@ class Component extends View {
1019
1058
  // If `el` is passed, mount component.
1020
1059
  if (el) {
1021
1060
  if (hydrate) {
1022
- // Generate subcomponents.
1061
+ // Hydrate existing DOM, only generate subcomponents calling `toString`.
1023
1062
  component.toString();
1024
- // Hydrate existing DOM.
1025
- component.hydrate(el);
1026
1063
  } else {
1027
- // Append element to the DOM.
1028
- el.append(component.render().el);
1064
+ // Render the component and append it to the provided element.
1065
+ el.append(utils_parseHTML(component));
1029
1066
  }
1067
+ // Hydrate in both cases.
1068
+ component.hydrate(el);
1030
1069
  }
1031
1070
  // Return component instance.
1032
1071
  return component;
@@ -104,7 +104,7 @@ class Interpolation {
104
104
  element.after(divider);
105
105
  divider.after(fragment.firstChild);
106
106
  handleComponents();
107
- element.remove();
107
+ if (element.nextSibling === divider) element.remove();
108
108
  divider.remove();
109
109
  }
110
110
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rasti",
3
- "version": "4.0.0-alpha.6",
3
+ "version": "4.0.0-alpha.7",
4
4
  "description": "Modern MVC for building user interfaces",
5
5
  "type": "module",
6
6
  "main": "lib/index.cjs",
@@ -30,7 +30,7 @@
30
30
  "posttest": "npm run lint",
31
31
  "lint": "eslint src test",
32
32
  "test": "mocha --require jsdom-global/register --reporter nyan test/*.js",
33
- "version": "node -e \"const fs=require('fs');const v=require('./package.json').version;const re=/@v\\d+\\.\\d+\\.\\d+(?:-[\\w.-]+)?/g;const t=fs.readFileSync('README.md','utf8');fs.writeFileSync('README.md',t.replace(re,'@v'+v));\" && git add README.md",
33
+ "version": "node -e \"const fs=require('fs');const v=require('./package.json').version;const re=/@v\\d+\\.\\d+\\.\\d+(?:-[\\w.-]+)?/g;const t1=fs.readFileSync('README.md','utf8');fs.writeFileSync('README.md',t1.replace(re,'@v'+v));const t2=fs.readFileSync('example/todo/index.html','utf8');fs.writeFileSync('example/todo/index.html',t2.replace(re,'@v'+v));\" && git add README.md example/todo/index.html",
34
34
  "docs:api": "jsdoc2md --module-index-format grouped --helper jsdoc2md/helper.cjs --partial jsdoc2md/header.hbs --partial jsdoc2md/sig-link.hbs --partial jsdoc2md/sig-link-html.hbs --partial jsdoc2md/sig-link-parent.hbs --partial jsdoc2md/link.hbs --files 'src/**/*.js' > docs/api.md"
35
35
  },
36
36
  "repository": {
package/src/Component.js CHANGED
@@ -415,17 +415,23 @@ const parseAttributes = (attributesStr, expressions) => {
415
415
  const PH = Component.PLACEHOLDER('(\\d+)');
416
416
  const attributes = [];
417
417
  // Parse attributes string with support for placeholders in both names and values.
418
- const regExp = new RegExp(`(${PH}|[\\w-]+)(?:=(["']?)(?:${PH}|((?:.?(?!["']?\\s+(?:\\S+)=|\\s*/?[>"']))+.))\\3)?`, 'g');
418
+ const regExp = new RegExp(`(?:${PH}|([\\w-]+))(?:=(["']?)(?:${PH}|((?:.?(?!["']?\\s+(?:\\S+)=|\\s*/?[>"']))+.))?\\3)?`, 'g');
419
419
 
420
420
  let attributeMatch;
421
421
  while ((attributeMatch = regExp.exec(attributesStr)) !== null) {
422
- const [, attribute, attributeIdx, quotes, valueIdx, value] = attributeMatch;
422
+ const [, attributeIdx, attribute, quotes, valueIdx, value] = attributeMatch;
423
423
 
424
- const attr = typeof attributeIdx !== 'undefined' ? expressions[parseInt(attributeIdx, 10)] : attribute;
425
- const val = typeof valueIdx !== 'undefined' ? expressions[parseInt(valueIdx, 10)] : value;
424
+ const hasQuotes = !!quotes;
425
+
426
+ let attr = typeof attributeIdx !== 'undefined' ? expressions[parseInt(attributeIdx, 10)] : attribute;
427
+ let val = typeof valueIdx !== 'undefined' ? expressions[parseInt(valueIdx, 10)] : value;
428
+
429
+ if (hasQuotes && typeof val === 'undefined') {
430
+ val = '';
431
+ }
426
432
 
427
433
  if (typeof val !== 'undefined') {
428
- attributes.push([attr, val, !!quotes]);
434
+ attributes.push([attr, val, hasQuotes]);
429
435
  } else {
430
436
  attributes.push([attr]);
431
437
  }
@@ -876,17 +882,17 @@ class Component extends View {
876
882
  // Parse the rendered content and get the fragment.
877
883
  const fragment = parseHTML(rendered);
878
884
 
879
- const handleComponents = () => {
885
+ const handleComponents = (parent) => () => {
880
886
  // Add recycled components to children and move them to the new template in the fragment.
881
- recycledChildren.forEach(recycled => recycle(recycled, this.el));
887
+ recycledChildren.forEach(recycled => recycle(recycled, parent));
882
888
  // Add new children. Hydrate them and update the interpolation.
883
- nextChildren.forEach(child => this.addChild(child).hydrate(this.el));
889
+ nextChildren.forEach(child => this.addChild(child).hydrate(parent));
884
890
  };
885
891
 
886
892
  if (this.isContainer()) {
887
- interpolation.updateElement(this.el, fragment, handleComponents);
893
+ interpolation.updateElement(this.el, fragment, handleComponents(this.el.parentNode));
888
894
  } else {
889
- interpolation.update(fragment, handleComponents);
895
+ interpolation.update(fragment, handleComponents(this.el));
890
896
  }
891
897
  });
892
898
  // Destroy unused children.
@@ -997,16 +1003,49 @@ class Component extends View {
997
1003
  }
998
1004
 
999
1005
  /**
1000
- * Mount the component into the dom.
1001
- * It instantiate the Component view using options,
1002
- * appends its element into the DOM (if `el` is provided).
1003
- * The `onMount` lifecycle method will be called after the component is added to the DOM.
1004
- * And returns the view instance.
1006
+ * Mount the component into the DOM.
1007
+ * Creates a new component instance with the provided options and optionally mounts it into the DOM.
1008
+ *
1009
+ * **Mounting modes:**
1010
+ * - **Normal mount** (default): Renders the component as HTML and appends it to the provided element. Use this for client-side rendering.
1011
+ * - **Hydration mode**: Assumes the DOM already contains the component's HTML (from server-side rendering).
1012
+ *
1013
+ * If `el` is not provided, the component is instantiated but not mounted (the same as using `new Component(options)`). You can mount it later by calling `render()` and appending the element (`this.el`) to the DOM.
1014
+ *
1005
1015
  * @static
1006
- * @param {object} [options] The view options.
1007
- * @param {node} [el] Dom element to append the view element.
1008
- * @param {boolean} [hydrate] If true, the view will hydrate existing DOM.
1016
+ * @param {object} [options={}] The component options. These will be passed to the constructor and can include
1017
+ * `model`, `state`, `props`, lifecycle methods, and any other component-specific options.
1018
+ * @param {node} [el] The DOM element where the component will be mounted. If provided, the component will be
1019
+ * rendered and appended to this element. If not provided, the component is created but not mounted.
1020
+ * @param {boolean} [hydrate=false] If `true`, enables hydration mode for server-side rendering. The component will
1021
+ * assume the DOM already contains its HTML structure and will only hydrate it.
1022
+ * If `false` (default), the component will be rendered from scratch and appended to `el`.
1009
1023
  * @return {Component} The component instance.
1024
+ * @example
1025
+ * import { Component, Model } from 'rasti';
1026
+ *
1027
+ * const Button = Component.create`
1028
+ * <button class="${({ props }) => props.className}">
1029
+ * ${({ props }) => props.label}
1030
+ * </button>
1031
+ * `;
1032
+ *
1033
+ * // Normal mount: render and append to DOM.
1034
+ * const button = Button.mount({
1035
+ * label: 'Click me'
1036
+ * }, document.body);
1037
+ *
1038
+ * // Create without mounting (mount later).
1039
+ * const button2 = Button.mount({ className : 'secondary', label : 'Save' });
1040
+ * // Later, render and append it to the DOM.
1041
+ * document.body.appendChild(button2.render().el);
1042
+ *
1043
+ * // Hydration mode: hydrate existing server-rendered HTML
1044
+ * // Assuming document.body already contains the HTML structure of the button.
1045
+ * const hydratedButton = Button.mount({
1046
+ * className : 'primary',
1047
+ * label : 'Click me'
1048
+ * }, document.body, true);
1010
1049
  */
1011
1050
  static mount(options, el, hydrate) {
1012
1051
  // Instantiate component.
@@ -1014,14 +1053,14 @@ class Component extends View {
1014
1053
  // If `el` is passed, mount component.
1015
1054
  if (el) {
1016
1055
  if (hydrate) {
1017
- // Generate subcomponents.
1056
+ // Hydrate existing DOM, only generate subcomponents calling `toString`.
1018
1057
  component.toString();
1019
- // Hydrate existing DOM.
1020
- component.hydrate(el);
1021
1058
  } else {
1022
- // Append element to the DOM.
1023
- el.append(component.render().el);
1059
+ // Render the component and append it to the provided element.
1060
+ el.append(parseHTML(component));
1024
1061
  }
1062
+ // Hydrate in both cases.
1063
+ component.hydrate(el);
1025
1064
  }
1026
1065
  // Return component instance.
1027
1066
  return component;
@@ -102,7 +102,7 @@ class Interpolation {
102
102
  element.after(divider);
103
103
  divider.after(fragment.firstChild);
104
104
  handleComponents();
105
- element.remove();
105
+ if (element.nextSibling === divider) element.remove();
106
106
  divider.remove();
107
107
  }
108
108
  }