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