@unsetsoft/ryunixjs 1.0.3 → 1.1.2

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
@@ -79,10 +79,16 @@
79
79
 
80
80
  const createElement = (type, props, ...children) => {
81
81
  children = childArray(children, []);
82
+ const key =
83
+ props && props.key
84
+ ? props.key
85
+ : `${type}-${Math.random().toString(36).substring(2, 9)}`;
86
+
82
87
  return {
83
88
  type,
84
89
  props: {
85
90
  ...props,
91
+ key,
86
92
  children: children.map((child) =>
87
93
  typeof child === STRINGS.object ? child : createTextElement(child),
88
94
  ),
@@ -108,11 +114,12 @@
108
114
  };
109
115
 
110
116
  /**
111
- * The function renders an element into a container using a work-in-progress root.
112
- * @param element - The element parameter is the component or element that needs to be rendered in the
113
- * container. It could be a Ryunix component or a DOM element.
114
- * @param container - The container parameter is the DOM element where the rendered element will be
115
- * appended to. this parameter is optional if you use createRoot().
117
+ * Renders an element into a container using a work-in-progress (WIP) root.
118
+ * @function render
119
+ * @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.
120
+ * @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.
121
+ * @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.
122
+ * It also clears any scheduled deletions and establishes the next unit of work for incremental rendering.
116
123
  */
117
124
  const render = (element, container) => {
118
125
  vars.wipRoot = {
@@ -122,20 +129,24 @@
122
129
  },
123
130
  alternate: vars.currentRoot,
124
131
  };
132
+
125
133
  vars.deletions = [];
126
134
  vars.nextUnitOfWork = vars.wipRoot;
127
135
  };
128
136
 
129
137
  /**
130
- * @description The function creates a reference to a DOM element with the specified ID. This will be used to initialize the app.
131
- * @example Ryunix.init("root") -> <div id="root" />
132
- * @param root - The parameter "root" is the id of the HTML element that will serve as the container
133
- * for the root element.
138
+ * Initializes the application by creating a reference to a DOM element with the specified ID and rendering the main component.
139
+ * @function init
140
+ * @param {Object} MainElement - The main component to render, typically the root component of the application.
141
+ * @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.
142
+ * @example
143
+ * Ryunix.init(App, "__ryunix"); // Initializes and renders the App component into the <div id="__ryunix"></div> element.
144
+ * @description This function retrieves the container element by its ID and invokes the `render` function to render the main component into it.
134
145
  */
135
- const init = (root) => {
136
- const rootElement = root || '__ryunix';
137
- vars.containerRoot = document.getElementById(rootElement);
138
- return undefined
146
+ const init = (MainElement, root = '__ryunix') => {
147
+ vars.containerRoot = document.getElementById(root);
148
+
149
+ render(MainElement, vars.containerRoot);
139
150
  };
140
151
 
141
152
  /**
@@ -151,24 +162,19 @@
151
162
  vars.wipFiber.alternate.hooks[vars.hookIndex];
152
163
  const hook = {
153
164
  state: oldHook ? oldHook.state : initial,
154
- queue: [],
165
+ queue: oldHook ? [...oldHook.queue] : [],
155
166
  };
156
167
 
157
- const actions = oldHook ? oldHook.queue : [];
158
- actions.forEach((action) => {
168
+ hook.queue.forEach((action) => {
159
169
  hook.state =
160
170
  typeof action === STRINGS.function ? action(hook.state) : action;
161
171
  });
162
172
 
163
- /**
164
- * The function `setState` updates the state of a component in Ryunix by adding an action to a queue
165
- * and setting up a new work-in-progress root.
166
- * @param action - The `action` parameter is an object that represents a state update to be performed
167
- * on a component. It contains information about the type of update to be performed and any new data
168
- * that needs to be applied to the component's state.
169
- */
173
+ hook.queue = [];
174
+
170
175
  const setState = (action) => {
171
176
  hook.queue.push(action);
177
+
172
178
  vars.wipRoot = {
173
179
  dom: vars.currentRoot.dom,
174
180
  props: vars.currentRoot.props,
@@ -182,6 +188,7 @@
182
188
  vars.wipFiber.hooks.push(hook);
183
189
  vars.hookIndex++;
184
190
  }
191
+
185
192
  return [hook.state, setState]
186
193
  };
187
194
 
@@ -322,10 +329,31 @@
322
329
  }
323
330
  }, []);
324
331
 
325
- const currentRoute = routes.find((route) => route.path === location);
326
- const Children = () => (currentRoute ? currentRoute.component : null);
332
+ let currentRoute = routes.find((route) => route.path === location);
333
+ if (!currentRoute) {
334
+ currentRoute = {
335
+ component: routes.find((route) => route.NotFound)?.NotFound || null,
336
+ };
337
+ }
327
338
 
328
- return { Children, navigate }
339
+ const Children = () =>
340
+ currentRoute.component ? currentRoute.component : null;
341
+
342
+ const NavLink = ({ to, ...props }) => {
343
+ const { children, ...restProps } = props;
344
+
345
+ const NewProps = {
346
+ href: to,
347
+ onClick: (e) => {
348
+ e.preventDefault();
349
+ navigate(to);
350
+ },
351
+ ...restProps,
352
+ };
353
+ return createElement('a', NewProps, children)
354
+ };
355
+
356
+ return { Children, navigate, NavLink }
329
357
  };
330
358
 
331
359
  const isEvent = (key) => key.startsWith('on');
@@ -486,9 +514,7 @@
486
514
  * @returns The function does not return anything, it performs side effects by manipulating the DOM.
487
515
  */
488
516
  const commitWork = (fiber) => {
489
- if (!fiber) {
490
- return
491
- }
517
+ if (!fiber) return
492
518
 
493
519
  let domParentFiber = fiber.parent;
494
520
  while (!domParentFiber.dom) {
@@ -501,15 +527,19 @@
501
527
  domParent.appendChild(fiber.dom);
502
528
  }
503
529
  runEffects(fiber);
504
- } else if (fiber.effectTag === EFFECT_TAGS.UPDATE) {
530
+ }
531
+
532
+ if (fiber.effectTag === EFFECT_TAGS.UPDATE) {
505
533
  cancelEffects(fiber);
506
534
  if (fiber.dom != undefined) {
507
535
  updateDom(fiber.dom, fiber.alternate.props, fiber.props);
508
536
  }
509
537
  runEffects(fiber);
510
- } else if (fiber.effectTag === EFFECT_TAGS.DELETION) {
511
- cancelEffects(fiber);
538
+ }
539
+
540
+ if (fiber.effectTag === EFFECT_TAGS.DELETION) {
512
541
  commitDeletion(fiber, domParent);
542
+ cancelEffects(fiber);
513
543
  return
514
544
  }
515
545
 
@@ -525,9 +555,9 @@
525
555
  * @param domParent - The parent DOM element from which the fiber's DOM element needs to be removed.
526
556
  */
527
557
  const commitDeletion = (fiber, domParent) => {
528
- if (fiber && fiber.dom) {
558
+ if (fiber.dom) {
529
559
  domParent.removeChild(fiber.dom);
530
- } else if (fiber && fiber.child) {
560
+ } else {
531
561
  commitDeletion(fiber.child, domParent);
532
562
  }
533
563
  };
@@ -551,25 +581,34 @@
551
581
  const reconcileChildren = (wipFiber, elements) => {
552
582
  let index = 0;
553
583
  let oldFiber = wipFiber.alternate && wipFiber.alternate.child;
554
- let prevSibling;
584
+ let prevSibling = null;
585
+
586
+ const oldFibersMap = new Map();
587
+ while (oldFiber) {
588
+ const oldKey = oldFiber.props.key || oldFiber.type;
589
+ oldFibersMap.set(oldKey, oldFiber);
590
+ oldFiber = oldFiber.sibling;
591
+ }
555
592
 
556
- while (index < elements.length || oldFiber != undefined) {
593
+ while (index < elements.length) {
557
594
  const element = elements[index];
558
- let newFiber;
595
+ const key = element.props.key || element.type;
596
+ const oldFiber = oldFibersMap.get(key);
559
597
 
560
- const sameType = oldFiber && element && element.type == oldFiber.type;
598
+ let newFiber;
599
+ const sameType = oldFiber && element && element.type === oldFiber.type;
561
600
 
562
601
  if (sameType) {
563
602
  newFiber = {
564
- type: oldFiber ? oldFiber.type : undefined,
603
+ type: oldFiber.type,
565
604
  props: element.props,
566
- dom: oldFiber ? oldFiber.dom : undefined,
605
+ dom: oldFiber.dom,
567
606
  parent: wipFiber,
568
607
  alternate: oldFiber,
569
608
  effectTag: EFFECT_TAGS.UPDATE,
570
609
  };
571
- }
572
- if (element && !sameType) {
610
+ oldFibersMap.delete(key);
611
+ } else if (element) {
573
612
  newFiber = {
574
613
  type: element.type,
575
614
  props: element.props,
@@ -579,18 +618,15 @@
579
618
  effectTag: EFFECT_TAGS.PLACEMENT,
580
619
  };
581
620
  }
582
- if (oldFiber && !sameType) {
621
+
622
+ oldFibersMap.forEach((oldFiber) => {
583
623
  oldFiber.effectTag = EFFECT_TAGS.DELETION;
584
624
  vars.deletions.push(oldFiber);
585
- }
586
-
587
- if (oldFiber) {
588
- oldFiber = oldFiber.sibling;
589
- }
625
+ });
590
626
 
591
627
  if (index === 0) {
592
628
  wipFiber.child = newFiber;
593
- } else if (element && prevSibling) {
629
+ } else if (prevSibling) {
594
630
  prevSibling.sibling = newFiber;
595
631
  }
596
632
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@unsetsoft/ryunixjs",
3
- "version": "1.0.3",
3
+ "version": "1.1.2",
4
4
  "license": "MIT",
5
5
  "main": "./dist/Ryunix.js",
6
6
  "types": "./dist/Ryunix.d.ts",
@@ -23,7 +23,7 @@
23
23
  "rollup": "4.9.2"
24
24
  },
25
25
  "engines": {
26
- "node": ">=18.16.0"
26
+ "node": "^18 || ^20 || ^22"
27
27
  },
28
28
  "keywords": [
29
29
  "ryunixjs"
@@ -22,9 +22,7 @@ const commitRoot = () => {
22
22
  * @returns The function does not return anything, it performs side effects by manipulating the DOM.
23
23
  */
24
24
  const commitWork = (fiber) => {
25
- if (!fiber) {
26
- return
27
- }
25
+ if (!fiber) return
28
26
 
29
27
  let domParentFiber = fiber.parent
30
28
  while (!domParentFiber.dom) {
@@ -37,15 +35,19 @@ const commitWork = (fiber) => {
37
35
  domParent.appendChild(fiber.dom)
38
36
  }
39
37
  runEffects(fiber)
40
- } else if (fiber.effectTag === EFFECT_TAGS.UPDATE) {
38
+ }
39
+
40
+ if (fiber.effectTag === EFFECT_TAGS.UPDATE) {
41
41
  cancelEffects(fiber)
42
42
  if (fiber.dom != undefined) {
43
43
  updateDom(fiber.dom, fiber.alternate.props, fiber.props)
44
44
  }
45
45
  runEffects(fiber)
46
- } else if (fiber.effectTag === EFFECT_TAGS.DELETION) {
47
- cancelEffects(fiber)
46
+ }
47
+
48
+ if (fiber.effectTag === EFFECT_TAGS.DELETION) {
48
49
  commitDeletion(fiber, domParent)
50
+ cancelEffects(fiber)
49
51
  return
50
52
  }
51
53
 
@@ -61,11 +63,10 @@ const commitWork = (fiber) => {
61
63
  * @param domParent - The parent DOM element from which the fiber's DOM element needs to be removed.
62
64
  */
63
65
  const commitDeletion = (fiber, domParent) => {
64
- if (fiber && fiber.dom) {
66
+ if (fiber.dom) {
65
67
  domParent.removeChild(fiber.dom)
66
- } else if (fiber && fiber.child) {
68
+ } else {
67
69
  commitDeletion(fiber.child, domParent)
68
70
  }
69
71
  }
70
-
71
72
  export { commitDeletion, commitWork, commitRoot }
@@ -47,10 +47,16 @@ const cloneElement = (element, props) => {
47
47
 
48
48
  const createElement = (type, props, ...children) => {
49
49
  children = childArray(children, [])
50
+ const key =
51
+ props && props.key
52
+ ? props.key
53
+ : `${type}-${Math.random().toString(36).substring(2, 9)}`
54
+
50
55
  return {
51
56
  type,
52
57
  props: {
53
58
  ...props,
59
+ key,
54
60
  children: children.map((child) =>
55
61
  typeof child === STRINGS.object ? child : createTextElement(child),
56
62
  ),
package/src/lib/hooks.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { RYUNIX_TYPES, STRINGS, vars } from '../utils/index'
2
2
  import { isEqual } from 'lodash'
3
- import { Fragment, createElement, cloneElement } from './createElement'
3
+ import { createElement } from './createElement'
4
4
  /**
5
5
  * @description The function creates a state.
6
6
  * @param initial - The initial value of the state for the hook.
@@ -14,24 +14,19 @@ const useStore = (initial) => {
14
14
  vars.wipFiber.alternate.hooks[vars.hookIndex]
15
15
  const hook = {
16
16
  state: oldHook ? oldHook.state : initial,
17
- queue: [],
17
+ queue: oldHook ? [...oldHook.queue] : [],
18
18
  }
19
19
 
20
- const actions = oldHook ? oldHook.queue : []
21
- actions.forEach((action) => {
20
+ hook.queue.forEach((action) => {
22
21
  hook.state =
23
22
  typeof action === STRINGS.function ? action(hook.state) : action
24
23
  })
25
24
 
26
- /**
27
- * The function `setState` updates the state of a component in Ryunix by adding an action to a queue
28
- * and setting up a new work-in-progress root.
29
- * @param action - The `action` parameter is an object that represents a state update to be performed
30
- * on a component. It contains information about the type of update to be performed and any new data
31
- * that needs to be applied to the component's state.
32
- */
25
+ hook.queue = []
26
+
33
27
  const setState = (action) => {
34
28
  hook.queue.push(action)
29
+
35
30
  vars.wipRoot = {
36
31
  dom: vars.currentRoot.dom,
37
32
  props: vars.currentRoot.props,
@@ -45,6 +40,7 @@ const useStore = (initial) => {
45
40
  vars.wipFiber.hooks.push(hook)
46
41
  vars.hookIndex++
47
42
  }
43
+
48
44
  return [hook.state, setState]
49
45
  }
50
46
 
@@ -185,10 +181,31 @@ const useRouter = (routes) => {
185
181
  }
186
182
  }, [])
187
183
 
188
- const currentRoute = routes.find((route) => route.path === location)
189
- const Children = () => (currentRoute ? currentRoute.component : null)
184
+ let currentRoute = routes.find((route) => route.path === location)
185
+ if (!currentRoute) {
186
+ currentRoute = {
187
+ component: routes.find((route) => route.NotFound)?.NotFound || null,
188
+ }
189
+ }
190
+
191
+ const Children = () =>
192
+ currentRoute.component ? currentRoute.component : null
193
+
194
+ const NavLink = ({ to, ...props }) => {
195
+ const { children, ...restProps } = props
196
+
197
+ const NewProps = {
198
+ href: to,
199
+ onClick: (e) => {
200
+ e.preventDefault()
201
+ navigate(to)
202
+ },
203
+ ...restProps,
204
+ }
205
+ return createElement('a', NewProps, children)
206
+ }
190
207
 
191
- return { Children, navigate }
208
+ return { Children, navigate, NavLink }
192
209
  }
193
210
 
194
211
  export {
@@ -12,25 +12,34 @@ import { EFFECT_TAGS, vars } from '../utils/index'
12
12
  const reconcileChildren = (wipFiber, elements) => {
13
13
  let index = 0
14
14
  let oldFiber = wipFiber.alternate && wipFiber.alternate.child
15
- let prevSibling
15
+ let prevSibling = null
16
16
 
17
- while (index < elements.length || oldFiber != undefined) {
17
+ const oldFibersMap = new Map()
18
+ while (oldFiber) {
19
+ const oldKey = oldFiber.props.key || oldFiber.type
20
+ oldFibersMap.set(oldKey, oldFiber)
21
+ oldFiber = oldFiber.sibling
22
+ }
23
+
24
+ while (index < elements.length) {
18
25
  const element = elements[index]
19
- let newFiber
26
+ const key = element.props.key || element.type
27
+ const oldFiber = oldFibersMap.get(key)
20
28
 
21
- const sameType = oldFiber && element && element.type == oldFiber.type
29
+ let newFiber
30
+ const sameType = oldFiber && element && element.type === oldFiber.type
22
31
 
23
32
  if (sameType) {
24
33
  newFiber = {
25
- type: oldFiber ? oldFiber.type : undefined,
34
+ type: oldFiber.type,
26
35
  props: element.props,
27
- dom: oldFiber ? oldFiber.dom : undefined,
36
+ dom: oldFiber.dom,
28
37
  parent: wipFiber,
29
38
  alternate: oldFiber,
30
39
  effectTag: EFFECT_TAGS.UPDATE,
31
40
  }
32
- }
33
- if (element && !sameType) {
41
+ oldFibersMap.delete(key)
42
+ } else if (element) {
34
43
  newFiber = {
35
44
  type: element.type,
36
45
  props: element.props,
@@ -40,18 +49,15 @@ const reconcileChildren = (wipFiber, elements) => {
40
49
  effectTag: EFFECT_TAGS.PLACEMENT,
41
50
  }
42
51
  }
43
- if (oldFiber && !sameType) {
52
+
53
+ oldFibersMap.forEach((oldFiber) => {
44
54
  oldFiber.effectTag = EFFECT_TAGS.DELETION
45
55
  vars.deletions.push(oldFiber)
46
- }
47
-
48
- if (oldFiber) {
49
- oldFiber = oldFiber.sibling
50
- }
56
+ })
51
57
 
52
58
  if (index === 0) {
53
59
  wipFiber.child = newFiber
54
- } else if (element && prevSibling) {
60
+ } else if (prevSibling) {
55
61
  prevSibling.sibling = newFiber
56
62
  }
57
63
 
@@ -59,4 +65,5 @@ const reconcileChildren = (wipFiber, elements) => {
59
65
  index++
60
66
  }
61
67
  }
68
+
62
69
  export { reconcileChildren }
package/src/lib/render.js CHANGED
@@ -1,11 +1,18 @@
1
1
  import { vars } from '../utils/index'
2
2
 
3
+ const clearContainer = (container) => {
4
+ while (container.firstChild) {
5
+ container.removeChild(container.firstChild)
6
+ }
7
+ }
8
+
3
9
  /**
4
- * The function renders an element into a container using a work-in-progress root.
5
- * @param element - The element parameter is the component or element that needs to be rendered in the
6
- * container. It could be a Ryunix component or a DOM element.
7
- * @param container - The container parameter is the DOM element where the rendered element will be
8
- * appended to. this parameter is optional if you use createRoot().
10
+ * Renders an element into a container using a work-in-progress (WIP) root.
11
+ * @function render
12
+ * @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.
13
+ * @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.
14
+ * @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.
15
+ * It also clears any scheduled deletions and establishes the next unit of work for incremental rendering.
9
16
  */
10
17
  const render = (element, container) => {
11
18
  vars.wipRoot = {
@@ -15,20 +22,24 @@ const render = (element, container) => {
15
22
  },
16
23
  alternate: vars.currentRoot,
17
24
  }
25
+
18
26
  vars.deletions = []
19
27
  vars.nextUnitOfWork = vars.wipRoot
20
28
  }
21
29
 
22
30
  /**
23
- * @description The function creates a reference to a DOM element with the specified ID. This will be used to initialize the app.
24
- * @example Ryunix.init("root") -> <div id="root" />
25
- * @param root - The parameter "root" is the id of the HTML element that will serve as the container
26
- * for the root element.
31
+ * Initializes the application by creating a reference to a DOM element with the specified ID and rendering the main component.
32
+ * @function init
33
+ * @param {Object} MainElement - The main component to render, typically the root component of the application.
34
+ * @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.
35
+ * @example
36
+ * Ryunix.init(App, "__ryunix"); // Initializes and renders the App component into the <div id="__ryunix"></div> element.
37
+ * @description This function retrieves the container element by its ID and invokes the `render` function to render the main component into it.
27
38
  */
28
- const init = (root) => {
29
- const rootElement = root || '__ryunix'
30
- vars.containerRoot = document.getElementById(rootElement)
31
- return this
39
+ const init = (MainElement, root = '__ryunix') => {
40
+ vars.containerRoot = document.getElementById(root)
41
+
42
+ render(MainElement, vars.containerRoot)
32
43
  }
33
44
 
34
45
  export { render, init }
package/dist/cj/Ryunix.js DELETED
@@ -1,702 +0,0 @@
1
- ;(function (global, factory) {
2
- typeof exports === 'object' && typeof module !== 'undefined'
3
- ? factory(exports)
4
- : typeof define === 'function' && define.amd
5
- ? define(['exports'], factory)
6
- : ((global =
7
- typeof globalThis !== 'undefined' ? globalThis : global || self),
8
- factory((global.Ryunix = {})))
9
- })(this, function (exports) {
10
- 'use strict'
11
-
12
- const vars = {
13
- containerRoot: null,
14
- nextUnitOfWork: null,
15
- currentRoot: null,
16
- wipRoot: null,
17
- deletions: null,
18
- wipFiber: null,
19
- hookIndex: null,
20
- }
21
-
22
- const reg = /[A-Z]/g
23
-
24
- const RYUNIX_TYPES = Object.freeze({
25
- TEXT_ELEMENT: Symbol('text.element'),
26
- RYUNIX_EFFECT: Symbol('ryunix.effect'),
27
- })
28
-
29
- const STRINGS = Object.freeze({
30
- object: 'object',
31
- function: 'function',
32
- style: 'style',
33
- className: 'className',
34
- children: 'children',
35
- })
36
-
37
- const EFFECT_TAGS = Object.freeze({
38
- PLACEMENT: Symbol(),
39
- UPDATE: Symbol(),
40
- DELETION: Symbol(),
41
- })
42
-
43
- /**
44
- * The function creates a new element with the given type, props, and children.
45
- * @param type - The type of the element to be created, such as "div", "span", "h1", etc.
46
- * @param props - The `props` parameter is an object that contains the properties or attributes of the
47
- * element being created. These properties can include things like `className`, `id`, `style`, and any
48
- * other custom attributes that the user wants to add to the element. The `props` object is spread
49
- * using the spread
50
- * @param children - The `children` parameter is a rest parameter that allows the function to accept
51
- * any number of arguments after the `props` parameter. These arguments will be treated as children
52
- * elements of the created element. The `map` function is used to iterate over each child and create a
53
- * new element if it is not
54
- * @returns A JavaScript object with a `type` property and a `props` property. The `type` property is
55
- * set to the `type` argument passed into the function, and the `props` property is an object that
56
- * includes any additional properties passed in the `props` argument, as well as a `children` property
57
- * that is an array of any child elements passed in the `...children` argument
58
- */
59
-
60
- const createElement = (type, props, ...children) => {
61
- return {
62
- type,
63
- props: {
64
- ...props,
65
- children: children
66
- .flat()
67
- .map((child) =>
68
- typeof child === STRINGS.object ? child : createTextElement(child),
69
- ),
70
- },
71
- }
72
- }
73
-
74
- /**
75
- * The function creates a text element with a given text value.
76
- * @param text - The text content that will be used to create a new text element.
77
- * @returns A JavaScript object with a `type` property set to `"TEXT_ELEMENT"` and a `props` property
78
- * that contains a `nodeValue` property set to the `text` parameter and an empty `children` array.
79
- */
80
-
81
- const createTextElement = (text) => {
82
- return {
83
- type: RYUNIX_TYPES.TEXT_ELEMENT,
84
- props: {
85
- nodeValue: text,
86
- children: [],
87
- },
88
- }
89
- }
90
-
91
- const Fragments = (props) => {
92
- if (props.style) {
93
- throw new Error('The style attribute is not supported')
94
- }
95
- if (props.className === '') {
96
- throw new Error('className cannot be empty.')
97
- }
98
- return createElement('div', props, props.children)
99
- }
100
-
101
- /**
102
- * The function renders an element into a container using a work-in-progress root.
103
- * @param element - The element parameter is the component or element that needs to be rendered in the
104
- * container. It could be a Ryunix component or a DOM element.
105
- * @param container - The container parameter is the DOM element where the rendered element will be
106
- * appended to. this parameter is optional if you use createRoot().
107
- */
108
- const render = (element, container) => {
109
- vars.wipRoot = {
110
- dom: vars.containerRoot || container,
111
- props: {
112
- children: [element],
113
- },
114
- alternate: vars.currentRoot,
115
- }
116
- vars.deletions = []
117
- vars.nextUnitOfWork = vars.wipRoot
118
- }
119
-
120
- /**
121
- * @description The function creates a reference to a DOM element with the specified ID. This will be used to initialize the app.
122
- * @example Ryunix.init("root") -> <div id="root" />
123
- * @param root - The parameter "root" is the id of the HTML element that will serve as the container
124
- * for the root element.
125
- */
126
- const init = (root) => {
127
- const rootElement = root || '__ryunix'
128
- vars.containerRoot = document.getElementById(rootElement)
129
- }
130
-
131
- const isEvent = (key) => key.startsWith('on')
132
- const isProperty = (key) => key !== STRINGS.children && !isEvent(key)
133
- const isNew = (prev, next) => (key) => prev[key] !== next[key]
134
- const isGone = (next) => (key) => !(key in next)
135
- const hasDepsChanged = (prevDeps, nextDeps) =>
136
- !prevDeps ||
137
- !nextDeps ||
138
- prevDeps.length !== nextDeps.length ||
139
- prevDeps.some((dep, index) => dep !== nextDeps[index])
140
-
141
- /**
142
- * The function cancels all effect hooks in a given fiber.
143
- * @param fiber - The "fiber" parameter is likely referring to a data structure used in React.js to
144
- * represent a component and its state. It contains information about the component's props, state, and
145
- * children, as well as metadata used by React to manage updates and rendering. The function
146
- * "cancelEffects" is likely intended
147
- */
148
- const cancelEffects = (fiber) => {
149
- if (fiber.hooks) {
150
- fiber.hooks
151
- .filter(
152
- (hook) => hook.tag === RYUNIX_TYPES.RYUNIX_EFFECT && hook.cancel,
153
- )
154
- .forEach((effectHook) => {
155
- effectHook.cancel()
156
- })
157
- }
158
- }
159
-
160
- /**
161
- * The function runs all effect hooks in a given fiber.
162
- * @param fiber - The "fiber" parameter is likely referring to a data structure used in the
163
- * implementation of a fiber-based reconciliation algorithm, such as the one used in React. A fiber
164
- * represents a unit of work that needs to be performed by the reconciliation algorithm, and it
165
- * contains information about a component and its children, as
166
- */
167
- const runEffects = (fiber) => {
168
- if (fiber.hooks) {
169
- fiber.hooks
170
- .filter(
171
- (hook) => hook.tag === RYUNIX_TYPES.RYUNIX_EFFECT && hook.effect,
172
- )
173
- .forEach((effectHook) => {
174
- effectHook.cancel = effectHook.effect()
175
- })
176
- }
177
- }
178
-
179
- /**
180
- * @description The function creates a state.
181
- * @param initial - The initial value of the state for the hook.
182
- * @returns The `useStore` function returns an array with two elements: the current state value and a
183
- * `setState` function that can be used to update the state.
184
- */
185
- const useStore = (initial) => {
186
- const oldHook =
187
- vars.wipFiber.alternate &&
188
- vars.wipFiber.alternate.hooks &&
189
- vars.wipFiber.alternate.hooks[vars.hookIndex]
190
- const hook = {
191
- state: oldHook ? oldHook.state : initial,
192
- queue: [],
193
- }
194
-
195
- const actions = oldHook ? oldHook.queue : []
196
- actions.forEach((action) => {
197
- hook.state =
198
- typeof action === STRINGS.function ? action(hook.state) : action
199
- })
200
-
201
- /**
202
- * The function `setState` updates the state of a component in Ryunix by adding an action to a queue
203
- * and setting up a new work-in-progress root.
204
- * @param action - The `action` parameter is an object that represents a state update to be performed
205
- * on a component. It contains information about the type of update to be performed and any new data
206
- * that needs to be applied to the component's state.
207
- */
208
- const setState = (action) => {
209
- hook.queue.push(action)
210
- vars.wipRoot = {
211
- dom: vars.currentRoot.dom,
212
- props: vars.currentRoot.props,
213
- alternate: vars.currentRoot,
214
- }
215
- vars.nextUnitOfWork = vars.wipRoot
216
- vars.deletions = []
217
- }
218
-
219
- vars.wipFiber.hooks.push(hook)
220
- vars.hookIndex++
221
- return [hook.state, setState]
222
- }
223
-
224
- /**
225
- * This is a function that creates a hook for managing side effects in Ryunix components.
226
- * @param effect - The effect function that will be executed after the component has rendered or when
227
- * the dependencies have changed. It can perform side effects such as fetching data, updating the DOM,
228
- * or subscribing to events.
229
- * @param deps - An array of dependencies that the effect depends on. If any of the dependencies change
230
- * between renders, the effect will be re-run. If the array is empty, the effect will only run once on
231
- * mount and never again.
232
- */
233
- const useEffect = (effect, deps) => {
234
- const oldHook =
235
- vars.wipFiber.alternate &&
236
- vars.wipFiber.alternate.hooks &&
237
- vars.wipFiber.alternate.hooks[vars.hookIndex]
238
-
239
- const hasChanged = hasDepsChanged(oldHook ? oldHook.deps : undefined, deps)
240
-
241
- const hook = {
242
- tag: RYUNIX_TYPES.RYUNIX_EFFECT,
243
- effect: hasChanged ? effect : null,
244
- cancel: hasChanged && oldHook && oldHook.cancel,
245
- deps,
246
- }
247
-
248
- vars.wipFiber.hooks.push(hook)
249
- vars.hookIndex++
250
- }
251
-
252
- const useQuery = () => {
253
- vars.hookIndex++
254
-
255
- const oldHook =
256
- vars.wipFiber.alternate &&
257
- vars.wipFiber.alternate.hooks &&
258
- vars.wipFiber.alternate.hooks[vars.hookIndex]
259
-
260
- const hasOld = oldHook ? oldHook : undefined
261
-
262
- const urlSearchParams = new URLSearchParams(window.location.search)
263
- const params = Object.fromEntries(urlSearchParams.entries())
264
- const Query = hasOld ? hasOld : params
265
-
266
- const hook = {
267
- tag: RYUNIX_TYPES.RYUNIX_EFFECT,
268
- query: Query,
269
- }
270
-
271
- vars.wipFiber.hooks.push(hook)
272
-
273
- return hook.query
274
- }
275
-
276
- const Router = ({ path, component }) => {
277
- const [currentPath, setCurrentPath] = useStore(window.location.pathname)
278
-
279
- useEffect(() => {
280
- const onLocationChange = () => {
281
- setCurrentPath(() => window.location.pathname)
282
- }
283
-
284
- window.addEventListener('navigate', onLocationChange)
285
- window.addEventListener('pushsatate', onLocationChange)
286
- window.addEventListener('popstate', onLocationChange)
287
-
288
- return () => {
289
- window.removeEventListener('navigate', onLocationChange)
290
- window.removeEventListener('pushsatate', onLocationChange)
291
- window.removeEventListener('popstate', onLocationChange)
292
- }
293
- }, [currentPath])
294
-
295
- return currentPath === path ? component() : null
296
- }
297
-
298
- const Navigate = () => {
299
- /**
300
- * The function `push` is used to push a new state to the browser's history and trigger a custom
301
- * event called 'pushstate'.
302
- * @param to - The `to` parameter is a string representing the URL path to which you want to
303
- * navigate.
304
- * @param [state] - The `state` parameter is an optional object that represents the state associated
305
- * with the new history entry. It can be used to store any data that you want to associate with the
306
- * new URL. When you navigate back or forward in the browser history, this state object will be
307
- * passed to the `popstate
308
- * @returns The function `push` does not have a return statement, so it returns `undefined` by
309
- * default.
310
- */
311
- const push = (to, state = {}) => {
312
- if (window.location.pathname === to) return
313
- window.history.pushState(state, '', to)
314
- const navigationEvent = new Event('pushsatate')
315
- window.dispatchEvent(navigationEvent)
316
- }
317
-
318
- return { push }
319
- }
320
-
321
- const Link = (props) => {
322
- if (props.style) {
323
- throw new Error(
324
- 'The style attribute is not supported on internal components, use className.',
325
- )
326
- }
327
- if (props.to === '') {
328
- throw new Error("'to=' cannot be empty.")
329
- }
330
- if (props.className === '') {
331
- throw new Error('className cannot be empty.')
332
- }
333
- if (props.label === '' && !props.children) {
334
- throw new Error("'label=' cannot be empty.")
335
- }
336
-
337
- if (!props.to) {
338
- throw new Error("Missig 'to' param.")
339
- }
340
- const preventReload = (event) => {
341
- event.preventDefault()
342
- const { push } = Navigate()
343
- push(props.to)
344
- }
345
-
346
- const anchor = {
347
- href: props.to,
348
- onClick: preventReload,
349
- ...props,
350
- }
351
-
352
- const children = props.label ? props.label : props.children
353
-
354
- return createElement('a', anchor, children)
355
- }
356
-
357
- /**
358
- * The function creates a new DOM element based on the given fiber object and updates its properties.
359
- * @param fiber - The fiber parameter is an object that represents a node in the fiber tree. It
360
- * contains information about the element type, props, and children of the node.
361
- * @returns The `createDom` function returns a newly created DOM element based on the `fiber` object
362
- * passed as an argument. If the `fiber` object represents a text element, a text node is created using
363
- * `document.createTextNode("")`. Otherwise, a new element is created using
364
- * `document.createElement(fiber.type)`. The function then calls the `updateDom` function to update the
365
- * properties of the newly created
366
- */
367
- const createDom = (fiber) => {
368
- const dom =
369
- fiber.type == RYUNIX_TYPES.TEXT_ELEMENT
370
- ? document.createTextNode('')
371
- : document.createElement(fiber.type)
372
-
373
- updateDom(dom, {}, fiber.props)
374
-
375
- return dom
376
- }
377
-
378
- /**
379
- * The function updates the DOM by removing old event listeners and properties, and adding new ones
380
- * based on the previous and next props.
381
- * @param dom - The DOM element that needs to be updated with new props.
382
- * @param prevProps - An object representing the previous props (properties) of a DOM element.
383
- * @param nextProps - An object containing the new props that need to be updated in the DOM.
384
- */
385
- const updateDom = (dom, prevProps, nextProps) => {
386
- Object.keys(prevProps)
387
- .filter(isEvent)
388
- .filter(
389
- (key) => isGone(nextProps)(key) || isNew(prevProps, nextProps)(key),
390
- )
391
- .forEach((name) => {
392
- const eventType = name.toLowerCase().substring(2)
393
- dom.removeEventListener(eventType, prevProps[name])
394
- })
395
-
396
- Object.keys(prevProps)
397
- .filter(isProperty)
398
- .filter(isGone(nextProps))
399
- .forEach((name) => {
400
- dom[name] = ''
401
- })
402
-
403
- Object.keys(nextProps)
404
- .filter(isProperty)
405
- .filter(isNew(prevProps, nextProps))
406
- .forEach((name) => {
407
- if (name === STRINGS.style) {
408
- DomStyle(dom, nextProps.style)
409
- } else if (name === STRINGS.className) {
410
- if (nextProps.className === '') {
411
- throw new Error('className cannot be empty.')
412
- }
413
- prevProps.className &&
414
- dom.classList.remove(...prevProps.className.split(/\s+/))
415
- dom.classList.add(...nextProps.className.split(/\s+/))
416
- } else {
417
- dom[name] = nextProps[name]
418
- }
419
- })
420
-
421
- Object.keys(nextProps)
422
- .filter(isEvent)
423
- .filter(isNew(prevProps, nextProps))
424
- .forEach((name) => {
425
- const eventType = name.toLowerCase().substring(2)
426
- dom.addEventListener(eventType, nextProps[name])
427
- })
428
- }
429
-
430
- const DomStyle = (dom, style) => {
431
- dom.style = Object.keys(style).reduce((acc, styleName) => {
432
- const key = styleName.replace(reg, function (v) {
433
- return '-' + v.toLowerCase()
434
- })
435
- acc += `${key}: ${style[styleName]};`
436
- return acc
437
- }, '')
438
- }
439
-
440
- var Dom = /*#__PURE__*/ Object.freeze({
441
- __proto__: null,
442
- DomStyle: DomStyle,
443
- createDom: createDom,
444
- updateDom: updateDom,
445
- })
446
-
447
- /**
448
- * The function commits changes made to the virtual DOM to the actual DOM.
449
- */
450
- const commitRoot = () => {
451
- vars.deletions.forEach(commitWork)
452
- commitWork(vars.wipRoot.child)
453
- vars.currentRoot = vars.wipRoot
454
- vars.wipRoot = null
455
- }
456
-
457
- /**
458
- * The function commits changes made to the DOM based on the effect tag of the fiber.
459
- * @param fiber - A fiber is a unit of work in Ryunix's reconciliation process. It represents a
460
- * component and its state at a particular point in time. The `commitWork` function takes a fiber as a
461
- * parameter to commit the changes made during the reconciliation process to the actual DOM.
462
- * @returns The function does not return anything, it performs side effects by manipulating the DOM.
463
- */
464
- const commitWork = (fiber) => {
465
- if (!fiber) {
466
- return
467
- }
468
-
469
- let domParentFiber = fiber.parent
470
- while (!domParentFiber.dom) {
471
- domParentFiber = domParentFiber.parent
472
- }
473
- const domParent = domParentFiber.dom
474
-
475
- if (fiber.effectTag === EFFECT_TAGS.PLACEMENT) {
476
- if (fiber.dom != null) {
477
- domParent.appendChild(fiber.dom)
478
- }
479
- runEffects(fiber)
480
- } else if (fiber.effectTag === EFFECT_TAGS.UPDATE) {
481
- cancelEffects(fiber)
482
- if (fiber.dom != null) {
483
- updateDom(fiber.dom, fiber.alternate.props, fiber.props)
484
- }
485
- runEffects(fiber)
486
- } else if (fiber.effectTag === EFFECT_TAGS.DELETION) {
487
- cancelEffects(fiber)
488
- commitDeletion(fiber, domParent)
489
- return
490
- }
491
-
492
- commitWork(fiber.child)
493
- commitWork(fiber.sibling)
494
- }
495
-
496
- /**
497
- * The function removes a fiber's corresponding DOM node from its parent node or recursively removes
498
- * its child's DOM node until it finds a node to remove.
499
- * @param fiber - a fiber node in a fiber tree, which represents a component or an element in the Ryunix
500
- * application.
501
- * @param domParent - The parent DOM element from which the fiber's DOM element needs to be removed.
502
- */
503
- const commitDeletion = (fiber, domParent) => {
504
- if (fiber.dom) {
505
- domParent.removeChild(fiber.dom)
506
- } else {
507
- commitDeletion(fiber.child, domParent)
508
- }
509
- }
510
-
511
- var Commits = /*#__PURE__*/ Object.freeze({
512
- __proto__: null,
513
- commitDeletion: commitDeletion,
514
- commitRoot: commitRoot,
515
- commitWork: commitWork,
516
- })
517
-
518
- /**
519
- * This function reconciles the children of a fiber node with a new set of elements, creating new
520
- * fibers for new elements, updating existing fibers for elements with the same type, and marking old
521
- * fibers for deletion if they are not present in the new set of elements.
522
- * @param wipFiber - A work-in-progress fiber object representing a component or element in the virtual
523
- * DOM tree.
524
- * @param elements - an array of elements representing the new children to be rendered in the current
525
- * fiber's subtree
526
- */
527
- const reconcileChildren = (wipFiber, elements) => {
528
- let index = 0
529
- let oldFiber = wipFiber.alternate && wipFiber.alternate.child
530
- let prevSibling
531
-
532
- while (index < elements.length || oldFiber != null) {
533
- const element = elements[index]
534
- let newFiber
535
-
536
- const sameType = oldFiber && element && element.type == oldFiber.type
537
-
538
- if (sameType) {
539
- newFiber = {
540
- type: oldFiber.type,
541
- props: element.props,
542
- dom: oldFiber.dom,
543
- parent: wipFiber,
544
- alternate: oldFiber,
545
- effectTag: EFFECT_TAGS.UPDATE,
546
- }
547
- }
548
- if (element && !sameType) {
549
- newFiber = {
550
- type: element.type,
551
- props: element.props,
552
- dom: null,
553
- parent: wipFiber,
554
- alternate: null,
555
- effectTag: EFFECT_TAGS.PLACEMENT,
556
- }
557
- }
558
- if (oldFiber && !sameType) {
559
- oldFiber.effectTag = EFFECT_TAGS.DELETION
560
- vars.deletions.push(oldFiber)
561
- }
562
-
563
- if (oldFiber) {
564
- oldFiber = oldFiber.sibling
565
- }
566
-
567
- if (index === 0) {
568
- wipFiber.child = newFiber
569
- } else if (element) {
570
- prevSibling.sibling = newFiber
571
- }
572
-
573
- prevSibling = newFiber
574
- index++
575
- }
576
- }
577
-
578
- var Reconciler = /*#__PURE__*/ Object.freeze({
579
- __proto__: null,
580
- reconcileChildren: reconcileChildren,
581
- })
582
-
583
- /**
584
- * This function updates a function component by setting up a work-in-progress fiber, resetting the
585
- * hook index, creating an empty hooks array, rendering the component, and reconciling its children.
586
- * @param fiber - The fiber parameter is an object that represents a node in the fiber tree. It
587
- * contains information about the component, its props, state, and children. In this function, it is
588
- * used to update the state of the component and its children.
589
- */
590
- const updateFunctionComponent = (fiber) => {
591
- vars.wipFiber = fiber
592
- vars.hookIndex = 0
593
- vars.wipFiber.hooks = []
594
- const children = [fiber.type(fiber.props)]
595
- reconcileChildren(fiber, children)
596
- }
597
-
598
- /**
599
- * This function updates a host component's DOM element and reconciles its children.
600
- * @param fiber - A fiber is a unit of work in Ryunix that represents a component and its state. It
601
- * contains information about the component's type, props, and children, as well as pointers to other
602
- * fibers in the tree.
603
- */
604
- const updateHostComponent = (fiber) => {
605
- if (!fiber.dom) {
606
- fiber.dom = createDom(fiber)
607
- }
608
- reconcileChildren(fiber, fiber.props.children.flat())
609
- }
610
-
611
- var Components = /*#__PURE__*/ Object.freeze({
612
- __proto__: null,
613
- updateFunctionComponent: updateFunctionComponent,
614
- updateHostComponent: updateHostComponent,
615
- })
616
-
617
- /**
618
- * This function uses requestIdleCallback to perform work on a fiber tree until it is complete or the
619
- * browser needs to yield to other tasks.
620
- * @param deadline - The `deadline` parameter is an object that represents the amount of time the
621
- * browser has to perform work before it needs to handle other tasks. It has a `timeRemaining()` method
622
- * that returns the amount of time remaining before the deadline is reached. The `shouldYield` variable
623
- * is used to determine
624
- */
625
- const workLoop = (deadline) => {
626
- let shouldYield = false
627
- while (vars.nextUnitOfWork && !shouldYield) {
628
- vars.nextUnitOfWork = performUnitOfWork(vars.nextUnitOfWork)
629
- shouldYield = deadline.timeRemaining() < 1
630
- }
631
-
632
- if (!vars.nextUnitOfWork && vars.wipRoot) {
633
- commitRoot()
634
- }
635
-
636
- requestIdleCallback(workLoop)
637
- }
638
-
639
- requestIdleCallback(workLoop)
640
-
641
- /**
642
- * The function performs a unit of work by updating either a function component or a host component and
643
- * returns the next fiber to be processed.
644
- * @param fiber - A fiber is a unit of work in Ryunix that represents a component and its state. It
645
- * contains information about the component's type, props, and children, as well as pointers to its
646
- * parent, child, and sibling fibers. The `performUnitOfWork` function takes a fiber as a parameter and
647
- * performs work
648
- * @returns The function `performUnitOfWork` returns the next fiber to be processed. If the current
649
- * fiber has a child, it returns the child. Otherwise, it looks for the next sibling of the current
650
- * fiber. If there are no more siblings, it goes up the tree to the parent and looks for the next
651
- * sibling of the parent. The function returns `null` if there are no more fibers to process.
652
- */
653
- const performUnitOfWork = (fiber) => {
654
- const isFunctionComponent = fiber.type instanceof Function
655
- if (isFunctionComponent) {
656
- updateFunctionComponent(fiber)
657
- } else {
658
- updateHostComponent(fiber)
659
- }
660
- if (fiber.child) {
661
- return fiber.child
662
- }
663
- let nextFiber = fiber
664
- while (nextFiber) {
665
- if (nextFiber.sibling) {
666
- return nextFiber.sibling
667
- }
668
- nextFiber = nextFiber.parent
669
- }
670
- }
671
-
672
- var Workers = /*#__PURE__*/ Object.freeze({
673
- __proto__: null,
674
- performUnitOfWork: performUnitOfWork,
675
- workLoop: workLoop,
676
- })
677
-
678
- var Ryunix = {
679
- createElement,
680
- render,
681
- init,
682
- Fragments,
683
- Dom,
684
- Workers,
685
- Reconciler,
686
- Components,
687
- Commits,
688
- }
689
-
690
- window.Ryunix = Ryunix
691
-
692
- exports.Fragments = Fragments
693
- exports.Link = Link
694
- exports.Navigate = Navigate
695
- exports.Router = Router
696
- exports.default = Ryunix
697
- exports.useEffect = useEffect
698
- exports.useQuery = useQuery
699
- exports.useStore = useStore
700
-
701
- Object.defineProperty(exports, '__esModule', { value: true })
702
- })