pawajs 1.3.6 → 1.3.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -11,11 +11,11 @@ PawaJS is a JavaScript library designed for building dynamic user interfaces. It
11
11
 
12
12
  ## Features
13
13
 
14
- - **Declarative Rendering:** Use a clean, HTML-based template syntax with powerful directives (`if`, `for`, `on-event`, etc.) to describe your UI.
14
+ - **Declarative Rendering:** Use a clean, HTML-based template syntax with powerful directives (`if`, `for-each`, `on-event`, etc.) to describe your UI.
15
15
  - **Reactive State Management:** Effortlessly create reactive state that automatically updates the DOM when it changes using the `$state` utility.
16
16
  - **Component-Based Architecture:** Build encapsulated components that manage their own state, making your code more reusable and maintainable.
17
17
  - **Async Components:** First-class support for asynchronous components, allowing you to fetch data directly within your component definition.
18
- - **Efficient List Rendering:** Render lists of data with the `for` directive, including support for keyed updates for optimal performance.
18
+ - **Efficient List Rendering:** Render lists of data with the `for-each` directive, including support for keyed updates for optimal performance.
19
19
  - **Lifecycle Hooks:** Tap into a component's lifecycle with `mount` and `unmount` directives, or the `runEffect` hook for more complex side effects.
20
20
  - **Context API:** Pass data through the component tree without having to pass props down manually at every level.
21
21
  - **Server-Side Rendering (SSR) Isomorphic architecture:** PawaJS is built with SSR in mind, featuring a "resuming" mechanism to efficiently continue server-rendered HTML on the client.
@@ -34,10 +34,10 @@ CDN
34
34
 
35
35
  ```html
36
36
  <head>
37
- <script type="module" href="https://cdn.jsdelivr.net/npm/pawajs@1.2.0/cdn/index.js"></script>
38
- </head>
37
+ </head>
39
38
  <body>
40
39
  <div id="app"></div>
40
+ <script type="module" src="https://cdn.jsdelivr.net/npm/pawajs@1.2.0/cdn/index.js"></script>
41
41
  <script type="module">
42
42
  window.$pawa.pawaStartApp(document.getElementById('app'));
43
43
  </script>
@@ -263,12 +263,12 @@ makes pawajs engine to wait before removing the element until the animation/tran
263
263
  <input value="@{user.value.name}" on-input="user.value.value = e.target.value">
264
264
  </div>
265
265
  ```
266
- #### `for`
266
+ #### `for-each`
267
267
  For rendering lists from an array. Use `for-key` to give each element a unique identity, which helps PawaJS optimize rendering.
268
268
 
269
269
  ```html
270
270
  <ul>
271
- <li for="todo, i in todos.value" for-key="{{todo.id}}">
271
+ <li for-each="todo, i in todos.value" for-key="{{todo.id}}">
272
272
  <span>@{i + 1}. @{todo.text}</span>
273
273
  </li>
274
274
  </ul>
@@ -342,6 +342,7 @@ useValidateComponent(TodoList, {
342
342
  - `useContext(contextObject)` & `setContext()`: A mechanism for providing and consuming data throughout a component tree.
343
343
  - `useRef()`: Creates a reference object that can be attached to a DOM element using the `ref` directive.
344
344
  - `useValidateComponent(Component, rules)`: Defines validation rules for a component's props.
345
+ - `useServer()`: Returns `{ setServerData, getServerData }` for handling server-side data serialization and client-side retrieval in SSR applications.
345
346
 
346
347
  ---
347
348
 
package/index.d.ts CHANGED
@@ -1,3 +1,4 @@
1
+
1
2
  export interface PawaElement extends HTMLElement {
2
3
  _running: boolean;
3
4
  _context: any;
@@ -75,6 +76,42 @@ export interface PawaElement extends HTMLElement {
75
76
  setProps(): void;
76
77
  }
77
78
 
79
+ export interface PawaComment extends Comment {
80
+ _index: number | null;
81
+ _el: Comment;
82
+ _setCoveringElement: (el: any) => void;
83
+ _data: Record<string, any>;
84
+ _terminateEffects: Set<Function>;
85
+ _run: any;
86
+ _coveringElement: any;
87
+ _setData: (obj: any) => void;
88
+ _removeSiblings: (endComment: any) => void;
89
+ _controlComponent: boolean;
90
+ _componentTerminate: Function | null;
91
+ _componentElement: any;
92
+ _setComponentOut: any;
93
+ _deleteEffects: () => void;
94
+ _remove: () => void;
95
+ _terminateByComponent: (endComment: any) => void;
96
+ _forKey: string | null;
97
+ _forIndex: any;
98
+ _setKey: (arg: any) => void;
99
+ _endComment: Comment | null;
100
+ _keyRemover: (callback?: Function, firstElement?: boolean) => Promise<void>;
101
+ _resetForKeyElement: () => DocumentFragment;
102
+ _deletKey: () => void;
103
+ forKeyResetElement(): DocumentFragment;
104
+ keyRemoveElement(callback?: Function, firstElement?: boolean): Promise<void>;
105
+ deleteKey(): void;
106
+ setForKey(arg: any): void;
107
+ terminateEffects(): void;
108
+ setCoveringElement(el: any): void;
109
+ setData(obj: any): void;
110
+ terminate(endComment: any): void;
111
+ remove(): void;
112
+ removeSiblings(endComment: any): void;
113
+ }
114
+
78
115
  export interface AttriPlugin {
79
116
  startsWith?: string;
80
117
  fullName?: string;
@@ -149,6 +186,8 @@ export function getDependentAttribute(): Set<string>;
149
186
 
150
187
  export function getPawaAttributes(): Set<string>;
151
188
 
189
+ export function getPrimaryDirectives(): Set<string>;
190
+
152
191
  export function setError(params: { error: any }): void;
153
192
 
154
193
  /**
@@ -159,10 +198,15 @@ export function RegisterComponent(...args: (string | Function)[]): void;
159
198
 
160
199
  /**
161
200
  * Runs a side effect or lifecycle hook.
162
- * @param {() => void | (() => void)} callback - Effect function, optionally returning cleanup.
201
+ * @param {(comment:PawaComment) => void | (() => void)} callback - Effect function,comment for component hacking and optionally returning cleanup .
163
202
  * @param {any[] | object | number | null} [deps] - Dependencies or hook type (null=mount).
203
+ * # number - beforemount
204
+ * # null - onMount
205
+ * # array[...deps] - dependency reactive
206
+ * # object<{component:true}|any element from ref> - read alone effect tied to either the component or element
164
207
  */
165
- export function runEffect(callback: () => void | (() => void), deps?: any[] | object | number | null): void;
208
+ export function runEffect(callback: (comment:PawaComment) => void | (() => void), deps?: any[] | object | number | null): void;
209
+
166
210
 
167
211
  export interface PropValidation {
168
212
  strict?: boolean;
@@ -203,16 +247,18 @@ export function useInnerContext<T=any>(): T;
203
247
  /**
204
248
  * Tells pawa-ssr to serialize the children prop.
205
249
  * Then pawajs adds it into the component unpon re-execution
206
- * @returns {()=>void}
250
+ * @returns {void}
207
251
  */
208
- export function accessChild(): (()=>void);
252
+ export function accessChild(): void;
209
253
  /**
210
- * Tells pawa-ssr to serialized data from useInsert.
211
- * Then pawajs continuity model de-serializes it and adds to the rendering context
212
- * @returns {void}
213
- * Note: meant for server-only component
254
+ * Tells pawa-ssr to serialized data.
255
+ * Then pawajs continuity model de-serializes it and adds to the rendering context or getServerData hook
256
+ * @returns {{setServerData:(data:object)=>void,getServerData:()=>any}}
214
257
  */
215
- export function useServer(): (() => void) | undefined;
258
+ export function useServer<T = Record<string, any>>(): {
259
+ setServerData: (data: T) => void;
260
+ getServerData: () => T;
261
+ };
216
262
 
217
263
  /**
218
264
  * Stores the component instance into the returned $async hook.
package/index.js CHANGED
@@ -271,6 +271,7 @@ let stateContext = {
271
271
  _transportContext: {},
272
272
  _reactiveProps: {},
273
273
  _template: '',
274
+ _serializedData:{},
274
275
  _static: [],
275
276
  }
276
277
 
@@ -295,9 +296,11 @@ const setPrimaryAttibute = (...name) => {
295
296
  primaryDirective.add(att)
296
297
  })
297
298
  }
298
- export const getPrimaryDirective=()=>primaryDirective
299
- setPrimaryAttibute('if', 'else-if', 'for', 'else','switch','case','default','case','key')
300
- setPawaAttributes('if', 'else-if', 'for', 'else', 'mount',
299
+
300
+ export const getPrimaryDirectives=()=>primaryDirective
301
+
302
+ setPrimaryAttibute('if', 'else-if', 'for-each', 'else','switch','case','default','case','key')
303
+ setPawaAttributes('if', 'else-if', 'for-each', 'else', 'mount',
301
304
  'unmount', 'forKey', 'state-', 'on-', 'out-','key','switch','case','default')
302
305
  export const getDependentAttribute = () => dependentPawaAttribute
303
306
  export const getPawaAttributes = () => {
@@ -492,10 +495,26 @@ export const accessChild = () => {
492
495
  return serverInstance.accessChild?.()
493
496
  }
494
497
  }
495
- /**@returns {()=>void} - let's component serialized useInsert data on server*/
498
+ /**@returns {()=>{getServerData:()=>any,setServerData:(data:object)=>void}} - let's component serialized data on server*/
496
499
  export const useServer = () => {
497
500
  if (client) {
498
- return
501
+ if (stateContext && stateContext._hasRun === false) {
502
+ const component=stateContext._template
503
+ const data=stateContext._serializedData
504
+ /**
505
+ * Meant to ne used on server rendering
506
+ */
507
+ const setServerData=(data={})=>{
508
+ console.warn( `meant to be used on server-only at ${component}`)
509
+ }
510
+ /**
511
+ * Get serialized data from server during pawajs continuity
512
+ */
513
+ const getServerData=()=>data
514
+ return {setServerData,getServerData}
515
+ }else{
516
+ return {data:null}
517
+ }
499
518
  } else {
500
519
  return serverInstance.useServer?.()
501
520
  }
@@ -581,6 +600,7 @@ export const setStateContext = (context) => {
581
600
  }
582
601
  stateContext._transportContext = {}
583
602
  stateContext._static = []
603
+ stateContext._serializedData={}
584
604
  stateContext._formerContext = formerStateContext
585
605
  stateContext._reactiveProps = {}
586
606
  stateContext._template = ''
@@ -841,8 +861,7 @@ const mainAttribute = (el, exp) => {
841
861
  }
842
862
  attrMap.set(exp.name, exp.value);
843
863
  el._preRenderAvoid.push(exp.name)
844
- const removeAttribute = new Set()
845
- removeAttribute.add('disabled')
864
+ const booleanAttributes = new Set(['checked', 'selected', 'disabled', 'readonly', 'required', 'multiple']);
846
865
  el._mainAttribute[exp.name] = exp.value
847
866
  el._checkStatic()
848
867
  let enter=false
@@ -859,26 +878,40 @@ const mainAttribute = (el, exp) => {
859
878
  };
860
879
  const values = keys.map((key) => resolvePath(key, el._context));
861
880
 
881
+ const hasExpression = regex.test(value);
882
+ regex.lastIndex = 0;
883
+
862
884
  value = value.replace(regex, (match, expression) => {
863
885
  if (checkKeywordsExistence(el._staticContext, expression)) {
864
886
  return ''
865
887
  } else {
866
888
  const func = new Function(...keys, `return ${expression}`);
867
- isBoolean = func(...values)
868
- if (typeof isBoolean !== 'boolean') {
869
- return isBoolean
889
+ const result = func(...values)
890
+ isBoolean = result
891
+ if (typeof result !== 'boolean') {
892
+ return result
870
893
  }else{
871
894
  return ''
872
895
  }
873
896
  }
874
897
  });
875
898
 
876
- if (removeAttribute.has(exp.name)) {
877
- if (isBoolean) {
899
+ if (booleanAttributes.has(exp.name)) {
900
+ const boolValue = hasExpression ? !!isBoolean : value.toLowerCase() !== 'false';
901
+ const propName = exp.name === 'readonly' ? 'readOnly' : exp.name;
902
+
903
+ if (propName in el) {
904
+ el[propName] = boolValue;
905
+ }
906
+
907
+ if (boolValue) {
878
908
  el.setAttribute(exp.name, '');
879
909
  } else {
880
- el.removeAttribute(exp.name)
910
+ el.removeAttribute(exp.name);
881
911
  }
912
+ } else if (exp.name === 'value' && 'value' in el) {
913
+ el.value = value;
914
+ el.setAttribute(exp.name, value);
882
915
  } else {
883
916
  if (exp.name === 'class' && !enter) {
884
917
  requestAnimationFrame(()=>{
@@ -941,7 +974,11 @@ const textContentHandler = (el, isName) => {
941
974
  return String(func(...values));
942
975
  }
943
976
  });
944
- textNode.nodeValue = value;
977
+ if(el.tagName === 'TEXTAREA'){
978
+ el.value=value
979
+ }else{
980
+ textNode.nodeValue = value;
981
+ }
945
982
 
946
983
  });
947
984
  } catch (error) {
@@ -969,7 +1006,7 @@ const template = (el,notRender, attr) => {
969
1006
 
970
1007
  const directives = {
971
1008
  if: If,
972
- for: For,
1009
+ 'for-each': For,
973
1010
  else:(el)=>{el._running =true},
974
1011
  case:(el)=>{el._running =true},
975
1012
  default:(el)=>{el._running =true},
@@ -1058,25 +1095,22 @@ export const render = (el, contexts = {}, notRender, isName) => {
1058
1095
  }
1059
1096
  else if (attr.name.startsWith('c-c-')) {
1060
1097
  stopResume.stop = true
1061
-
1062
- component(el, true, attr, notRender, stopResume)
1098
+ component(el, true, attr, notRender, stopResume)// component continuity
1063
1099
  } else if (attr.name.startsWith('c-at-')) {
1064
- resumer.resume_attribute?.(el, attr, notRender)
1100
+ resumer.resume_attribute?.(el, attr, notRender) //attribute continuity
1065
1101
  } else if (attr.name.startsWith('c-$-')) {
1066
- resumer.resume_state?.(el, attr, notRender)
1067
- } else if (attr.name.startsWith('c-t')) {
1102
+ resumer.resume_state?.(el, attr, notRender) //state continuity
1103
+ } else if (attr.name.startsWith('c-t')) {//text continuity
1068
1104
  resumer.resume_text(el, attr, isName)
1069
1105
  } else if (attr.name.startsWith('c-if-')) {
1070
- directives['if'](el, attr, stateContext, true, notRender, stopResume)
1106
+ directives['if'](el, attr, stateContext, true, notRender, stopResume) // condition continuity
1071
1107
  } else if (attr.name === 'c-for') {
1072
- directives['for'](el, attr, stateContext, true, notRender, stopResume)
1108
+ directives['for-each'](el, attr, stateContext, true, notRender, stopResume) // for-each continuity
1073
1109
  }else if (attr.name.startsWith('c-key-')) {
1074
- directives['key'](el, attr, stateContext, true, notRender, stopResume)
1110
+ directives['key'](el, attr, stateContext, true, notRender, stopResume)// key continuity
1075
1111
  }
1076
1112
  else if (attr.name.startsWith('c-sw-')) {
1077
- directives['switch'](el, attr, stateContext, true, notRender, stopResume)
1078
- } else if (attr.name === 'c-for') {
1079
- directives['for'](el, attr, stateContext, true, notRender, stopResume)
1113
+ directives['switch'](el, attr, stateContext, true, notRender, stopResume)// switch continuity
1080
1114
  } else if (fullNamePlugin.has(attr.name)) {
1081
1115
  if (externalPlugin[attr.name]) {
1082
1116
  const plugin = externalPlugin[attr.name]
@@ -1135,6 +1169,7 @@ export const render = (el, contexts = {}, notRender, isName) => {
1135
1169
  const number = { notRender: null, index: null }
1136
1170
  Array.from(el.children).forEach((child, index) => {
1137
1171
  number.index = index
1172
+ if (number.notRender !== null && index <= number.notRender) return
1138
1173
  render(child, context, number, isName)
1139
1174
  })
1140
1175
  el._callMount()
package/merger/for.js CHANGED
@@ -40,7 +40,7 @@ export const merger_for = (el, stateContext, attr, arrayName, arrayItem, indexes
40
40
  [indexes]: index,
41
41
  ...context
42
42
  }
43
- const newElement = el._attrElement('for')
43
+ const newElement = el._attrElement('for-each')
44
44
  newElement.setAttribute('data-for-index', index)
45
45
  processNode(newElement, itemContext)
46
46
  div.appendChild(newElement)
@@ -167,7 +167,7 @@ export const merger_for = (el, stateContext, attr, arrayName, arrayItem, indexes
167
167
  [indexes]: index,
168
168
  ...context
169
169
  }
170
- const newElement = el._attrElement('for')
170
+ const newElement = el._attrElement('for-each')
171
171
  const keyComment = document.createComment(`key=${newElement.getAttribute('for-key') || index}`)
172
172
  const endKeyComment = document.createComment('end key')
173
173
  PawaComment.Element(keyComment)
@@ -197,6 +197,7 @@ export const merger_for = (el, stateContext, attr, arrayName, arrayItem, indexes
197
197
  } else {
198
198
  if(!resume)return
199
199
  if(once)return
200
+ const number={notRender:null,index:null}
200
201
  elementArray.forEach((keyComment) => {
201
202
  if(keyComment.nextElementSibling === null) return
202
203
  const context = el._context
package/merger/if.js CHANGED
@@ -102,10 +102,11 @@ export const merger_if=(el,attr,stateContext,resume=false,{comment,endComment,ch
102
102
  stateContext._hasRun = false
103
103
  keepContext(stateContext)
104
104
  }
105
- const number={notRender:null,index:null}
105
+
106
+ const number = { notRender: null, index: null }
106
107
  children.forEach((value, index) => {
107
- number.index=index
108
- if(number.notRender && index >= number.notRender) return
108
+ number.index = index
109
+ if (number.notRender !== null && index <= number.notRender) return
109
110
  render(value,el._context,number)
110
111
  })
111
112
  stateContext._hasRun=true
package/merger/key.js CHANGED
@@ -51,7 +51,7 @@ export const merger_key=(el,attr,stateContext,resume=false,{comment,endComment,c
51
51
  const number={notRender:null,index:null}
52
52
  children.forEach((value, index) => {
53
53
  number.index=index
54
- if(number.notRender && index >= number.notRender) return
54
+ if (number.notRender !== null && index <= number.notRender) return
55
55
  render(value,el._context,number)
56
56
  })
57
57
  stateContext._hasRun=true
package/merger/switch.js CHANGED
@@ -106,8 +106,8 @@ export const merger_switch=(el,attr,stateContext,resume=false,{comment,endCommen
106
106
  }
107
107
  const number={notRender:null,index:null}
108
108
  children.forEach((value, index) => {
109
- number.index=index
110
- if(number.notRender && index >= number.notRender) return
109
+ number.index = index
110
+ if (number.notRender !== null && index <= number.notRender) return
111
111
  render(value,el._context,number)
112
112
  })
113
113
  stateContext._hasRun=true
@@ -1,6 +1,7 @@
1
1
  import {propsValidator, setPawaDevError, pawaWayRemover, checkKeywordsExistence, sanitizeTemplate } from '../utils.js';
2
2
  import {PawaElement,PawaComment} from '../pawaElement.js';
3
3
  import {keepContext,render} from '../index.js'
4
+ import {createEffect} from '../reactive.js'
4
5
  export const normal_component=(el,stateContext,setStateContext,mapsPlugin,formerStateContext,pawaContext,stateWatch)=>{
5
6
  const compoBeforeCall=mapsPlugin.compoBeforeCall
6
7
  const compoAfterCall=mapsPlugin.compoAfterCall
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pawajs",
3
- "version": "1.3.6",
3
+ "version": "1.3.8",
4
4
  "type":"module",
5
5
  "description": "pawajs library (html runtime) for easily building web ui, enhancing html element, micro frontend etc ",
6
6
  "main": "index.js",
package/pawaComponent.js CHANGED
@@ -41,6 +41,10 @@ class PawaComponent {
41
41
  isMount: [],
42
42
  isUnMount: [],
43
43
  };
44
+ /**
45
+ * Data serialized from pawa-ssr
46
+ */
47
+ this._serializedData={}
44
48
  /**
45
49
  * Map for non-reactive internal state (currently unused).
46
50
  * @type {Map}
package/pawaElement.js CHANGED
@@ -1,4 +1,4 @@
1
- import {components,escapePawaAttribute,getPawaAttributes,getDependentAttribute} from './index.js';
1
+ import {components,escapePawaAttribute,getPawaAttributes,getDependentAttribute,getPrimaryDirectives } from './index.js';
2
2
  import {splitAndAdd,replaceTemplateOperators,setPawaDevError,getEvalValues,safeEval} from './utils.js';
3
3
  import PawaComponent from './pawaComponent.js';
4
4
 
@@ -163,8 +163,9 @@ export class PawaElement {
163
163
  this._attr[attr.name]=attr.value
164
164
  })
165
165
  }
166
+
166
167
  hasForOrIf(){
167
- const primary=getPrimaryDirective()
168
+ const primary=getPrimaryDirectives()
168
169
  let truth=false
169
170
  primary.forEach((att)=>{
170
171
  if(truth) return
package/power.js CHANGED
@@ -244,7 +244,7 @@ export const For = (el, attr, stateContext,resume=false,notRender,stopResume) =>
244
244
  getEndComment(comment,setEndComment,id,children,'isFor')
245
245
  const numberIfChildren=notRender.index + children.length - 1
246
246
  notRender.notRender=numberIfChildren
247
- dataElement=comment.nextSibling
247
+ dataElement=document.querySelector(`[p\\:store-for="${id}"]`);
248
248
  el.removeAttribute(attr.name)
249
249
  dataElement.remove()
250
250
  resumer.resume_for?.(el,attr,stateContext,{comment,endComment,id,children,dataElement})
package/resumer.js CHANGED
@@ -14,8 +14,6 @@ export const resumer={
14
14
  */
15
15
  export const setResumer=(resume)=>{
16
16
  for(const [key,value] of Object.entries(resume)){
17
- if(resumer[key] === null && resumer[key] !== undefined){
18
17
  resumer[key]=value
19
- }
20
18
  }
21
19
  }
package/server.js CHANGED
@@ -13,8 +13,6 @@ const serverInstance={
13
13
  export const getServerInstance=()=>serverInstance
14
14
  export const setServer=(obj={})=>{
15
15
  for (const [key,value] of Object.entries(obj)) {
16
- if (serverInstance[key] === null) {
17
16
  serverInstance[key]=value
18
- }
19
17
  }
20
18
  }