@plentico/pattr 0.0.19 → 0.0.21

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.
Files changed (3) hide show
  1. package/package.json +1 -1
  2. package/pattr.js +107 -30
  3. package/pattr.min.js +1 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@plentico/pattr",
3
- "version": "0.0.19",
3
+ "version": "0.0.21",
4
4
  "description": "A lightweight reactive framework",
5
5
  "type": "module",
6
6
  "main": "pattr.js",
package/pattr.js CHANGED
@@ -954,17 +954,20 @@ window.Pattr = {
954
954
  // This handles cases like content[key] where content is inherited
955
955
  const scope = el._scope;
956
956
 
957
- // For bracket notation like content[key], we need to evaluate key first
957
+ // For bracket notation like content[key] or nested content[key][i],
958
+ // split at the LAST bracket pair: evaluate the container object and the key
959
+ // separately through the scope chain, then set directly on the container.
960
+ // This handles arbitrary nesting depth AND avoids a subtle `with`-statement
961
+ // variable-shadowing bug: if the loop uses `for [key, value] of ...` then
962
+ // `value` is a scope property, so `eval('with (scope) { expr = value }')` would
963
+ // assign the loop variable back to itself instead of the new input value.
958
964
  if (modelAttr.includes('[')) {
959
- // Extract the object name and key expression
960
- const match = modelAttr.match(/^(.+)\[(.+)\]$/);
961
- if (match) {
962
- const [, objName, keyExpr] = match;
963
- const key = eval(`with (scope) { ${keyExpr} }`);
964
- scope[objName][key] = value; // Set on Proxy to trigger reactivity
965
- } else {
966
- eval(`with (scope) { ${modelAttr} = value }`);
967
- }
965
+ const lastBracket = modelAttr.lastIndexOf('[');
966
+ const containerExpr = modelAttr.substring(0, lastBracket);
967
+ const keyExpr = modelAttr.substring(lastBracket + 1, modelAttr.length - 1);
968
+ const container = eval(`with (scope) { (${containerExpr}) }`);
969
+ const key = eval(`with (scope) { (${keyExpr}) }`);
970
+ container[key] = value;
968
971
  } else if (modelAttr.includes('.')) {
969
972
  // Handle dot notation like content.path
970
973
  const parts = modelAttr.split('.');
@@ -992,22 +995,34 @@ window.Pattr = {
992
995
  // Store the p-model value to identify this specific input
993
996
  const modelAttrValue = el.getAttribute('p-model');
994
997
  const inputTagName = el.tagName;
995
- const inputType = el.type;
998
+
999
+ // Save cursor position before re-render so we can restore it after
1000
+ // the loop re-creates the input element.
1001
+ const selStart = el.selectionStart;
1002
+ const selEnd = el.selectionEnd;
996
1003
 
997
1004
  // Re-render DOM to reflect changes
998
1005
  this.walkDom(this.root, this.data, false);
999
1006
 
1000
- // Restore focus after DOM update (only for inputs in p-for loops)
1001
- // Non-loop inputs don't need focus restoration as they're not recreated
1007
+ // Restore focus AND cursor position after DOM update.
1008
+ // Only needed for inputs inside p-for loops those elements are
1009
+ // removed and re-created by refreshLoop, which drops focus and resets
1010
+ // the selection. Non-loop inputs are updated in-place, so they keep focus.
1002
1011
  if (pForKey && modelAttrValue) {
1003
1012
  requestAnimationFrame(() => {
1004
1013
  // Find the container with p-for-key, then find input with matching p-model
1005
1014
  const containerEl = document.querySelector(`[p-for-key="${pForKey}"]`);
1006
1015
  if (containerEl) {
1007
- // Look for input with same p-model attribute
1008
1016
  const elementToFocus = containerEl.querySelector(`${inputTagName.toLowerCase()}[p-model="${modelAttrValue}"]`);
1009
1017
  if (elementToFocus) {
1010
1018
  elementToFocus.focus();
1019
+ // Restore cursor/selection position.
1020
+ // setSelectionRange is only valid on text-like inputs (not number, date, etc.)
1021
+ if (selStart !== null && selEnd !== null) {
1022
+ try {
1023
+ elementToFocus.setSelectionRange(selStart, selEnd);
1024
+ } catch (_) { /* ignore for input types that don't support selection */ }
1025
+ }
1011
1026
  }
1012
1027
  }
1013
1028
  });
@@ -1025,23 +1040,16 @@ window.Pattr = {
1025
1040
  value = e.target.checked ? e.target.value : undefined;
1026
1041
  }
1027
1042
  if (value !== undefined) {
1028
- // Set on the scope (Proxy) to trigger reactive updates
1029
- // This handles cases like content[key] where content is inherited
1030
1043
  const scope = el._scope;
1031
-
1032
- // For bracket notation like content[key], we need to evaluate key first
1033
1044
  if (modelAttr.includes('[')) {
1034
- // Extract the object name and key expression
1035
- const match = modelAttr.match(/^(.+)\[(.+)\]$/);
1036
- if (match) {
1037
- const [, objName, keyExpr] = match;
1038
- const key = eval(`with (scope) { ${keyExpr} }`);
1039
- scope[objName][key] = value; // Set on Proxy to trigger reactivity
1040
- } else {
1041
- eval(`with (scope) { ${modelAttr} = value }`);
1042
- }
1045
+ const lastBracket = modelAttr.lastIndexOf('[');
1046
+ const containerExpr = modelAttr.substring(0, lastBracket);
1047
+ const keyExpr = modelAttr.substring(lastBracket + 1, modelAttr.length - 1);
1048
+ const container = eval(`with (scope) { (${containerExpr}) }`);
1049
+ const key = eval(`with (scope) { (${keyExpr}) }`);
1050
+ container[key] = value;
1043
1051
  } else {
1044
- scope[modelAttr] = value; // Set on Proxy to trigger reactivity
1052
+ scope[modelAttr] = value;
1045
1053
  }
1046
1054
  }
1047
1055
  });
@@ -1138,6 +1146,11 @@ window.Pattr = {
1138
1146
  // No ancestor prefix, just scope ID
1139
1147
  template._forScopeId = ssrScopePrefix.substring(0, ssrScopePrefix.length - 1);
1140
1148
  }
1149
+ // Persist the scope ID as a data attribute so it survives cloneNode.
1150
+ // When refreshLoop clones the outer template's content, the inner template
1151
+ // clone carries this attribute and reuses the same ID on the next render,
1152
+ // keeping p-for-key values stable across re-renders.
1153
+ template.setAttribute('data-p-for-scope-id', template._forScopeId);
1141
1154
  return ssrScopePrefix;
1142
1155
  }
1143
1156
  }
@@ -1199,9 +1212,19 @@ window.Pattr = {
1199
1212
  }
1200
1213
  }
1201
1214
 
1202
- // Generate a unique scope ID for this template if not already set from SSR
1215
+ // Generate a unique scope ID for this template if not already set from SSR.
1216
+ // Also check data-p-for-scope-id: cloneNode copies HTML attributes but not JS
1217
+ // properties, so a cloned inner template will have the attribute but not _forScopeId.
1218
+ // Reading it back here keeps the same ID across refreshLoop re-renders.
1203
1219
  if (!template._forScopeId) {
1204
- template._forScopeId = 's' + (this._templateScopeCounter++);
1220
+ if (template.hasAttribute('data-p-for-scope-id')) {
1221
+ // Restore the ID that was persisted by a previous call on the original template
1222
+ template._forScopeId = template.getAttribute('data-p-for-scope-id');
1223
+ } else {
1224
+ template._forScopeId = 's' + (this._templateScopeCounter++);
1225
+ // Persist so future clones of this template also reuse the same ID
1226
+ template.setAttribute('data-p-for-scope-id', template._forScopeId);
1227
+ }
1205
1228
  }
1206
1229
 
1207
1230
  return ancestorKey + template._forScopeId + ':';
@@ -1234,6 +1257,7 @@ window.Pattr = {
1234
1257
  tplChild.tagName === 'TEMPLATE' &&
1235
1258
  tplChild.getAttribute('p-for') === ssrChild.getAttribute('p-for');
1236
1259
 
1260
+ let targetTpl; // the template element inside outerTemplate.content to annotate
1237
1261
  if (!alreadyPresent) {
1238
1262
  // Insert a clone of the SSR inner template into the outer template content.
1239
1263
  // cloneNode(true) copies the <template> element and its .content fragment
@@ -1241,7 +1265,42 @@ window.Pattr = {
1241
1265
  const clonedInnerTemplate = ssrChild.cloneNode(true);
1242
1266
  templateEl.insertBefore(clonedInnerTemplate, tplChild || null);
1243
1267
  tplChildren.splice(ti, 0, clonedInnerTemplate);
1268
+ targetTpl = clonedInnerTemplate;
1269
+ } else {
1270
+ targetTpl = tplChild;
1244
1271
  }
1272
+
1273
+ // Stamp a stable data-p-for-scope-id on the template inside outerTemplate.content
1274
+ // so that every subsequent refreshLoop clone of that content inherits the same
1275
+ // scope ID. Without this, each clone starts without _forScopeId (cloneNode
1276
+ // doesn't copy JS properties) and getTemplateScopePrefix mints a fresh counter
1277
+ // ID, making p-for-key values change on every re-render and breaking the
1278
+ // requestAnimationFrame focus-restoration querySelector.
1279
+ //
1280
+ // enrichTemplateContent runs *before* hydrateLoop processes SSR elements, but
1281
+ // the SSR-rendered loop items (div.array-item[p-for-key="s0:2-s4:0"] etc.) are
1282
+ // already in the DOM as siblings of ssrChild, so we can read the scope ID from
1283
+ // them right here.
1284
+ if (targetTpl && !targetTpl.hasAttribute('data-p-for-scope-id')) {
1285
+ const firstLoopItem = ssrChild.nextElementSibling;
1286
+ if (firstLoopItem && firstLoopItem.hasAttribute('p-for-key')) {
1287
+ // Extract the inner scope ID segment from the SSR key
1288
+ // e.g. "s0:2-s4:0" → scopePart "s0:2-s4" → scopeId "s4"
1289
+ const ssrKey = firstLoopItem.getAttribute('p-for-key');
1290
+ const col = ssrKey.lastIndexOf(':');
1291
+ if (col > 0) {
1292
+ const scopePart = ssrKey.substring(0, col);
1293
+ const dash = scopePart.lastIndexOf('-');
1294
+ const scopeId = dash >= 0 ? scopePart.substring(dash + 1) : scopePart;
1295
+ targetTpl.setAttribute('data-p-for-scope-id', scopeId);
1296
+ }
1297
+ } else {
1298
+ // No SSR items (e.g. initially-empty array) — generate a stable ID now
1299
+ // so that all future refreshLoop clones also use the same ID.
1300
+ targetTpl.setAttribute('data-p-for-scope-id', 's' + (this._templateScopeCounter++));
1301
+ }
1302
+ }
1303
+
1245
1304
  ti++;
1246
1305
  } else if (ti < tplChildren.length && tplChildren[ti].tagName === ssrChild.tagName) {
1247
1306
  // Structurally matching element — recurse into children
@@ -1593,6 +1652,24 @@ window.Pattr = {
1593
1652
  });
1594
1653
  }
1595
1654
 
1655
+ // During hydration, don't recurse into children of a hidden element.
1656
+ // This prevents nested <template p-for> directives from being evaluated with
1657
+ // undefined loop variables when their containing p-show element is false
1658
+ // (e.g. the inner array loop inside an array-container that is hidden for scalar values).
1659
+ if (isHydrating && currentScope) {
1660
+ for (const attr of el.attributes) {
1661
+ const parsed = this.parseDirectiveModifiers(attr.name);
1662
+ if (parsed.directive === 'p-show' && !parsed.modifiers['pre-scope']) {
1663
+ try {
1664
+ if (!eval(`with (currentScope) { (${attr.value}) }`)) {
1665
+ return;
1666
+ }
1667
+ } catch (e) { /* ignore evaluation errors, proceed with children */ }
1668
+ break;
1669
+ }
1670
+ }
1671
+ }
1672
+
1596
1673
  // Recurse to children
1597
1674
  // Use Array.from to capture a snapshot of children, avoiding issues where
1598
1675
  // p-for templates insert new elements during the walk
package/pattr.min.js CHANGED
@@ -4,4 +4,4 @@
4
4
  * https://github.com/plentico/pattr
5
5
  * MIT License
6
6
  */
7
- window.Pattr={_templateScopeCounter:0,directives:{"p-text":(e,t,r={})=>{let a=String(t);if(r.trim&&r.trim.length>0){const e=parseInt(r.trim[0])||100;a.length>e&&(a=a.substring(0,e)+"...")}e.innerText=a},"p-html":(e,t,r={})=>{let a=t;if(r.allow&&r.allow.length>0){const e=r.allow,t=document.createElement("div");t.innerHTML=a;const s=t=>{if(t.nodeType===Node.ELEMENT_NODE){const r=t.tagName.toLowerCase();if(!e.includes(r)){const e=document.createDocumentFragment();return Array.from(t.childNodes).forEach(t=>{const r=s(t);r&&e.appendChild(r)}),e}const a=t.cloneNode(!1);return Array.from(t.childNodes).forEach(e=>{const t=s(e);t&&a.appendChild(t)}),a}return t.cloneNode()},o=document.createElement("div");Array.from(t.childNodes).forEach(e=>{const t=s(e);t&&o.appendChild(t)}),a=o.innerHTML}if(r.trim&&r.trim.length>0){const e=parseInt(r.trim[0])||100,t=document.createElement("div");t.innerHTML=a;let s=0,o=!1;const n=t=>{if(o)return null;if(t.nodeType===Node.TEXT_NODE){const r=t.textContent,a=e-s;if(r.length<=a)return s+=r.length,t.cloneNode();{o=!0;const e=r.substring(0,a)+"...";return document.createTextNode(e)}}if(t.nodeType===Node.ELEMENT_NODE){const e=t.cloneNode(!1);for(let r of t.childNodes){const t=n(r);if(t&&e.appendChild(t),o)break}return e}return t.cloneNode()},i=document.createElement("div");for(let e of t.childNodes){const t=n(e);if(t&&i.appendChild(t),o)break}a=i.innerHTML}e.innerHTML=a},"p-show":(e,t)=>{e.style.display=t?"":"none"},"p-style":(e,t)=>{"string"==typeof t?e.style.cssText=t:"object"==typeof t&&null!==t&&Object.assign(e.style,t)},"p-class":(e,t,r={})=>{if(void 0!==r.replace)return void("string"==typeof t?e.className=t:Array.isArray(t)?e.className=t.join(" "):"object"==typeof t&&null!==t&&(e.className=Object.keys(t).filter(e=>t[e]).join(" ")));let a=new Set,s=new Set;if("string"==typeof t&&t?t.split(" ").forEach(e=>{e&&(a.add(e),s.add(e))}):Array.isArray(t)?t.forEach(e=>{e&&(a.add(e),s.add(e))}):"object"==typeof t&&null!==t&&Object.keys(t).forEach(e=>{s.add(e),t[e]&&a.add(e)}),!e._p_staticClasses){const t=e.className?e.className.split(" ").filter(e=>e):[];e._p_staticClasses=t.filter(e=>!s.has(e))}e._p_dynamicClasses||(e._p_dynamicClasses=new Set),e._p_dynamicClasses=a;const o=[...e._p_staticClasses,...e._p_dynamicClasses];e.className=o.join(" ")},"p-model":(e,t)=>{Array.isArray(t)?e.value=t.join(", "):e.value=t},"p-attr":(e,t,r={})=>{const a=Object.keys(r);if(a.length>0){const r=a[0];null==t||!1===t?e.removeAttribute(r):e.setAttribute(r,String(t))}else"object"==typeof t&&null!==t&&Object.keys(t).forEach(r=>{const a=t[r];null==a||!1===a?e.removeAttribute(r):e.setAttribute(r,String(a))})}},parseDirectiveModifiers(e){const t=e.split(":"),r=t[0],a={};for(let e=1;e<t.length;e++){const r=t[e].split("."),s=r[0],o=r.slice(1);a[s]=o}return{directive:r,modifiers:a}},async start(){this.root=document.documentElement;const e=document.getElementById("p-root-data")?.textContent;try{this.rawData=JSON.parse(e||"{}")}catch(e){console.error("Error parsing root data JSON:",e)}this.buildScopeData(this.root,this.rawData),this.data=this.observe(this.rawData),this.walkDom(this.root,this.data,!0),this.refreshAllLoops()},generateDomPathId(e){const t=[];let r=e;for(;r&&"HTML"!==r.tagName;){const e=r.parentElement;if(e){const a=Array.from(e.children).indexOf(r);t.unshift(`${r.tagName}[${a}]`)}r=e}return t.join("/")},buildScopeData(e,t){let r=t;const a=Array.from(e.attributes).find(e=>e.name.startsWith("p-scope"));if(a){const s=e.getAttribute("p-id")||this.generateDomPathId(e);t._p_children||(t._p_children={}),t._p_children[s]||(t._p_children[s]={}),r=t._p_children[s],r._p_scope=e.getAttribute(a.name),r._p_id=s}let s=e.firstElementChild;for(;s;)this.buildScopeData(s,r),s=s.nextElementSibling},observe(e,t,r=[]){const a=e;let s=a;t&&(s=Object.create(t._p_target||t),Object.assign(s,a));const o=new Set(r),n=t?._p_target||t,i=new WeakSet,c=(e,t=!1,r=null)=>{if(null===e||"object"!=typeof e)return e;if(i.has(e))return e;if(e instanceof Date||e instanceof RegExp||e instanceof Map||e instanceof Set)return e;const a=new Proxy(e,{get:(e,a)=>{if("_p_target"===a)return e;if("_p_syncParent"===a)return n;if("_p_syncKey"===a)return r;const s=e[a];return t&&null!==s&&"object"==typeof s?c(s,!0,r):s},set:(e,a,s)=>{if(null!==s&&"object"==typeof s&&(s=c(s,t,r)),e[a]=s,t&&n&&r){const e=n[r];e&&"object"==typeof e&&(null===s||"object"!=typeof s||e[a]._p_isProxy?e[a]=s:e[a]=c(s,t,r))}return this.walkDom(this.root,this.data,!1),!0},has:(e,t)=>t in e});return i.add(e),a};if(n)for(const e of o){if(e in s){const t=s[e];null!==t&&"object"==typeof t&&(s[e]=c(t,!0))}if(e in n){const t=n[e];null!==t&&"object"==typeof t&&(n[e]=c(t,!0))}}return new Proxy(s,{get:(e,t)=>"_p_target"===t?e:e[t],set:(e,t,r)=>(o.has(t)&&null!==r&&"object"==typeof r&&(r=c(r,!0,t)),e[t]=r,o.has(t)&&n&&t in n&&(n[t]=r),this.walkDom(this.root,this.data,!1),!0),has:(e,t)=>t in e})},splitPScopeStatements(e){const t=[];let r="",a=0,s=0,o=!1,n=null;for(let i=0;i<e.length;i++){const c=e[i],l=i>0?e[i-1]:null;'"'!==c&&"'"!==c&&"`"!==c||"\\"===l||(o?n===c&&(o=!1,n=null):(o=!0,n=c)),o||("{"===c?a++:"}"===c?a--:"("===c?s++:")"===c&&s--),";"!==c||o||0!==a||0!==s?r+=c:(r.trim()&&t.push(r.trim()),r="")}return r.trim()&&t.push(r.trim()),t},getPScopeOutputVars(e){const t=[];return this.splitPScopeStatements(e).forEach(e=>{const r=e.match(/^\[([^\]]+)\]\s*=\s*(.+)$/);if(r){const e=r[1].split(",").map(e=>e.trim());return void t.push(...e)}const a=e.match(/^\{([^}]+)\}\s*=\s*(.+)$/);if(a){const e=a[1].split(",").map(e=>e.trim());return void t.push(...e)}const s=e.match(/^(\w+)\s*=\s*(.+)$/);s&&t.push(s[1])}),t},initScope(e,t){const r=e.getAttribute("p-id")||this.generateDomPathId(e),a=t._p_target._p_children[r],s=e.getAttribute("p-scope"),o=Array.from(e.attributes).find(e=>e.name.startsWith("p-scope")&&e.name.includes("sync"));let n=[],i=[];if(s&&n.push(s),o){const t=e.getAttribute(o.name);n.push(t),i=this.getPScopeOutputVars(t)}const c=n.join("; "),l=this.observe(a,t,i);i.length>0&&(e._p_syncVars=new Set(i)),this.executePScopeStatements(l,c);const p=Object.getPrototypeOf(l._p_target);e._parentSnapshot={};for(let t in p)t.startsWith("_p_")||(e._parentSnapshot[t]=p[t]);const h=l._p_target;e._localSnapshot={};for(let t of Object.keys(h))t.startsWith("_p_")||(e._localSnapshot[t]=h[t]);return l},refreshScope(e,t){let r=e._scope;if(!r)return t;const a=e.getAttribute("p-scope"),s=Array.from(e.attributes).find(e=>e.name.startsWith("p-scope")&&e.name.includes("sync"));let o=[];a&&o.push(a),s&&o.push(e.getAttribute(s.name));const n=o.join("; ");return n&&r._p_target&&this.updateScopeFromParent(e,r,n),r},executePScopeStatements(scope,pScopeExpr){const statements=this.splitPScopeStatements(pScopeExpr),target=scope._p_target,sequentialScope=new Proxy(target,{get:(e,t)=>Object.prototype.hasOwnProperty.call(e,t)?e[t]:scope[t],has:(e,t)=>Object.prototype.hasOwnProperty.call(e,t)||t in scope});for(const stmt of statements){const arrayDestructMatch=stmt.match(/^\[([^\]]+)\]\s*=\s*(.+)$/);if(arrayDestructMatch){const[,vars,expr]=arrayDestructMatch,varNames=vars.split(",").map(e=>e.trim());try{const value=eval(`with (sequentialScope) { (${expr}) }`);Array.isArray(value)&&varNames.forEach((e,t)=>{target[e]=value[t]})}catch(e){console.error(`Error executing p-scope array destructuring "${stmt}":`,e)}continue}const objDestructMatch=stmt.match(/^\{([^}]+)\}\s*=\s*(.+)$/);if(objDestructMatch){const[,vars,expr]=objDestructMatch,varNames=vars.split(",").map(e=>e.trim());try{const value=eval(`with (sequentialScope) { (${expr}) }`);"object"==typeof value&&null!==value&&varNames.forEach(e=>{target[e]=value[e]})}catch(e){console.error(`Error executing p-scope object destructuring "${stmt}":`,e)}continue}const match=stmt.match(/^(\w+)\s*=\s*(.+)$/);if(match){const[,varName,expr]=match;try{const value=eval(`with (sequentialScope) { (${expr}) }`);target[varName]=value}catch(e){console.error(`Error executing p-scope statement "${stmt}":`,e)}}}},updateScopeFromParent(el,scope,pScopeExpr){const parentProto=Object.getPrototypeOf(scope._p_target),target=scope._p_target,changedParentVars=new Set,changedLocalVars=new Set;el._parentSnapshot||(el._parentSnapshot={});for(let e in parentProto)e.startsWith("_p_")||(el._parentSnapshot[e]!==parentProto[e]&&changedParentVars.add(e),el._parentSnapshot[e]=parentProto[e]);el._localSnapshot||(el._localSnapshot={});for(let e of Object.keys(target))e.startsWith("_p_")||(el._localSnapshot[e]!==target[e]&&changedLocalVars.add(e),el._localSnapshot[e]=target[e]);const allChangedVars=new Set([...changedParentVars,...changedLocalVars]);if(0!==allChangedVars.size)try{const statements=this.splitPScopeStatements(pScopeExpr),outputVars=new Set;statements.forEach(e=>{const t=e.match(/^\[([^\]]+)\]\s*=\s*(.+)$/);if(t){return void t[1].split(",").map(e=>e.trim()).forEach(e=>outputVars.add(e))}const r=e.match(/^\{([^}]+)\}\s*=\s*(.+)$/);if(r){return void r[1].split(",").map(e=>e.trim()).forEach(e=>outputVars.add(e))}const a=e.match(/^(\w+)\s*=\s*(.+)$/);a&&outputVars.add(a[1])});const setInThisPass=new Set,sequentialScope=new Proxy(target,{get:(e,t)=>"_p_target"===t||"_p_children"===t||"_p_scope"===t||setInThisPass.has(t)?e[t]:changedParentVars.has(t)?parentProto[t]:changedLocalVars.has(t)||Object.prototype.hasOwnProperty.call(e,t)?e[t]:parentProto[t],set:(e,t,r)=>(e[t]=r,!0),has:(e,t)=>setInThisPass.has(t)||Object.prototype.hasOwnProperty.call(e,t)||t in parentProto});statements.forEach(stmt=>{let shouldExecute=!1;const parts=stmt.split("=");if(parts.length<=1)return;const rhs=parts.slice(1).join("=");if(changedParentVars.forEach(e=>{rhs.includes(e)&&(shouldExecute=!0)}),changedLocalVars.forEach(e=>{!outputVars.has(e)&&rhs.includes(e)&&(shouldExecute=!0)}),shouldExecute||setInThisPass.forEach(e=>{rhs.includes(e)&&(shouldExecute=!0)}),shouldExecute){const arrayMatch=stmt.match(/^\[([^\]]+)\]\s*=\s*(.+)$/);if(arrayMatch){const[,vars,expr]=arrayMatch,varNames=vars.split(",").map(e=>e.trim()),value=eval(`with (sequentialScope) { (${expr}) }`);return void(Array.isArray(value)&&varNames.forEach((e,t)=>{target[e]=value[t],setInThisPass.add(e)}))}const objMatch=stmt.match(/^\{([^}]+)\}\s*=\s*(.+)$/);if(objMatch){const[,vars,expr]=objMatch,varNames=vars.split(",").map(e=>e.trim()),value=eval(`with (sequentialScope) { (${expr}) }`);return void("object"==typeof value&&null!==value&&varNames.forEach(e=>{target[e]=value[e],setInThisPass.add(e)}))}const match=stmt.match(/^(\w+)\s*=\s*(.+)$/);if(match){const[,varName,expr]=match,value=eval(`with (sequentialScope) { (${expr}) }`);target[varName]=value,setInThisPass.add(varName)}}});for(let e of Object.keys(target))e.startsWith("_p_")||(el._localSnapshot[e]=target[e])}catch(e){console.error("Error re-executing p-scope expression:",e)}},registerEventListeners(el){Array.from(el.attributes).forEach(attr=>{if(attr.name.startsWith("p-on:")){const event=attr.name.replace("p-on:","");el.addEventListener(event,()=>{eval(`with (el._scope) { ${attr.value} }`),this.walkDom(this.root,this.data,!1),this.refreshAllLoops()})}})},registerModelBinding(el){const modelAttr=el.getAttribute("p-model");modelAttr&&(el.addEventListener("input",e=>{let value;const type=e.target.type;if("number"===type||"range"===type)value=""===e.target.value?null:Number(e.target.value);else if("checkbox"===type)value=e.target.checked;else if("radio"===type)value=e.target.checked?e.target.value:void 0;else{const currentValue=eval(`with (el._scope) { ${modelAttr} }`);value=Array.isArray(currentValue)?e.target.value.split(",").map(e=>e.trim()).filter(e=>e):e.target.value}if(void 0!==value){const scope=el._scope;if(modelAttr.includes("[")){const match=modelAttr.match(/^(.+)\[(.+)\]$/);if(match){const[,objName,keyExpr]=match,key=eval(`with (scope) { ${keyExpr} }`);scope[objName][key]=value}else eval(`with (scope) { ${modelAttr} = value }`)}else if(modelAttr.includes(".")){const e=modelAttr.split(".");let t=scope;for(let r=0;r<e.length-1;r++)t=t[e[r]];t[e[e.length-1]]=value}else scope[modelAttr]=value;let pForKey=el.getAttribute("p-for-key"),container=el;if(!pForKey)for(container=el.parentElement;container&&!pForKey;)pForKey=container.getAttribute("p-for-key"),pForKey||(container=container.parentElement);const modelAttrValue=el.getAttribute("p-model"),inputTagName=el.tagName,inputType=el.type;this.walkDom(this.root,this.data,!1),pForKey&&modelAttrValue&&requestAnimationFrame(()=>{const e=document.querySelector(`[p-for-key="${pForKey}"]`);if(e){const t=e.querySelector(`${inputTagName.toLowerCase()}[p-model="${modelAttrValue}"]`);t&&t.focus()}})}}),"checkbox"!==el.type&&"radio"!==el.type||el.addEventListener("change",e=>{let value;if(value="checkbox"===el.type?e.target.checked:e.target.checked?e.target.value:void 0,void 0!==value){const scope=el._scope;if(modelAttr.includes("[")){const match=modelAttr.match(/^(.+)\[(.+)\]$/);if(match){const[,objName,keyExpr]=match,key=eval(`with (scope) { ${keyExpr} }`);scope[objName][key]=value}else eval(`with (scope) { ${modelAttr} = value }`)}else scope[modelAttr]=value}}))},evaluateDirectives(el,scope){Array.from(el.attributes).forEach(attr=>{const parsed=this.parseDirectiveModifiers(attr.name);if(Object.keys(this.directives).includes(parsed.directive)){const evalScope=el._scope||scope,value=eval(`with (evalScope) { (${attr.value}) }`);this.directives[parsed.directive](el,value,parsed.modifiers)}})},setForTemplateRecursive(e,t){e._forTemplate=t,Array.from(e.children).forEach(e=>{this.setForTemplateRecursive(e,t)})},refreshAllLoops(e=this.root,t=new Set){if("TEMPLATE"===e.tagName&&e.hasAttribute("p-for")){if(t.has(e))return;return t.add(e),void this.handleFor(e,e._scope||this.data,!1)}let r=e.firstElementChild;for(;r;){const e=r.nextElementSibling;this.refreshAllLoops(r,t),r=e}},handleFor(e,t,r){const a=e.getAttribute("p-for"),s=a.match(/^(?:const|let)?\s*(.+?)\s+(of|in)\s+(.+)$/);if(!s)return void console.error(`Invalid p-for expression: ${a}`);const[,o,n,i]=s;r?this.hydrateLoop(e,t,o,i):this.refreshLoop(e,t,o,i)},getTemplateScopePrefix(e){let t="",r=e.nextElementSibling;if(r&&r.hasAttribute&&r.hasAttribute("p-for-key")){const a=r.getAttribute("p-for-key"),s=a.lastIndexOf(":");if(s>0){const r=a.substring(0,s+1),o=r.lastIndexOf("-");if(o>=0){t=r.substring(0,o+1);const a=r.substring(o+1,r.length-1);e._forScopeId=a}else e._forScopeId=r.substring(0,r.length-1);return r}}if(e._forTemplate){const r=e._forTemplate._forData;if(r&&r.scopePrefix){let r=e.previousElementSibling;for(;r;){if(r.hasAttribute&&r.hasAttribute("p-for-key")){t=r.getAttribute("p-for-key")+"-";break}r=r.previousElementSibling}}}if(!t){let r=e.previousElementSibling;for(;r;){if(r.hasAttribute&&r.hasAttribute("p-for-key")&&r._forTemplate===e._forTemplate){t=r.getAttribute("p-for-key")+"-";break}r=r.previousElementSibling}}if(!t){let r=e.parentElement;for(;r;){if(r.hasAttribute&&r.hasAttribute("p-for-key")){t=r.getAttribute("p-for-key")+"-";break}if(r._forTemplate){const e=r._forTemplate._forData;if(e){const a=e.renderedElements.indexOf(r);if(a>=0){t=(e.scopePrefix||"")+a+"-";break}}}r=r.parentElement}}return e._forScopeId||(e._forScopeId="s"+this._templateScopeCounter++),t+e._forScopeId+":"},enrichTemplateContent(e,t){const r=Array.from(t.children).filter(e=>!e.hasAttribute("p-for-key")),a=Array.from(e.children);let s=0;for(const t of r)if("TEMPLATE"===t.tagName&&t.hasAttribute("p-for")){const r=a[s];if(!(r&&"TEMPLATE"===r.tagName&&r.getAttribute("p-for")===t.getAttribute("p-for"))){const o=t.cloneNode(!0);e.insertBefore(o,r||null),a.splice(s,0,o)}s++}else s<a.length&&a[s].tagName===t.tagName&&(this.enrichTemplateContent(a[s],t),s++)},hydrateLoop(template,parentScope,varPattern,iterableExpr){template._scope=parentScope;try{const iterable=eval(`with (parentScope) { (${iterableExpr}) }`),scopePrefix=this.getTemplateScopePrefix(template);template._forData={varPattern:varPattern,iterableExpr:iterableExpr,renderedElements:[],scopePrefix:scopePrefix};const existingElementsByKey={};let sibling=template.nextElementSibling;for(;sibling&&sibling.hasAttribute("p-for-key");){const e=sibling.getAttribute("p-for-key"),t=e.startsWith(scopePrefix)||!e.includes(":")&&!e.includes("-");if(t){let t;t=e.startsWith(scopePrefix)?e.substring(scopePrefix.length):e,existingElementsByKey[t]||(existingElementsByKey[t]=[]),existingElementsByKey[t].push(sibling)}sibling=sibling.nextElementSibling}const templateRoot=template.content.firstElementChild;if(templateRoot)for(const e of Object.values(existingElementsByKey))for(const t of e)t.querySelector&&t.querySelector("template[p-for]")&&this.enrichTemplateContent(templateRoot,t);let index=0,lastInserted=template;for(const e of iterable){const t=this.createLoopScope(parentScope,varPattern,e);if(existingElementsByKey[String(index)])existingElementsByKey[String(index)].forEach(e=>{e._scope=t,this.setForTemplateRecursive(e,template),"TEMPLATE"!==e.tagName&&e.setAttribute("p-for-key",scopePrefix+index),this.walkDom(e,t,!0),e._p_loopHydrated=!0,template._forData.renderedElements.push(e),lastInserted=e});else{const e=template.content.cloneNode(!0),r=Array.from(e.children);r.forEach(e=>{e._scope=t,this.setForTemplateRecursive(e,template),"TEMPLATE"!==e.tagName&&e.setAttribute("p-for-key",scopePrefix+index),this.walkDom(e,t,!0)});const a=document.createDocumentFragment();r.forEach(e=>a.appendChild(e)),lastInserted.parentNode.insertBefore(a,lastInserted.nextSibling),template._forData.renderedElements.push(...r),lastInserted=r[r.length-1]||lastInserted}index++}Object.keys(existingElementsByKey).forEach(e=>{parseInt(e)>=index&&existingElementsByKey[e].forEach(e=>e.remove())})}catch(e){console.error(`Error in p-for hydration: ${iterableExpr}`,e)}},removeLoopElements(e){e.forEach(e=>{if(e._forData&&e._forData.renderedElements&&(this.removeLoopElements(e._forData.renderedElements),e._forData.renderedElements=[]),e.querySelectorAll){e.querySelectorAll("template[p-for]").forEach(e=>{e._forData&&e._forData.renderedElements&&(this.removeLoopElements(e._forData.renderedElements),e._forData.renderedElements=[])})}e.remove()})},refreshLoop(template,parentScope,varPattern,iterableExpr){const forData=template._forData;if(forData)try{const iterable=eval(`with (parentScope._p_target || parentScope) { (${iterableExpr}) }`),scopePrefix=forData.scopePrefix||this.getTemplateScopePrefix(template);this.removeLoopElements(forData.renderedElements),forData.renderedElements=[];let lastInsertedElement=template,index=0;for(const e of iterable){const t=template.content.cloneNode(!0),r=this.createLoopScope(parentScope,forData.varPattern,e),a=Array.from(t.children);a.forEach(e=>{e._scope=r,this.setForTemplateRecursive(e,template),"TEMPLATE"!==e.tagName&&e.setAttribute("p-for-key",scopePrefix+index)});const s=document.createDocumentFragment();a.forEach(e=>s.appendChild(e)),lastInsertedElement.parentNode.insertBefore(s,lastInsertedElement.nextSibling),forData.renderedElements.push(...a),a.forEach(e=>{this.walkDom(e,r,!0)});const o=a[a.length-1];o&&(lastInsertedElement=this.findLastRenderedSibling(o)),index++}}catch(e){console.error(`Error in p-for refresh: ${iterableExpr}`,e)}},findLastRenderedSibling(e){if("TEMPLATE"===e.tagName&&e._forData&&e._forData.renderedElements.length>0){const t=e._forData.renderedElements[e._forData.renderedElements.length-1];return this.findLastRenderedSibling(t)}return e},createLoopScope(e,t,r){const a={};if((t=t.trim()).startsWith("[")){const e=t.slice(1,-1).split(",").map(e=>e.trim());Array.isArray(r)?e.forEach((e,t)=>a[e]=r[t]):a[e[0]]=r}else if(t.startsWith("{")){t.slice(1,-1).split(",").map(e=>e.trim()).forEach(e=>{const[t,s]=e.split(":").map(e=>e.trim());a[s||t]=r[t]})}else a[t]=r;if(!e)return console.error("parentScope is undefined in createLoopScope"),new Proxy({},{get:()=>{},set:()=>!1});const s=e._p_target||e;if(!s||"object"!=typeof s)return console.error("Invalid parentTarget:",s),new Proxy(a,{get:(e,t)=>e[t],set:(e,t,r)=>(e[t]=r,!0)});const o=Object.create(s);Object.assign(o,a);const n=new Proxy(o,{get:(e,t)=>e[t],set:(e,t,r)=>(t in a?e[t]=r:s[t]=r,!0)});return n._p_target=o,n},walkDom(el,parentScope,isHydrating=!1){if(isHydrating&&el._p_loopHydrated)return;if("TEMPLATE"===el.tagName&&el.hasAttribute("p-for")){if(!isHydrating&&el._forTemplate)return;return void this.handleFor(el,parentScope,isHydrating)}const preScopeShowAttr=Array.from(el.attributes).find(e=>{const t=this.parseDirectiveModifiers(e.name);return"p-show"===t.directive&&t.modifiers["pre-scope"]});if(preScopeShowAttr&&parentScope){const value=eval(`with (parentScope) { (${preScopeShowAttr.value}) }`);this.directives["p-show"](el,value)}let currentScope=parentScope;const hasPScope=Array.from(el.attributes).some(e=>e.name.startsWith("p-scope"));hasPScope&&(currentScope=isHydrating?this.initScope(el,parentScope):this.refreshScope(el,parentScope)),isHydrating&&(el._scope=currentScope,this.registerEventListeners(el),this.registerModelBinding(el)),currentScope&&Array.from(el.attributes).forEach(attr=>{const parsed=this.parseDirectiveModifiers(attr.name);if(("p-show"!==parsed.directive||!parsed.modifiers["pre-scope"])&&Object.keys(this.directives).includes(parsed.directive)){const evalScope=el._scope||currentScope,value=eval(`with (evalScope) { (${attr.value}) }`);this.directives[parsed.directive](el,value,parsed.modifiers)}});const children=Array.from(el.children);for(const e of children)this.walkDom(e,currentScope,isHydrating)}},window.Pattr.start();
7
+ window.Pattr={_templateScopeCounter:0,directives:{"p-text":(e,t,r={})=>{let s=String(t);if(r.trim&&r.trim.length>0){const e=parseInt(r.trim[0])||100;s.length>e&&(s=s.substring(0,e)+"...")}e.innerText=s},"p-html":(e,t,r={})=>{let s=t;if(r.allow&&r.allow.length>0){const e=r.allow,t=document.createElement("div");t.innerHTML=s;const a=t=>{if(t.nodeType===Node.ELEMENT_NODE){const r=t.tagName.toLowerCase();if(!e.includes(r)){const e=document.createDocumentFragment();return Array.from(t.childNodes).forEach(t=>{const r=a(t);r&&e.appendChild(r)}),e}const s=t.cloneNode(!1);return Array.from(t.childNodes).forEach(e=>{const t=a(e);t&&s.appendChild(t)}),s}return t.cloneNode()},o=document.createElement("div");Array.from(t.childNodes).forEach(e=>{const t=a(e);t&&o.appendChild(t)}),s=o.innerHTML}if(r.trim&&r.trim.length>0){const e=parseInt(r.trim[0])||100,t=document.createElement("div");t.innerHTML=s;let a=0,o=!1;const n=t=>{if(o)return null;if(t.nodeType===Node.TEXT_NODE){const r=t.textContent,s=e-a;if(r.length<=s)return a+=r.length,t.cloneNode();{o=!0;const e=r.substring(0,s)+"...";return document.createTextNode(e)}}if(t.nodeType===Node.ELEMENT_NODE){const e=t.cloneNode(!1);for(let r of t.childNodes){const t=n(r);if(t&&e.appendChild(t),o)break}return e}return t.cloneNode()},i=document.createElement("div");for(let e of t.childNodes){const t=n(e);if(t&&i.appendChild(t),o)break}s=i.innerHTML}e.innerHTML=s},"p-show":(e,t)=>{e.style.display=t?"":"none"},"p-style":(e,t)=>{"string"==typeof t?e.style.cssText=t:"object"==typeof t&&null!==t&&Object.assign(e.style,t)},"p-class":(e,t,r={})=>{if(void 0!==r.replace)return void("string"==typeof t?e.className=t:Array.isArray(t)?e.className=t.join(" "):"object"==typeof t&&null!==t&&(e.className=Object.keys(t).filter(e=>t[e]).join(" ")));let s=new Set,a=new Set;if("string"==typeof t&&t?t.split(" ").forEach(e=>{e&&(s.add(e),a.add(e))}):Array.isArray(t)?t.forEach(e=>{e&&(s.add(e),a.add(e))}):"object"==typeof t&&null!==t&&Object.keys(t).forEach(e=>{a.add(e),t[e]&&s.add(e)}),!e._p_staticClasses){const t=e.className?e.className.split(" ").filter(e=>e):[];e._p_staticClasses=t.filter(e=>!a.has(e))}e._p_dynamicClasses||(e._p_dynamicClasses=new Set),e._p_dynamicClasses=s;const o=[...e._p_staticClasses,...e._p_dynamicClasses];e.className=o.join(" ")},"p-model":(e,t)=>{Array.isArray(t)?e.value=t.join(", "):e.value=t},"p-attr":(e,t,r={})=>{const s=Object.keys(r);if(s.length>0){const r=s[0];null==t||!1===t?e.removeAttribute(r):e.setAttribute(r,String(t))}else"object"==typeof t&&null!==t&&Object.keys(t).forEach(r=>{const s=t[r];null==s||!1===s?e.removeAttribute(r):e.setAttribute(r,String(s))})}},parseDirectiveModifiers(e){const t=e.split(":"),r=t[0],s={};for(let e=1;e<t.length;e++){const r=t[e].split("."),a=r[0],o=r.slice(1);s[a]=o}return{directive:r,modifiers:s}},async start(){this.root=document.documentElement;const e=document.getElementById("p-root-data")?.textContent;try{this.rawData=JSON.parse(e||"{}")}catch(e){console.error("Error parsing root data JSON:",e)}this.buildScopeData(this.root,this.rawData),this.data=this.observe(this.rawData),this.walkDom(this.root,this.data,!0),this.refreshAllLoops()},generateDomPathId(e){const t=[];let r=e;for(;r&&"HTML"!==r.tagName;){const e=r.parentElement;if(e){const s=Array.from(e.children).indexOf(r);t.unshift(`${r.tagName}[${s}]`)}r=e}return t.join("/")},buildScopeData(e,t){let r=t;const s=Array.from(e.attributes).find(e=>e.name.startsWith("p-scope"));if(s){const a=e.getAttribute("p-id")||this.generateDomPathId(e);t._p_children||(t._p_children={}),t._p_children[a]||(t._p_children[a]={}),r=t._p_children[a],r._p_scope=e.getAttribute(s.name),r._p_id=a}let a=e.firstElementChild;for(;a;)this.buildScopeData(a,r),a=a.nextElementSibling},observe(e,t,r=[]){const s=e;let a=s;t&&(a=Object.create(t._p_target||t),Object.assign(a,s));const o=new Set(r),n=t?._p_target||t,i=new WeakSet,c=(e,t=!1,r=null)=>{if(null===e||"object"!=typeof e)return e;if(i.has(e))return e;if(e instanceof Date||e instanceof RegExp||e instanceof Map||e instanceof Set)return e;const s=new Proxy(e,{get:(e,s)=>{if("_p_target"===s)return e;if("_p_syncParent"===s)return n;if("_p_syncKey"===s)return r;const a=e[s];return t&&null!==a&&"object"==typeof a?c(a,!0,r):a},set:(e,s,a)=>{if(null!==a&&"object"==typeof a&&(a=c(a,t,r)),e[s]=a,t&&n&&r){const e=n[r];e&&"object"==typeof e&&(null===a||"object"!=typeof a||e[s]._p_isProxy?e[s]=a:e[s]=c(a,t,r))}return this.walkDom(this.root,this.data,!1),!0},has:(e,t)=>t in e});return i.add(e),s};if(n)for(const e of o){if(e in a){const t=a[e];null!==t&&"object"==typeof t&&(a[e]=c(t,!0))}if(e in n){const t=n[e];null!==t&&"object"==typeof t&&(n[e]=c(t,!0))}}return new Proxy(a,{get:(e,t)=>"_p_target"===t?e:e[t],set:(e,t,r)=>(o.has(t)&&null!==r&&"object"==typeof r&&(r=c(r,!0,t)),e[t]=r,o.has(t)&&n&&t in n&&(n[t]=r),this.walkDom(this.root,this.data,!1),!0),has:(e,t)=>t in e})},splitPScopeStatements(e){const t=[];let r="",s=0,a=0,o=!1,n=null;for(let i=0;i<e.length;i++){const c=e[i],l=i>0?e[i-1]:null;'"'!==c&&"'"!==c&&"`"!==c||"\\"===l||(o?n===c&&(o=!1,n=null):(o=!0,n=c)),o||("{"===c?s++:"}"===c?s--:"("===c?a++:")"===c&&a--),";"!==c||o||0!==s||0!==a?r+=c:(r.trim()&&t.push(r.trim()),r="")}return r.trim()&&t.push(r.trim()),t},getPScopeOutputVars(e){const t=[];return this.splitPScopeStatements(e).forEach(e=>{const r=e.match(/^\[([^\]]+)\]\s*=\s*(.+)$/);if(r){const e=r[1].split(",").map(e=>e.trim());return void t.push(...e)}const s=e.match(/^\{([^}]+)\}\s*=\s*(.+)$/);if(s){const e=s[1].split(",").map(e=>e.trim());return void t.push(...e)}const a=e.match(/^(\w+)\s*=\s*(.+)$/);a&&t.push(a[1])}),t},initScope(e,t){const r=e.getAttribute("p-id")||this.generateDomPathId(e),s=t._p_target._p_children[r],a=e.getAttribute("p-scope"),o=Array.from(e.attributes).find(e=>e.name.startsWith("p-scope")&&e.name.includes("sync"));let n=[],i=[];if(a&&n.push(a),o){const t=e.getAttribute(o.name);n.push(t),i=this.getPScopeOutputVars(t)}const c=n.join("; "),l=this.observe(s,t,i);i.length>0&&(e._p_syncVars=new Set(i)),this.executePScopeStatements(l,c);const p=Object.getPrototypeOf(l._p_target);e._parentSnapshot={};for(let t in p)t.startsWith("_p_")||(e._parentSnapshot[t]=p[t]);const d=l._p_target;e._localSnapshot={};for(let t of Object.keys(d))t.startsWith("_p_")||(e._localSnapshot[t]=d[t]);return l},refreshScope(e,t){let r=e._scope;if(!r)return t;const s=e.getAttribute("p-scope"),a=Array.from(e.attributes).find(e=>e.name.startsWith("p-scope")&&e.name.includes("sync"));let o=[];s&&o.push(s),a&&o.push(e.getAttribute(a.name));const n=o.join("; ");return n&&r._p_target&&this.updateScopeFromParent(e,r,n),r},executePScopeStatements(scope,pScopeExpr){const statements=this.splitPScopeStatements(pScopeExpr),target=scope._p_target,sequentialScope=new Proxy(target,{get:(e,t)=>Object.prototype.hasOwnProperty.call(e,t)?e[t]:scope[t],has:(e,t)=>Object.prototype.hasOwnProperty.call(e,t)||t in scope});for(const stmt of statements){const arrayDestructMatch=stmt.match(/^\[([^\]]+)\]\s*=\s*(.+)$/);if(arrayDestructMatch){const[,vars,expr]=arrayDestructMatch,varNames=vars.split(",").map(e=>e.trim());try{const value=eval(`with (sequentialScope) { (${expr}) }`);Array.isArray(value)&&varNames.forEach((e,t)=>{target[e]=value[t]})}catch(e){console.error(`Error executing p-scope array destructuring "${stmt}":`,e)}continue}const objDestructMatch=stmt.match(/^\{([^}]+)\}\s*=\s*(.+)$/);if(objDestructMatch){const[,vars,expr]=objDestructMatch,varNames=vars.split(",").map(e=>e.trim());try{const value=eval(`with (sequentialScope) { (${expr}) }`);"object"==typeof value&&null!==value&&varNames.forEach(e=>{target[e]=value[e]})}catch(e){console.error(`Error executing p-scope object destructuring "${stmt}":`,e)}continue}const match=stmt.match(/^(\w+)\s*=\s*(.+)$/);if(match){const[,varName,expr]=match;try{const value=eval(`with (sequentialScope) { (${expr}) }`);target[varName]=value}catch(e){console.error(`Error executing p-scope statement "${stmt}":`,e)}}}},updateScopeFromParent(el,scope,pScopeExpr){const parentProto=Object.getPrototypeOf(scope._p_target),target=scope._p_target,changedParentVars=new Set,changedLocalVars=new Set;el._parentSnapshot||(el._parentSnapshot={});for(let e in parentProto)e.startsWith("_p_")||(el._parentSnapshot[e]!==parentProto[e]&&changedParentVars.add(e),el._parentSnapshot[e]=parentProto[e]);el._localSnapshot||(el._localSnapshot={});for(let e of Object.keys(target))e.startsWith("_p_")||(el._localSnapshot[e]!==target[e]&&changedLocalVars.add(e),el._localSnapshot[e]=target[e]);const allChangedVars=new Set([...changedParentVars,...changedLocalVars]);if(0!==allChangedVars.size)try{const statements=this.splitPScopeStatements(pScopeExpr),outputVars=new Set;statements.forEach(e=>{const t=e.match(/^\[([^\]]+)\]\s*=\s*(.+)$/);if(t){return void t[1].split(",").map(e=>e.trim()).forEach(e=>outputVars.add(e))}const r=e.match(/^\{([^}]+)\}\s*=\s*(.+)$/);if(r){return void r[1].split(",").map(e=>e.trim()).forEach(e=>outputVars.add(e))}const s=e.match(/^(\w+)\s*=\s*(.+)$/);s&&outputVars.add(s[1])});const setInThisPass=new Set,sequentialScope=new Proxy(target,{get:(e,t)=>"_p_target"===t||"_p_children"===t||"_p_scope"===t||setInThisPass.has(t)?e[t]:changedParentVars.has(t)?parentProto[t]:changedLocalVars.has(t)||Object.prototype.hasOwnProperty.call(e,t)?e[t]:parentProto[t],set:(e,t,r)=>(e[t]=r,!0),has:(e,t)=>setInThisPass.has(t)||Object.prototype.hasOwnProperty.call(e,t)||t in parentProto});statements.forEach(stmt=>{let shouldExecute=!1;const parts=stmt.split("=");if(parts.length<=1)return;const rhs=parts.slice(1).join("=");if(changedParentVars.forEach(e=>{rhs.includes(e)&&(shouldExecute=!0)}),changedLocalVars.forEach(e=>{!outputVars.has(e)&&rhs.includes(e)&&(shouldExecute=!0)}),shouldExecute||setInThisPass.forEach(e=>{rhs.includes(e)&&(shouldExecute=!0)}),shouldExecute){const arrayMatch=stmt.match(/^\[([^\]]+)\]\s*=\s*(.+)$/);if(arrayMatch){const[,vars,expr]=arrayMatch,varNames=vars.split(",").map(e=>e.trim()),value=eval(`with (sequentialScope) { (${expr}) }`);return void(Array.isArray(value)&&varNames.forEach((e,t)=>{target[e]=value[t],setInThisPass.add(e)}))}const objMatch=stmt.match(/^\{([^}]+)\}\s*=\s*(.+)$/);if(objMatch){const[,vars,expr]=objMatch,varNames=vars.split(",").map(e=>e.trim()),value=eval(`with (sequentialScope) { (${expr}) }`);return void("object"==typeof value&&null!==value&&varNames.forEach(e=>{target[e]=value[e],setInThisPass.add(e)}))}const match=stmt.match(/^(\w+)\s*=\s*(.+)$/);if(match){const[,varName,expr]=match,value=eval(`with (sequentialScope) { (${expr}) }`);target[varName]=value,setInThisPass.add(varName)}}});for(let e of Object.keys(target))e.startsWith("_p_")||(el._localSnapshot[e]=target[e])}catch(e){console.error("Error re-executing p-scope expression:",e)}},registerEventListeners(el){Array.from(el.attributes).forEach(attr=>{if(attr.name.startsWith("p-on:")){const event=attr.name.replace("p-on:","");el.addEventListener(event,()=>{eval(`with (el._scope) { ${attr.value} }`),this.walkDom(this.root,this.data,!1),this.refreshAllLoops()})}})},registerModelBinding(el){const modelAttr=el.getAttribute("p-model");modelAttr&&(el.addEventListener("input",e=>{let value;const type=e.target.type;if("number"===type||"range"===type)value=""===e.target.value?null:Number(e.target.value);else if("checkbox"===type)value=e.target.checked;else if("radio"===type)value=e.target.checked?e.target.value:void 0;else{const currentValue=eval(`with (el._scope) { ${modelAttr} }`);value=Array.isArray(currentValue)?e.target.value.split(",").map(e=>e.trim()).filter(e=>e):e.target.value}if(void 0!==value){const scope=el._scope;if(modelAttr.includes("[")){const lastBracket=modelAttr.lastIndexOf("["),containerExpr=modelAttr.substring(0,lastBracket),keyExpr=modelAttr.substring(lastBracket+1,modelAttr.length-1),container=eval(`with (scope) { (${containerExpr}) }`),key=eval(`with (scope) { (${keyExpr}) }`);container[key]=value}else if(modelAttr.includes(".")){const e=modelAttr.split(".");let t=scope;for(let r=0;r<e.length-1;r++)t=t[e[r]];t[e[e.length-1]]=value}else scope[modelAttr]=value;let pForKey=el.getAttribute("p-for-key"),container=el;if(!pForKey)for(container=el.parentElement;container&&!pForKey;)pForKey=container.getAttribute("p-for-key"),pForKey||(container=container.parentElement);const modelAttrValue=el.getAttribute("p-model"),inputTagName=el.tagName,selStart=el.selectionStart,selEnd=el.selectionEnd;this.walkDom(this.root,this.data,!1),pForKey&&modelAttrValue&&requestAnimationFrame(()=>{const e=document.querySelector(`[p-for-key="${pForKey}"]`);if(e){const t=e.querySelector(`${inputTagName.toLowerCase()}[p-model="${modelAttrValue}"]`);if(t&&(t.focus(),null!==selStart&&null!==selEnd))try{t.setSelectionRange(selStart,selEnd)}catch(e){}}})}}),"checkbox"!==el.type&&"radio"!==el.type||el.addEventListener("change",e=>{let value;if(value="checkbox"===el.type?e.target.checked:e.target.checked?e.target.value:void 0,void 0!==value){const scope=el._scope;if(modelAttr.includes("[")){const lastBracket=modelAttr.lastIndexOf("["),containerExpr=modelAttr.substring(0,lastBracket),keyExpr=modelAttr.substring(lastBracket+1,modelAttr.length-1),container=eval(`with (scope) { (${containerExpr}) }`),key=eval(`with (scope) { (${keyExpr}) }`);container[key]=value}else scope[modelAttr]=value}}))},evaluateDirectives(el,scope){Array.from(el.attributes).forEach(attr=>{const parsed=this.parseDirectiveModifiers(attr.name);if(Object.keys(this.directives).includes(parsed.directive)){const evalScope=el._scope||scope,value=eval(`with (evalScope) { (${attr.value}) }`);this.directives[parsed.directive](el,value,parsed.modifiers)}})},setForTemplateRecursive(e,t){e._forTemplate=t,Array.from(e.children).forEach(e=>{this.setForTemplateRecursive(e,t)})},refreshAllLoops(e=this.root,t=new Set){if("TEMPLATE"===e.tagName&&e.hasAttribute("p-for")){if(t.has(e))return;return t.add(e),void this.handleFor(e,e._scope||this.data,!1)}let r=e.firstElementChild;for(;r;){const e=r.nextElementSibling;this.refreshAllLoops(r,t),r=e}},handleFor(e,t,r){const s=e.getAttribute("p-for"),a=s.match(/^(?:const|let)?\s*(.+?)\s+(of|in)\s+(.+)$/);if(!a)return void console.error(`Invalid p-for expression: ${s}`);const[,o,n,i]=a;r?this.hydrateLoop(e,t,o,i):this.refreshLoop(e,t,o,i)},getTemplateScopePrefix(e){let t="",r=e.nextElementSibling;if(r&&r.hasAttribute&&r.hasAttribute("p-for-key")){const s=r.getAttribute("p-for-key"),a=s.lastIndexOf(":");if(a>0){const r=s.substring(0,a+1),o=r.lastIndexOf("-");if(o>=0){t=r.substring(0,o+1);const s=r.substring(o+1,r.length-1);e._forScopeId=s}else e._forScopeId=r.substring(0,r.length-1);return e.setAttribute("data-p-for-scope-id",e._forScopeId),r}}if(e._forTemplate){const r=e._forTemplate._forData;if(r&&r.scopePrefix){let r=e.previousElementSibling;for(;r;){if(r.hasAttribute&&r.hasAttribute("p-for-key")){t=r.getAttribute("p-for-key")+"-";break}r=r.previousElementSibling}}}if(!t){let r=e.previousElementSibling;for(;r;){if(r.hasAttribute&&r.hasAttribute("p-for-key")&&r._forTemplate===e._forTemplate){t=r.getAttribute("p-for-key")+"-";break}r=r.previousElementSibling}}if(!t){let r=e.parentElement;for(;r;){if(r.hasAttribute&&r.hasAttribute("p-for-key")){t=r.getAttribute("p-for-key")+"-";break}if(r._forTemplate){const e=r._forTemplate._forData;if(e){const s=e.renderedElements.indexOf(r);if(s>=0){t=(e.scopePrefix||"")+s+"-";break}}}r=r.parentElement}}return e._forScopeId||(e.hasAttribute("data-p-for-scope-id")?e._forScopeId=e.getAttribute("data-p-for-scope-id"):(e._forScopeId="s"+this._templateScopeCounter++,e.setAttribute("data-p-for-scope-id",e._forScopeId))),t+e._forScopeId+":"},enrichTemplateContent(e,t){const r=Array.from(t.children).filter(e=>!e.hasAttribute("p-for-key")),s=Array.from(e.children);let a=0;for(const t of r)if("TEMPLATE"===t.tagName&&t.hasAttribute("p-for")){const r=s[a];let o;if(r&&"TEMPLATE"===r.tagName&&r.getAttribute("p-for")===t.getAttribute("p-for"))o=r;else{const n=t.cloneNode(!0);e.insertBefore(n,r||null),s.splice(a,0,n),o=n}if(o&&!o.hasAttribute("data-p-for-scope-id")){const e=t.nextElementSibling;if(e&&e.hasAttribute("p-for-key")){const t=e.getAttribute("p-for-key"),r=t.lastIndexOf(":");if(r>0){const e=t.substring(0,r),s=e.lastIndexOf("-"),a=s>=0?e.substring(s+1):e;o.setAttribute("data-p-for-scope-id",a)}}else o.setAttribute("data-p-for-scope-id","s"+this._templateScopeCounter++)}a++}else a<s.length&&s[a].tagName===t.tagName&&(this.enrichTemplateContent(s[a],t),a++)},hydrateLoop(template,parentScope,varPattern,iterableExpr){template._scope=parentScope;try{const iterable=eval(`with (parentScope) { (${iterableExpr}) }`),scopePrefix=this.getTemplateScopePrefix(template);template._forData={varPattern:varPattern,iterableExpr:iterableExpr,renderedElements:[],scopePrefix:scopePrefix};const existingElementsByKey={};let sibling=template.nextElementSibling;for(;sibling&&sibling.hasAttribute("p-for-key");){const e=sibling.getAttribute("p-for-key"),t=e.startsWith(scopePrefix)||!e.includes(":")&&!e.includes("-");if(t){let t;t=e.startsWith(scopePrefix)?e.substring(scopePrefix.length):e,existingElementsByKey[t]||(existingElementsByKey[t]=[]),existingElementsByKey[t].push(sibling)}sibling=sibling.nextElementSibling}const templateRoot=template.content.firstElementChild;if(templateRoot)for(const e of Object.values(existingElementsByKey))for(const t of e)t.querySelector&&t.querySelector("template[p-for]")&&this.enrichTemplateContent(templateRoot,t);let index=0,lastInserted=template;for(const e of iterable){const t=this.createLoopScope(parentScope,varPattern,e);if(existingElementsByKey[String(index)])existingElementsByKey[String(index)].forEach(e=>{e._scope=t,this.setForTemplateRecursive(e,template),"TEMPLATE"!==e.tagName&&e.setAttribute("p-for-key",scopePrefix+index),this.walkDom(e,t,!0),e._p_loopHydrated=!0,template._forData.renderedElements.push(e),lastInserted=e});else{const e=template.content.cloneNode(!0),r=Array.from(e.children);r.forEach(e=>{e._scope=t,this.setForTemplateRecursive(e,template),"TEMPLATE"!==e.tagName&&e.setAttribute("p-for-key",scopePrefix+index),this.walkDom(e,t,!0)});const s=document.createDocumentFragment();r.forEach(e=>s.appendChild(e)),lastInserted.parentNode.insertBefore(s,lastInserted.nextSibling),template._forData.renderedElements.push(...r),lastInserted=r[r.length-1]||lastInserted}index++}Object.keys(existingElementsByKey).forEach(e=>{parseInt(e)>=index&&existingElementsByKey[e].forEach(e=>e.remove())})}catch(e){console.error(`Error in p-for hydration: ${iterableExpr}`,e)}},removeLoopElements(e){e.forEach(e=>{if(e._forData&&e._forData.renderedElements&&(this.removeLoopElements(e._forData.renderedElements),e._forData.renderedElements=[]),e.querySelectorAll){e.querySelectorAll("template[p-for]").forEach(e=>{e._forData&&e._forData.renderedElements&&(this.removeLoopElements(e._forData.renderedElements),e._forData.renderedElements=[])})}e.remove()})},refreshLoop(template,parentScope,varPattern,iterableExpr){const forData=template._forData;if(forData)try{const iterable=eval(`with (parentScope._p_target || parentScope) { (${iterableExpr}) }`),scopePrefix=forData.scopePrefix||this.getTemplateScopePrefix(template);this.removeLoopElements(forData.renderedElements),forData.renderedElements=[];let lastInsertedElement=template,index=0;for(const e of iterable){const t=template.content.cloneNode(!0),r=this.createLoopScope(parentScope,forData.varPattern,e),s=Array.from(t.children);s.forEach(e=>{e._scope=r,this.setForTemplateRecursive(e,template),"TEMPLATE"!==e.tagName&&e.setAttribute("p-for-key",scopePrefix+index)});const a=document.createDocumentFragment();s.forEach(e=>a.appendChild(e)),lastInsertedElement.parentNode.insertBefore(a,lastInsertedElement.nextSibling),forData.renderedElements.push(...s),s.forEach(e=>{this.walkDom(e,r,!0)});const o=s[s.length-1];o&&(lastInsertedElement=this.findLastRenderedSibling(o)),index++}}catch(e){console.error(`Error in p-for refresh: ${iterableExpr}`,e)}},findLastRenderedSibling(e){if("TEMPLATE"===e.tagName&&e._forData&&e._forData.renderedElements.length>0){const t=e._forData.renderedElements[e._forData.renderedElements.length-1];return this.findLastRenderedSibling(t)}return e},createLoopScope(e,t,r){const s={};if((t=t.trim()).startsWith("[")){const e=t.slice(1,-1).split(",").map(e=>e.trim());Array.isArray(r)?e.forEach((e,t)=>s[e]=r[t]):s[e[0]]=r}else if(t.startsWith("{")){t.slice(1,-1).split(",").map(e=>e.trim()).forEach(e=>{const[t,a]=e.split(":").map(e=>e.trim());s[a||t]=r[t]})}else s[t]=r;if(!e)return console.error("parentScope is undefined in createLoopScope"),new Proxy({},{get:()=>{},set:()=>!1});const a=e._p_target||e;if(!a||"object"!=typeof a)return console.error("Invalid parentTarget:",a),new Proxy(s,{get:(e,t)=>e[t],set:(e,t,r)=>(e[t]=r,!0)});const o=Object.create(a);Object.assign(o,s);const n=new Proxy(o,{get:(e,t)=>e[t],set:(e,t,r)=>(t in s?e[t]=r:a[t]=r,!0)});return n._p_target=o,n},walkDom(el,parentScope,isHydrating=!1){if(isHydrating&&el._p_loopHydrated)return;if("TEMPLATE"===el.tagName&&el.hasAttribute("p-for")){if(!isHydrating&&el._forTemplate)return;return void this.handleFor(el,parentScope,isHydrating)}const preScopeShowAttr=Array.from(el.attributes).find(e=>{const t=this.parseDirectiveModifiers(e.name);return"p-show"===t.directive&&t.modifiers["pre-scope"]});if(preScopeShowAttr&&parentScope){const value=eval(`with (parentScope) { (${preScopeShowAttr.value}) }`);this.directives["p-show"](el,value)}let currentScope=parentScope;const hasPScope=Array.from(el.attributes).some(e=>e.name.startsWith("p-scope"));if(hasPScope&&(currentScope=isHydrating?this.initScope(el,parentScope):this.refreshScope(el,parentScope)),isHydrating&&(el._scope=currentScope,this.registerEventListeners(el),this.registerModelBinding(el)),currentScope&&Array.from(el.attributes).forEach(attr=>{const parsed=this.parseDirectiveModifiers(attr.name);if(("p-show"!==parsed.directive||!parsed.modifiers["pre-scope"])&&Object.keys(this.directives).includes(parsed.directive)){const evalScope=el._scope||currentScope,value=eval(`with (evalScope) { (${attr.value}) }`);this.directives[parsed.directive](el,value,parsed.modifiers)}}),isHydrating&&currentScope)for(const attr of el.attributes){const parsed=this.parseDirectiveModifiers(attr.name);if("p-show"===parsed.directive&&!parsed.modifiers["pre-scope"]){try{if(!eval(`with (currentScope) { (${attr.value}) }`))return}catch(e){}break}}const children=Array.from(el.children);for(const e of children)this.walkDom(e,currentScope,isHydrating)}},window.Pattr.start();