@unsetsoft/ryunixjs 1.1.6-canary.13 → 1.1.6-canary.130

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