pawajs 1.5.7 → 2.0.0

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/index.d.ts CHANGED
@@ -163,7 +163,11 @@ export function pluginsMap(): {
163
163
  pawaAttributes: Set<string>;
164
164
  allowAsProp: Set<string>;
165
165
  };
166
-
166
+ export function PawaCustomEvent(eventType:string,handler:(el:PawaElement,modifiers:Set,options:{
167
+ capture: Boolean,
168
+ once: Boolean,
169
+ passive: Boolean
170
+ },execute:(e:EventListener)=>void)=>{})
167
171
  export const escapePawaAttribute: Set<string>;
168
172
  export const dependentPawaAttribute: Set<string>;
169
173
 
package/index.js CHANGED
@@ -49,6 +49,15 @@ const createPawaDev = () => {
49
49
  dev.errors = [];
50
50
  dev.emit('clear', null);
51
51
  },
52
+ getSnapshot() {
53
+ return {
54
+ renderCount: dev.renderCount,
55
+ totalEffect: dev.totalEffect,
56
+ performance: dev.performance,
57
+ errors: dev.errors,
58
+ componentCount: dev.components.size
59
+ };
60
+ },
52
61
  logRender(c, t) { dev.renderCount++; /* preserve logging */ },
53
62
  logEffect(e, t) { dev.totalEffect++; /* preserve */ },
54
63
  logComponent(n, t) { /* preserve */ }
@@ -66,6 +75,7 @@ if (typeof globalThis !== 'undefined') {
66
75
  global.__pawaDev = pawaDevInstance;
67
76
  }
68
77
 
78
+ export const customEventMap = new Map()
69
79
  const compoBeforeCall = new Set()
70
80
  const compoAfterCall = new Set()
71
81
  const renderBeforePawa = new Set()
@@ -517,7 +527,10 @@ export const setContext = () => {
517
527
  }
518
528
 
519
529
  }
520
-
530
+ export const PawaCustomEvent=(eventName,handler)=>{
531
+ if (customEventMap.has(eventName) && !__pawaDev.tool)return
532
+ customEventMap.set(eventName, handler)
533
+ }
521
534
  /**
522
535
  * Get parent Context
523
536
  * @param {object} context
@@ -975,17 +988,26 @@ const component = (el, resume = false, attr, notRender, stopResume) => {
975
988
  const mainAttribute = (el, exp) => {
976
989
  const attrMap = new Map();
977
990
  if (el._running) return
978
- // Store original attribute value
991
+
992
+ // Check if attribute starts with @ (shorthand for reactive attributes)
993
+ const isAtAttr = exp.name.startsWith('@');
994
+ const targetName = isAtAttr ? exp.name.slice(1) : exp.name;
995
+
979
996
  if (el._hasForOrIf()) {
980
997
  return
981
998
  }
982
999
  if (el._componentName) {
983
1000
  return
984
1001
  }
1002
+
1003
+ if (isAtAttr) {
1004
+ el.removeAttribute(exp.name);
1005
+ }
1006
+
985
1007
  attrMap.set(exp.name, exp.value);
986
- el._preRenderAvoid.push(exp.name)
1008
+ el._preRenderAvoid.push(targetName)
987
1009
  const booleanAttributes = new Set(['checked', 'selected', 'disabled', 'readonly', 'required', 'multiple']);
988
- el._mainAttribute[exp.name] = exp.value
1010
+ el._mainAttribute[targetName] = exp.value
989
1011
  el._checkStatic()
990
1012
  let enter=false
991
1013
  const context=el._context
@@ -1014,30 +1036,32 @@ const mainAttribute = (el, exp) => {
1014
1036
  }
1015
1037
  });
1016
1038
 
1017
- if (booleanAttributes.has(exp.name)) {
1039
+ if (booleanAttributes.has(targetName)) {
1018
1040
  const boolValue = hasExpression ? !!isBoolean : value.toLowerCase() !== 'false';
1019
- const propName = exp.name === 'readonly' ? 'readOnly' : exp.name;
1041
+ const propName = targetName === 'readonly' ? 'readOnly' : targetName;
1020
1042
 
1021
1043
  if (propName in el) {
1022
1044
  el[propName] = boolValue;
1023
1045
  }
1024
1046
 
1025
1047
  if (boolValue) {
1026
- el.setAttribute(exp.name, value);
1048
+ el.setAttribute(targetName, value);
1027
1049
  } else {
1028
- el.removeAttribute(exp.name);
1050
+ el.removeAttribute(targetName);
1029
1051
  }
1030
- } else if (exp.name === 'value' && 'value' in el) {
1052
+ } else if (targetName === 'value' && 'value' in el) {
1031
1053
  el.value = value;
1032
- el.setAttribute(exp.name, value);
1054
+ el.setAttribute(targetName, value);
1033
1055
  } else {
1034
- if ((exp.name === 'class' || exp.name === 'style') && enter) {
1056
+ if ((targetName === 'class' || targetName === 'style') && enter) {
1035
1057
  requestAnimationFrame(()=>{
1036
- el.setAttribute(exp.name, value);
1058
+ el.setAttribute(targetName, value);
1037
1059
  })
1038
1060
  enter=true
1039
1061
  }else{
1040
- el.setAttribute(exp.name, value);
1062
+ el.setAttribute(targetName, value);
1063
+ // Toggle enter to true after initial set so subsequent reactive updates use requestAnimationFrame
1064
+ if (targetName === 'class' || targetName === 'style') enter = true;
1041
1065
  }
1042
1066
  }
1043
1067
  } catch (error) {
@@ -1196,7 +1220,7 @@ export const render = (el, contexts = {}, notRender, isName) => {
1196
1220
  directives[attr.name](el, attr, stateContext)
1197
1221
  } else if (attr.name.startsWith('on-') && !isAcomponent) {
1198
1222
  event(el, attr, stateContext)
1199
- } else if (attr.value.includes('@{') && !attr.name.startsWith('c-at-')) {
1223
+ } else if ((attr.value.includes('@{') || attr.name.startsWith('@')) && !attr.name.startsWith('c-at-')) {
1200
1224
  mainAttribute(el, attr, isName)
1201
1225
  } else if (attr.name.startsWith('state-')) {
1202
1226
  States(el, attr, getCurrentContext())
@@ -1230,6 +1254,7 @@ export const render = (el, contexts = {}, notRender, isName) => {
1230
1254
  directives['switch'](el, attr, stateContext, true, notRender, stopResume)// switch continuity
1231
1255
  } else if (fullNamePlugin.has(attr.name)) {
1232
1256
  if (externalPlugin[attr.name]) {
1257
+ if (el._componentName && !attr.name.startsWith('c-'))return
1233
1258
  const plugin = externalPlugin[attr.name]
1234
1259
  try {
1235
1260
  if (typeof plugin !== 'function') {
@@ -1244,6 +1269,7 @@ export const render = (el, contexts = {}, notRender, isName) => {
1244
1269
  } else if (startAttribute) {
1245
1270
  const name = startObject[attr.name]
1246
1271
  if (externalPlugin[name]) {
1272
+ if (el._componentName && !attr.name.startsWith('c-'))return
1247
1273
  const plugin = externalPlugin[name]
1248
1274
  try {
1249
1275
  if (typeof plugin !== 'function') {
@@ -1361,10 +1387,12 @@ const Pawa = {
1361
1387
  $state,
1362
1388
  pawaStartApp,
1363
1389
  useAsync,
1390
+ forwardProps,
1364
1391
  useInnerContext,
1365
1392
  RegisterComponent,
1366
1393
  runEffect,
1367
- html
1394
+ html,
1395
+ PawaCustomEvent
1368
1396
  }
1369
1397
 
1370
1398
  export default Pawa
@@ -3,6 +3,10 @@ import {PawaElement,PawaComment} from '../pawaElement.js';
3
3
  import {keepContext,render, HmrComponentMap } from '../index.js'
4
4
  import {createEffect} from '../reactive.js'
5
5
  export const normal_component=(el,stateContext,setStateContext,mapsPlugin,formerStateContext,pawaContext,stateWatch)=>{
6
+ // Checks if the content is an SVG fragment (graphic elements) without a root <svg> tag.
7
+ // We exclude 'svg' from the match because a root <svg> tag can be parsed safely inside a <div>.
8
+ const isSvgFragment = (str) => /^\s*<(path|circle|rect|line|polyline|polygon|ellipse|g|defs|symbol|use|image|text|animate|mask|pattern|clipPath|linearGradient|radialGradient|filter)/i.test(str);
9
+
6
10
  const compoBeforeCall=mapsPlugin.compoBeforeCall
7
11
  const compoAfterCall=mapsPlugin.compoAfterCall
8
12
  const endComment = document.createComment(`end ${el.tagName}`)
@@ -61,7 +65,7 @@ export const normal_component=(el,stateContext,setStateContext,mapsPlugin,former
61
65
  console.error(error.message)
62
66
  }
63
67
  }
64
- let div =el._compoToSvg?document.createElementNS('http://www.w3.org/2000/svg', 'svg'): document.createElement('div')
68
+ let div
65
69
  el._componentTerminate=() => {
66
70
  comment._terminateByComponent(endComment)
67
71
  }
@@ -147,6 +151,10 @@ export const normal_component=(el,stateContext,setStateContext,mapsPlugin,former
147
151
  }
148
152
 
149
153
  // stateContext._hasRun=true
154
+ div = isSvgFragment(compo)
155
+ ? document.createElementNS('http://www.w3.org/2000/svg', 'svg')
156
+ : document.createElement('div');
157
+
150
158
  div.innerHTML = compo;
151
159
  if (component?._insert) {
152
160
  Object.assign(el._context,component._insert)
@@ -173,10 +181,15 @@ export const normal_component=(el,stateContext,setStateContext,mapsPlugin,former
173
181
  }
174
182
  const context=el._context
175
183
  const getAsChild=()=>{
176
- const asChild=div.firstElementChild
177
- if (splitAndAdd(asChild?.tagName|| '') === 'ASCHILD') {
178
- const getChildren=asChild.firstElementChild
179
- Array.from(asChild.attributes).forEach(attr=>{
184
+ const asChild=div
185
+ if (el._asChild) {
186
+ const divFirst=div.firstElementChild
187
+ if (isSvgFragment(children)) {
188
+ div=document.createElementNS('http://www.w3.org/2000/svg', 'svg')
189
+ }
190
+ div.innerHTML=children
191
+ const getChildren=div.firstElementChild
192
+ Array.from(divFirst.attributes).forEach(attr=>{
180
193
  if (getChildren.hasAttribute(attr.name)) {
181
194
  let attrName=getChildren.getAttribute(attr.name)
182
195
  attrName=attr.value +' '+attrName
@@ -184,9 +197,7 @@ export const normal_component=(el,stateContext,setStateContext,mapsPlugin,former
184
197
  }else{
185
198
  getChildren.setAttribute(attr.name, attr.value)
186
199
  }
187
- })
188
- asChild.remove()
189
- div.appendChild(getChildren)
200
+ })
190
201
  }
191
202
  }
192
203
  const propsSetter=()=>{
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pawajs",
3
- "version": "1.5.7",
3
+ "version": "2.0.0",
4
4
  "type":"module",
5
5
  "description": "pawajs library (reactive web runtime) for easily building web ui, enhancing html element, micro frontend etc ",
6
6
  "main": "index.js",
package/pawaElement.js CHANGED
@@ -252,7 +252,7 @@ export class PawaElement {
252
252
  }
253
253
  const tag = this._el.tagName
254
254
  try {
255
- if (components.has(splitAndAdd(tag)) || this._lazy) {
255
+ if (components.has(splitAndAdd(tag)) || this._lazy && !this._el.hasAttribute('data-prevent:c')) {
256
256
  this._elementType='component'
257
257
  this._componentOrTemplate=true
258
258
  this._deCompositionElement=true
@@ -304,14 +304,14 @@ export class PawaElement {
304
304
  const {allowAsProp}=pluginsMap()
305
305
  const dependAttribute=getDependentAttribute()
306
306
  this._attributes.forEach((attr) => {
307
- if (attr.name === 'svg') {
308
- this._compoToSvg = true
307
+ if (attr.name.startsWith('state-')) {
309
308
  return
310
- }else if(attr.name === 'aschild' || attr.name === 'as-child'){
309
+ }else if(attr.name === 'aschild' || attr.name === 'as-child' || attr.name === ':as-child'){
311
310
  this._asChild = true
312
311
  return
313
312
  }
314
- if (!attr.name.startsWith(':') && (!pawaAttribute.has(attr.name) && !dependAttribute.has(attr.name) )) {
313
+ const primaryAttribute=['if','else','else-if','key','for-each','case','switch','s-default']
314
+ if (!attr.name.startsWith(':') && !primaryAttribute.includes(attr.name)) {
315
315
  let name=''
316
316
  if (attr.name.startsWith('-')) {
317
317
  name=attr.name.slice(1)
@@ -353,7 +353,7 @@ export class PawaElement {
353
353
  }
354
354
 
355
355
  this._restProps[attr.name]={name:attr.name,value:attr.value}
356
- }else if(!pawaAttribute.has(attr.name) && attr.name.startsWith(':')){
356
+ }else if(!primaryAttribute.includes(attr.name) && attr.name.startsWith(':')){
357
357
 
358
358
  const propsName=attr.name.slice(1)
359
359
  if(attr.value === '') attr.value="true";
package/power.js CHANGED
@@ -7,6 +7,7 @@ import {normal_If} from './normal/If.js'
7
7
  import {normal_For} from './normal/For.js'
8
8
  import {resumer} from './resumer.js'
9
9
  import { normal_key } from './normal/Key.js';
10
+ import { customEventMap } from './index.js';
10
11
 
11
12
  // Shared helper to check common event modifiers
12
13
  const checkCommonModifiers = (e, modifiers) => {
@@ -102,11 +103,14 @@ const checkKeyModifiers = (e, modifiers, eventType) => {
102
103
  const processEventExecution = (el, attrName, modifiers, callback, eventType, directiveName,context) => {
103
104
  const execute = () => {
104
105
  try { callback(); }
105
- catch (error) { setPawaDevError({ message: `Error from ${directiveName}-${eventType} directive ${error.message}`, error, template: el._template }); }
106
+ catch (error) {
107
+ console.log(error,'at event')
108
+ setPawaDevError({ message: `Error from ${directiveName}-${eventType} directive ${error.message}`, error, template: el._template }); }
106
109
  };
107
110
  if (!el._eventTimers) el._eventTimers = {};
108
111
  const delay = parseInt([...modifiers].find(m => /^\d+$/.test(m)) || '300');
109
112
  if (modifiers.has('debounce')) {
113
+
110
114
  const timerKey = `${attrName}_debounce`;
111
115
  clearTimeout(el._eventTimers[timerKey]);
112
116
  el._eventTimers[timerKey] = setTimeout(execute, delay);
@@ -308,7 +312,35 @@ export const event = (el, attr, stateContext) => {
308
312
  };
309
313
 
310
314
  const target = modifiers.has('window') ? window : el;
311
- target.addEventListener(eventType, handler, options);
315
+
316
+ // Wrapper to ensure custom events respect standard action modifiers
317
+ const wrappedExecute = (e) => {
318
+ // Apply common modifiers first
319
+ if (!checkCommonModifiers(e, modifiers)) return;
320
+ if (!checkKeyModifiers(e, modifiers, eventType)) return;
321
+
322
+ // Apply target modifiers
323
+ if (modifiers.has('self') && e.target !== el) return;
324
+ if (modifiers.has('not-self') && e.target === el) return;
325
+
326
+ // Apply form modifiers (if applicable, though less common for custom events)
327
+ if (modifiers.has('dirty') && e.target && !e.target.value) return;
328
+ if (modifiers.has('pristine') && e.target && e.target.value) return;
329
+ if (modifiers.has('valid') && e.target && !e.target.checkValidity?.()) return;
330
+ if (modifiers.has('invalid') && e.target && e.target.checkValidity?.()) return;
331
+
332
+ // Apply action modifiers
333
+ if (modifiers.has('prevent')) e.preventDefault?.();
334
+ if (modifiers.has('stop')) e.stopPropagation?.();
335
+ processEventExecution(el, attr.name, modifiers, () => executeEvent(e), eventType, 'on');
336
+ };
337
+
338
+ if (customEventMap.has(eventType)) {
339
+ const custom=customEventMap.get(eventType)
340
+ custom(el, modifiers, options, wrappedExecute)
341
+ return
342
+ }
343
+
312
344
 
313
345
  el._MountFunctions.push(() => target.addEventListener(eventType, handler, options));
314
346
  el._setUnMount(() => {