@unsetsoft/ryunixjs 1.2.2 → 1.2.3-canary.0
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 +677 -544
- package/dist/Ryunix.min.js +1 -1
- package/package.json +1 -1
package/dist/Ryunix.js
CHANGED
|
@@ -1,105 +1,115 @@
|
|
|
1
1
|
(function (global, factory) {
|
|
2
|
-
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports
|
|
3
|
-
typeof define === 'function' && define.amd ? define(['exports'
|
|
4
|
-
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.Ryunix = {}
|
|
5
|
-
})(this, (function (exports
|
|
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
|
-
|
|
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:
|
|
15
|
+
deletions: [],
|
|
13
16
|
wipFiber: null,
|
|
14
|
-
hookIndex:
|
|
15
|
-
effects:
|
|
16
|
-
};
|
|
17
|
+
hookIndex: 0,
|
|
18
|
+
effects: [],
|
|
19
|
+
});
|
|
17
20
|
|
|
18
|
-
|
|
21
|
+
// Singleton for backward compatibility, but allows testing with isolated instances
|
|
22
|
+
let globalState = createRenderState();
|
|
23
|
+
|
|
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')
|
|
22
|
-
|
|
23
|
-
RYUNIX_EFFECT: Symbol('ryunix.effect')
|
|
24
|
-
RYUNIX_MEMO: Symbol('ryunix.memo')
|
|
25
|
-
RYUNIX_URL_QUERY: Symbol('ryunix.urlQuery')
|
|
26
|
-
RYUNIX_REF: Symbol('ryunix.ref')
|
|
27
|
-
RYUNIX_STORE: Symbol('ryunix.store')
|
|
28
|
-
RYUNIX_REDUCE: Symbol('ryunix.reduce')
|
|
29
|
-
RYUNIX_FRAGMENT: Symbol('ryunix.fragment')
|
|
30
|
-
RYUNIX_CONTEXT: Symbol('ryunix.context')
|
|
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
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
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
|
-
|
|
45
|
-
|
|
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')
|
|
50
|
-
UPDATE: Symbol('ryunix.reconciler.status.update')
|
|
51
|
-
DELETION: Symbol('ryunix.reconciler.status.deletion')
|
|
52
|
-
NO_EFFECT: Symbol('ryunix.reconciler.status.
|
|
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
|
-
*
|
|
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
|
-
|
|
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
|
-
|
|
77
|
-
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
|
-
*
|
|
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
|
|
98
|
+
type,
|
|
96
99
|
props: {
|
|
97
|
-
|
|
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,351 @@
|
|
|
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
|
-
|
|
112
|
-
|
|
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
|
-
*
|
|
122
|
-
* @param fiber -
|
|
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
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
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
|
-
*
|
|
139
|
-
*
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
*
|
|
170
|
-
* @param fiber -
|
|
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
|
|
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
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
hook.
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
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
|
-
|
|
229
|
+
// Run new effect
|
|
230
|
+
try {
|
|
231
|
+
const cleanup = hook.effect();
|
|
190
232
|
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
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
|
+
if (!nextClasses) {
|
|
300
|
+
if (process.env.NODE_ENV !== 'production') {
|
|
301
|
+
throw new Error('className/ryunix-class cannot be empty')
|
|
196
302
|
}
|
|
303
|
+
return
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
// Remove old classes
|
|
307
|
+
if (prevClasses) {
|
|
308
|
+
const oldClasses = prevClasses.split(/\s+/).filter(Boolean);
|
|
309
|
+
dom.classList.remove(...oldClasses);
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
// Add new classes
|
|
313
|
+
const newClasses = nextClasses.split(/\s+/).filter(Boolean);
|
|
314
|
+
if (newClasses.length > 0) {
|
|
315
|
+
dom.classList.add(...newClasses);
|
|
197
316
|
}
|
|
198
317
|
};
|
|
199
318
|
|
|
200
319
|
/**
|
|
201
|
-
*
|
|
202
|
-
* @param fiber -
|
|
203
|
-
*
|
|
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
|
|
320
|
+
* Create a DOM element from fiber
|
|
321
|
+
* @param {Object} fiber - Fiber node
|
|
322
|
+
* @returns {HTMLElement|Text|null}
|
|
209
323
|
*/
|
|
210
324
|
const createDom = (fiber) => {
|
|
325
|
+
// Fragments don't create real DOM nodes
|
|
211
326
|
if (fiber.type === RYUNIX_TYPES.RYUNIX_FRAGMENT) {
|
|
212
|
-
return null
|
|
327
|
+
return null
|
|
213
328
|
}
|
|
214
|
-
const dom =
|
|
215
|
-
fiber.type == RYUNIX_TYPES.TEXT_ELEMENT
|
|
216
|
-
? document.createTextNode('')
|
|
217
|
-
: document.createElement(fiber.type);
|
|
218
329
|
|
|
219
|
-
|
|
330
|
+
let dom;
|
|
220
331
|
|
|
221
|
-
|
|
332
|
+
try {
|
|
333
|
+
if (fiber.type === RYUNIX_TYPES.TEXT_ELEMENT) {
|
|
334
|
+
dom = document.createTextNode('');
|
|
335
|
+
} else if (is.string(fiber.type)) {
|
|
336
|
+
dom = document.createElement(fiber.type);
|
|
337
|
+
} else {
|
|
338
|
+
if (process.env.NODE_ENV !== 'production') {
|
|
339
|
+
console.warn(
|
|
340
|
+
'Attempted to create DOM for non-host component:',
|
|
341
|
+
fiber.type,
|
|
342
|
+
);
|
|
343
|
+
}
|
|
344
|
+
return null
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
updateDom(dom, {}, fiber.props);
|
|
348
|
+
return dom
|
|
349
|
+
} catch (error) {
|
|
350
|
+
if (process.env.NODE_ENV !== 'production') {
|
|
351
|
+
console.error('Error creating DOM element:', error, fiber);
|
|
352
|
+
}
|
|
353
|
+
return null
|
|
354
|
+
}
|
|
222
355
|
};
|
|
223
356
|
|
|
224
357
|
/**
|
|
225
|
-
*
|
|
226
|
-
*
|
|
227
|
-
* @param
|
|
228
|
-
* @param
|
|
229
|
-
* @param nextProps - An object containing the new props that need to be updated in the DOM.
|
|
358
|
+
* Update DOM element with new props
|
|
359
|
+
* @param {HTMLElement|Text} dom - DOM element
|
|
360
|
+
* @param {Object} prevProps - Previous props
|
|
361
|
+
* @param {Object} nextProps - Next props
|
|
230
362
|
*/
|
|
231
|
-
const updateDom = (dom, prevProps, nextProps) => {
|
|
363
|
+
const updateDom = (dom, prevProps = {}, nextProps = {}) => {
|
|
364
|
+
// Remove old event listeners
|
|
232
365
|
Object.keys(prevProps)
|
|
233
366
|
.filter(isEvent)
|
|
234
367
|
.filter((key) => isGone(nextProps)(key) || isNew(prevProps, nextProps)(key))
|
|
235
368
|
.forEach((name) => {
|
|
236
369
|
const eventType = name.toLowerCase().substring(2);
|
|
237
|
-
|
|
370
|
+
try {
|
|
371
|
+
dom.removeEventListener(eventType, prevProps[name]);
|
|
372
|
+
} catch (error) {
|
|
373
|
+
if (process.env.NODE_ENV !== 'production') {
|
|
374
|
+
console.warn('Error removing event listener:', error);
|
|
375
|
+
}
|
|
376
|
+
}
|
|
238
377
|
});
|
|
239
378
|
|
|
379
|
+
// Remove old properties
|
|
240
380
|
Object.keys(prevProps)
|
|
241
381
|
.filter(isProperty)
|
|
242
382
|
.filter(isGone(nextProps))
|
|
243
383
|
.forEach((name) => {
|
|
384
|
+
// Skip special properties
|
|
385
|
+
if (
|
|
386
|
+
[
|
|
387
|
+
STRINGS.STYLE,
|
|
388
|
+
OLD_STRINGS.STYLE,
|
|
389
|
+
STRINGS.CLASS_NAME,
|
|
390
|
+
OLD_STRINGS.CLASS_NAME,
|
|
391
|
+
].includes(name)
|
|
392
|
+
) {
|
|
393
|
+
return
|
|
394
|
+
}
|
|
244
395
|
dom[name] = '';
|
|
245
396
|
});
|
|
246
397
|
|
|
398
|
+
// Set new properties
|
|
247
399
|
Object.keys(nextProps)
|
|
248
400
|
.filter(isProperty)
|
|
249
401
|
.filter(isNew(prevProps, nextProps))
|
|
250
402
|
.forEach((name) => {
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
if (nextProps['ryunix-class'] === '') {
|
|
257
|
-
throw new Error('data-class cannot be empty.')
|
|
403
|
+
try {
|
|
404
|
+
// Handle style properties
|
|
405
|
+
if (name === STRINGS.STYLE || name === OLD_STRINGS.STYLE) {
|
|
406
|
+
const styleValue = nextProps[name];
|
|
407
|
+
applyStyles(dom, styleValue);
|
|
258
408
|
}
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
409
|
+
// Handle className properties
|
|
410
|
+
else if (name === STRINGS.CLASS_NAME) {
|
|
411
|
+
applyClasses(
|
|
412
|
+
dom,
|
|
413
|
+
prevProps[STRINGS.CLASS_NAME],
|
|
414
|
+
nextProps[STRINGS.CLASS_NAME],
|
|
263
415
|
);
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
throw new Error('className cannot be empty.')
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
prevProps.className &&
|
|
273
|
-
dom.classList.remove(
|
|
274
|
-
...(prevProps.className.split(/\s+/).filter(Boolean) || []),
|
|
416
|
+
} else if (name === OLD_STRINGS.CLASS_NAME) {
|
|
417
|
+
applyClasses(
|
|
418
|
+
dom,
|
|
419
|
+
prevProps[OLD_STRINGS.CLASS_NAME],
|
|
420
|
+
nextProps[OLD_STRINGS.CLASS_NAME],
|
|
275
421
|
);
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
422
|
+
}
|
|
423
|
+
// Handle other properties
|
|
424
|
+
else {
|
|
425
|
+
// Special handling for value and checked (controlled components)
|
|
426
|
+
if (name === 'value' || name === 'checked') {
|
|
427
|
+
if (dom[name] !== nextProps[name]) {
|
|
428
|
+
dom[name] = nextProps[name];
|
|
429
|
+
}
|
|
430
|
+
} else {
|
|
431
|
+
dom[name] = nextProps[name];
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
} catch (error) {
|
|
435
|
+
if (process.env.NODE_ENV !== 'production') {
|
|
436
|
+
console.warn(`Error setting property ${name}:`, error);
|
|
437
|
+
}
|
|
279
438
|
}
|
|
280
439
|
});
|
|
281
440
|
|
|
441
|
+
// Add new event listeners
|
|
282
442
|
Object.keys(nextProps)
|
|
283
443
|
.filter(isEvent)
|
|
284
444
|
.filter(isNew(prevProps, nextProps))
|
|
285
445
|
.forEach((name) => {
|
|
286
446
|
const eventType = name.toLowerCase().substring(2);
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
return '-' + v.toLowerCase()
|
|
447
|
+
try {
|
|
448
|
+
dom.addEventListener(eventType, nextProps[name]);
|
|
449
|
+
} catch (error) {
|
|
450
|
+
if (process.env.NODE_ENV !== 'production') {
|
|
451
|
+
console.warn('Error adding event listener:', error);
|
|
452
|
+
}
|
|
453
|
+
}
|
|
295
454
|
});
|
|
296
|
-
acc += `${key}: ${style[styleName]};`;
|
|
297
|
-
return acc
|
|
298
|
-
}, '');
|
|
299
455
|
};
|
|
300
456
|
|
|
301
|
-
/**
|
|
302
|
-
* The function commits changes made to the virtual DOM to the actual DOM.
|
|
303
|
-
*/
|
|
304
457
|
const commitRoot = () => {
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
458
|
+
const state = getState();
|
|
459
|
+
state.deletions.forEach(commitWork);
|
|
460
|
+
commitWork(state.wipRoot.child);
|
|
461
|
+
state.currentRoot = state.wipRoot;
|
|
462
|
+
state.wipRoot = null;
|
|
309
463
|
};
|
|
310
464
|
|
|
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
465
|
const commitWork = (fiber) => {
|
|
319
466
|
if (!fiber) {
|
|
320
467
|
return
|
|
@@ -347,13 +494,6 @@
|
|
|
347
494
|
commitWork(fiber.sibling);
|
|
348
495
|
};
|
|
349
496
|
|
|
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
497
|
const commitDeletion = (fiber, domParent) => {
|
|
358
498
|
if (fiber.dom) {
|
|
359
499
|
domParent.removeChild(fiber.dom);
|
|
@@ -366,16 +506,8 @@
|
|
|
366
506
|
}
|
|
367
507
|
};
|
|
368
508
|
|
|
369
|
-
/**
|
|
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
|
|
377
|
-
*/
|
|
378
509
|
const reconcileChildren = (wipFiber, elements) => {
|
|
510
|
+
const state = getState();
|
|
379
511
|
let index = 0;
|
|
380
512
|
let oldFiber = wipFiber.alternate && wipFiber.alternate.child;
|
|
381
513
|
let prevSibling;
|
|
@@ -409,7 +541,7 @@
|
|
|
409
541
|
}
|
|
410
542
|
if (oldFiber && !sameType) {
|
|
411
543
|
oldFiber.effectTag = EFFECT_TAGS.DELETION;
|
|
412
|
-
|
|
544
|
+
state.deletions.push(oldFiber);
|
|
413
545
|
}
|
|
414
546
|
|
|
415
547
|
if (oldFiber) {
|
|
@@ -427,20 +559,14 @@
|
|
|
427
559
|
}
|
|
428
560
|
};
|
|
429
561
|
|
|
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
562
|
const updateFunctionComponent = (fiber) => {
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
563
|
+
const state = getState();
|
|
564
|
+
state.wipFiber = fiber;
|
|
565
|
+
state.hookIndex = 0;
|
|
566
|
+
state.wipFiber.hooks = [];
|
|
567
|
+
|
|
441
568
|
const children = [fiber.type(fiber.props)];
|
|
442
569
|
|
|
443
|
-
// Aquí detectamos si es Provider para guardar contexto y valor en fiber
|
|
444
570
|
if (fiber.type._contextId && fiber.props.value !== undefined) {
|
|
445
571
|
fiber._contextId = fiber.type._contextId;
|
|
446
572
|
fiber._contextValue = fiber.props.value;
|
|
@@ -449,62 +575,27 @@
|
|
|
449
575
|
reconcileChildren(fiber, children);
|
|
450
576
|
};
|
|
451
577
|
|
|
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
578
|
const updateHostComponent = (fiber) => {
|
|
459
|
-
|
|
460
|
-
|
|
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);
|
|
579
|
+
if (!fiber.dom) {
|
|
580
|
+
fiber.dom = createDom(fiber);
|
|
470
581
|
}
|
|
582
|
+
const children = fiber.props?.children || [];
|
|
583
|
+
reconcileChildren(fiber, children);
|
|
471
584
|
};
|
|
472
585
|
|
|
473
|
-
/**
|
|
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'.
|
|
478
|
-
*/
|
|
479
586
|
const Image = ({ src, ...props }) => {
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
const url = src;
|
|
483
|
-
|
|
484
|
-
const ImageProps = {
|
|
485
|
-
src: url,
|
|
486
|
-
...props,
|
|
487
|
-
};
|
|
488
|
-
|
|
489
|
-
return createElement('img', ImageProps, null)
|
|
587
|
+
return createElement('img', { ...props, src })
|
|
490
588
|
};
|
|
491
589
|
|
|
492
|
-
/**
|
|
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
|
|
499
|
-
*/
|
|
500
590
|
const workLoop = (deadline) => {
|
|
591
|
+
const state = getState();
|
|
501
592
|
let shouldYield = false;
|
|
502
|
-
while (
|
|
503
|
-
|
|
593
|
+
while (state.nextUnitOfWork && !shouldYield) {
|
|
594
|
+
state.nextUnitOfWork = performUnitOfWork(state.nextUnitOfWork);
|
|
504
595
|
shouldYield = deadline.timeRemaining() < 1;
|
|
505
596
|
}
|
|
506
597
|
|
|
507
|
-
if (!
|
|
598
|
+
if (!state.nextUnitOfWork && state.wipRoot) {
|
|
508
599
|
commitRoot();
|
|
509
600
|
}
|
|
510
601
|
|
|
@@ -513,18 +604,6 @@
|
|
|
513
604
|
|
|
514
605
|
requestIdleCallback(workLoop);
|
|
515
606
|
|
|
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
607
|
const performUnitOfWork = (fiber) => {
|
|
529
608
|
const isFunctionComponent = fiber.type instanceof Function;
|
|
530
609
|
if (isFunctionComponent) {
|
|
@@ -545,236 +624,216 @@
|
|
|
545
624
|
};
|
|
546
625
|
|
|
547
626
|
const scheduleWork = (root) => {
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
627
|
+
const state = getState();
|
|
628
|
+
state.nextUnitOfWork = root;
|
|
629
|
+
state.wipRoot = root;
|
|
630
|
+
state.deletions = [];
|
|
631
|
+
state.hookIndex = 0;
|
|
632
|
+
state.effects = [];
|
|
554
633
|
requestIdleCallback(workLoop);
|
|
555
634
|
};
|
|
556
635
|
|
|
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
636
|
const render = (element, container) => {
|
|
566
|
-
|
|
637
|
+
const state = getState();
|
|
638
|
+
state.wipRoot = {
|
|
567
639
|
dom: container,
|
|
568
640
|
props: {
|
|
569
641
|
children: [element],
|
|
570
642
|
},
|
|
571
|
-
alternate:
|
|
643
|
+
alternate: state.currentRoot,
|
|
572
644
|
};
|
|
573
645
|
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
scheduleWork(
|
|
577
|
-
return
|
|
646
|
+
state.nextUnitOfWork = state.wipRoot;
|
|
647
|
+
state.deletions = [];
|
|
648
|
+
scheduleWork(state.wipRoot);
|
|
649
|
+
return state.wipRoot
|
|
578
650
|
};
|
|
579
651
|
|
|
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
652
|
const init = (MainElement, root = '__ryunix') => {
|
|
590
|
-
|
|
653
|
+
const state = getState();
|
|
654
|
+
state.containerRoot = document.getElementById(root);
|
|
655
|
+
const renderProcess = render(MainElement, state.containerRoot);
|
|
656
|
+
return renderProcess
|
|
657
|
+
};
|
|
658
|
+
|
|
659
|
+
const safeRender = (component, props, onError) => {
|
|
660
|
+
try {
|
|
661
|
+
return component(props)
|
|
662
|
+
} catch (error) {
|
|
663
|
+
if (process.env.NODE_ENV !== 'production') {
|
|
664
|
+
console.error('Component error:', error);
|
|
665
|
+
}
|
|
666
|
+
if (onError) onError(error);
|
|
667
|
+
return null
|
|
668
|
+
}
|
|
669
|
+
};
|
|
591
670
|
|
|
592
|
-
|
|
671
|
+
const validateHookCall = () => {
|
|
672
|
+
const state = getState();
|
|
673
|
+
if (!state.wipFiber) {
|
|
674
|
+
throw new Error(
|
|
675
|
+
'Hooks can only be called inside the body of a function component.',
|
|
676
|
+
)
|
|
677
|
+
}
|
|
678
|
+
if (!Array.isArray(state.wipFiber.hooks)) {
|
|
679
|
+
state.wipFiber.hooks = [];
|
|
680
|
+
}
|
|
681
|
+
};
|
|
593
682
|
|
|
594
|
-
|
|
683
|
+
const haveDepsChanged = (oldDeps, newDeps) => {
|
|
684
|
+
if (!oldDeps || !newDeps) return true
|
|
685
|
+
if (oldDeps.length !== newDeps.length) return true
|
|
686
|
+
return oldDeps.some((dep, i) => !Object.is(dep, newDeps[i]))
|
|
595
687
|
};
|
|
596
688
|
|
|
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) => {
|
|
689
|
+
const useStore = (initialState) => {
|
|
604
690
|
const reducer = (state, action) =>
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
return useReducer(reducer, initialState, init)
|
|
691
|
+
is.function(action) ? action(state) : action;
|
|
692
|
+
return useReducer(reducer, initialState)
|
|
608
693
|
};
|
|
609
694
|
|
|
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
695
|
const useReducer = (reducer, initialState, init) => {
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
696
|
+
validateHookCall();
|
|
697
|
+
|
|
698
|
+
const state = getState();
|
|
699
|
+
const { wipFiber, hookIndex } = state;
|
|
700
|
+
const oldHook = wipFiber.alternate?.hooks?.[hookIndex];
|
|
631
701
|
|
|
632
702
|
const hook = {
|
|
633
|
-
hookID:
|
|
703
|
+
hookID: hookIndex,
|
|
634
704
|
type: RYUNIX_TYPES.RYUNIX_STORE,
|
|
635
705
|
state: oldHook ? oldHook.state : init ? init(initialState) : initialState,
|
|
636
|
-
queue:
|
|
706
|
+
queue: [], // Siempre nueva cola vacía
|
|
637
707
|
};
|
|
638
708
|
|
|
639
|
-
|
|
709
|
+
// Procesar acciones del render anterior
|
|
710
|
+
if (oldHook?.queue) {
|
|
640
711
|
oldHook.queue.forEach((action) => {
|
|
641
|
-
|
|
712
|
+
try {
|
|
713
|
+
hook.state = reducer(hook.state, action);
|
|
714
|
+
} catch (error) {
|
|
715
|
+
if (process.env.NODE_ENV !== 'production') {
|
|
716
|
+
console.error('Error in reducer:', error);
|
|
717
|
+
}
|
|
718
|
+
}
|
|
642
719
|
});
|
|
643
720
|
}
|
|
644
721
|
|
|
645
722
|
const dispatch = (action) => {
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
723
|
+
if (action === undefined) {
|
|
724
|
+
if (process.env.NODE_ENV !== 'production') {
|
|
725
|
+
console.warn('dispatch called with undefined action');
|
|
726
|
+
}
|
|
727
|
+
return
|
|
728
|
+
}
|
|
729
|
+
|
|
730
|
+
hook.queue.push(action);
|
|
649
731
|
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
732
|
+
const currentState = getState();
|
|
733
|
+
currentState.wipRoot = {
|
|
734
|
+
dom: currentState.currentRoot.dom,
|
|
735
|
+
props: currentState.currentRoot.props,
|
|
736
|
+
alternate: currentState.currentRoot,
|
|
654
737
|
};
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
scheduleWork(
|
|
738
|
+
currentState.deletions = [];
|
|
739
|
+
currentState.hookIndex = 0;
|
|
740
|
+
scheduleWork(currentState.wipRoot);
|
|
658
741
|
};
|
|
659
742
|
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
});
|
|
663
|
-
|
|
664
|
-
vars.wipFiber.hooks[vars.hookIndex] = hook;
|
|
665
|
-
vars.hookIndex++;
|
|
666
|
-
|
|
743
|
+
wipFiber.hooks[hookIndex] = hook;
|
|
744
|
+
state.hookIndex++;
|
|
667
745
|
return [hook.state, dispatch]
|
|
668
746
|
};
|
|
669
747
|
|
|
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
748
|
const useEffect = (callback, deps) => {
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
749
|
+
validateHookCall();
|
|
750
|
+
|
|
751
|
+
if (!is.function(callback)) {
|
|
752
|
+
throw new Error('useEffect callback must be a function')
|
|
753
|
+
}
|
|
754
|
+
if (deps !== undefined && !Array.isArray(deps)) {
|
|
755
|
+
throw new Error('useEffect dependencies must be an array or undefined')
|
|
756
|
+
}
|
|
685
757
|
|
|
686
|
-
const
|
|
758
|
+
const state = getState();
|
|
759
|
+
const { wipFiber, hookIndex } = state;
|
|
760
|
+
const oldHook = wipFiber.alternate?.hooks?.[hookIndex];
|
|
761
|
+
const hasChanged = haveDepsChanged(oldHook?.deps, deps);
|
|
687
762
|
|
|
688
763
|
const hook = {
|
|
689
|
-
hookID:
|
|
764
|
+
hookID: hookIndex,
|
|
690
765
|
type: RYUNIX_TYPES.RYUNIX_EFFECT,
|
|
691
766
|
deps,
|
|
692
767
|
effect: hasChanged ? callback : null,
|
|
693
768
|
cancel: oldHook?.cancel,
|
|
694
769
|
};
|
|
695
770
|
|
|
696
|
-
|
|
697
|
-
|
|
771
|
+
wipFiber.hooks[hookIndex] = hook;
|
|
772
|
+
state.hookIndex++;
|
|
698
773
|
};
|
|
699
774
|
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
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];
|
|
775
|
+
const useRef = (initialValue) => {
|
|
776
|
+
validateHookCall();
|
|
777
|
+
|
|
778
|
+
const state = getState();
|
|
779
|
+
const { wipFiber, hookIndex } = state;
|
|
780
|
+
const oldHook = wipFiber.alternate?.hooks?.[hookIndex];
|
|
714
781
|
|
|
715
782
|
const hook = {
|
|
783
|
+
hookID: hookIndex,
|
|
716
784
|
type: RYUNIX_TYPES.RYUNIX_REF,
|
|
717
|
-
value: oldHook ? oldHook.value : { current:
|
|
785
|
+
value: oldHook ? oldHook.value : { current: initialValue },
|
|
718
786
|
};
|
|
719
787
|
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
788
|
+
wipFiber.hooks[hookIndex] = hook;
|
|
789
|
+
state.hookIndex++;
|
|
723
790
|
return hook.value
|
|
724
791
|
};
|
|
725
792
|
|
|
726
|
-
|
|
727
|
-
|
|
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];
|
|
793
|
+
const useMemo = (compute, deps) => {
|
|
794
|
+
validateHookCall();
|
|
744
795
|
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
796
|
+
if (!is.function(compute)) {
|
|
797
|
+
throw new Error('useMemo callback must be a function')
|
|
798
|
+
}
|
|
799
|
+
if (!Array.isArray(deps)) {
|
|
800
|
+
throw new Error('useMemo requires a dependencies array')
|
|
801
|
+
}
|
|
750
802
|
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
803
|
+
const state = getState();
|
|
804
|
+
const { wipFiber, hookIndex } = state;
|
|
805
|
+
const oldHook = wipFiber.alternate?.hooks?.[hookIndex];
|
|
806
|
+
|
|
807
|
+
let value;
|
|
808
|
+
if (oldHook && !haveDepsChanged(oldHook.deps, deps)) {
|
|
809
|
+
value = oldHook.value;
|
|
757
810
|
} else {
|
|
758
|
-
|
|
811
|
+
try {
|
|
812
|
+
value = compute();
|
|
813
|
+
} catch (error) {
|
|
814
|
+
if (process.env.NODE_ENV !== 'production') {
|
|
815
|
+
console.error('Error in useMemo computation:', error);
|
|
816
|
+
}
|
|
817
|
+
value = undefined;
|
|
818
|
+
}
|
|
759
819
|
}
|
|
760
820
|
|
|
761
|
-
|
|
762
|
-
|
|
821
|
+
const hook = {
|
|
822
|
+
hookID: hookIndex,
|
|
823
|
+
type: RYUNIX_TYPES.RYUNIX_MEMO,
|
|
824
|
+
value,
|
|
825
|
+
deps,
|
|
826
|
+
};
|
|
763
827
|
|
|
764
|
-
|
|
828
|
+
wipFiber.hooks[hookIndex] = hook;
|
|
829
|
+
state.hookIndex++;
|
|
830
|
+
return value
|
|
765
831
|
};
|
|
766
832
|
|
|
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
833
|
const useCallback = (callback, deps) => {
|
|
834
|
+
if (!is.function(callback)) {
|
|
835
|
+
throw new Error('useCallback requires a function as first argument')
|
|
836
|
+
}
|
|
778
837
|
return useMemo(() => callback, deps)
|
|
779
838
|
};
|
|
780
839
|
|
|
@@ -782,55 +841,109 @@
|
|
|
782
841
|
contextId = RYUNIX_TYPES.RYUNIX_CONTEXT,
|
|
783
842
|
defaultValue = {},
|
|
784
843
|
) => {
|
|
785
|
-
const Provider = ({ children }) => {
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
844
|
+
const Provider = ({ children, value }) => {
|
|
845
|
+
const element = Fragment({ children });
|
|
846
|
+
element._contextId = contextId;
|
|
847
|
+
element._contextValue = value;
|
|
848
|
+
return element
|
|
789
849
|
};
|
|
790
850
|
|
|
791
851
|
Provider._contextId = contextId;
|
|
792
852
|
|
|
793
|
-
const useContext = (ctxID =
|
|
794
|
-
|
|
853
|
+
const useContext = (ctxID = contextId) => {
|
|
854
|
+
validateHookCall();
|
|
855
|
+
|
|
856
|
+
const state = getState();
|
|
857
|
+
let fiber = state.wipFiber;
|
|
858
|
+
|
|
795
859
|
while (fiber) {
|
|
796
|
-
if (fiber.
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
860
|
+
if (fiber._contextId === ctxID && fiber._contextValue !== undefined) {
|
|
861
|
+
return fiber._contextValue
|
|
862
|
+
}
|
|
863
|
+
if (
|
|
864
|
+
fiber.type?._contextId === ctxID &&
|
|
865
|
+
fiber.props?.value !== undefined
|
|
866
|
+
) {
|
|
867
|
+
return fiber.props.value
|
|
801
868
|
}
|
|
802
869
|
fiber = fiber.parent;
|
|
803
870
|
}
|
|
804
871
|
return defaultValue
|
|
805
872
|
};
|
|
806
873
|
|
|
807
|
-
return {
|
|
808
|
-
Provider,
|
|
809
|
-
useContext,
|
|
810
|
-
}
|
|
874
|
+
return { Provider, useContext }
|
|
811
875
|
};
|
|
812
876
|
|
|
813
877
|
const useQuery = () => {
|
|
878
|
+
if (typeof window === 'undefined') return {}
|
|
879
|
+
|
|
814
880
|
const searchParams = new URLSearchParams(window.location.search);
|
|
815
881
|
const query = {};
|
|
816
|
-
for (
|
|
882
|
+
for (const [key, value] of searchParams.entries()) {
|
|
817
883
|
query[key] = value;
|
|
818
884
|
}
|
|
819
885
|
return query
|
|
820
886
|
};
|
|
821
887
|
|
|
822
888
|
const useHash = () => {
|
|
889
|
+
if (typeof window === 'undefined') return ''
|
|
890
|
+
|
|
823
891
|
const [hash, setHash] = useStore(window.location.hash);
|
|
824
892
|
useEffect(() => {
|
|
825
|
-
const onHashChange = () =>
|
|
826
|
-
setHash(window.location.hash);
|
|
827
|
-
};
|
|
893
|
+
const onHashChange = () => setHash(window.location.hash);
|
|
828
894
|
window.addEventListener('hashchange', onHashChange);
|
|
829
895
|
return () => window.removeEventListener('hashchange', onHashChange)
|
|
830
896
|
}, []);
|
|
831
897
|
return hash
|
|
832
898
|
};
|
|
833
899
|
|
|
900
|
+
const useMetadata = (tags = {}, options = {}) => {
|
|
901
|
+
useEffect(() => {
|
|
902
|
+
if (typeof document === 'undefined') return
|
|
903
|
+
|
|
904
|
+
let finalTitle = 'Ryunix App';
|
|
905
|
+
const template = options.title?.template;
|
|
906
|
+
const defaultTitle = options.title?.prefix || 'Ryunix App';
|
|
907
|
+
const pageTitle = tags.pageTitle || tags.title;
|
|
908
|
+
|
|
909
|
+
if (is.string(pageTitle) && pageTitle.trim()) {
|
|
910
|
+
finalTitle = template?.includes('%s')
|
|
911
|
+
? template.replace('%s', pageTitle)
|
|
912
|
+
: pageTitle;
|
|
913
|
+
} else {
|
|
914
|
+
finalTitle = defaultTitle;
|
|
915
|
+
}
|
|
916
|
+
|
|
917
|
+
document.title = finalTitle;
|
|
918
|
+
|
|
919
|
+
if (tags.canonical) {
|
|
920
|
+
let link = document.querySelector('link[rel="canonical"]');
|
|
921
|
+
if (!link) {
|
|
922
|
+
link = document.createElement('link');
|
|
923
|
+
link.setAttribute('rel', 'canonical');
|
|
924
|
+
document.head.appendChild(link);
|
|
925
|
+
}
|
|
926
|
+
link.setAttribute('href', tags.canonical);
|
|
927
|
+
}
|
|
928
|
+
|
|
929
|
+
Object.entries(tags).forEach(([key, value]) => {
|
|
930
|
+
if (['title', 'pageTitle', 'canonical'].includes(key)) return
|
|
931
|
+
|
|
932
|
+
const isProperty = key.startsWith('og:') || key.startsWith('twitter:');
|
|
933
|
+
const selector = `meta[${isProperty ? 'property' : 'name'}='${key}']`;
|
|
934
|
+
let meta = document.head.querySelector(selector);
|
|
935
|
+
|
|
936
|
+
if (!meta) {
|
|
937
|
+
meta = document.createElement('meta');
|
|
938
|
+
meta.setAttribute(isProperty ? 'property' : 'name', key);
|
|
939
|
+
document.head.appendChild(meta);
|
|
940
|
+
}
|
|
941
|
+
meta.setAttribute('content', value);
|
|
942
|
+
});
|
|
943
|
+
}, [JSON.stringify(tags), JSON.stringify(options)]);
|
|
944
|
+
};
|
|
945
|
+
|
|
946
|
+
// Router Context
|
|
834
947
|
const RouterContext = createContext('ryunix.navigation', {
|
|
835
948
|
location: '/',
|
|
836
949
|
params: {},
|
|
@@ -841,7 +954,6 @@
|
|
|
841
954
|
|
|
842
955
|
const findRoute = (routes, path) => {
|
|
843
956
|
const pathname = path.split('?')[0].split('#')[0];
|
|
844
|
-
|
|
845
957
|
const notFoundRoute = routes.find((route) => route.NotFound);
|
|
846
958
|
const notFound = notFoundRoute
|
|
847
959
|
? { route: { component: notFoundRoute.NotFound }, params: {} }
|
|
@@ -852,18 +964,8 @@
|
|
|
852
964
|
const childRoute = findRoute(route.subRoutes, path);
|
|
853
965
|
if (childRoute) return childRoute
|
|
854
966
|
}
|
|
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
|
-
}
|
|
967
|
+
if (route.path === '*') return notFound
|
|
968
|
+
if (!route.path || typeof route.path !== 'string') continue
|
|
867
969
|
|
|
868
970
|
const keys = [];
|
|
869
971
|
const pattern = new RegExp(
|
|
@@ -879,11 +981,9 @@
|
|
|
879
981
|
acc[key] = match[index + 1];
|
|
880
982
|
return acc
|
|
881
983
|
}, {});
|
|
882
|
-
|
|
883
984
|
return { route, params }
|
|
884
985
|
}
|
|
885
986
|
}
|
|
886
|
-
|
|
887
987
|
return notFound
|
|
888
988
|
};
|
|
889
989
|
|
|
@@ -892,7 +992,6 @@
|
|
|
892
992
|
|
|
893
993
|
useEffect(() => {
|
|
894
994
|
const update = () => setLocation(window.location.pathname);
|
|
895
|
-
|
|
896
995
|
window.addEventListener('popstate', update);
|
|
897
996
|
window.addEventListener('hashchange', update);
|
|
898
997
|
return () => {
|
|
@@ -907,7 +1006,6 @@
|
|
|
907
1006
|
};
|
|
908
1007
|
|
|
909
1008
|
const currentRouteData = findRoute(routes, location) || {};
|
|
910
|
-
|
|
911
1009
|
const query = useQuery();
|
|
912
1010
|
|
|
913
1011
|
const contextValue = {
|
|
@@ -921,9 +1019,7 @@
|
|
|
921
1019
|
return createElement(
|
|
922
1020
|
RouterContext.Provider,
|
|
923
1021
|
{ value: contextValue },
|
|
924
|
-
Fragment({
|
|
925
|
-
children: children,
|
|
926
|
-
}),
|
|
1022
|
+
Fragment({ children }),
|
|
927
1023
|
)
|
|
928
1024
|
};
|
|
929
1025
|
|
|
@@ -954,7 +1050,6 @@
|
|
|
954
1050
|
|
|
955
1051
|
const NavLink = ({ to, exact = false, ...props }) => {
|
|
956
1052
|
const { location, navigate } = useRouter();
|
|
957
|
-
|
|
958
1053
|
const isActive = exact ? location === to : location.startsWith(to);
|
|
959
1054
|
|
|
960
1055
|
const resolveClass = (cls) =>
|
|
@@ -966,7 +1061,6 @@
|
|
|
966
1061
|
};
|
|
967
1062
|
|
|
968
1063
|
const classAttrName = props['ryunix-class'] ? 'ryunix-class' : 'className';
|
|
969
|
-
|
|
970
1064
|
const classAttrValue = resolveClass(
|
|
971
1065
|
props['ryunix-class'] || props['className'],
|
|
972
1066
|
);
|
|
@@ -989,77 +1083,6 @@
|
|
|
989
1083
|
)
|
|
990
1084
|
};
|
|
991
1085
|
|
|
992
|
-
/**
|
|
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.
|
|
997
|
-
|
|
998
|
-
*/
|
|
999
|
-
|
|
1000
|
-
const useMetadata = (tags = {}, options = {}) => {
|
|
1001
|
-
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
|
-
}
|
|
1013
|
-
|
|
1014
|
-
// pageTitle tiene prioridad sobre title
|
|
1015
|
-
let pageTitle = tags.pageTitle || tags.title;
|
|
1016
|
-
|
|
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)]);
|
|
1061
|
-
};
|
|
1062
|
-
|
|
1063
1086
|
var Hooks = /*#__PURE__*/Object.freeze({
|
|
1064
1087
|
__proto__: null,
|
|
1065
1088
|
Children: Children,
|
|
@@ -1072,17 +1095,126 @@
|
|
|
1072
1095
|
useMemo: useMemo,
|
|
1073
1096
|
useMetadata: useMetadata,
|
|
1074
1097
|
useQuery: useQuery,
|
|
1098
|
+
useReducer: useReducer,
|
|
1075
1099
|
useRef: useRef,
|
|
1076
1100
|
useRouter: useRouter,
|
|
1077
1101
|
useStore: useStore
|
|
1078
1102
|
});
|
|
1079
1103
|
|
|
1104
|
+
/**
|
|
1105
|
+
* memo - Memoize component to prevent unnecessary re-renders
|
|
1106
|
+
*/
|
|
1107
|
+
const memo = (Component, arePropsEqual) => {
|
|
1108
|
+
return (props) => {
|
|
1109
|
+
const memoizedElement = useMemo(() => {
|
|
1110
|
+
return Component(props)
|
|
1111
|
+
}, [
|
|
1112
|
+
// Default comparison: shallow props comparison
|
|
1113
|
+
...Object.values(props),
|
|
1114
|
+
]);
|
|
1115
|
+
|
|
1116
|
+
return memoizedElement
|
|
1117
|
+
}
|
|
1118
|
+
};
|
|
1119
|
+
|
|
1120
|
+
/**
|
|
1121
|
+
* Lazy load component
|
|
1122
|
+
*/
|
|
1123
|
+
const lazy = (importFn) => {
|
|
1124
|
+
let Component = null;
|
|
1125
|
+
let promise = null;
|
|
1126
|
+
let error = null;
|
|
1127
|
+
|
|
1128
|
+
return (props) => {
|
|
1129
|
+
const [, forceUpdate] = useStore(0);
|
|
1130
|
+
|
|
1131
|
+
useEffect(() => {
|
|
1132
|
+
if (Component || error) return
|
|
1133
|
+
|
|
1134
|
+
if (!promise) {
|
|
1135
|
+
promise = importFn()
|
|
1136
|
+
.then((module) => {
|
|
1137
|
+
Component = module.default || module;
|
|
1138
|
+
forceUpdate((x) => x + 1);
|
|
1139
|
+
})
|
|
1140
|
+
.catch((err) => {
|
|
1141
|
+
error = err;
|
|
1142
|
+
forceUpdate((x) => x + 1);
|
|
1143
|
+
});
|
|
1144
|
+
}
|
|
1145
|
+
}, []);
|
|
1146
|
+
|
|
1147
|
+
if (error) throw error
|
|
1148
|
+
if (!Component) return null
|
|
1149
|
+
return createElement(Component, props)
|
|
1150
|
+
}
|
|
1151
|
+
};
|
|
1152
|
+
|
|
1153
|
+
/**
|
|
1154
|
+
* Suspense component (basic implementation)
|
|
1155
|
+
*/
|
|
1156
|
+
const Suspense = ({ fallback, children }) => {
|
|
1157
|
+
const [isLoading, setIsLoading] = useStore(true);
|
|
1158
|
+
|
|
1159
|
+
useEffect(() => {
|
|
1160
|
+
setIsLoading(false);
|
|
1161
|
+
}, []);
|
|
1162
|
+
|
|
1163
|
+
if (isLoading && fallback) {
|
|
1164
|
+
return fallback
|
|
1165
|
+
}
|
|
1166
|
+
|
|
1167
|
+
return children
|
|
1168
|
+
};
|
|
1169
|
+
|
|
1170
|
+
let isBatching = false;
|
|
1171
|
+
let pendingUpdates = [];
|
|
1172
|
+
|
|
1173
|
+
/**
|
|
1174
|
+
* Batch multiple state updates into single render
|
|
1175
|
+
*/
|
|
1176
|
+
const batchUpdates = (callback) => {
|
|
1177
|
+
const wasBatching = isBatching;
|
|
1178
|
+
isBatching = true;
|
|
1179
|
+
|
|
1180
|
+
try {
|
|
1181
|
+
callback();
|
|
1182
|
+
} finally {
|
|
1183
|
+
isBatching = wasBatching;
|
|
1184
|
+
|
|
1185
|
+
if (!isBatching && pendingUpdates.length > 0) {
|
|
1186
|
+
flushUpdates();
|
|
1187
|
+
}
|
|
1188
|
+
}
|
|
1189
|
+
};
|
|
1190
|
+
|
|
1191
|
+
/**
|
|
1192
|
+
* Flush all pending updates
|
|
1193
|
+
*/
|
|
1194
|
+
const flushUpdates = () => {
|
|
1195
|
+
if (pendingUpdates.length === 0) return
|
|
1196
|
+
|
|
1197
|
+
const updates = pendingUpdates;
|
|
1198
|
+
pendingUpdates = [];
|
|
1199
|
+
|
|
1200
|
+
// Execute all updates
|
|
1201
|
+
updates.forEach((update) => update());
|
|
1202
|
+
};
|
|
1203
|
+
|
|
1204
|
+
// Ryunix.*
|
|
1080
1205
|
var Ryunix = {
|
|
1081
1206
|
createElement,
|
|
1082
1207
|
render,
|
|
1083
1208
|
init,
|
|
1084
1209
|
Fragment,
|
|
1085
1210
|
Hooks,
|
|
1211
|
+
|
|
1212
|
+
memo,
|
|
1213
|
+
lazy,
|
|
1214
|
+
Suspense,
|
|
1215
|
+
|
|
1216
|
+
safeRender,
|
|
1217
|
+
batchUpdates,
|
|
1086
1218
|
};
|
|
1087
1219
|
|
|
1088
1220
|
window.Ryunix = Ryunix;
|
|
@@ -1099,6 +1231,7 @@
|
|
|
1099
1231
|
exports.useMemo = useMemo;
|
|
1100
1232
|
exports.useMetadata = useMetadata;
|
|
1101
1233
|
exports.useQuery = useQuery;
|
|
1234
|
+
exports.useReducer = useReducer;
|
|
1102
1235
|
exports.useRef = useRef;
|
|
1103
1236
|
exports.useRouter = useRouter;
|
|
1104
1237
|
exports.useStore = useStore;
|