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 +2 -2
- package/dist/rasti.js +63 -24
- package/dist/rasti.min.js +1 -1
- package/es/Component.js +62 -23
- package/es/core/Interpolation.js +1 -1
- package/lib/Component.cjs +62 -23
- package/lib/core/Interpolation.cjs +1 -1
- package/package.json +2 -2
- package/src/Component.js +62 -23
- package/src/core/Interpolation.js +1 -1
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.
|
|
4
|
-
<img alt="Rasti.js" src="https://cdn.jsdelivr.net/gh/8tentaculos/rasti@v4.0.0-alpha.
|
|
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(`(
|
|
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 [,
|
|
2037
|
+
const [, attributeIdx, attribute, quotes, valueIdx, value] = attributeMatch;
|
|
2038
2038
|
|
|
2039
|
-
const
|
|
2040
|
-
|
|
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,
|
|
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,
|
|
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(
|
|
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
|
|
2616
|
-
*
|
|
2617
|
-
*
|
|
2618
|
-
*
|
|
2619
|
-
*
|
|
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
|
|
2622
|
-
*
|
|
2623
|
-
* @param {
|
|
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
|
-
//
|
|
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
|
-
//
|
|
2638
|
-
el.append(component
|
|
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=>({"&":"&","<":"<",">":">",'"':""","'":"'"}[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=>({"&":"&","<":"<",">":">",'"':""","'":"'"}[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(`(
|
|
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 [,
|
|
425
|
+
const [, attributeIdx, attribute, quotes, valueIdx, value] = attributeMatch;
|
|
426
426
|
|
|
427
|
-
const
|
|
428
|
-
|
|
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,
|
|
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,
|
|
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(
|
|
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
|
|
1004
|
-
*
|
|
1005
|
-
*
|
|
1006
|
-
*
|
|
1007
|
-
*
|
|
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
|
|
1010
|
-
*
|
|
1011
|
-
* @param {
|
|
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
|
-
//
|
|
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
|
-
//
|
|
1026
|
-
el.append(component
|
|
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;
|
package/es/core/Interpolation.js
CHANGED
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(`(
|
|
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 [,
|
|
427
|
+
const [, attributeIdx, attribute, quotes, valueIdx, value] = attributeMatch;
|
|
428
428
|
|
|
429
|
-
const
|
|
430
|
-
|
|
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,
|
|
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,
|
|
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(
|
|
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
|
|
1006
|
-
*
|
|
1007
|
-
*
|
|
1008
|
-
*
|
|
1009
|
-
*
|
|
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
|
|
1012
|
-
*
|
|
1013
|
-
* @param {
|
|
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
|
-
//
|
|
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
|
-
//
|
|
1028
|
-
el.append(component
|
|
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;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "rasti",
|
|
3
|
-
"version": "4.0.0-alpha.
|
|
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
|
|
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(`(
|
|
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 [,
|
|
422
|
+
const [, attributeIdx, attribute, quotes, valueIdx, value] = attributeMatch;
|
|
423
423
|
|
|
424
|
-
const
|
|
425
|
-
|
|
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,
|
|
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,
|
|
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(
|
|
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
|
|
1001
|
-
*
|
|
1002
|
-
*
|
|
1003
|
-
*
|
|
1004
|
-
*
|
|
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
|
|
1007
|
-
*
|
|
1008
|
-
* @param {
|
|
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
|
-
//
|
|
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
|
-
//
|
|
1023
|
-
el.append(component
|
|
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;
|