@unsetsoft/ryunixjs 1.1.6 → 1.1.7

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