pawajs 1.3.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.js ADDED
@@ -0,0 +1,1180 @@
1
+ import { track, trigger, createEffect } from './reactive.js'
2
+ import { PawaElement, PawaComment } from './pawaElement.js';
3
+ import {
4
+ If,
5
+ event,
6
+ unMountElement,
7
+ mountElement,
8
+ For,
9
+ States,
10
+ ref,
11
+ documentEvent,
12
+ Switch,
13
+ exitTransition,
14
+ After,
15
+ Every,
16
+ Key
17
+ } from './power.js'
18
+ import { propsValidator, sanitizeTemplate, setPawaDevError, splitAndAdd, pawaWayRemover, checkKeywordsExistence } from './utils.js';
19
+ import PawaComponent from './pawaComponent.js';
20
+ import { getServerInstance, isServer } from './server.js'
21
+ import { templates } from './normal/template.js'
22
+ import { normal_component } from './normal/component.js';
23
+ import { resumer } from './resumer.js';
24
+ let ERROR_CALLER
25
+ export const setErrorCALLER = (callback) => {
26
+ ERROR_CALLER = callback
27
+ }
28
+ // in progress
29
+ const errorCaller = (message) => {
30
+
31
+ }
32
+ const client = !isServer()
33
+ const serverInstance = getServerInstance()
34
+ if (client) {
35
+ window.__pawaDev = {
36
+ tool: false,
37
+ errors: [],
38
+ totalEffect: 0,
39
+ errorState: null,
40
+ components: new Set(),
41
+ renderCount: 0,
42
+ reactiveUpdates: 0,
43
+ totalComponent: 0,
44
+ performance: {
45
+ renderTime: [],
46
+ effectTime: [],
47
+ componentTime: [],
48
+ start: 0,
49
+ end: 0
50
+ },
51
+ setError: ({ el, msg, directives, stack, template, warn } = {}) => {
52
+ if (__pawaDev.tool !== true) return
53
+ if (__pawaDev.errorState) {
54
+ __pawaDev.errorState.value = true
55
+ }
56
+ __pawaDev.errors.push({
57
+ el,
58
+ msg,
59
+ directives,
60
+ stack,
61
+ timestamp: Date.now(),
62
+ template: template ? template : ''
63
+ })
64
+ if (warn) {
65
+ console.warn(msg, stack, template)
66
+ }
67
+ console.error(msg, stack)
68
+ },
69
+ logRender: (component, time) => {
70
+ __pawaDev.renderCount++
71
+ __pawaDev.performance.renderTime.push({
72
+ component,
73
+ time,
74
+ timestamp: Date.now()
75
+ })
76
+ },
77
+ logEffect: (effect, time) => {
78
+ __pawaDev.totalEffect++
79
+ __pawaDev.performance.effectTime.push({
80
+ effect,
81
+ time,
82
+ timestamp: Date.now()
83
+ })
84
+ },
85
+ logComponent: (name, time) => {
86
+ __pawaDev.components.add(name)
87
+ __pawaDev.performance.componentTime.push({
88
+ name,
89
+ time,
90
+ timestamp: Date.now()
91
+ })
92
+ }
93
+ }
94
+ }
95
+
96
+ const compoBeforeCall = new Set()
97
+ const compoAfterCall = new Set()
98
+ const renderBeforePawa = new Set()
99
+ const renderAfterPawa = new Set()
100
+ const renderBeforeChild = new Set()
101
+ const startsWithSet = new Set()
102
+ const fullNamePlugin = new Set()
103
+ const externalPlugin = {}
104
+ const externalPluginMap = new Map()
105
+ let pawaAttributes = new Set()
106
+ let primaryDirective = new Set()
107
+
108
+ const mapsPlugins = {
109
+ compoAfterCall,
110
+ compoBeforeCall,
111
+ renderAfterPawa,
112
+ renderBeforePawa,
113
+ renderBeforeChild,
114
+ startsWithSet,
115
+ fullNamePlugin,
116
+ externalPlugin,
117
+ externalPluginMap,
118
+ primaryDirective
119
+ }
120
+ export const pluginsMap = () => mapsPlugins
121
+ export const escapePawaAttribute = new Set()
122
+ export const dependentPawaAttribute = new Set()
123
+
124
+ export const removePlugin = (...pluginName) => {
125
+ pluginName.forEach(n => {
126
+ if (pawaAttributes.has(n)) {
127
+ pawaAttributes.delete(n)
128
+ delete externalPlugin[n]
129
+ if (externalPluginMap.has(n)) {
130
+ const extArray = externalPluginMap.get(n)
131
+ extArray.forEach(ex => {
132
+ dependentPawaAttribute.delete(ex)
133
+ })
134
+ }
135
+ }
136
+
137
+ })
138
+ }
139
+ const applyMode = (mode, callback) => {
140
+ if (mode === null || mode === undefined) {
141
+ callback()
142
+ } else if (mode === 'client' && client) {
143
+ callback()
144
+ } else if (mode === 'server' && !client) {
145
+ callback()
146
+ }
147
+ }
148
+ /**
149
+ * @typedef {{startsWith:string,mode:null |'client'|'server',dependency:Array<string>,fullName:string,plugin:(el:HTMLElement | PawaElement,attr:object)=>void}} AttriPlugin
150
+ */
151
+ /**
152
+ * @typedef {{
153
+ * attribute?:{register:Array<AttriPlugin>},
154
+ * component?:{
155
+ * beforeCall?:(stateContext:PawaComponent,app:object)=>void,
156
+ * afterCall?:(stateContext:PawaComponent,el:HTMLElement)=>void
157
+ * },
158
+ * renderSystem?:{
159
+ * beforePawa?:(el:HTMLElement,context:object)=>void,
160
+ * afterPawa?:(el:PawaElement)=>void,
161
+ * beforeChildRender?:(el:PawaElement)=>void
162
+ * }
163
+ * }} PluginObject
164
+ */
165
+ /**
166
+ * @param {Array<()=>PluginObject>} func
167
+ */
168
+ export const PluginSystem = (...func) => {
169
+
170
+
171
+ func.forEach(fn => {
172
+ /**
173
+ * @type {PluginObject}
174
+ */
175
+ if (typeof fn !== 'function') {
176
+ console.warn('plugin must be a function that returns the plugin objects')
177
+ return
178
+ }
179
+ const getPlugin = fn()
180
+ // attributes plugin or extension
181
+
182
+ if (getPlugin?.attribute) {
183
+ getPlugin.attribute.register.forEach(attrPlugins => {
184
+ if (attrPlugins.fullName && attrPlugins.startsWith) {
185
+ console.warn('Either Plugins FullName or startsWith. you are not required to use two of does plugin registers at this same entry.')
186
+ return
187
+ }
188
+ const extPluginArray = []
189
+ if (attrPlugins?.dependency && attrPlugins?.dependency.length > 0) {
190
+ attrPlugins.dependency.forEach(dp => {
191
+ if (dependentPawaAttribute.has(dp)) {
192
+ __pawaDev.setError({ msg: `${dp} is already used - from pawa plugin it might cause some issues`, warn: true })
193
+ }
194
+ dependentPawaAttribute.add(dp)
195
+ extPluginArray.push(dp)
196
+ })
197
+ }
198
+ if (attrPlugins?.fullName) {
199
+ if (pawaAttributes.has(attrPlugins.fullName)) {
200
+ console.warn(`attribute plugin already exist ${attrPlugins.fullName}`)
201
+ return
202
+ }
203
+ // console.log(attrPlugins)
204
+ applyMode(attrPlugins?.mode, () => {
205
+ pawaAttributes.add(attrPlugins.fullName)
206
+ fullNamePlugin.add(attrPlugins.fullName)
207
+ externalPlugin[attrPlugins.fullName] = attrPlugins?.plugin
208
+ if (extPluginArray.length > 0) externalPluginMap.set(attrPlugins.fullName, extPluginArray)
209
+ })
210
+ } else if (attrPlugins?.startsWith) {
211
+ if (pawaAttributes.has(attrPlugins.startsWith)) {
212
+ console.warn(`attribute plugin already exist ${attrPlugins.startsWith}`)
213
+ return
214
+ }
215
+ applyMode(attrPlugins?.mode, () => {
216
+ pawaAttributes.add(attrPlugins.startsWith)
217
+ startsWithSet.add(attrPlugins.startsWith)
218
+ externalPlugin[attrPlugins.startsWith] = attrPlugins?.plugin
219
+ if (extPluginArray.length > 0) externalPluginMap.set(attrPlugins.startsWith, extPluginArray)
220
+ })
221
+ }
222
+ })
223
+
224
+
225
+
226
+ }
227
+ if (getPlugin?.component) {
228
+ if (getPlugin.component?.beforeCall && typeof getPlugin.component?.beforeCall === 'function') {
229
+ compoBeforeCall.add(getPlugin.component.beforeCall)
230
+ }
231
+ if (getPlugin.component?.afterCall && typeof getPlugin.component?.afterCall === 'function') {
232
+ compoAfterCall.add(getPlugin.component.afterCall)
233
+ }
234
+ }
235
+ if (getPlugin?.renderSystem) {
236
+ if (getPlugin.renderSystem?.beforePawa && typeof getPlugin.renderSystem?.beforePawa === 'function') {
237
+ renderBeforePawa.add(getPlugin.renderSystem?.beforePawa)
238
+ }
239
+ if (getPlugin.renderSystem?.afterPawa && typeof getPlugin.renderSystem?.afterPawa === 'function') {
240
+ renderAfterPawa.add(getPlugin.renderSystem?.afterPawa)
241
+ }
242
+ if (getPlugin.renderSystem?.beforeChildRender && typeof getPlugin.renderSystem?.beforeChildRender === 'function') {
243
+ renderBeforePawa.add(getPlugin.renderSystem?.beforeChildRender)
244
+ }
245
+ }
246
+ })
247
+
248
+ }
249
+ export const keepContext = (context) => {
250
+ if (!client) return
251
+ stateContext = context
252
+ formerStateContext = stateContext
253
+
254
+ }
255
+ export const components = new Map()
256
+ /**
257
+ * @type {PawaComponent}
258
+ */
259
+ let stateContext = {
260
+ _hasRun: false,
261
+ _formerContext: null,
262
+ _insert: {},
263
+ _resume: false,
264
+ _hook: {
265
+ effect: [],
266
+ isMount: [],
267
+ isUnMount: []
268
+ },
269
+ _stateMap: new Map,
270
+ component: null,
271
+ _transportContext: {},
272
+ _reactiveProps: {},
273
+ _template: '',
274
+ _static: [],
275
+ }
276
+
277
+ export const getCurrentContext = () => {
278
+ return stateContext
279
+ }
280
+
281
+ let formerStateContext = null
282
+ let pawaContext = {}
283
+
284
+ export const setPawaAttributes = (...attr) => {
285
+ attr.forEach((att) => {
286
+ if (pawaAttributes.has(att)) {
287
+ throw Error(`${att} already exits`)
288
+ return
289
+ }
290
+ pawaAttributes.add(att)
291
+ })
292
+ }
293
+ const setPrimaryAttibute = (...name) => {
294
+ name.forEach(att => {
295
+ primaryDirective.add(att)
296
+ })
297
+ }
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',
301
+ 'unmount', 'forKey', 'state-', 'on-', 'out-','key')
302
+ export const getDependentAttribute = () => dependentPawaAttribute
303
+ export const getPawaAttributes = () => {
304
+ return pawaAttributes
305
+ }
306
+ export const setError = ({ error }) => {
307
+ if (!client) return
308
+ if (!stateContext) {
309
+ console.warn('must be used inside of a component')
310
+ return
311
+ }
312
+ if (!stateContext._hasRun) {
313
+ if (!stateContext?._error) {
314
+ stateContext._error = []
315
+ }
316
+ stateContext?._error.push(error)
317
+ }
318
+ }
319
+
320
+ /**
321
+ *
322
+ * @param {...()=>string|null} component
323
+ * Function registrar for pawajs component
324
+ */
325
+ export const RegisterComponent = (...args) => {
326
+ // Handle new signature from plugin: RegisterComponent('Name1', Func1, 'Name2', Func2, ...)
327
+
328
+ if (typeof args[0] === 'string') {
329
+ for (let i = 0; i < args.length; i += 2) {
330
+ const name = args[i];
331
+ const component = args[i + 1];
332
+ if (typeof name === 'string' && typeof component === 'function') {
333
+ // if (components.has(name.toUpperCase())) continue;
334
+ components.set(name.toUpperCase(), component);
335
+ } else {
336
+ console.warn('Mismatched arguments for RegisterComponent. Expected pairs of (string, function).');
337
+ break;
338
+ }
339
+ }
340
+ return;
341
+ }
342
+ // Handle old signature for dev mode: RegisterComponent(ComponentFunc1, ComponentFunc2, ...)
343
+ args.forEach((component) => {
344
+ if (typeof component === 'function' && component.name) {
345
+ // if (components.has(component.name.toUpperCase())) return;
346
+ components.set(component.name.toUpperCase(), component);
347
+ } else {
348
+ console.warn('Component registration failed: Component must be a named function. This might happen in production builds without the pawajs Vite plugin.');
349
+ }
350
+ });
351
+ }
352
+
353
+ /**
354
+ *
355
+ * @param {()=>()=>any} callback
356
+ * A function that runs based on the deps and the returns are for unMounted hook
357
+ * ( from Array,Number,null deps) while deps(object) are for the main reactive effect
358
+ * @param {Array|null|object|number} deps
359
+ * Array - for state dependency.
360
+ *
361
+ * object- for any state used inside of the callback but under the use of element or component.
362
+ *
363
+ * Number - before mount hook.
364
+ *
365
+ * null- for Mount hook
366
+ * @returns {void}
367
+ */
368
+ export const runEffect = (callback, deps) => {
369
+ if (client) {
370
+ if (stateContext._hasRun) {
371
+ return
372
+ }
373
+ if (stateContext) {
374
+ if (!stateContext._hook) {
375
+ stateContext._hook = {}
376
+ }
377
+ if (!stateContext._hook.isMount) {
378
+ stateContext._hook.isMount = []
379
+ }
380
+ if (!stateContext._hook.beforeMount) {
381
+ stateContext._hook.beforeMount = []
382
+ }
383
+ if (!stateContext._hook.reactiveEffect) {
384
+ stateContext._hook.reactiveEffect = []
385
+ }
386
+ if (!stateContext._hook.effect) {
387
+ stateContext._hook.effect = []
388
+ }
389
+ if (deps === undefined || deps === null) {
390
+ stateContext._hook.isMount.push(callback)
391
+ } else if (typeof deps === 'object' && !Array.isArray(deps)) {
392
+ stateContext._hook.reactiveEffect.push({ deps: deps, effect: callback })
393
+ } else if (Array.isArray(deps)) {
394
+ stateContext._hook.effect.push({
395
+ deps: deps,
396
+ effect: callback
397
+ })
398
+ } else if (typeof deps === 'number') {
399
+ stateContext._hook.beforeMount.push(callback)
400
+ }
401
+ }
402
+ }
403
+ }
404
+
405
+ /**
406
+ *
407
+ * @param {object} props
408
+ * @returns {object}
409
+ */
410
+ export const useValidateComponent = (component, object) => {
411
+ if (typeof component === 'function') {
412
+ if (component.name) {
413
+ component.validateProps = object
414
+ }
415
+ }
416
+ }
417
+ /**
418
+ * @returns {{id:string,setValue:()=>void}}
419
+ */
420
+ export const setContext = () => {
421
+ if (client) {
422
+
423
+ const id = crypto.randomUUID()
424
+ const setValue = (val = {}) => {
425
+ if (stateContext._hasRun) {
426
+ return
427
+ }
428
+ if (!stateContext) {
429
+ console.warn('set Context value must be inside of a component')
430
+ return null
431
+ }
432
+ if (!stateContext._transportContext) {
433
+ stateContext._transportContext = {}
434
+ }
435
+ if (stateContext._transportContext[id]) {
436
+ delete stateContext._transportContext[id]
437
+ }
438
+ stateContext._transportContext[id] = val
439
+ }
440
+ return {
441
+ id,
442
+ setValue
443
+ }
444
+ } else {
445
+ return serverInstance.setContext?.()
446
+ }
447
+
448
+ }
449
+
450
+ /**
451
+ * Get parent Context
452
+ * @param {object} context
453
+ * @return {object}
454
+ */
455
+ export const useContext = (context) => {
456
+ if (client) {
457
+ if (!stateContext) {
458
+ console.warn('getContext must be called inside of a component')
459
+ return
460
+ }
461
+ if (stateContext?._transportContext[context.id]) {
462
+ const contexts = stateContext._transportContext[context.id]
463
+ return contexts
464
+ } else {
465
+ console.warn('this component not in the context tree')
466
+ }
467
+
468
+ } else {
469
+ return serverInstance.useContext?.(context)
470
+ }
471
+ }
472
+
473
+ /**
474
+ * Get Current component context from the html
475
+ * @returns {object}
476
+ */
477
+ export const useInnerContext = () => {
478
+ if (client) {
479
+ if (!stateContext) {
480
+ console.warn('must be used inside component')
481
+ return
482
+ }
483
+ return stateContext._elementContext
484
+ } else {
485
+ return serverInstance.useInnerContext?.()
486
+ }
487
+ }
488
+ export const accessChild = () => {
489
+ if (client) {
490
+ return
491
+ } else {
492
+ return serverInstance.accessChild?.()
493
+ }
494
+ }
495
+ /**@returns {()=>void} - let's component serialized useInsert data on server*/
496
+ export const useServer = () => {
497
+ if (client) {
498
+ return
499
+ } else {
500
+ return serverInstance.useServer?.()
501
+ }
502
+ }
503
+ export const useAsync = () => {
504
+ if (client) {
505
+ if (stateContext._hasRun) return { $async: () => {} }
506
+ const storeContext = stateContext
507
+ return {
508
+ $async: (callback) => {
509
+ if (storeContext._hasRun) {
510
+ storeContext._hasRun = false
511
+ keepContext(storeContext)
512
+ }
513
+ const res = callback()
514
+ storeContext._hasRun = true
515
+ stateContext = null
516
+ return res
517
+ }
518
+ }
519
+ } else {
520
+ return {
521
+ $async: (callback) => {
522
+ return callback()
523
+ }
524
+ }
525
+ }
526
+ }
527
+ export const isResume = () => {
528
+ if (client) {
529
+ return stateContext._resume
530
+ } else {
531
+ return false
532
+ }
533
+ }
534
+ /**
535
+ * Insert into the html context in component
536
+ * @param {object} obj
537
+ * @returns void
538
+ */
539
+ export const useInsert = (obj = {}) => {
540
+ if (client) {
541
+ if (stateContext._hasRun) {
542
+ return
543
+ }
544
+ if (!stateContext._insert) {
545
+ stateContext._insert = {}
546
+ }
547
+ Object.assign(stateContext._insert, obj)
548
+ } else {
549
+ // console.log(serverInstance.useInsert)
550
+ const res = serverInstance.useInsert(obj)
551
+ }
552
+ }
553
+ const createDeepProxy = (target, callback) => {
554
+ return new Proxy(target, {
555
+ get(target, property) {
556
+ const value = target[property];
557
+ track(target, property);
558
+ if (typeof value === "object" && value !== null) {
559
+ return createDeepProxy(value, callback);
560
+ }
561
+ return value;
562
+ },
563
+ set(target, property, value) {
564
+ target[property] = value;
565
+ trigger(target, property);
566
+ callback(target, property);
567
+ return true;
568
+ },
569
+ });
570
+ };
571
+ const globalEffectMap = new Map();
572
+ /**
573
+ * @param {PawaComponent} context
574
+ */
575
+ export const setStateContext = (context) => {
576
+ const map = new Map()
577
+ // const formerMap=formerStateContext
578
+ formerStateContext = stateContext
579
+ stateContext = context
580
+ if (stateContext._hasRun) {
581
+ return
582
+ }
583
+ stateContext._transportContext = {}
584
+ stateContext._static = []
585
+ stateContext._formerContext = formerStateContext
586
+ stateContext._reactiveProps = {}
587
+ stateContext._template = ''
588
+ stateContext._resume = false
589
+ stateContext._hook={
590
+ beforeMount:[],
591
+ reactiveEffect:[],
592
+ effect:[],
593
+ isMount:[],
594
+ isUnMount:[],
595
+ }
596
+ Object.assign(stateContext._transportContext, formerStateContext._transportContext)
597
+ formerStateContext = stateContext
598
+ return stateContext
599
+ }
600
+
601
+ const promiseCallback = (func, main) => {
602
+ const promise = func()
603
+ promise.then(res => {
604
+ main.value = res
605
+ main.failed = false
606
+ main.async = false
607
+ }).catch(error => {
608
+ main.async = false
609
+ main.failed = true
610
+ })
611
+ }
612
+
613
+ /**
614
+ * @param {FunctionConstructor|number|string|null} initialValue
615
+ * Any Function(can be return Promise or string or number) or string,number, null.
616
+ * @param {string|null}section
617
+ * A string for identifing or creating the localStorage(string) or compute value (array)
618
+ * @returns {{value:any,id:string,async?:boolean,failed?:boolean,retry?:()=>void}}
619
+ * notice the async, failed and retry works when Promised is pas into initialValue Function.
620
+ *
621
+ * id is not meant to be touched its pawajs way of tracking state
622
+ */
623
+ export const $state = (initialValue, section = null) => {
624
+ if (!client) {
625
+ return serverInstance.$state?.(initialValue)
626
+ }
627
+
628
+ const id = crypto.randomUUID()
629
+ const states = {
630
+ value: null,
631
+ id: id
632
+ }
633
+ let promise
634
+ if (initialValue instanceof Function) {
635
+ const result = initialValue()
636
+ if (result instanceof Promise) {
637
+ promise = result
638
+ states.async = true
639
+ states.failed = false
640
+ } else {
641
+ states.value = result
642
+ }
643
+ } else {
644
+ states.value = initialValue
645
+ }
646
+ if (typeof section === 'string') {
647
+
648
+ try {
649
+ if (localStorage.getItem(section)) {
650
+ const stored = JSON.parse(sanitizeTemplate(localStorage.getItem(section)))
651
+ states.value = stored.value
652
+
653
+ } else {
654
+ localStorage.setItem(section, JSON.stringify(states))
655
+ }
656
+ } catch (e) {
657
+ console.warn('error while trying to use localStorage')
658
+ }
659
+ }
660
+ let timeOut
661
+ const main = createDeepProxy(states, (target, property) => {
662
+ if (typeof section === 'string') {
663
+ if (timeOut) {
664
+ clearTimeout(timeOut)
665
+ }
666
+ timeOut = setTimeout(() => {
667
+ localStorage.setItem(section, JSON.stringify(states))
668
+ }, 50)
669
+ }
670
+ globalEffectMap.forEach((effect) => {
671
+
672
+ if (effect.deps?.has(target.id)) {
673
+ if (effect.cleanup) {
674
+ effect.cleanup();
675
+ }
676
+ effect.cleanup = effect.callback();
677
+ } else if (effect.deps.size === 0) {
678
+ effect.cleanup = effect.callback();
679
+ }
680
+ });
681
+ });
682
+ if (Array.isArray(section)) {
683
+ if (stateContext._hasRun === false && typeof initialValue === 'function') {
684
+ const cleanup=stateWatch(()=>{
685
+ main.value=initialValue()
686
+ },section)
687
+ stateContext._hook.isUnMount.push(cleanup)
688
+ }else{
689
+ console.error('state compute must be inside a component and initialValue must be a function')
690
+ }
691
+ }
692
+ if (promise instanceof Promise) {
693
+
694
+ promise.then(res => {
695
+ main.value = res
696
+ main.failed = false
697
+ main.async = false
698
+ }).catch(error => {
699
+ main.async = false
700
+ main.failed = true
701
+ })
702
+
703
+ const asyncObject = {
704
+ retry: () => {
705
+ promiseCallback(initialValue, main)
706
+ }
707
+ }
708
+ Object.assign(main, asyncObject)
709
+ return main
710
+ } else {
711
+ return main
712
+ }
713
+
714
+ }
715
+
716
+ const watchCallbacks = new Map();
717
+ const stateWatch = (callback, dependencies) => {
718
+ if (!callback) {
719
+ console.warn('stateWatch: Callback function is required');
720
+ return;
721
+ }
722
+ const dep = new Set()
723
+ if (dependencies) {
724
+ dependencies.forEach(d => {
725
+ dep.add(d.id)
726
+ })
727
+ }
728
+ const effect = {
729
+ callback: () => {
730
+ if (!watchCallbacks.has(callback)) {
731
+ watchCallbacks.set(callback, true);
732
+ Promise.resolve().then(() => {
733
+ if (effect.cleanup) {
734
+ effect.cleanup();
735
+ }
736
+ const result = callback();
737
+ // Handle cleanup function returned from callback
738
+ if (typeof result === 'function') {
739
+ effect.cleanup = result;
740
+ }
741
+ watchCallbacks.delete(callback);
742
+ });
743
+ }
744
+ },
745
+ deps: dep,
746
+ cleanup: null,
747
+ };
748
+
749
+ // Initial run with cleanup handling
750
+ const result = callback();
751
+ if (typeof result === 'function') {
752
+ effect.cleanup = result;
753
+ }
754
+
755
+ globalEffectMap.set(callback, effect);
756
+
757
+ return () => {
758
+ if (effect.cleanup) {
759
+ effect.cleanup();
760
+ }
761
+ globalEffectMap.delete(callback);
762
+ watchCallbacks.delete(callback);
763
+ };
764
+ };
765
+
766
+ export const restoreContext = (state_context) => {
767
+ stateContext = state_context._formerContext
768
+ }
769
+ /**
770
+ *
771
+ * @param {PawaElement|HTMLElement} el
772
+ * @returns null
773
+ */
774
+ const component = (el, resume = false, attr, notRender, stopResume) => {
775
+ if (el._running) {
776
+ return
777
+ }
778
+ el._running = true
779
+
780
+ if (!resume) {
781
+ normal_component(el, stateContext, setStateContext, mapsPlugins, formerStateContext, pawaContext, stateWatch)
782
+ } else {
783
+ stopResume.stop = true
784
+ let name
785
+ let comment
786
+ let endComment
787
+ const children = []
788
+ let serialized
789
+ let id
790
+ const getComment = (node) => {
791
+ if (node.previousSibling.nodeType === 8) {
792
+ const c = node.previousSibling.data.split('+')
793
+ if (c[1] === attr.value) {
794
+ comment = node.previousSibling
795
+ name = c[2]
796
+ serialized = c[3]
797
+ id = c[1]
798
+ } else {
799
+ getComment(node.previousSibling)
800
+ }
801
+
802
+ } else {
803
+ getComment(node.previousSibling)
804
+ }
805
+ }
806
+ const getEndComment = (comment) => {
807
+ const isComment = comment.nextSibling
808
+ if (comment.nextSibling.nodeType === 8) {
809
+ if (isComment.data.split('+')[1] === id) {
810
+ endComment = isComment
811
+ } else {
812
+ getEndComment(isComment)
813
+ }
814
+ } else if (isComment.nodeType === 1) {
815
+ children.push(isComment)
816
+ getEndComment(isComment)
817
+ } else {
818
+ getEndComment(isComment)
819
+ }
820
+ }
821
+ getComment(el)
822
+ getEndComment(comment)
823
+ el.removeAttribute(attr.name)
824
+ const numberComponentChildren = notRender.index + children.length - 1
825
+ notRender.notRender = numberComponentChildren
826
+ resumer.resume_component?.(el, attr, setStateContext, mapsPlugins, formerStateContext, pawaContext, stateWatch, { comment, endComment, name, serialized, id, children })
827
+ }
828
+ }
829
+
830
+ /**
831
+ * @param {PawaElement | HTMLElement} el
832
+ */
833
+ const mainAttribute = (el, exp) => {
834
+ const attrMap = new Map();
835
+ if (el._running) return
836
+ // Store original attribute value
837
+ if (el._hasForOrIf()) {
838
+ return
839
+ }
840
+ if (el._componentName) {
841
+ return
842
+ }
843
+ attrMap.set(exp.name, exp.value);
844
+ el._preRenderAvoid.push(exp.name)
845
+ const removeAttribute = new Set()
846
+ removeAttribute.add('disabled')
847
+ el._mainAttribute[exp.name] = exp.value
848
+ el._checkStatic()
849
+ let enter=false
850
+ const evaluate = () => {
851
+
852
+ try {
853
+ // Always use original value from map for evaluation
854
+ let value = attrMap.get(exp.name);
855
+ let isBoolean
856
+ const regex = /@{([^}]*)}/g;
857
+ const keys = Object.keys(el._context);
858
+ const resolvePath = (path, obj) => {
859
+ return path.split('.').reduce((acc, key) => acc?.[key], obj);
860
+ };
861
+ const values = keys.map((key) => resolvePath(key, el._context));
862
+
863
+ value = value.replace(regex, (match, expression) => {
864
+ if (checkKeywordsExistence(el._staticContext, expression)) {
865
+ return ''
866
+ } else {
867
+ const func = new Function(...keys, `return ${expression}`);
868
+ isBoolean = func(...values)
869
+ if (typeof isBoolean !== 'boolean') {
870
+ return isBoolean
871
+ }else{
872
+ return ''
873
+ }
874
+ }
875
+ });
876
+
877
+ if (removeAttribute.has(exp.name)) {
878
+ if (isBoolean) {
879
+ el.setAttribute(exp.name, '');
880
+ } else {
881
+ el.removeAttribute(exp.name)
882
+ }
883
+ } else {
884
+ if (exp.name === 'class' && !enter) {
885
+ requestAnimationFrame(()=>{
886
+ el.setAttribute(exp.name, value);
887
+ })
888
+ enter=true
889
+ }else{
890
+ el.setAttribute(exp.name, value);
891
+ }
892
+ }
893
+ } catch (error) {
894
+ console.warn(`failed at attribute ${exp.name}`, el)
895
+ setPawaDevError({
896
+ message: `error at attribute ${error.message}`,
897
+ error: error,
898
+ template: el._template
899
+ })
900
+ }
901
+ };
902
+ createEffect(() => {
903
+ evaluate();
904
+ });
905
+ };
906
+
907
+ const textContentHandler = (el, isName) => {
908
+ if (el._hasForOrIf()) {
909
+ return
910
+ }
911
+ if (el._running) {
912
+ return
913
+ }
914
+ const nodesMap = new Map();
915
+
916
+ // Get all text nodes and store their original content
917
+ const textNodes = Array.from(el.childNodes).filter(node => node.nodeType === Node.TEXT_NODE);
918
+ textNodes.forEach(node => {
919
+ nodesMap.set(node, node.nodeValue);
920
+ });
921
+ el._checkStatic()
922
+ const evaluate = () => {
923
+ try {
924
+ textNodes.forEach(textNode => {
925
+ // Always use original content from map for evaluation
926
+ let value = nodesMap.get(textNode);
927
+ const regex = /@{([^}]*)}/g;
928
+
929
+ const keys = Object.keys(el._context);
930
+ const resolvePath = (path, obj) => {
931
+ return path.split('.').reduce((acc, key) => acc?.[key], obj);
932
+ };
933
+ const values = keys.map((key) => resolvePath(key, el._context));
934
+
935
+ value = value.replace(regex, (match, expression) => {
936
+ if (checkKeywordsExistence(el._staticContext, expression)) {
937
+ return ''
938
+ } else {
939
+
940
+ el._textContent[expression] = value
941
+ const func = new Function(...keys, `return ${expression}`);
942
+ return String(func(...values));
943
+ }
944
+ });
945
+ textNode.nodeValue = value;
946
+
947
+ });
948
+ } catch (error) {
949
+ // console.warn(`error at ${el} textcontent`)
950
+ setPawaDevError({
951
+ message: `error at TextContent ${error.message}`,
952
+ error: error,
953
+ template: el._template
954
+ })
955
+ }
956
+ };
957
+
958
+ createEffect(() => {
959
+ evaluate();
960
+ }, el);
961
+ };
962
+
963
+ const template = (el, resume = false, notRender, attr) => {
964
+ if (el._running) {
965
+ return
966
+ }
967
+ el._running = true
968
+ templates(el)
969
+ }
970
+
971
+ const directives = {
972
+ if: If,
973
+ for: For,
974
+ else:(el)=>{el._running =true},
975
+ case:(el)=>{el._running =true},
976
+ default:(el)=>{el._running =true},
977
+ 'else-if':(el)=>{el._running =true},
978
+ mount: mountElement,
979
+ unmount: unMountElement,
980
+ ref: ref,
981
+ switch:Switch,
982
+ key:Key,
983
+ 'is-exit':exitTransition,
984
+ }
985
+ export const useRef = () => {
986
+ return { value: null }
987
+ }
988
+ export const render = (el, contexts = {}, notRender, isName) => {
989
+ const stopResume = { stop: false }
990
+ if (el.tagName === 'SCRIPT') {
991
+ return false
992
+ }
993
+ if (el.tagName === 'TITLE') {
994
+ document.title=el.textContent
995
+ el.remove()
996
+ return
997
+ }
998
+ const context = {
999
+ ...contexts
1000
+ }
1001
+
1002
+ for (const fn of renderBeforePawa) {
1003
+ try {
1004
+ fn(el, context)
1005
+ } catch (error) {
1006
+ __pawaDev.setError({ el: el, msg: error.message })
1007
+ console.error(error.message)
1008
+ }
1009
+ }
1010
+ PawaElement.Element(el, context)
1011
+ el._staticContext = stateContext._static
1012
+ for (const fn of renderAfterPawa) {
1013
+ try {
1014
+ fn(el)
1015
+ } catch (error) {
1016
+ __pawaDev.setError({ el: el, msg: error.message })
1017
+ console.error(error.message)
1018
+ }
1019
+ }
1020
+
1021
+ if (Array.from(el.childNodes).some(node =>
1022
+ node.nodeType === Node.TEXT_NODE && node.nodeValue.includes('@{')
1023
+ ) && !el._avoidPawaRender) {
1024
+ textContentHandler(el, isName)
1025
+ }
1026
+ let startAttribute = false
1027
+ const startObject = {}
1028
+ //get startsWith plugin
1029
+ if (!el._avoidPawaRender) {
1030
+
1031
+ startsWithSet.forEach(starts => {
1032
+
1033
+ el._attributes.forEach(attr => {
1034
+ if (attr.name.startsWith('on:')) {
1035
+ startAttribute = true
1036
+ startObject[attr.name] = starts
1037
+ }
1038
+ })
1039
+ })
1040
+ const number = { notRender: null }
1041
+ el._attributes.forEach(attr => {
1042
+
1043
+ if (stopResume.stop || el._hasRun) return
1044
+ if (directives[attr.name]) {
1045
+ directives[attr.name](el, attr, stateContext)
1046
+ } else if (attr.name.startsWith('on-')) {
1047
+ event(el, attr, stateContext)
1048
+ } else if (attr.value.includes('@{') && !attr.name.startsWith('resume-attr')) {
1049
+ mainAttribute(el, attr, isName)
1050
+ } else if (attr.name.startsWith('state-')) {
1051
+ States(el, attr, getCurrentContext())
1052
+ } else if (attr.name.startsWith('out-')) {
1053
+ documentEvent(el, attr)
1054
+ } else if (attr.name.startsWith('after-[') && attr.name.endsWith(']')) {
1055
+ After(el, attr)
1056
+ }
1057
+ else if (attr.name.startsWith('every-[') && attr.name.endsWith(']')) {
1058
+ Every(el, attr)
1059
+ }
1060
+ else if (attr.name.startsWith('c-c-')) {
1061
+ stopResume.stop = true
1062
+
1063
+ component(el, true, attr, notRender, stopResume)
1064
+ } else if (attr.name.startsWith('c-at-')) {
1065
+ resumer.resume_attribute?.(el, attr, notRender)
1066
+ } else if (attr.name.startsWith('c-$-')) {
1067
+ resumer.resume_state?.(el, attr, notRender)
1068
+ } else if (attr.name.startsWith('c-t')) {
1069
+ resumer.resume_text(el, attr, isName)
1070
+ } else if (attr.name.startsWith('c-if-')) {
1071
+ // console.log('resume -if',el,el._attributes)
1072
+ directives['if'](el, attr, stateContext, true, notRender, stopResume)
1073
+ } else if (attr.name === 'c-for') {
1074
+ directives['for'](el, attr, stateContext, true, notRender, stopResume)
1075
+ } else if (attr.name.startsWith('c-sw-')) {
1076
+ directives['switch'](el, attr, stateContext, true, notRender, stopResume)
1077
+ } else if (attr.name === 'c-for') {
1078
+ directives['for'](el, attr, stateContext, true, notRender, stopResume)
1079
+ } else if (fullNamePlugin.has(attr.name)) {
1080
+ if (externalPlugin[attr.name]) {
1081
+ const plugin = externalPlugin[attr.name]
1082
+ try {
1083
+ if (typeof plugin !== 'function') {
1084
+ console.warn(`${attr.name} plugin must be a function`)
1085
+ return
1086
+ }
1087
+ plugin(el, attr, stateContext, notRender, stopResume)
1088
+ } catch (error) {
1089
+ console.warn(error.message, error.stack)
1090
+ }
1091
+ }
1092
+ } else if (startAttribute) {
1093
+ const name = startObject[attr.name]
1094
+ if (externalPlugin[name]) {
1095
+ const plugin = externalPlugin[name]
1096
+ try {
1097
+ if (typeof plugin !== 'function') {
1098
+ console.warn(`${name} plugin must be a function`)
1099
+ return
1100
+ }
1101
+ plugin(el, attr, stateContext, notRender, stopResume)
1102
+ } catch (error) {
1103
+ console.warn(error.message, error.stack)
1104
+ }
1105
+ }
1106
+ }
1107
+
1108
+
1109
+ })
1110
+ }
1111
+ if (stopResume.stop) return
1112
+ if (el._componentName && !el._avoidPawaRender) {
1113
+ component(el)
1114
+ return
1115
+ }
1116
+ if (el._elementType === 'template' && !el._avoidPawaRender) {
1117
+ template(el)
1118
+ return
1119
+ }
1120
+
1121
+ if (el._out === false || el._running === false || el._componentOrTemplate !== true) {
1122
+
1123
+ if (el._running) {
1124
+ return true
1125
+ }
1126
+ for (const fn of renderBeforeChild) {
1127
+ try {
1128
+ fn(el)
1129
+ } catch (error) {
1130
+ __pawaDev.setError({ el: el, msg: error.message })
1131
+ console.error(error.message)
1132
+ }
1133
+ }
1134
+ const number = { notRender: null, index: null }
1135
+ Array.from(el.children).forEach((child, index) => {
1136
+ number.index = index
1137
+ if (number.notRender && index <= number.notRender) return
1138
+ render(child, context, number, isName)
1139
+ })
1140
+ el._callMount()
1141
+ if (el.hasAttribute('p:c')) {
1142
+ el.removeAttribute('p:c')
1143
+ }
1144
+ }
1145
+ }
1146
+
1147
+ export const pawaStartApp = (app, context = {}) => {
1148
+ render(app, context)
1149
+ }
1150
+
1151
+ /**
1152
+ * Tagged template function for syntax highlighting and future tooling support.
1153
+ * Usage: return html`<div>...</div>`
1154
+ */
1155
+ export const html = (strings, ...values) => {
1156
+ if (strings.length === 1) return strings[0];
1157
+ let result = "";
1158
+ for (let i = 0; i < strings.length; i++) {
1159
+ result += strings[i];
1160
+ if (i < values.length) {
1161
+ result += values[i];
1162
+ }
1163
+ }
1164
+ return result;
1165
+ }
1166
+
1167
+ const Pawa = {
1168
+ useInsert,
1169
+ useContext,
1170
+ useValidateComponent,
1171
+ setPawaAttributes,
1172
+ setContext,
1173
+ $state,
1174
+ pawaStartApp,
1175
+ RegisterComponent,
1176
+ runEffect,
1177
+ html
1178
+ }
1179
+
1180
+ export default Pawa