vaderjs-native 1.0.35 → 1.0.37
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 +494 -340
- package/main.ts +36 -1
- package/package.json +1 -1
package/index.ts
CHANGED
|
@@ -4,14 +4,14 @@
|
|
|
4
4
|
dialogResolver = null;
|
|
5
5
|
}
|
|
6
6
|
};
|
|
7
|
-
window.nativeHttpCallbacks = {};
|
|
7
|
+
window.nativeHttpCallbacks = {};
|
|
8
8
|
|
|
9
9
|
window.nativeHttpResponse = (response) => {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
10
|
+
const callback = window.nativeHttpCallbacks[response.id];
|
|
11
|
+
if (callback) {
|
|
12
|
+
callback(response);
|
|
13
|
+
delete window.nativeHttpCallbacks[response.id]; // Only delete the specific ID
|
|
14
|
+
}
|
|
15
15
|
};
|
|
16
16
|
|
|
17
17
|
// Add these near the top of your file (after imports)
|
|
@@ -21,18 +21,18 @@ let globalErrorHandler: ((error: Error, componentStack?: string) => void) | null
|
|
|
21
21
|
// Enable dev mode automatically in web environment
|
|
22
22
|
if (typeof window !== 'undefined') {
|
|
23
23
|
// Check for dev mode (you could also check URL or localStorage)
|
|
24
|
-
isDev = window.location.hostname === 'localhost' ||
|
|
25
|
-
|
|
26
|
-
|
|
24
|
+
isDev = window.location.hostname === 'localhost' ||
|
|
25
|
+
window.location.hostname === '127.0.0.1' ||
|
|
26
|
+
window.location.protocol === 'http:';
|
|
27
27
|
}
|
|
28
28
|
|
|
29
29
|
// Error Boundary Component
|
|
30
30
|
// Error Boundary Component - FIXED VERSION
|
|
31
|
-
export function ErrorBoundary({
|
|
32
|
-
children,
|
|
31
|
+
export function ErrorBoundary({
|
|
32
|
+
children,
|
|
33
33
|
fallback,
|
|
34
|
-
onError
|
|
35
|
-
}: {
|
|
34
|
+
onError
|
|
35
|
+
}: {
|
|
36
36
|
children: VNode | VNode[];
|
|
37
37
|
fallback?: (error: Error, reset: () => void) => VNode;
|
|
38
38
|
onError?: (error: Error, errorInfo: { componentStack: string }) => void;
|
|
@@ -47,7 +47,7 @@ export function ErrorBoundary({
|
|
|
47
47
|
if (fallback) {
|
|
48
48
|
return fallback(errorObj, () => window.location.reload());
|
|
49
49
|
}
|
|
50
|
-
|
|
50
|
+
|
|
51
51
|
return createElement(
|
|
52
52
|
"div",
|
|
53
53
|
{
|
|
@@ -66,11 +66,11 @@ export function ErrorBoundary({
|
|
|
66
66
|
}
|
|
67
67
|
|
|
68
68
|
// Inner component that uses hooks
|
|
69
|
-
function ErrorBoundaryInner({
|
|
70
|
-
children,
|
|
69
|
+
function ErrorBoundaryInner({
|
|
70
|
+
children,
|
|
71
71
|
fallback,
|
|
72
|
-
onError
|
|
73
|
-
}: {
|
|
72
|
+
onError
|
|
73
|
+
}: {
|
|
74
74
|
children: VNode | VNode[];
|
|
75
75
|
fallback?: (error: Error, reset: () => void) => VNode;
|
|
76
76
|
onError?: (error: Error, errorInfo: { componentStack: string }) => void;
|
|
@@ -122,7 +122,7 @@ function ErrorBoundaryInner({
|
|
|
122
122
|
if (fallback) {
|
|
123
123
|
return fallback(error, resetError);
|
|
124
124
|
}
|
|
125
|
-
|
|
125
|
+
|
|
126
126
|
return createElement(
|
|
127
127
|
"div",
|
|
128
128
|
{
|
|
@@ -142,36 +142,42 @@ function ErrorBoundaryInner({
|
|
|
142
142
|
flexDirection: 'column'
|
|
143
143
|
}
|
|
144
144
|
},
|
|
145
|
-
createElement("h1", {
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
145
|
+
createElement("h1", {
|
|
146
|
+
style: {
|
|
147
|
+
color: '#ff6b6b',
|
|
148
|
+
marginBottom: '20px',
|
|
149
|
+
fontSize: '24px'
|
|
150
|
+
}
|
|
151
|
+
}, "⚠️ Application Error"),
|
|
152
|
+
|
|
153
|
+
createElement("div", {
|
|
154
|
+
style: {
|
|
155
|
+
backgroundColor: '#2a2a2a',
|
|
156
|
+
padding: '15px',
|
|
157
|
+
borderRadius: '5px',
|
|
158
|
+
marginBottom: '10px',
|
|
159
|
+
overflow: 'auto'
|
|
160
|
+
}
|
|
161
|
+
},
|
|
162
|
+
createElement("pre", { style: { margin: 0, whiteSpace: 'pre-wrap' } },
|
|
159
163
|
error.toString() + (error.stack ? '\n\nStack trace:\n' + error.stack : '')
|
|
160
164
|
)
|
|
161
165
|
),
|
|
162
|
-
|
|
163
|
-
errorInfo && createElement("div", {
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
166
|
+
|
|
167
|
+
errorInfo && createElement("div", {
|
|
168
|
+
style: {
|
|
169
|
+
backgroundColor: '#2a2a2a',
|
|
170
|
+
padding: '15px',
|
|
171
|
+
borderRadius: '5px',
|
|
172
|
+
marginBottom: '10px',
|
|
173
|
+
overflow: 'auto'
|
|
174
|
+
}
|
|
175
|
+
},
|
|
176
|
+
createElement("pre", { style: { margin: 0, whiteSpace: 'pre-wrap', fontSize: '12px' } },
|
|
171
177
|
"Component stack:\n" + errorInfo.componentStack
|
|
172
178
|
)
|
|
173
179
|
),
|
|
174
|
-
|
|
180
|
+
|
|
175
181
|
createElement("div", { style: { display: 'flex', gap: '10px', marginTop: '20px' } },
|
|
176
182
|
createElement("button", {
|
|
177
183
|
style: {
|
|
@@ -194,11 +200,11 @@ function ErrorBoundaryInner({
|
|
|
194
200
|
};
|
|
195
201
|
deletions = [];
|
|
196
202
|
nextUnitOfWork = wipRoot;
|
|
197
|
-
|
|
203
|
+
requestIdleCallback(workLoop);
|
|
198
204
|
}
|
|
199
205
|
}
|
|
200
206
|
}, "Try Again"),
|
|
201
|
-
|
|
207
|
+
|
|
202
208
|
isDev && createElement("button", {
|
|
203
209
|
style: {
|
|
204
210
|
backgroundColor: '#4a90e2',
|
|
@@ -211,8 +217,8 @@ function ErrorBoundaryInner({
|
|
|
211
217
|
},
|
|
212
218
|
onClick: () => {
|
|
213
219
|
// Copy error to clipboard
|
|
214
|
-
const errorText = error.toString() + (error.stack ? '\n\n' + error.stack : '') +
|
|
215
|
-
|
|
220
|
+
const errorText = error.toString() + (error.stack ? '\n\n' + error.stack : '') +
|
|
221
|
+
(errorInfo ? '\n\nComponent stack:\n' + errorInfo.componentStack : '');
|
|
216
222
|
navigator.clipboard.writeText(errorText).then(() => {
|
|
217
223
|
showToast('Error copied to clipboard', 2000);
|
|
218
224
|
});
|
|
@@ -257,7 +263,7 @@ export function reportError(error: Error, componentStack?: string) {
|
|
|
257
263
|
if (componentStack) {
|
|
258
264
|
console.error('[VaderJS Component Stack]', componentStack);
|
|
259
265
|
}
|
|
260
|
-
|
|
266
|
+
|
|
261
267
|
// Display error in UI for Android/Windows where console might not be visible
|
|
262
268
|
if (platform() !== 'web') {
|
|
263
269
|
if (globalErrorHandler) {
|
|
@@ -279,21 +285,25 @@ export function renderWithErrorBoundary(element: VNode, container: Node) {
|
|
|
279
285
|
fallback: (error: Error, reset: () => void) => {
|
|
280
286
|
return createElement(
|
|
281
287
|
"div",
|
|
282
|
-
{
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
288
|
+
{
|
|
289
|
+
style: {
|
|
290
|
+
padding: '20px',
|
|
291
|
+
backgroundColor: '#f8d7da',
|
|
292
|
+
color: '#721c24',
|
|
293
|
+
border: '1px solid #f5c6cb',
|
|
294
|
+
borderRadius: '5px',
|
|
295
|
+
margin: '20px'
|
|
296
|
+
}
|
|
297
|
+
},
|
|
290
298
|
createElement("h3", { style: { marginTop: 0 } }, "App Error"),
|
|
291
|
-
createElement("pre", {
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
299
|
+
createElement("pre", {
|
|
300
|
+
style: {
|
|
301
|
+
backgroundColor: '#f5f5f5',
|
|
302
|
+
padding: '10px',
|
|
303
|
+
borderRadius: '3px',
|
|
304
|
+
overflow: 'auto'
|
|
305
|
+
}
|
|
306
|
+
}, error.toString()),
|
|
297
307
|
createElement("button", {
|
|
298
308
|
onClick: reset,
|
|
299
309
|
style: {
|
|
@@ -334,76 +344,76 @@ export function navigate(path) {
|
|
|
334
344
|
* @returns Promise<{status: number, body: any}>
|
|
335
345
|
*/
|
|
336
346
|
export async function nativeHttp(options: {
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
347
|
+
url: string,
|
|
348
|
+
method?: string,
|
|
349
|
+
headers?: Record<string, string>,
|
|
350
|
+
body?: any
|
|
341
351
|
}): Promise<{ status: number, body: any }> {
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
}
|
|
357
|
-
};
|
|
358
|
-
|
|
359
|
-
const request = {
|
|
360
|
-
id,
|
|
361
|
-
url: options.url,
|
|
362
|
-
method: options.method ?? "GET",
|
|
363
|
-
headers: options.headers ?? {},
|
|
364
|
-
body: options.body ? JSON.stringify(options.body) : null
|
|
365
|
-
};
|
|
366
|
-
|
|
367
|
-
window.Android.nativeHttp(JSON.stringify(request));
|
|
368
|
-
return;
|
|
352
|
+
const id = Math.random().toString(36).substring(2);
|
|
353
|
+
const platformName = platform(); // "android", "windows", "web"
|
|
354
|
+
|
|
355
|
+
return new Promise<any>((resolve, reject) => {
|
|
356
|
+
if (platformName === "android" && window.Android) {
|
|
357
|
+
|
|
358
|
+
// 2. Register this specific request's resolver
|
|
359
|
+
window.nativeHttpCallbacks[id] = (response) => {
|
|
360
|
+
if (response.success) {
|
|
361
|
+
let parsedBody = response.body;
|
|
362
|
+
try { parsedBody = JSON.parse(response.body); } catch { }
|
|
363
|
+
resolve({ status: response.status, body: parsedBody });
|
|
364
|
+
} else {
|
|
365
|
+
reject(new Error(response.error || "HTTP Request Failed"));
|
|
369
366
|
}
|
|
367
|
+
};
|
|
370
368
|
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
return;
|
|
369
|
+
const request = {
|
|
370
|
+
id,
|
|
371
|
+
url: options.url,
|
|
372
|
+
method: options.method ?? "GET",
|
|
373
|
+
headers: options.headers ?? {},
|
|
374
|
+
body: options.body ? JSON.stringify(options.body) : null
|
|
375
|
+
};
|
|
376
|
+
|
|
377
|
+
window.Android.nativeHttp(JSON.stringify(request));
|
|
378
|
+
return;
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
if (platformName === "windows" && isWebView) {
|
|
382
|
+
function handler(event: any) {
|
|
383
|
+
const data = event.data;
|
|
384
|
+
if (data.id === id) {
|
|
385
|
+
window.chrome.webview.removeEventListener("message", handler);
|
|
386
|
+
data.data.body = JSON.parse(data.data.body);
|
|
387
|
+
if (data.error) reject(new Error(data.error));
|
|
388
|
+
else resolve(data.data);
|
|
392
389
|
}
|
|
390
|
+
}
|
|
391
|
+
window.chrome.webview.addEventListener("message", handler);
|
|
392
|
+
|
|
393
|
+
window.chrome.webview.postMessage({
|
|
394
|
+
command: "http",
|
|
395
|
+
id,
|
|
396
|
+
url: options.url,
|
|
397
|
+
method: options.method ?? "GET",
|
|
398
|
+
headers: options.headers ?? {},
|
|
399
|
+
body: options.body ?? null
|
|
400
|
+
});
|
|
401
|
+
return;
|
|
402
|
+
}
|
|
393
403
|
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
404
|
+
// Web fallback
|
|
405
|
+
fetch(options.url, {
|
|
406
|
+
method: options.method ?? "GET",
|
|
407
|
+
headers: options.headers,
|
|
408
|
+
body: options.body ? JSON.stringify(options.body) : undefined
|
|
409
|
+
})
|
|
410
|
+
.then(async res => {
|
|
411
|
+
const contentType = res.headers.get("Content-Type") || "";
|
|
412
|
+
const body = contentType.includes("application/json") ? await res.json() : await res.text();
|
|
413
|
+
resolve({ status: res.status, body });
|
|
414
|
+
})
|
|
415
|
+
.catch(reject);
|
|
416
|
+
});
|
|
407
417
|
}
|
|
408
418
|
|
|
409
419
|
/**
|
|
@@ -446,7 +456,7 @@ let deletions: Fiber[] | null = null;
|
|
|
446
456
|
let wipFiber: Fiber | null = null;
|
|
447
457
|
let hookIndex = 0;
|
|
448
458
|
let isRenderScheduled = false;
|
|
449
|
-
|
|
459
|
+
|
|
450
460
|
|
|
451
461
|
|
|
452
462
|
interface Fiber {
|
|
@@ -455,6 +465,7 @@ interface Fiber {
|
|
|
455
465
|
props: {
|
|
456
466
|
children: VNode[];
|
|
457
467
|
[key: string]: any;
|
|
468
|
+
ref?: { current: any };
|
|
458
469
|
};
|
|
459
470
|
parent?: Fiber;
|
|
460
471
|
child?: Fiber;
|
|
@@ -474,6 +485,7 @@ export interface VNode {
|
|
|
474
485
|
props: {
|
|
475
486
|
children: VNode[];
|
|
476
487
|
[key: string]: any;
|
|
488
|
+
ref?: { current: any };
|
|
477
489
|
};
|
|
478
490
|
key?: string | number | null;
|
|
479
491
|
}
|
|
@@ -534,12 +546,18 @@ function createDom(fiber: Fiber): Node {
|
|
|
534
546
|
: document.createElement(fiber.type as string);
|
|
535
547
|
}
|
|
536
548
|
|
|
537
|
-
//
|
|
549
|
+
// Initialize ref to null first
|
|
538
550
|
if (fiber.props.ref) {
|
|
539
|
-
fiber.props.ref.current =
|
|
551
|
+
fiber.props.ref.current = null;
|
|
540
552
|
}
|
|
541
553
|
|
|
542
554
|
updateDom(dom, {}, fiber.props);
|
|
555
|
+
|
|
556
|
+
// Now assign the DOM element to ref
|
|
557
|
+
if (fiber.props.ref) {
|
|
558
|
+
fiber.props.ref.current = dom;
|
|
559
|
+
}
|
|
560
|
+
|
|
543
561
|
return dom;
|
|
544
562
|
}
|
|
545
563
|
|
|
@@ -574,7 +592,17 @@ function createWebViewSecureStore() {
|
|
|
574
592
|
const data = event.data;
|
|
575
593
|
if (data?.id === id) {
|
|
576
594
|
window.chrome.webview.removeEventListener("message", handler);
|
|
577
|
-
|
|
595
|
+
if (data.error) {
|
|
596
|
+
reject(new Error(data.error))
|
|
597
|
+
} else {
|
|
598
|
+
try {
|
|
599
|
+
resolve(JSON.parse(data.data));
|
|
600
|
+
} catch {
|
|
601
|
+
resolve(data.data); // plain string fallback
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
|
|
578
606
|
}
|
|
579
607
|
}
|
|
580
608
|
|
|
@@ -667,68 +695,68 @@ function createBrowserSecureStore() {
|
|
|
667
695
|
/* ─────────────────────────────── */
|
|
668
696
|
|
|
669
697
|
function createAndroidSecureStore() {
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
698
|
+
return {
|
|
699
|
+
async set(key: string, value: any) {
|
|
700
|
+
try {
|
|
701
|
+
const result = window.Android.secureStoreSet(key, JSON.stringify(value));
|
|
702
|
+
return result === true || result === "true";
|
|
703
|
+
} catch (err) {
|
|
704
|
+
console.error("Android secureStore set error:", err);
|
|
705
|
+
return false;
|
|
706
|
+
}
|
|
707
|
+
},
|
|
708
|
+
async get(key: string) {
|
|
709
|
+
try {
|
|
710
|
+
const result = window.Android.secureStoreGet(key);
|
|
711
|
+
return result ? JSON.parse(result) : null;
|
|
712
|
+
} catch (err) {
|
|
713
|
+
console.error("Android secureStore get error:", err);
|
|
714
|
+
return null;
|
|
715
|
+
}
|
|
716
|
+
},
|
|
717
|
+
async delete(key: string) {
|
|
718
|
+
try {
|
|
719
|
+
const result = window.Android.secureStoreDelete(key);
|
|
720
|
+
return result === true || result === "true";
|
|
721
|
+
} catch (err) {
|
|
722
|
+
console.error("Android secureStore delete error:", err);
|
|
723
|
+
return false;
|
|
724
|
+
}
|
|
725
|
+
},
|
|
726
|
+
async clear() {
|
|
727
|
+
try {
|
|
728
|
+
const result = window.Android.secureStoreClear();
|
|
729
|
+
return result === true || result === "true";
|
|
730
|
+
} catch (err) {
|
|
731
|
+
console.error("Android secureStore clear error:", err);
|
|
732
|
+
return false;
|
|
733
|
+
}
|
|
734
|
+
},
|
|
735
|
+
async getAll() {
|
|
736
|
+
try {
|
|
737
|
+
const result = window.Android.secureStoreGetAll();
|
|
738
|
+
return result ? JSON.parse(result) : {};
|
|
739
|
+
} catch (err) {
|
|
740
|
+
console.error("Android secureStore getAll error:", err);
|
|
741
|
+
return {};
|
|
742
|
+
}
|
|
743
|
+
},
|
|
744
|
+
async isAvailable() {
|
|
745
|
+
return typeof window.Android?.secureStoreSet === "function";
|
|
746
|
+
}
|
|
747
|
+
};
|
|
720
748
|
}
|
|
721
749
|
|
|
722
750
|
// Final export
|
|
723
751
|
export const secureStore = (() => {
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
}
|
|
728
|
-
if (isWebView) return createWebViewSecureStore();
|
|
729
|
-
return createBrowserSecureStore();
|
|
752
|
+
if (typeof window !== "undefined") {
|
|
753
|
+
if (platform() === "android" && window.Android?.secureStoreSet) {
|
|
754
|
+
return createAndroidSecureStore();
|
|
730
755
|
}
|
|
756
|
+
if (isWebView) return createWebViewSecureStore();
|
|
731
757
|
return createBrowserSecureStore();
|
|
758
|
+
}
|
|
759
|
+
return createBrowserSecureStore();
|
|
732
760
|
})();
|
|
733
761
|
|
|
734
762
|
/**
|
|
@@ -737,12 +765,24 @@ export const secureStore = (() => {
|
|
|
737
765
|
* @param {object} prevProps - The previous properties.
|
|
738
766
|
* @param {object} nextProps - The new properties.
|
|
739
767
|
*/
|
|
740
|
-
function updateDom(dom: Node, prevProps: any, nextProps: any): void {
|
|
768
|
+
function updateDom(dom: Node, prevProps: any, nextProps: any): void {
|
|
741
769
|
prevProps = prevProps || {};
|
|
742
770
|
nextProps = nextProps || {};
|
|
743
771
|
|
|
744
772
|
const isSvg = dom instanceof SVGElement;
|
|
745
773
|
|
|
774
|
+
// Handle ref changes
|
|
775
|
+
if (prevProps.ref !== nextProps.ref) {
|
|
776
|
+
// Clear old ref
|
|
777
|
+
if (prevProps.ref) {
|
|
778
|
+
prevProps.ref.current = null;
|
|
779
|
+
}
|
|
780
|
+
// Set new ref
|
|
781
|
+
if (nextProps.ref) {
|
|
782
|
+
nextProps.ref.current = dom;
|
|
783
|
+
}
|
|
784
|
+
}
|
|
785
|
+
|
|
746
786
|
// Remove old or changed event listeners
|
|
747
787
|
Object.keys(prevProps)
|
|
748
788
|
.filter(isEvent)
|
|
@@ -763,6 +803,8 @@ function updateDom(dom: Node, prevProps: any, nextProps: any): void {
|
|
|
763
803
|
(dom as Element).removeAttribute('class');
|
|
764
804
|
} else if (name === 'style') {
|
|
765
805
|
(dom as HTMLElement).style.cssText = '';
|
|
806
|
+
} else if (name === 'ref') {
|
|
807
|
+
// Already handled above
|
|
766
808
|
} else {
|
|
767
809
|
if (isSvg) {
|
|
768
810
|
(dom as Element).removeAttribute(name);
|
|
@@ -788,6 +830,8 @@ function updateDom(dom: Node, prevProps: any, nextProps: any): void {
|
|
|
788
830
|
}
|
|
789
831
|
} else if (name === 'className' || name === 'class') {
|
|
790
832
|
(dom as Element).setAttribute('class', nextProps[name]);
|
|
833
|
+
} else if (name === 'ref') {
|
|
834
|
+
// Already handled above
|
|
791
835
|
} else {
|
|
792
836
|
if (isSvg) {
|
|
793
837
|
(dom as Element).setAttribute(name, nextProps[name]);
|
|
@@ -808,24 +852,10 @@ function updateDom(dom: Node, prevProps: any, nextProps: any): void {
|
|
|
808
852
|
(dom as Element).addEventListener(eventType, handler);
|
|
809
853
|
}
|
|
810
854
|
});
|
|
811
|
-
|
|
812
|
-
Object.keys(nextProps)
|
|
813
|
-
.filter(isEvent)
|
|
814
|
-
.filter(isNew(prevProps, nextProps))
|
|
815
|
-
.forEach(name => {
|
|
816
|
-
const eventType = name.toLowerCase().substring(2);
|
|
817
|
-
const handler = nextProps[name];
|
|
818
|
-
if (typeof handler === 'function') {
|
|
819
|
-
// Remove old listener first if it exists
|
|
820
|
-
if (prevProps[name]) {
|
|
821
|
-
dom.removeEventListener(eventType, prevProps[name]);
|
|
822
|
-
}
|
|
823
|
-
// Add new listener with passive: true for better performance
|
|
824
|
-
dom.addEventListener(eventType, handler, { passive: true });
|
|
825
|
-
}
|
|
826
|
-
});
|
|
827
855
|
}
|
|
828
856
|
|
|
857
|
+
|
|
858
|
+
|
|
829
859
|
|
|
830
860
|
/**
|
|
831
861
|
* Commits the entire work-in-progress tree to the DOM.
|
|
@@ -855,6 +885,7 @@ function commitWork(fiber: Fiber | null): void {
|
|
|
855
885
|
|
|
856
886
|
if (fiber.effectTag === "PLACEMENT" && fiber.dom != null) {
|
|
857
887
|
if (domParent) domParent.appendChild(fiber.dom);
|
|
888
|
+
// Ref already set in createDom
|
|
858
889
|
} else if (fiber.effectTag === "UPDATE" && fiber.dom != null) {
|
|
859
890
|
updateDom(fiber.dom, fiber.alternate?.props ?? {}, fiber.props);
|
|
860
891
|
} else if (fiber.effectTag === "DELETION") {
|
|
@@ -870,9 +901,13 @@ function commitWork(fiber: Fiber | null): void {
|
|
|
870
901
|
* @param {Fiber} fiber - The fiber to remove.
|
|
871
902
|
*/
|
|
872
903
|
function commitDeletion(fiber: Fiber | null): void {
|
|
873
|
-
if (!fiber)
|
|
874
|
-
|
|
904
|
+
if (!fiber) return;
|
|
905
|
+
|
|
906
|
+
// Only clear ref if this is an actual deletion (not just an update)
|
|
907
|
+
if (fiber.effectTag === "DELETION" && fiber.props?.ref) {
|
|
908
|
+
fiber.props.ref.current = null;
|
|
875
909
|
}
|
|
910
|
+
|
|
876
911
|
if (fiber.dom) {
|
|
877
912
|
if (fiber.dom.parentNode) {
|
|
878
913
|
fiber.dom.parentNode.removeChild(fiber.dom);
|
|
@@ -881,51 +916,104 @@ function commitDeletion(fiber: Fiber | null): void {
|
|
|
881
916
|
commitDeletion(fiber.child);
|
|
882
917
|
}
|
|
883
918
|
}
|
|
884
|
-
|
|
885
919
|
|
|
920
|
+
var framesProcessed = 0;
|
|
886
921
|
/**
|
|
887
922
|
* Renders a virtual DOM element into a container.
|
|
888
923
|
* @param {VNode} element - The root virtual DOM element to render.
|
|
889
924
|
* @param {Node} container - The DOM container to render into.
|
|
890
925
|
*/
|
|
891
|
-
export function render(element: VNode, container: Node): void {
|
|
926
|
+
export function render(element: VNode, container: Node): void {
|
|
892
927
|
container.innerHTML = "";
|
|
893
|
-
|
|
928
|
+
|
|
894
929
|
wipRoot = {
|
|
895
930
|
dom: container,
|
|
896
931
|
props: {
|
|
897
|
-
children:
|
|
932
|
+
children: [element], // Remove ErrorBoundary wrapper for now to debug
|
|
898
933
|
},
|
|
899
934
|
alternate: currentRoot,
|
|
900
935
|
};
|
|
901
936
|
deletions = [];
|
|
902
937
|
nextUnitOfWork = wipRoot;
|
|
903
|
-
|
|
938
|
+
|
|
939
|
+
|
|
940
|
+
// Force immediate start with requestAnimationFrame first,
|
|
941
|
+
// then continue with requestIdleCallback
|
|
942
|
+
requestAnimationFrame(() => {
|
|
943
|
+
const startTime = performance.now();
|
|
944
|
+
|
|
945
|
+
while (nextUnitOfWork && framesProcessed < 10 && performance.now() - startTime < 8) {
|
|
946
|
+
nextUnitOfWork = performUnitOfWork(nextUnitOfWork);
|
|
947
|
+
framesProcessed++;
|
|
948
|
+
}
|
|
949
|
+
|
|
950
|
+
if (!nextUnitOfWork && wipRoot) {
|
|
951
|
+
commitRoot();
|
|
952
|
+
}
|
|
953
|
+
|
|
954
|
+
// Continue with requestIdleCallback
|
|
955
|
+
if (nextUnitOfWork || wipRoot) {
|
|
956
|
+
requestIdleCallback(workLoop, { timeout: 100 });
|
|
957
|
+
}
|
|
958
|
+
});
|
|
904
959
|
}
|
|
905
960
|
|
|
961
|
+
|
|
906
962
|
/**
|
|
907
963
|
* The main work loop for rendering and reconciliation.
|
|
908
964
|
*/
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
965
|
+
let isRendering = false;
|
|
966
|
+
let renderScheduled = false;
|
|
967
|
+
|
|
968
|
+
function scheduleRender() {
|
|
969
|
+
if (isRendering || renderScheduled) return;
|
|
970
|
+
|
|
971
|
+
renderScheduled = true;
|
|
972
|
+
|
|
973
|
+
// Try requestIdleCallback first
|
|
974
|
+
if ('requestIdleCallback' in window) {
|
|
975
|
+
requestIdleCallback((deadline) => {
|
|
976
|
+
renderScheduled = false;
|
|
977
|
+
isRendering = true;
|
|
978
|
+
workLoop(deadline);
|
|
979
|
+
isRendering = false;
|
|
980
|
+
}, { timeout: 100 }); // Timeout ensures it runs even if idle time never comes
|
|
981
|
+
} else {
|
|
982
|
+
// Fallback to requestAnimationFrame
|
|
983
|
+
requestAnimationFrame(() => {
|
|
984
|
+
renderScheduled = false;
|
|
985
|
+
isRendering = true;
|
|
986
|
+
workLoop();
|
|
987
|
+
isRendering = false;
|
|
988
|
+
});
|
|
918
989
|
}
|
|
990
|
+
}
|
|
919
991
|
|
|
920
|
-
|
|
992
|
+
function workLoop(deadline?: IdleDeadline): void {
|
|
993
|
+
// Process units of work
|
|
994
|
+
while (nextUnitOfWork && (!deadline || deadline.timeRemaining() > 1)) {
|
|
921
995
|
nextUnitOfWork = performUnitOfWork(nextUnitOfWork);
|
|
922
996
|
}
|
|
923
|
-
|
|
997
|
+
|
|
998
|
+
// If we finished all work, commit to DOM
|
|
924
999
|
if (!nextUnitOfWork && wipRoot) {
|
|
925
1000
|
commitRoot();
|
|
926
1001
|
}
|
|
1002
|
+
|
|
1003
|
+
// If there's still work to do, schedule more
|
|
1004
|
+
if (nextUnitOfWork || wipRoot) {
|
|
1005
|
+
// Use a hybrid approach: requestIdleCallback with fallback
|
|
1006
|
+
if (deadline) {
|
|
1007
|
+
// We came from requestIdleCallback, use it again
|
|
1008
|
+
requestIdleCallback(workLoop);
|
|
1009
|
+
} else {
|
|
1010
|
+
// We came from elsewhere, start with requestIdleCallback
|
|
1011
|
+
requestIdleCallback(workLoop);
|
|
1012
|
+
}
|
|
1013
|
+
}
|
|
927
1014
|
}
|
|
928
1015
|
|
|
1016
|
+
|
|
929
1017
|
|
|
930
1018
|
/**
|
|
931
1019
|
* Performs work on a single fiber unit.
|
|
@@ -957,17 +1045,17 @@ function performUnitOfWork(fiber: Fiber): Fiber | null {
|
|
|
957
1045
|
function getComponentStack(fiber: Fiber): string {
|
|
958
1046
|
const stack: string[] = [];
|
|
959
1047
|
let currentFiber: Fiber | null = fiber;
|
|
960
|
-
|
|
1048
|
+
|
|
961
1049
|
while (currentFiber) {
|
|
962
1050
|
if (currentFiber.type) {
|
|
963
|
-
const name = typeof currentFiber.type === 'function'
|
|
1051
|
+
const name = typeof currentFiber.type === 'function'
|
|
964
1052
|
? currentFiber.type.name || 'AnonymousComponent'
|
|
965
1053
|
: String(currentFiber.type);
|
|
966
1054
|
stack.push(name);
|
|
967
1055
|
}
|
|
968
1056
|
currentFiber = currentFiber.parent;
|
|
969
1057
|
}
|
|
970
|
-
|
|
1058
|
+
|
|
971
1059
|
return stack.reverse().join(' → ');
|
|
972
1060
|
}
|
|
973
1061
|
export const DevTools = {
|
|
@@ -976,39 +1064,39 @@ export const DevTools = {
|
|
|
976
1064
|
localStorage.setItem('vader-dev-mode', 'true');
|
|
977
1065
|
console.log('[VaderJS] Dev mode enabled');
|
|
978
1066
|
},
|
|
979
|
-
|
|
1067
|
+
|
|
980
1068
|
disable: () => {
|
|
981
1069
|
isDev = false;
|
|
982
1070
|
localStorage.removeItem('vader-dev-mode');
|
|
983
1071
|
console.log('[VaderJS] Dev mode disabled');
|
|
984
1072
|
},
|
|
985
|
-
|
|
1073
|
+
|
|
986
1074
|
isEnabled: () => isDev,
|
|
987
|
-
|
|
1075
|
+
|
|
988
1076
|
// Force error for testing
|
|
989
1077
|
throwTestError: (message = 'Test error from DevTools') => {
|
|
990
1078
|
throw new Error(message);
|
|
991
1079
|
},
|
|
992
|
-
|
|
1080
|
+
|
|
993
1081
|
// Get component tree
|
|
994
1082
|
getComponentTree: () => {
|
|
995
1083
|
const tree: any[] = [];
|
|
996
1084
|
let fiber: Fiber | null = currentRoot;
|
|
997
|
-
|
|
1085
|
+
|
|
998
1086
|
function traverse(fiber: Fiber | null, depth = 0) {
|
|
999
1087
|
if (!fiber) return;
|
|
1000
|
-
|
|
1088
|
+
|
|
1001
1089
|
tree.push({
|
|
1002
1090
|
depth,
|
|
1003
1091
|
type: typeof fiber.type === 'function' ? fiber.type.name : fiber.type,
|
|
1004
1092
|
props: fiber.props,
|
|
1005
1093
|
key: fiber.key
|
|
1006
1094
|
});
|
|
1007
|
-
|
|
1095
|
+
|
|
1008
1096
|
traverse(fiber.child, depth + 1);
|
|
1009
1097
|
traverse(fiber.sibling, depth);
|
|
1010
1098
|
}
|
|
1011
|
-
|
|
1099
|
+
|
|
1012
1100
|
traverse(fiber);
|
|
1013
1101
|
return tree;
|
|
1014
1102
|
}
|
|
@@ -1026,7 +1114,7 @@ function updateFunctionComponent(fiber: Fiber) {
|
|
|
1026
1114
|
try {
|
|
1027
1115
|
// Track component stack for better error reporting
|
|
1028
1116
|
const componentStack = getComponentStack(fiber);
|
|
1029
|
-
|
|
1117
|
+
|
|
1030
1118
|
// Wrap component execution with error boundary
|
|
1031
1119
|
if (isDev) {
|
|
1032
1120
|
children = [(fiber.type as Function)(fiber.props)]
|
|
@@ -1043,7 +1131,7 @@ function updateFunctionComponent(fiber: Fiber) {
|
|
|
1043
1131
|
// Handle error in component
|
|
1044
1132
|
const componentStack = getComponentStack(fiber);
|
|
1045
1133
|
reportError(error as Error, componentStack);
|
|
1046
|
-
|
|
1134
|
+
|
|
1047
1135
|
// Return error boundary or fallback UI
|
|
1048
1136
|
children = [createElement(
|
|
1049
1137
|
"div",
|
|
@@ -1086,59 +1174,54 @@ function reconcileChildren(wipFiber: Fiber, elements: VNode[]) {
|
|
|
1086
1174
|
let oldFiber = wipFiber.alternate?.child;
|
|
1087
1175
|
let prevSibling: Fiber | null = null;
|
|
1088
1176
|
|
|
1089
|
-
//
|
|
1177
|
+
// 1. Map existing fibers by key for O(1) lookup
|
|
1090
1178
|
const existingFibers = new Map<string | number | null, Fiber>();
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1179
|
+
let tempOld = oldFiber;
|
|
1180
|
+
let i = 0;
|
|
1181
|
+
while (tempOld) {
|
|
1182
|
+
const key = tempOld.key ?? i;
|
|
1183
|
+
existingFibers.set(key, tempOld);
|
|
1184
|
+
tempOld = tempOld.sibling;
|
|
1185
|
+
i++;
|
|
1096
1186
|
}
|
|
1097
1187
|
|
|
1098
|
-
|
|
1099
|
-
for (; index < elements.length; index++) {
|
|
1188
|
+
// 2. Iterate through new elements
|
|
1189
|
+
for (index = 0; index < elements.length; index++) {
|
|
1100
1190
|
const element = elements[index];
|
|
1101
1191
|
const key = element?.key ?? index;
|
|
1102
|
-
const
|
|
1192
|
+
const matchedOldFiber = existingFibers.get(key);
|
|
1193
|
+
|
|
1194
|
+
const sameType = matchedOldFiber && element && element.type === matchedOldFiber.type;
|
|
1103
1195
|
|
|
1104
|
-
const sameType = oldFiber && element && element.type === oldFiber.type;
|
|
1105
1196
|
let newFiber: Fiber | null = null;
|
|
1106
1197
|
|
|
1107
1198
|
if (sameType) {
|
|
1108
|
-
// Reuse the fiber
|
|
1109
1199
|
newFiber = {
|
|
1110
|
-
type:
|
|
1200
|
+
type: matchedOldFiber!.type,
|
|
1111
1201
|
props: element.props,
|
|
1112
|
-
dom:
|
|
1202
|
+
dom: matchedOldFiber!.dom,
|
|
1113
1203
|
parent: wipFiber,
|
|
1114
|
-
alternate:
|
|
1204
|
+
alternate: matchedOldFiber!,
|
|
1115
1205
|
effectTag: "UPDATE",
|
|
1116
|
-
|
|
1117
|
-
key
|
|
1206
|
+
key: key,
|
|
1118
1207
|
};
|
|
1119
1208
|
existingFibers.delete(key);
|
|
1120
1209
|
} else if (element) {
|
|
1121
|
-
// Create new fiber
|
|
1122
1210
|
newFiber = {
|
|
1123
1211
|
type: element.type,
|
|
1124
1212
|
props: element.props,
|
|
1125
|
-
dom:
|
|
1213
|
+
dom: undefined,
|
|
1126
1214
|
parent: wipFiber,
|
|
1127
|
-
alternate:
|
|
1215
|
+
alternate: undefined,
|
|
1128
1216
|
effectTag: "PLACEMENT",
|
|
1129
|
-
key
|
|
1217
|
+
key: key,
|
|
1130
1218
|
};
|
|
1131
1219
|
}
|
|
1132
1220
|
|
|
1133
|
-
if (oldFiber && !sameType) {
|
|
1134
|
-
oldFiber.effectTag = "DELETION";
|
|
1135
|
-
deletions.push(oldFiber);
|
|
1136
|
-
}
|
|
1137
|
-
|
|
1138
1221
|
if (index === 0) {
|
|
1139
|
-
wipFiber.child = newFiber
|
|
1140
|
-
} else if (
|
|
1141
|
-
prevSibling
|
|
1222
|
+
wipFiber.child = newFiber!;
|
|
1223
|
+
} else if (element) {
|
|
1224
|
+
prevSibling!.sibling = newFiber!;
|
|
1142
1225
|
}
|
|
1143
1226
|
|
|
1144
1227
|
if (newFiber) {
|
|
@@ -1146,10 +1229,10 @@ function reconcileChildren(wipFiber: Fiber, elements: VNode[]) {
|
|
|
1146
1229
|
}
|
|
1147
1230
|
}
|
|
1148
1231
|
|
|
1149
|
-
//
|
|
1232
|
+
// 3. Any fibers remaining in the map were not matched and must be deleted
|
|
1150
1233
|
existingFibers.forEach(fiber => {
|
|
1151
1234
|
fiber.effectTag = "DELETION";
|
|
1152
|
-
deletions
|
|
1235
|
+
deletions!.push(fiber);
|
|
1153
1236
|
});
|
|
1154
1237
|
}
|
|
1155
1238
|
|
|
@@ -1214,26 +1297,34 @@ export function useState<T>(initial: T | (() => T)): [T, (action: T | ((prevStat
|
|
|
1214
1297
|
hook = {
|
|
1215
1298
|
state: typeof initial === "function" ? (initial as () => T)() : initial,
|
|
1216
1299
|
queue: [],
|
|
1217
|
-
_needsUpdate: false
|
|
1218
1300
|
};
|
|
1219
1301
|
wipFiber.hooks[hookIndex] = hook;
|
|
1220
1302
|
}
|
|
1221
1303
|
|
|
1222
1304
|
const setState = (action: T | ((prevState: T) => T)) => {
|
|
1223
|
-
// Calculate new state based on current state
|
|
1224
1305
|
const newState = typeof action === "function"
|
|
1225
1306
|
? (action as (prevState: T) => T)(hook.state)
|
|
1226
1307
|
: action;
|
|
1227
1308
|
|
|
1228
1309
|
hook.state = newState;
|
|
1229
1310
|
|
|
1230
|
-
//
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1311
|
+
// Schedule a re-render
|
|
1312
|
+
if (currentRoot) {
|
|
1313
|
+
wipRoot = {
|
|
1314
|
+
dom: currentRoot.dom,
|
|
1315
|
+
props: currentRoot.props,
|
|
1316
|
+
alternate: currentRoot,
|
|
1317
|
+
};
|
|
1318
|
+
deletions = [];
|
|
1319
|
+
nextUnitOfWork = wipRoot;
|
|
1320
|
+
|
|
1321
|
+
if (typeof scheduleRender === 'function') {
|
|
1322
|
+
scheduleRender();
|
|
1323
|
+
} else {
|
|
1324
|
+
// Fallback
|
|
1325
|
+
requestAnimationFrame(workLoop);
|
|
1326
|
+
}
|
|
1327
|
+
}
|
|
1237
1328
|
};
|
|
1238
1329
|
|
|
1239
1330
|
hookIndex++;
|
|
@@ -1290,9 +1381,9 @@ export function Switch({ children }: { children?: VNode | VNode[] }): VNode | nu
|
|
|
1290
1381
|
if (match) return match;
|
|
1291
1382
|
return childrenArray.find(child => child && child.props?.default) || null;
|
|
1292
1383
|
}
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1384
|
+
|
|
1385
|
+
|
|
1386
|
+
|
|
1296
1387
|
|
|
1297
1388
|
|
|
1298
1389
|
/**
|
|
@@ -1326,14 +1417,11 @@ export function Show({ when, children }: { when: boolean; children?: VNode | VNo
|
|
|
1326
1417
|
* @param duration
|
|
1327
1418
|
*/
|
|
1328
1419
|
export function showToast(message: string, duration = 3000) {
|
|
1329
|
-
if (typeof window !== "undefined" && (window as any).Android?.showToast) {
|
|
1330
|
-
console.log("[Vader] Android Toast");
|
|
1420
|
+
if (typeof window !== "undefined" && (window as any).Android?.showToast) {
|
|
1331
1421
|
(window as any).Android.showToast(message);
|
|
1332
1422
|
return;
|
|
1333
1423
|
}
|
|
1334
|
-
|
|
1335
|
-
// Web fallback
|
|
1336
|
-
console.log("[Toast]", message);
|
|
1424
|
+
|
|
1337
1425
|
|
|
1338
1426
|
const toast = document.createElement("div");
|
|
1339
1427
|
toast.textContent = message;
|
|
@@ -1451,8 +1539,7 @@ const pendingRequests = new Map<string, (data: any) => void>();
|
|
|
1451
1539
|
|
|
1452
1540
|
// Listen for responses from Windows C#
|
|
1453
1541
|
if (typeof window !== "undefined" && window.chrome?.webview) {
|
|
1454
|
-
window.chrome.webview.addEventListener('message', (event: any) => {
|
|
1455
|
-
console.log("Received from C#:", event.data);
|
|
1542
|
+
window.chrome.webview.addEventListener('message', (event: any) => {
|
|
1456
1543
|
const { id, data, error } = event.data;
|
|
1457
1544
|
|
|
1458
1545
|
if (pendingRequests.has(id)) {
|
|
@@ -1479,9 +1566,7 @@ function callWindows(command: string, args: any): Promise<any> {
|
|
|
1479
1566
|
id,
|
|
1480
1567
|
command,
|
|
1481
1568
|
...args
|
|
1482
|
-
};
|
|
1483
|
-
|
|
1484
|
-
console.log("Sending to C#:", message);
|
|
1569
|
+
};
|
|
1485
1570
|
window.chrome.webview.postMessage(message);
|
|
1486
1571
|
});
|
|
1487
1572
|
}
|
|
@@ -1518,10 +1603,10 @@ export const FS: FS = {
|
|
|
1518
1603
|
if (currentPlatform === "android" && window.Android) {
|
|
1519
1604
|
const result = window.Android.writeFile(path, content);
|
|
1520
1605
|
return result === true || result === 'true';
|
|
1521
|
-
}else {
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1606
|
+
} else {
|
|
1607
|
+
// write to localStorage as fallback
|
|
1608
|
+
localStorage.setItem(path, content);
|
|
1609
|
+
return true;
|
|
1525
1610
|
}
|
|
1526
1611
|
} catch (error) {
|
|
1527
1612
|
console.error('FS.writeFile error:', error);
|
|
@@ -1544,24 +1629,24 @@ export const FS: FS = {
|
|
|
1544
1629
|
}
|
|
1545
1630
|
|
|
1546
1631
|
if (currentPlatform === "android" && window.Android) {
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1632
|
+
const result = window.Android.readFile(path);
|
|
1633
|
+
|
|
1634
|
+
if (typeof result === 'string') {
|
|
1635
|
+
try {
|
|
1636
|
+
const parsed = JSON.parse(result);
|
|
1637
|
+
// If the native side returned an error object, treat it as a failure
|
|
1638
|
+
if (parsed && parsed.error) {
|
|
1639
|
+
throw new Error(parsed.error);
|
|
1640
|
+
}
|
|
1641
|
+
} catch (e: any) {
|
|
1642
|
+
// If it's the Error we just threw, rethrow it to the caller
|
|
1643
|
+
if (e.message === "File not found") throw e;
|
|
1644
|
+
// Otherwise, it was just normal file content that wasn't JSON,
|
|
1645
|
+
// which is fine, so we let it fall through to return result.
|
|
1555
1646
|
}
|
|
1556
|
-
} catch (e: any) {
|
|
1557
|
-
// If it's the Error we just threw, rethrow it to the caller
|
|
1558
|
-
if (e.message === "File not found") throw e;
|
|
1559
|
-
// Otherwise, it was just normal file content that wasn't JSON,
|
|
1560
|
-
// which is fine, so we let it fall through to return result.
|
|
1561
1647
|
}
|
|
1648
|
+
return result || '';
|
|
1562
1649
|
}
|
|
1563
|
-
return result || '';
|
|
1564
|
-
}
|
|
1565
1650
|
} catch (error) {
|
|
1566
1651
|
throw error;
|
|
1567
1652
|
}
|
|
@@ -1585,8 +1670,8 @@ export const FS: FS = {
|
|
|
1585
1670
|
return result === true || result === 'true';
|
|
1586
1671
|
}
|
|
1587
1672
|
return await this.writeFile(path, '');
|
|
1588
|
-
}else{
|
|
1589
|
-
|
|
1673
|
+
} else {
|
|
1674
|
+
localStorage.removeItem(path);
|
|
1590
1675
|
}
|
|
1591
1676
|
} catch (error) {
|
|
1592
1677
|
console.error('FS.deleteFile error:', error);
|
|
@@ -1617,9 +1702,9 @@ export const FS: FS = {
|
|
|
1617
1702
|
return result ? [result] : [];
|
|
1618
1703
|
}
|
|
1619
1704
|
}
|
|
1620
|
-
}else{
|
|
1621
|
-
|
|
1622
|
-
|
|
1705
|
+
} else {
|
|
1706
|
+
const keys = Object.keys(localStorage);
|
|
1707
|
+
return keys;
|
|
1623
1708
|
}
|
|
1624
1709
|
} catch (error) {
|
|
1625
1710
|
console.error('FS.listDir error:', error);
|
|
@@ -1641,7 +1726,7 @@ let dialogResolver: ((value: boolean) => void) | null = null;
|
|
|
1641
1726
|
* Use dialog hook for showing alert and confirm dialogs.
|
|
1642
1727
|
* @returns {object} An object with alert and confirm methods.
|
|
1643
1728
|
*/
|
|
1644
|
-
export function useDialog(
|
|
1729
|
+
export function useDialog() {
|
|
1645
1730
|
// ---- ANDROID IMPLEMENTATION ----
|
|
1646
1731
|
if (typeof window !== "undefined" && (window as any).Android?.showDialog) {
|
|
1647
1732
|
return {
|
|
@@ -1711,7 +1796,7 @@ export function useDialog( ) {
|
|
|
1711
1796
|
* @param {T} initial - The initial reference value.
|
|
1712
1797
|
* @returns {{current: T}} A mutable ref object.
|
|
1713
1798
|
*/
|
|
1714
|
-
export function useRef<T>(initial: T): { current: T } {
|
|
1799
|
+
export function useRef<T>(initial: T): { current: T } {
|
|
1715
1800
|
if (!wipFiber) {
|
|
1716
1801
|
throw new Error("Hooks can only be called inside a Vader.js function component.");
|
|
1717
1802
|
}
|
|
@@ -1723,10 +1808,12 @@ export function useRef<T>(initial: T): { current: T } {
|
|
|
1723
1808
|
}
|
|
1724
1809
|
|
|
1725
1810
|
hookIndex++;
|
|
1726
|
-
|
|
1727
|
-
return hook;
|
|
1811
|
+
return hook as { current: T };
|
|
1728
1812
|
}
|
|
1729
1813
|
|
|
1814
|
+
|
|
1815
|
+
|
|
1816
|
+
|
|
1730
1817
|
/**
|
|
1731
1818
|
* A React-like useLayoutEffect hook that runs synchronously after DOM mutations.
|
|
1732
1819
|
* @param {Function} callback - The effect callback.
|
|
@@ -1762,7 +1849,7 @@ export function useLayoutEffect(callback: Function, deps?: any[]): void {
|
|
|
1762
1849
|
hook.deps = deps;
|
|
1763
1850
|
hookIndex++;
|
|
1764
1851
|
}
|
|
1765
|
-
|
|
1852
|
+
if (platform() === "windows") {
|
|
1766
1853
|
const block = (method: string) => {
|
|
1767
1854
|
return function () {
|
|
1768
1855
|
throw new Error(
|
|
@@ -2044,16 +2131,84 @@ export function useQuery<T>(
|
|
|
2044
2131
|
return { data, loading, error, refetch: fetchData };
|
|
2045
2132
|
}
|
|
2046
2133
|
|
|
2047
|
-
|
|
2048
|
-
|
|
2049
|
-
|
|
2050
|
-
|
|
2051
|
-
|
|
2052
|
-
|
|
2053
|
-
|
|
2054
|
-
return each.map((item, index) => children(item, index));
|
|
2055
|
-
}
|
|
2134
|
+
type ForChildren<T> =
|
|
2135
|
+
| ((item: T, index: number) => VNode | VNode[] | null)
|
|
2136
|
+
| VNode
|
|
2137
|
+
| VNode[]
|
|
2138
|
+
| null;
|
|
2139
|
+
|
|
2140
|
+
type RenderFn<T> = (item: T, index: number) => VNode | VNode[] | null;
|
|
2056
2141
|
|
|
2142
|
+
|
|
2143
|
+
export function For<T>(props: {
|
|
2144
|
+
each?: readonly T[] | null;
|
|
2145
|
+
children?: RenderFn<T> | RenderFn<T>[];
|
|
2146
|
+
}): VNode | null {
|
|
2147
|
+
const list = props.each;
|
|
2148
|
+
if (!list || list.length === 0) return null;
|
|
2149
|
+
|
|
2150
|
+
// Extract the render function from children
|
|
2151
|
+
// In JSX, children is passed as a prop, not as arguments to createElement
|
|
2152
|
+
let renderFn: RenderFn<T> | null = null;
|
|
2153
|
+
|
|
2154
|
+
if (props.children) {
|
|
2155
|
+
if (Array.isArray(props.children)) {
|
|
2156
|
+
// Find the first function in children array
|
|
2157
|
+
for (const child of props.children) {
|
|
2158
|
+
if (typeof child === "function") {
|
|
2159
|
+
renderFn = child as RenderFn<T>;
|
|
2160
|
+
break;
|
|
2161
|
+
}
|
|
2162
|
+
}
|
|
2163
|
+
} else if (typeof props.children === "function") {
|
|
2164
|
+
renderFn = props.children;
|
|
2165
|
+
}
|
|
2166
|
+
}
|
|
2167
|
+
|
|
2168
|
+
if (!renderFn) {
|
|
2169
|
+
console.warn("For component requires a function as children");
|
|
2170
|
+
return null;
|
|
2171
|
+
}
|
|
2172
|
+
|
|
2173
|
+
// Execute the render function for each item
|
|
2174
|
+
const renderedItems: (VNode | VNode[] | null)[] = [];
|
|
2175
|
+
|
|
2176
|
+
for (let i = 0; i < list.length; i++) {
|
|
2177
|
+
const item = list[i];
|
|
2178
|
+
try {
|
|
2179
|
+
const result = renderFn(item, i);
|
|
2180
|
+
if (result !== null && result !== undefined) {
|
|
2181
|
+
renderedItems.push(result);
|
|
2182
|
+
}
|
|
2183
|
+
} catch (error) {
|
|
2184
|
+
console.error("Error rendering item in For loop:", error);
|
|
2185
|
+
}
|
|
2186
|
+
}
|
|
2187
|
+
|
|
2188
|
+
// Flatten the results (renderFn might return arrays)
|
|
2189
|
+
const flatItems: VNode[] = [];
|
|
2190
|
+
for (const item of renderedItems) {
|
|
2191
|
+
if (Array.isArray(item)) {
|
|
2192
|
+
for (const subItem of item) {
|
|
2193
|
+
if (subItem !== null && subItem !== undefined) {
|
|
2194
|
+
flatItems.push(subItem);
|
|
2195
|
+
}
|
|
2196
|
+
}
|
|
2197
|
+
} else if (item !== null && item !== undefined) {
|
|
2198
|
+
flatItems.push(item);
|
|
2199
|
+
}
|
|
2200
|
+
}
|
|
2201
|
+
|
|
2202
|
+
if (flatItems.length === 0) return null;
|
|
2203
|
+
|
|
2204
|
+
// Return a fragment containing all rendered items
|
|
2205
|
+
return {
|
|
2206
|
+
type: "fragment",
|
|
2207
|
+
props: {
|
|
2208
|
+
children: flatItems
|
|
2209
|
+
}
|
|
2210
|
+
} as VNode;
|
|
2211
|
+
}
|
|
2057
2212
|
/**
|
|
2058
2213
|
* A hook for tracking window focus state.
|
|
2059
2214
|
* @returns {boolean} True if the window is focused.
|
|
@@ -2189,7 +2344,7 @@ export function platform(): "windows" | "android" | "web" {
|
|
|
2189
2344
|
* @param {(props: P) => VNode} renderFn - The component render function
|
|
2190
2345
|
* @returns {(props: P) => VNode} A memoized component function
|
|
2191
2346
|
*/
|
|
2192
|
-
export function component<P extends object>(
|
|
2347
|
+
export function component<P extends object>(renderFn: (props: P) => VNode): (props: P) => VNode {
|
|
2193
2348
|
// Create a wrapper function that will be the actual component
|
|
2194
2349
|
const ComponentWrapper = (props: P): VNode => {
|
|
2195
2350
|
// Check if props have changed
|
|
@@ -2197,18 +2352,18 @@ export function component<P extends object>( renderFn: (props: P) => VNode): (pr
|
|
|
2197
2352
|
while (fiber && fiber.type !== ComponentWrapper) {
|
|
2198
2353
|
fiber = fiber.alternate;
|
|
2199
2354
|
}
|
|
2200
|
-
|
|
2355
|
+
|
|
2201
2356
|
const prevProps = fiber?.alternate?.props || {};
|
|
2202
2357
|
const nextProps = props;
|
|
2203
|
-
|
|
2358
|
+
|
|
2204
2359
|
// Create a simple props comparison
|
|
2205
2360
|
// For now, we'll do a shallow comparison of props
|
|
2206
2361
|
let shouldUpdate = false;
|
|
2207
|
-
|
|
2362
|
+
|
|
2208
2363
|
// Check if props count changed
|
|
2209
2364
|
const prevKeys = Object.keys(prevProps);
|
|
2210
2365
|
const nextKeys = Object.keys(nextProps);
|
|
2211
|
-
|
|
2366
|
+
|
|
2212
2367
|
if (prevKeys.length !== nextKeys.length) {
|
|
2213
2368
|
shouldUpdate = true;
|
|
2214
2369
|
} else {
|
|
@@ -2220,7 +2375,7 @@ export function component<P extends object>( renderFn: (props: P) => VNode): (pr
|
|
|
2220
2375
|
}
|
|
2221
2376
|
}
|
|
2222
2377
|
}
|
|
2223
|
-
|
|
2378
|
+
|
|
2224
2379
|
// Mark fiber for memoization
|
|
2225
2380
|
const currentFiber = wipFiber;
|
|
2226
2381
|
if (currentFiber) {
|
|
@@ -2228,31 +2383,31 @@ export function component<P extends object>( renderFn: (props: P) => VNode): (pr
|
|
|
2228
2383
|
currentFiber.__compareProps = (prev: P, next: P) => {
|
|
2229
2384
|
const prevKeys = Object.keys(prev);
|
|
2230
2385
|
const nextKeys = Object.keys(next);
|
|
2231
|
-
|
|
2386
|
+
|
|
2232
2387
|
if (prevKeys.length !== nextKeys.length) return false;
|
|
2233
|
-
|
|
2388
|
+
|
|
2234
2389
|
for (const key of nextKeys) {
|
|
2235
2390
|
if (next[key] !== prev[key]) return false;
|
|
2236
2391
|
}
|
|
2237
|
-
|
|
2392
|
+
|
|
2238
2393
|
return true;
|
|
2239
2394
|
};
|
|
2240
|
-
|
|
2395
|
+
|
|
2241
2396
|
currentFiber.__skipMemo = !shouldUpdate;
|
|
2242
2397
|
}
|
|
2243
|
-
|
|
2398
|
+
|
|
2244
2399
|
// If props haven't changed, return the previous fiber's children
|
|
2245
2400
|
if (!shouldUpdate && fiber?.alternate?.child) {
|
|
2246
2401
|
return fiber.alternate.child.props.children[0];
|
|
2247
2402
|
}
|
|
2248
|
-
|
|
2403
|
+
|
|
2249
2404
|
// Otherwise render with new props
|
|
2250
2405
|
return renderFn(props);
|
|
2251
2406
|
};
|
|
2252
|
-
|
|
2407
|
+
|
|
2253
2408
|
// Set display name for debugging
|
|
2254
2409
|
(ComponentWrapper as any).displayName = name;
|
|
2255
|
-
|
|
2410
|
+
|
|
2256
2411
|
return ComponentWrapper;
|
|
2257
2412
|
}
|
|
2258
2413
|
export function Fragment({ children }: { children: VNode | VNode[] }): VNode | null {
|
|
@@ -2303,4 +2458,3 @@ Object.defineProperty(window, "Vader", {
|
|
|
2303
2458
|
configurable: false,
|
|
2304
2459
|
});
|
|
2305
2460
|
|
|
2306
|
-
|