@unsetsoft/ryunixjs 1.2.3-canary.1 → 1.2.3-canary.11

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.
@@ -0,0 +1,1576 @@
1
+ // Improved state management - avoid global mutable object
2
+ // Instead, create a state manager that can be instantiated per render tree
3
+
4
+ const createRenderState = () => ({
5
+ containerRoot: null,
6
+ nextUnitOfWork: null,
7
+ currentRoot: null,
8
+ wipRoot: null,
9
+ deletions: [],
10
+ wipFiber: null,
11
+ hookIndex: 0,
12
+ effects: [],
13
+ });
14
+
15
+ // Singleton for backward compatibility, but allows testing with isolated instances
16
+ let globalState = createRenderState();
17
+
18
+ const getState = () => globalState;
19
+
20
+ // Use const for regex to prevent accidental modification
21
+ const CAMEL_TO_KEBAB_REGEX = /[A-Z]/g;
22
+
23
+ const RYUNIX_TYPES = Object.freeze({
24
+ TEXT_ELEMENT: Symbol.for('ryunix.text.element'),
25
+ RYUNIX_ELEMENT: Symbol.for('ryunix.element'),
26
+ RYUNIX_EFFECT: Symbol.for('ryunix.effect'),
27
+ RYUNIX_MEMO: Symbol.for('ryunix.memo'),
28
+ RYUNIX_URL_QUERY: Symbol.for('ryunix.urlQuery'),
29
+ RYUNIX_REF: Symbol.for('ryunix.ref'),
30
+ RYUNIX_STORE: Symbol.for('ryunix.store'),
31
+ RYUNIX_REDUCE: Symbol.for('ryunix.reduce'),
32
+ RYUNIX_FRAGMENT: Symbol.for('ryunix.fragment'),
33
+ RYUNIX_CONTEXT: Symbol.for('ryunix.context'),
34
+ });
35
+
36
+ const STRINGS = Object.freeze({
37
+ OBJECT: 'object',
38
+ FUNCTION: 'function',
39
+ STYLE: 'ryunix-style',
40
+ CLASS_NAME: 'ryunix-class',
41
+ CHILDREN: 'children',
42
+ BOOLEAN: 'boolean',
43
+ STRING: 'string',
44
+ UNDEFINED: 'undefined',
45
+ });
46
+
47
+ const OLD_STRINGS = Object.freeze({
48
+ STYLE: 'style',
49
+ CLASS_NAME: 'className',
50
+ });
51
+
52
+ const EFFECT_TAGS = Object.freeze({
53
+ PLACEMENT: Symbol.for('ryunix.reconciler.status.placement'),
54
+ UPDATE: Symbol.for('ryunix.reconciler.status.update'),
55
+ DELETION: Symbol.for('ryunix.reconciler.status.deletion'),
56
+ NO_EFFECT: Symbol.for('ryunix.reconciler.status.no_effect'),
57
+ });
58
+
59
+ /**
60
+ * Type checking utilities
61
+ */
62
+ const is = {
63
+ object: (val) => val !== null && typeof val === STRINGS.OBJECT,
64
+ function: (val) => typeof val === STRINGS.FUNCTION,
65
+ string: (val) => typeof val === STRINGS.STRING,
66
+ undefined: (val) => typeof val === STRINGS.UNDEFINED,
67
+ null: (val) => val === null,
68
+ array: (val) => Array.isArray(val),
69
+ promise: (val) => val instanceof Promise,
70
+ };
71
+
72
+ /**
73
+ * Create text element
74
+ */
75
+ const createTextElement = (text) => {
76
+ return {
77
+ type: RYUNIX_TYPES.TEXT_ELEMENT,
78
+ props: {
79
+ nodeValue: text,
80
+ children: [],
81
+ },
82
+ }
83
+ };
84
+
85
+ /**
86
+ * Create element
87
+ */
88
+ const createElement = (type, props, ...children) => {
89
+ const safeProps = props || {};
90
+
91
+ return {
92
+ type,
93
+ props: {
94
+ ...safeProps,
95
+ children: children
96
+ .flat()
97
+ .map((child) =>
98
+ typeof child === STRINGS.OBJECT ? child : createTextElement(child),
99
+ ),
100
+ },
101
+ }
102
+ };
103
+
104
+ /**
105
+ * Fragment component
106
+ */
107
+ const Fragment = (props) => {
108
+ const children = Array.isArray(props.children)
109
+ ? props.children
110
+ : [props.children];
111
+ return createElement(RYUNIX_TYPES.RYUNIX_FRAGMENT, {}, ...children)
112
+ };
113
+
114
+ /**
115
+ * Check if a key is an event handler
116
+ * @param {string} key - Prop key
117
+ * @returns {boolean}
118
+ */
119
+ const isEvent = (key) => key.startsWith('on');
120
+
121
+ /**
122
+ * Check if a key is a property (not children or event)
123
+ * @param {string} key - Prop key
124
+ * @returns {boolean}
125
+ */
126
+ const isProperty = (key) => key !== STRINGS.CHILDREN && !isEvent(key);
127
+
128
+ /**
129
+ * Check if a property is new or changed
130
+ * @param {Object} prev - Previous props
131
+ * @param {Object} next - Next props
132
+ * @returns {Function}
133
+ */
134
+ const isNew = (prev, next) => (key) => {
135
+ // Use Object.is for better comparison (handles NaN, -0, +0)
136
+ return !Object.is(prev[key], next[key])
137
+ };
138
+
139
+ /**
140
+ * Check if a property was removed
141
+ * @param {Object} next - Next props
142
+ * @returns {Function}
143
+ */
144
+ const isGone = (next) => (key) => !(key in next);
145
+
146
+ /**
147
+ * Cancel effects for a single fiber
148
+ * @param {Object} fiber - Fiber node
149
+ */
150
+ const cancelEffects = (fiber) => {
151
+ if (!fiber?.hooks?.length) return
152
+
153
+ fiber.hooks
154
+ .filter(
155
+ (hook) =>
156
+ hook.type === RYUNIX_TYPES.RYUNIX_EFFECT && is.function(hook.cancel),
157
+ )
158
+ .forEach((hook) => {
159
+ try {
160
+ hook.cancel();
161
+ hook.cancel = null; // Clear reference to prevent memory leaks
162
+ } catch (error) {
163
+ if (process.env.NODE_ENV !== 'production') {
164
+ console.error('Error in effect cleanup:', error);
165
+ }
166
+ }
167
+ });
168
+ };
169
+
170
+ /**
171
+ * Recursively cancel effects in fiber tree
172
+ * @param {Object} fiber - Root fiber node
173
+ */
174
+ const cancelEffectsDeep = (fiber) => {
175
+ if (!fiber) return
176
+
177
+ // Cancel effects for current fiber
178
+ if (fiber.hooks?.length > 0) {
179
+ fiber.hooks
180
+ .filter(
181
+ (hook) =>
182
+ hook.type === RYUNIX_TYPES.RYUNIX_EFFECT && is.function(hook.cancel),
183
+ )
184
+ .forEach((hook) => {
185
+ try {
186
+ hook.cancel();
187
+ hook.cancel = null; // Clear reference
188
+ } catch (error) {
189
+ if (process.env.NODE_ENV !== 'production') {
190
+ console.error('Error in deep effect cleanup:', error);
191
+ }
192
+ }
193
+ });
194
+ }
195
+
196
+ // Recursively process children
197
+ if (fiber.child) cancelEffectsDeep(fiber.child);
198
+ if (fiber.sibling) cancelEffectsDeep(fiber.sibling);
199
+ };
200
+
201
+ /**
202
+ * Run effects for a fiber
203
+ * @param {Object} fiber - Fiber node
204
+ */
205
+ const runEffects = (fiber) => {
206
+ if (!fiber?.hooks?.length) return
207
+
208
+ for (let i = 0; i < fiber.hooks.length; i++) {
209
+ const hook = fiber.hooks[i];
210
+
211
+ if (hook.type === RYUNIX_TYPES.RYUNIX_EFFECT && is.function(hook.effect)) {
212
+ // Cancel previous cleanup if exists
213
+ if (is.function(hook.cancel)) {
214
+ try {
215
+ hook.cancel();
216
+ } catch (error) {
217
+ if (process.env.NODE_ENV !== 'production') {
218
+ console.error('Error in effect cleanup:', error);
219
+ }
220
+ }
221
+ }
222
+
223
+ // Run new effect
224
+ try {
225
+ const cleanup = hook.effect();
226
+
227
+ // Store cleanup function if returned
228
+ if (is.function(cleanup)) {
229
+ hook.cancel = cleanup;
230
+ } else {
231
+ hook.cancel = null;
232
+ }
233
+ } catch (error) {
234
+ if (process.env.NODE_ENV !== 'production') {
235
+ console.error('Error in effect:', error);
236
+ }
237
+ hook.cancel = null;
238
+ }
239
+
240
+ // Clear effect reference after running
241
+ hook.effect = null;
242
+ }
243
+ }
244
+ };
245
+
246
+ /**
247
+ * Convert camelCase to kebab-case for CSS properties
248
+ * @param {string} camelCase - CamelCase string
249
+ * @returns {string} Kebab-case string
250
+ */
251
+ const camelToKebab = (camelCase) => {
252
+ return camelCase.replace(
253
+ CAMEL_TO_KEBAB_REGEX,
254
+ (match) => `-${match.toLowerCase()}`,
255
+ )
256
+ };
257
+
258
+ /**
259
+ * Apply styles to DOM element
260
+ * @param {HTMLElement} dom - DOM element
261
+ * @param {Object} styleObj - Style object
262
+ */
263
+ const applyStyles = (dom, styleObj) => {
264
+ if (!is.object(styleObj) || is.null(styleObj)) {
265
+ dom.style.cssText = '';
266
+ return
267
+ }
268
+
269
+ try {
270
+ const cssText = Object.entries(styleObj)
271
+ .filter(([_, value]) => value != null) // Filter out null/undefined
272
+ .map(([key, value]) => {
273
+ const kebabKey = camelToKebab(key);
274
+ return `${kebabKey}: ${value}`
275
+ })
276
+ .join('; ');
277
+
278
+ dom.style.cssText = cssText;
279
+ } catch (error) {
280
+ if (process.env.NODE_ENV !== 'production') {
281
+ console.error('Error applying styles:', error);
282
+ }
283
+ }
284
+ };
285
+
286
+ /**
287
+ * Apply CSS classes to DOM element
288
+ * @param {HTMLElement} dom - DOM element
289
+ * @param {string} prevClasses - Previous class string
290
+ * @param {string} nextClasses - Next class string
291
+ */
292
+ const applyClasses = (dom, prevClasses, nextClasses) => {
293
+ // Allow empty/undefined - just remove classes
294
+ if (!nextClasses || nextClasses.trim() === '') {
295
+ if (prevClasses) {
296
+ const oldClasses = prevClasses.split(/\s+/).filter(Boolean);
297
+ dom.classList.remove(...oldClasses);
298
+ }
299
+ return
300
+ }
301
+
302
+ // Remove old classes
303
+ if (prevClasses) {
304
+ const oldClasses = prevClasses.split(/\s+/).filter(Boolean);
305
+ dom.classList.remove(...oldClasses);
306
+ }
307
+
308
+ // Add new classes
309
+ const newClasses = nextClasses.split(/\s+/).filter(Boolean);
310
+ if (newClasses.length > 0) {
311
+ dom.classList.add(...newClasses);
312
+ }
313
+ };
314
+
315
+ /**
316
+ * Create a DOM element from fiber
317
+ * @param {Object} fiber - Fiber node
318
+ * @returns {HTMLElement|Text|null}
319
+ */
320
+ const createDom = (fiber) => {
321
+ // Fragments don't create real DOM nodes
322
+ if (fiber.type === RYUNIX_TYPES.RYUNIX_FRAGMENT) {
323
+ return null
324
+ }
325
+
326
+ let dom;
327
+
328
+ try {
329
+ if (fiber.type === RYUNIX_TYPES.TEXT_ELEMENT) {
330
+ dom = document.createTextNode('');
331
+ } else if (is.string(fiber.type)) {
332
+ dom = document.createElement(fiber.type);
333
+ } else {
334
+ if (process.env.NODE_ENV !== 'production') {
335
+ console.warn(
336
+ 'Attempted to create DOM for non-host component:',
337
+ fiber.type,
338
+ );
339
+ }
340
+ return null
341
+ }
342
+
343
+ updateDom(dom, {}, fiber.props);
344
+ return dom
345
+ } catch (error) {
346
+ if (process.env.NODE_ENV !== 'production') {
347
+ console.error('Error creating DOM element:', error, fiber);
348
+ }
349
+ return null
350
+ }
351
+ };
352
+
353
+ /**
354
+ * Update DOM element with new props
355
+ * @param {HTMLElement|Text} dom - DOM element
356
+ * @param {Object} prevProps - Previous props
357
+ * @param {Object} nextProps - Next props
358
+ */
359
+ const updateDom = (dom, prevProps = {}, nextProps = {}) => {
360
+ // Remove old event listeners
361
+ Object.keys(prevProps)
362
+ .filter(isEvent)
363
+ .filter((key) => isGone(nextProps)(key) || isNew(prevProps, nextProps)(key))
364
+ .forEach((name) => {
365
+ const eventType = name.toLowerCase().substring(2);
366
+ try {
367
+ dom.removeEventListener(eventType, prevProps[name]);
368
+ } catch (error) {
369
+ if (process.env.NODE_ENV !== 'production') {
370
+ console.warn('Error removing event listener:', error);
371
+ }
372
+ }
373
+ });
374
+
375
+ // Remove old properties
376
+ Object.keys(prevProps)
377
+ .filter(isProperty)
378
+ .filter(isGone(nextProps))
379
+ .forEach((name) => {
380
+ // Skip special properties
381
+ if (
382
+ [
383
+ STRINGS.STYLE,
384
+ OLD_STRINGS.STYLE,
385
+ STRINGS.CLASS_NAME,
386
+ OLD_STRINGS.CLASS_NAME,
387
+ ].includes(name)
388
+ ) {
389
+ return
390
+ }
391
+ dom[name] = '';
392
+ });
393
+
394
+ // Set new properties
395
+ Object.keys(nextProps)
396
+ .filter(isProperty)
397
+ .filter(isNew(prevProps, nextProps))
398
+ .forEach((name) => {
399
+ try {
400
+ // Handle style properties
401
+ if (name === STRINGS.STYLE || name === OLD_STRINGS.STYLE) {
402
+ const styleValue = nextProps[name];
403
+ applyStyles(dom, styleValue);
404
+ }
405
+ // Handle className properties
406
+ else if (name === STRINGS.CLASS_NAME) {
407
+ applyClasses(
408
+ dom,
409
+ prevProps[STRINGS.CLASS_NAME],
410
+ nextProps[STRINGS.CLASS_NAME],
411
+ );
412
+ } else if (name === OLD_STRINGS.CLASS_NAME) {
413
+ applyClasses(
414
+ dom,
415
+ prevProps[OLD_STRINGS.CLASS_NAME],
416
+ nextProps[OLD_STRINGS.CLASS_NAME],
417
+ );
418
+ }
419
+ // Handle other properties
420
+ else {
421
+ // Special handling for value and checked (controlled components)
422
+ if (name === 'value' || name === 'checked') {
423
+ if (dom[name] !== nextProps[name]) {
424
+ dom[name] = nextProps[name];
425
+ }
426
+ } else {
427
+ dom[name] = nextProps[name];
428
+ }
429
+ }
430
+ } catch (error) {
431
+ if (process.env.NODE_ENV !== 'production') {
432
+ console.warn(`Error setting property ${name}:`, error);
433
+ }
434
+ }
435
+ });
436
+
437
+ // Add new event listeners
438
+ Object.keys(nextProps)
439
+ .filter(isEvent)
440
+ .filter(isNew(prevProps, nextProps))
441
+ .forEach((name) => {
442
+ const eventType = name.toLowerCase().substring(2);
443
+ try {
444
+ dom.addEventListener(eventType, nextProps[name]);
445
+ } catch (error) {
446
+ if (process.env.NODE_ENV !== 'production') {
447
+ console.warn('Error adding event listener:', error);
448
+ }
449
+ }
450
+ });
451
+ };
452
+
453
+ const commitRoot = () => {
454
+ const state = getState();
455
+ state.deletions.forEach(commitWork);
456
+ commitWork(state.wipRoot.child);
457
+ state.currentRoot = state.wipRoot;
458
+ state.wipRoot = null;
459
+ };
460
+
461
+ const commitWork = (fiber) => {
462
+ if (!fiber) {
463
+ return
464
+ }
465
+
466
+ let domParentFiber = fiber.parent;
467
+ while (!domParentFiber.dom) {
468
+ domParentFiber = domParentFiber.parent;
469
+ }
470
+ const domParent = domParentFiber.dom;
471
+
472
+ if (fiber.effectTag === EFFECT_TAGS.PLACEMENT) {
473
+ if (fiber.dom != null) {
474
+ domParent.appendChild(fiber.dom);
475
+ }
476
+ runEffects(fiber);
477
+ } else if (fiber.effectTag === EFFECT_TAGS.UPDATE) {
478
+ cancelEffects(fiber);
479
+ if (fiber.dom != null) {
480
+ updateDom(fiber.dom, fiber.alternate.props, fiber.props);
481
+ }
482
+ runEffects(fiber);
483
+ } else if (fiber.effectTag === EFFECT_TAGS.DELETION) {
484
+ cancelEffectsDeep(fiber);
485
+ commitDeletion(fiber, domParent);
486
+ return
487
+ }
488
+
489
+ commitWork(fiber.child);
490
+ commitWork(fiber.sibling);
491
+ };
492
+
493
+ const commitDeletion = (fiber, domParent) => {
494
+ if (fiber.dom) {
495
+ domParent.removeChild(fiber.dom);
496
+ } else {
497
+ let child = fiber.child;
498
+ while (child) {
499
+ commitDeletion(child, domParent);
500
+ child = child.sibling;
501
+ }
502
+ }
503
+ };
504
+
505
+ /**
506
+ * Reconcile children with key optimization
507
+ */
508
+ const reconcileChildren = (wipFiber, elements) => {
509
+ const state = getState();
510
+ let index = 0;
511
+ let prevSibling;
512
+
513
+ // Build map of old fibers by key/index
514
+ const oldFiberMap = new Map();
515
+ let oldFiber = wipFiber.alternate?.child;
516
+ let position = 0;
517
+
518
+ while (oldFiber) {
519
+ const key = oldFiber.key ?? `__index_${position}__`;
520
+ oldFiberMap.set(key, oldFiber);
521
+ oldFiber = oldFiber.sibling;
522
+ position++;
523
+ }
524
+
525
+ // Process new elements
526
+ while (index < elements.length) {
527
+ const element = elements[index];
528
+ if (!element) {
529
+ index++;
530
+ continue
531
+ }
532
+
533
+ const key = element.key ?? `__index_${index}__`;
534
+ const matchedFiber = oldFiberMap.get(key);
535
+
536
+ let newFiber;
537
+ const sameType = matchedFiber && element.type === matchedFiber.type;
538
+
539
+ if (sameType) {
540
+ // Update existing fiber
541
+ newFiber = {
542
+ type: matchedFiber.type,
543
+ props: element.props,
544
+ dom: matchedFiber.dom,
545
+ parent: wipFiber,
546
+ alternate: matchedFiber,
547
+ effectTag: EFFECT_TAGS.UPDATE,
548
+ hooks: matchedFiber.hooks,
549
+ key: element.key,
550
+ };
551
+ oldFiberMap.delete(key);
552
+ } else {
553
+ // Create new fiber
554
+ newFiber = {
555
+ type: element.type,
556
+ props: element.props,
557
+ dom: null,
558
+ parent: wipFiber,
559
+ alternate: null,
560
+ effectTag: EFFECT_TAGS.PLACEMENT,
561
+ key: element.key,
562
+ };
563
+
564
+ // Mark matched fiber for deletion if exists
565
+ if (matchedFiber) {
566
+ matchedFiber.effectTag = EFFECT_TAGS.DELETION;
567
+ state.deletions.push(matchedFiber);
568
+ oldFiberMap.delete(key);
569
+ }
570
+ }
571
+
572
+ // Link fibers
573
+ if (index === 0) {
574
+ wipFiber.child = newFiber;
575
+ } else if (newFiber) {
576
+ prevSibling.sibling = newFiber;
577
+ }
578
+
579
+ prevSibling = newFiber;
580
+ index++;
581
+ }
582
+
583
+ // Delete remaining old fibers
584
+ oldFiberMap.forEach((fiber) => {
585
+ fiber.effectTag = EFFECT_TAGS.DELETION;
586
+ state.deletions.push(fiber);
587
+ });
588
+ };
589
+
590
+ /**
591
+ * Priority levels for updates
592
+ */
593
+ const Priority = {
594
+ IMMEDIATE: 1, // User input (clicks, typing)
595
+ USER_BLOCKING: 2, // Hover, scroll
596
+ NORMAL: 3, // Data fetching
597
+ LOW: 4, // Analytics
598
+ IDLE: 5, // Background tasks
599
+ };
600
+
601
+ Priority.NORMAL;
602
+
603
+ const validateHookCall = () => {
604
+ const state = getState();
605
+ if (!state.wipFiber) {
606
+ throw new Error(
607
+ 'Hooks can only be called inside the body of a function component.',
608
+ )
609
+ }
610
+ if (!Array.isArray(state.wipFiber.hooks)) {
611
+ state.wipFiber.hooks = [];
612
+ }
613
+ };
614
+
615
+ const haveDepsChanged = (oldDeps, newDeps) => {
616
+ if (!oldDeps || !newDeps) return true
617
+ if (oldDeps.length !== newDeps.length) return true
618
+ return oldDeps.some((dep, i) => !Object.is(dep, newDeps[i]))
619
+ };
620
+
621
+ const useStore = (initialState) => {
622
+ const reducer = (state, action) =>
623
+ is.function(action) ? action(state) : action;
624
+ return useReducer(reducer, initialState)
625
+ };
626
+
627
+ const useReducer = (reducer, initialState, init) => {
628
+ validateHookCall();
629
+
630
+ const state = getState();
631
+ const { wipFiber, hookIndex } = state;
632
+ const oldHook = wipFiber.alternate?.hooks?.[hookIndex];
633
+
634
+ const hook = {
635
+ hookID: hookIndex,
636
+ type: RYUNIX_TYPES.RYUNIX_STORE,
637
+ state: oldHook ? oldHook.state : init ? init(initialState) : initialState,
638
+ queue: [], // Siempre nueva cola vacía
639
+ };
640
+
641
+ // Procesar acciones del render anterior
642
+ if (oldHook?.queue) {
643
+ oldHook.queue.forEach((action) => {
644
+ try {
645
+ hook.state = reducer(hook.state, action);
646
+ } catch (error) {
647
+ if (process.env.NODE_ENV !== 'production') {
648
+ console.error('Error in reducer:', error);
649
+ }
650
+ }
651
+ });
652
+ }
653
+
654
+ const dispatch = (action) => {
655
+ if (action === undefined) {
656
+ if (process.env.NODE_ENV !== 'production') {
657
+ console.warn('dispatch called with undefined action');
658
+ }
659
+ return
660
+ }
661
+
662
+ hook.queue.push(action);
663
+
664
+ const currentState = getState();
665
+ currentState.wipRoot = {
666
+ dom: currentState.currentRoot.dom,
667
+ props: currentState.currentRoot.props,
668
+ alternate: currentState.currentRoot,
669
+ };
670
+ currentState.deletions = [];
671
+ currentState.hookIndex = 0;
672
+ scheduleWork(currentState.wipRoot);
673
+ };
674
+
675
+ wipFiber.hooks[hookIndex] = hook;
676
+ state.hookIndex++;
677
+ return [hook.state, dispatch]
678
+ };
679
+
680
+ const useEffect = (callback, deps) => {
681
+ validateHookCall();
682
+
683
+ if (!is.function(callback)) {
684
+ throw new Error('useEffect callback must be a function')
685
+ }
686
+ if (deps !== undefined && !Array.isArray(deps)) {
687
+ throw new Error('useEffect dependencies must be an array or undefined')
688
+ }
689
+
690
+ const state = getState();
691
+ const { wipFiber, hookIndex } = state;
692
+ const oldHook = wipFiber.alternate?.hooks?.[hookIndex];
693
+ const hasChanged = haveDepsChanged(oldHook?.deps, deps);
694
+
695
+ const hook = {
696
+ hookID: hookIndex,
697
+ type: RYUNIX_TYPES.RYUNIX_EFFECT,
698
+ deps,
699
+ effect: hasChanged ? callback : null,
700
+ cancel: oldHook?.cancel,
701
+ };
702
+
703
+ wipFiber.hooks[hookIndex] = hook;
704
+ state.hookIndex++;
705
+ };
706
+
707
+ const useRef = (initialValue) => {
708
+ validateHookCall();
709
+
710
+ const state = getState();
711
+ const { wipFiber, hookIndex } = state;
712
+ const oldHook = wipFiber.alternate?.hooks?.[hookIndex];
713
+
714
+ const hook = {
715
+ hookID: hookIndex,
716
+ type: RYUNIX_TYPES.RYUNIX_REF,
717
+ value: oldHook ? oldHook.value : { current: initialValue },
718
+ };
719
+
720
+ wipFiber.hooks[hookIndex] = hook;
721
+ state.hookIndex++;
722
+ return hook.value
723
+ };
724
+
725
+ const useMemo = (compute, deps) => {
726
+ validateHookCall();
727
+
728
+ if (!is.function(compute)) {
729
+ throw new Error('useMemo callback must be a function')
730
+ }
731
+ if (!Array.isArray(deps)) {
732
+ throw new Error('useMemo requires a dependencies array')
733
+ }
734
+
735
+ const state = getState();
736
+ const { wipFiber, hookIndex } = state;
737
+ const oldHook = wipFiber.alternate?.hooks?.[hookIndex];
738
+
739
+ let value;
740
+ if (oldHook && !haveDepsChanged(oldHook.deps, deps)) {
741
+ value = oldHook.value;
742
+ } else {
743
+ try {
744
+ value = compute();
745
+ } catch (error) {
746
+ if (process.env.NODE_ENV !== 'production') {
747
+ console.error('Error in useMemo computation:', error);
748
+ }
749
+ value = undefined;
750
+ }
751
+ }
752
+
753
+ const hook = {
754
+ hookID: hookIndex,
755
+ type: RYUNIX_TYPES.RYUNIX_MEMO,
756
+ value,
757
+ deps,
758
+ };
759
+
760
+ wipFiber.hooks[hookIndex] = hook;
761
+ state.hookIndex++;
762
+ return value
763
+ };
764
+
765
+ const useCallback = (callback, deps) => {
766
+ if (!is.function(callback)) {
767
+ throw new Error('useCallback requires a function as first argument')
768
+ }
769
+ return useMemo(() => callback, deps)
770
+ };
771
+
772
+ const createContext = (
773
+ contextId = RYUNIX_TYPES.RYUNIX_CONTEXT,
774
+ defaultValue = {},
775
+ ) => {
776
+ const Provider = ({ children, value }) => {
777
+ const element = Fragment({ children });
778
+ element._contextId = contextId;
779
+ element._contextValue = value;
780
+ return element
781
+ };
782
+
783
+ Provider._contextId = contextId;
784
+
785
+ const useContext = (ctxID = contextId) => {
786
+ validateHookCall();
787
+
788
+ const state = getState();
789
+ let fiber = state.wipFiber;
790
+
791
+ while (fiber) {
792
+ if (fiber._contextId === ctxID && fiber._contextValue !== undefined) {
793
+ return fiber._contextValue
794
+ }
795
+ if (
796
+ fiber.type?._contextId === ctxID &&
797
+ fiber.props?.value !== undefined
798
+ ) {
799
+ return fiber.props.value
800
+ }
801
+ fiber = fiber.parent;
802
+ }
803
+ return defaultValue
804
+ };
805
+
806
+ return { Provider, useContext }
807
+ };
808
+
809
+ const useQuery = () => {
810
+ if (typeof window === 'undefined') return {}
811
+
812
+ const searchParams = new URLSearchParams(window.location.search);
813
+ const query = {};
814
+ for (const [key, value] of searchParams.entries()) {
815
+ query[key] = value;
816
+ }
817
+ return query
818
+ };
819
+
820
+ const useHash = () => {
821
+ if (typeof window === 'undefined') return ''
822
+
823
+ const [hash, setHash] = useStore(window.location.hash);
824
+ useEffect(() => {
825
+ const onHashChange = () => setHash(window.location.hash);
826
+ window.addEventListener('hashchange', onHashChange);
827
+ return () => window.removeEventListener('hashchange', onHashChange)
828
+ }, []);
829
+ return hash
830
+ };
831
+
832
+ const useMetadata = (tags = {}, options = {}) => {
833
+ useEffect(() => {
834
+ if (typeof document === 'undefined') return
835
+
836
+ let finalTitle = 'Ryunix App';
837
+ const template = options.title?.template;
838
+ const defaultTitle = options.title?.prefix || 'Ryunix App';
839
+ const pageTitle = tags.pageTitle || tags.title;
840
+
841
+ if (is.string(pageTitle) && pageTitle.trim()) {
842
+ finalTitle = template?.includes('%s')
843
+ ? template.replace('%s', pageTitle)
844
+ : pageTitle;
845
+ } else {
846
+ finalTitle = defaultTitle;
847
+ }
848
+
849
+ document.title = finalTitle;
850
+
851
+ if (tags.canonical) {
852
+ let link = document.querySelector('link[rel="canonical"]');
853
+ if (!link) {
854
+ link = document.createElement('link');
855
+ link.setAttribute('rel', 'canonical');
856
+ document.head.appendChild(link);
857
+ }
858
+ link.setAttribute('href', tags.canonical);
859
+ }
860
+
861
+ Object.entries(tags).forEach(([key, value]) => {
862
+ if (['title', 'pageTitle', 'canonical'].includes(key)) return
863
+
864
+ const isProperty = key.startsWith('og:') || key.startsWith('twitter:');
865
+ const selector = `meta[${isProperty ? 'property' : 'name'}='${key}']`;
866
+ let meta = document.head.querySelector(selector);
867
+
868
+ if (!meta) {
869
+ meta = document.createElement('meta');
870
+ meta.setAttribute(isProperty ? 'property' : 'name', key);
871
+ document.head.appendChild(meta);
872
+ }
873
+ meta.setAttribute('content', value);
874
+ });
875
+ }, [JSON.stringify(tags), JSON.stringify(options)]);
876
+ };
877
+
878
+ // Router Context
879
+ const RouterContext = createContext('ryunix.navigation', {
880
+ location: '/',
881
+ params: {},
882
+ query: {},
883
+ navigate: (path) => {},
884
+ route: null,
885
+ });
886
+
887
+ const findRoute = (routes, path) => {
888
+ const pathname = path.split('?')[0].split('#')[0];
889
+ const notFoundRoute = routes.find((route) => route.NotFound);
890
+ const notFound = notFoundRoute
891
+ ? { route: { component: notFoundRoute.NotFound }, params: {} }
892
+ : { route: { component: null }, params: {} };
893
+
894
+ for (const route of routes) {
895
+ if (route.subRoutes) {
896
+ const childRoute = findRoute(route.subRoutes, path);
897
+ if (childRoute) return childRoute
898
+ }
899
+ if (route.path === '*') return notFound
900
+ if (!route.path || typeof route.path !== 'string') continue
901
+
902
+ const keys = [];
903
+ const pattern = new RegExp(
904
+ `^${route.path.replace(/:\w+/g, (match) => {
905
+ keys.push(match.substring(1));
906
+ return '([^/]+)'
907
+ })}$`,
908
+ );
909
+
910
+ const match = pathname.match(pattern);
911
+ if (match) {
912
+ const params = keys.reduce((acc, key, index) => {
913
+ acc[key] = match[index + 1];
914
+ return acc
915
+ }, {});
916
+ return { route, params }
917
+ }
918
+ }
919
+ return notFound
920
+ };
921
+
922
+ const RouterProvider = ({ routes, children }) => {
923
+ const [location, setLocation] = useStore(window.location.pathname);
924
+
925
+ useEffect(() => {
926
+ const update = () => setLocation(window.location.pathname);
927
+ window.addEventListener('popstate', update);
928
+ window.addEventListener('hashchange', update);
929
+ return () => {
930
+ window.removeEventListener('popstate', update);
931
+ window.removeEventListener('hashchange', update);
932
+ }
933
+ }, [location]);
934
+
935
+ const navigate = (path) => {
936
+ window.history.pushState({}, '', path);
937
+ setLocation(path);
938
+ };
939
+
940
+ const currentRouteData = findRoute(routes, location) || {};
941
+ const query = useQuery();
942
+
943
+ const contextValue = {
944
+ location,
945
+ params: currentRouteData.params || {},
946
+ query,
947
+ navigate,
948
+ route: currentRouteData.route,
949
+ };
950
+
951
+ return createElement(
952
+ RouterContext.Provider,
953
+ { value: contextValue },
954
+ Fragment({ children }),
955
+ )
956
+ };
957
+
958
+ const useRouter = () => {
959
+ return RouterContext.useContext('ryunix.navigation')
960
+ };
961
+
962
+ const Children = () => {
963
+ const { route, params, query, location } = useRouter();
964
+ if (!route || !route.component) return null
965
+ const hash = useHash();
966
+
967
+ useEffect(() => {
968
+ if (hash) {
969
+ const id = hash.slice(1);
970
+ const el = document.getElementById(id);
971
+ if (el) el.scrollIntoView({ block: 'start', behavior: 'smooth' });
972
+ }
973
+ }, [hash]);
974
+
975
+ return createElement(route.component, {
976
+ key: location,
977
+ params,
978
+ query,
979
+ hash,
980
+ })
981
+ };
982
+
983
+ const NavLink = ({ to, exact = false, ...props }) => {
984
+ const { location, navigate } = useRouter();
985
+ const isActive = exact ? location === to : location.startsWith(to);
986
+
987
+ const resolveClass = (cls) =>
988
+ typeof cls === 'function' ? cls({ isActive }) : cls || '';
989
+
990
+ const handleClick = (e) => {
991
+ e.preventDefault();
992
+ navigate(to);
993
+ };
994
+
995
+ const classAttrName = props['ryunix-class'] ? 'ryunix-class' : 'className';
996
+ const classAttrValue = resolveClass(
997
+ props['ryunix-class'] || props['className'],
998
+ );
999
+
1000
+ const {
1001
+ ['ryunix-class']: _omitRyunix,
1002
+ className: _omitClassName,
1003
+ ...cleanedProps
1004
+ } = props;
1005
+
1006
+ return createElement(
1007
+ 'a',
1008
+ {
1009
+ href: to,
1010
+ onClick: handleClick,
1011
+ [classAttrName]: classAttrValue,
1012
+ ...cleanedProps,
1013
+ },
1014
+ props.children,
1015
+ )
1016
+ };
1017
+
1018
+ /**
1019
+ * useStore with priority support
1020
+ */
1021
+ const useStorePriority = (initialState) => {
1022
+ const reducer = (state, action) =>
1023
+ typeof action === 'function' ? action.value(state) : action.value;
1024
+
1025
+ const [state, baseDispatch] = useReducer(reducer, initialState);
1026
+
1027
+ const dispatch = (action, priority = Priority.NORMAL) => {
1028
+ const wrappedAction = {
1029
+ value: action,
1030
+ priority,
1031
+ };
1032
+
1033
+ baseDispatch(wrappedAction);
1034
+ };
1035
+
1036
+ return [state, dispatch]
1037
+ };
1038
+
1039
+ /**
1040
+ * useTransition - Mark updates as non-urgent
1041
+ */
1042
+ const useTransition = () => {
1043
+ const [isPending, setIsPending] = useStorePriority(false);
1044
+
1045
+ const startTransition = (callback) => {
1046
+ setIsPending(true, Priority.IMMEDIATE);
1047
+
1048
+ setTimeout(() => {
1049
+ callback();
1050
+ setIsPending(false, Priority.IMMEDIATE);
1051
+ }, 0);
1052
+ };
1053
+
1054
+ return [isPending, startTransition]
1055
+ };
1056
+
1057
+ /**
1058
+ * useDeferredValue - Defer value updates
1059
+ */
1060
+ const useDeferredValue = (value) => {
1061
+ const [deferredValue, setDeferredValue] = useStorePriority(value);
1062
+
1063
+ useEffect(() => {
1064
+ const timeout = setTimeout(() => {
1065
+ setDeferredValue(value, Priority.LOW);
1066
+ }, 100);
1067
+
1068
+ return () => clearTimeout(timeout)
1069
+ }, [value]);
1070
+
1071
+ return deferredValue
1072
+ };
1073
+
1074
+ var hooks = /*#__PURE__*/Object.freeze({
1075
+ __proto__: null,
1076
+ Children: Children,
1077
+ NavLink: NavLink,
1078
+ RouterProvider: RouterProvider,
1079
+ createContext: createContext,
1080
+ useCallback: useCallback,
1081
+ useDeferredValue: useDeferredValue,
1082
+ useEffect: useEffect,
1083
+ useHash: useHash,
1084
+ useMemo: useMemo,
1085
+ useMetadata: useMetadata,
1086
+ useQuery: useQuery,
1087
+ useReducer: useReducer,
1088
+ useRef: useRef,
1089
+ useRouter: useRouter,
1090
+ useStore: useStore,
1091
+ useStorePriority: useStorePriority,
1092
+ useTransition: useTransition
1093
+ });
1094
+
1095
+ const updateFunctionComponent = (fiber) => {
1096
+ const state = getState();
1097
+ state.wipFiber = fiber;
1098
+ state.hookIndex = 0;
1099
+ state.wipFiber.hooks = [];
1100
+
1101
+ const children = [fiber.type(fiber.props)];
1102
+
1103
+ if (fiber.type._contextId && fiber.props.value !== undefined) {
1104
+ fiber._contextId = fiber.type._contextId;
1105
+ fiber._contextValue = fiber.props.value;
1106
+ }
1107
+
1108
+ reconcileChildren(fiber, children);
1109
+ };
1110
+
1111
+ const updateHostComponent = (fiber) => {
1112
+ if (!fiber.dom) {
1113
+ fiber.dom = createDom(fiber);
1114
+ }
1115
+ const children = fiber.props?.children || [];
1116
+ reconcileChildren(fiber, children);
1117
+ };
1118
+
1119
+ /* Image component */
1120
+ const Image = ({ src, ...props }) => {
1121
+ return createElement('img', { ...props, src })
1122
+ };
1123
+
1124
+ const { Provider: MDXProvider, useContext: useMDXComponents } = createContext(
1125
+ 'ryunix.mdx',
1126
+ {},
1127
+ );
1128
+
1129
+ /**
1130
+ * Get merged MDX components from context and provided components
1131
+ * @param {Object} components - Additional components to merge
1132
+ * @returns {Object} Merged components object
1133
+ */
1134
+ const getMDXComponents = (components) => {
1135
+ const contextComponents = useMDXComponents();
1136
+ return {
1137
+ ...contextComponents,
1138
+ ...components,
1139
+ }
1140
+ };
1141
+
1142
+ /**
1143
+ * Default MDX components with Ryunix-optimized rendering
1144
+ */
1145
+ const defaultComponents = {
1146
+ // Headings
1147
+ h1: (props) => createElement('h1', { ...props }),
1148
+ h2: (props) => createElement('h2', { ...props }),
1149
+ h3: (props) => createElement('h3', { ...props }),
1150
+ h4: (props) => createElement('h4', { ...props }),
1151
+ h5: (props) => createElement('h5', { ...props }),
1152
+ h6: (props) => createElement('h6', { ...props }),
1153
+
1154
+ // Text
1155
+ p: (props) => createElement('p', { ...props }),
1156
+ a: (props) => createElement('a', { ...props }),
1157
+ strong: (props) => createElement('strong', { ...props }),
1158
+ em: (props) => createElement('em', { ...props }),
1159
+ code: (props) => createElement('code', { ...props }),
1160
+
1161
+ // Lists
1162
+ ul: (props) => createElement('ul', { ...props }),
1163
+ ol: (props) => createElement('ol', { ...props }),
1164
+ li: (props) => createElement('li', { ...props }),
1165
+
1166
+ // Blocks
1167
+ blockquote: (props) => createElement('blockquote', { ...props }),
1168
+ pre: (props) => createElement('pre', { ...props }),
1169
+ hr: (props) => createElement('hr', { ...props }),
1170
+
1171
+ // Tables
1172
+ table: (props) => createElement('table', { ...props }),
1173
+ thead: (props) => createElement('thead', { ...props }),
1174
+ tbody: (props) => createElement('tbody', { ...props }),
1175
+ tr: (props) => createElement('tr', { ...props }),
1176
+ th: (props) => createElement('th', { ...props }),
1177
+ td: (props) => createElement('td', { ...props }),
1178
+
1179
+ // Media
1180
+ img: (props) => createElement('img', { ...props }),
1181
+ };
1182
+
1183
+ /**
1184
+ * MDX Wrapper component
1185
+ * Provides default styling and components for MDX content
1186
+ */
1187
+ const MDXContent = ({ children, components = {} }) => {
1188
+ const mergedComponents = getMDXComponents(components);
1189
+
1190
+ return createElement(
1191
+ MDXProvider,
1192
+ { value: mergedComponents },
1193
+ createElement('div', null, children),
1194
+ )
1195
+ };
1196
+
1197
+ /**
1198
+ * Performance profiler for Ryunix
1199
+ */
1200
+ class Profiler {
1201
+ constructor() {
1202
+ this.enabled = process.env.NODE_ENV !== 'production';
1203
+ this.measures = new Map();
1204
+ this.renderTimes = [];
1205
+ this.maxSamples = 100;
1206
+ }
1207
+
1208
+ startMeasure(name) {
1209
+ if (!this.enabled) return
1210
+ this.measures.set(name, performance.now());
1211
+ }
1212
+
1213
+ endMeasure(name) {
1214
+ if (!this.enabled) return
1215
+ const start = this.measures.get(name);
1216
+ if (!start) return
1217
+
1218
+ const duration = performance.now() - start;
1219
+ this.measures.delete(name);
1220
+
1221
+ return duration
1222
+ }
1223
+
1224
+ recordRender(componentName, duration) {
1225
+ if (!this.enabled) return
1226
+
1227
+ this.renderTimes.push({
1228
+ component: componentName,
1229
+ duration,
1230
+ timestamp: Date.now(),
1231
+ });
1232
+
1233
+ if (this.renderTimes.length > this.maxSamples) {
1234
+ this.renderTimes.shift();
1235
+ }
1236
+ }
1237
+
1238
+ getStats() {
1239
+ if (!this.enabled) return null
1240
+
1241
+ const total = this.renderTimes.reduce((sum, r) => sum + r.duration, 0);
1242
+ const avg = total / this.renderTimes.length;
1243
+ const max = Math.max(...this.renderTimes.map((r) => r.duration));
1244
+ const min = Math.min(...this.renderTimes.map((r) => r.duration));
1245
+
1246
+ return { total, avg, max, min, count: this.renderTimes.length }
1247
+ }
1248
+
1249
+ getSlowestComponents(limit = 10) {
1250
+ if (!this.enabled) return []
1251
+
1252
+ const byComponent = new Map();
1253
+
1254
+ this.renderTimes.forEach(({ component, duration }) => {
1255
+ if (!byComponent.has(component)) {
1256
+ byComponent.set(component, { total: 0, count: 0, max: 0 });
1257
+ }
1258
+ const stats = byComponent.get(component);
1259
+ stats.total += duration;
1260
+ stats.count++;
1261
+ stats.max = Math.max(stats.max, duration);
1262
+ });
1263
+
1264
+ return Array.from(byComponent.entries())
1265
+ .map(([name, stats]) => ({
1266
+ name,
1267
+ avg: stats.total / stats.count,
1268
+ max: stats.max,
1269
+ count: stats.count,
1270
+ }))
1271
+ .sort((a, b) => b.avg - a.avg)
1272
+ .slice(0, limit)
1273
+ }
1274
+
1275
+ logStats() {
1276
+ if (!this.enabled) return
1277
+
1278
+ const stats = this.getStats();
1279
+ if (!stats) return
1280
+
1281
+ console.group('🔍 Ryunix Performance Stats');
1282
+ console.log(`Total renders: ${stats.count}`);
1283
+ console.log(`Avg render time: ${stats.avg.toFixed(2)}ms`);
1284
+ console.log(
1285
+ `Min: ${stats.min.toFixed(2)}ms | Max: ${stats.max.toFixed(2)}ms`,
1286
+ );
1287
+
1288
+ const slowest = this.getSlowestComponents(5);
1289
+ if (slowest.length > 0) {
1290
+ console.log('\n⚠️ Slowest components:');
1291
+ slowest.forEach((comp, i) => {
1292
+ console.log(
1293
+ `${i + 1}. ${comp.name}: ${comp.avg.toFixed(2)}ms avg (${comp.count} renders)`,
1294
+ );
1295
+ });
1296
+ }
1297
+ console.groupEnd();
1298
+ }
1299
+
1300
+ clear() {
1301
+ this.renderTimes = [];
1302
+ this.measures.clear();
1303
+ }
1304
+
1305
+ enable() {
1306
+ this.enabled = true;
1307
+ }
1308
+
1309
+ disable() {
1310
+ this.enabled = false;
1311
+ }
1312
+ }
1313
+
1314
+ // Global profiler instance
1315
+ const profiler = new Profiler();
1316
+
1317
+ /**
1318
+ * Hook to profile component render
1319
+ */
1320
+ const useProfiler = (componentName) => {
1321
+ const startTime = performance.now();
1322
+
1323
+ return () => {
1324
+ const duration = performance.now() - startTime;
1325
+ profiler.recordRender(componentName, duration);
1326
+ }
1327
+ };
1328
+
1329
+ /**
1330
+ * HOC to profile component
1331
+ */
1332
+ const withProfiler = (Component, name) => {
1333
+ return (props) => {
1334
+ profiler.startMeasure(name);
1335
+ const result = Component(props);
1336
+ const duration = profiler.endMeasure(name);
1337
+ if (duration) profiler.recordRender(name, duration);
1338
+ return result
1339
+ }
1340
+ };
1341
+
1342
+ const workLoop = (deadline) => {
1343
+ const state = getState();
1344
+ let shouldYield = false;
1345
+
1346
+ while (state.nextUnitOfWork && !shouldYield) {
1347
+ state.nextUnitOfWork = performUnitOfWork(state.nextUnitOfWork);
1348
+ shouldYield = deadline.timeRemaining() < 1;
1349
+ }
1350
+
1351
+ if (!state.nextUnitOfWork && state.wipRoot) {
1352
+ commitRoot();
1353
+ }
1354
+
1355
+ requestIdleCallback(workLoop);
1356
+ };
1357
+
1358
+ requestIdleCallback(workLoop);
1359
+
1360
+ const performUnitOfWork = (fiber) => {
1361
+ const componentName = fiber.type?.name || fiber.type?.displayName || 'Unknown';
1362
+
1363
+ profiler.startMeasure(componentName);
1364
+
1365
+ const isFunctionComponent = fiber.type instanceof Function;
1366
+ if (isFunctionComponent) {
1367
+ updateFunctionComponent(fiber);
1368
+ } else {
1369
+ updateHostComponent(fiber);
1370
+ }
1371
+
1372
+ const duration = profiler.endMeasure(componentName);
1373
+ if (duration) profiler.recordRender(componentName, duration);
1374
+
1375
+ if (fiber.child) {
1376
+ return fiber.child
1377
+ }
1378
+ let nextFiber = fiber;
1379
+ while (nextFiber) {
1380
+ if (nextFiber.sibling) {
1381
+ return nextFiber.sibling
1382
+ }
1383
+ nextFiber = nextFiber.parent;
1384
+ }
1385
+ };
1386
+
1387
+ const scheduleWork = (root, priority = Priority.NORMAL) => {
1388
+ const state = getState();
1389
+ state.nextUnitOfWork = root;
1390
+ state.wipRoot = root;
1391
+ state.deletions = [];
1392
+ state.hookIndex = 0;
1393
+ state.effects = [];
1394
+
1395
+ // Higher priority = faster scheduling
1396
+ if (priority <= Priority.USER_BLOCKING) {
1397
+ requestIdleCallback(workLoop);
1398
+ } else {
1399
+ setTimeout(() => requestIdleCallback(workLoop), 0);
1400
+ }
1401
+ };
1402
+
1403
+ const render = (element, container) => {
1404
+ const state = getState();
1405
+ state.wipRoot = {
1406
+ dom: container,
1407
+ props: {
1408
+ children: [element],
1409
+ },
1410
+ alternate: state.currentRoot,
1411
+ };
1412
+
1413
+ state.nextUnitOfWork = state.wipRoot;
1414
+ state.deletions = [];
1415
+ scheduleWork(state.wipRoot);
1416
+ return state.wipRoot
1417
+ };
1418
+
1419
+ const init = (MainElement, root = '__ryunix') => {
1420
+ const state = getState();
1421
+ state.containerRoot = document.getElementById(root);
1422
+ const renderProcess = render(MainElement, state.containerRoot);
1423
+ return renderProcess
1424
+ };
1425
+
1426
+ const safeRender = (component, props, onError) => {
1427
+ try {
1428
+ return component(props)
1429
+ } catch (error) {
1430
+ if (process.env.NODE_ENV !== 'production') {
1431
+ console.error('Component error:', error);
1432
+ }
1433
+ if (onError) onError(error);
1434
+ return null
1435
+ }
1436
+ };
1437
+
1438
+ /**
1439
+ * memo - Memoize component to prevent unnecessary re-renders
1440
+ */
1441
+ const memo = (Component, arePropsEqual) => {
1442
+ return (props) => {
1443
+ const memoizedElement = useMemo(() => {
1444
+ return Component(props)
1445
+ }, [
1446
+ // Default comparison: shallow props comparison
1447
+ ...Object.values(props),
1448
+ ]);
1449
+
1450
+ return memoizedElement
1451
+ }
1452
+ };
1453
+
1454
+ /**
1455
+ * Lazy load component
1456
+ */
1457
+ const lazy = (importFn) => {
1458
+ let Component = null;
1459
+ let promise = null;
1460
+ let error = null;
1461
+
1462
+ return (props) => {
1463
+ const [, forceUpdate] = useStore(0);
1464
+
1465
+ useEffect(() => {
1466
+ if (Component || error) return
1467
+
1468
+ if (!promise) {
1469
+ promise = importFn()
1470
+ .then((module) => {
1471
+ Component = module.default || module;
1472
+ forceUpdate((x) => x + 1);
1473
+ })
1474
+ .catch((err) => {
1475
+ error = err;
1476
+ forceUpdate((x) => x + 1);
1477
+ });
1478
+ }
1479
+ }, []);
1480
+
1481
+ if (error) throw error
1482
+ if (!Component) return null
1483
+ return createElement(Component, props)
1484
+ }
1485
+ };
1486
+
1487
+ /**
1488
+ * Suspense component (basic implementation)
1489
+ */
1490
+ const Suspense = ({ fallback, children }) => {
1491
+ const [isLoading, setIsLoading] = useStore(true);
1492
+
1493
+ useEffect(() => {
1494
+ setIsLoading(false);
1495
+ }, []);
1496
+
1497
+ if (isLoading && fallback) {
1498
+ return fallback
1499
+ }
1500
+
1501
+ return children
1502
+ };
1503
+
1504
+ let isBatching = false;
1505
+ let pendingUpdates = [];
1506
+
1507
+ /**
1508
+ * Batch multiple state updates into single render
1509
+ */
1510
+ const batchUpdates = (callback) => {
1511
+ const wasBatching = isBatching;
1512
+ isBatching = true;
1513
+
1514
+ try {
1515
+ callback();
1516
+ } finally {
1517
+ isBatching = wasBatching;
1518
+
1519
+ if (!isBatching && pendingUpdates.length > 0) {
1520
+ flushUpdates();
1521
+ }
1522
+ }
1523
+ };
1524
+
1525
+ /**
1526
+ * Flush all pending updates
1527
+ */
1528
+ const flushUpdates = () => {
1529
+ if (pendingUpdates.length === 0) return
1530
+
1531
+ const updates = pendingUpdates;
1532
+ pendingUpdates = [];
1533
+
1534
+ // Execute all updates
1535
+ updates.forEach((update) => update());
1536
+ };
1537
+
1538
+ var Ryunix = /*#__PURE__*/Object.freeze({
1539
+ __proto__: null,
1540
+ Children: Children,
1541
+ Fragment: Fragment,
1542
+ Hooks: hooks,
1543
+ NavLink: NavLink,
1544
+ Priority: Priority,
1545
+ RouterProvider: RouterProvider,
1546
+ Suspense: Suspense,
1547
+ batchUpdates: batchUpdates,
1548
+ createContext: createContext,
1549
+ createElement: createElement,
1550
+ init: init,
1551
+ lazy: lazy,
1552
+ memo: memo,
1553
+ profiler: profiler,
1554
+ render: render,
1555
+ safeRender: safeRender,
1556
+ useCallback: useCallback,
1557
+ useDeferredValue: useDeferredValue,
1558
+ useEffect: useEffect,
1559
+ useHash: useHash,
1560
+ useMemo: useMemo,
1561
+ useMetadata: useMetadata,
1562
+ useProfiler: useProfiler,
1563
+ useQuery: useQuery,
1564
+ useReducer: useReducer,
1565
+ useRef: useRef,
1566
+ useRouter: useRouter,
1567
+ useStore: useStore,
1568
+ useStorePriority: useStorePriority,
1569
+ useTransition: useTransition,
1570
+ withProfiler: withProfiler
1571
+ });
1572
+
1573
+ window.Ryunix = Ryunix;
1574
+
1575
+ export { Children, Fragment, hooks as Hooks, Image, MDXContent, MDXProvider, NavLink, Priority, RouterProvider, Suspense, batchUpdates, createContext, createElement, Ryunix as default, defaultComponents, getMDXComponents, init, lazy, memo, profiler, render, safeRender, useCallback, useDeferredValue, useEffect, useHash, useMDXComponents, useMemo, useMetadata, useProfiler, useQuery, useReducer, useRef, useRouter, useStore, useStorePriority, useTransition, withProfiler };
1576
+ //# sourceMappingURL=Ryunix.esm.js.map