@unsetsoft/ryunixjs 0.4.15-nightly.8 → 0.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/Ryunix.js CHANGED
@@ -1,17 +1,17 @@
1
1
  (function (global, factory) {
2
- typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
3
- typeof define === 'function' && define.amd ? define(['exports'], factory) :
4
- (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.Ryunix = {}));
5
- })(this, (function (exports) { 'use strict';
2
+ typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('lodash')) :
3
+ typeof define === 'function' && define.amd ? define(['exports', 'lodash'], factory) :
4
+ (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.Ryunix = {}, global.lodash));
5
+ })(this, (function (exports, lodash) { 'use strict';
6
6
 
7
7
  const vars = {
8
- containerRoot: null,
9
- nextUnitOfWork: null,
10
- currentRoot: null,
11
- wipRoot: null,
12
- deletions: null,
13
- wipFiber: null,
14
- hookIndex: null,
8
+ containerRoot: undefined,
9
+ nextUnitOfWork: undefined,
10
+ currentRoot: undefined,
11
+ wipRoot: undefined,
12
+ deletions: undefined,
13
+ wipFiber: undefined,
14
+ hookIndex: undefined,
15
15
  };
16
16
 
17
17
  const reg = /[A-Z]/g;
@@ -19,6 +19,9 @@
19
19
  const RYUNIX_TYPES = Object.freeze({
20
20
  TEXT_ELEMENT: Symbol('text.element'),
21
21
  RYUNIX_EFFECT: Symbol('ryunix.effect'),
22
+ RYUNIX_MEMO: Symbol('ryunix.memo'),
23
+ RYUNIX_URL_QUERY: Symbol('ryunix.urlQuery'),
24
+ RYUNIX_REF: Symbol('ryunix.ref'),
22
25
  });
23
26
 
24
27
  const STRINGS = Object.freeze({
@@ -27,6 +30,7 @@
27
30
  style: 'ryunix-style',
28
31
  className: 'ryunix-class',
29
32
  children: 'children',
33
+ boolean: 'boolean',
30
34
  });
31
35
 
32
36
  const OLD_STRINGS = Object.freeze({
@@ -40,14 +44,20 @@
40
44
  DELETION: Symbol(),
41
45
  });
42
46
 
43
- const cloneElement = (element, props) => {
44
- return {
45
- ...element,
46
- props: {
47
- ...element.props,
48
- ...props,
49
- },
47
+ const Fragments = (props) => {
48
+ return props.children
49
+ };
50
+
51
+ const childArray = (children, out) => {
52
+ out = out || [];
53
+ if (children == undefined || typeof children == STRINGS.boolean) ; else if (Array.isArray(children)) {
54
+ children.some((child) => {
55
+ childArray(child, out);
56
+ });
57
+ } else {
58
+ out.push(children);
50
59
  }
60
+ return out
51
61
  };
52
62
 
53
63
  /**
@@ -68,24 +78,33 @@
68
78
  */
69
79
 
70
80
  const createElement = (type, props, ...children) => {
81
+ children = childArray(children, []);
71
82
  return {
72
83
  type,
73
84
  props: {
74
85
  ...props,
75
- children: children
76
- .flat()
77
- .map((child, index) =>
78
- typeof child === STRINGS.object && child !== null
79
- ? cloneElement(child, { key: index })
80
- : child,
81
- ),
86
+ children: children.map((child) =>
87
+ typeof child === STRINGS.object ? child : createTextElement(child),
88
+ ),
82
89
  },
83
90
  }
84
91
  };
85
92
 
86
- const Fragments = (props) => {
87
- console.log(props);
88
- return
93
+ /**
94
+ * The function creates a text element with a given text value.
95
+ * @param text - The text content that will be used to create a new text element.
96
+ * @returns A JavaScript object with a `type` property set to `"TEXT_ELEMENT"` and a `props` property
97
+ * that contains a `nodeValue` property set to the `text` parameter and an empty `children` array.
98
+ */
99
+
100
+ const createTextElement = (text) => {
101
+ return {
102
+ type: RYUNIX_TYPES.TEXT_ELEMENT,
103
+ props: {
104
+ nodeValue: text,
105
+ children: [],
106
+ },
107
+ }
89
108
  };
90
109
 
91
110
  /**
@@ -118,50 +137,6 @@
118
137
  vars.containerRoot = document.getElementById(rootElement);
119
138
  };
120
139
 
121
- const isEvent = (key) => key.startsWith('on');
122
- const isProperty = (key) => key !== STRINGS.children && !isEvent(key);
123
- const isNew = (prev, next) => (key) => prev[key] !== next[key];
124
- const isGone = (next) => (key) => !(key in next);
125
- const hasDepsChanged = (prevDeps, nextDeps) =>
126
- !prevDeps ||
127
- !nextDeps ||
128
- prevDeps.length !== nextDeps.length ||
129
- prevDeps.some((dep, index) => dep !== nextDeps[index]);
130
-
131
- /**
132
- * The function cancels all effect hooks in a given fiber.
133
- * @param fiber - The "fiber" parameter is likely referring to a data structure used in React.js to
134
- * represent a component and its state. It contains information about the component's props, state, and
135
- * children, as well as metadata used by React to manage updates and rendering. The function
136
- * "cancelEffects" is likely intended
137
- */
138
- const cancelEffects = (fiber) => {
139
- if (fiber.hooks) {
140
- fiber.hooks
141
- .filter((hook) => hook.tag === RYUNIX_TYPES.RYUNIX_EFFECT && hook.cancel)
142
- .forEach((effectHook) => {
143
- effectHook.cancel();
144
- });
145
- }
146
- };
147
-
148
- /**
149
- * The function runs all effect hooks in a given fiber.
150
- * @param fiber - The "fiber" parameter is likely referring to a data structure used in the
151
- * implementation of a fiber-based reconciliation algorithm, such as the one used in React. A fiber
152
- * represents a unit of work that needs to be performed by the reconciliation algorithm, and it
153
- * contains information about a component and its children, as
154
- */
155
- const runEffects = (fiber) => {
156
- if (fiber.hooks) {
157
- fiber.hooks
158
- .filter((hook) => hook.tag === RYUNIX_TYPES.RYUNIX_EFFECT && hook.effect)
159
- .forEach((effectHook) => {
160
- effectHook.cancel = effectHook.effect();
161
- });
162
- }
163
- };
164
-
165
140
  /**
166
141
  * @description The function creates a state.
167
142
  * @param initial - The initial value of the state for the hook.
@@ -202,8 +177,10 @@
202
177
  vars.deletions = [];
203
178
  };
204
179
 
205
- vars.wipFiber.hooks.push(hook);
206
- vars.hookIndex++;
180
+ if (vars.wipFiber && vars.wipFiber.hooks) {
181
+ vars.wipFiber.hooks.push(hook);
182
+ vars.hookIndex++;
183
+ }
207
184
  return [hook.state, setState]
208
185
  };
209
186
 
@@ -216,23 +193,31 @@
216
193
  * between renders, the effect will be re-run. If the array is empty, the effect will only run once on
217
194
  * mount and never again.
218
195
  */
219
- const useEffect = (effect, deps) => {
196
+
197
+ const useEffect = (callback, deps) => {
220
198
  const oldHook =
221
199
  vars.wipFiber.alternate &&
222
200
  vars.wipFiber.alternate.hooks &&
223
201
  vars.wipFiber.alternate.hooks[vars.hookIndex];
224
202
 
225
- const hasChanged = hasDepsChanged(oldHook ? oldHook.deps : undefined, deps);
226
-
227
203
  const hook = {
228
- tag: RYUNIX_TYPES.RYUNIX_EFFECT,
229
- effect: hasChanged ? effect : null,
230
- cancel: hasChanged && oldHook && oldHook.cancel,
204
+ type: RYUNIX_TYPES.RYUNIX_EFFECT,
231
205
  deps,
232
206
  };
233
207
 
234
- vars.wipFiber.hooks.push(hook);
235
- vars.hookIndex++;
208
+ if (!oldHook) {
209
+ // invoke callback if this is the first time
210
+ callback();
211
+ } else {
212
+ if (!lodash.isEqual(oldHook.deps, hook.deps)) {
213
+ callback();
214
+ }
215
+ }
216
+
217
+ if (vars.wipFiber.hooks) {
218
+ vars.wipFiber.hooks.push(hook);
219
+ vars.hookIndex++;
220
+ }
236
221
  };
237
222
 
238
223
  /**
@@ -241,8 +226,6 @@
241
226
  * @returns The `useQuery` function returns the `query` property of the `hook` object.
242
227
  */
243
228
  const useQuery = () => {
244
- vars.hookIndex++;
245
-
246
229
  const oldHook =
247
230
  vars.wipFiber.alternate &&
248
231
  vars.wipFiber.alternate.hooks &&
@@ -255,101 +238,107 @@
255
238
  const Query = hasOld ? hasOld : params;
256
239
 
257
240
  const hook = {
258
- tag: RYUNIX_TYPES.RYUNIX_EFFECT,
241
+ type: RYUNIX_TYPES.RYUNIX_URL_QUERY,
259
242
  query: Query,
260
243
  };
261
244
 
262
- vars.wipFiber.hooks.push(hook);
245
+ if (vars.wipFiber.hooks) {
246
+ vars.wipFiber.hooks.push(hook);
247
+ vars.hookIndex++;
248
+ }
263
249
 
264
250
  return hook.query
265
251
  };
266
252
 
267
- const Router = ({ path, component, children }) => {
268
- const [currentPath, setCurrentPath] = useStore(window.location.pathname);
269
-
270
- useEffect(() => {
271
- const onLocationChange = () => {
272
- setCurrentPath(() => window.location.pathname);
273
- };
253
+ const useRef = (initial) => {
254
+ const oldHook =
255
+ vars.wipFiber.alternate &&
256
+ vars.wipFiber.alternate.hooks &&
257
+ vars.wipFiber.alternate.hooks[vars.hookIndex];
274
258
 
275
- window.addEventListener('navigate', onLocationChange);
276
- window.addEventListener('pushsatate', onLocationChange);
277
- window.addEventListener('popstate', onLocationChange);
259
+ const hook = {
260
+ type: RYUNIX_TYPES.RYUNIX_REF,
261
+ value: oldHook ? oldHook.value : { current: initial },
262
+ };
278
263
 
279
- return () => {
280
- setTimeout(() => {
281
- window.removeEventListener('navigate', onLocationChange);
282
- window.removeEventListener('pushsatate', onLocationChange);
283
- window.removeEventListener('popstate', onLocationChange);
284
- }, 100);
285
- }
286
- }, [currentPath]);
264
+ if (vars.wipFiber.hooks) {
265
+ vars.wipFiber.hooks.push(hook);
266
+ vars.hookIndex++;
267
+ }
287
268
 
288
- return currentPath === path ? component() : null
269
+ return hook.value
289
270
  };
290
271
 
291
- const Navigate = () => {
292
- /**
293
- * The function `push` is used to push a new state to the browser's history and trigger a custom
294
- * event called 'pushstate'.
295
- * @param to - The `to` parameter is a string representing the URL path to which you want to
296
- * navigate.
297
- * @param [state] - The `state` parameter is an optional object that represents the state associated
298
- * with the new history entry. It can be used to store any data that you want to associate with the
299
- * new URL. When you navigate back or forward in the browser history, this state object will be
300
- * passed to the `popstate
301
- * @returns The function `push` does not have a return statement, so it returns `undefined` by
302
- * default.
303
- */
304
- const push = (to, state = {}) => {
305
- if (window.location.pathname === to) return
272
+ const useMemo = (comp, deps) => {
273
+ const oldHook =
274
+ vars.wipFiber.alternate &&
275
+ vars.wipFiber.alternate.hooks &&
276
+ vars.wipFiber.alternate.hooks[vars.hookIndex];
306
277
 
307
- window.history.pushState(state, '', to);
308
- const navigationEvent = new PopStateEvent('popstate');
309
- window.dispatchEvent(navigationEvent);
278
+ const hook = {
279
+ type: RYUNIX_TYPES.RYUNIX_MEMO,
280
+ value: null,
281
+ deps,
310
282
  };
311
283
 
312
- return { push }
313
- };
314
-
315
- const Link = (props) => {
316
- if (props.style) {
317
- throw new Error(
318
- 'The style attribute is not supported on internal components, use className.',
319
- )
320
- }
321
- if (props.to === '') {
322
- throw new Error("'to=' cannot be empty.")
323
- }
324
- if (props.className === '') {
325
- throw new Error('className cannot be empty.')
326
- }
327
- if (props.label === '' && !props.children) {
328
- throw new Error("'label=' cannot be empty.")
284
+ if (oldHook) {
285
+ if (lodash.isEqual(oldHook.deps, hook.deps)) {
286
+ hook.value = oldHook.value;
287
+ } else {
288
+ hook.value = comp();
289
+ }
290
+ } else {
291
+ hook.value = comp();
329
292
  }
330
293
 
331
- if (!props.to) {
332
- throw new Error("Missig 'to' param.")
294
+ if (vars.wipFiber.hooks) {
295
+ vars.wipFiber.hooks.push(hook);
296
+ vars.hookIndex++;
333
297
  }
334
- const preventReload = (event) => {
335
- if (event.metaKey || event.ctrlKey) {
336
- return
337
- }
338
298
 
339
- event.preventDefault();
340
- const { push } = Navigate();
341
- push(props.to);
342
- };
299
+ return hook.value
300
+ };
301
+ const useCallback = (callback, deps) => {
302
+ return useMemo(() => callback, deps)
303
+ };
343
304
 
344
- const anchor = {
345
- href: props.to,
346
- onClick: preventReload,
347
- ...props,
348
- };
305
+ const isEvent = (key) => key.startsWith('on');
306
+ const isProperty = (key) => key !== STRINGS.children && !isEvent(key);
307
+ const isNew = (prev, next) => (key) => prev[key] !== next[key];
308
+ const isGone = (next) => (key) => !(key in next);
349
309
 
350
- const children = props.label ? props.label : props.children;
310
+ /**
311
+ * The function cancels all effect hooks in a given fiber.
312
+ * @param fiber - The "fiber" parameter is likely referring to a data structure used in React.js to
313
+ * represent a component and its state. It contains information about the component's props, state, and
314
+ * children, as well as metadata used by React to manage updates and rendering. The function
315
+ * "cancelEffects" is likely intended
316
+ */
317
+ const cancelEffects = (fiber) => {
318
+ if (fiber.hooks) {
319
+ fiber.hooks
320
+ .filter((hook) => hook.tag === RYUNIX_TYPES.RYUNIX_EFFECT && hook.cancel)
321
+ .forEach((effectHook) => {
322
+ effectHook.cancel();
323
+ });
324
+ }
325
+ };
351
326
 
352
- return createElement('a', anchor, children)
327
+ /**
328
+ * The function runs all effect hooks in a given fiber.
329
+ * @param fiber - The "fiber" parameter is likely referring to a data structure used in the
330
+ * implementation of a fiber-based reconciliation algorithm, such as the one used in React. A fiber
331
+ * represents a unit of work that needs to be performed by the reconciliation algorithm, and it
332
+ * contains information about a component and its children, as
333
+ */
334
+ const runEffects = (fiber) => {
335
+ if (fiber.hooks) {
336
+ fiber.hooks
337
+ .filter((hook) => hook.tag === RYUNIX_TYPES.RYUNIX_EFFECT && hook.effect)
338
+ .forEach((effectHook) => {
339
+ effectHook.cancel = effectHook.effect();
340
+ });
341
+ }
353
342
  };
354
343
 
355
344
  /**
@@ -456,9 +445,11 @@
456
445
  */
457
446
  const commitRoot = () => {
458
447
  vars.deletions.forEach(commitWork);
459
- commitWork(vars.wipRoot.child);
460
- vars.currentRoot = vars.wipRoot;
461
- vars.wipRoot = null;
448
+ if (vars.wipRoot && vars.wipRoot.child) {
449
+ commitWork(vars.wipRoot.child);
450
+ vars.currentRoot = vars.wipRoot;
451
+ }
452
+ vars.wipRoot = undefined;
462
453
  };
463
454
 
464
455
  /**
@@ -480,13 +471,13 @@
480
471
  const domParent = domParentFiber.dom;
481
472
 
482
473
  if (fiber.effectTag === EFFECT_TAGS.PLACEMENT) {
483
- if (fiber.dom != null) {
474
+ if (fiber.dom != undefined) {
484
475
  domParent.appendChild(fiber.dom);
485
476
  }
486
477
  runEffects(fiber);
487
478
  } else if (fiber.effectTag === EFFECT_TAGS.UPDATE) {
488
479
  cancelEffects(fiber);
489
- if (fiber.dom != null) {
480
+ if (fiber.dom != undefined) {
490
481
  updateDom(fiber.dom, fiber.alternate.props, fiber.props);
491
482
  }
492
483
  runEffects(fiber);
@@ -508,9 +499,9 @@
508
499
  * @param domParent - The parent DOM element from which the fiber's DOM element needs to be removed.
509
500
  */
510
501
  const commitDeletion = (fiber, domParent) => {
511
- if (fiber.dom) {
502
+ if (fiber && fiber.dom) {
512
503
  domParent.removeChild(fiber.dom);
513
- } else {
504
+ } else if (fiber && fiber.child) {
514
505
  commitDeletion(fiber.child, domParent);
515
506
  }
516
507
  };
@@ -536,7 +527,7 @@
536
527
  let oldFiber = wipFiber.alternate && wipFiber.alternate.child;
537
528
  let prevSibling;
538
529
 
539
- while (index < elements.length || oldFiber != null) {
530
+ while (index < elements.length || oldFiber != undefined) {
540
531
  const element = elements[index];
541
532
  let newFiber;
542
533
 
@@ -544,9 +535,9 @@
544
535
 
545
536
  if (sameType) {
546
537
  newFiber = {
547
- type: oldFiber.type,
538
+ type: oldFiber ? oldFiber.type : undefined,
548
539
  props: element.props,
549
- dom: oldFiber.dom,
540
+ dom: oldFiber ? oldFiber.dom : undefined,
550
541
  parent: wipFiber,
551
542
  alternate: oldFiber,
552
543
  effectTag: EFFECT_TAGS.UPDATE,
@@ -556,9 +547,9 @@
556
547
  newFiber = {
557
548
  type: element.type,
558
549
  props: element.props,
559
- dom: null,
550
+ dom: undefined,
560
551
  parent: wipFiber,
561
- alternate: null,
552
+ alternate: undefined,
562
553
  effectTag: EFFECT_TAGS.PLACEMENT,
563
554
  };
564
555
  }
@@ -573,7 +564,7 @@
573
564
 
574
565
  if (index === 0) {
575
566
  wipFiber.child = newFiber;
576
- } else if (element) {
567
+ } else if (element && prevSibling) {
577
568
  prevSibling.sibling = newFiber;
578
569
  }
579
570
 
@@ -598,8 +589,16 @@
598
589
  vars.wipFiber = fiber;
599
590
  vars.hookIndex = 0;
600
591
  vars.wipFiber.hooks = [];
601
- const children = [fiber.type(fiber.props)];
602
- reconcileChildren(fiber, children);
592
+ const children = fiber.type(fiber.props);
593
+ let childArr = [];
594
+ if (Array.isArray(children)) {
595
+ // Fragment results returns array
596
+ childArr = [...children];
597
+ } else {
598
+ // Normal function component returns single root node
599
+ childArr = [children];
600
+ }
601
+ reconcileChildren(fiber, childArr);
603
602
  };
604
603
 
605
604
  /**
@@ -612,7 +611,7 @@
612
611
  if (!fiber.dom) {
613
612
  fiber.dom = createDom(fiber);
614
613
  }
615
- reconcileChildren(fiber, fiber.props.children.flat());
614
+ reconcileChildren(fiber, fiber.props.children);
616
615
  };
617
616
 
618
617
  var Components = /*#__PURE__*/Object.freeze({
@@ -655,7 +654,7 @@
655
654
  * @returns The function `performUnitOfWork` returns the next fiber to be processed. If the current
656
655
  * fiber has a child, it returns the child. Otherwise, it looks for the next sibling of the current
657
656
  * fiber. If there are no more siblings, it goes up the tree to the parent and looks for the next
658
- * sibling of the parent. The function returns `null` if there are no more fibers to process.
657
+ * sibling of the parent. The function returns `undefined` if there are no more fibers to process.
659
658
  */
660
659
  const performUnitOfWork = (fiber) => {
661
660
  const isFunctionComponent = fiber.type instanceof Function;
@@ -674,6 +673,7 @@
674
673
  }
675
674
  nextFiber = nextFiber.parent;
676
675
  }
676
+ return undefined
677
677
  };
678
678
 
679
679
  var Workers = /*#__PURE__*/Object.freeze({
@@ -697,12 +697,12 @@
697
697
  window.Ryunix = Ryunix;
698
698
 
699
699
  exports.Fragments = Fragments;
700
- exports.Link = Link;
701
- exports.Navigate = Navigate;
702
- exports.Router = Router;
703
700
  exports.default = Ryunix;
701
+ exports.useCallback = useCallback;
704
702
  exports.useEffect = useEffect;
703
+ exports.useMemo = useMemo;
705
704
  exports.useQuery = useQuery;
705
+ exports.useRef = useRef;
706
706
  exports.useStore = useStore;
707
707
 
708
708
  Object.defineProperty(exports, '__esModule', { value: true });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@unsetsoft/ryunixjs",
3
- "version": "0.4.15-nightly.8",
3
+ "version": "0.5.1",
4
4
  "license": "MIT",
5
5
  "main": "./dist/Ryunix.js",
6
6
  "types": "./dist/Ryunix.d.ts",
@@ -10,7 +10,6 @@
10
10
  },
11
11
  "homepage": "https://github.com/UnSetSoft/Ryunixjs#readme",
12
12
  "scripts": {
13
- "build:ts": "npm run lint && tsc",
14
13
  "build:js": "rollup ./src/main.js --file ./dist/Ryunix.js --format umd --name Ryunix",
15
14
  "prepublishOnly": "npm run build:js",
16
15
  "postinstall": "npm run build:js",
@@ -20,13 +19,8 @@
20
19
  },
21
20
  "dependencies": {
22
21
  "eslint": "8.56.0",
23
- "@typescript-eslint/parser": "6.17.0",
24
- "@typescript-eslint/eslint-plugin": "6.17.0",
25
- "rollup": "4.9.2",
26
- "ts-node": "10.9.2",
27
- "tsup": "8.0.1",
28
- "typescript": "5.3.3",
29
- "tslint": "6.1.3"
22
+ "lodash": "^4.17.20",
23
+ "rollup": "4.9.2"
30
24
  },
31
25
  "engines": {
32
26
  "node": ">=18.16.0"
@@ -7,9 +7,11 @@ import { EFFECT_TAGS, vars } from '../utils/index'
7
7
  */
8
8
  const commitRoot = () => {
9
9
  vars.deletions.forEach(commitWork)
10
- commitWork(vars.wipRoot.child)
11
- vars.currentRoot = vars.wipRoot
12
- vars.wipRoot = null
10
+ if (vars.wipRoot && vars.wipRoot.child) {
11
+ commitWork(vars.wipRoot.child)
12
+ vars.currentRoot = vars.wipRoot
13
+ }
14
+ vars.wipRoot = undefined
13
15
  }
14
16
 
15
17
  /**
@@ -31,13 +33,13 @@ const commitWork = (fiber) => {
31
33
  const domParent = domParentFiber.dom
32
34
 
33
35
  if (fiber.effectTag === EFFECT_TAGS.PLACEMENT) {
34
- if (fiber.dom != null) {
36
+ if (fiber.dom != undefined) {
35
37
  domParent.appendChild(fiber.dom)
36
38
  }
37
39
  runEffects(fiber)
38
40
  } else if (fiber.effectTag === EFFECT_TAGS.UPDATE) {
39
41
  cancelEffects(fiber)
40
- if (fiber.dom != null) {
42
+ if (fiber.dom != undefined) {
41
43
  updateDom(fiber.dom, fiber.alternate.props, fiber.props)
42
44
  }
43
45
  runEffects(fiber)
@@ -59,9 +61,9 @@ const commitWork = (fiber) => {
59
61
  * @param domParent - The parent DOM element from which the fiber's DOM element needs to be removed.
60
62
  */
61
63
  const commitDeletion = (fiber, domParent) => {
62
- if (fiber.dom) {
64
+ if (fiber && fiber.dom) {
63
65
  domParent.removeChild(fiber.dom)
64
- } else {
66
+ } else if (fiber && fiber.child) {
65
67
  commitDeletion(fiber.child, domParent)
66
68
  }
67
69
  }
@@ -13,8 +13,16 @@ const updateFunctionComponent = (fiber) => {
13
13
  vars.wipFiber = fiber
14
14
  vars.hookIndex = 0
15
15
  vars.wipFiber.hooks = []
16
- const children = [fiber.type(fiber.props)]
17
- reconcileChildren(fiber, children)
16
+ const children = fiber.type(fiber.props)
17
+ let childArr = []
18
+ if (Array.isArray(children)) {
19
+ // Fragment results returns array
20
+ childArr = [...children]
21
+ } else {
22
+ // Normal function component returns single root node
23
+ childArr = [children]
24
+ }
25
+ reconcileChildren(fiber, childArr)
18
26
  }
19
27
 
20
28
  /**
@@ -27,7 +35,7 @@ const updateHostComponent = (fiber) => {
27
35
  if (!fiber.dom) {
28
36
  fiber.dom = createDom(fiber)
29
37
  }
30
- reconcileChildren(fiber, fiber.props.children.flat())
38
+ reconcileChildren(fiber, fiber.props.children)
31
39
  }
32
40
 
33
41
  export { updateFunctionComponent, updateHostComponent }
@@ -1,5 +1,23 @@
1
1
  import { RYUNIX_TYPES, STRINGS } from '../utils/index'
2
2
 
3
+ const Fragments = (props) => {
4
+ return props.children
5
+ }
6
+
7
+ const childArray = (children, out) => {
8
+ out = out || []
9
+ if (children == undefined || typeof children == STRINGS.boolean) {
10
+ // pass
11
+ } else if (Array.isArray(children)) {
12
+ children.some((child) => {
13
+ childArray(child, out)
14
+ })
15
+ } else {
16
+ out.push(children)
17
+ }
18
+ return out
19
+ }
20
+
3
21
  const cloneElement = (element, props) => {
4
22
  return {
5
23
  ...element,
@@ -28,17 +46,14 @@ const cloneElement = (element, props) => {
28
46
  */
29
47
 
30
48
  const createElement = (type, props, ...children) => {
49
+ children = childArray(children, [])
31
50
  return {
32
51
  type,
33
52
  props: {
34
53
  ...props,
35
- children: children
36
- .flat()
37
- .map((child, index) =>
38
- typeof child === STRINGS.object && child !== null
39
- ? cloneElement(child, { key: index })
40
- : child,
41
- ),
54
+ children: children.map((child) =>
55
+ typeof child === STRINGS.object ? child : createTextElement(child),
56
+ ),
42
57
  },
43
58
  }
44
59
  }
@@ -60,9 +75,4 @@ const createTextElement = (text) => {
60
75
  }
61
76
  }
62
77
 
63
- const Fragments = (props) => {
64
- console.log(props)
65
- return
66
- }
67
-
68
- export { createElement, createTextElement, Fragments }
78
+ export { createElement, createTextElement, Fragments, cloneElement }
package/src/lib/hooks.js CHANGED
@@ -1,6 +1,5 @@
1
- import { hasDepsChanged } from './effects'
2
1
  import { RYUNIX_TYPES, STRINGS, vars } from '../utils/index'
3
-
2
+ import { isEqual } from 'lodash'
4
3
  /**
5
4
  * @description The function creates a state.
6
5
  * @param initial - The initial value of the state for the hook.
@@ -41,8 +40,10 @@ const useStore = (initial) => {
41
40
  vars.deletions = []
42
41
  }
43
42
 
44
- vars.wipFiber.hooks.push(hook)
45
- vars.hookIndex++
43
+ if (vars.wipFiber && vars.wipFiber.hooks) {
44
+ vars.wipFiber.hooks.push(hook)
45
+ vars.hookIndex++
46
+ }
46
47
  return [hook.state, setState]
47
48
  }
48
49
 
@@ -55,23 +56,31 @@ const useStore = (initial) => {
55
56
  * between renders, the effect will be re-run. If the array is empty, the effect will only run once on
56
57
  * mount and never again.
57
58
  */
58
- const useEffect = (effect, deps) => {
59
+
60
+ const useEffect = (callback, deps) => {
59
61
  const oldHook =
60
62
  vars.wipFiber.alternate &&
61
63
  vars.wipFiber.alternate.hooks &&
62
64
  vars.wipFiber.alternate.hooks[vars.hookIndex]
63
65
 
64
- const hasChanged = hasDepsChanged(oldHook ? oldHook.deps : undefined, deps)
65
-
66
66
  const hook = {
67
- tag: RYUNIX_TYPES.RYUNIX_EFFECT,
68
- effect: hasChanged ? effect : null,
69
- cancel: hasChanged && oldHook && oldHook.cancel,
67
+ type: RYUNIX_TYPES.RYUNIX_EFFECT,
70
68
  deps,
71
69
  }
72
70
 
73
- vars.wipFiber.hooks.push(hook)
74
- vars.hookIndex++
71
+ if (!oldHook) {
72
+ // invoke callback if this is the first time
73
+ callback()
74
+ } else {
75
+ if (!isEqual(oldHook.deps, hook.deps)) {
76
+ callback()
77
+ }
78
+ }
79
+
80
+ if (vars.wipFiber.hooks) {
81
+ vars.wipFiber.hooks.push(hook)
82
+ vars.hookIndex++
83
+ }
75
84
  }
76
85
 
77
86
  /**
@@ -80,8 +89,6 @@ const useEffect = (effect, deps) => {
80
89
  * @returns The `useQuery` function returns the `query` property of the `hook` object.
81
90
  */
82
91
  const useQuery = () => {
83
- vars.hookIndex++
84
-
85
92
  const oldHook =
86
93
  vars.wipFiber.alternate &&
87
94
  vars.wipFiber.alternate.hooks &&
@@ -94,13 +101,68 @@ const useQuery = () => {
94
101
  const Query = hasOld ? hasOld : params
95
102
 
96
103
  const hook = {
97
- tag: RYUNIX_TYPES.RYUNIX_EFFECT,
104
+ type: RYUNIX_TYPES.RYUNIX_URL_QUERY,
98
105
  query: Query,
99
106
  }
100
107
 
101
- vars.wipFiber.hooks.push(hook)
108
+ if (vars.wipFiber.hooks) {
109
+ vars.wipFiber.hooks.push(hook)
110
+ vars.hookIndex++
111
+ }
102
112
 
103
113
  return hook.query
104
114
  }
105
115
 
106
- export { useStore, useEffect, useQuery }
116
+ const useRef = (initial) => {
117
+ const oldHook =
118
+ vars.wipFiber.alternate &&
119
+ vars.wipFiber.alternate.hooks &&
120
+ vars.wipFiber.alternate.hooks[vars.hookIndex]
121
+
122
+ const hook = {
123
+ type: RYUNIX_TYPES.RYUNIX_REF,
124
+ value: oldHook ? oldHook.value : { current: initial },
125
+ }
126
+
127
+ if (vars.wipFiber.hooks) {
128
+ vars.wipFiber.hooks.push(hook)
129
+ vars.hookIndex++
130
+ }
131
+
132
+ return hook.value
133
+ }
134
+
135
+ const useMemo = (comp, deps) => {
136
+ const oldHook =
137
+ vars.wipFiber.alternate &&
138
+ vars.wipFiber.alternate.hooks &&
139
+ vars.wipFiber.alternate.hooks[vars.hookIndex]
140
+
141
+ const hook = {
142
+ type: RYUNIX_TYPES.RYUNIX_MEMO,
143
+ value: null,
144
+ deps,
145
+ }
146
+
147
+ if (oldHook) {
148
+ if (isEqual(oldHook.deps, hook.deps)) {
149
+ hook.value = oldHook.value
150
+ } else {
151
+ hook.value = comp()
152
+ }
153
+ } else {
154
+ hook.value = comp()
155
+ }
156
+
157
+ if (vars.wipFiber.hooks) {
158
+ vars.wipFiber.hooks.push(hook)
159
+ vars.hookIndex++
160
+ }
161
+
162
+ return hook.value
163
+ }
164
+ const useCallback = (callback, deps) => {
165
+ return useMemo(() => callback, deps)
166
+ }
167
+
168
+ export { useStore, useEffect, useQuery, useRef, useMemo, useCallback }
package/src/lib/index.js CHANGED
@@ -1,14 +1,28 @@
1
- import { createElement, Fragments } from './createElement'
1
+ import { createElement, cloneElement, Fragments } from './createElement'
2
2
  import { render, init } from './render'
3
- import { useStore, useEffect, useQuery } from './hooks'
4
- import { Router, Navigate, Link } from './navigation'
3
+ import {
4
+ useStore,
5
+ useEffect,
6
+ useQuery,
7
+ useRef,
8
+ useMemo,
9
+ useCallback,
10
+ } from './hooks'
5
11
  import * as Dom from './dom'
6
12
  import * as Workers from './workers'
7
13
  import * as Reconciler from './reconciler'
8
14
  import * as Components from './components'
9
15
  import * as Commits from './commits'
10
16
 
11
- export { useStore, useEffect, useQuery, Fragments, Router, Navigate, Link }
17
+ export {
18
+ useStore,
19
+ useEffect,
20
+ useQuery,
21
+ useRef,
22
+ useMemo,
23
+ useCallback,
24
+ Fragments,
25
+ }
12
26
 
13
27
  export default {
14
28
  createElement,
@@ -14,7 +14,7 @@ const reconcileChildren = (wipFiber, elements) => {
14
14
  let oldFiber = wipFiber.alternate && wipFiber.alternate.child
15
15
  let prevSibling
16
16
 
17
- while (index < elements.length || oldFiber != null) {
17
+ while (index < elements.length || oldFiber != undefined) {
18
18
  const element = elements[index]
19
19
  let newFiber
20
20
 
@@ -22,9 +22,9 @@ const reconcileChildren = (wipFiber, elements) => {
22
22
 
23
23
  if (sameType) {
24
24
  newFiber = {
25
- type: oldFiber.type,
25
+ type: oldFiber ? oldFiber.type : undefined,
26
26
  props: element.props,
27
- dom: oldFiber.dom,
27
+ dom: oldFiber ? oldFiber.dom : undefined,
28
28
  parent: wipFiber,
29
29
  alternate: oldFiber,
30
30
  effectTag: EFFECT_TAGS.UPDATE,
@@ -34,9 +34,9 @@ const reconcileChildren = (wipFiber, elements) => {
34
34
  newFiber = {
35
35
  type: element.type,
36
36
  props: element.props,
37
- dom: null,
37
+ dom: undefined,
38
38
  parent: wipFiber,
39
- alternate: null,
39
+ alternate: undefined,
40
40
  effectTag: EFFECT_TAGS.PLACEMENT,
41
41
  }
42
42
  }
@@ -51,7 +51,7 @@ const reconcileChildren = (wipFiber, elements) => {
51
51
 
52
52
  if (index === 0) {
53
53
  wipFiber.child = newFiber
54
- } else if (element) {
54
+ } else if (element && prevSibling) {
55
55
  prevSibling.sibling = newFiber
56
56
  }
57
57
 
@@ -36,7 +36,7 @@ requestIdleCallback(workLoop)
36
36
  * @returns The function `performUnitOfWork` returns the next fiber to be processed. If the current
37
37
  * fiber has a child, it returns the child. Otherwise, it looks for the next sibling of the current
38
38
  * fiber. If there are no more siblings, it goes up the tree to the parent and looks for the next
39
- * sibling of the parent. The function returns `null` if there are no more fibers to process.
39
+ * sibling of the parent. The function returns `undefined` if there are no more fibers to process.
40
40
  */
41
41
  const performUnitOfWork = (fiber) => {
42
42
  const isFunctionComponent = fiber.type instanceof Function
@@ -55,6 +55,7 @@ const performUnitOfWork = (fiber) => {
55
55
  }
56
56
  nextFiber = nextFiber.parent
57
57
  }
58
+ return undefined
58
59
  }
59
60
 
60
61
  export { performUnitOfWork, workLoop }
package/src/main.js CHANGED
@@ -3,10 +3,10 @@ export {
3
3
  useStore,
4
4
  useEffect,
5
5
  useQuery,
6
+ useRef,
7
+ useMemo,
8
+ useCallback,
6
9
  Fragments,
7
- Navigate,
8
- Router,
9
- Link,
10
10
  } from './lib/index.js'
11
11
 
12
12
  window.Ryunix = Ryunix
@@ -1,11 +1,11 @@
1
1
  const vars = {
2
- containerRoot: null,
3
- nextUnitOfWork: null,
4
- currentRoot: null,
5
- wipRoot: null,
6
- deletions: null,
7
- wipFiber: null,
8
- hookIndex: null,
2
+ containerRoot: undefined,
3
+ nextUnitOfWork: undefined,
4
+ currentRoot: undefined,
5
+ wipRoot: undefined,
6
+ deletions: undefined,
7
+ wipFiber: undefined,
8
+ hookIndex: undefined,
9
9
  }
10
10
 
11
11
  const reg = /[A-Z]/g
@@ -13,6 +13,9 @@ const reg = /[A-Z]/g
13
13
  const RYUNIX_TYPES = Object.freeze({
14
14
  TEXT_ELEMENT: Symbol('text.element'),
15
15
  RYUNIX_EFFECT: Symbol('ryunix.effect'),
16
+ RYUNIX_MEMO: Symbol('ryunix.memo'),
17
+ RYUNIX_URL_QUERY: Symbol('ryunix.urlQuery'),
18
+ RYUNIX_REF: Symbol('ryunix.ref'),
16
19
  })
17
20
 
18
21
  const STRINGS = Object.freeze({
@@ -21,6 +24,7 @@ const STRINGS = Object.freeze({
21
24
  style: 'ryunix-style',
22
25
  className: 'ryunix-class',
23
26
  children: 'children',
27
+ boolean: 'boolean',
24
28
  })
25
29
 
26
30
  const OLD_STRINGS = Object.freeze({
@@ -1,91 +0,0 @@
1
- import { useStore, useEffect } from './hooks'
2
- import { createElement } from './createElement'
3
- const Router = ({ path, component, children }) => {
4
- const [currentPath, setCurrentPath] = useStore(window.location.pathname)
5
-
6
- useEffect(() => {
7
- const onLocationChange = () => {
8
- setCurrentPath(() => window.location.pathname)
9
- }
10
-
11
- window.addEventListener('navigate', onLocationChange)
12
- window.addEventListener('pushsatate', onLocationChange)
13
- window.addEventListener('popstate', onLocationChange)
14
-
15
- return () => {
16
- setTimeout(() => {
17
- window.removeEventListener('navigate', onLocationChange)
18
- window.removeEventListener('pushsatate', onLocationChange)
19
- window.removeEventListener('popstate', onLocationChange)
20
- }, 100)
21
- }
22
- }, [currentPath])
23
-
24
- return currentPath === path ? component() : null
25
- }
26
-
27
- const Navigate = () => {
28
- /**
29
- * The function `push` is used to push a new state to the browser's history and trigger a custom
30
- * event called 'pushstate'.
31
- * @param to - The `to` parameter is a string representing the URL path to which you want to
32
- * navigate.
33
- * @param [state] - The `state` parameter is an optional object that represents the state associated
34
- * with the new history entry. It can be used to store any data that you want to associate with the
35
- * new URL. When you navigate back or forward in the browser history, this state object will be
36
- * passed to the `popstate
37
- * @returns The function `push` does not have a return statement, so it returns `undefined` by
38
- * default.
39
- */
40
- const push = (to, state = {}) => {
41
- if (window.location.pathname === to) return
42
-
43
- window.history.pushState(state, '', to)
44
- const navigationEvent = new PopStateEvent('popstate')
45
- window.dispatchEvent(navigationEvent)
46
- }
47
-
48
- return { push }
49
- }
50
-
51
- const Link = (props) => {
52
- if (props.style) {
53
- throw new Error(
54
- 'The style attribute is not supported on internal components, use className.',
55
- )
56
- }
57
- if (props.to === '') {
58
- throw new Error("'to=' cannot be empty.")
59
- }
60
- if (props.className === '') {
61
- throw new Error('className cannot be empty.')
62
- }
63
- if (props.label === '' && !props.children) {
64
- throw new Error("'label=' cannot be empty.")
65
- }
66
-
67
- if (!props.to) {
68
- throw new Error("Missig 'to' param.")
69
- }
70
- const preventReload = (event) => {
71
- if (event.metaKey || event.ctrlKey) {
72
- return
73
- }
74
-
75
- event.preventDefault()
76
- const { push } = Navigate()
77
- push(props.to)
78
- }
79
-
80
- const anchor = {
81
- href: props.to,
82
- onClick: preventReload,
83
- ...props,
84
- }
85
-
86
- const children = props.label ? props.label : props.children
87
-
88
- return createElement('a', anchor, children)
89
- }
90
-
91
- export { Router, Navigate, Link }