@unsetsoft/ryunixjs 1.2.2 → 1.2.3-canary.1

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