pawa-ssr 1.3.17 → 1.3.18

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 (5) hide show
  1. package/index.js +110 -11
  2. package/package.json +1 -1
  3. package/pawaElement.js +11 -11
  4. package/power.js +277 -389
  5. package/utils.js +20 -1
package/index.js CHANGED
@@ -1,11 +1,11 @@
1
1
  import {getServerInstance, setServer} from 'pawajs/server.js'
2
2
  import { DOMParser,parseHTML, HTMLElement} from 'linkedom'
3
3
  import PawaComponent from './pawaComponent.js'
4
- import { propsValidator, evaluateExpr,extractAtExpressions, reArrangeAttri,resumeAttribute, pawaGenerateId, escapeHtml } from './utils.js'
4
+ import { propsValidator, evaluateExpr,extractAtExpressions, reArrangeAttri,resumeAttribute, pawaGenerateId, escapeHtml, splitAndAdd } from './utils.js'
5
5
  import {AsyncLocalStorage} from'node:async_hooks'
6
6
  import { If,For,State,Switch, Key } from'./power.js';
7
7
  import PawaElement from'./pawaElement.js'
8
- import { pluginsMap, lazyComponents } from "pawajs";
8
+ import { pluginsMap, lazyComponents } from "/pawajs";
9
9
 
10
10
  const PAWA_STORE_SYMBOL = Symbol.for('pawa.ssr.store');
11
11
 
@@ -84,6 +84,18 @@ const accessChild=()=>{
84
84
  }
85
85
  }
86
86
  }
87
+ const forwardProps=(props={})=>{
88
+ const appContext=store.getStore().stateContext
89
+ try {
90
+ if (appContext) {
91
+ appContext.restProps=Object.entries(props).length > 0 ? props : {bPAr:''}
92
+ }
93
+ } catch (error) {
94
+ if(isDevelopment){
95
+ console.log(error.message,'this is from component',error.stack)
96
+ }
97
+ }
98
+ }
87
99
  const useServer=()=>{
88
100
  const appContext = store.getStore().stateContext;
89
101
  try{
@@ -200,7 +212,8 @@ setServer({
200
212
  $state,
201
213
  accessChild,
202
214
  useServer,
203
- useAsync
215
+ useAsync,
216
+ forwardProps
204
217
  })
205
218
  export const pawaForServer=setServer
206
219
  const components = new Map();
@@ -231,7 +244,7 @@ const setPawaAttribute=(...attr)=>{
231
244
  pawaAttributes.add(att)
232
245
  })
233
246
  }
234
- setPawaAttribute('if','else','else-if','for-each','ref','key')
247
+ setPawaAttribute('if','else','else-if','for-each','case','switch','s-default','for-key','key')
235
248
  export const getPawaAttributes=()=>pawaAttributes
236
249
  /**
237
250
  * @typedef {{startsWith:string,fullName:string,plugin:(el:HTMLElement | PawaElement,attr:object)=>void}} AttriPlugin
@@ -348,6 +361,7 @@ const component=async (el,stream)=>{
348
361
  let appContext={
349
362
  transportContext: {},
350
363
  innerContext:el._context,
364
+ restProps:{},
351
365
  mount:[],
352
366
  formerContext:oldAppContext,
353
367
  name:el._componentName,
@@ -454,9 +468,47 @@ appContext.component._prop={children,...el._props,...slots}
454
468
  if(typeof compo !== 'boolean' && typeof compo === 'string'){
455
469
  div.innerHTML=compo
456
470
  }
471
+ const restProps={}
472
+ if (Object.entries(appContext.restProps).length > 0) {
473
+ const props=el._restProps
474
+ if (appContext.restProps['className'] && props['class']) {
475
+ restProps['class']={...props['class']}
476
+ }
477
+ if (appContext.restProps['defaultValue'] && props['default']) {
478
+ restProps['default']={...props['default']}
479
+ }
480
+ for (const key in props) {
481
+ let name=key
482
+ name=name.replace(/-([a-z])/g, (g) => g[1].toUpperCase());
483
+ if (appContext.restProps[name]) {
484
+ restProps[key]={name:key,value:props[key]}
485
+ }
486
+ }
487
+ }else{
488
+ Object.assign(restProps,el._restProps)
489
+ }
490
+ const getAsChild=()=>{
491
+ const asChild=div.firstElementChild
492
+ if (splitAndAdd(asChild?.tagName|| '') === 'ASCHILD') {
493
+ const getChildren=asChild.firstElementChild
494
+ if (getChildren === null) return
495
+ Array.from(asChild.attributes).forEach(attr=>{
496
+ if (getChildren.hasAttribute(attr.name)) {
497
+ let attrName=getChildren.getAttribute(attr.name)
498
+ attrName=attr.value +' '+attrName
499
+ getChildren.setAttribute(attr.name, attrName)
500
+ }else{
501
+ getChildren.setAttribute(attr.name, attr.value)
502
+ }
503
+ })
504
+ asChild.remove()
505
+ div.appendChild(getChildren)
506
+ }
507
+ }
508
+ getAsChild()
457
509
  const findElement=div.querySelector('[--]') || div.querySelector('[r-]')
458
510
  if (findElement) {
459
- for (const [key,value] of Object.entries(el?._restProps)) {
511
+ for (const [key,value] of Object.entries(restProps)) {
460
512
  findElement.setAttribute(value.name,value.value)
461
513
  }
462
514
  findElement.removeAttribute('--')
@@ -539,6 +591,7 @@ const streamingComponent=async (el,stream)=>{
539
591
  transportContext: {},
540
592
  innerContext:el._context,
541
593
  mount:[],
594
+ restProps:{},
542
595
  formerContext:oldAppContext,
543
596
  name:el._componentName,
544
597
  insert:{},
@@ -645,6 +698,44 @@ appContext.component._prop={children,...el._props,...slots}
645
698
  },
646
699
  slots:{...slotHydrates},
647
700
  }
701
+ const restProps={}
702
+ if (Object.entries(appContext.restProps).length > 0) {
703
+ const props=el._restProps
704
+
705
+ if (appContext.restProps['className'] && props['class']) {
706
+ restProps['class']={...props['class']}
707
+ }
708
+ if (appContext.restProps['defaultValue'] && props['default']) {
709
+ restProps['default']={...props['default']}
710
+ }
711
+ for (const key in props) {
712
+ let name=key
713
+ name=name.replace(/-([a-z])/g, (g) => g[1].toUpperCase());
714
+ if (appContext.restProps[name]) {
715
+ restProps[key]={...props[key]}
716
+ }
717
+ }
718
+ }else{
719
+ Object.assign(restProps,el._restProps)
720
+ }
721
+ const getAsChild=(divs)=>{
722
+ const asChild=divs.firstElementChild
723
+ if (splitAndAdd(asChild?.tagName|| '') === 'ASCHILD') {
724
+ const getChildren=asChild.firstElementChild
725
+ if (getChildren === null) return
726
+ Array.from(asChild.attributes).forEach(attr=>{
727
+ if (getChildren.hasAttribute(attr.name)) {
728
+ let attrName=getChildren.getAttribute(attr.name)
729
+ attrName=attr.value +' '+attrName
730
+ getChildren.setAttribute(attr.name, attrName)
731
+ }else{
732
+ getChildren.setAttribute(attr.name, attr.value)
733
+ }
734
+ })
735
+ asChild.remove()
736
+ divs.appendChild(getChildren)
737
+ }
738
+ }
648
739
  if (isBoundary) {
649
740
  store.getStore().batch.push({
650
741
  component:compo,
@@ -656,8 +747,9 @@ appContext.component._prop={children,...el._props,...slots}
656
747
  },
657
748
  appContext:appContext,
658
749
  context:{...el._context},
659
- restProps:el._restProps,
660
- hydrate:hydrate
750
+ restProps:restProps,
751
+ hydrate:hydrate,
752
+ getAsChild:getAsChild
661
753
  })
662
754
 
663
755
  }
@@ -666,10 +758,12 @@ appContext.component._prop={children,...el._props,...slots}
666
758
  }
667
759
  if(typeof compo !== 'boolean' && typeof compo === 'string'){
668
760
  div.innerHTML=compo
669
- }
761
+ }
762
+
763
+ getAsChild(div)
670
764
  const findElement=div.querySelector('[--]') || div.querySelector('[r-]')
671
765
  if (findElement) {
672
- for (const [key,value] of Object.entries(el?._restProps)) {
766
+ for (const [key,value] of Object.entries(restProps)) {
673
767
  findElement.setAttribute(value.name,value.value)
674
768
  }
675
769
  findElement.removeAttribute('--')
@@ -837,6 +931,9 @@ const attributeHandler =async (el, attr) => {
837
931
  if (el._running) {
838
932
  return;
839
933
  }
934
+ if (el._componentName) {
935
+ return
936
+ }
840
937
  el._replaceResumeAttr(attr.name,`c-at-${attr.name}`,attr.value)
841
938
  const currentHtmlString = el.outerHTML;
842
939
  const removableAttributes = new Set();
@@ -970,13 +1067,14 @@ export const render =async (el, contexts = {},stream) => {
970
1067
  console.error(error.message,error.stack)
971
1068
  }
972
1069
  }
1070
+ const isAcomponent=el._componentName?true:false
973
1071
  if (!el._avoidPawaRender) {
974
1072
 
975
1073
  const attributes = Array.from(el.attributes);
976
1074
  for(const attr of attributes){
977
1075
  if (directives[attr.name]) {
978
1076
  await directives[attr.name](el,attr,stream)
979
- }else if(attr.value.includes('@{')){
1077
+ }else if(attr.value.includes('@{') && !isAcomponent){
980
1078
  await attributeHandler(el,attr)
981
1079
  }else if (attr.name.startsWith('state-')) {
982
1080
  directives['state-'](el,attr)
@@ -1213,7 +1311,7 @@ const resolvesAsync=async({component,
1213
1311
  encodeJSON,
1214
1312
  name
1215
1313
  },
1216
- appContext,context,restProps,hydrate},root,stream,index)=>{
1314
+ appContext,context,restProps,hydrate,getAsChild},root,stream,index)=>{
1217
1315
  let chunk=''
1218
1316
  const bufferStream=(string)=>{
1219
1317
  chunk+=string
@@ -1226,6 +1324,7 @@ const resolvesAsync=async({component,
1226
1324
  if(typeof compo !== 'boolean' && compo){
1227
1325
  div.innerHTML=compo
1228
1326
  }
1327
+ getAsChild(div)
1229
1328
  const findElement=div.querySelector('[--]') || div.querySelector('[r-]')
1230
1329
  if (findElement) {
1231
1330
  for (const [key,value] of Object.entries(restProps)) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pawa-ssr",
3
- "version": "1.3.17",
3
+ "version": "1.3.18",
4
4
  "type":"module",
5
5
  "description": "pawajs ssr libary",
6
6
  "main": "index.js",
package/pawaElement.js CHANGED
@@ -186,7 +186,10 @@ class PawaElement {
186
186
  if (this._componentName) {
187
187
  const allServerAttr=getPawaAttributes()
188
188
  this._el.attributes.forEach(attr=>{
189
- if(!allServerAttr.has(attr.name) ){
189
+ if(!allServerAttr.has(attr.name)){
190
+ if (attr.name === 'svg') {
191
+ return
192
+ }
190
193
  if ( !attr.name.startsWith(':')) {
191
194
  if( attr.name.startsWith('c-') || attr.name.startsWith('p:c') || attr.name.startsWith('state-')) return
192
195
  let name=''
@@ -201,24 +204,21 @@ class PawaElement {
201
204
  }else if(name === 'default'){
202
205
  hydatename=':'+'defaultValue'
203
206
  }else{
204
- hydatename=':'+name.replace(/-([a-z])/g, (g) => g[1].toUpperCase());
207
+ hydatename=':'+name
205
208
  }
206
209
  this._hydrateProps[hydatename]=attr.value
207
210
  const setProps=()=>{
208
- delete this._restProps[name]
209
211
  let value=attr.value
210
212
  if (value.includes('@{')) {
211
213
  const regex = /@{([^}]*)}/g;
212
214
  value = value.replace(regex, (match, expression) => {
213
- if (checkKeywordsExistence(this._staticContext, expression)) {
214
- return value
215
- } else {
215
+
216
216
  const res = this.evaluateExpr(expression,this._context,`evaluating props with template operators at ${attr.name} - ${attr.value} : ${this._template}`)
217
217
  return res
218
- }
218
+
219
219
  });
220
220
  return value
221
- }else if( attr.name.startsWith('on-') || attr.name.startsWith('out-')){
221
+ }else if( attr.name.startsWith('on-') || attr.name.startsWith('out-') || attr.name === 'ref'){
222
222
  const res=this.evaluateExpr(`(e)=>{
223
223
  ${attr.value}
224
224
  }`, this._context,`evaluating props with template operators at ${attr.name} - ${attr.value} : ${this._template}`)
@@ -226,8 +226,10 @@ class PawaElement {
226
226
  }
227
227
  return attr.value
228
228
  }
229
+
230
+ this._restProps[name]={name:name,value:attr.value}
229
231
  name=name.replace(/-([a-z])/g, (g) => g[1].toUpperCase());
230
- if (this._props[name] || this._props.className || this._props.defaultValue) return
232
+ if (this._props[name] || name ==='class' && this._props?.className || name ==='default' && this._props.defaultValue) return
231
233
  if (name === 'class') {
232
234
  this._props['className']=setProps
233
235
  }else if(name === 'default'){
@@ -235,8 +237,6 @@ class PawaElement {
235
237
  }else{
236
238
  this._props[name]=setProps
237
239
  }
238
-
239
- this._restProps[name]={name:name,value:attr.value}
240
240
 
241
241
  } else if(attr.name.startsWith(':')) {
242
242
  this._hydrateProps[attr.name.slice(1)]=attr.value
package/power.js CHANGED
@@ -1,410 +1,298 @@
1
- import {render} from "./index.js";
2
- import { convertToNumber,evaluateExpr, processNode, reArrangeAttri, pawaGenerateId } from "./utils.js";
3
- import {parseHTML} from "linkedom"
4
- export const If = async(el, attr,stream) => {
5
- if (el._running) return;
6
- el._running = true;
1
+ import { HTMLElement, parseHTML } from "linkedom"
2
+ import {getPawaAttributes, getDevelopment } from "./index.js"
3
+ import {components,lazyComponents} from '/pawajs'
4
+ import PawaComponent from "./pawaComponent.js"
5
+ import { evaluateExpr, splitAndAdd,replaceTemplateOperators } from "./utils.js"
7
6
 
8
- const nextSiblings = el.nextElementSibling || null;
9
- const chained=[{
10
- exp:el.getAttribute('if'),
11
- condition:'if',
12
- element:el
13
- }]
14
- const chainMap=new Map()
15
- chainMap.set(el.getAttribute('if'),{condition:'if',element:el})
16
- const getChained = (sibling) => {
17
- while (sibling) {
18
- const next = sibling.nextElementSibling;
19
- const isElseIf = sibling.hasAttribute('else-if');
20
- const isElse = sibling.hasAttribute('else');
21
-
22
- if (isElseIf) {
23
- const exp = sibling.getAttribute('else-if');
24
- chained.push({ exp, condition: 'else-if', element: sibling });
25
- chainMap.set(exp, { condition: 'else-if', element: sibling });
26
- sibling.remove();
27
- } else if (isElse) {
28
- chained.push({ exp: 'false', condition: 'else', element: sibling });
29
- chainMap.set('else', { condition: 'else', element: sibling });
30
- sibling.remove();
31
- } else {
32
- break;
33
- }
34
- sibling = next;
7
+ const expressionCache = new Map();
8
+ // changed the hydrateProps
9
+
10
+ class PawaElement {
11
+ /**
12
+ *
13
+ * @param {HTMLElement} element
14
+ * @param {object} context
15
+ */
16
+ constructor(element,context) {
17
+ const {document}=parseHTML()
18
+ /**
19
+ * @type{PawaElement|HTMLElement}
20
+ */
21
+ this._el=element
22
+ this._slots=document.createDocumentFragment()
23
+ this._context=context
24
+ this._avoidPawaRender=element.hasAttribute('s-pawa-avoid')
25
+ this._client=element.hasAttribute('client')
26
+ this._props={}
27
+ this._template=element.outerHTML
28
+ this._component=null
29
+ this._componentName=''
30
+ this._thisSame=element.hasAttribute('pawa-same')
31
+ this._arrangeAttribute={}
32
+ this._pawaAlready=element.hasAttribute('p:c')
33
+ /**@type {Array<{message:string,stack:string}>} */
34
+ this._error=[]
35
+ this._lazy=false
36
+ this._running=false
37
+ this._hasForOrIf=this.hasForOrIf
38
+ this._createError=this.createError
39
+ this._setError=this.setError
40
+ this._hydrateProps={}
41
+ this._resumeAttr=''
42
+ this._evaluateExpr=this.evaluateExpr
43
+ this._reArrangeAttri=this.reArrange
44
+ this._replaceResumeAttr=this.replaceResumeAttr
45
+ this._setResumeAttr=this.setResumeAttr
46
+ if(this._avoidPawaRender){
47
+ element.removeAttribute('s-pawa-avoid')
48
+ Array.from(element.children).forEach((child) => {
49
+ if (child.nodeType === 1) {
50
+ child.setAttribute('s-pawa-avoid','')
35
51
  }
52
+ })
36
53
  }
37
- getChained(nextSiblings)
38
- let func=new Map()
39
- let current
40
- let latestChain
41
- chained.forEach((item)=>{
42
- if(item.condition === 'else' || current)return
43
- const result = el._evaluateExpr(item.exp, el._context,`at condition directives check - ${item.exp} at ${item.element.toString()}`);
44
- current=result
45
- if (current) {
46
- latestChain={
47
- id:item.exp,
48
- condition:item.condition
49
- }
50
- }else{
51
- latestChain={
52
- id:'else',
53
- condition:'else'
54
- }
55
- }
56
-
57
- })
58
- const document=el.ownerDocument
59
- const id=pawaGenerateId(10)
60
- const comment=document.createComment(`condition(${latestChain.id})@-$@-$@${id}`)
61
- const endComment=document.createComment(`end condition(${latestChain.id})@-$@-$@${id}`)
62
- el.replaceWith(endComment);
63
- let stringHtml=''
64
- const template=document.createElement('template')
65
- const store=document.createElement('template')
66
- chained.forEach((item) => {
67
- const clone = item.element.cloneNode(true);
68
- clone._avoidPawaRender = true;
69
- Array.from(clone.attributes).forEach(at => {
70
- if (at.name.startsWith('c-') || at.name === 'p:c') {
71
- clone.removeAttribute(at.name);
72
- }
73
- });
74
- store.appendChild(clone);
75
- template.appendChild(item.element);
76
- });
77
- const getRightElement=chainMap.get(latestChain.id)
78
- if (getRightElement) {
79
- const copyElement=getRightElement.element.cloneNode(true)
80
- copyElement.attributes.forEach((att)=>{
81
- if(att.name.startsWith('c-')){
82
- copyElement.removeAttribute(att.name)
54
+ /**
55
+ * @typedef{object}
56
+ * @property{any}
57
+ * Object of Html Attributes for Rest Attributes
58
+ */
59
+ this._restProps={}
60
+ this._componentChildren=null
61
+ this.getComponent()
62
+ this.setProps()
63
+ this.pawaAttribute()
64
+ }
65
+ /**
66
+ *
67
+ * @param {HTMLElement} el
68
+ * @param {object} context
69
+ * @returns {PawaElement}
70
+ */
71
+ static Element(el,context){
72
+ const pawa=new PawaElement(el,context)
73
+ Object.assign(el,pawa)
74
+ return el
75
+ }
76
+ attributes(){
77
+ this._el.attributes.forEach((value, index,) => {
78
+ this._arrangeAttribute[value.name]=value.value
79
+ })
80
+ }
81
+ checkLazy(){
82
+ if (lazyComponents.has(splitAndAdd(this._el.tagName))) {
83
+ this._lazy=true
83
84
  }
84
- })
85
- const dataComment=document.createComment(stringHtml)
86
- let newElement = el.cloneNode(true);
87
- if (attr.value === latestChain.id) {
88
- el._replaceResumeAttr('if',`c-if-${id}`,latestChain.id)
89
- newElement = el.cloneNode(true);
90
- }else{
91
- el._replaceResumeAttr('if',`c-if-${id}`,latestChain.id,copyElement)
92
- newElement = copyElement;
93
85
  }
94
- newElement.removeAttribute(latestChain.condition)
95
- newElement.setAttribute('pawa-same',true)
96
- endComment.parentElement.insertBefore(comment,endComment)
97
- stream(`<!--${comment.data}-->`)
98
- store.setAttribute('p:store-if',id)
99
- store.setAttribute('p:store','')
100
- comment.parentElement.insertBefore(store,endComment)
101
- stream(store.outerHTML)
102
- comment.parentElement.insertBefore(newElement,endComment)
103
- await render(newElement, el._context,stream);
104
- stream(`<!--${endComment.data}-->`)
105
- } else {
106
-
107
- template.setAttribute('pawa-render',true)
108
- endComment.replaceWith(template);
109
- stream(`${template.outerHTML}`)
110
- // await render(template, el._context,stream);
111
- }
112
- };
113
- export const Switch = async(el, attr,stream) => {
114
- if (el._running) return;
115
- el._running = true;
116
-
117
- const nextSiblings = el.nextElementSibling || null;
118
- const cases =el.getAttribute('case')
119
- const chained=[{
120
- exp:el.getAttribute('case'),
121
- condition:'case',
122
- element:el
123
- }]
124
- const chainMap=new Map()
125
- chainMap.set(el.getAttribute('case'),{condition:'case',element:el})
126
- const getChained = (sibling) => {
127
- while (sibling) {
128
- const next = sibling.nextElementSibling;
129
- const isCase = sibling.hasAttribute('case') && !sibling.hasAttribute('switch');
130
- const isDefault = sibling.hasAttribute('s-default');
131
-
132
- if (isCase) {
133
- const exp = sibling.getAttribute('case');
134
- chained.push({ exp, condition: 'case', element: sibling });
135
- chainMap.set(exp, { condition: 'case', element: sibling });
136
- sibling.remove();
137
- } else if (isDefault) {
138
- chained.push({ exp: 'false', condition: 'default', element: sibling });
139
- chainMap.set('default', { condition: 'default', element: sibling });
140
- sibling.remove();
141
- } else {
142
- break;
143
- }
144
- sibling = next;
145
- }
86
+ setResumeAttr(name){
87
+ if(name.startsWith(':')) return
88
+ this._resumeAttr+=`${name};`
89
+ this._el.setAttribute('p:C',this._resumeAttr)
146
90
  }
147
- getChained(nextSiblings)
148
- let func=new Map()
149
- let current
150
- let latestChain
151
- const switchFunc=el._evaluateExpr(attr.value,el._context,`at switch directive ${attr.value}`)
152
- chained.forEach((item)=>{
153
- if(item.condition === 'default' || current)return
154
- const result = switchFunc === el._evaluateExpr(item.exp, el._context,`at condition directives check case - ${item.exp} ${item.element.toString()}`);
155
- current=result
156
- if (current || item.condition === 'default') {
157
- latestChain={
158
- id:item.exp,
159
- condition:item.condition
160
- }
161
- }else{
162
- latestChain={
163
- id:'default',
164
- condition:'default'
165
- }
166
- }
167
-
168
- })
169
- const document=el.ownerDocument
170
- const id=pawaGenerateId(10)
171
- const comment=document.createComment(`switch case (${latestChain.id})@-$@-$@${id}`)
172
- const endComment=document.createComment(`end switch(${latestChain.id})@-$@-$@${id}`)
173
- el.replaceWith(endComment);
174
- let stringHtml=''
175
- const template=document.createElement('template')
176
- const store=document.createElement('template')
177
- // let index=0
178
- chained.forEach((item) => {
179
- const clone = item.element.cloneNode(true);
180
- clone._avoidPawaRender = true;
181
- Array.from(clone.attributes).forEach(at => {
182
- if (at.name.startsWith('c-') || at.name === 'p:c') {
183
- clone.removeAttribute(at.name);
184
- }
185
- });
186
- store.appendChild(clone);
187
- template.appendChild(item.element);
188
- });
189
- el.removeAttribute('switch')
190
- const getRightElement=chainMap.get(latestChain.id)
191
- if (getRightElement && (current || latestChain.condition === 'default')) {
192
- const copyElement=getRightElement.element.cloneNode(true)
193
-
194
- copyElement.attributes.forEach((att)=>{
195
- if(att.name.startsWith('c-')){
196
- copyElement.removeAttribute(att.name)
91
+ pawaAttribute(){
92
+ const pawaAttr=getPawaAttributes()
93
+ const setTextResume=()=>{
94
+ if( this._componentName === ''&& this._el.firstElementChild === null && this._el.childNodes.some(node=>node.nodeType === 3 && node.nodeValue.includes('@{')) && !this._avoidPawaRender){
95
+ return this._resumeAttr+='c-t'
96
+ }
197
97
  }
198
- })
199
- let newElement = el.cloneNode(true);
200
- if (cases === latestChain.id) {
201
- el._replaceResumeAttr(latestChain.condition,`c-sw-${id}`,latestChain.id)
202
- newElement = el.cloneNode(true);
98
+ if (this._thisSame) {
99
+ this._resumeAttr=this._el.getAttribute('p:c')
100
+ this._el.removeAttribute('pawa-same')
101
+ }else {
102
+ if (this._el.hasAttribute('p:c')) {
103
+ this._resumeAttr=this._el.getAttribute('p:c')
104
+ this._el.attributes.forEach((value, index, array) => {
105
+ if(this._resumeAttr.includes(value.name) || value.name === 'p:c')return
106
+ if (value.name.startsWith(':')) return
107
+ this._resumeAttr+=`${value.name};`
108
+ })
109
+ }else{
110
+ this._el.attributes.forEach((value, index, array) => {
111
+ if(this._resumeAttr.includes(value.name) || value.name === 'p:c' )return
112
+ if (value.name.startsWith(':')) return
113
+ this._resumeAttr+=`${value.name};`
114
+ })
115
+ }
116
+ setTextResume()
117
+ }
118
+ this._el.setAttribute('p:c',this._resumeAttr)
119
+ }
120
+ replaceResumeAttr(name,newName,value,ele){
121
+ if (ele === undefined) {
122
+ this._el.setAttribute(newName,value)
123
+ const array=this._resumeAttr.split(';')
124
+ const index=array.indexOf(name,0)
125
+ array[index]=newName
126
+ const toString=array.join(';')
127
+ this._el.setAttribute('p:c',toString)
128
+ this._resumeAttr=toString
203
129
  }else{
204
- el._replaceResumeAttr(latestChain.condition === 's-default'?'s-default':latestChain.condition ,`c-sw-${id}`,latestChain.id,copyElement)
205
- newElement = copyElement;
130
+ ele.setAttribute(newName,value)
131
+ ele.setAttribute('p:c',`${newName};`)
206
132
  }
207
- newElement.removeAttribute(latestChain.condition)
208
- newElement.setAttribute('pawa-same',true)
209
- store.setAttribute('p:store-switch',id)
210
- store.setAttribute('p:store','')
211
- endComment.parentElement.insertBefore(comment,endComment)
212
- stream(`<!--${comment.data}-->`)
213
- comment.parentElement.insertBefore(store,endComment)
214
- stream(store.outerHTML)
215
- comment.parentElement.insertBefore(newElement,endComment)
216
- await render(newElement, el._context,stream);
217
- stream(`<!--${endComment.data}-->`)
218
- } else {
219
- // If no case matches and no default, we just leave the comments
220
- template.setAttribute('pawa-render',true)
221
- endComment.parentElement.insertBefore(comment, endComment);
222
- stream(`${template.outerHTML}`)
223
- // stream(`<!--${comment.data}-->`)
224
- comment.parentElement.insertBefore(template, endComment);
225
- // await render(template, el._context,stream);
226
- // No element rendered
227
- // stream(`<!--${endComment.data}-->`)
228
- }
229
- };
230
-
231
- export const For=async(el,attr,stream)=>{
232
- if(el._running){
233
- return
234
133
  }
235
- el._running=true
236
- try {
237
- const value=attr.value
238
- const document=el.ownerDocument
239
- const dirId=pawaGenerateId(10)
240
- const comment=document.createComment(`for(${value})@-$@-$@${dirId}`)
241
- const endComment=document.createComment(`endfor(${value})@-$@-$@${dirId}`)
242
- el.replaceWith(endComment)
243
- const hasKey=el.getAttribute('for-key')
244
- endComment.parentElement.insertBefore(comment,endComment)
245
- // More robust split using regex to find the last occurrence of ' in ' or handle simple cases
246
- const match = value.match(/^(.*?) in (.*)$/);
247
- if (!match) throw new Error(`Invalid for loop syntax: ${value}`);
248
- const [_, itemPart, arrayName] = match;
249
- const arrayItems=itemPart.split(',')
250
- const arrayItem=arrayItems[0].trim()
251
- const indexes=arrayItems[1]
252
- const array=el._evaluateExpr(arrayName,el._context,`at for directives check - ${attr.value}`)
253
- const copyElement=el.cloneNode(true)
254
- const store=[]
255
- Array.from(copyElement.attributes).forEach(at=>{
256
- if (at.name.startsWith('c-')) {
257
- store.push(at)
258
- copyElement.removeAttribute(at.name)
259
- }
260
- })
261
-
262
- const template=document.createElement('template')
263
- const storeClone = copyElement.cloneNode(true)
264
- storeClone._avoidPawaRender = true
265
- template.appendChild(storeClone)
266
- store.forEach(at=>{
267
- copyElement.setAttribute(at.name,at.value)
268
- })
269
- const componentAttr={}
270
- if(Array.isArray(array)){
271
- if (array.length > 0) {
272
- stream(`<!--${comment.data}-->`)
273
- template.setAttribute('p:store-for',dirId)
274
- template.setAttribute('p:store','')
275
- endComment.parentElement.insertBefore(template,endComment)
276
- stream(template.outerHTML)
277
- el.attributes.forEach(attri =>{
278
- if(attri.name.startsWith('c-')){
279
- componentAttr[attri.name]=attri.value
280
- }
134
+ reArrange(name,value,replace){
135
+ const newAttribute={}
136
+ if(this._arrangeAttribute){
137
+ this._el.attributes.forEach((value, index) => {
138
+ this._el.removeAttribute(value.name)
281
139
  })
282
- el._replaceResumeAttr('for-each',`c-for`,dirId)
283
- el.removeAttribute('for-each')
140
+ for (const [key,value] of Object.entries(this._arrangeAttribute)) {
141
+ if (key === replace) {
142
+ this._el.setAttribute(name,value)
143
+ }else{
144
+ this._el.setAttribute(key,value)
145
+ }
146
+ }
147
+ }
148
+ }
149
+ hasForOrIf(){
150
+ if (this._el.getAttribute('if') || this._el.getAttribute('for-each') || this._el.getAttribute('else') || this._el.getAttribute('else-if')) {
151
+ return true
152
+ }else{
153
+ return false
154
+ }
155
+ }
156
+
157
+ getComponent(){
158
+ if (components.has(splitAndAdd(this._el.tagName.toUpperCase())) && !this._client || this._lazy) {
159
+ this._componentName=splitAndAdd(this._el.tagName.toUpperCase())
160
+ const fakeCompo=()=>true
161
+ this._component=new PawaComponent(components.get(splitAndAdd(this._el.tagName.toUpperCase())),fakeCompo)
162
+ Array.from(this._el.children).forEach(slot =>{
284
163
 
285
- // Fix: Use for...of to ensure await works correctly in SSR
286
- for (const [index, item] of array.entries()) {
287
- const context=el._context
288
- const itemContext = {
289
- [arrayItem]: item,
290
- [indexes]: index,
291
- ...context
164
+ if (slot.tagName === 'TEMPLATE' && slot.getAttribute('prop')) {
165
+
166
+ this._slots.appendChild(slot)
167
+ }
168
+ })
169
+ this._componentChildren=this._el.innerHTML
170
+ }else{
171
+ if(this._el.hasAttribute('only-client')){
172
+ this._el.removeAttribute('only-client')
292
173
  }
293
- itemContext[arrayItem]=item
294
- const newElement=el.cloneNode(true)
295
- if(index !== 0){
296
- newElement.removeAttribute('c-for')
297
- for (const [key,value] of Object.entries(componentAttr)) {
298
- if(newElement.hasAttribute(key)){ //removing element with resuming attri from second -- for element
299
- newElement.removeAttribute(key)
174
+ }
175
+ }
176
+ setError(){
177
+ if (getDevelopment() && this._error.length > 0) {
178
+ this._el.setAttribute('ssr-error',JSON.stringify(this._error))
179
+ }
180
+ }
181
+ createError({message,stack}){
182
+ this._error.push({message,stack})
183
+ }
184
+ //set Component props
185
+ setProps(){
186
+ if (this._componentName) {
187
+ const allServerAttr=getPawaAttributes()
188
+ this._el.attributes.forEach(attr=>{
189
+ if(!allServerAttr.has(attr.name)){
190
+ if ( !attr.name.startsWith(':')) {
191
+ if( attr.name.startsWith('c-') || attr.name.startsWith('p:c') || attr.name.startsWith('state-')) return
192
+ let name=''
193
+ if (attr.name.startsWith('-')) {
194
+ name=attr.name.slice(1)
195
+ }else{
196
+ name=attr.name
197
+ }
198
+ let hydatename
199
+ if(name === 'class'){
200
+ hydatename=':'+'className'
201
+ }else if(name === 'default'){
202
+ hydatename=':'+'defaultValue'
203
+ }else{
204
+ hydatename=':'+name
205
+ }
206
+ this._hydrateProps[hydatename]=attr.value
207
+ const setProps=()=>{
208
+ let value=attr.value
209
+ if (value.includes('@{')) {
210
+ const regex = /@{([^}]*)}/g;
211
+ value = value.replace(regex, (match, expression) => {
212
+
213
+ const res = this.evaluateExpr(expression,this._context,`evaluating props with template operators at ${attr.name} - ${attr.value} : ${this._template}`)
214
+ return res
215
+
216
+ });
217
+ return value
218
+ }else if( attr.name.startsWith('on-') || attr.name.startsWith('out-') || attr.name === 'ref'){
219
+ const res=this.evaluateExpr(`(e)=>{
220
+ ${attr.value}
221
+ }`, this._context,`evaluating props with template operators at ${attr.name} - ${attr.value} : ${this._template}`)
222
+ return res
223
+ }
224
+ return attr.value
225
+ }
226
+
227
+ this._restProps[name]={name:name,value:attr.value}
228
+ name=name.replace(/-([a-z])/g, (g) => g[1].toUpperCase());
229
+ if (this._props[name] || name ==='class' && this._props?.className || name ==='default' && this._props.defaultValue) return
230
+ if (name === 'class') {
231
+ this._props['className']=setProps
232
+ }else if(name === 'default'){
233
+ this._props['defaultValue']=setProps
234
+ }else{
235
+ this._props[name]=setProps
236
+ }
237
+
238
+ } else if(attr.name.startsWith(':')) {
239
+ this._hydrateProps[attr.name.slice(1)]=attr.value
240
+ if(attr.value === '') attr.value=true;
241
+ try {
242
+ const func = this.evaluateExpr(`()=>{
243
+ const prop= ${replaceTemplateOperators(attr.value)};
244
+ if(prop === '')return prop
245
+ return prop
246
+ }
247
+ `,this._context,`setting props at ${attr.name} - ${attr.value} : ${this._template}`)
248
+ let name=attr.name.slice(1)
249
+ if(name.includes('-')){
250
+ name=name.replace(/-([a-z])/g, (g) => g[1].toUpperCase());
251
+ }
252
+ this._props[name]=func
253
+ } catch (error) {
254
+ console.log(error.message,error.stack)
255
+ __pawaDev.setError({
256
+ el:this._el,
257
+ msg:`errors while setting props at runtime ${error.message}`,
258
+ directives:'setting props',
259
+ stack:error.stack,
260
+ template:el?._template,
261
+ })
262
+ }
300
263
  }
301
264
  }
302
- }
303
- newElement.setAttribute('pawa-same',true)
304
- processNode(newElement,itemContext)
305
-
306
- const key=newElement.getAttribute('for-key')
307
- const keyComment=document.createComment(`forKey@-$@-$@${dirId}@-$@-$@${key || index}`)
308
- const endKeyComment=document.createComment(`endForKey@-$@-$@${dirId}@-$@-$@${key || index}`)
309
- endComment.parentElement.insertBefore(endKeyComment,endComment)
310
- endKeyComment.parentElement.insertBefore(keyComment,endKeyComment)
311
- stream(`<!--${keyComment.data}-->`)
312
- endKeyComment.parentElement.insertBefore(newElement,endKeyComment)
313
- await render(newElement,itemContext,stream)
314
- stream(`<!--${endKeyComment.data}-->`)
265
+ })
315
266
  }
316
- stream(`<!--${endComment.data}-->`)
317
- }else{
318
- template.setAttribute('pawa-render',true)
319
- stream(`<template pawa-render="true">${el.outerHTML}</template>`)
320
- template.appendChild(el)
321
- comment.replaceWith(template)
322
- endComment.remove()
323
267
  }
324
- }
325
-
326
- } catch (error) {
327
- console.error(error.message,error.stack)
328
- __pawaDev.setError({
329
- el:el,
330
- msg:`error from for-each ${attr.value}`+ error.message + error.stack,
331
- directives:'for-each',
332
- stack:error.stack,
333
- template:el?._template,
334
- })
335
- }
336
- }
337
-
338
-
339
- export const State=async(el,attr)=>{
340
- if (el._running) {
341
- return
342
- }
268
+ evaluateExpr(expr, context = {},error){
343
269
  try {
270
+ const keys = Object.keys(context);
271
+ // Create a cache key based on the expression and the available context keys
272
+ // We sort keys to ensure consistent cache hits regardless of key order
273
+ const cacheKey = expr + '|||' + keys.sort().join(',');
344
274
 
345
- const name=attr.name.split('-')[1]
346
- el._replaceResumeAttr(attr.name,`c-$-${name}`,attr.value)
347
- el.removeAttribute(attr.name)
348
- const result=el._evaluateExpr(attr.value,el._context,`at state directive ${attr.name}=${attr.value}`)
349
- // el.setAttribute(`resume-state-${name}`,attr.value)
350
- el._context[name]={value:result}
351
- } catch (error) {
352
- console.log(error.message,error.stack)
353
- __pawaDev.setError({
354
- el:el,
355
- msg:`error from state ${attr.name} : ${attr.value}`+ error.message + error.stack,
356
- directives:el.tagName,
357
- stack:error.stack,
358
- template:el?._template,
359
- })
360
- }
361
-
362
- }
363
-
364
- export const Key=async(el,attr,stream)=>{
365
- if(el._running){
366
- return
275
+ let func = expressionCache.get(cacheKey);
276
+ if (!func) {
277
+ func = new Function(...keys, `
278
+ const require=null
279
+ return ${expr}`);
280
+ expressionCache.set(cacheKey, func);
367
281
  }
368
- el._running=true
369
- try {
370
- const value=attr.value
371
- const document=el.ownerDocument
372
- const dirId=pawaGenerateId(10)
373
- const comment=document.createComment(`key`)
374
- const endComment=document.createComment(`key`)
375
- const clone=el.cloneNode(true)
376
- clone._avoidPawaRender = true
377
- const template=document.createElement('template')
378
- template.setAttribute('p:store-key',dirId)
379
- template.setAttribute('p:store','')
380
- Array.from(clone.attributes).forEach(at => {
381
- if (at.name.startsWith('c-') || at.name === 'p:c') {
382
- clone.removeAttribute(at.name);
383
- }
384
- });
385
- template.appendChild(clone)
386
- el.replaceWith(endComment)
387
- endComment.parentElement.insertBefore(comment,endComment)
388
- const func=el._evaluateExpr(attr.value,el._context,`error at Key - ${attr.name} = ${attr.value}`)
389
- endComment.parentElement.insertBefore(template,endComment)
390
- comment.data=`key(${func})@-$@-$@${dirId}`
391
- endComment.data=`key(${func})@-$@-$@${dirId}`
392
- stream(`<!--${comment.data}-->`)
393
- stream(template.outerHTML)
394
- el._replaceResumeAttr('key',`c-key-${dirId}`,typeof func === 'string'?`'${func}'`:func)
395
- el.removeAttribute(attr.name)
396
- const newElement=el.cloneNode(true)
397
- endComment.parentElement.insertBefore(newElement,endComment)
398
- await render(newElement,el._context,stream)
399
- stream(`<!--${endComment.data}-->`)
400
- }catch(error){
401
- console.error(error.message,error.stack)
402
- __pawaDev.setError({
403
- el:el,
404
- msg:`error from Key at ${attr.value}`+ error.message + error.stack,
405
- directives:el.tagName,
406
- stack:error.stack,
407
- template:el?._template,
408
- })
282
+
283
+ const values = keys.map((key) => context[key]);
284
+ return func(...values);
285
+ } catch (err) {
286
+ console.error(`Evaluation failed for: ${expr}`,error,err.message,err.stack);
287
+ __pawaDev.setError({
288
+ el:this._el,
289
+ msg:`${error} ${err.message}`,
290
+ directives:'expression',
291
+ stack:err.stack,
292
+ template:this._el?._template,
293
+ })
294
+ return null;
295
+ }
296
+ };
409
297
  }
410
- }
298
+ export default PawaElement
package/utils.js CHANGED
@@ -156,7 +156,26 @@ export const propsValidator=(obj={},propsAttri,name,template,el)=>{
156
156
  if(propsAttri[key] || propsAttri[key] === 0){
157
157
  const checker=ComponentProps(propsAttri[key],value?.err,name)
158
158
  if (value.type) {
159
- checker[value.type.name]()
159
+ if (Array.isArray(value.type)) {
160
+ let isValid = false
161
+ for (const type of value.type) {
162
+ try {
163
+ checker[type.name]()
164
+ isValid = true
165
+ break
166
+ } catch (error) {}
167
+ }
168
+ if (!isValid) {
169
+ const types = value.type.map(t => t.name).join(' or ')
170
+ throw new Error(value?.err ? value.err : `${key} must be type of ${types} at ${name} component`);
171
+ }
172
+ } else {
173
+ try {
174
+ checker[value.type.name]()
175
+ } catch (error) {
176
+ throw new Error(value?.err ? value.err : `${key} must be type of ${value.type.name} at ${name} component`);
177
+ }
178
+ }
160
179
  }
161
180
  }else{
162
181
  if (value.strict) {