rasti 3.0.0-alpha.2 → 3.0.0-alpha.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/rasti.js CHANGED
@@ -734,10 +734,10 @@
734
734
  // so all the components are added as children by the parent component.
735
735
  while ((match = regExp.exec(main)) !== null) {
736
736
  const before = main.slice(lastIndex, match.index);
737
- out.push(new SafeHTML(before), expressions[match[1]]);
737
+ out.push(Component.markAsSafeHTML(before), expressions[match[1]]);
738
738
  lastIndex = match.index + match[0].length;
739
739
  }
740
- out.push(new SafeHTML(main.slice(lastIndex)));
740
+ out.push(Component.markAsSafeHTML(main.slice(lastIndex)));
741
741
 
742
742
  return out;
743
743
  };
@@ -1031,7 +1031,7 @@
1031
1031
  if (value === null || typeof value === 'undefined') value = '';
1032
1032
 
1033
1033
  add[key] = value;
1034
- html.push(`${View.sanitize(key)}="${View.sanitize(value)}"`);
1034
+ html.push(`${Component.sanitize(key)}="${Component.sanitize(value)}"`);
1035
1035
  }
1036
1036
  });
1037
1037
  // Remove attributes that were in previousAttributes but not in current attributes.
@@ -1135,8 +1135,9 @@
1135
1135
 
1136
1136
  /**
1137
1137
  * Tagged template helper method.
1138
- * Used to create a partial template.
1139
- * It will return a one-dimensional array with strings and expressions.
1138
+ * Used to create a partial template.
1139
+ * It will return a one-dimensional array with strings and expressions.
1140
+ * Components will be added as children by the parent component. Template strings will be marked as safe HTML to be rendered.
1140
1141
  * @param {TemplateStringsArray} strings - Template strings.
1141
1142
  * @param {...any} expressions - Template expressions.
1142
1143
  * @return {Array} Array containing strings and expressions.
@@ -1305,6 +1306,20 @@
1305
1306
  return this;
1306
1307
  }
1307
1308
 
1309
+ /**
1310
+ * Mark a string as safe HTML to be rendered.
1311
+ * Normally you don't need to use this method, as Rasti will automatically mark strings
1312
+ * as safe HTML when the component is @link{#module_component_create created} and when
1313
+ * using the @link{#module_component__partial Component.partial} method.
1314
+ * Be sure that the string is safe to be rendered, as it will be inserted into the DOM without any sanitization.
1315
+ * @static
1316
+ * @param {string} value
1317
+ * @return {Rasti.SafeHTML} A safe HTML object.
1318
+ */
1319
+ static markAsSafeHTML(value) {
1320
+ return new SafeHTML(value);
1321
+ }
1322
+
1308
1323
  /**
1309
1324
  * Helper method used to extend a `Component`, creating a subclass.
1310
1325
  * @static
@@ -1465,7 +1480,7 @@
1465
1480
  const { tag : tagExpression, attributes : attributesAndEvents, inner, close } = parseMatch(match, expressions);
1466
1481
  // Get tag, attributes.
1467
1482
  tag = function() {
1468
- return View.sanitize(getExpressionResult(tagExpression, this));
1483
+ return Component.sanitize(getExpressionResult(tagExpression, this));
1469
1484
  };
1470
1485
  // Get attributes.
1471
1486
  attributes = function() {
@@ -1493,7 +1508,7 @@
1493
1508
  if (typeof item !== 'undefined' && item !== null && item !== false && item !== true) {
1494
1509
  if (item instanceof SafeHTML) return item;
1495
1510
  if (item instanceof Component) return addChild(item);
1496
- return View.sanitize(`${item}`);
1511
+ return Component.sanitize(`${item}`);
1497
1512
  }
1498
1513
  return '';
1499
1514
  }).join('');
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";class e{on(t,e){if("function"!=typeof e)throw new TypeError("Listener must be a function");return this.listeners||(this.listeners={}),this.listeners[t]||(this.listeners[t]=[]),this.listeners[t].push(e),()=>this.off(t,e)}once(t,e){if("function"==typeof e){const s=this,i=e;e=function(...n){i(...n),s.off(t,e)}}return this.on(t,e)}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((function(t){t(...e)}))}}var s=(t,e,...s)=>"function"!=typeof t?t:t.apply(e,s);const i={el:!0,tag:!0,attributes:!0,events:!0,model:!0,template:!0,onDestroy:!0};class n extends e{constructor(t={}){super(),this.preinitialize.apply(this,arguments),this.uid="uid"+ ++n.uid,this.delegatedEventListeners=[],this.children=[],this.destroyQueue=[],Object.keys(t).forEach((e=>{i[e]&&(this[e]=t[e])})),this.ensureElement()}preinitialize(){}$(t){return this.el.querySelector(t)}$$(t){return this.el.querySelectorAll(t)}destroy(){return this.destroyChildren(),this.undelegateEvents(),this.off(),this.destroyQueue.forEach((t=>t())),this.destroyQueue=[],this.onDestroy.apply(this,arguments),this}onDestroy(){}addChild(t){return this.children.push(t),t}destroyChildren(){this.children.forEach((t=>t.destroy())),this.children=[]}ensureElement(){if(this.el)this.el=s(this.el,this);else{const t=s(this.tag,this),e=s(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.parentNode.removeChild(this.el),this}delegateEvents(t){if(t||(t=s(this.events,this)),!t)return this;this.delegatedEventListeners.length&&this.undelegateEvents();let e={};return Object.keys(t).forEach((s=>{const i=s.split(" "),n=i.shift(),r=i.join(" ");let h=t[s];h=("string"==typeof h?this[h]:h).bind(this),e[n]||(e[n]=[]),e[n].push({selector:r,listener:h})})),Object.keys(e).forEach((t=>{const s=s=>{e[t].forEach((({selector:t,listener:e})=>{t&&!s.target.closest(t)||e(s,this)}))};this.delegatedEventListeners.push({type:t,listener:s}),this.el.addEventListener(t,s)})),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])))}}n.uid=0;const r=t=>t.reduce(((t,e)=>(Array.isArray(e)?t.push(...r(e)):t.push(e),t)),[]);class h{constructor(t){this.value=t}toString(){return this.value}}const o=(t,e)=>s(t,e,e),l=(t,e)=>t.reduce(((t,s,i)=>(t.push(s),void 0!==e[i]&&t.push(f.PLACEHOLDER_EXPRESSION(i)),t)),[]).join(""),a=(t,e)=>{const s=f.PLACEHOLDER_EXPRESSION("(\\d+)"),i=new RegExp(`${s}`,"g"),n=[];let r,o=0;for(;null!==(r=i.exec(t));){const s=t.slice(o,r.index);n.push(new h(s),e[r[1]]),o=r.index+r[0].length}return n.push(new h(t.slice(o))),n},c=(t,e)=>{const s=t.reduce(((t,s)=>{const i=e(s[0]);if(1===s.length)"object"==typeof i?t.all=Object.assign(t.all,i):"string"==typeof i&&(t.all[i]=!0);else{const n=e(s[1]);t.all[i]=n}return t}),{all:{},events:{},attributes:{}});return Object.keys(s.all).forEach((t=>{const e=t.match(/on(([A-Z]{1}[a-z]+)+)/);e&&e[1]?s.events[e[1].toLowerCase()]=s.all[t]:s.attributes[t]=s.all[t]})),s},u=(t,e)=>{const s=f.PLACEHOLDER_EXPRESSION("(\\d+)");return t.replace(new RegExp(`<(${s})([^>]*)>([\\s\\S]*?)</(${s})>|<(${s})([^>]*)/>`,"g"),(function(){const{tag:t,attributes:s,inner:i,close:n,raw:h}=d(arguments,e);if(!(t.prototype instanceof f))return h;let l;if(n){if(t!==n)return h;const s=a(u(i,e),e);l=function(){return r(s.map((t=>o(t,this))))}}return e.push((function(){const e=c(s,(t=>o(t,this))).all;return l&&(e.renderChildren=l.bind(this)),t.mount(e)})),f.PLACEHOLDER_EXPRESSION(e.length-1)}))},d=(t,e)=>{const s=f.PLACEHOLDER_EXPRESSION("(\\d+)"),[i,n,r,h,o,l,a,c,u,d]=t,E={raw:i,attributes:[]};let p;n?(E.tag=void 0!==r?e[r]:n,E.inner=o,E.close=void 0!==a?e[a]:l,p=h):(E.tag=void 0!==u?e[u]:c,p=d);const y=new RegExp(`(${s}|[\\w-]+)(?:=(["']?)(?:${s}|((?:.?(?!["']?\\s+(?:\\S+)=|\\s*/?[>"']))+.))\\3)?`,"g");let g;for(;null!==(g=y.exec(p));){const[,t,s,,i,n]=g,r=void 0!==s?e[s]:t,h=void 0!==i?e[i]:n;void 0!==h?E.attributes.push([r,h]):E.attributes.push([r])}return E},E={area:!0,base:!0,br:!0,col:!0,embed:!0,hr:!0,img:!0,input:!0,link:!0,meta:!0,source:!0,track:!0,wbr:!0},p={key:!0,state:!0,onCreate:!0,onChange:!0,onRender:!0};class f extends n{constructor(t={}){super(...arguments),Object.keys(t).forEach((e=>{p[e]&&(this[e]=t[e])})),this.options=t,this.onCreate.apply(this,arguments)}subscribe(t){if(!t.on)return;const e=this.onChange.bind(this),s=t.on("change",e);return this.destroyQueue.push("function"==typeof s?s:()=>t.off("change",e)),this}isContainer(){return!(this.tag||!this.template)}ensureElement(){this.el&&(this.el=s(this.el,this),this.delegateEvents())}findElement(t){return(t||document).querySelector(`[${f.DATA_ATTRIBUTE_UID}="${this.uid}"]`)}getAttributes(){const t={},e={},i=[],r={[f.DATA_ATTRIBUTE_UID]:this.uid};this.attributes&&Object.assign(r,s(this.attributes,this));const h=this.previousAttributes||{};return this.previousAttributes=r,Object.keys(r).forEach((s=>{let h=r[s];!1===h?e[s]=!0:!0===h?(t[s]="",i.push(s)):(null==h&&(h=""),t[s]=h,i.push(`${n.sanitize(s)}="${n.sanitize(h)}"`))})),Object.keys(h).forEach((t=>{t in r||(e[t]=!0)})),{add:t,remove:e,html:i.join(" ")}}hydrate(t){return this.model&&this.subscribe(this.model),this.state&&this.subscribe(this.state),this.isContainer()?(this.children[0].hydrate(t),this.el=this.children[0].el):(this.el=this.findElement(t),this.delegateEvents(),this.children.forEach((t=>t.hydrate(this.el)))),this.onRender.call(this,f.RENDER_TYPE_HYDRATE),this}recycle(t){if(this.isContainer())return this.children[0].recycle(t);return this.findElement(t).replaceWith(this.el),this.onRender.call(this,f.RENDER_TYPE_RECYCLE),this}destroy(){return super.destroy.apply(this,arguments),this.destroyed=!0,this}onCreate(){}onChange(){this.render()}onRender(){}onDestroy(){}partial(t,...e){return r(a(u(l(t,e),e),e).map((t=>o(t,this))))}getRecyclePlaceholder(){if(this.isContainer())return this.children[0].getRecyclePlaceholder();const t=s(this.tag,this)||"div",e=`${f.DATA_ATTRIBUTE_UID}="${this.uid}"`;return this.template||!E[t]?`<${t} ${e}></${t}>`:`<${t} ${e} />`}toString(){if(this.destroyChildren(),this.isContainer())return this.template.call(this,this.addChild.bind(this));const t=s(this.tag,this)||"div",e=this.getAttributes().html,i=this.template?this.template.call(this,this.addChild.bind(this)):"";return this.template||!E[t]?`<${t} ${e}>${i}</${t}>`:`<${t} ${e} />`}render(){if(this.destroyed)return this;if(!this.isContainer()){if(!this.el){const t=this.createElement("template");return t.innerHTML=this,this.hydrate(t.content),this}const t=this.getAttributes();Object.keys(t.remove).forEach((t=>{this.el.removeAttribute(t)})),Object.keys(t.add).forEach((e=>{this.el.setAttribute(e,t.add[e])}))}if(this.template){const t=document.activeElement,e=[],s=[],i=this.children;this.children=[];const n=this.template.call(this,(t=>{let n=t;const r=t.key&&i.find((e=>e.key===t.key));return r?(n=r.getRecyclePlaceholder(),s.push(r),t.destroy()):e.push(t),n}));if(this.isContainer())if(e[0]){const t=this.createElement("template");t.innerHTML=n,this.addChild(e[0]).hydrate(t.content);const s=t.content.children[0];this.el&&this.el.replaceWith(s),this.el=s}else{if(!s[0])throw new Error("Container component must have a child component");this.addChild(s[0])}else this.el.innerHTML=n,e.forEach((t=>{this.addChild(t).hydrate(this.el)})),s.forEach((t=>{this.addChild(t).recycle(this.el)}));i.forEach((t=>{s.indexOf(t)>-1||t.destroy()})),this.el.contains(t)&&t.focus()}return this.onRender.call(this,f.RENDER_TYPE_RENDER),this}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.appendChild(i.render().el)),i}static create(t,...e){const s=f.PLACEHOLDER_EXPRESSION("(\\d+)");let i,E,p,y;"function"==typeof t&&(e=[t],t=["",""]);const g=u(l(t,e),e);let b=g.match(new RegExp(`^\\s*<([a-z]+[1-6]?|${s})([^>]*)>([\\s\\S]*?)</(\\1|${s})>\\s*$|^\\s*<([a-z]+[1-6]?|${s})([^>]*)/>\\s*$`));if(b){const{tag:t,attributes:s,inner:l,close:u}=d(b,e);if(i=function(){return n.sanitize(o(t,this))},E=function(){return c(s,(t=>o(t,this))).attributes},p=function(){const t=c(s,(t=>o(t,this))).events;return Object.keys(t).reduce(((e,s)=>{const i=o(t[s],this);return Object.keys(i).forEach((t=>{e[`${s}${"&"===t?"":` ${t}`}`]=i[t]})),e}),{})},u){const t=l?a(l,e):[];y=function(e){return r(t.map((t=>o(t,this)))).map((t=>null!=t&&!1!==t&&!0!==t?t instanceof h?t:t instanceof f?e(t):n.sanitize(`${t}`):"")).join("")}}}else{if(b=g.match(new RegExp(`^\\s*${s}\\s*$`)),!b)throw new SyntaxError("Invalid component");y=function(t){return t(o(e[b[1]],this)).toString()}}return this.extend({tag:i,attributes:E,events:p,template:y})}}f.PLACEHOLDER_EXPRESSION=t=>`__RASTI_{${t}}__`,f.DATA_ATTRIBUTE_UID="data-rasti-uid",f.RENDER_TYPE_HYDRATE="hydrate",f.RENDER_TYPE_RECYCLE="recycle",f.RENDER_TYPE_RENDER="render",t.Component=f,t.Emitter=e,t.Model=class extends e{constructor(t={}){super(),this.preinitialize.apply(this,arguments);const e=s(this.defaults,this)||{};this.attributes=Object.assign({},e,t),this.previous={},Object.keys(this.attributes).forEach(this.defineAttribute.bind(this))}preinitialize(){}defineAttribute(t){Object.defineProperty(this,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}toJSON(){return Object.assign({},this.attributes)}},t.View=n}));
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";class e{on(t,e){if("function"!=typeof e)throw new TypeError("Listener must be a function");return this.listeners||(this.listeners={}),this.listeners[t]||(this.listeners[t]=[]),this.listeners[t].push(e),()=>this.off(t,e)}once(t,e){if("function"==typeof e){const s=this,i=e;e=function(...n){i(...n),s.off(t,e)}}return this.on(t,e)}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((function(t){t(...e)}))}}var s=(t,e,...s)=>"function"!=typeof t?t:t.apply(e,s);const i={el:!0,tag:!0,attributes:!0,events:!0,model:!0,template:!0,onDestroy:!0};class n extends e{constructor(t={}){super(),this.preinitialize.apply(this,arguments),this.uid="uid"+ ++n.uid,this.delegatedEventListeners=[],this.children=[],this.destroyQueue=[],Object.keys(t).forEach((e=>{i[e]&&(this[e]=t[e])})),this.ensureElement()}preinitialize(){}$(t){return this.el.querySelector(t)}$$(t){return this.el.querySelectorAll(t)}destroy(){return this.destroyChildren(),this.undelegateEvents(),this.off(),this.destroyQueue.forEach((t=>t())),this.destroyQueue=[],this.onDestroy.apply(this,arguments),this}onDestroy(){}addChild(t){return this.children.push(t),t}destroyChildren(){this.children.forEach((t=>t.destroy())),this.children=[]}ensureElement(){if(this.el)this.el=s(this.el,this);else{const t=s(this.tag,this),e=s(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.parentNode.removeChild(this.el),this}delegateEvents(t){if(t||(t=s(this.events,this)),!t)return this;this.delegatedEventListeners.length&&this.undelegateEvents();let e={};return Object.keys(t).forEach((s=>{const i=s.split(" "),n=i.shift(),r=i.join(" ");let h=t[s];h=("string"==typeof h?this[h]:h).bind(this),e[n]||(e[n]=[]),e[n].push({selector:r,listener:h})})),Object.keys(e).forEach((t=>{const s=s=>{e[t].forEach((({selector:t,listener:e})=>{t&&!s.target.closest(t)||e(s,this)}))};this.delegatedEventListeners.push({type:t,listener:s}),this.el.addEventListener(t,s)})),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])))}}n.uid=0;const r=t=>t.reduce(((t,e)=>(Array.isArray(e)?t.push(...r(e)):t.push(e),t)),[]);class h{constructor(t){this.value=t}toString(){return this.value}}const o=(t,e)=>s(t,e,e),l=(t,e)=>t.reduce(((t,s,i)=>(t.push(s),void 0!==e[i]&&t.push(p.PLACEHOLDER_EXPRESSION(i)),t)),[]).join(""),a=(t,e)=>{const s=p.PLACEHOLDER_EXPRESSION("(\\d+)"),i=new RegExp(`${s}`,"g"),n=[];let r,h=0;for(;null!==(r=i.exec(t));){const s=t.slice(h,r.index);n.push(p.markAsSafeHTML(s),e[r[1]]),h=r.index+r[0].length}return n.push(p.markAsSafeHTML(t.slice(h))),n},c=(t,e)=>{const s=t.reduce(((t,s)=>{const i=e(s[0]);if(1===s.length)"object"==typeof i?t.all=Object.assign(t.all,i):"string"==typeof i&&(t.all[i]=!0);else{const n=e(s[1]);t.all[i]=n}return t}),{all:{},events:{},attributes:{}});return Object.keys(s.all).forEach((t=>{const e=t.match(/on(([A-Z]{1}[a-z]+)+)/);e&&e[1]?s.events[e[1].toLowerCase()]=s.all[t]:s.attributes[t]=s.all[t]})),s},u=(t,e)=>{const s=p.PLACEHOLDER_EXPRESSION("(\\d+)");return t.replace(new RegExp(`<(${s})([^>]*)>([\\s\\S]*?)</(${s})>|<(${s})([^>]*)/>`,"g"),(function(){const{tag:t,attributes:s,inner:i,close:n,raw:h}=d(arguments,e);if(!(t.prototype instanceof p))return h;let l;if(n){if(t!==n)return h;const s=a(u(i,e),e);l=function(){return r(s.map((t=>o(t,this))))}}return e.push((function(){const e=c(s,(t=>o(t,this))).all;return l&&(e.renderChildren=l.bind(this)),t.mount(e)})),p.PLACEHOLDER_EXPRESSION(e.length-1)}))},d=(t,e)=>{const s=p.PLACEHOLDER_EXPRESSION("(\\d+)"),[i,n,r,h,o,l,a,c,u,d]=t,E={raw:i,attributes:[]};let f;n?(E.tag=void 0!==r?e[r]:n,E.inner=o,E.close=void 0!==a?e[a]:l,f=h):(E.tag=void 0!==u?e[u]:c,f=d);const y=new RegExp(`(${s}|[\\w-]+)(?:=(["']?)(?:${s}|((?:.?(?!["']?\\s+(?:\\S+)=|\\s*/?[>"']))+.))\\3)?`,"g");let g;for(;null!==(g=y.exec(f));){const[,t,s,,i,n]=g,r=void 0!==s?e[s]:t,h=void 0!==i?e[i]:n;void 0!==h?E.attributes.push([r,h]):E.attributes.push([r])}return E},E={area:!0,base:!0,br:!0,col:!0,embed:!0,hr:!0,img:!0,input:!0,link:!0,meta:!0,source:!0,track:!0,wbr:!0},f={key:!0,state:!0,onCreate:!0,onChange:!0,onRender:!0};class p extends n{constructor(t={}){super(...arguments),Object.keys(t).forEach((e=>{f[e]&&(this[e]=t[e])})),this.options=t,this.onCreate.apply(this,arguments)}subscribe(t){if(!t.on)return;const e=this.onChange.bind(this),s=t.on("change",e);return this.destroyQueue.push("function"==typeof s?s:()=>t.off("change",e)),this}isContainer(){return!(this.tag||!this.template)}ensureElement(){this.el&&(this.el=s(this.el,this),this.delegateEvents())}findElement(t){return(t||document).querySelector(`[${p.DATA_ATTRIBUTE_UID}="${this.uid}"]`)}getAttributes(){const t={},e={},i=[],n={[p.DATA_ATTRIBUTE_UID]:this.uid};this.attributes&&Object.assign(n,s(this.attributes,this));const r=this.previousAttributes||{};return this.previousAttributes=n,Object.keys(n).forEach((s=>{let r=n[s];!1===r?e[s]=!0:!0===r?(t[s]="",i.push(s)):(null==r&&(r=""),t[s]=r,i.push(`${p.sanitize(s)}="${p.sanitize(r)}"`))})),Object.keys(r).forEach((t=>{t in n||(e[t]=!0)})),{add:t,remove:e,html:i.join(" ")}}hydrate(t){return this.model&&this.subscribe(this.model),this.state&&this.subscribe(this.state),this.isContainer()?(this.children[0].hydrate(t),this.el=this.children[0].el):(this.el=this.findElement(t),this.delegateEvents(),this.children.forEach((t=>t.hydrate(this.el)))),this.onRender.call(this,p.RENDER_TYPE_HYDRATE),this}recycle(t){if(this.isContainer())return this.children[0].recycle(t);return this.findElement(t).replaceWith(this.el),this.onRender.call(this,p.RENDER_TYPE_RECYCLE),this}destroy(){return super.destroy.apply(this,arguments),this.destroyed=!0,this}onCreate(){}onChange(){this.render()}onRender(){}onDestroy(){}partial(t,...e){return r(a(u(l(t,e),e),e).map((t=>o(t,this))))}getRecyclePlaceholder(){if(this.isContainer())return this.children[0].getRecyclePlaceholder();const t=s(this.tag,this)||"div",e=`${p.DATA_ATTRIBUTE_UID}="${this.uid}"`;return this.template||!E[t]?`<${t} ${e}></${t}>`:`<${t} ${e} />`}toString(){if(this.destroyChildren(),this.isContainer())return this.template.call(this,this.addChild.bind(this));const t=s(this.tag,this)||"div",e=this.getAttributes().html,i=this.template?this.template.call(this,this.addChild.bind(this)):"";return this.template||!E[t]?`<${t} ${e}>${i}</${t}>`:`<${t} ${e} />`}render(){if(this.destroyed)return this;if(!this.isContainer()){if(!this.el){const t=this.createElement("template");return t.innerHTML=this,this.hydrate(t.content),this}const t=this.getAttributes();Object.keys(t.remove).forEach((t=>{this.el.removeAttribute(t)})),Object.keys(t.add).forEach((e=>{this.el.setAttribute(e,t.add[e])}))}if(this.template){const t=document.activeElement,e=[],s=[],i=this.children;this.children=[];const n=this.template.call(this,(t=>{let n=t;const r=t.key&&i.find((e=>e.key===t.key));return r?(n=r.getRecyclePlaceholder(),s.push(r),t.destroy()):e.push(t),n}));if(this.isContainer())if(e[0]){const t=this.createElement("template");t.innerHTML=n,this.addChild(e[0]).hydrate(t.content);const s=t.content.children[0];this.el&&this.el.replaceWith(s),this.el=s}else{if(!s[0])throw new Error("Container component must have a child component");this.addChild(s[0])}else this.el.innerHTML=n,e.forEach((t=>{this.addChild(t).hydrate(this.el)})),s.forEach((t=>{this.addChild(t).recycle(this.el)}));i.forEach((t=>{s.indexOf(t)>-1||t.destroy()})),this.el.contains(t)&&t.focus()}return this.onRender.call(this,p.RENDER_TYPE_RENDER),this}static markAsSafeHTML(t){return new h(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.appendChild(i.render().el)),i}static create(t,...e){const s=p.PLACEHOLDER_EXPRESSION("(\\d+)");let i,n,E,f;"function"==typeof t&&(e=[t],t=["",""]);const y=u(l(t,e),e);let g=y.match(new RegExp(`^\\s*<([a-z]+[1-6]?|${s})([^>]*)>([\\s\\S]*?)</(\\1|${s})>\\s*$|^\\s*<([a-z]+[1-6]?|${s})([^>]*)/>\\s*$`));if(g){const{tag:t,attributes:s,inner:l,close:u}=d(g,e);if(i=function(){return p.sanitize(o(t,this))},n=function(){return c(s,(t=>o(t,this))).attributes},E=function(){const t=c(s,(t=>o(t,this))).events;return Object.keys(t).reduce(((e,s)=>{const i=o(t[s],this);return Object.keys(i).forEach((t=>{e[`${s}${"&"===t?"":` ${t}`}`]=i[t]})),e}),{})},u){const t=l?a(l,e):[];f=function(e){return r(t.map((t=>o(t,this)))).map((t=>null!=t&&!1!==t&&!0!==t?t instanceof h?t:t instanceof p?e(t):p.sanitize(`${t}`):"")).join("")}}}else{if(g=y.match(new RegExp(`^\\s*${s}\\s*$`)),!g)throw new SyntaxError("Invalid component");f=function(t){return t(o(e[g[1]],this)).toString()}}return this.extend({tag:i,attributes:n,events:E,template:f})}}p.PLACEHOLDER_EXPRESSION=t=>`__RASTI_{${t}}__`,p.DATA_ATTRIBUTE_UID="data-rasti-uid",p.RENDER_TYPE_HYDRATE="hydrate",p.RENDER_TYPE_RECYCLE="recycle",p.RENDER_TYPE_RENDER="render",t.Component=p,t.Emitter=e,t.Model=class extends e{constructor(t={}){super(),this.preinitialize.apply(this,arguments);const e=s(this.defaults,this)||{};this.attributes=Object.assign({},e,t),this.previous={},Object.keys(this.attributes).forEach(this.defineAttribute.bind(this))}preinitialize(){}defineAttribute(t){Object.defineProperty(this,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}toJSON(){return Object.assign({},this.attributes)}},t.View=n}));
package/es/Component.js CHANGED
@@ -58,10 +58,10 @@ const splitPlaceholders = (main, expressions) => {
58
58
  // so all the components are added as children by the parent component.
59
59
  while ((match = regExp.exec(main)) !== null) {
60
60
  const before = main.slice(lastIndex, match.index);
61
- out.push(new SafeHTML(before), expressions[match[1]]);
61
+ out.push(Component.markAsSafeHTML(before), expressions[match[1]]);
62
62
  lastIndex = match.index + match[0].length;
63
63
  }
64
- out.push(new SafeHTML(main.slice(lastIndex)));
64
+ out.push(Component.markAsSafeHTML(main.slice(lastIndex)));
65
65
 
66
66
  return out;
67
67
  };
@@ -355,7 +355,7 @@ class Component extends View {
355
355
  if (value === null || typeof value === 'undefined') value = '';
356
356
 
357
357
  add[key] = value;
358
- html.push(`${View.sanitize(key)}="${View.sanitize(value)}"`);
358
+ html.push(`${Component.sanitize(key)}="${Component.sanitize(value)}"`);
359
359
  }
360
360
  });
361
361
  // Remove attributes that were in previousAttributes but not in current attributes.
@@ -459,8 +459,9 @@ class Component extends View {
459
459
 
460
460
  /**
461
461
  * Tagged template helper method.
462
- * Used to create a partial template.
463
- * It will return a one-dimensional array with strings and expressions.
462
+ * Used to create a partial template.
463
+ * It will return a one-dimensional array with strings and expressions.
464
+ * Components will be added as children by the parent component. Template strings will be marked as safe HTML to be rendered.
464
465
  * @param {TemplateStringsArray} strings - Template strings.
465
466
  * @param {...any} expressions - Template expressions.
466
467
  * @return {Array} Array containing strings and expressions.
@@ -629,6 +630,20 @@ class Component extends View {
629
630
  return this;
630
631
  }
631
632
 
633
+ /**
634
+ * Mark a string as safe HTML to be rendered.
635
+ * Normally you don't need to use this method, as Rasti will automatically mark strings
636
+ * as safe HTML when the component is @link{#module_component_create created} and when
637
+ * using the @link{#module_component__partial Component.partial} method.
638
+ * Be sure that the string is safe to be rendered, as it will be inserted into the DOM without any sanitization.
639
+ * @static
640
+ * @param {string} value
641
+ * @return {Rasti.SafeHTML} A safe HTML object.
642
+ */
643
+ static markAsSafeHTML(value) {
644
+ return new SafeHTML(value);
645
+ }
646
+
632
647
  /**
633
648
  * Helper method used to extend a `Component`, creating a subclass.
634
649
  * @static
@@ -789,7 +804,7 @@ class Component extends View {
789
804
  const { tag : tagExpression, attributes : attributesAndEvents, inner, close } = parseMatch(match, expressions);
790
805
  // Get tag, attributes.
791
806
  tag = function() {
792
- return View.sanitize(getExpressionResult(tagExpression, this));
807
+ return Component.sanitize(getExpressionResult(tagExpression, this));
793
808
  };
794
809
  // Get attributes.
795
810
  attributes = function() {
@@ -817,7 +832,7 @@ class Component extends View {
817
832
  if (typeof item !== 'undefined' && item !== null && item !== false && item !== true) {
818
833
  if (item instanceof SafeHTML) return item;
819
834
  if (item instanceof Component) return addChild(item);
820
- return View.sanitize(`${item}`);
835
+ return Component.sanitize(`${item}`);
821
836
  }
822
837
  return '';
823
838
  }).join('');
package/lib/Component.cjs CHANGED
@@ -60,10 +60,10 @@ const splitPlaceholders = (main, expressions) => {
60
60
  // so all the components are added as children by the parent component.
61
61
  while ((match = regExp.exec(main)) !== null) {
62
62
  const before = main.slice(lastIndex, match.index);
63
- out.push(new SafeHTML(before), expressions[match[1]]);
63
+ out.push(Component.markAsSafeHTML(before), expressions[match[1]]);
64
64
  lastIndex = match.index + match[0].length;
65
65
  }
66
- out.push(new SafeHTML(main.slice(lastIndex)));
66
+ out.push(Component.markAsSafeHTML(main.slice(lastIndex)));
67
67
 
68
68
  return out;
69
69
  };
@@ -357,7 +357,7 @@ class Component extends View {
357
357
  if (value === null || typeof value === 'undefined') value = '';
358
358
 
359
359
  add[key] = value;
360
- html.push(`${View.sanitize(key)}="${View.sanitize(value)}"`);
360
+ html.push(`${Component.sanitize(key)}="${Component.sanitize(value)}"`);
361
361
  }
362
362
  });
363
363
  // Remove attributes that were in previousAttributes but not in current attributes.
@@ -461,8 +461,9 @@ class Component extends View {
461
461
 
462
462
  /**
463
463
  * Tagged template helper method.
464
- * Used to create a partial template.
465
- * It will return a one-dimensional array with strings and expressions.
464
+ * Used to create a partial template.
465
+ * It will return a one-dimensional array with strings and expressions.
466
+ * Components will be added as children by the parent component. Template strings will be marked as safe HTML to be rendered.
466
467
  * @param {TemplateStringsArray} strings - Template strings.
467
468
  * @param {...any} expressions - Template expressions.
468
469
  * @return {Array} Array containing strings and expressions.
@@ -631,6 +632,20 @@ class Component extends View {
631
632
  return this;
632
633
  }
633
634
 
635
+ /**
636
+ * Mark a string as safe HTML to be rendered.
637
+ * Normally you don't need to use this method, as Rasti will automatically mark strings
638
+ * as safe HTML when the component is @link{#module_component_create created} and when
639
+ * using the @link{#module_component__partial Component.partial} method.
640
+ * Be sure that the string is safe to be rendered, as it will be inserted into the DOM without any sanitization.
641
+ * @static
642
+ * @param {string} value
643
+ * @return {Rasti.SafeHTML} A safe HTML object.
644
+ */
645
+ static markAsSafeHTML(value) {
646
+ return new SafeHTML(value);
647
+ }
648
+
634
649
  /**
635
650
  * Helper method used to extend a `Component`, creating a subclass.
636
651
  * @static
@@ -791,7 +806,7 @@ class Component extends View {
791
806
  const { tag : tagExpression, attributes : attributesAndEvents, inner, close } = parseMatch(match, expressions);
792
807
  // Get tag, attributes.
793
808
  tag = function() {
794
- return View.sanitize(getExpressionResult(tagExpression, this));
809
+ return Component.sanitize(getExpressionResult(tagExpression, this));
795
810
  };
796
811
  // Get attributes.
797
812
  attributes = function() {
@@ -819,7 +834,7 @@ class Component extends View {
819
834
  if (typeof item !== 'undefined' && item !== null && item !== false && item !== true) {
820
835
  if (item instanceof SafeHTML) return item;
821
836
  if (item instanceof Component) return addChild(item);
822
- return View.sanitize(`${item}`);
837
+ return Component.sanitize(`${item}`);
823
838
  }
824
839
  return '';
825
840
  }).join('');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rasti",
3
- "version": "3.0.0-alpha.2",
3
+ "version": "3.0.0-alpha.3",
4
4
  "description": "Modern MVC for building user interfaces",
5
5
  "type": "module",
6
6
  "main": "lib/index.cjs",
package/src/Component.js CHANGED
@@ -57,10 +57,10 @@ const splitPlaceholders = (main, expressions) => {
57
57
  // so all the components are added as children by the parent component.
58
58
  while ((match = regExp.exec(main)) !== null) {
59
59
  const before = main.slice(lastIndex, match.index);
60
- out.push(new SafeHTML(before), expressions[match[1]]);
60
+ out.push(Component.markAsSafeHTML(before), expressions[match[1]]);
61
61
  lastIndex = match.index + match[0].length;
62
62
  }
63
- out.push(new SafeHTML(main.slice(lastIndex)));
63
+ out.push(Component.markAsSafeHTML(main.slice(lastIndex)));
64
64
 
65
65
  return out;
66
66
  };
@@ -354,7 +354,7 @@ export default class Component extends View {
354
354
  if (value === null || typeof value === 'undefined') value = '';
355
355
 
356
356
  add[key] = value;
357
- html.push(`${View.sanitize(key)}="${View.sanitize(value)}"`);
357
+ html.push(`${Component.sanitize(key)}="${Component.sanitize(value)}"`);
358
358
  }
359
359
  });
360
360
  // Remove attributes that were in previousAttributes but not in current attributes.
@@ -458,8 +458,9 @@ export default class Component extends View {
458
458
 
459
459
  /**
460
460
  * Tagged template helper method.
461
- * Used to create a partial template.
462
- * It will return a one-dimensional array with strings and expressions.
461
+ * Used to create a partial template.
462
+ * It will return a one-dimensional array with strings and expressions.
463
+ * Components will be added as children by the parent component. Template strings will be marked as safe HTML to be rendered.
463
464
  * @param {TemplateStringsArray} strings - Template strings.
464
465
  * @param {...any} expressions - Template expressions.
465
466
  * @return {Array} Array containing strings and expressions.
@@ -628,6 +629,20 @@ export default class Component extends View {
628
629
  return this;
629
630
  }
630
631
 
632
+ /**
633
+ * Mark a string as safe HTML to be rendered.
634
+ * Normally you don't need to use this method, as Rasti will automatically mark strings
635
+ * as safe HTML when the component is @link{#module_component_create created} and when
636
+ * using the @link{#module_component__partial Component.partial} method.
637
+ * Be sure that the string is safe to be rendered, as it will be inserted into the DOM without any sanitization.
638
+ * @static
639
+ * @param {string} value
640
+ * @return {Rasti.SafeHTML} A safe HTML object.
641
+ */
642
+ static markAsSafeHTML(value) {
643
+ return new SafeHTML(value);
644
+ }
645
+
631
646
  /**
632
647
  * Helper method used to extend a `Component`, creating a subclass.
633
648
  * @static
@@ -788,7 +803,7 @@ export default class Component extends View {
788
803
  const { tag : tagExpression, attributes : attributesAndEvents, inner, close } = parseMatch(match, expressions);
789
804
  // Get tag, attributes.
790
805
  tag = function() {
791
- return View.sanitize(getExpressionResult(tagExpression, this));
806
+ return Component.sanitize(getExpressionResult(tagExpression, this));
792
807
  };
793
808
  // Get attributes.
794
809
  attributes = function() {
@@ -816,7 +831,7 @@ export default class Component extends View {
816
831
  if (typeof item !== 'undefined' && item !== null && item !== false && item !== true) {
817
832
  if (item instanceof SafeHTML) return item;
818
833
  if (item instanceof Component) return addChild(item);
819
- return View.sanitize(`${item}`);
834
+ return Component.sanitize(`${item}`);
820
835
  }
821
836
  return '';
822
837
  }).join('');