vaderjs 2.3.12 → 2.3.14

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 (2) hide show
  1. package/index.ts +167 -73
  2. package/package.json +1 -1
package/index.ts CHANGED
@@ -174,12 +174,10 @@ function isSvgElement(fiber: Fiber): boolean {
174
174
  * @param {object} prevProps - The previous properties.
175
175
  * @param {object} nextProps - The new properties.
176
176
  */
177
- function updateDom(dom: Node, prevProps: any, nextProps: any): void {
177
+ function updateDom(dom: Node, prevProps: any, nextProps: any, isSvg: boolean = false): void {
178
178
  prevProps = prevProps || {};
179
179
  nextProps = nextProps || {};
180
180
 
181
- const isSvg = dom instanceof SVGElement;
182
-
183
181
  if (dom.nodeType === Node.TEXT_NODE) {
184
182
  if (prevProps.nodeValue !== nextProps.nodeValue) {
185
183
  (dom as Text).nodeValue = nextProps.nodeValue;
@@ -187,7 +185,7 @@ function updateDom(dom: Node, prevProps: any, nextProps: any): void {
187
185
  return;
188
186
  }
189
187
 
190
- // Handle ref updates - IMPORTANT: This must come BEFORE event listeners
188
+ // Handle ref updates
191
189
  if (prevProps.ref && prevProps.ref !== nextProps.ref) {
192
190
  if (prevProps.ref.current === dom) {
193
191
  prevProps.ref.current = null;
@@ -197,7 +195,7 @@ function updateDom(dom: Node, prevProps: any, nextProps: any): void {
197
195
  nextProps.ref.current = dom;
198
196
  }
199
197
 
200
- // Remove old event listeners
198
+ // Remove old event listeners
201
199
  Object.keys(prevProps)
202
200
  .filter(key => key.startsWith("on"))
203
201
  .filter(key => !(key in nextProps) || isNew(prevProps, nextProps)(key))
@@ -209,13 +207,14 @@ function updateDom(dom: Node, prevProps: any, nextProps: any): void {
209
207
  }
210
208
  });
211
209
 
210
+ // ✅ FIX: Handle className updates properly
212
211
  // Remove old properties
213
212
  Object.keys(prevProps)
214
213
  .filter(isProperty)
215
214
  .filter(isGone(prevProps, nextProps))
216
215
  .forEach(name => {
217
216
  if (name === 'className' || name === 'class') {
218
- (dom as Element).removeAttribute('class');
217
+ (dom as Element).setAttribute('class', '');
219
218
  } else if (name === 'style') {
220
219
  (dom as HTMLElement).style.cssText = '';
221
220
  } else if (name in dom && !isSvg) {
@@ -225,7 +224,7 @@ function updateDom(dom: Node, prevProps: any, nextProps: any): void {
225
224
  }
226
225
  });
227
226
 
228
- // Set new or changed properties
227
+ // ✅ FIX: Set new or changed properties - IMPORTANT FIX for className
229
228
  Object.keys(nextProps)
230
229
  .filter(isProperty)
231
230
  .filter(isNew(prevProps, nextProps))
@@ -236,13 +235,18 @@ function updateDom(dom: Node, prevProps: any, nextProps: any): void {
236
235
  if (typeof value === 'string') {
237
236
  (dom as HTMLElement).style.cssText = value;
238
237
  } else if (typeof value === 'object' && value !== null) {
239
- for (const [key, val] of Object.entries(value)) {
238
+ Object.entries(value).forEach(([key, val]) => {
240
239
  const cssKey = key.replace(/[A-Z]/g, match => `-${match.toLowerCase()}`);
241
240
  (dom as HTMLElement).style[cssKey as any] = val;
242
- }
241
+ });
243
242
  }
244
243
  } else if (name === 'className' || name === 'class') {
245
- (dom as Element).setAttribute('class', value);
244
+ // FIX: Set the entire className, don't append!
245
+ if (value) {
246
+ (dom as Element).setAttribute('class', value);
247
+ } else {
248
+ (dom as Element).removeAttribute('class');
249
+ }
246
250
  } else if (typeof value === 'boolean') {
247
251
  if (value) {
248
252
  (dom as Element).setAttribute(name, '');
@@ -262,7 +266,7 @@ function updateDom(dom: Node, prevProps: any, nextProps: any): void {
262
266
  }
263
267
  });
264
268
 
265
- // Add new event listeners - This is the crucial part!
269
+ // Add new event listeners
266
270
  Object.keys(nextProps)
267
271
  .filter(key => key.startsWith("on"))
268
272
  .filter(isNew(prevProps, nextProps))
@@ -287,11 +291,52 @@ function updateDom(dom: Node, prevProps: any, nextProps: any): void {
287
291
  function commitRoot(): void {
288
292
  deletions.forEach(commitWork);
289
293
  commitWork(wipRoot.child);
294
+
295
+ // Run effects after DOM is committed
296
+ runEffects(wipRoot);
297
+
290
298
  currentRoot = wipRoot;
291
299
  wipRoot = null;
292
300
  isRenderScheduled = false;
293
301
  }
294
302
 
303
+ function runEffects(fiber: Fiber | null): void {
304
+ if (!fiber) return;
305
+
306
+ // Run effects for this fiber
307
+ if (fiber._pendingEffects) {
308
+ fiber._pendingEffects.forEach(({ callback, cleanup, hookIndex }) => {
309
+ // Run cleanup from previous effect
310
+ if (cleanup) {
311
+ try {
312
+ cleanup();
313
+ } catch (err) {
314
+ console.error('Error in effect cleanup:', err);
315
+ }
316
+ }
317
+
318
+ // Run the new effect
319
+ try {
320
+ const newCleanup = callback();
321
+
322
+ // Store the cleanup function in the hook
323
+ if (fiber.hooks && fiber.hooks[hookIndex]) {
324
+ fiber.hooks[hookIndex]._cleanupFn = typeof newCleanup === 'function' ? newCleanup : undefined;
325
+ }
326
+ } catch (err) {
327
+ console.error('Error in effect:', err);
328
+ }
329
+ });
330
+
331
+ // Clear pending effects
332
+ fiber._pendingEffects = [];
333
+ }
334
+
335
+ // Recursively run effects for children
336
+ runEffects(fiber.child);
337
+ runEffects(fiber.sibling);
338
+ }
339
+
295
340
  /**
296
341
  * Recursively commits a fiber and its children to the DOM.
297
342
  * @param {Fiber} fiber - The fiber to commit.
@@ -468,9 +513,14 @@ function updateFunctionComponent(fiber: Fiber) {
468
513
  fiber.hooks = [];
469
514
  }
470
515
 
471
- // Copy hooks from alternate if it exists
516
+ // Copy hooks from alternate but ensure they're fresh
472
517
  if (fiber.alternate?.hooks) {
473
- fiber.hooks = fiber.alternate.hooks.map(hook => ({ ...hook }));
518
+ // Create new hooks array with same structure
519
+ fiber.hooks = fiber.alternate.hooks.map(altHook => {
520
+ // Create a shallow copy to avoid mutation issues
521
+ const newHook = { ...altHook };
522
+ return newHook;
523
+ });
474
524
  }
475
525
 
476
526
  // Call the component function
@@ -487,21 +537,26 @@ function normalizeChildren(children: any, parentFiber: Fiber): VNode[] {
487
537
  if (!children) return [];
488
538
 
489
539
  // Handle arrays, single elements, and conditional rendering
490
- let arr = Array.isArray(children) ? children : [children];
540
+ let arr = Array.isArray(children) ? children.flat() : [children];
491
541
 
492
- return arr.flatMap((child, index) => {
493
- // Skip null, undefined, false, true (conditional rendering)
494
- if (child == null || typeof child === "boolean") {
495
- return [];
496
- }
497
-
542
+ return arr.filter(child => child != null && typeof child !== "boolean").map((child, index) => {
498
543
  if (typeof child === "string" || typeof child === "number") {
499
- return [createTextElement(String(child))];
544
+ return createTextElement(String(child));
500
545
  }
501
546
 
502
- // Ensure every child has a stable key
503
- const key = child.key ?? child.props?.id ?? `${parentFiber.key ?? "root"}-${index}`;
504
- return [{ ...child, key }];
547
+ // FIX: Preserve existing key or create a stable one
548
+ if (typeof child === "object") {
549
+ // If child already has a key, keep it
550
+ if (child.key != null) {
551
+ return child;
552
+ }
553
+
554
+ // Otherwise create a stable key
555
+ const key = `${parentFiber.key || "root"}-${child.type?.name || child.type || "child"}-${index}`;
556
+ return { ...child, key };
557
+ }
558
+
559
+ return child;
505
560
  });
506
561
  }
507
562
  /**
@@ -510,9 +565,11 @@ function normalizeChildren(children: any, parentFiber: Fiber): VNode[] {
510
565
  */
511
566
  function updateHostComponent(fiber: Fiber): void {
512
567
  if (!fiber.dom) fiber.dom = createDom(fiber);
513
-
514
- const children = normalizeChildren(fiber.props.children, fiber);
568
+ if(fiber?.props?.children)
569
+ {
570
+ const children = normalizeChildren(fiber.props.children, fiber);
515
571
  reconcileChildren(fiber, children);
572
+ }
516
573
  }
517
574
 
518
575
  /**
@@ -525,19 +582,18 @@ function reconcileChildren(wipFiber: Fiber, elements: VNode[]) {
525
582
  let oldFiber = wipFiber.alternate?.child;
526
583
  let prevSibling: Fiber | null = null;
527
584
 
528
- // Build a map of existing fibers by key
585
+ // ✅ FIX: Build a map of existing fibers by key
529
586
  const existingFibers = new Map<string | number | null, Fiber>();
530
- while (oldFiber) {
531
- const key = oldFiber.key ?? `index-${index}`;
532
- existingFibers.set(key, oldFiber);
533
- oldFiber = oldFiber.sibling;
534
- index++;
587
+ let tempOldFiber = oldFiber;
588
+ let tempIndex = 0;
589
+ while (tempOldFiber) {
590
+ const key = tempOldFiber.key ?? `index-${tempIndex}`;
591
+ existingFibers.set(key, tempOldFiber);
592
+ tempOldFiber = tempOldFiber.sibling;
593
+ tempIndex++;
535
594
  }
536
595
 
537
- index = 0;
538
- let newChildFiber: Fiber | null = null;
539
-
540
- // Process each element
596
+ // FIX: Process each element in order
541
597
  for (let i = 0; i < elements.length; i++) {
542
598
  const element = elements[i];
543
599
 
@@ -546,13 +602,16 @@ function reconcileChildren(wipFiber: Fiber, elements: VNode[]) {
546
602
  continue;
547
603
  }
548
604
 
605
+ // ✅ FIX: Use the same key logic as when building the map
549
606
  const key = element.key ?? `index-${index}`;
550
607
  const oldFiber = existingFibers.get(key);
551
608
 
552
609
  const sameType = oldFiber && element.type === oldFiber.type;
553
610
 
611
+ let newChildFiber: Fiber | null = null;
612
+
554
613
  if (sameType) {
555
- // Update existing fiber
614
+ // ✅ FIX: Update existing fiber
556
615
  newChildFiber = {
557
616
  type: oldFiber.type,
558
617
  props: element.props,
@@ -568,18 +627,16 @@ function reconcileChildren(wipFiber: Fiber, elements: VNode[]) {
568
627
  existingFibers.delete(key);
569
628
  } else {
570
629
  // Create new fiber
571
- if (element) {
572
- newChildFiber = {
573
- type: element.type,
574
- props: element.props,
575
- dom: null,
576
- parent: wipFiber,
577
- alternate: null,
578
- effectTag: "PLACEMENT",
579
- key,
580
- ref: element.ref,
581
- };
582
- }
630
+ newChildFiber = {
631
+ type: element.type,
632
+ props: element.props,
633
+ dom: null,
634
+ parent: wipFiber,
635
+ alternate: null,
636
+ effectTag: "PLACEMENT",
637
+ key,
638
+ ref: element.ref,
639
+ };
583
640
 
584
641
  // Mark old fiber for deletion if it exists
585
642
  if (oldFiber) {
@@ -600,7 +657,7 @@ function reconcileChildren(wipFiber: Fiber, elements: VNode[]) {
600
657
  }
601
658
  }
602
659
 
603
- // Mark any remaining old fibers for deletion
660
+ // ✅ FIX: Mark any remaining old fibers for deletion
604
661
  existingFibers.forEach(fiber => {
605
662
  fiber.effectTag = "DELETION";
606
663
  deletions.push(fiber);
@@ -677,17 +734,16 @@ export function useStableRef<T>(initialValue: T | null = null): { current: T | n
677
734
  * @param {T|(() => T)} initial - The initial state value or initializer function.
678
735
  * @returns {[T, (action: T | ((prevState: T) => T)) => void]} A stateful value and a function to update it.
679
736
  */
680
-
681
737
 
682
-
683
- // Add this to your useState hook to ensure re-renders
684
738
  export function useState<T>(initial: T | (() => T)): [T, (action: T | ((prevState: T) => T)) => void] {
685
739
  if (!wipFiber) {
686
740
  throw new Error("Hooks can only be called inside a Vader.js function component.");
687
741
  }
688
742
 
689
743
  const currentHookIndex = hookIndex;
690
- let hook = wipFiber.hooks[currentHookIndex];
744
+ const currentFiber = wipFiber; // Capture current fiber
745
+
746
+ let hook = currentFiber.hooks[currentHookIndex];
691
747
 
692
748
  if (!hook) {
693
749
  hook = {
@@ -695,27 +751,66 @@ export function useState<T>(initial: T | (() => T)): [T, (action: T | ((prevStat
695
751
  queue: [],
696
752
  _needsUpdate: false
697
753
  };
698
- wipFiber.hooks[currentHookIndex] = hook;
754
+ currentFiber.hooks[currentHookIndex] = hook;
699
755
  }
700
756
 
757
+ // Create setState that captures current hook and fiber
701
758
  const setState = (action: T | ((prevState: T) => T)) => {
702
- // Calculate new state
703
- const newState = typeof action === "function"
704
- ? (action as (prevState: T) => T)(hook.state)
705
- : action;
706
-
707
- if (!Object.is(hook.state, newState)) {
708
- hook.state = newState;
759
+ // Use setTimeout to ensure we're outside the current render cycle
760
+ setTimeout(() => {
761
+ // Re-find the hook in the current fiber (in case it moved)
762
+ const fiber = currentRoot || wipRoot;
763
+ if (!fiber) return;
709
764
 
710
- // Schedule a re-render starting from the component's fiber
711
- scheduleRender();
712
- }
765
+ // Find the component fiber that owns this hook
766
+ let targetFiber = findFiberWithHook(fiber, currentFiber, currentHookIndex);
767
+ if (!targetFiber || !targetFiber.hooks) return;
768
+
769
+ const targetHook = targetFiber.hooks[currentHookIndex];
770
+ if (!targetHook) return;
771
+
772
+ // Calculate new state
773
+ const newState = typeof action === "function"
774
+ ? (action as (prevState: T) => T)(targetHook.state)
775
+ : action;
776
+
777
+ if (!Object.is(targetHook.state, newState)) {
778
+ targetHook.state = newState;
779
+ targetHook._needsUpdate = true;
780
+
781
+ // Schedule a re-render
782
+ scheduleRender();
783
+ }
784
+ }, 0);
713
785
  };
714
786
 
715
787
  hookIndex++;
716
788
  return [hook.state, setState];
717
789
  }
718
790
 
791
+ // Helper to find the fiber containing a specific hook
792
+ function findFiberWithHook(root: Fiber, targetFiber: Fiber, hookIndex: number): Fiber | null {
793
+ // Simple BFS to find the fiber
794
+ let queue: Fiber[] = [root];
795
+
796
+ while (queue.length > 0) {
797
+ const fiber = queue.shift()!;
798
+
799
+ // Check if this is our target fiber
800
+ if (fiber === targetFiber ||
801
+ (fiber.type === targetFiber.type &&
802
+ fiber.key === targetFiber.key)) {
803
+ return fiber;
804
+ }
805
+
806
+ // Add children to queue
807
+ if (fiber.child) queue.push(fiber.child);
808
+ if (fiber.sibling) queue.push(fiber.sibling);
809
+ }
810
+
811
+ return null;
812
+ }
813
+
719
814
  /**
720
815
  * Schedules a re-render of the entire app
721
816
  */
@@ -756,15 +851,14 @@ export function useEffect(callback: Function, deps?: any[]): void {
756
851
  deps.some((dep, i) => !Object.is(dep, hook.deps[i]));
757
852
 
758
853
  if (hasChanged) {
759
- if (hook._cleanupFn) {
760
- hook._cleanupFn();
761
- }
854
+ // Schedule effect to run after render
762
855
  setTimeout(() => {
763
- const newCleanup = callback();
764
- if (typeof newCleanup === 'function') {
765
- hook._cleanupFn = newCleanup;
766
- } else {
767
- hook._cleanupFn = undefined;
856
+ if (hook._cleanupFn) {
857
+ hook._cleanupFn();
858
+ }
859
+ const cleanup = callback();
860
+ if (typeof cleanup === 'function') {
861
+ hook._cleanupFn = cleanup;
768
862
  }
769
863
  }, 0);
770
864
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vaderjs",
3
- "version": "2.3.12",
3
+ "version": "2.3.14",
4
4
  "description": "A simple and powerful JavaScript library for building modern web applications.",
5
5
  "bin": {
6
6
  "vaderjs": "./main.js"