vaderjs 1.1.7 → 1.1.8

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.
@@ -0,0 +1,5 @@
1
+ {
2
+ "className":{
3
+
4
+ }
5
+ }
package/index.js CHANGED
@@ -5,7 +5,7 @@
5
5
  * @version 1.1.5
6
6
  *
7
7
  */
8
- import { Vader } from "./vader.js";
8
+ import Vader from "./vader.js";
9
9
  // @ts-ignore
10
10
  import { VaderRouter } from "./vader-router.js";
11
11
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vaderjs",
3
- "version": "1.1.7",
3
+ "version": "1.1.8",
4
4
  "description": "A Reactive Framework for Single-Page Applications (SPA)",
5
5
  "main": "index.js",
6
6
  "scripts": {
package/vader.js CHANGED
@@ -1,55 +1,39 @@
1
-
2
1
  let dom = /**@type {Obect} **/ {};
3
2
  /**
4
3
  * @function useRef
5
4
  * @description Allows you to get reference to DOM element
6
5
  * @param {String} ref
7
- * @returns {Object} {current}
6
+ * @returns {void | Object} {current, update}
8
7
  */
9
- export const useRef = (ref /** @type {string} */) => {
10
- // Try to get the existing DOM element from window.dom
11
- /**@type {object }*/
12
- let el = dom[ref] || document.querySelector(`[ref="${ref}"]`);
8
+ export const useRef = (ref) => {
9
+ const element = document.querySelector(`[ref="${ref}"]`);
10
+ const getElement = () => element;
13
11
 
14
- // Function to update the DOM element with new HTML content
15
- /**
16
- * @function update
17
- * @description Allows you to update the DOM element with new HTML content
18
- * @param {String} data
19
- */
20
12
  const update = (data) => {
21
- // Parse the new HTML data
22
13
  const newDom = new DOMParser().parseFromString(data, "text/html");
23
-
24
-
25
- const newHtml = newDom.body.firstChild;
14
+ const newElement = newDom.body.firstChild;
26
15
 
27
- if (el) {
28
- // If the element already exists, update it
29
- const isDifferent = !newHtml.isEqualNode(el);
16
+ if (element) {
17
+ // @ts-ignore
18
+ const isDifferent = !newElement.isEqualNode(element);
30
19
  if (isDifferent) {
31
- const newElement = newHtml.cloneNode(true);
32
- // Replace the old element with the new one
33
- el.parentNode.replaceChild(newElement, el);
34
- dom[ref] = newElement;
20
+ // @ts-ignore
21
+ element.parentNode.replaceChild(newElement, element);
35
22
  }
36
- } else {
37
- // If the element doesn't exist, create it
38
- el = newHtml.cloneNode(true);
39
- dom[ref] = el;
40
23
  }
41
24
  };
42
25
 
43
26
  return {
44
- current: el,
27
+ current: getElement(),
45
28
  update,
46
29
  };
47
30
  };
31
+
48
32
  let components = [];
49
33
  /**
50
34
  * @class Component
51
35
  * @description Allows you to create a component
52
- * @returns {null}
36
+ * @returns {void}
53
37
  * @example
54
38
  * import { Vader } from "../../dist/vader/index.js";
55
39
  * export class Home extends Vader.Component {
@@ -70,7 +54,6 @@ export class Component {
70
54
  this.states = {};
71
55
  //@ts-ignore
72
56
  this.name = this.constructor.name;
73
- console.log(this.name);
74
57
  this.executedEffects = {};
75
58
  this.storedProps = {};
76
59
  this.componentMounted = false;
@@ -80,8 +63,8 @@ export class Component {
80
63
  * @property {Array} $_signal_subscribers_ran
81
64
  * @description Allows you to keep track of signal subscribers
82
65
  * @private
83
- */
84
- this.$_signal_subscribers_ran = [];
66
+ */
67
+ this.$_signal_subscribers_ran = [];
85
68
  this.effects = {};
86
69
  this.$_useStore_subscribers = [];
87
70
  this.init();
@@ -92,42 +75,22 @@ export class Component {
92
75
  state: null,
93
76
  },
94
77
  });
78
+ this.snapshots = [];
95
79
  }
96
80
 
97
81
  init() {
98
- //@ts-ignore
99
82
  this.registerComponent();
100
- //@ts-ignore
101
- window.states = this.states;
102
- //@ts-ignore
103
- window.useState = this.useState;
104
- //@ts-ignore
105
- window.setState = this.setState;
106
- //@ts-ignore
107
- window.useEffect = this.useEffect;
108
- //@ts-ignore
109
- window.useAuth = this.useAuth;
110
- //@ts-ignore
111
- window.useSyncStore = this.useSyncStore;
112
- //@ts-ignore
113
- window.useReducer = this.useReducer;
114
- //@ts-ignore
115
- window.runEffects = this.runEffects;
116
- //@ts-ignore
117
- window.rf = this.rf;
118
- //@ts-ignore
119
- window.signal = this.signal;
120
83
  }
121
84
 
122
85
  registerComponent() {
123
86
  components.push(this);
124
87
  }
125
-
88
+
126
89
  /**
127
90
  * @method setState
128
91
  * @description Allows you to set state
129
92
  * @param {String} key
130
- * @param {*} value
93
+ * @param {*} value
131
94
  * @returns {void}
132
95
  * @example
133
96
  * this.setState('count', 1)
@@ -136,13 +99,51 @@ export class Component {
136
99
  this.states[key] = value;
137
100
  this.updateComponent();
138
101
  }
102
+ /**
103
+ * @method componentUnmount
104
+ * @description Allows you to run code after component has unmounted
105
+ * @type {VoidFunction}
106
+ * @returns {void}
107
+ */
108
+ unmount() {
109
+ this.componentMounted = false;
110
+ this.componentWillUnmount();
111
+ // @ts-ignore
112
+ document.querySelector(`[data-component="${this.name}"]`).remove();
113
+ if (!document.querySelector(`[data-component="${this.name}"]`)) {
114
+ components = components.filter(
115
+ (component) => component.name !== this.name
116
+ );
117
+ }
118
+ }
139
119
 
140
- componentUpdate() {}
120
+ /**
121
+ * @method componentUpdate
122
+ * @description Allows you to run code after component has updated
123
+ * @param {Object} prev_state
124
+ * @param {Object} prev_props
125
+ * @param {Object} snapshot
126
+ * @returns {void}
127
+ * @example
128
+ * componentUpdate(prev_state, prev_props, snapshot) {
129
+ * console.log(prev_state, prev_props, snapshot)
130
+ * }
131
+ * */
132
+ componentUpdate(prev_state, prev_props, snapshot) {}
141
133
  /**
142
134
  * @method componentDidMount
143
135
  * @description Allows you to run code after component has mounted
144
136
  */
145
137
  componentDidMount() {}
138
+
139
+ /**
140
+ * @method componentWillUnmount
141
+ * @description Allows you to run code before component unmounts
142
+ * @type {VoidFunction}
143
+ * @returns {void}
144
+ */
145
+ componentWillUnmount() {}
146
+
146
147
  /**
147
148
  * @method signal
148
149
  * @description Allows you to create a signal
@@ -227,6 +228,7 @@ export class Component {
227
228
  };
228
229
  this.$_signal_call = () => {
229
230
  hasCaller = true;
231
+ // @ts-ignore
230
232
  this.$_signal_dispatch();
231
233
  };
232
234
  /**
@@ -305,8 +307,8 @@ export class Component {
305
307
  * auth.can('create') // true
306
308
  */
307
309
  useAuth(options) {
308
- /**@type {Array}**/
309
- let rules = options.rulesets;
310
+ /**@type {Array}**/
311
+ let rules = options.rulesets;
310
312
  if (!rules) {
311
313
  throw new Error("No rulesets provided");
312
314
  }
@@ -336,8 +338,8 @@ export class Component {
336
338
  /**
337
339
  * @function canWithRole
338
340
  * @param {String} action
339
- * @param {String} role
340
- * @returns
341
+ * @param {String} role
342
+ * @returns
341
343
  */
342
344
  canWithRole: (action, role) => {
343
345
  return auth.can(action) && auth.hasRole(role);
@@ -388,12 +390,22 @@ export class Component {
388
390
  * setCount({type: 'increment'})
389
391
  */
390
392
  useReducer(key, reducer, initialState) {
391
- const [state, setState] = this.useState(key, initialState);
392
- const dispatch = (action) => {
393
- const newState = reducer(state, action);
394
- setState(newState);
395
- };
396
- return [state, dispatch];
393
+ if (!this.states[key]) {
394
+ this.states[key] = initialState;
395
+ }
396
+ return [
397
+ this.states[key],
398
+ /**
399
+ * @function dispatch
400
+ * @description Allows you to dispatch a reducer
401
+ * @param {*} action
402
+ * @returns {void}
403
+ */
404
+ (action) => {
405
+ this.states[key] = reducer(this.states[key], action);
406
+ this.updateComponent();
407
+ },
408
+ ];
397
409
  }
398
410
  runEffects() {
399
411
  Object.keys(this.effects).forEach((component) => {
@@ -408,28 +420,28 @@ export class Component {
408
420
  /**
409
421
  * @method useSyncStore
410
422
  * @description Allows you to create a store
411
- * @param {*} storeName
412
- * @param {*} initialState
423
+ * @param {String} storeName
424
+ * @param {*} initialState
413
425
  * @returns {Object} {getField, setField, subscribe, clear}
414
426
  * @example
415
427
  * let store = this.useSyncStore('store', {count: 0})
416
428
  * store.setField('count', 1)
417
429
  * store.getField('count') // 1
418
- *
430
+ *
419
431
  */
420
432
  useSyncStore(storeName, initialState) {
421
433
  let [storedState, setStoredState] = this.useState(
422
434
  storeName,
423
435
  initialState ||
424
- // @ts-ignore
436
+ // @ts-ignore
425
437
  localStorage.getItem(`$_vader_${storeName}`, (s) => {
426
-
427
438
  localStorage.setItem(`$_vader_${storeName}`, JSON.stringify(s));
428
439
  this.$_useStore_subscribers.forEach((subscriber) => {
429
440
  subscriber(s);
430
441
  });
431
442
  }) ||
432
- {}
443
+ {},
444
+ () => {}
433
445
  );
434
446
 
435
447
  const getField = (fieldName) => {
@@ -460,7 +472,7 @@ export class Component {
460
472
  * @description Allows you to create a state
461
473
  * @param {String} key
462
474
  * @param {*} initialValue
463
- * @param {*} callback
475
+ * @param {Function} callback
464
476
  * @url - useState works similarly to - https://react.dev/reference/react/useState
465
477
  * @returns {Array} [state, setState]
466
478
  * @example
@@ -476,6 +488,12 @@ export class Component {
476
488
  }
477
489
  return [
478
490
  this.states[key],
491
+ /**
492
+ * @function setState
493
+ * @description Allows you to set state
494
+ * @param {*} value
495
+ * @returns {void}
496
+ */
479
497
  (value) => {
480
498
  this.states[key] = value;
481
499
  this.updateComponent();
@@ -511,13 +529,13 @@ export class Component {
511
529
  this.hasMounted = true;
512
530
  }
513
531
 
514
- return{
532
+ return {
515
533
  cleanup: () => {
516
534
  this.effects[this.name] = this.effects[this.name].filter(
517
535
  (effect) => effect !== effectFn
518
536
  );
519
537
  },
520
- }
538
+ };
521
539
  }
522
540
  /**
523
541
  * @method $Function
@@ -536,7 +554,7 @@ export class Component {
536
554
  }
537
555
  let name = fn.name;
538
556
  if (!name) {
539
- name = "anonymous";
557
+ name = "anonymous" + Math.floor(Math.random() * 100000000000000000);
540
558
  }
541
559
  window[name] = fn;
542
560
  // @ts-ignore
@@ -546,12 +564,29 @@ export class Component {
546
564
  // Add other methods like render, useEffect, useReducer, useAuth, etc.
547
565
 
548
566
  updateComponent() {
567
+ const fragment = document.createDocumentFragment();
549
568
  Object.keys(components).forEach(async (component) => {
550
569
  const { name } = components[component];
551
570
  const componentContainer = document.querySelector(
552
571
  `[data-component="${name}"]`
553
572
  );
554
573
 
574
+ let time = new Date().getTime().toLocaleString();
575
+ /**
576
+ * @property {Object} snapshot
577
+ * @description Allows you to keep track of component snapshots
578
+ * @private
579
+ * @returns {Object} {name, time, prev_state, prev_props, content}
580
+ */
581
+ let snapshot = {
582
+ name: name,
583
+ time: time,
584
+ prev_state: this.states,
585
+ prev_props: this.storedProps,
586
+ // @ts-ignore
587
+ content: componentContainer.innerHTML,
588
+ };
589
+
555
590
  if (!componentContainer) return;
556
591
  const newHtml = await new Function(
557
592
  "useState",
@@ -573,14 +608,92 @@ export class Component {
573
608
  this.signal,
574
609
  this.render
575
610
  );
576
- this.componentDidMount();
577
611
 
578
- if (newHtml && newHtml !== componentContainer.innerHTML) {
579
- componentContainer.outerHTML = newHtml;
612
+ if (newHtml !== componentContainer.innerHTML) {
613
+ if (this.snapshots.length > 0) {
614
+ let lastSnapshot = this.snapshots[this.snapshots.length - 1];
615
+ if (lastSnapshot !== snapshot) {
616
+ this.snapshots.push(snapshot);
617
+ }
618
+ } else {
619
+ this.snapshots.push(snapshot);
620
+ }
621
+ this.componentUpdate(
622
+ snapshot.prev_state,
623
+ snapshot.prev_props,
624
+ snapshot.content
625
+ );
626
+ // batch updates
627
+ fragment.appendChild(
628
+ document.createRange().createContextualFragment(newHtml)
629
+ );
630
+ componentContainer.innerHTML = "";
631
+ componentContainer.appendChild(fragment);
632
+ this.runEffects();
580
633
  }
581
634
  });
582
635
  }
636
+ /**
637
+ * @method validateClassName
638
+ * @param {String} className
639
+ * @private
640
+ * @returns {Boolean}
641
+ */
642
+ validateClassName(className) {
643
+ // validate classNames ensure they are camelCase but also allow for - and _
644
+ return /^[a-zA-Z0-9-_]+$/.test(className);
645
+ }
646
+
647
+ /**
648
+ * The `html` method generates and processes HTML content for a component, performing various validations and tasks.
649
+ *
650
+ * @param {String} strings - The HTML content to be processed.
651
+ * @param {...any} args - Dynamic values to be inserted into the template.
652
+ * @returns {string} - The processed HTML content as a string.
653
+ *
654
+ * @throws {SyntaxError} - Throws a `SyntaxError` if image-related attributes are missing or invalid.
655
+ * @throws {Error} - Throws an `Error` if there are issues with class names or relative paths.
656
+ *
657
+ * @example
658
+ * // Example usage within a component:
659
+ * const myComponent = new Component();
660
+ * const htmlContent = myComponent.html`
661
+ * <div>
662
+ * <img src="/images/example.jpg" alt="Example Image" />
663
+ * </div>
664
+ * `;
665
+ * document.body.innerHTML = htmlContent;
666
+ *
667
+ * @remarks
668
+ * The `html` method is a core function used in component rendering. It allows you to define and generate HTML content within your component while enforcing best practices and accessibility standards. The method performs several essential tasks:
669
+ *
670
+ * 1. **Image Validation**: It checks images for the presence of 'alt' attributes and their validity.
671
+ * - Throws a `SyntaxError` if an image is missing the 'alt' attribute.
672
+ * - Throws a `SyntaxError` if the 'alt' attribute is empty.
673
+ * - Checks for an 'aria-hidden' attribute for image elements.
674
+ *
675
+ * 2. **Class Attribute Handling**: It enforces class attribute usage and allows optional configuration via comments.
676
+ * - Throws an `Error` if 'class' attributes are used without permission.
677
+ * - Supports 'className' attributes for class definitions.
678
+ * - Allows or disallows class-related comments based on your configuration.
679
+ *
680
+ * 3. **Relative Path Handling**: It processes relative paths in 'href' and 'src' attributes, ensuring proper routing.
681
+ * - Converts relative 'href' attributes to anchor links with appropriate routing.
682
+ * - Converts relative 'src' attributes to absolute paths with 'public' directories.
683
+ *
684
+ * 4. **Custom Component Attributes**: It supports adding a 'data-component' attribute to the root element.
685
+ * - Ensures that the 'data-component' attribute is present for component identification.
686
+ *
687
+ * 5. **Lifecycle Method Invocation**: It invokes the `componentDidMount` method if called from a 'render' context.
688
+ * - Executes `componentDidMount` to handle component initialization once the DOM is ready.
689
+ *
690
+ * @see {@link Component}
691
+ * @see {@link Component#componentDidMount}
692
+ */
583
693
  html(strings, ...args) {
694
+ // @ts-ignore
695
+ let caller = new Error().stack.split("\n")[2].trim();
696
+
584
697
  let result = "";
585
698
  for (let i = 0; i < strings.length; i++) {
586
699
  result += strings[i];
@@ -588,26 +701,179 @@ export class Component {
588
701
  result += args[i];
589
702
  }
590
703
  }
591
- // add ref to all elements
704
+
705
+ if (!result.trim().startsWith("<body>")) {
706
+ console.warn(
707
+ "You should wrap your html in a body tag, vader may not grab all html!"
708
+ );
709
+ }
592
710
  let dom = new DOMParser().parseFromString(result, "text/html");
593
711
  let elements = dom.body.querySelectorAll("*");
712
+
594
713
  elements.forEach((element) => {
595
- if (element.hasAttribute("ref")) {
596
- dom[element.getAttribute("ref")] = element;
714
+ switch (element.nodeName) {
715
+ case "IMG":
716
+ if (
717
+ !element.hasAttribute("alt") &&
718
+ !document.documentElement.outerHTML
719
+ .trim()
720
+ .includes("<!-- #vader-disable_accessibility -->")
721
+ ) {
722
+ throw new SyntaxError(
723
+ `Image: ${element.outerHTML} missing alt attribute`
724
+ );
725
+ } else if (
726
+ element.hasAttribute("alt") &&
727
+ // @ts-ignore
728
+ element.getAttribute("alt").length < 1 &&
729
+ !document.documentElement.outerHTML
730
+ .trim()
731
+ .includes("<!-- #vader-disable_accessibility -->")
732
+ ) {
733
+ throw new SyntaxError(
734
+ `Image: ${element.outerHTML} alt attribute cannot be empty`
735
+ );
736
+ } else if (
737
+ element.hasAttribute("src") &&
738
+ !document.documentElement.outerHTML
739
+ .trim()
740
+ .includes("<!-- #vader-disable_accessibility -->")
741
+ ) {
742
+ let prevurl = element.getAttribute("src");
743
+ element.setAttribute("aria-hidden", "true");
744
+ element.setAttribute("hidden", "true");
745
+ let url =
746
+ window.location.origin +
747
+ window.location.pathname +
748
+ "/public/" +
749
+ element.getAttribute("src");
750
+ let image = new Image();
751
+ image.src = url;
752
+ image.onerror = () => {
753
+ // @ts-ignore
754
+ element.setAttribute("src", prevurl);
755
+ throw new Error(`Image: ${element.outerHTML} not found`);
756
+ };
757
+ element.setAttribute("src", url);
758
+
759
+ image.onload = () => {
760
+ document.querySelectorAll(`img[src="${url}"]`).forEach((img) => {
761
+ img.setAttribute("src", url);
762
+ img.removeAttribute("aria-hidden");
763
+ img.removeAttribute("hidden");
764
+ });
765
+ };
766
+ }
767
+ break;
768
+
769
+ default:
770
+ if (element.hasAttribute("ref")) {
771
+ // @ts-ignore
772
+ dom[element.getAttribute("ref")] = element;
773
+ }
774
+
775
+ if (element.hasAttribute("class")) {
776
+ const allowClassComments =
777
+ document.documentElement.outerHTML.includes(
778
+ "<!-- #vader-allow_class -->"
779
+ );
780
+ if (!allowClassComments) {
781
+ console.warn(
782
+ "you can disable class errors using, <!-- #vader-allow_class -->"
783
+ );
784
+ throw new Error(
785
+ "class attribute is not allowed, please use className instead"
786
+ );
787
+ }
788
+ } else if (element.hasAttribute("className")) {
789
+ const isLocalhost = window.location.href.includes("localhost");
790
+ const is127001 = window.location.href.includes("127.0.0.1");
791
+ const ignoreClassComments = document.documentElement.outerHTML
792
+ .trim()
793
+ .includes("<!-- #vader-class-ignore -->");
794
+ const allowClassComments = document.documentElement.outerHTML
795
+ .trim()
796
+ .includes("<!-- #vader-allow_class -->");
797
+
798
+ if (
799
+ // @ts-ignore
800
+ (!this.validateClassName(element.getAttribute("className")) &&
801
+ isLocalhost) ||
802
+ (is127001 && !ignoreClassComments && !allowClassComments)
803
+ ) {
804
+ throw new Error(
805
+ `Invalid className ${element.getAttribute(
806
+ "className"
807
+ )}, please use camelCase instead - example: myClass`
808
+ );
809
+ }
810
+ // @ts-ignore
811
+ element.setAttribute("class", element.getAttribute("className"));
812
+ element.removeAttribute("className");
813
+ }
814
+
815
+ if (
816
+ element.hasAttribute("href") &&
817
+ // @ts-ignore
818
+ element.getAttribute("href").startsWith("/") &&
819
+ !document.documentElement.outerHTML
820
+ .trim()
821
+ .includes("<!-- #vader-disable_relative-paths -->")
822
+ ) {
823
+ element.setAttribute(
824
+ "href",
825
+ // @ts-ignore
826
+ `#/${element.getAttribute("href").replace("/", "")}`
827
+ );
828
+ }
829
+
830
+ if (
831
+ element.hasAttribute("src") &&
832
+ // @ts-ignore
833
+ element.getAttribute("src").startsWith("/") &&
834
+ !document.documentElement.outerHTML
835
+ .trim()
836
+ .includes("<!-- #vader-disable_relative-paths -->")
837
+ ) {
838
+ element.setAttribute(
839
+ "src",
840
+ `${window.location.origin}/public${element.getAttribute("src")}`
841
+ );
842
+ }
843
+ break;
597
844
  }
598
845
  });
846
+
847
+ result = dom.body.innerHTML;
848
+
599
849
  this.Componentcontent = result;
850
+
600
851
  if (!result.includes("<div data-component")) {
601
- result = `<div data-component="${this.name}">${result}</div>`;
852
+ result = `<div
853
+
854
+ data-component="${this.name}">${result}</div>`;
855
+ }
856
+ if (caller.includes("render") && !this.componentMounted) {
857
+ this.componentMounted = true;
858
+ this.componentDidMount();
602
859
  }
603
860
 
604
861
  return result;
605
862
  }
606
- async render() {
607
- this.componentMounted = true;
608
- this.componentDidMount();
609
- return await new Function(`return \`${this.Componentcontent}\`;`)();
610
- }
863
+ // write types to ensure it returns a string
864
+ /**
865
+ * @method render
866
+ * @description Allows you to render html
867
+ * @returns {Promise <any>}
868
+ * @example
869
+ * async render() {
870
+ * return this.html(`
871
+ * <div className="hero p-5">
872
+ * <h1>Home</h1>
873
+ * </div>
874
+ * `);
875
+ */
876
+ async render() {}
611
877
  }
612
878
 
613
879
  /**
@@ -629,12 +895,32 @@ export class Component {
629
895
  * `);
630
896
  * }
631
897
  */
632
- export const Vader = {
898
+ const Vader = {
899
+ /**
900
+ * @class Component
901
+ * @description Allows you to create a component
902
+ * @returns {void}
903
+ * @memberof {Vader}
904
+ * @example
905
+ * import { Vader } from "../../dist/vader/index.js";
906
+ * export class Home extends Vader.Component {
907
+ * constructor() {
908
+ * super();
909
+ * }
910
+ * async render() {
911
+ * return this.html(`
912
+ * <div className="hero p-5">
913
+ * <h1>Home</h1>
914
+ * </div>
915
+ * `);
916
+ * }
917
+ * }
918
+ */
633
919
  Component: Component,
634
920
  useRef: useRef,
635
921
  };
636
922
  export const component = (name) => {
637
- return new Component()
923
+ return new Component();
638
924
  };
639
925
 
640
926
  /**
@@ -652,12 +938,20 @@ let cache = {};
652
938
  /**
653
939
  * @function include
654
940
  * @description Allows you to include html file
655
- * @returns - modified string with html content
941
+ * @returns {Promise} - modified string with html content
656
942
  * @param {string} path
657
- * @param {Object} options
658
943
  */
659
944
 
660
- export const include = (path, options) => {
945
+ export const include = async (path) => {
946
+ if (
947
+ path.startsWith("/") &&
948
+ !path.includes("/src/") &&
949
+ !document.documentElement.outerHTML
950
+ .trim()
951
+ .includes("<!-- #vader-disable_relative-paths -->")
952
+ ) {
953
+ path = "/src/" + path;
954
+ }
661
955
  if (cache[path]) {
662
956
  return new Function(`return \`${cache[path]}\`;`)();
663
957
  }
@@ -669,13 +963,23 @@ export const include = (path, options) => {
669
963
  }
670
964
  return res.text();
671
965
  })
672
- .then((data) => {
966
+ .then(async (data) => {
673
967
  // Handle includes
674
968
  let includes = data.match(/<include src="(.*)"\/>/gs);
675
969
  if (includes) {
676
970
  // Use Promise.all to fetch all includes concurrently
677
971
  const includePromises = includes.map((e) => {
972
+ // @ts-ignore
678
973
  let includePath = e.match(/<include src="(.*)"\/>/)[1];
974
+
975
+ if (
976
+ includePath.startsWith("/") &&
977
+ !document.documentElement.outerHTML
978
+ .trim()
979
+ .includes("<!-- #vader-disable_relative-paths -->")
980
+ ) {
981
+ includePath = "/src" + includePath;
982
+ }
679
983
  return include(includePath).then((includeData) => {
680
984
  // Replace the include tag with the fetched data
681
985
  data = data.replace(e, includeData);
@@ -692,4 +996,6 @@ export const include = (path, options) => {
692
996
  return new Function(`return \`${data}\`;`)();
693
997
  }
694
998
  });
695
- };
999
+ };
1000
+
1001
+ export default Vader;
package/vaderRouter.js CHANGED
@@ -1,6 +1,6 @@
1
- // @ts-ignore
2
- window.$URL_PARAMS = {};
3
- // @ts-ignore
1
+ // @ts-ignore
2
+ window.$URL_PARAMS = {};
3
+ // @ts-ignore
4
4
  window.$URL_QUERY = {};
5
5
 
6
6
  /**
@@ -31,6 +31,7 @@ class VaderRouter {
31
31
  * Listener function for hash change events.
32
32
  * @type {Function}
33
33
  */
34
+ //@ts-ignore
34
35
  this.hashChangeListener = null;
35
36
 
36
37
  /**
@@ -43,6 +44,7 @@ class VaderRouter {
43
44
  * Flag indicating if custom error handling is enabled.
44
45
  * @type {boolean}
45
46
  */
47
+ //@ts-ignore
46
48
  this.customerror = null;
47
49
 
48
50
  /**
@@ -95,8 +97,9 @@ class VaderRouter {
95
97
  this.customerror = true;
96
98
  }
97
99
  handleRoute(method) {
100
+
98
101
  let route = window.location.hash.substring(1);
99
- // @ts-ignore
102
+ // @ts-ignore
100
103
  window.$CURRENT_URL = route;
101
104
 
102
105
  // remove query params from route
@@ -105,11 +108,12 @@ class VaderRouter {
105
108
  }
106
109
  route = route.split("/")[0] + "/" + route.split("/")[1];
107
110
  if (this.routes[route]) {
111
+
108
112
  this.storedroutes.push(route);
109
113
  const req = {
110
- // @ts-ignore
114
+ // @ts-ignore
111
115
  params: $URL_PARAMS ? $URL_PARAMS : {},
112
- // @ts-ignore
116
+ // @ts-ignore
113
117
  query: $URL_QUERY ? $URL_QUERY : {},
114
118
  url: route,
115
119
  method: method ? method : "GET",
@@ -122,16 +126,19 @@ class VaderRouter {
122
126
  document.querySelector(selector).innerHTML = data;
123
127
  },
124
128
  };
125
- if (typeof this.routes[route] === "function") {
129
+ if(typeof this.routes[route] === 'function'){
126
130
  this.routes[route](req, res);
127
131
  }
128
132
  } else {
129
133
  if (this.customerror) {
134
+ //@ts-ignore
130
135
  this.handleError("404", route);
131
136
  console.error("404: Route not found");
132
137
  } else {
133
138
  console.error("404: Route not found");
134
139
  }
140
+
141
+
135
142
  }
136
143
  }
137
144
  /**
@@ -143,7 +150,7 @@ class VaderRouter {
143
150
  * @description used by start() to handle errors.
144
151
  */
145
152
 
146
- handleError(type, data, res) {
153
+ handleError(type, data) {
147
154
  if (this.errorHandlers[type]) {
148
155
  this.errorHandlers[type](data);
149
156
  } else {
@@ -187,19 +194,17 @@ class VaderRouter {
187
194
  const params = {};
188
195
 
189
196
  for (let i = 0; i < paramNames.length; i++) {
197
+ //@ts-ignore
190
198
  params[paramNames[i]] = matches[i + 1];
191
199
  }
192
200
  if (
193
201
  path.includes(":") &&
194
202
  window.location.hash.substring(1).split("?")[1]
195
203
  ) {
204
+
205
+
196
206
  return false;
197
207
  }
198
- /**
199
- * @alias query
200
- * @type {Object}
201
- * @property {Object} params - The params object.
202
- */
203
208
  const query = {};
204
209
 
205
210
  const queryString = window.location.hash.substring(1).split("?")[1];
@@ -223,11 +228,11 @@ class VaderRouter {
223
228
  url: window.location.hash.substring(1),
224
229
  method: "GET",
225
230
  };
226
- // @ts-ignore
231
+ // @ts-ignore
227
232
  window.$URL_PARAMS = params;
228
- // @ts-ignore
233
+ // @ts-ignore
229
234
  window.$URL_QUERY = query;
230
- // @ts-ignore
235
+ // @ts-ignore
231
236
  window.$CURRENT_URL = window.location.hash.substring(1);
232
237
  /**
233
238
  * @alias render
@@ -238,26 +243,23 @@ class VaderRouter {
238
243
  * @description Allows you to perform actions when the currentRoute changes.
239
244
  */
240
245
  const res = {
241
-
246
+ return: function (data) {
247
+ this.hooked = false;
248
+ },
242
249
  render: function (selector, data) {
243
250
  document.querySelector(selector).innerHTML = data;
244
251
  },
245
252
  };
246
253
 
247
254
  callback(req, res);
248
- // @ts-ignore
255
+ // @ts-ignore
249
256
  return true;
250
257
  }
251
258
 
252
259
  this.hooked = false;
253
260
  return false;
254
261
  }
255
-
256
- /**
257
- * @alias kill
258
- * @description Allows you to kill a route.
259
- * @param {string} path
260
- */
262
+
261
263
  kill(path) {
262
264
  if (this.routes[path]) {
263
265
  delete this.routes[path];
@@ -266,36 +268,36 @@ class VaderRouter {
266
268
  /**
267
269
  * @alias use
268
270
  * @param {String} pattern
269
- * @param {Function} callback
271
+ * @param { void} callback
270
272
  * @returns {void}
271
273
  * @memberof VaderRouter
272
274
  * @description Allows you to set routes to be used throughout your spa.
273
275
  */
274
- use(pattern, callback = null) {
276
+ use(pattern, callback) {
275
277
  const regexPattern = pattern
276
278
  .replace(/:[^/]+/g, "([^/]+)") // Replace :param with a capturing group
277
279
  .replace(/\//g, "\\/"); // Escape forward slashes
278
-
280
+
279
281
  const regex = new RegExp("^" + regexPattern + "(\\?(.*))?$");
280
282
  let params = {};
281
283
  let query = {};
282
-
284
+
283
285
  // Get params
284
286
  const match = window.location.hash.substring(1).match(regex);
285
-
287
+
286
288
  if (match) {
287
289
  this.storedroutes.push(window.location.hash.substring(1));
288
290
  const matches = match.slice(1); // Extract matched groups
289
-
291
+
290
292
  // Extract named params from the pattern
291
293
  const paramNames = pattern.match(/:[^/]+/g) || [];
292
294
  for (let i = 0; i < paramNames.length; i++) {
293
295
  const paramName = paramNames[i].substring(1); // Remove the leading ":"
294
296
  params[paramName] = matches[i];
295
297
  }
296
-
298
+
297
299
  query = {};
298
-
300
+
299
301
  const queryString = matches[paramNames.length]; // The last match is the query string
300
302
  if (queryString) {
301
303
  const queryParts = queryString.split("&");
@@ -305,11 +307,11 @@ class VaderRouter {
305
307
  }
306
308
  }
307
309
  }
308
- // @ts-ignore
310
+ // @ts-ignore
309
311
  window.$URL_PARAMS = params;
310
- // @ts-ignore
312
+ // @ts-ignore
311
313
  window.$URL_QUERY = query;
312
-
314
+ //@ts-ignore
313
315
  if (callback) {
314
316
  this.routes[pattern] = callback;
315
317
  } else {
@@ -317,16 +319,14 @@ class VaderRouter {
317
319
  this.storedroutes.push(window.location.hash.substring(1));
318
320
  }
319
321
  }
322
+
323
+
320
324
 
321
- /**
322
- * @alias onload
323
- * @param {Function} callback
324
- */
325
325
  onload(callback) {
326
326
  // await dom to be done make sure no new elements are added
327
327
  if (
328
328
  document.readyState === "complete" ||
329
- // @ts-ignore
329
+ // @ts-ignore
330
330
  document.readyState === "loaded" ||
331
331
  document.readyState === "interactive"
332
332
  ) {
@@ -345,16 +345,8 @@ class VaderRouter {
345
345
  */
346
346
  on(path, callback) {
347
347
  window.addEventListener("hashchange", () => {
348
- /**
349
- * @alias paramNames
350
- * @typedef {Array}
351
- */
352
- const paramNames = new Array();
353
- /**
354
- * @alias queryNames
355
- * @typedef {Array}
356
- */
357
- const queryNames = new Array();
348
+ const paramNames = [];
349
+ const queryNames = [];
358
350
  const parsedPath = path
359
351
  .split("/")
360
352
  .map((part) => {
@@ -389,11 +381,11 @@ class VaderRouter {
389
381
  this.currentUrl = route;
390
382
  // @ts-ignore
391
383
  window.$CURRENT_URL = route;
392
- // @ts-ignore
384
+ // @ts-ignore
393
385
  window.$URL_PARAMS = {};
394
386
  if (
395
387
  window.location.hash.substring(1).match(regex) &&
396
- // @ts-ignore
388
+ // @ts-ignore
397
389
  this.routes[window.$CURRENT_URL]
398
390
  ) {
399
391
  this.storedroutes.push(window.location.hash.substring(1));
@@ -401,7 +393,7 @@ class VaderRouter {
401
393
  const params = {};
402
394
 
403
395
  for (let i = 0; i < paramNames.length; i++) {
404
- // @ts-ignore
396
+ //@ts-ignore
405
397
  params[paramNames[i]] = matches[i + 1];
406
398
  }
407
399
  if (
@@ -422,7 +414,6 @@ class VaderRouter {
422
414
  const queryParts = queryString.split("&");
423
415
  for (let i = 0; i < queryParts.length; i++) {
424
416
  const queryParam = queryParts[i].split("=");
425
- // @ts-ignore
426
417
  query[queryParam[0]] = queryParam[1];
427
418
  }
428
419
  }
@@ -433,7 +424,9 @@ class VaderRouter {
433
424
  method: "POST",
434
425
  };
435
426
  const res = {
436
-
427
+ return: function (data) {
428
+ this.hooked = false;
429
+ },
437
430
  /**
438
431
  * @alias send
439
432
  * @param {String} selector
@@ -445,7 +438,7 @@ class VaderRouter {
445
438
  * res.send('#root', '<h1>Hello World</h1>');
446
439
  * */
447
440
  send: function (selector, data) {
448
- // @ts-ignore
441
+ //@ts-ignore
449
442
  document.querySelector(selector).innerHTML = data;
450
443
  },
451
444
  /**
@@ -457,13 +450,13 @@ class VaderRouter {
457
450
  * @description Allows you to perform actions when the currentRoute changes.
458
451
  */
459
452
  render: function (selector, data) {
460
- // @ts-ignore
453
+ //@ts-ignore
461
454
  document.querySelector(selector).innerHTML = data;
462
455
  },
463
456
  };
464
- // @ts-ignore
457
+ // @ts-ignore
465
458
  window.$URL_QUERY = query;
466
- // @ts-ignore
459
+ // @ts-ignore
467
460
  window.$URL_PARAMS = params;
468
461
 
469
462
  /**
@@ -475,10 +468,10 @@ class VaderRouter {
475
468
  * @description Allows you to perform actions when the currentRoute changes.
476
469
  */
477
470
  callback(req, res);
478
- } else {
479
- console.log("no route");
471
+ }else{
472
+ console.log('no route')
480
473
  }
481
474
  });
482
475
  }
483
476
  }
484
- export default VaderRouter;
477
+ export default VaderRouter;