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.
- package/index.ts +167 -73
- 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
|
|
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
|
-
//
|
|
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).
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
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
|
|
516
|
+
// Copy hooks from alternate but ensure they're fresh
|
|
472
517
|
if (fiber.alternate?.hooks) {
|
|
473
|
-
|
|
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.
|
|
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
|
|
544
|
+
return createTextElement(String(child));
|
|
500
545
|
}
|
|
501
546
|
|
|
502
|
-
//
|
|
503
|
-
|
|
504
|
-
|
|
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
|
-
|
|
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
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
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
|
-
|
|
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
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
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
|
-
//
|
|
711
|
-
|
|
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
|
-
|
|
760
|
-
hook._cleanupFn();
|
|
761
|
-
}
|
|
854
|
+
// Schedule effect to run after render
|
|
762
855
|
setTimeout(() => {
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
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
|
}
|