@unsetsoft/ryunixjs 1.1.2 → 1.1.3-nightly.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
@@ -1,773 +1 @@
1
- (function (global, factory) {
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
-
7
- const vars = {
8
- containerRoot: undefined,
9
- nextUnitOfWork: undefined,
10
- currentRoot: undefined,
11
- wipRoot: undefined,
12
- deletions: undefined,
13
- wipFiber: undefined,
14
- hookIndex: undefined,
15
- };
16
-
17
- const reg = /[A-Z]/g;
18
-
19
- const RYUNIX_TYPES = Object.freeze({
20
- TEXT_ELEMENT: Symbol('text.element'),
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'),
25
- });
26
-
27
- const STRINGS = Object.freeze({
28
- object: 'object',
29
- function: 'function',
30
- style: 'ryunix-style',
31
- className: 'ryunix-class',
32
- children: 'children',
33
- boolean: 'boolean',
34
- });
35
-
36
- const OLD_STRINGS = Object.freeze({
37
- style: 'style',
38
- className: 'className',
39
- });
40
-
41
- const EFFECT_TAGS = Object.freeze({
42
- PLACEMENT: Symbol(),
43
- UPDATE: Symbol(),
44
- DELETION: Symbol(),
45
- });
46
-
47
- const Fragment = (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);
59
- }
60
- return out
61
- };
62
-
63
- /**
64
- * The function creates a new element with the given type, props, and children.
65
- * @param type - The type of the element to be created, such as "div", "span", "h1", etc.
66
- * @param props - The `props` parameter is an object that contains the properties or attributes of the
67
- * element being created. These properties can include things like `className`, `id`, `style`, and any
68
- * other custom attributes that the user wants to add to the element. The `props` object is spread
69
- * using the spread
70
- * @param children - The `children` parameter is a rest parameter that allows the function to accept
71
- * any number of arguments after the `props` parameter. These arguments will be treated as children
72
- * elements of the created element. The `map` function is used to iterate over each child and create a
73
- * new element if it is not
74
- * @returns A JavaScript object with a `type` property and a `props` property. The `type` property is
75
- * set to the `type` argument passed into the function, and the `props` property is an object that
76
- * includes any additional properties passed in the `props` argument, as well as a `children` property
77
- * that is an array of any child elements passed in the `...children` argument
78
- */
79
-
80
- const createElement = (type, props, ...children) => {
81
- children = childArray(children, []);
82
- const key =
83
- props && props.key
84
- ? props.key
85
- : `${type}-${Math.random().toString(36).substring(2, 9)}`;
86
-
87
- return {
88
- type,
89
- props: {
90
- ...props,
91
- key,
92
- children: children.map((child) =>
93
- typeof child === STRINGS.object ? child : createTextElement(child),
94
- ),
95
- },
96
- }
97
- };
98
-
99
- /**
100
- * The function creates a text element with a given text value.
101
- * @param text - The text content that will be used to create a new text element.
102
- * @returns A JavaScript object with a `type` property set to `"TEXT_ELEMENT"` and a `props` property
103
- * that contains a `nodeValue` property set to the `text` parameter and an empty `children` array.
104
- */
105
-
106
- const createTextElement = (text) => {
107
- return {
108
- type: RYUNIX_TYPES.TEXT_ELEMENT,
109
- props: {
110
- nodeValue: text,
111
- children: [],
112
- },
113
- }
114
- };
115
-
116
- /**
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.
123
- */
124
- const render = (element, container) => {
125
- vars.wipRoot = {
126
- dom: vars.containerRoot || container,
127
- props: {
128
- children: [element],
129
- },
130
- alternate: vars.currentRoot,
131
- };
132
-
133
- vars.deletions = [];
134
- vars.nextUnitOfWork = vars.wipRoot;
135
- };
136
-
137
- /**
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.
145
- */
146
- const init = (MainElement, root = '__ryunix') => {
147
- vars.containerRoot = document.getElementById(root);
148
-
149
- render(MainElement, vars.containerRoot);
150
- };
151
-
152
- /**
153
- * @description The function creates a state.
154
- * @param initial - The initial value of the state for the hook.
155
- * @returns The `useStore` function returns an array with two elements: the current state value and a
156
- * `setState` function that can be used to update the state.
157
- */
158
- const useStore = (initial) => {
159
- const oldHook =
160
- vars.wipFiber.alternate &&
161
- vars.wipFiber.alternate.hooks &&
162
- vars.wipFiber.alternate.hooks[vars.hookIndex];
163
- const hook = {
164
- state: oldHook ? oldHook.state : initial,
165
- queue: oldHook ? [...oldHook.queue] : [],
166
- };
167
-
168
- hook.queue.forEach((action) => {
169
- hook.state =
170
- typeof action === STRINGS.function ? action(hook.state) : action;
171
- });
172
-
173
- hook.queue = [];
174
-
175
- const setState = (action) => {
176
- hook.queue.push(action);
177
-
178
- vars.wipRoot = {
179
- dom: vars.currentRoot.dom,
180
- props: vars.currentRoot.props,
181
- alternate: vars.currentRoot,
182
- };
183
- vars.nextUnitOfWork = vars.wipRoot;
184
- vars.deletions = [];
185
- };
186
-
187
- if (vars.wipFiber && vars.wipFiber.hooks) {
188
- vars.wipFiber.hooks.push(hook);
189
- vars.hookIndex++;
190
- }
191
-
192
- return [hook.state, setState]
193
- };
194
-
195
- /**
196
- * This is a function that creates a hook for managing side effects in Ryunix components.
197
- * @param effect - The effect function that will be executed after the component has rendered or when
198
- * the dependencies have changed. It can perform side effects such as fetching data, updating the DOM,
199
- * or subscribing to events.
200
- * @param deps - An array of dependencies that the effect depends on. If any of the dependencies change
201
- * between renders, the effect will be re-run. If the array is empty, the effect will only run once on
202
- * mount and never again.
203
- */
204
-
205
- const useEffect = (callback, deps) => {
206
- const oldHook =
207
- vars.wipFiber.alternate &&
208
- vars.wipFiber.alternate.hooks &&
209
- vars.wipFiber.alternate.hooks[vars.hookIndex];
210
-
211
- const hook = {
212
- type: RYUNIX_TYPES.RYUNIX_EFFECT,
213
- deps,
214
- };
215
-
216
- if (!oldHook) {
217
- // invoke callback if this is the first time
218
- callback();
219
- } else {
220
- if (!lodash.isEqual(oldHook.deps, hook.deps)) {
221
- callback();
222
- }
223
- }
224
-
225
- if (vars.wipFiber.hooks) {
226
- vars.wipFiber.hooks.push(hook);
227
- vars.hookIndex++;
228
- }
229
- };
230
-
231
- /**
232
- * The `useQuery` function is a custom hook in JavaScript that retrieves query parameters from the URL
233
- * and stores them in a hook for easy access.
234
- * @returns The `useQuery` function returns the `query` property of the `hook` object.
235
- */
236
- const useQuery = () => {
237
- const oldHook =
238
- vars.wipFiber.alternate &&
239
- vars.wipFiber.alternate.hooks &&
240
- vars.wipFiber.alternate.hooks[vars.hookIndex];
241
-
242
- const hasOld = oldHook ? oldHook : undefined;
243
-
244
- const urlSearchParams = new URLSearchParams(window.location.search);
245
- const params = Object.fromEntries(urlSearchParams.entries());
246
- const Query = hasOld ? hasOld : params;
247
-
248
- const hook = {
249
- type: RYUNIX_TYPES.RYUNIX_URL_QUERY,
250
- query: Query,
251
- };
252
-
253
- if (vars.wipFiber.hooks) {
254
- vars.wipFiber.hooks.push(hook);
255
- vars.hookIndex++;
256
- }
257
-
258
- return hook.query
259
- };
260
-
261
- const useRef = (initial) => {
262
- const oldHook =
263
- vars.wipFiber.alternate &&
264
- vars.wipFiber.alternate.hooks &&
265
- vars.wipFiber.alternate.hooks[vars.hookIndex];
266
-
267
- const hook = {
268
- type: RYUNIX_TYPES.RYUNIX_REF,
269
- value: oldHook ? oldHook.value : { current: initial },
270
- };
271
-
272
- if (vars.wipFiber.hooks) {
273
- vars.wipFiber.hooks.push(hook);
274
- vars.hookIndex++;
275
- }
276
-
277
- return hook.value
278
- };
279
-
280
- const useMemo = (comp, deps) => {
281
- const oldHook =
282
- vars.wipFiber.alternate &&
283
- vars.wipFiber.alternate.hooks &&
284
- vars.wipFiber.alternate.hooks[vars.hookIndex];
285
-
286
- const hook = {
287
- type: RYUNIX_TYPES.RYUNIX_MEMO,
288
- value: null,
289
- deps,
290
- };
291
-
292
- if (oldHook) {
293
- if (lodash.isEqual(oldHook.deps, hook.deps)) {
294
- hook.value = oldHook.value;
295
- } else {
296
- hook.value = comp();
297
- }
298
- } else {
299
- hook.value = comp();
300
- }
301
-
302
- if (vars.wipFiber.hooks) {
303
- vars.wipFiber.hooks.push(hook);
304
- vars.hookIndex++;
305
- }
306
-
307
- return hook.value
308
- };
309
- const useCallback = (callback, deps) => {
310
- return useMemo(() => callback, deps)
311
- };
312
-
313
- const useRouter = (routes) => {
314
- const [location, setLocation] = useStore(window.location.pathname);
315
-
316
- const navigate = (path) => {
317
- window.history.pushState({}, '', path);
318
- setLocation(path);
319
- };
320
-
321
- useEffect(() => {
322
- const onPopState = () => {
323
- setLocation(window.location.pathname);
324
- };
325
-
326
- window.addEventListener('popstate', onPopState);
327
- return () => {
328
- window.removeEventListener('popstate', onPopState);
329
- }
330
- }, []);
331
-
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
- }
338
-
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 }
357
- };
358
-
359
- const isEvent = (key) => key.startsWith('on');
360
- const isProperty = (key) => key !== STRINGS.children && !isEvent(key);
361
- const isNew = (prev, next) => (key) => prev[key] !== next[key];
362
- const isGone = (next) => (key) => !(key in next);
363
-
364
- /**
365
- * The function cancels all effect hooks in a given fiber.
366
- * @param fiber - The "fiber" parameter is likely referring to a data structure used in React.js to
367
- * represent a component and its state. It contains information about the component's props, state, and
368
- * children, as well as metadata used by React to manage updates and rendering. The function
369
- * "cancelEffects" is likely intended
370
- */
371
- const cancelEffects = (fiber) => {
372
- if (fiber.hooks) {
373
- fiber.hooks
374
- .filter((hook) => hook.tag === RYUNIX_TYPES.RYUNIX_EFFECT && hook.cancel)
375
- .forEach((effectHook) => {
376
- effectHook.cancel();
377
- });
378
- }
379
- };
380
-
381
- /**
382
- * The function runs all effect hooks in a given fiber.
383
- * @param fiber - The "fiber" parameter is likely referring to a data structure used in the
384
- * implementation of a fiber-based reconciliation algorithm, such as the one used in React. A fiber
385
- * represents a unit of work that needs to be performed by the reconciliation algorithm, and it
386
- * contains information about a component and its children, as
387
- */
388
- const runEffects = (fiber) => {
389
- if (fiber.hooks) {
390
- fiber.hooks
391
- .filter((hook) => hook.tag === RYUNIX_TYPES.RYUNIX_EFFECT && hook.effect)
392
- .forEach((effectHook) => {
393
- effectHook.cancel = effectHook.effect();
394
- });
395
- }
396
- };
397
-
398
- /**
399
- * The function creates a new DOM element based on the given fiber object and updates its properties.
400
- * @param fiber - The fiber parameter is an object that represents a node in the fiber tree. It
401
- * contains information about the element type, props, and children of the node.
402
- * @returns The `createDom` function returns a newly created DOM element based on the `fiber` object
403
- * passed as an argument. If the `fiber` object represents a text element, a text node is created using
404
- * `document.createTextNode("")`. Otherwise, a new element is created using
405
- * `document.createElement(fiber.type)`. The function then calls the `updateDom` function to update the
406
- * properties of the newly created
407
- */
408
- const createDom = (fiber) => {
409
- const dom =
410
- fiber.type == RYUNIX_TYPES.TEXT_ELEMENT
411
- ? document.createTextNode('')
412
- : document.createElement(fiber.type);
413
-
414
- updateDom(dom, {}, fiber.props);
415
-
416
- return dom
417
- };
418
-
419
- /**
420
- * The function updates the DOM by removing old event listeners and properties, and adding new ones
421
- * based on the previous and next props.
422
- * @param dom - The DOM element that needs to be updated with new props.
423
- * @param prevProps - An object representing the previous props (properties) of a DOM element.
424
- * @param nextProps - An object containing the new props that need to be updated in the DOM.
425
- */
426
- const updateDom = (dom, prevProps, nextProps) => {
427
- Object.keys(prevProps)
428
- .filter(isEvent)
429
- .filter((key) => isGone(nextProps)(key) || isNew(prevProps, nextProps)(key))
430
- .forEach((name) => {
431
- const eventType = name.toLowerCase().substring(2);
432
- dom.removeEventListener(eventType, prevProps[name]);
433
- });
434
-
435
- Object.keys(prevProps)
436
- .filter(isProperty)
437
- .filter(isGone(nextProps))
438
- .forEach((name) => {
439
- dom[name] = '';
440
- });
441
-
442
- Object.keys(nextProps)
443
- .filter(isProperty)
444
- .filter(isNew(prevProps, nextProps))
445
- .forEach((name) => {
446
- if (name === STRINGS.style) {
447
- DomStyle(dom, nextProps['ryunix-style']);
448
- } else if (name === OLD_STRINGS.style) {
449
- DomStyle(dom, nextProps.style);
450
- } else if (name === STRINGS.className) {
451
- if (nextProps['ryunix-class'] === '') {
452
- throw new Error('data-class cannot be empty.')
453
- }
454
-
455
- prevProps['ryunix-class'] &&
456
- dom.classList.remove(...prevProps['ryunix-class'].split(/\s+/));
457
- dom.classList.add(...nextProps['ryunix-class'].split(/\s+/));
458
- } else if (name === OLD_STRINGS.className) {
459
- if (nextProps.className === '') {
460
- throw new Error('className cannot be empty.')
461
- }
462
-
463
- prevProps.className &&
464
- dom.classList.remove(...prevProps.className.split(/\s+/));
465
- dom.classList.add(...nextProps.className.split(/\s+/));
466
- } else {
467
- dom[name] = nextProps[name];
468
- }
469
- });
470
-
471
- Object.keys(nextProps)
472
- .filter(isEvent)
473
- .filter(isNew(prevProps, nextProps))
474
- .forEach((name) => {
475
- const eventType = name.toLowerCase().substring(2);
476
- dom.addEventListener(eventType, nextProps[name]);
477
- });
478
- };
479
-
480
- const DomStyle = (dom, style) => {
481
- dom.style = Object.keys(style).reduce((acc, styleName) => {
482
- const key = styleName.replace(reg, function (v) {
483
- return '-' + v.toLowerCase()
484
- });
485
- acc += `${key}: ${style[styleName]};`;
486
- return acc
487
- }, '');
488
- };
489
-
490
- var Dom = /*#__PURE__*/Object.freeze({
491
- __proto__: null,
492
- DomStyle: DomStyle,
493
- createDom: createDom,
494
- updateDom: updateDom
495
- });
496
-
497
- /**
498
- * The function commits changes made to the virtual DOM to the actual DOM.
499
- */
500
- const commitRoot = () => {
501
- vars.deletions.forEach(commitWork);
502
- if (vars.wipRoot && vars.wipRoot.child) {
503
- commitWork(vars.wipRoot.child);
504
- vars.currentRoot = vars.wipRoot;
505
- }
506
- vars.wipRoot = undefined;
507
- };
508
-
509
- /**
510
- * The function commits changes made to the DOM based on the effect tag of the fiber.
511
- * @param fiber - A fiber is a unit of work in Ryunix's reconciliation process. It represents a
512
- * component and its state at a particular point in time. The `commitWork` function takes a fiber as a
513
- * parameter to commit the changes made during the reconciliation process to the actual DOM.
514
- * @returns The function does not return anything, it performs side effects by manipulating the DOM.
515
- */
516
- const commitWork = (fiber) => {
517
- if (!fiber) return
518
-
519
- let domParentFiber = fiber.parent;
520
- while (!domParentFiber.dom) {
521
- domParentFiber = domParentFiber.parent;
522
- }
523
- const domParent = domParentFiber.dom;
524
-
525
- if (fiber.effectTag === EFFECT_TAGS.PLACEMENT) {
526
- if (fiber.dom != undefined) {
527
- domParent.appendChild(fiber.dom);
528
- }
529
- runEffects(fiber);
530
- }
531
-
532
- if (fiber.effectTag === EFFECT_TAGS.UPDATE) {
533
- cancelEffects(fiber);
534
- if (fiber.dom != undefined) {
535
- updateDom(fiber.dom, fiber.alternate.props, fiber.props);
536
- }
537
- runEffects(fiber);
538
- }
539
-
540
- if (fiber.effectTag === EFFECT_TAGS.DELETION) {
541
- commitDeletion(fiber, domParent);
542
- cancelEffects(fiber);
543
- return
544
- }
545
-
546
- commitWork(fiber.child);
547
- commitWork(fiber.sibling);
548
- };
549
-
550
- /**
551
- * The function removes a fiber's corresponding DOM node from its parent node or recursively removes
552
- * its child's DOM node until it finds a node to remove.
553
- * @param fiber - a fiber node in a fiber tree, which represents a component or an element in the Ryunix
554
- * application.
555
- * @param domParent - The parent DOM element from which the fiber's DOM element needs to be removed.
556
- */
557
- const commitDeletion = (fiber, domParent) => {
558
- if (fiber.dom) {
559
- domParent.removeChild(fiber.dom);
560
- } else {
561
- commitDeletion(fiber.child, domParent);
562
- }
563
- };
564
-
565
- var Commits = /*#__PURE__*/Object.freeze({
566
- __proto__: null,
567
- commitDeletion: commitDeletion,
568
- commitRoot: commitRoot,
569
- commitWork: commitWork
570
- });
571
-
572
- /**
573
- * This function reconciles the children of a fiber node with a new set of elements, creating new
574
- * fibers for new elements, updating existing fibers for elements with the same type, and marking old
575
- * fibers for deletion if they are not present in the new set of elements.
576
- * @param wipFiber - A work-in-progress fiber object representing a component or element in the virtual
577
- * DOM tree.
578
- * @param elements - an array of elements representing the new children to be rendered in the current
579
- * fiber's subtree
580
- */
581
- const reconcileChildren = (wipFiber, elements) => {
582
- let index = 0;
583
- let oldFiber = wipFiber.alternate && wipFiber.alternate.child;
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
- }
592
-
593
- while (index < elements.length) {
594
- const element = elements[index];
595
- const key = element.props.key || element.type;
596
- const oldFiber = oldFibersMap.get(key);
597
-
598
- let newFiber;
599
- const sameType = oldFiber && element && element.type === oldFiber.type;
600
-
601
- if (sameType) {
602
- newFiber = {
603
- type: oldFiber.type,
604
- props: element.props,
605
- dom: oldFiber.dom,
606
- parent: wipFiber,
607
- alternate: oldFiber,
608
- effectTag: EFFECT_TAGS.UPDATE,
609
- };
610
- oldFibersMap.delete(key);
611
- } else if (element) {
612
- newFiber = {
613
- type: element.type,
614
- props: element.props,
615
- dom: undefined,
616
- parent: wipFiber,
617
- alternate: undefined,
618
- effectTag: EFFECT_TAGS.PLACEMENT,
619
- };
620
- }
621
-
622
- oldFibersMap.forEach((oldFiber) => {
623
- oldFiber.effectTag = EFFECT_TAGS.DELETION;
624
- vars.deletions.push(oldFiber);
625
- });
626
-
627
- if (index === 0) {
628
- wipFiber.child = newFiber;
629
- } else if (prevSibling) {
630
- prevSibling.sibling = newFiber;
631
- }
632
-
633
- prevSibling = newFiber;
634
- index++;
635
- }
636
- };
637
-
638
- var Reconciler = /*#__PURE__*/Object.freeze({
639
- __proto__: null,
640
- reconcileChildren: reconcileChildren
641
- });
642
-
643
- /**
644
- * This function updates a function component by setting up a work-in-progress fiber, resetting the
645
- * hook index, creating an empty hooks array, rendering the component, and reconciling its children.
646
- * @param fiber - The fiber parameter is an object that represents a node in the fiber tree. It
647
- * contains information about the component, its props, state, and children. In this function, it is
648
- * used to update the state of the component and its children.
649
- */
650
- const updateFunctionComponent = (fiber) => {
651
- vars.wipFiber = fiber;
652
- vars.hookIndex = 0;
653
- vars.wipFiber.hooks = [];
654
- const children = fiber.type(fiber.props);
655
- let childArr = [];
656
- if (Array.isArray(children)) {
657
- // Fragment results returns array
658
- childArr = [...children];
659
- } else {
660
- // Normal function component returns single root node
661
- childArr = [children];
662
- }
663
- reconcileChildren(fiber, childArr);
664
- };
665
-
666
- /**
667
- * This function updates a host component's DOM element and reconciles its children.
668
- * @param fiber - A fiber is a unit of work in Ryunix that represents a component and its state. It
669
- * contains information about the component's type, props, and children, as well as pointers to other
670
- * fibers in the tree.
671
- */
672
- const updateHostComponent = (fiber) => {
673
- if (!fiber.dom) {
674
- fiber.dom = createDom(fiber);
675
- }
676
- reconcileChildren(fiber, fiber.props.children);
677
- };
678
-
679
- var Components = /*#__PURE__*/Object.freeze({
680
- __proto__: null,
681
- updateFunctionComponent: updateFunctionComponent,
682
- updateHostComponent: updateHostComponent
683
- });
684
-
685
- /**
686
- * This function uses requestIdleCallback to perform work on a fiber tree until it is complete or the
687
- * browser needs to yield to other tasks.
688
- * @param deadline - The `deadline` parameter is an object that represents the amount of time the
689
- * browser has to perform work before it needs to handle other tasks. It has a `timeRemaining()` method
690
- * that returns the amount of time remaining before the deadline is reached. The `shouldYield` variable
691
- * is used to determine
692
- */
693
- const workLoop = (deadline) => {
694
- let shouldYield = false;
695
- while (vars.nextUnitOfWork && !shouldYield) {
696
- vars.nextUnitOfWork = performUnitOfWork(vars.nextUnitOfWork);
697
- shouldYield = deadline.timeRemaining() < 1;
698
- }
699
-
700
- if (!vars.nextUnitOfWork && vars.wipRoot) {
701
- commitRoot();
702
- }
703
-
704
- requestIdleCallback(workLoop);
705
- };
706
-
707
- requestIdleCallback(workLoop);
708
-
709
- /**
710
- * The function performs a unit of work by updating either a function component or a host component and
711
- * returns the next fiber to be processed.
712
- * @param fiber - A fiber is a unit of work in Ryunix that represents a component and its state. It
713
- * contains information about the component's type, props, and children, as well as pointers to its
714
- * parent, child, and sibling fibers. The `performUnitOfWork` function takes a fiber as a parameter and
715
- * performs work
716
- * @returns The function `performUnitOfWork` returns the next fiber to be processed. If the current
717
- * fiber has a child, it returns the child. Otherwise, it looks for the next sibling of the current
718
- * fiber. If there are no more siblings, it goes up the tree to the parent and looks for the next
719
- * sibling of the parent. The function returns `undefined` if there are no more fibers to process.
720
- */
721
- const performUnitOfWork = (fiber) => {
722
- const isFunctionComponent = fiber.type instanceof Function;
723
- if (isFunctionComponent) {
724
- updateFunctionComponent(fiber);
725
- } else {
726
- updateHostComponent(fiber);
727
- }
728
- if (fiber.child) {
729
- return fiber.child
730
- }
731
- let nextFiber = fiber;
732
- while (nextFiber) {
733
- if (nextFiber.sibling) {
734
- return nextFiber.sibling
735
- }
736
- nextFiber = nextFiber.parent;
737
- }
738
- return undefined
739
- };
740
-
741
- var Workers = /*#__PURE__*/Object.freeze({
742
- __proto__: null,
743
- performUnitOfWork: performUnitOfWork,
744
- workLoop: workLoop
745
- });
746
-
747
- var Ryunix = {
748
- createElement,
749
- render,
750
- init,
751
- Fragment,
752
- Dom,
753
- Workers,
754
- Reconciler,
755
- Components,
756
- Commits,
757
- };
758
-
759
- window.Ryunix = Ryunix;
760
-
761
- exports.Fragment = Fragment;
762
- exports.default = Ryunix;
763
- exports.useCallback = useCallback;
764
- exports.useEffect = useEffect;
765
- exports.useMemo = useMemo;
766
- exports.useQuery = useQuery;
767
- exports.useRef = useRef;
768
- exports.useRouter = useRouter;
769
- exports.useStore = useStore;
770
-
771
- Object.defineProperty(exports, '__esModule', { value: true });
772
-
773
- }));
1
+ !function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports,require("lodash")):"function"==typeof define&&define.amd?define(["exports","lodash"],t):t((e="undefined"!=typeof globalThis?globalThis:e||self).Ryunix={},e.lodash)}(this,(function(e,t){"use strict";const o={containerRoot:void 0,nextUnitOfWork:void 0,currentRoot:void 0,wipRoot:void 0,deletions:void 0,wipFiber:void 0,hookIndex:void 0},r=/[A-Z]/g,n=Object.freeze({TEXT_ELEMENT:Symbol("text.element"),RYUNIX_EFFECT:Symbol("ryunix.effect"),RYUNIX_MEMO:Symbol("ryunix.memo"),RYUNIX_URL_QUERY:Symbol("ryunix.urlQuery"),RYUNIX_REF:Symbol("ryunix.ref")}),i=Object.freeze({object:"object",function:"function",style:"ryunix-style",className:"ryunix-class",children:"children",boolean:"boolean"}),s=Object.freeze({style:"style",className:"className"}),a=Object.freeze({PLACEMENT:Symbol(),UPDATE:Symbol(),DELETION:Symbol()}),l=e=>e.children,c=(e,t)=>(t=t||[],null==e||typeof e==i.boolean||(Array.isArray(e)?e.some((e=>{c(e,t)})):t.push(e)),t),p=(e,t,...o)=>{o=c(o,[]);const r=t&&t.key?t.key:`${e}-${Math.random().toString(36).substring(2,9)}`;return{type:e,props:{...t,key:r,children:o.map((e=>typeof e===i.object?e:u(e)))}}},u=e=>({type:n.TEXT_ELEMENT,props:{nodeValue:e,children:[]}}),d=(e,t)=>{o.wipRoot={dom:o.containerRoot||t,props:{children:[e]},alternate:o.currentRoot},o.deletions=[],o.nextUnitOfWork=o.wipRoot},f=e=>{const t=o.wipFiber.alternate&&o.wipFiber.alternate.hooks&&o.wipFiber.alternate.hooks[o.hookIndex],r={state:t?t.state:e,queue:t?[...t.queue]:[]};r.queue.forEach((e=>{r.state=typeof e===i.function?e(r.state):e})),r.queue=[];return o.wipFiber&&o.wipFiber.hooks&&(o.wipFiber.hooks.push(r),o.hookIndex++),[r.state,e=>{r.queue.push(e),o.wipRoot={dom:o.currentRoot.dom,props:o.currentRoot.props,alternate:o.currentRoot},o.nextUnitOfWork=o.wipRoot,o.deletions=[]}]},m=(e,r)=>{const i=o.wipFiber.alternate&&o.wipFiber.alternate.hooks&&o.wipFiber.alternate.hooks[o.hookIndex],s={type:n.RYUNIX_EFFECT,deps:r};i&&t.isEqual(i.deps,s.deps)||e(),o.wipFiber.hooks&&(o.wipFiber.hooks.push(s),o.hookIndex++)},h=(e,r)=>{const i=o.wipFiber.alternate&&o.wipFiber.alternate.hooks&&o.wipFiber.alternate.hooks[o.hookIndex],s={type:n.RYUNIX_MEMO,value:null,deps:r};return i&&t.isEqual(i.deps,s.deps)?s.value=i.value:s.value=e(),o.wipFiber.hooks&&(o.wipFiber.hooks.push(s),o.hookIndex++),s.value},y=()=>{const e=new URLSearchParams(window.location.search),t={};for(let[o,r]of e.entries())t[o]=r;return t},b=e=>e.startsWith("on"),E=e=>e!==i.children&&!b(e),w=(e,t)=>o=>e[o]!==t[o],k=e=>t=>!(t in e),R=e=>{e.hooks&&e.hooks.filter((e=>e.tag===n.RYUNIX_EFFECT&&e.cancel)).forEach((e=>{e.cancel()}))},F=e=>{e.hooks&&e.hooks.filter((e=>e.tag===n.RYUNIX_EFFECT&&e.effect)).forEach((e=>{e.cancel=e.effect()}))},x=e=>{const t=e.type==n.TEXT_ELEMENT?document.createTextNode(""):document.createElement(e.type);return _(t,{},e.props),t},_=(e,t,o)=>{Object.keys(t).filter(b).filter((e=>k(o)(e)||w(t,o)(e))).forEach((o=>{const r=o.toLowerCase().substring(2);e.removeEventListener(r,t[o])})),Object.keys(t).filter(E).filter(k(o)).forEach((t=>{e[t]=""})),Object.keys(o).filter(E).filter(w(t,o)).forEach((r=>{if(r===i.style)v(e,o["ryunix-style"]);else if(r===s.style)v(e,o.style);else if(r===i.className){if(""===o["ryunix-class"])throw new Error("data-class cannot be empty.");t["ryunix-class"]&&e.classList.remove(...t["ryunix-class"].split(/\s+/)),e.classList.add(...o["ryunix-class"].split(/\s+/))}else if(r===s.className){if(""===o.className)throw new Error("className cannot be empty.");t.className&&e.classList.remove(...t.className.split(/\s+/)),e.classList.add(...o.className.split(/\s+/))}else e[r]=o[r]})),Object.keys(o).filter(b).filter(w(t,o)).forEach((t=>{const r=t.toLowerCase().substring(2);e.addEventListener(r,o[t])}))},v=(e,t)=>{e.style=Object.keys(t).reduce(((e,o)=>e+=`${o.replace(r,(function(e){return"-"+e.toLowerCase()}))}: ${t[o]};`),"")};var N=Object.freeze({__proto__:null,DomStyle:v,createDom:x,updateDom:_});const g=()=>{o.deletions.forEach(T),o.wipRoot&&o.wipRoot.child&&(T(o.wipRoot.child),o.currentRoot=o.wipRoot),o.wipRoot=void 0},T=e=>{if(!e)return;let t=e.parent;for(;!t.dom;)t=t.parent;const o=t.dom;if(e.effectTag===a.PLACEMENT&&(null!=e.dom&&o.appendChild(e.dom),F(e)),e.effectTag===a.UPDATE&&(R(e),null!=e.dom&&_(e.dom,e.alternate.props,e.props),F(e)),e.effectTag===a.DELETION)return O(e,o),void R(e);T(e.child),T(e.sibling)},O=(e,t)=>{e.dom?t.removeChild(e.dom):O(e.child,t)};var I=Object.freeze({__proto__:null,commitDeletion:O,commitRoot:g,commitWork:T});const L=(e,t)=>{let r=0,n=e.alternate&&e.alternate.child,i=null;const s=new Map;for(;n;){const e=n.props.key||n.type;s.set(e,n),n=n.sibling}for(;r<t.length;){const n=t[r],l=n.props.key||n.type,c=s.get(l);let p;c&&n&&n.type===c.type?(p={type:c.type,props:n.props,dom:c.dom,parent:e,alternate:c,effectTag:a.UPDATE},s.delete(l)):n&&(p={type:n.type,props:n.props,dom:void 0,parent:e,alternate:void 0,effectTag:a.PLACEMENT}),s.forEach((e=>{e.effectTag=a.DELETION,o.deletions.push(e)})),0===r?e.child=p:i&&(i.sibling=p),i=p,r++}};var U=Object.freeze({__proto__:null,reconcileChildren:L});const C=e=>{o.wipFiber=e,o.hookIndex=0,o.wipFiber.hooks=[];const t=e.type(e.props);let r=[];r=Array.isArray(t)?[...t]:[t],L(e,r)},j=e=>{e.dom||(e.dom=x(e)),L(e,e.props.children)};var M=Object.freeze({__proto__:null,updateFunctionComponent:C,updateHostComponent:j});const S=e=>{let t=!1;for(;o.nextUnitOfWork&&!t;)o.nextUnitOfWork=D(o.nextUnitOfWork),t=e.timeRemaining()<1;!o.nextUnitOfWork&&o.wipRoot&&g(),requestIdleCallback(S)};requestIdleCallback(S);const D=e=>{if(e.type instanceof Function?C(e):j(e),e.child)return e.child;let t=e;for(;t;){if(t.sibling)return t.sibling;t=t.parent}};var X={createElement:p,render:d,init:(e,t="__ryunix")=>{o.containerRoot=document.getElementById(t),d(e,o.containerRoot)},Fragment:l,Dom:N,Workers:Object.freeze({__proto__:null,performUnitOfWork:D,workLoop:S}),Reconciler:U,Components:M,Commits:I};window.Ryunix=X,e.Fragment=l,e.default=X,e.useCallback=(e,t)=>h((()=>e),t),e.useEffect=m,e.useMemo=h,e.useQuery=y,e.useRef=e=>{const t=o.wipFiber.alternate&&o.wipFiber.alternate.hooks&&o.wipFiber.alternate.hooks[o.hookIndex],r={type:n.RYUNIX_REF,value:t?t.value:{current:e}};return o.wipFiber.hooks&&(o.wipFiber.hooks.push(r),o.hookIndex++),r.value},e.useRouter=e=>{const[t,o]=f(window.location.pathname),r=e=>{window.history.pushState({},"",e),n(e)},n=e=>{o(e.split("?")[0])};m((()=>{const e=()=>n(window.location.pathname);return window.addEventListener("popstate",e),()=>window.removeEventListener("popstate",e)}),[]);const i=((e,t)=>{const o=t.split("?")[0];for(const{path:t,component:r}of e){if(!t)continue;const e=[],n=new RegExp(`^${t.replace(/:\w+/g,(()=>(e.push(RegExp.$_),"([^/]+)")))}$`),i=o.match(n);if(i){return{route:{component:r},params:e.reduce(((e,t,o)=>({...e,[t.substring(1)]:i[o+1]})),{})}}}const r=e.find((e=>e.NotFound));return r?{route:{component:r.NotFound},params:{}}:{route:{component:null},params:{}}})(e,t);return{Children:()=>{const e=y();return i.route.component?i.route.component({params:i.params,query:e}):null},NavLink:({to:e,...t})=>p("a",{href:e,onClick:t=>{t.preventDefault(),r(e)},...t},t.children),navigate:r}},e.useStore=f,Object.defineProperty(e,"__esModule",{value:!0})}));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@unsetsoft/ryunixjs",
3
- "version": "1.1.2",
3
+ "version": "1.1.3-nightly.2",
4
4
  "license": "MIT",
5
5
  "main": "./dist/Ryunix.js",
6
6
  "types": "./dist/Ryunix.d.ts",
@@ -10,7 +10,7 @@
10
10
  },
11
11
  "homepage": "https://github.com/UnSetSoft/Ryunixjs#readme",
12
12
  "scripts": {
13
- "build:js": "rollup ./src/main.js --file ./dist/Ryunix.js --format umd --name Ryunix",
13
+ "build:js": "rollup ./src/main.js --file ./dist/Ryunix.js --format umd --name Ryunix --plugin @rollup/plugin-terser",
14
14
  "prepublishOnly": "npm run build:js",
15
15
  "postinstall": "npm run build:js",
16
16
  "nightly:release": "npm publish --tag nightly",
@@ -18,6 +18,7 @@
18
18
  "lint": "eslint . --ext .ts --fix --max-warnings=0 --config .eslintrc.js --no-eslintrc"
19
19
  },
20
20
  "dependencies": {
21
+ "@rollup/plugin-terser": "^0.4.4",
21
22
  "eslint": "8.56.0",
22
23
  "lodash": "^4.17.20",
23
24
  "rollup": "4.9.2"
package/src/lib/hooks.js CHANGED
@@ -80,36 +80,6 @@ const useEffect = (callback, deps) => {
80
80
  }
81
81
  }
82
82
 
83
- /**
84
- * The `useQuery` function is a custom hook in JavaScript that retrieves query parameters from the URL
85
- * and stores them in a hook for easy access.
86
- * @returns The `useQuery` function returns the `query` property of the `hook` object.
87
- */
88
- const useQuery = () => {
89
- const oldHook =
90
- vars.wipFiber.alternate &&
91
- vars.wipFiber.alternate.hooks &&
92
- vars.wipFiber.alternate.hooks[vars.hookIndex]
93
-
94
- const hasOld = oldHook ? oldHook : undefined
95
-
96
- const urlSearchParams = new URLSearchParams(window.location.search)
97
- const params = Object.fromEntries(urlSearchParams.entries())
98
- const Query = hasOld ? hasOld : params
99
-
100
- const hook = {
101
- type: RYUNIX_TYPES.RYUNIX_URL_QUERY,
102
- query: Query,
103
- }
104
-
105
- if (vars.wipFiber.hooks) {
106
- vars.wipFiber.hooks.push(hook)
107
- vars.hookIndex++
108
- }
109
-
110
- return hook.query
111
- }
112
-
113
83
  const useRef = (initial) => {
114
84
  const oldHook =
115
85
  vars.wipFiber.alternate &&
@@ -158,54 +128,152 @@ const useMemo = (comp, deps) => {
158
128
 
159
129
  return hook.value
160
130
  }
131
+
161
132
  const useCallback = (callback, deps) => {
162
133
  return useMemo(() => callback, deps)
163
134
  }
164
135
 
136
+ /**
137
+ * The `useQuery` function parses the query parameters from the URL and returns them as an object.
138
+ * @returns An object containing key-value pairs of the query parameters from the URLSearchParams in
139
+ * the current window's URL is being returned.
140
+ */
141
+ const useQuery = () => {
142
+ const searchParams = new URLSearchParams(window.location.search)
143
+ const query = {}
144
+ for (let [key, value] of searchParams.entries()) {
145
+ query[key] = value
146
+ }
147
+ return query
148
+ }
149
+
150
+ /**
151
+ * useRouter is a routing function to manage navigation and route matching.
152
+ *
153
+ * This function handles client-side routing, URL updates, and component rendering based on defined routes.
154
+ * It supports dynamic routes (e.g., "/user/:id") and allows navigation using links.
155
+ *
156
+ * @param {Array} routes - An array of route objects, each containing:
157
+ * - `path` (string): The URL path to match (supports dynamic segments like "/user/:id").
158
+ * - `component` (function): The component to render when the route matches.
159
+ * - `NotFound` (optional function): Component to render for unmatched routes (default 404 behavior).
160
+ *
161
+ * @returns {Object} - An object with:
162
+ * - `Children` (function): Returns the component that matches the current route, passing route parameters and query parameters as props.
163
+ * - `NavLink` (component): A link component to navigate within the application without refreshing the page.
164
+ *
165
+ * @example
166
+ * // Define routes
167
+ * const routes = [
168
+ * {
169
+ * path: "/",
170
+ * component: HomePage,
171
+ * },
172
+ * {
173
+ * path: "/user/:id",
174
+ * component: UserProfile,
175
+ * },
176
+ * {
177
+ * path: "*",
178
+ * NotFound: NotFoundPage,
179
+ * },
180
+ * ];
181
+ *
182
+ * // Use the routing function
183
+ * const { Children, NavLink } = useRouter(routes);
184
+ *
185
+ * // Render the matched component
186
+ * const App = () => (
187
+ * <div>
188
+ * <NavLink to="/">Home</NavLink>
189
+ * <NavLink to="/user/123">User Profile</NavLink>
190
+ * <Children />
191
+ * </div>
192
+ * );
193
+ *
194
+ * // Example: UserProfile Component that receives route parameters
195
+ * const UserProfile = ({ params, query }) => {
196
+ * return (
197
+ * <div>
198
+ * <h1>User ID: {params.id}</h1>
199
+ * <p>Query Parameters: {JSON.stringify(query)}</p>
200
+ * </div>
201
+ * );
202
+ * };
203
+ */
204
+
165
205
  const useRouter = (routes) => {
166
206
  const [location, setLocation] = useStore(window.location.pathname)
167
207
 
168
208
  const navigate = (path) => {
169
209
  window.history.pushState({}, '', path)
170
- setLocation(path)
210
+ updateRoute(path)
171
211
  }
172
212
 
173
- useEffect(() => {
174
- const onPopState = () => {
175
- setLocation(window.location.pathname)
176
- }
213
+ const updateRoute = (path) => {
214
+ setLocation(path.split('?')[0])
215
+ }
177
216
 
217
+ useEffect(() => {
218
+ const onPopState = () => updateRoute(window.location.pathname)
178
219
  window.addEventListener('popstate', onPopState)
179
- return () => {
180
- window.removeEventListener('popstate', onPopState)
181
- }
220
+ return () => window.removeEventListener('popstate', onPopState)
182
221
  }, [])
183
222
 
184
- let currentRoute = routes.find((route) => route.path === location)
185
- if (!currentRoute) {
186
- currentRoute = {
187
- component: routes.find((route) => route.NotFound)?.NotFound || null,
223
+ const findCurrentRoute = (routes, path) => {
224
+ const pathname = path.split('?')[0]
225
+
226
+ for (const { path: routePath, component } of routes) {
227
+ if (!routePath) continue
228
+
229
+ const keys = []
230
+ const pattern = new RegExp(
231
+ `^${routePath.replace(/:\w+/g, () => (keys.push(RegExp.$_), '([^/]+)'))}$`,
232
+ )
233
+
234
+ const match = pathname.match(pattern)
235
+ if (match) {
236
+ const params = keys.reduce(
237
+ (acc, key, index) => ({
238
+ ...acc,
239
+ [key.substring(1)]: match[index + 1],
240
+ }),
241
+ {},
242
+ )
243
+ return { route: { component }, params }
244
+ }
188
245
  }
246
+
247
+ const notFoundRoute = routes.find((route) => route.NotFound)
248
+ return notFoundRoute
249
+ ? { route: { component: notFoundRoute.NotFound }, params: {} }
250
+ : { route: { component: null }, params: {} }
189
251
  }
190
252
 
191
- const Children = () =>
192
- currentRoute.component ? currentRoute.component : null
253
+ const currentRouteData = findCurrentRoute(routes, location)
254
+ const Children = () => {
255
+ const query = useQuery()
256
+ return currentRouteData.route.component
257
+ ? currentRouteData.route.component({
258
+ params: currentRouteData.params,
259
+ query,
260
+ })
261
+ : null
262
+ }
193
263
 
194
264
  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,
265
+ const handleClick = (e) => {
266
+ e.preventDefault()
267
+ navigate(to)
204
268
  }
205
- return createElement('a', NewProps, children)
269
+ return createElement(
270
+ 'a',
271
+ { href: to, onClick: handleClick, ...props },
272
+ props.children,
273
+ )
206
274
  }
207
275
 
208
- return { Children, navigate, NavLink }
276
+ return { Children, NavLink, navigate }
209
277
  }
210
278
 
211
279
  export {