@unsetsoft/ryunixjs 1.1.6 → 1.1.7-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/README.md +4 -4
- package/dist/Ryunix.js +654 -629
- package/dist/Ryunix.min.js +1 -1
- package/package.json +16 -5
package/dist/Ryunix.js
CHANGED
|
@@ -5,13 +5,14 @@
|
|
|
5
5
|
})(this, (function (exports, lodash) { 'use strict';
|
|
6
6
|
|
|
7
7
|
let vars = {
|
|
8
|
-
containerRoot:
|
|
9
|
-
nextUnitOfWork:
|
|
10
|
-
currentRoot:
|
|
11
|
-
wipRoot:
|
|
12
|
-
deletions:
|
|
13
|
-
wipFiber:
|
|
14
|
-
hookIndex:
|
|
8
|
+
containerRoot: null,
|
|
9
|
+
nextUnitOfWork: null,
|
|
10
|
+
currentRoot: null,
|
|
11
|
+
wipRoot: null,
|
|
12
|
+
deletions: null,
|
|
13
|
+
wipFiber: null,
|
|
14
|
+
hookIndex: null,
|
|
15
|
+
effects: null,
|
|
15
16
|
};
|
|
16
17
|
|
|
17
18
|
const reg = /[A-Z]/g;
|
|
@@ -52,18 +53,6 @@
|
|
|
52
53
|
return props.children
|
|
53
54
|
};
|
|
54
55
|
|
|
55
|
-
const childArray = (children, out) => {
|
|
56
|
-
out = out || [];
|
|
57
|
-
if (children == undefined || typeof children == STRINGS.boolean) ; else if (Array.isArray(children)) {
|
|
58
|
-
children.some((child) => {
|
|
59
|
-
childArray(child, out);
|
|
60
|
-
});
|
|
61
|
-
} else {
|
|
62
|
-
out.push(children);
|
|
63
|
-
}
|
|
64
|
-
return out
|
|
65
|
-
};
|
|
66
|
-
|
|
67
56
|
/**
|
|
68
57
|
* The function creates a new element with the given type, props, and children.
|
|
69
58
|
* @param type - The type of the element to be created, such as "div", "span", "h1", etc.
|
|
@@ -82,20 +71,15 @@
|
|
|
82
71
|
*/
|
|
83
72
|
|
|
84
73
|
const createElement = (type, props, ...children) => {
|
|
85
|
-
children = childArray(children, []);
|
|
86
|
-
const key =
|
|
87
|
-
props && props.key
|
|
88
|
-
? props.key
|
|
89
|
-
: `${RYUNIX_TYPES.Ryunix_ELEMENT.toString()}-${Math.random().toString(36).substring(2, 9)}`;
|
|
90
|
-
|
|
91
74
|
return {
|
|
92
75
|
type,
|
|
93
76
|
props: {
|
|
94
77
|
...props,
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
78
|
+
children: children
|
|
79
|
+
.flat()
|
|
80
|
+
.map((child) =>
|
|
81
|
+
typeof child === STRINGS.object ? child : createTextElement(child),
|
|
82
|
+
),
|
|
99
83
|
},
|
|
100
84
|
}
|
|
101
85
|
};
|
|
@@ -117,726 +101,770 @@
|
|
|
117
101
|
}
|
|
118
102
|
};
|
|
119
103
|
|
|
104
|
+
const isEvent = (key) => key.startsWith('on');
|
|
105
|
+
const isProperty = (key) => key !== STRINGS.children && !isEvent(key);
|
|
106
|
+
const isNew = (prev, next) => (key) => prev[key] !== next[key];
|
|
107
|
+
const isGone = (next) => (key) => !(key in next);
|
|
108
|
+
|
|
120
109
|
/**
|
|
121
|
-
*
|
|
122
|
-
* @
|
|
123
|
-
*
|
|
124
|
-
*
|
|
125
|
-
*
|
|
126
|
-
* It also clears any scheduled deletions and establishes the next unit of work for incremental rendering.
|
|
110
|
+
* The function cancels all effect hooks in a given fiber.
|
|
111
|
+
* @param fiber - The "fiber" parameter is likely referring to a data structure used in React.js to
|
|
112
|
+
* represent a component and its state. It contains information about the component's props, state, and
|
|
113
|
+
* children, as well as metadata used by React to manage updates and rendering. The function
|
|
114
|
+
* "cancelEffects" is likely intended
|
|
127
115
|
*/
|
|
128
|
-
const
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
}
|
|
116
|
+
const cancelEffects = (fiber) => {
|
|
117
|
+
if (fiber.hooks) {
|
|
118
|
+
fiber.hooks
|
|
119
|
+
.filter((hook) => hook.tag === RYUNIX_TYPES.RYUNIX_EFFECT && hook.cancel)
|
|
120
|
+
.forEach((effectHook) => {
|
|
121
|
+
effectHook.cancel();
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
};
|
|
136
125
|
|
|
137
|
-
|
|
138
|
-
|
|
126
|
+
/**
|
|
127
|
+
* The function runs all effect hooks in a given fiber.
|
|
128
|
+
* @param fiber - The "fiber" parameter is likely referring to a data structure used in the
|
|
129
|
+
* implementation of a fiber-based reconciliation algorithm, such as the one used in React. A fiber
|
|
130
|
+
* represents a unit of work that needs to be performed by the reconciliation algorithm, and it
|
|
131
|
+
* contains information about a component and its children, as
|
|
132
|
+
*/
|
|
133
|
+
const runEffects = (fiber) => {
|
|
134
|
+
if (fiber.hooks) {
|
|
135
|
+
fiber.hooks
|
|
136
|
+
.filter((hook) => hook.tag === RYUNIX_TYPES.RYUNIX_EFFECT && hook.effect)
|
|
137
|
+
.forEach((effectHook) => {
|
|
138
|
+
effectHook.cancel = effectHook.effect();
|
|
139
|
+
});
|
|
140
|
+
}
|
|
139
141
|
};
|
|
140
142
|
|
|
141
143
|
/**
|
|
142
|
-
*
|
|
143
|
-
* @
|
|
144
|
-
*
|
|
145
|
-
* @
|
|
146
|
-
*
|
|
147
|
-
*
|
|
148
|
-
*
|
|
144
|
+
* The function creates a new DOM element based on the given fiber object and updates its properties.
|
|
145
|
+
* @param fiber - The fiber parameter is an object that represents a node in the fiber tree. It
|
|
146
|
+
* contains information about the element type, props, and children of the node.
|
|
147
|
+
* @returns The `createDom` function returns a newly created DOM element based on the `fiber` object
|
|
148
|
+
* passed as an argument. If the `fiber` object represents a text element, a text node is created using
|
|
149
|
+
* `document.createTextNode("")`. Otherwise, a new element is created using
|
|
150
|
+
* `document.createElement(fiber.type)`. The function then calls the `updateDom` function to update the
|
|
151
|
+
* properties of the newly created
|
|
149
152
|
*/
|
|
150
|
-
const
|
|
151
|
-
|
|
153
|
+
const createDom = (fiber) => {
|
|
154
|
+
const dom =
|
|
155
|
+
fiber.type == RYUNIX_TYPES.TEXT_ELEMENT
|
|
156
|
+
? document.createTextNode('')
|
|
157
|
+
: document.createElement(fiber.type);
|
|
158
|
+
|
|
159
|
+
updateDom(dom, {}, fiber.props);
|
|
152
160
|
|
|
153
|
-
|
|
161
|
+
return dom
|
|
154
162
|
};
|
|
155
163
|
|
|
156
164
|
/**
|
|
157
|
-
*
|
|
158
|
-
*
|
|
159
|
-
* @
|
|
160
|
-
*
|
|
165
|
+
* The function updates the DOM by removing old event listeners and properties, and adding new ones
|
|
166
|
+
* based on the previous and next props.
|
|
167
|
+
* @param dom - The DOM element that needs to be updated with new props.
|
|
168
|
+
* @param prevProps - An object representing the previous props (properties) of a DOM element.
|
|
169
|
+
* @param nextProps - An object containing the new props that need to be updated in the DOM.
|
|
161
170
|
*/
|
|
162
|
-
const
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
};
|
|
171
|
+
const updateDom = (dom, prevProps, nextProps) => {
|
|
172
|
+
Object.keys(prevProps)
|
|
173
|
+
.filter(isEvent)
|
|
174
|
+
.filter((key) => isGone(nextProps)(key) || isNew(prevProps, nextProps)(key))
|
|
175
|
+
.forEach((name) => {
|
|
176
|
+
const eventType = name.toLowerCase().substring(2);
|
|
177
|
+
dom.removeEventListener(eventType, prevProps[name]);
|
|
178
|
+
});
|
|
171
179
|
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
180
|
+
Object.keys(prevProps)
|
|
181
|
+
.filter(isProperty)
|
|
182
|
+
.filter(isGone(nextProps))
|
|
183
|
+
.forEach((name) => {
|
|
184
|
+
dom[name] = '';
|
|
185
|
+
});
|
|
176
186
|
|
|
177
|
-
|
|
187
|
+
Object.keys(nextProps)
|
|
188
|
+
.filter(isProperty)
|
|
189
|
+
.filter(isNew(prevProps, nextProps))
|
|
190
|
+
.forEach((name) => {
|
|
191
|
+
if (name === STRINGS.style) {
|
|
192
|
+
DomStyle(dom, nextProps['ryunix-style']);
|
|
193
|
+
} else if (name === OLD_STRINGS.style) {
|
|
194
|
+
DomStyle(dom, nextProps.style);
|
|
195
|
+
} else if (name === STRINGS.className) {
|
|
196
|
+
if (nextProps['ryunix-class'] === '') {
|
|
197
|
+
throw new Error('data-class cannot be empty.')
|
|
198
|
+
}
|
|
178
199
|
|
|
179
|
-
|
|
180
|
-
|
|
200
|
+
prevProps['ryunix-class'] &&
|
|
201
|
+
dom.classList.remove(...prevProps['ryunix-class'].split(/\s+/));
|
|
202
|
+
dom.classList.add(...nextProps['ryunix-class'].split(/\s+/));
|
|
203
|
+
} else if (name === OLD_STRINGS.className) {
|
|
204
|
+
if (nextProps.className === '') {
|
|
205
|
+
throw new Error('className cannot be empty.')
|
|
206
|
+
}
|
|
181
207
|
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
};
|
|
189
|
-
vars.nextUnitOfWork = vars.wipRoot;
|
|
190
|
-
vars.deletions = [];
|
|
191
|
-
};
|
|
208
|
+
prevProps.className &&
|
|
209
|
+
dom.classList.remove(...prevProps.className.split(/\s+/));
|
|
210
|
+
dom.classList.add(...nextProps.className.split(/\s+/));
|
|
211
|
+
} else {
|
|
212
|
+
dom[name] = nextProps[name];
|
|
213
|
+
}
|
|
214
|
+
});
|
|
192
215
|
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
216
|
+
Object.keys(nextProps)
|
|
217
|
+
.filter(isEvent)
|
|
218
|
+
.filter(isNew(prevProps, nextProps))
|
|
219
|
+
.forEach((name) => {
|
|
220
|
+
const eventType = name.toLowerCase().substring(2);
|
|
221
|
+
dom.addEventListener(eventType, nextProps[name]);
|
|
222
|
+
});
|
|
223
|
+
};
|
|
197
224
|
|
|
198
|
-
|
|
225
|
+
const DomStyle = (dom, style) => {
|
|
226
|
+
dom.style = Object.keys(style).reduce((acc, styleName) => {
|
|
227
|
+
const key = styleName.replace(reg, function (v) {
|
|
228
|
+
return '-' + v.toLowerCase()
|
|
229
|
+
});
|
|
230
|
+
acc += `${key}: ${style[styleName]};`;
|
|
231
|
+
return acc
|
|
232
|
+
}, '');
|
|
199
233
|
};
|
|
200
234
|
|
|
201
235
|
/**
|
|
202
|
-
*
|
|
203
|
-
* @param effect - The effect function that will be executed after the component has rendered or when
|
|
204
|
-
* the dependencies have changed. It can perform side effects such as fetching data, updating the DOM,
|
|
205
|
-
* or subscribing to events.
|
|
206
|
-
* @param deps - An array of dependencies that the effect depends on. If any of the dependencies change
|
|
207
|
-
* between renders, the effect will be re-run. If the array is empty, the effect will only run once on
|
|
208
|
-
* mount and never again.
|
|
236
|
+
* The function commits changes made to the virtual DOM to the actual DOM.
|
|
209
237
|
*/
|
|
238
|
+
const commitRoot = () => {
|
|
239
|
+
vars.deletions.forEach(commitWork);
|
|
240
|
+
commitWork(vars.wipRoot.child);
|
|
241
|
+
vars.currentRoot = vars.wipRoot;
|
|
242
|
+
vars.wipRoot = null;
|
|
243
|
+
vars.effects.forEach((effect) => {
|
|
244
|
+
try {
|
|
245
|
+
effect();
|
|
246
|
+
} catch (err) {
|
|
247
|
+
console.error('Error in effect:', err);
|
|
248
|
+
}
|
|
249
|
+
});
|
|
210
250
|
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
vars.wipFiber.alternate &&
|
|
214
|
-
vars.wipFiber.alternate.hooks &&
|
|
215
|
-
vars.wipFiber.alternate.hooks[vars.hookIndex];
|
|
251
|
+
vars.effects = [];
|
|
252
|
+
};
|
|
216
253
|
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
254
|
+
/**
|
|
255
|
+
* The function commits changes made to the DOM based on the effect tag of the fiber.
|
|
256
|
+
* @param fiber - A fiber is a unit of work in Ryunix's reconciliation process. It represents a
|
|
257
|
+
* component and its state at a particular point in time. The `commitWork` function takes a fiber as a
|
|
258
|
+
* parameter to commit the changes made during the reconciliation process to the actual DOM.
|
|
259
|
+
* @returns The function does not return anything, it performs side effects by manipulating the DOM.
|
|
260
|
+
*/
|
|
261
|
+
const commitWork = (fiber) => {
|
|
262
|
+
if (!fiber) return
|
|
221
263
|
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
} else {
|
|
226
|
-
if (!lodash.isEqual(oldHook.deps, hook.deps)) {
|
|
227
|
-
callback();
|
|
228
|
-
}
|
|
264
|
+
let domParentFiber = fiber.parent;
|
|
265
|
+
while (!domParentFiber.dom) {
|
|
266
|
+
domParentFiber = domParentFiber.parent;
|
|
229
267
|
}
|
|
268
|
+
const domParent = domParentFiber.dom;
|
|
230
269
|
|
|
231
|
-
if (
|
|
232
|
-
|
|
233
|
-
|
|
270
|
+
if (fiber.effectTag === EFFECT_TAGS.PLACEMENT) {
|
|
271
|
+
domParent.appendChild(fiber.dom);
|
|
272
|
+
runEffects(fiber);
|
|
273
|
+
} else if (fiber.effectTag === EFFECT_TAGS.UPDATE) {
|
|
274
|
+
cancelEffects(fiber);
|
|
275
|
+
updateDom(fiber.dom, fiber.alternate.props, fiber.props);
|
|
276
|
+
runEffects(fiber);
|
|
277
|
+
} else if (fiber.effectTag === EFFECT_TAGS.DELETION) {
|
|
278
|
+
commitDeletion(fiber, domParent);
|
|
279
|
+
cancelEffects(fiber);
|
|
280
|
+
return
|
|
234
281
|
}
|
|
235
|
-
};
|
|
236
|
-
|
|
237
|
-
const useRef = (initial) => {
|
|
238
|
-
const oldHook =
|
|
239
|
-
vars.wipFiber.alternate &&
|
|
240
|
-
vars.wipFiber.alternate.hooks &&
|
|
241
|
-
vars.wipFiber.alternate.hooks[vars.hookIndex];
|
|
242
282
|
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
283
|
+
// Recorre los "fibers" hijos y hermanos
|
|
284
|
+
commitWork(fiber.child);
|
|
285
|
+
commitWork(fiber.sibling);
|
|
286
|
+
};
|
|
247
287
|
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
288
|
+
/**
|
|
289
|
+
* The function removes a fiber's corresponding DOM node from its parent node or recursively removes
|
|
290
|
+
* its child's DOM node until it finds a node to remove.
|
|
291
|
+
* @param fiber - a fiber node in a fiber tree, which represents a component or an element in the Ryunix
|
|
292
|
+
* application.
|
|
293
|
+
* @param domParent - The parent DOM element from which the fiber's DOM element needs to be removed.
|
|
294
|
+
*/
|
|
295
|
+
const commitDeletion = (fiber, domParent) => {
|
|
296
|
+
if (fiber.dom) {
|
|
297
|
+
domParent.removeChild(fiber.dom);
|
|
298
|
+
} else {
|
|
299
|
+
commitDeletion(fiber.child, domParent);
|
|
251
300
|
}
|
|
252
|
-
|
|
253
|
-
return hook.value
|
|
254
301
|
};
|
|
255
302
|
|
|
256
|
-
const
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
vars.wipFiber.alternate.hooks[vars.hookIndex];
|
|
303
|
+
const reconcileChildren = (wipFiber, elements) => {
|
|
304
|
+
let index = 0;
|
|
305
|
+
let oldFiber = wipFiber.alternate && wipFiber.alternate.child;
|
|
306
|
+
let prevSibling;
|
|
261
307
|
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
deps,
|
|
266
|
-
};
|
|
308
|
+
while (index < elements.length || oldFiber != null) {
|
|
309
|
+
const element = elements[index];
|
|
310
|
+
let newFiber;
|
|
267
311
|
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
312
|
+
const sameType = oldFiber && element && element.type == oldFiber.type;
|
|
313
|
+
|
|
314
|
+
if (sameType) {
|
|
315
|
+
newFiber = {
|
|
316
|
+
type: oldFiber.type,
|
|
317
|
+
props: element.props,
|
|
318
|
+
dom: oldFiber.dom,
|
|
319
|
+
parent: wipFiber,
|
|
320
|
+
alternate: oldFiber,
|
|
321
|
+
effectTag: EFFECT_TAGS.UPDATE,
|
|
322
|
+
};
|
|
323
|
+
}
|
|
324
|
+
if (element && !sameType) {
|
|
325
|
+
newFiber = {
|
|
326
|
+
type: element.type,
|
|
327
|
+
props: element.props,
|
|
328
|
+
dom: null,
|
|
329
|
+
parent: wipFiber,
|
|
330
|
+
alternate: null,
|
|
331
|
+
effectTag: EFFECT_TAGS.PLACEMENT,
|
|
332
|
+
};
|
|
333
|
+
}
|
|
334
|
+
if (oldFiber && !sameType) {
|
|
335
|
+
oldFiber.effectTag = EFFECT_TAGS.DELETION;
|
|
336
|
+
vars.deletions.push(oldFiber);
|
|
273
337
|
}
|
|
274
|
-
} else {
|
|
275
|
-
hook.value = comp();
|
|
276
|
-
}
|
|
277
338
|
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
}
|
|
339
|
+
if (oldFiber) {
|
|
340
|
+
oldFiber = oldFiber.sibling;
|
|
341
|
+
}
|
|
282
342
|
|
|
283
|
-
|
|
343
|
+
if (index === 0) {
|
|
344
|
+
wipFiber.child = newFiber;
|
|
345
|
+
} else if (element) {
|
|
346
|
+
prevSibling.sibling = newFiber;
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
prevSibling = newFiber;
|
|
350
|
+
index++;
|
|
351
|
+
}
|
|
284
352
|
};
|
|
285
353
|
|
|
286
|
-
|
|
287
|
-
|
|
354
|
+
/**
|
|
355
|
+
* This function updates a function component by setting up a work-in-progress fiber, resetting the
|
|
356
|
+
* hook index, creating an empty hooks array, rendering the component, and reconciling its children.
|
|
357
|
+
* @param fiber - The fiber parameter is an object that represents a node in the fiber tree. It
|
|
358
|
+
* contains information about the component, its props, state, and children. In this function, it is
|
|
359
|
+
* used to update the state of the component and its children.
|
|
360
|
+
*/
|
|
361
|
+
const updateFunctionComponent = (fiber) => {
|
|
362
|
+
vars.wipFiber = fiber;
|
|
363
|
+
vars.hookIndex = 0;
|
|
364
|
+
vars.wipFiber.hooks = [];
|
|
365
|
+
|
|
366
|
+
const children = [fiber.type(fiber.props)];
|
|
367
|
+
reconcileChildren(fiber, children);
|
|
288
368
|
};
|
|
289
369
|
|
|
290
370
|
/**
|
|
291
|
-
*
|
|
292
|
-
* @
|
|
293
|
-
* the
|
|
371
|
+
* This function updates a host component's DOM element and reconciles its children.
|
|
372
|
+
* @param fiber - A fiber is a unit of work in Ryunix that represents a component and its state. It
|
|
373
|
+
* contains information about the component's type, props, and children, as well as pointers to other
|
|
374
|
+
* fibers in the tree.
|
|
294
375
|
*/
|
|
295
|
-
const
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
for (let [key, value] of searchParams.entries()) {
|
|
299
|
-
query[key] = value;
|
|
376
|
+
const updateHostComponent = (fiber) => {
|
|
377
|
+
if (!fiber.dom) {
|
|
378
|
+
fiber.dom = createDom(fiber);
|
|
300
379
|
}
|
|
301
|
-
|
|
380
|
+
reconcileChildren(fiber, fiber.props.children.flat());
|
|
302
381
|
};
|
|
303
382
|
|
|
383
|
+
/* Internal components*/
|
|
384
|
+
|
|
304
385
|
/**
|
|
305
|
-
* `
|
|
306
|
-
*
|
|
307
|
-
*
|
|
308
|
-
*
|
|
309
|
-
*
|
|
310
|
-
* - Default pre-loading of all routes except the current active route.
|
|
311
|
-
*
|
|
312
|
-
* @param {Array} routes - An array of route objects, each containing:
|
|
313
|
-
* - `path` (string): The URL path to match (supports dynamic segments like "/user/:id").
|
|
314
|
-
* - `component` (function): The component to render when the route matches.
|
|
315
|
-
* - `subRoutes` (optional array): An optional array of nested route objects, defining sub-routes for this route.
|
|
316
|
-
* - `NotFound` (optional function): Component to render for unmatched routes (default 404 behavior).
|
|
317
|
-
*
|
|
318
|
-
* @returns {Object} - An object with:
|
|
319
|
-
* - `Children` (function): Returns the component that matches the current route, passing route parameters and query parameters as props.
|
|
320
|
-
* - `NavLink` (component): A link component to navigate within the application without refreshing the page.
|
|
321
|
-
* - `navigate` (function): Allows programmatically navigating to a specific path.
|
|
322
|
-
*
|
|
323
|
-
* @example
|
|
324
|
-
* // Define nested routes
|
|
325
|
-
* const routes = [
|
|
326
|
-
* {
|
|
327
|
-
* path: "/",
|
|
328
|
-
* component: HomePage,
|
|
329
|
-
* subRoutes: [
|
|
330
|
-
* {
|
|
331
|
-
* path: "/settings",
|
|
332
|
-
* component: SettingsPage,
|
|
333
|
-
* },
|
|
334
|
-
* ],
|
|
335
|
-
* },
|
|
336
|
-
* {
|
|
337
|
-
* path: "/user/:id",
|
|
338
|
-
* component: UserProfile,
|
|
339
|
-
* },
|
|
340
|
-
* {
|
|
341
|
-
* path: "*",
|
|
342
|
-
* NotFound: NotFoundPage,
|
|
343
|
-
* },
|
|
344
|
-
* ];
|
|
345
|
-
*
|
|
346
|
-
* // Use the routing function
|
|
347
|
-
* const { Children, NavLink } = useRouter(routes);
|
|
348
|
-
*
|
|
349
|
-
* // Render the matched component
|
|
350
|
-
* const App = () => (
|
|
351
|
-
* <>
|
|
352
|
-
* <NavLink to="/">Home</NavLink>
|
|
353
|
-
* <NavLink to="/settings">Settings</NavLink>
|
|
354
|
-
* <NavLink to="/user/123">User Profile</NavLink>
|
|
355
|
-
* <Children />
|
|
356
|
-
* </>
|
|
357
|
-
* );
|
|
386
|
+
* The function `optimizationImageApi` optimizes image URLs by adding query parameters for width,
|
|
387
|
+
* height, quality, and extension, and handles local and remote image sources.
|
|
388
|
+
* @returns The function `optimizationImageApi` returns either the original `src` if it is a local
|
|
389
|
+
* image and the page is being run on localhost, or it returns a modified image URL with optimization
|
|
390
|
+
* parameters added if the `src` is not local.
|
|
358
391
|
*/
|
|
359
|
-
const
|
|
360
|
-
const
|
|
361
|
-
|
|
362
|
-
const findRoute = (routes, path) => {
|
|
363
|
-
const pathname = path.split('?')[0];
|
|
392
|
+
const optimizationImageApi = ({ src, props }) => {
|
|
393
|
+
const query = new URLSearchParams();
|
|
394
|
+
const apiEndpoint = 'https://image.unsetsoft.com';
|
|
364
395
|
|
|
365
|
-
|
|
366
|
-
const notFound = notFoundRoute
|
|
367
|
-
? { route: { component: notFoundRoute.NotFound }, params: {} }
|
|
368
|
-
: { route: { component: null }, params: {} };
|
|
396
|
+
const isLocal = !src.startsWith('http') || !src.startsWith('https');
|
|
369
397
|
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
if (childRoute) return childRoute
|
|
374
|
-
}
|
|
398
|
+
if (props.width) query.set('width', props.width);
|
|
399
|
+
if (props.height) query.set('width', props.height);
|
|
400
|
+
if (props.quality) query.set('quality', props.quality);
|
|
375
401
|
|
|
376
|
-
|
|
377
|
-
return notFound
|
|
378
|
-
}
|
|
402
|
+
const extension = props.extension ? `@${props.extension}` : '';
|
|
379
403
|
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
);
|
|
385
|
-
continue
|
|
386
|
-
}
|
|
404
|
+
const localhost =
|
|
405
|
+
window.location.origin === 'http://localhost:3000' ||
|
|
406
|
+
window.location.origin === 'http://localhost:5173' ||
|
|
407
|
+
window.location.origin === 'http://localhost:4173';
|
|
387
408
|
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
return '([^/]+)'
|
|
393
|
-
})}$`,
|
|
409
|
+
if (isLocal) {
|
|
410
|
+
if (localhost) {
|
|
411
|
+
console.warn(
|
|
412
|
+
'Image optimizations only work with full links and must not contain localhost.',
|
|
394
413
|
);
|
|
395
|
-
|
|
396
|
-
const match = pathname.match(pattern);
|
|
397
|
-
if (match) {
|
|
398
|
-
const params = keys.reduce((acc, key, index) => {
|
|
399
|
-
acc[key] = match[index + 1];
|
|
400
|
-
return acc
|
|
401
|
-
}, {});
|
|
402
|
-
|
|
403
|
-
return { route, params }
|
|
404
|
-
}
|
|
414
|
+
return src
|
|
405
415
|
}
|
|
406
416
|
|
|
407
|
-
return
|
|
408
|
-
}
|
|
409
|
-
|
|
410
|
-
const navigate = (path) => {
|
|
411
|
-
window.history.pushState({}, '', path);
|
|
412
|
-
updateRoute(path);
|
|
413
|
-
};
|
|
414
|
-
|
|
415
|
-
const updateRoute = (path) => {
|
|
416
|
-
const cleanedPath = path.split('?')[0];
|
|
417
|
-
setLocation(cleanedPath);
|
|
418
|
-
};
|
|
419
|
-
|
|
420
|
-
useEffect(() => {
|
|
421
|
-
const onPopState = () => updateRoute(window.location.pathname);
|
|
422
|
-
window.addEventListener('popstate', onPopState);
|
|
423
|
-
|
|
424
|
-
return () => window.removeEventListener('popstate', onPopState)
|
|
425
|
-
}, []);
|
|
426
|
-
|
|
427
|
-
const currentRouteData = findRoute(routes, location) || {};
|
|
428
|
-
|
|
429
|
-
const Children = () => {
|
|
430
|
-
const query = useQuery();
|
|
431
|
-
const { route } = currentRouteData;
|
|
432
|
-
|
|
433
|
-
if (
|
|
434
|
-
!route ||
|
|
435
|
-
!route.component ||
|
|
436
|
-
typeof route.component !== STRINGS.function
|
|
437
|
-
) {
|
|
438
|
-
console.error(
|
|
439
|
-
'Component not found for current path or the component is not a valid function:',
|
|
440
|
-
currentRouteData,
|
|
441
|
-
);
|
|
442
|
-
return null
|
|
443
|
-
}
|
|
417
|
+
return `${window.location.origin}/${src}`
|
|
418
|
+
}
|
|
444
419
|
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
query,
|
|
448
|
-
})
|
|
449
|
-
};
|
|
420
|
+
return `${apiEndpoint}/image/${src}${extension}?${query.toString()}`
|
|
421
|
+
};
|
|
450
422
|
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
423
|
+
/**
|
|
424
|
+
* The `Image` function in JavaScript optimizes image loading based on a specified optimization flag.
|
|
425
|
+
* @returns An `<img>` element is being returned with the specified `src` and other props passed to the
|
|
426
|
+
* `Image` component. The `src` is either the original `src` value or the result of calling
|
|
427
|
+
* `optimizationImageApi` function with `src` and `props` if `optimization` is set to 'true'.
|
|
428
|
+
*/
|
|
429
|
+
const Image = ({ src, ...props }) => {
|
|
430
|
+
const optimization = props.optimization === 'true' ? true : false;
|
|
431
|
+
|
|
432
|
+
const url = optimization
|
|
433
|
+
? optimizationImageApi({
|
|
434
|
+
src,
|
|
435
|
+
props,
|
|
436
|
+
})
|
|
437
|
+
: src;
|
|
438
|
+
|
|
439
|
+
const ImageProps = {
|
|
440
|
+
src: url,
|
|
441
|
+
props,
|
|
461
442
|
};
|
|
462
443
|
|
|
463
|
-
return
|
|
444
|
+
return createElement('img', ImageProps, null)
|
|
464
445
|
};
|
|
465
446
|
|
|
466
|
-
const isEvent = (key) => key.startsWith('on');
|
|
467
|
-
const isProperty = (key) => key !== STRINGS.children && !isEvent(key);
|
|
468
|
-
const isNew = (prev, next) => (key) => prev[key] !== next[key];
|
|
469
|
-
const isGone = (next) => (key) => !(key in next);
|
|
470
|
-
|
|
471
447
|
/**
|
|
472
|
-
*
|
|
473
|
-
*
|
|
474
|
-
*
|
|
475
|
-
*
|
|
476
|
-
*
|
|
448
|
+
* This function uses requestIdleCallback to perform work on a fiber tree until it is complete or the
|
|
449
|
+
* browser needs to yield to other tasks.
|
|
450
|
+
* @param deadline - The `deadline` parameter is an object that represents the amount of time the
|
|
451
|
+
* browser has to perform work before it needs to handle other tasks. It has a `timeRemaining()` method
|
|
452
|
+
* that returns the amount of time remaining before the deadline is reached. The `shouldYield` variable
|
|
453
|
+
* is used to determine
|
|
477
454
|
*/
|
|
478
|
-
const
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
455
|
+
const workLoop = (deadline) => {
|
|
456
|
+
let shouldYield = false;
|
|
457
|
+
while (vars.nextUnitOfWork && !shouldYield) {
|
|
458
|
+
vars.nextUnitOfWork = performUnitOfWork(vars.nextUnitOfWork);
|
|
459
|
+
shouldYield = deadline.timeRemaining() < 1;
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
if (!vars.nextUnitOfWork && vars.wipRoot) {
|
|
463
|
+
commitRoot();
|
|
485
464
|
}
|
|
465
|
+
|
|
466
|
+
requestIdleCallback(workLoop);
|
|
486
467
|
};
|
|
487
468
|
|
|
469
|
+
//requestIdleCallback(workLoop)
|
|
470
|
+
|
|
488
471
|
/**
|
|
489
|
-
* The function
|
|
490
|
-
*
|
|
491
|
-
*
|
|
492
|
-
*
|
|
493
|
-
*
|
|
472
|
+
* The function performs a unit of work by updating either a function component or a host component and
|
|
473
|
+
* returns the next fiber to be processed.
|
|
474
|
+
* @param fiber - A fiber is a unit of work in Ryunix that represents a component and its state. It
|
|
475
|
+
* contains information about the component's type, props, and children, as well as pointers to its
|
|
476
|
+
* parent, child, and sibling fibers. The `performUnitOfWork` function takes a fiber as a parameter and
|
|
477
|
+
* performs work
|
|
478
|
+
* @returns The function `performUnitOfWork` returns the next fiber to be processed. If the current
|
|
479
|
+
* fiber has a child, it returns the child. Otherwise, it looks for the next sibling of the current
|
|
480
|
+
* fiber. If there are no more siblings, it goes up the tree to the parent and looks for the next
|
|
481
|
+
* sibling of the parent. The function returns `undefined` if there are no more fibers to process.
|
|
494
482
|
*/
|
|
495
|
-
const
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
483
|
+
const performUnitOfWork = (fiber) => {
|
|
484
|
+
const isFunctionComponent = fiber.type instanceof Function;
|
|
485
|
+
if (isFunctionComponent) {
|
|
486
|
+
updateFunctionComponent(fiber);
|
|
487
|
+
} else {
|
|
488
|
+
updateHostComponent(fiber);
|
|
489
|
+
}
|
|
490
|
+
if (fiber.child) {
|
|
491
|
+
return fiber.child
|
|
492
|
+
}
|
|
493
|
+
let nextFiber = fiber;
|
|
494
|
+
while (nextFiber) {
|
|
495
|
+
if (nextFiber.sibling) {
|
|
496
|
+
return nextFiber.sibling
|
|
497
|
+
}
|
|
498
|
+
nextFiber = nextFiber.parent;
|
|
502
499
|
}
|
|
503
500
|
};
|
|
504
501
|
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
* @returns The `createDom` function returns a newly created DOM element based on the `fiber` object
|
|
510
|
-
* passed as an argument. If the `fiber` object represents a text element, a text node is created using
|
|
511
|
-
* `document.createTextNode("")`. Otherwise, a new element is created using
|
|
512
|
-
* `document.createElement(fiber.type)`. The function then calls the `updateDom` function to update the
|
|
513
|
-
* properties of the newly created
|
|
514
|
-
*/
|
|
515
|
-
const createDom = (fiber) => {
|
|
516
|
-
const dom =
|
|
517
|
-
fiber.type == RYUNIX_TYPES.TEXT_ELEMENT
|
|
518
|
-
? document.createTextNode('')
|
|
519
|
-
: document.createElement(fiber.type);
|
|
520
|
-
|
|
521
|
-
updateDom(dom, {}, fiber.props);
|
|
502
|
+
const scheduleWork = (root) => {
|
|
503
|
+
vars.nextUnitOfWork = root;
|
|
504
|
+
vars.wipRoot = root;
|
|
505
|
+
vars.deletions = [];
|
|
522
506
|
|
|
523
|
-
|
|
507
|
+
vars.hookIndex = 0;
|
|
508
|
+
vars.effects = [];
|
|
509
|
+
requestIdleCallback(workLoop);
|
|
524
510
|
};
|
|
525
511
|
|
|
526
512
|
/**
|
|
527
|
-
*
|
|
528
|
-
*
|
|
529
|
-
* @param
|
|
530
|
-
* @param
|
|
531
|
-
* @
|
|
513
|
+
* Renders an element into a container using a work-in-progress (WIP) root.
|
|
514
|
+
* @function render
|
|
515
|
+
* @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.
|
|
516
|
+
* @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.
|
|
517
|
+
* @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.
|
|
518
|
+
* It also clears any scheduled deletions and establishes the next unit of work for incremental rendering.
|
|
532
519
|
*/
|
|
533
|
-
const
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
Object.keys(prevProps)
|
|
543
|
-
.filter(isProperty)
|
|
544
|
-
.filter(isGone(nextProps))
|
|
545
|
-
.forEach((name) => {
|
|
546
|
-
dom[name] = '';
|
|
547
|
-
});
|
|
548
|
-
|
|
549
|
-
Object.keys(nextProps)
|
|
550
|
-
.filter(isProperty)
|
|
551
|
-
.filter(isNew(prevProps, nextProps))
|
|
552
|
-
.forEach((name) => {
|
|
553
|
-
if (name === STRINGS.style) {
|
|
554
|
-
DomStyle(dom, nextProps['ryunix-style']);
|
|
555
|
-
} else if (name === OLD_STRINGS.style) {
|
|
556
|
-
DomStyle(dom, nextProps.style);
|
|
557
|
-
} else if (name === STRINGS.className) {
|
|
558
|
-
if (nextProps['ryunix-class'] === '') {
|
|
559
|
-
throw new Error('data-class cannot be empty.')
|
|
560
|
-
}
|
|
561
|
-
|
|
562
|
-
prevProps['ryunix-class'] &&
|
|
563
|
-
dom.classList.remove(...prevProps['ryunix-class'].split(/\s+/));
|
|
564
|
-
dom.classList.add(...nextProps['ryunix-class'].split(/\s+/));
|
|
565
|
-
} else if (name === OLD_STRINGS.className) {
|
|
566
|
-
if (nextProps.className === '') {
|
|
567
|
-
throw new Error('className cannot be empty.')
|
|
568
|
-
}
|
|
569
|
-
|
|
570
|
-
prevProps.className &&
|
|
571
|
-
dom.classList.remove(...prevProps.className.split(/\s+/));
|
|
572
|
-
dom.classList.add(...nextProps.className.split(/\s+/));
|
|
573
|
-
} else {
|
|
574
|
-
dom[name] = nextProps[name];
|
|
575
|
-
}
|
|
576
|
-
});
|
|
577
|
-
|
|
578
|
-
Object.keys(nextProps)
|
|
579
|
-
.filter(isEvent)
|
|
580
|
-
.filter(isNew(prevProps, nextProps))
|
|
581
|
-
.forEach((name) => {
|
|
582
|
-
const eventType = name.toLowerCase().substring(2);
|
|
583
|
-
dom.addEventListener(eventType, nextProps[name]);
|
|
584
|
-
});
|
|
585
|
-
};
|
|
520
|
+
const render = (element, container) => {
|
|
521
|
+
vars.wipRoot = {
|
|
522
|
+
dom: container,
|
|
523
|
+
props: {
|
|
524
|
+
children: [element],
|
|
525
|
+
},
|
|
526
|
+
alternate: vars.currentRoot,
|
|
527
|
+
};
|
|
586
528
|
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
});
|
|
592
|
-
acc += `${key}: ${style[styleName]};`;
|
|
593
|
-
return acc
|
|
594
|
-
}, '');
|
|
529
|
+
vars.nextUnitOfWork = vars.wipRoot;
|
|
530
|
+
vars.deletions = [];
|
|
531
|
+
scheduleWork(vars.wipRoot);
|
|
532
|
+
return vars.wipRoot
|
|
595
533
|
};
|
|
596
534
|
|
|
597
|
-
var Dom = /*#__PURE__*/Object.freeze({
|
|
598
|
-
__proto__: null,
|
|
599
|
-
DomStyle: DomStyle,
|
|
600
|
-
createDom: createDom,
|
|
601
|
-
updateDom: updateDom
|
|
602
|
-
});
|
|
603
|
-
|
|
604
535
|
/**
|
|
605
|
-
*
|
|
536
|
+
* Initializes the application by creating a reference to a DOM element with the specified ID and rendering the main component.
|
|
537
|
+
* @function init
|
|
538
|
+
* @param {Object} MainElement - The main component to render, typically the root component of the application.
|
|
539
|
+
* @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.
|
|
540
|
+
* @example
|
|
541
|
+
* Ryunix.init(App, "__ryunix"); // Initializes and renders the App component into the <div id="__ryunix"></div> element.
|
|
542
|
+
* @description This function retrieves the container element by its ID and invokes the `render` function to render the main component into it.
|
|
606
543
|
*/
|
|
607
|
-
const
|
|
608
|
-
vars.
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
vars.wipRoot = undefined;
|
|
544
|
+
const init = (MainElement, root = '__ryunix') => {
|
|
545
|
+
vars.containerRoot = document.getElementById(root);
|
|
546
|
+
|
|
547
|
+
const renderProcess = render(MainElement, vars.containerRoot);
|
|
548
|
+
|
|
549
|
+
return renderProcess
|
|
614
550
|
};
|
|
615
551
|
|
|
616
552
|
/**
|
|
617
|
-
* The function
|
|
618
|
-
* @param
|
|
619
|
-
*
|
|
620
|
-
*
|
|
621
|
-
* @returns The function does not return anything, it performs side effects by manipulating the DOM.
|
|
553
|
+
* @description The function creates a state.
|
|
554
|
+
* @param initial - The initial value of the state for the hook.
|
|
555
|
+
* @returns The `useStore` function returns an array with two elements: the current state value and a
|
|
556
|
+
* `setState` function that can be used to update the state.
|
|
622
557
|
*/
|
|
623
|
-
const
|
|
624
|
-
|
|
558
|
+
const useStore = (initial) => {
|
|
559
|
+
const oldHook =
|
|
560
|
+
vars.wipFiber.alternate &&
|
|
561
|
+
vars.wipFiber.alternate.hooks &&
|
|
562
|
+
vars.wipFiber.alternate.hooks[vars.hookIndex];
|
|
625
563
|
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
}
|
|
630
|
-
const domParent = domParentFiber.dom;
|
|
564
|
+
const hook = {
|
|
565
|
+
state: oldHook ? oldHook.state : initial,
|
|
566
|
+
queue: [],
|
|
567
|
+
};
|
|
631
568
|
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
updateDom(fiber.dom, fiber.alternate.props, fiber.props);
|
|
638
|
-
runEffects(fiber);
|
|
639
|
-
} else if (fiber.effectTag === EFFECT_TAGS.DELETION) {
|
|
640
|
-
commitDeletion(fiber, domParent);
|
|
641
|
-
cancelEffects(fiber);
|
|
642
|
-
return
|
|
643
|
-
}
|
|
569
|
+
const actions = oldHook ? oldHook.queue : [];
|
|
570
|
+
actions.forEach((action) => {
|
|
571
|
+
hook.state =
|
|
572
|
+
typeof action === STRINGS.function ? action(hook.state) : action;
|
|
573
|
+
});
|
|
644
574
|
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
575
|
+
const setState = (action) => {
|
|
576
|
+
hook.queue.push(action);
|
|
577
|
+
vars.wipRoot = {
|
|
578
|
+
dom: vars.currentRoot.dom,
|
|
579
|
+
props: vars.currentRoot.props,
|
|
580
|
+
alternate: vars.currentRoot,
|
|
581
|
+
};
|
|
582
|
+
vars.nextUnitOfWork = vars.wipRoot;
|
|
583
|
+
vars.deletions = [];
|
|
584
|
+
};
|
|
585
|
+
|
|
586
|
+
vars.wipFiber.hooks.push(hook);
|
|
587
|
+
vars.hookIndex++;
|
|
588
|
+
return [hook.state, setState]
|
|
648
589
|
};
|
|
649
590
|
|
|
650
591
|
/**
|
|
651
|
-
*
|
|
652
|
-
*
|
|
653
|
-
*
|
|
654
|
-
*
|
|
655
|
-
* @param
|
|
592
|
+
* This is a function that creates a hook for managing side effects in Ryunix components.
|
|
593
|
+
* @param effect - The effect function that will be executed after the component has rendered or when
|
|
594
|
+
* the dependencies have changed. It can perform side effects such as fetching data, updating the DOM,
|
|
595
|
+
* or subscribing to events.
|
|
596
|
+
* @param deps - An array of dependencies that the effect depends on. If any of the dependencies change
|
|
597
|
+
* between renders, the effect will be re-run. If the array is empty, the effect will only run once on
|
|
598
|
+
* mount and never again.
|
|
656
599
|
*/
|
|
657
|
-
const commitDeletion = (fiber, domParent) => {
|
|
658
|
-
if (fiber.dom) {
|
|
659
|
-
domParent.removeChild(fiber.dom);
|
|
660
|
-
} else {
|
|
661
|
-
commitDeletion(fiber.child, domParent);
|
|
662
|
-
}
|
|
663
|
-
};
|
|
664
600
|
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
});
|
|
601
|
+
const useEffect = (callback, deps) => {
|
|
602
|
+
const oldHook =
|
|
603
|
+
vars.wipFiber.alternate &&
|
|
604
|
+
vars.wipFiber.alternate.hooks &&
|
|
605
|
+
vars.wipFiber.alternate.hooks[vars.hookIndex];
|
|
671
606
|
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
* DOM tree.
|
|
678
|
-
* @param elements - an array of elements representing the new children to be rendered in the current
|
|
679
|
-
* fiber's subtree
|
|
680
|
-
*/
|
|
681
|
-
const reconcileChildren = (wipFiber, elements) => {
|
|
682
|
-
let index = 0;
|
|
683
|
-
let oldFiber = wipFiber.alternate && wipFiber.alternate.child;
|
|
684
|
-
let prevSibling = null;
|
|
607
|
+
const hook = {
|
|
608
|
+
type: RYUNIX_TYPES.RYUNIX_EFFECT,
|
|
609
|
+
deps,
|
|
610
|
+
cleanup: oldHook?.cleanup,
|
|
611
|
+
};
|
|
685
612
|
|
|
686
|
-
const
|
|
687
|
-
while (oldFiber) {
|
|
688
|
-
const oldKey = oldFiber.props.key || oldFiber.type;
|
|
689
|
-
oldFibersMap.set(oldKey, oldFiber);
|
|
690
|
-
oldFiber = oldFiber.sibling;
|
|
691
|
-
}
|
|
613
|
+
const hasChanged = !oldHook || !lodash.isEqual(oldHook.deps, deps);
|
|
692
614
|
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
615
|
+
if (hasChanged) {
|
|
616
|
+
vars.effects.push(() => {
|
|
617
|
+
// Llama al cleanup anterior si existe
|
|
618
|
+
if (typeof hook.cleanup === 'function') {
|
|
619
|
+
hook.cleanup();
|
|
620
|
+
}
|
|
697
621
|
|
|
698
|
-
|
|
699
|
-
|
|
622
|
+
// Ejecuta el nuevo efecto y guarda el nuevo cleanup
|
|
623
|
+
const result = callback();
|
|
624
|
+
if (typeof result === 'function') {
|
|
625
|
+
hook.cleanup = result;
|
|
626
|
+
}
|
|
627
|
+
});
|
|
628
|
+
}
|
|
700
629
|
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
props: element.props,
|
|
705
|
-
dom: oldFiber.dom,
|
|
706
|
-
parent: wipFiber,
|
|
707
|
-
alternate: oldFiber,
|
|
708
|
-
effectTag: EFFECT_TAGS.UPDATE,
|
|
709
|
-
};
|
|
710
|
-
oldFibersMap.delete(key);
|
|
711
|
-
} else if (element) {
|
|
712
|
-
newFiber = {
|
|
713
|
-
type: element.type,
|
|
714
|
-
props: element.props,
|
|
715
|
-
dom: undefined,
|
|
716
|
-
parent: wipFiber,
|
|
717
|
-
alternate: undefined,
|
|
718
|
-
effectTag: EFFECT_TAGS.PLACEMENT,
|
|
719
|
-
};
|
|
720
|
-
}
|
|
630
|
+
vars.wipFiber.hooks[vars.hookIndex] = hook;
|
|
631
|
+
vars.hookIndex++;
|
|
632
|
+
};
|
|
721
633
|
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
634
|
+
const useRef = (initial) => {
|
|
635
|
+
const oldHook =
|
|
636
|
+
vars.wipFiber.alternate &&
|
|
637
|
+
vars.wipFiber.alternate.hooks &&
|
|
638
|
+
vars.wipFiber.alternate.hooks[vars.hookIndex];
|
|
727
639
|
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
640
|
+
const hook = {
|
|
641
|
+
type: RYUNIX_TYPES.RYUNIX_REF,
|
|
642
|
+
value: oldHook ? oldHook.value : { current: initial },
|
|
643
|
+
};
|
|
731
644
|
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
645
|
+
vars.wipFiber.hooks[vars.hookIndex] = hook;
|
|
646
|
+
vars.hookIndex++;
|
|
647
|
+
|
|
648
|
+
return hook.value
|
|
736
649
|
};
|
|
737
650
|
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
651
|
+
const useMemo = (comp, deps) => {
|
|
652
|
+
const oldHook =
|
|
653
|
+
vars.wipFiber.alternate &&
|
|
654
|
+
vars.wipFiber.alternate.hooks &&
|
|
655
|
+
vars.wipFiber.alternate.hooks[vars.hookIndex];
|
|
742
656
|
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
657
|
+
const hook = {
|
|
658
|
+
type: RYUNIX_TYPES.RYUNIX_MEMO,
|
|
659
|
+
value: null,
|
|
660
|
+
deps,
|
|
661
|
+
};
|
|
662
|
+
|
|
663
|
+
if (oldHook) {
|
|
664
|
+
if (lodash.isEqual(oldHook.deps, hook.deps)) {
|
|
665
|
+
hook.value = oldHook.value;
|
|
666
|
+
} else {
|
|
667
|
+
hook.value = comp();
|
|
668
|
+
}
|
|
669
|
+
} else {
|
|
670
|
+
hook.value = comp();
|
|
671
|
+
}
|
|
754
672
|
|
|
755
|
-
|
|
756
|
-
|
|
673
|
+
vars.wipFiber.hooks[vars.hookIndex] = hook;
|
|
674
|
+
vars.hookIndex++;
|
|
757
675
|
|
|
758
|
-
|
|
676
|
+
return hook.value
|
|
677
|
+
};
|
|
678
|
+
|
|
679
|
+
const useCallback = (callback, deps) => {
|
|
680
|
+
return useMemo(() => callback, deps)
|
|
759
681
|
};
|
|
760
682
|
|
|
761
683
|
/**
|
|
762
|
-
*
|
|
763
|
-
* @
|
|
764
|
-
*
|
|
765
|
-
* fibers in the tree.
|
|
684
|
+
* The `useQuery` function parses the query parameters from the URL and returns them as an object.
|
|
685
|
+
* @returns An object containing key-value pairs of the query parameters from the URLSearchParams in
|
|
686
|
+
* the current window's URL is being returned.
|
|
766
687
|
*/
|
|
767
|
-
const
|
|
768
|
-
|
|
769
|
-
|
|
688
|
+
const useQuery = () => {
|
|
689
|
+
const searchParams = new URLSearchParams(window.location.search);
|
|
690
|
+
const query = {};
|
|
691
|
+
for (let [key, value] of searchParams.entries()) {
|
|
692
|
+
query[key] = value;
|
|
770
693
|
}
|
|
771
|
-
|
|
694
|
+
return query
|
|
772
695
|
};
|
|
773
696
|
|
|
774
|
-
var Components = /*#__PURE__*/Object.freeze({
|
|
775
|
-
__proto__: null,
|
|
776
|
-
updateFunctionComponent: updateFunctionComponent,
|
|
777
|
-
updateHostComponent: updateHostComponent
|
|
778
|
-
});
|
|
779
|
-
|
|
780
697
|
/**
|
|
781
|
-
*
|
|
782
|
-
*
|
|
783
|
-
*
|
|
784
|
-
*
|
|
785
|
-
*
|
|
786
|
-
*
|
|
698
|
+
* `useRouter` is a routing function to manage navigation, nested routes, and route pre-loading.
|
|
699
|
+
*
|
|
700
|
+
* This function handles client-side routing, URL updates, and component rendering based on defined routes. It supports:
|
|
701
|
+
* - Dynamic routes (e.g., "/user/:id").
|
|
702
|
+
* - Optional nested routes with an `subRoutes` property in route objects.
|
|
703
|
+
* - Default pre-loading of all routes except the current active route.
|
|
704
|
+
*
|
|
705
|
+
* @param {Array} routes - An array of route objects, each containing:
|
|
706
|
+
* - `path` (string): The URL path to match (supports dynamic segments like "/user/:id").
|
|
707
|
+
* - `component` (function): The component to render when the route matches.
|
|
708
|
+
* - `subRoutes` (optional array): An optional array of nested route objects, defining sub-routes for this route.
|
|
709
|
+
* - `NotFound` (optional function): Component to render for unmatched routes (default 404 behavior).
|
|
710
|
+
*
|
|
711
|
+
* @returns {Object} - An object with:
|
|
712
|
+
* - `Children` (function): Returns the component that matches the current route, passing route parameters and query parameters as props.
|
|
713
|
+
* - `NavLink` (component): A link component to navigate within the application without refreshing the page.
|
|
714
|
+
* - `navigate` (function): Allows programmatically navigating to a specific path.
|
|
715
|
+
*
|
|
716
|
+
* @example
|
|
717
|
+
* // Define nested routes
|
|
718
|
+
* const routes = [
|
|
719
|
+
* {
|
|
720
|
+
* path: "/",
|
|
721
|
+
* component: HomePage,
|
|
722
|
+
* subRoutes: [
|
|
723
|
+
* {
|
|
724
|
+
* path: "/settings",
|
|
725
|
+
* component: SettingsPage,
|
|
726
|
+
* },
|
|
727
|
+
* ],
|
|
728
|
+
* },
|
|
729
|
+
* {
|
|
730
|
+
* path: "/user/:id",
|
|
731
|
+
* component: UserProfile,
|
|
732
|
+
* },
|
|
733
|
+
* {
|
|
734
|
+
* path: "*",
|
|
735
|
+
* NotFound: NotFoundPage,
|
|
736
|
+
* },
|
|
737
|
+
* ];
|
|
738
|
+
*
|
|
739
|
+
* // Use the routing function
|
|
740
|
+
* const { Children, NavLink } = useRouter(routes);
|
|
741
|
+
*
|
|
742
|
+
* // Render the matched component
|
|
743
|
+
* const App = () => (
|
|
744
|
+
* <>
|
|
745
|
+
* <NavLink to="/">Home</NavLink>
|
|
746
|
+
* <NavLink to="/settings">Settings</NavLink>
|
|
747
|
+
* <NavLink to="/user/123">User Profile</NavLink>
|
|
748
|
+
* <Children />
|
|
749
|
+
* </>
|
|
750
|
+
* );
|
|
787
751
|
*/
|
|
788
|
-
const
|
|
789
|
-
|
|
790
|
-
while (vars.nextUnitOfWork && !shouldYield) {
|
|
791
|
-
vars.nextUnitOfWork = performUnitOfWork(vars.nextUnitOfWork);
|
|
792
|
-
shouldYield = deadline.timeRemaining() < 1;
|
|
793
|
-
}
|
|
752
|
+
const useRouter = (routes) => {
|
|
753
|
+
const [location, setLocation] = useStore(window.location.pathname);
|
|
794
754
|
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
}
|
|
755
|
+
const findRoute = (routes, path) => {
|
|
756
|
+
const pathname = path.split('?')[0];
|
|
798
757
|
|
|
799
|
-
|
|
800
|
-
|
|
758
|
+
const notFoundRoute = routes.find((route) => route.NotFound);
|
|
759
|
+
const notFound = notFoundRoute
|
|
760
|
+
? { route: { component: notFoundRoute.NotFound }, params: {} }
|
|
761
|
+
: { route: { component: null }, params: {} };
|
|
801
762
|
|
|
802
|
-
|
|
763
|
+
for (const route of routes) {
|
|
764
|
+
if (route.subRoutes) {
|
|
765
|
+
const childRoute = findRoute(route.subRoutes, path);
|
|
766
|
+
if (childRoute) return childRoute
|
|
767
|
+
}
|
|
803
768
|
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
769
|
+
if (route.path === '*') {
|
|
770
|
+
return notFound
|
|
771
|
+
}
|
|
772
|
+
|
|
773
|
+
if (!route.path || typeof route.path !== 'string') {
|
|
774
|
+
console.warn('Invalid route detected:', route);
|
|
775
|
+
console.info(
|
|
776
|
+
"if you are using { NotFound: NotFound } please add { path: '*', NotFound: NotFound }",
|
|
777
|
+
);
|
|
778
|
+
continue
|
|
779
|
+
}
|
|
780
|
+
|
|
781
|
+
const keys = [];
|
|
782
|
+
const pattern = new RegExp(
|
|
783
|
+
`^${route.path.replace(/:\w+/g, (match) => {
|
|
784
|
+
keys.push(match.substring(1));
|
|
785
|
+
return '([^/]+)'
|
|
786
|
+
})}$`,
|
|
787
|
+
);
|
|
788
|
+
|
|
789
|
+
const match = pathname.match(pattern);
|
|
790
|
+
if (match) {
|
|
791
|
+
const params = keys.reduce((acc, key, index) => {
|
|
792
|
+
acc[key] = match[index + 1];
|
|
793
|
+
return acc
|
|
794
|
+
}, {});
|
|
795
|
+
|
|
796
|
+
return { route, params }
|
|
797
|
+
}
|
|
830
798
|
}
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
799
|
+
|
|
800
|
+
return notFound
|
|
801
|
+
};
|
|
802
|
+
|
|
803
|
+
const navigate = (path) => {
|
|
804
|
+
window.history.pushState({}, '', path);
|
|
805
|
+
updateRoute(path);
|
|
806
|
+
};
|
|
807
|
+
|
|
808
|
+
const updateRoute = (path) => {
|
|
809
|
+
const cleanedPath = path.split('?')[0];
|
|
810
|
+
setLocation(cleanedPath);
|
|
811
|
+
};
|
|
812
|
+
|
|
813
|
+
useEffect(() => {
|
|
814
|
+
const onPopState = () => updateRoute(window.location.pathname);
|
|
815
|
+
window.addEventListener('popstate', onPopState);
|
|
816
|
+
|
|
817
|
+
return () => window.removeEventListener('popstate', onPopState)
|
|
818
|
+
}, []);
|
|
819
|
+
|
|
820
|
+
const currentRouteData = findRoute(routes, location) || {};
|
|
821
|
+
|
|
822
|
+
const Children = () => {
|
|
823
|
+
const query = useQuery();
|
|
824
|
+
const { route } = currentRouteData;
|
|
825
|
+
|
|
826
|
+
if (
|
|
827
|
+
!route ||
|
|
828
|
+
!route.component ||
|
|
829
|
+
typeof route.component !== STRINGS.function
|
|
830
|
+
) {
|
|
831
|
+
console.error(
|
|
832
|
+
'Component not found for current path or the component is not a valid function:',
|
|
833
|
+
currentRouteData,
|
|
834
|
+
);
|
|
835
|
+
return null
|
|
836
|
+
}
|
|
837
|
+
|
|
838
|
+
return route.component({
|
|
839
|
+
params: currentRouteData.params || {},
|
|
840
|
+
query,
|
|
841
|
+
})
|
|
842
|
+
};
|
|
843
|
+
|
|
844
|
+
const NavLink = ({ to, ...props }) => {
|
|
845
|
+
const handleClick = (e) => {
|
|
846
|
+
e.preventDefault();
|
|
847
|
+
navigate(to);
|
|
848
|
+
};
|
|
849
|
+
return createElement(
|
|
850
|
+
'a',
|
|
851
|
+
{ href: to, onClick: handleClick, ...props },
|
|
852
|
+
props.children,
|
|
853
|
+
)
|
|
854
|
+
};
|
|
855
|
+
|
|
856
|
+
return { Children, NavLink, navigate }
|
|
834
857
|
};
|
|
835
858
|
|
|
836
|
-
var
|
|
859
|
+
var Hooks = /*#__PURE__*/Object.freeze({
|
|
837
860
|
__proto__: null,
|
|
838
|
-
|
|
839
|
-
|
|
861
|
+
useCallback: useCallback,
|
|
862
|
+
useEffect: useEffect,
|
|
863
|
+
useMemo: useMemo,
|
|
864
|
+
useQuery: useQuery,
|
|
865
|
+
useRef: useRef,
|
|
866
|
+
useRouter: useRouter,
|
|
867
|
+
useStore: useStore
|
|
840
868
|
});
|
|
841
869
|
|
|
842
870
|
var Ryunix = {
|
|
@@ -844,15 +872,12 @@
|
|
|
844
872
|
render,
|
|
845
873
|
init,
|
|
846
874
|
Fragment,
|
|
847
|
-
|
|
848
|
-
Workers,
|
|
849
|
-
Reconciler,
|
|
850
|
-
Components,
|
|
851
|
-
Commits,
|
|
875
|
+
Hooks,
|
|
852
876
|
};
|
|
853
877
|
|
|
854
878
|
window.Ryunix = Ryunix;
|
|
855
879
|
|
|
880
|
+
exports.Image = Image;
|
|
856
881
|
exports.default = Ryunix;
|
|
857
882
|
exports.useCallback = useCallback;
|
|
858
883
|
exports.useEffect = useEffect;
|