vaderjs 1.1.4 → 1.1.5

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/vader.js CHANGED
@@ -1,32 +1,22 @@
1
- /**
2
- * @Object window
3
- * @property {Object} props
4
- * @description Allows you to store props for component
5
- */
6
- window.props = {};
7
-
8
- let events = {};
9
- /**
10
- * @function vhtml
11
- * @param {String} strings
12
- * @param {...any} args
13
- * @returns modified string
14
- *
15
- */
16
1
 
17
- window.dom = {};
2
+ let dom = /**@type {Obect} **/ {};
18
3
  /**
19
4
  * @function useRef
20
5
  * @description Allows you to get reference to DOM element
21
6
  * @param {String} ref
22
7
  * @returns {Object} {current}
23
- * @returns
24
8
  */
25
- export const useRef = (ref) => {
9
+ export const useRef = (ref /** @type {string} */) => {
26
10
  // Try to get the existing DOM element from window.dom
27
- let el = window.dom[ref] || document.querySelector(`[ref="${ref}"]`);
11
+ /**@type {object }*/
12
+ let el = dom[ref] || document.querySelector(`[ref="${ref}"]`);
28
13
 
29
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
+ */
30
20
  const update = (data) => {
31
21
  // Parse the new HTML data
32
22
  const newDom = new DOMParser().parseFromString(data, "text/html");
@@ -39,13 +29,12 @@ export const useRef = (ref) => {
39
29
  const newElement = newHtml.cloneNode(true);
40
30
  // Replace the old element with the new one
41
31
  el.parentNode.replaceChild(newElement, el);
42
- window.dom[ref] = newElement;
32
+ dom[ref] = newElement;
43
33
  }
44
34
  } else {
45
35
  // If the element doesn't exist, create it
46
36
  el = newHtml.cloneNode(true);
47
- window.dom[ref] = el;
48
- console.log("Element created:", ref);
37
+ dom[ref] = el;
49
38
  }
50
39
  };
51
40
 
@@ -54,326 +43,274 @@ export const useRef = (ref) => {
54
43
  update,
55
44
  };
56
45
  };
57
-
58
- export function vhtml(strings, options, ...args) {
59
- console.log(options);
60
- let result = "";
61
-
62
- for (let i = 0; i < strings.length; i++) {
63
- result += strings[i];
64
- if (i < args.length) {
65
- result += args[i];
66
- }
46
+ let components = [];
47
+ /**
48
+ * @class Component
49
+ * @description Allows you to create a component
50
+ * @returns {null}
51
+ * @example
52
+ * import { Vader } from "../../dist/vader/index.js";
53
+ * export class Home extends Vader.Component {
54
+ * constructor() {
55
+ * super();
56
+ * }
57
+ * async render() {
58
+ * return this.html(`
59
+ * <div className="hero p-5">
60
+ * <h1>Home</h1>
61
+ * </div>
62
+ * `);
63
+ * }
64
+ * }
65
+ */
66
+ export class Component {
67
+ constructor() {
68
+ this.states = {};
69
+ //@ts-ignore
70
+ this.name = this.constructor.name;
71
+ console.log(this.name);
72
+ this.executedEffects = {};
73
+ this.storedProps = {};
74
+ this.componentMounted = false;
75
+ this.hasMounted = false;
76
+ this.$_signal_subscribers = [];
77
+ /**
78
+ * @property {Array} $_signal_subscribers_ran
79
+ * @description Allows you to keep track of signal subscribers
80
+ * @private
81
+ */
82
+ this.$_signal_subscribers_ran = [];
83
+ this.effects = {};
84
+ this.$_useStore_subscribers = [];
85
+ this.init();
86
+ this.Componentcontent = null;
87
+ this.$_signal_dispatch_event = new CustomEvent("signalDispatch", {
88
+ detail: {
89
+ hasUpdated: false,
90
+ state: null,
91
+ },
92
+ });
67
93
  }
68
94
 
69
- let dom = new DOMParser().parseFromString(result, "text/html");
70
- if (dom.body.firstChild.nodeName.toLowerCase() !== "div") {
71
- throw new Error(
72
- `Ensure that you have a parent div for all component elements`
73
- );
95
+ init() {
96
+ //@ts-ignore
97
+ this.registerComponent();
98
+ //@ts-ignore
99
+ window.states = this.states;
100
+ //@ts-ignore
101
+ window.useState = this.useState;
102
+ //@ts-ignore
103
+ window.setState = this.setState;
104
+ //@ts-ignore
105
+ window.useEffect = this.useEffect;
106
+ //@ts-ignore
107
+ window.useAuth = this.useAuth;
108
+ //@ts-ignore
109
+ window.useSyncStore = this.useSyncStore;
110
+ //@ts-ignore
111
+ window.useReducer = this.useReducer;
112
+ //@ts-ignore
113
+ window.runEffects = this.runEffects;
114
+ //@ts-ignore
115
+ window.rf = this.rf;
116
+ //@ts-ignore
117
+ window.signal = this.signal;
74
118
  }
75
119
 
76
- dom.body.querySelectorAll("[className]").forEach((el) => {
77
- el.setAttribute("class", el.getAttribute("classname"));
78
- el.removeAttribute("classname");
79
- });
80
-
81
- return dom.body.innerHTML;
82
- }
83
-
84
- /**
85
- * @function component
86
- * @param {*} name
87
- * @param {*} options
88
- * @returns
89
- * @param {*} states
90
- * @param {*} setState
91
- * @param {*} useState
92
- * @param {*} useEffect
93
- * @param {*} useAuth
94
- * @param {*} render
95
- *
96
- * @example
97
- *
98
- * const app = component('app', {
99
- render: (states, props) => {
100
-
101
-
102
- let [count, setCount] = useState('count', 0);
103
-
104
- useEffect(() => {
105
- console.log('App component mounted');
106
- });
107
-
108
- function incrementHandler() {
109
- setCount(count + 1);
110
- }
111
- rf('incrementHandler', incrementHandler);
112
-
120
+ registerComponent() {
121
+ components.push(this);
122
+ }
113
123
 
114
- return vhtml`
115
- <div>
116
- <button onclick="incrementHandler()" >Click me</button>
117
- <p>You clicked ${count} times</p>
118
- </div>
119
- `;
120
- },
121
- });
122
- ***/
123
-
124
- export function component(name, options) {
125
- let states = {};
126
- const effects = {};
127
- const executedEffects = {};
128
- let storedProps = {};
129
- let componentMounted = false;
130
- let hasMounted = false;
131
- /**
132
- * @function setState
133
- * @param {*} key
134
- * @param {*} value
135
- * @returns {null}
136
- * @description Allows you to change state of component and re-render it
137
- */
138
- const setState = (key, value) => {
139
- states[key] = value;
140
- window.props[key] = value;
141
- updateComponent();
142
- };
143
-
144
124
  /**
145
- * @function signal
146
- * @description Manage state much efficiently - alternative to vaders useEffect method
125
+ * @method setState
126
+ * @description Allows you to set state
147
127
  * @param {String} key
148
- * @param {Object | String | Number | Boolean} initialState
149
- * @returns {Object} {subscribe, cleanup, dispatch, getDetail, call, setDetail}
128
+ * @param {*} value
129
+ * @returns {void}
150
130
  * @example
151
- * let count = signal('count', 0);
152
- *
153
- * count.subscribe((state) => {
154
- * console.log(state)
155
- * });
156
- *
157
- * count.call();
158
- * count.set(1);
159
- * count.call();
160
- * count.get();
161
- *
162
- */
163
-
164
- const signal = (key, initialState) => {
165
- let [state, setState] = useState(key, initialState);
166
- let subscribers = [];
167
-
168
- function subscribe(fn) {
169
- subscribers.push(fn);
170
- }
171
- function cleanup() {
172
- subscribers = subscribers.filter((subscriber) => subscriber !== fn);
173
- setState(null);
174
- }
175
- function dispatch() {
176
- subscribers.forEach((subscriber) => subscriber(state));
177
- }
178
- function get() {
179
- return state;
180
- }
181
- function call() {
182
- dispatch();
183
- }
184
- function set(detail) {
185
- setState(detail);
186
- }
187
- return {
188
- subscribe,
189
- cleanup,
190
- dispatch,
191
-
192
- call,
193
- set,
194
- get,
195
- };
196
- };
131
+ * this.setState('count', 1)
132
+ * */
133
+ setState(key, value) {
134
+ this.states[key] = value;
135
+ this.updateComponent();
136
+ }
197
137
 
138
+ componentUpdate() {}
198
139
  /**
199
- * @function useState
200
- * @param {*} key
201
- * @param {*} initialValue
202
- * @returns {Array} [state, setState]
203
- * @description Allows you to bind state to component
140
+ * @method componentDidMount
141
+ * @description Allows you to run code after component has mounted
204
142
  */
205
-
206
- const useState = (key, initialValue) => {
207
- if (!states[key]) {
208
- states[key] = initialValue;
209
- }
210
- return [
211
- states[key],
212
- (value) => {
213
- states[key] = value;
214
- window.props[key] = value;
215
- updateComponent();
216
- },
217
- ];
218
- };
143
+ componentDidMount() {}
219
144
  /**
220
- * @function useEffect
221
- * @param {*} effectFn
222
- * @returns {null}
223
- * @description Allows you to run side effects
145
+ * @method signal
146
+ * @description Allows you to create a signal
147
+ * @param {String} key
148
+ * @param {any} initialState
149
+ * @returns {Object} {subscribe, cleanup, dispatch, call, set, get}
150
+ * @example
151
+ * let signal = this.signal('count', 0);
152
+ * signal.subscribe((value) => {
153
+ * console.log(value)
154
+ * }, false) // false means it will run every time
155
+ * signal.subscribe((value) => {
156
+ * console.log(value)
157
+ * }, true) // true means it will run once
158
+ * signal.call() // this will call all subscribers
159
+ * signal.set(1) // this will set the value of the signal
160
+ * signal.get() // this will get the value of the signal
161
+ * signal.cleanup() // this will remove all subscribers
224
162
  */
225
-
226
- const useEffect = (effectFn, dependencies) => {
227
- if (!effects[name]) {
228
- effects[name] = [];
229
- }
230
- effects[name].push(effectFn);
231
-
232
- if (dependencies.length > 0) {
233
- const oldState = states[name];
234
- const newState = dependencies.map((dependency) => {
235
- return states[dependency];
236
- });
237
- if (oldState) {
238
- const hasChanged = newState.some((state) => {
239
- return state !== oldState;
240
- });
241
- if (hasChanged) {
242
- effectFn();
163
+ signal = (key, initialState) => {
164
+ let hasCaller = false;
165
+ let [state, setState] = this.useState(key, initialState, () => {
166
+ if (this.$_signal_subscribers.length > 0) {
167
+ for (var i = 0; i < this.$_signal_subscribers.length; i++) {
168
+ if (!hasCaller) {
169
+ if (
170
+ this.$_signal_subscribers[i].runonce &&
171
+ // @ts-ignore
172
+ this.$_signal_subscribers_ran.includes(
173
+ this.$_signal_subscribers[i]
174
+ )
175
+ ) {
176
+ break;
177
+ } else {
178
+ this.$_signal_subscribers[i].function(state);
179
+ this.$_signal_subscribers_ran.push(this.$_signal_subscribers[i]);
180
+ return;
181
+ }
182
+ }
243
183
  }
184
+ } else {
185
+ this.$_signal_dispatch_event.detail.hasUpdated = true;
186
+ this.$_signal_dispatch_event.detail.state = state;
187
+ window.dispatchEvent(this.$_signal_dispatch_event);
244
188
  }
245
- } else if (!hasMounted) {
246
- effectFn();
247
- hasMounted = true;
248
- }
249
-
250
- return () => {
251
- effects[name] = effects[name].filter((fn) => fn !== effectFn);
252
- };
253
- };
254
-
255
- /**
256
- *
257
- * @param {String} key
258
- * @param {Function} reducer
259
- * @param {Object} initialState
260
- * @returns {Array} [state, dispatch]
261
- * @description Allows you to bind state to component
262
- */
263
-
264
- const useReducer = (key, reducer, initialState) => {
265
- const [state, setState] = useState(key, initialState);
266
-
267
- const dispatch = (action) => {
268
- const newState = reducer(state, action);
269
- setState(newState);
270
- };
271
-
272
- return [state, dispatch];
273
- };
274
- /**
275
- * @function useSyncStore
276
- * @param {*} storeName
277
- * @param {*} initialState
278
- * @returns {Object} {getField, setField, subscribe, clear}
279
- * @description Allows you to manage state in local storage
280
- */
281
- const useSyncStore = (storeName, initialState) => {
282
- // Load state from local storage or use initial state
283
- const storedState =
284
- JSON.parse(localStorage.getItem(storeName)) || initialState;
285
-
286
- // Create a store object
287
- const store = createStore(storedState);
288
-
189
+ });
289
190
  /**
290
- * Get the value of a specific field from the store's state.
191
+ * @function $_signal_subscribe
192
+ * @description Allows you to subscribe to a signal
193
+ * @param {*} fn
194
+ * @param {*} runonce
195
+ * @returns {void}
291
196
  *
292
- * @param {string} fieldName - The name of the field.
293
- * @returns {*} The value of the specified field.
294
197
  */
295
- const getField = (fieldName) => {
296
- return store.state[fieldName];
198
+ this.$_signal_subscribe = (fn, runonce) => {
199
+ this.$_signal_subscribers.push({
200
+ function: fn,
201
+ runonce: runonce,
202
+ });
297
203
  };
298
-
299
- /**
300
- * Set the value of a specific field in the store's state.
301
- *
302
- * @param {string} fieldName - The name of the field.
303
- * @param {*} value - The new value to set for the field.
304
- */
305
- const setField = (fieldName, value) => {
306
- // Create a new state object with the updated field
307
- const newState = { ...store.state, [fieldName]: value };
308
- // Update the store's state and save it to local storage
309
- store.setState(newState);
310
- saveStateToLocalStorage(storeName, newState);
204
+ this.$_signal_cleanup = (fn) => {
205
+ this.$_signal_subscribers = this.$_signal_subscribers.filter(
206
+ (subscriber) => subscriber.function !== fn
207
+ );
311
208
  };
312
-
313
- /**
314
- * Subscribe a function to be notified of state changes.
315
- *
316
- * @param {Function} subscriber - The function to call when the state changes.
317
- * @returns {Function} A function to unsubscribe the subscriber.
318
- */
319
- const subscribe = (subscriber) => {
320
- return store.subscribe(subscriber);
209
+ this.$_signal_dispatch = () => {
210
+ for (var i = 0; i < this.$_signal_subscribers.length; i++) {
211
+ if (
212
+ this.$_signal_subscribers[i].runonce &&
213
+ // @ts-ignore
214
+ this.$_signal_subscribers_ran.includes(this.$_signal_subscribers[i])
215
+ ) {
216
+ break;
217
+ } else {
218
+ this.$_signal_subscribers[i].function(state);
219
+ this.$_signal_subscribers_ran.push(this.$_signal_subscribers[i]);
220
+ }
221
+ }
321
222
  };
322
-
323
- /**
324
- * Clear the stored state from local storage.
325
- */
326
- const clear = () => {
327
- localStorage.removeItem(storeName);
223
+ this.$_signal_get = () => {
224
+ return state;
225
+ };
226
+ this.$_signal_call = () => {
227
+ hasCaller = true;
228
+ this.$_signal_dispatch();
328
229
  };
329
-
330
230
  /**
331
- * Save the state to local storage.
332
- *
333
- * @param {string} key - The key under which to store the state.
334
- * @param {*} state - The state to be stored.
231
+ * @function $_signal_set
232
+ * @description Allows you to set the value of a signal
233
+ * @param {*} detail
335
234
  */
336
- const saveStateToLocalStorage = (key, state) => {
337
- try {
338
- localStorage.setItem(key, JSON.stringify(state));
339
- } catch (error) {
340
- // Handle errors when saving to local storage
341
- console.error("Error saving state to local storage:", error);
342
- }
235
+ this.$_signal_set = (detail) => {
236
+ setState(detail);
343
237
  };
344
238
 
345
239
  return {
346
- getField,
347
- setField,
348
- subscribe,
349
- clear,
240
+ /**
241
+ * @function subscribe
242
+ * @description Allows you to subscribe to a signal
243
+ * @param {*} fn
244
+ * @param {*} runonce
245
+ */
246
+ subscribe: this.$_signal_subscribe,
247
+ /**
248
+ * @function cleanup
249
+ * @description Allows you to cleanup a signal
250
+ * @param {*} fn
251
+ * @returns {null}
252
+ */
253
+ cleanup: this.$_signal_cleanup,
254
+ /**
255
+ * @function dispatch
256
+ * @description Allows you to dispatch a signal
257
+ * @returns {null}
258
+ */
259
+ dispatch: this.$_signal_dispatch,
260
+ /**
261
+ * @function call
262
+ * @description Allows you to call a signal
263
+ * @returns {null}
264
+ */
265
+ call: this.$_signal_call,
266
+ /**
267
+ * @function set
268
+ * @description Allows you to set the value of a signal
269
+ * @param {*} detail
270
+ * @returns {null}
271
+ */
272
+ set: this.$_signal_set,
273
+ /**
274
+ * @function get
275
+ * @readonly
276
+ * @description Allows you to get the value of a signal
277
+ * @returns {any}
278
+ */
279
+ get: this.$_signal_get,
350
280
  };
351
281
  };
352
-
353
282
  /**
354
- * @function useAuth
355
- * @param {*} rulesets
356
- * @param {*} options
357
- * @returns {Object} {canAccess, grantAccess, revokeAccess}
358
- * @description Allows you to manage access to resources through rulesets
359
- * @returns
283
+ * @method useAuth
284
+ * @description Allows you to create an auth object
285
+ * @param {Object} options
286
+ * @param {Array} options.rulesets
287
+ * @param {Object} options.user
288
+ * @returns {Object} {can, hasRole, canWithRole, assignRule, revokeRule, canAnyOf, canAllOf, canGroup}
289
+ * @example
290
+ * let auth = this.useAuth({
291
+ * rulesets: [
292
+ * {
293
+ * action: 'create',
294
+ * condition: (user) => {
295
+ * return user.role === 'admin'
296
+ * }
297
+ * }
298
+ * ],
299
+ * user: {
300
+ * role: 'admin'
301
+ * }
302
+ * })
303
+ * auth.can('create') // true
360
304
  */
361
-
362
- function useAuth(options) {
363
- if (!options.rulesets) {
305
+ useAuth(options) {
306
+ /**@type {Array}**/
307
+ let rules = options.rulesets;
308
+ if (!rules) {
364
309
  throw new Error("No rulesets provided");
365
310
  }
366
-
367
- let rules = options.rulesets;
311
+ /**@type {Object}**/
368
312
  let user = options.user;
369
-
370
- const auth = {
371
- /**
372
- * Check if the user can perform a specific action.
373
- *
374
- * @param {string} action - The action to check.
375
- * @returns {boolean} True if the user can perform the action, false otherwise.
376
- */
313
+ let auth = {
377
314
  can: (action) => {
378
315
  let can = false;
379
316
  rules.forEach((rule) => {
@@ -386,29 +323,23 @@ export function component(name, options) {
386
323
  return can;
387
324
  },
388
325
  /**
389
- * Check if the user has a specific role.
390
- *
391
- * @param {string} role - The role to check.
392
- * @returns {boolean} True if the user has the role, false otherwise.
326
+ * @function hasRole
327
+ * @description Allows you to check if user has a role
328
+ * @param {String} role
329
+ * @returns {Boolean}
393
330
  */
394
331
  hasRole: (role) => {
395
332
  return user.role && user.role.includes(role);
396
333
  },
397
334
  /**
398
- * Check if the user can perform a specific action with a specific role.
399
- *
400
- * @param {string} action - The action to check.
401
- * @param {string} role - The role to check.
402
- * @returns {boolean} True if the user can perform the action with the role, false otherwise.
335
+ * @function canWithRole
336
+ * @param {String} action
337
+ * @param {String} role
338
+ * @returns
403
339
  */
404
340
  canWithRole: (action, role) => {
405
341
  return auth.can(action) && auth.hasRole(role);
406
342
  },
407
- /**
408
- * Assign a new rule to the rulesets.
409
- *
410
- * @param {Object} rule - The rule to assign.
411
- */
412
343
  assignRule: (rule) => {
413
344
  if (
414
345
  !rules.some((existingRule) => existingRule.action === rule.action)
@@ -416,162 +347,314 @@ export function component(name, options) {
416
347
  rules.push(rule);
417
348
  }
418
349
  },
419
- /**
420
- * Revoke a rule from the rulesets.
421
- *
422
- * @param {string} action - The action of the rule to revoke.
423
- */
424
350
  revokeRule: (action) => {
425
351
  rules = rules.filter((rule) => rule.action !== action);
426
352
  },
427
- /**
428
- * Check if the user can perform any of the specified actions.
429
- *
430
- * @param {Array} actions - An array of actions to check.
431
- * @returns {boolean} True if the user can perform any of the actions, false otherwise.
432
- */
433
353
  canAnyOf: (actions) => {
434
354
  return actions.some((action) => auth.can(action));
435
355
  },
436
- /**
437
- * Check if the user can perform all of the specified actions.
438
- *
439
- * @param {Array} actions - An array of actions to check.
440
- * @returns {boolean} True if the user can perform all of the actions, false otherwise.
441
- */
442
356
  canAllOf: (actions) => {
443
357
  return actions.every((action) => auth.can(action));
444
358
  },
445
- /**
446
- * Check if the user can perform a group of actions based on a logical operator.
447
- *
448
- * @param {Array} actions - An array of actions to check.
449
- * @param {string} logicalOperator - The logical operator to use ('any' or 'all').
450
- * @returns {boolean} True if the user can perform the actions based on the logical operator, false otherwise.
451
- */
452
359
  canGroup: (actions, logicalOperator = "any") => {
453
360
  return logicalOperator === "any"
454
361
  ? auth.canAnyOf(actions)
455
362
  : auth.canAllOf(actions);
456
363
  },
457
364
  };
458
-
459
365
  return auth;
460
366
  }
367
+ /**
368
+ * @method useReducer
369
+ * @description Allows you to create a reducer
370
+ * @param {*} key
371
+ * @param {*} reducer
372
+ * @param {*} initialState
373
+ * @url - useReducer works similarly to - https://react.dev/reference/react/useReducer
374
+ * @returns {Array} [state, dispatch]
375
+ * @example
376
+ * let [count, setCount] = this.useReducer('count', (state, action) => {
377
+ * switch (action.type) {
378
+ * case 'increment':
379
+ * return state + 1
380
+ * case 'decrement':
381
+ * return state - 1
382
+ * default:
383
+ * throw new Error()
384
+ * }
385
+ * }, 0)
386
+ * setCount({type: 'increment'})
387
+ */
388
+ useReducer(key, reducer, initialState) {
389
+ const [state, setState] = this.useState(key, initialState);
390
+ const dispatch = (action) => {
391
+ const newState = reducer(state, action);
392
+ setState(newState);
393
+ };
394
+ return [state, dispatch];
395
+ }
396
+ runEffects() {
397
+ Object.keys(this.effects).forEach((component) => {
398
+ this.effects[component].forEach((effect) => {
399
+ if (!this.executedEffects[effect]) {
400
+ effect();
401
+ this.executedEffects[effect] = true;
402
+ }
403
+ });
404
+ });
405
+ }
406
+ /**
407
+ * @method useSyncStore
408
+ * @description Allows you to create a store
409
+ * @param {*} storeName
410
+ * @param {*} initialState
411
+ * @returns {Object} {getField, setField, subscribe, clear}
412
+ * @example
413
+ * let store = this.useSyncStore('store', {count: 0})
414
+ * store.setField('count', 1)
415
+ * store.getField('count') // 1
416
+ *
417
+ */
418
+ useSyncStore(storeName, initialState) {
419
+ let [storedState, setStoredState] = this.useState(
420
+ storeName,
421
+ initialState ||
422
+ // @ts-ignore
423
+ localStorage.getItem(`$_vader_${storeName}`, (s) => {
424
+
425
+ localStorage.setItem(`$_vader_${storeName}`, JSON.stringify(s));
426
+ this.$_useStore_subscribers.forEach((subscriber) => {
427
+ subscriber(s);
428
+ });
429
+ }) ||
430
+ {}
431
+ );
432
+
433
+ const getField = (fieldName) => {
434
+ return storedState[fieldName];
435
+ };
436
+ const setField = (fieldName, value) => {
437
+ const newState = { ...storedState, [fieldName]: value };
438
+ setStoredState(newState);
439
+ };
440
+ const subscribe = (subscriber) => {
441
+ return this.$_useStore_subscribers.push(subscriber);
442
+ };
443
+
444
+ const clear = (fieldName) => {
445
+ let newState = storedState;
446
+ delete newState[fieldName];
447
+ setStoredState(newState);
448
+ };
449
+ return {
450
+ getField,
451
+ setField,
452
+ subscribe,
453
+ clear,
454
+ };
455
+ }
456
+ /**
457
+ * @method useState
458
+ * @description Allows you to create a state
459
+ * @param {String} key
460
+ * @param {*} initialValue
461
+ * @param {*} callback
462
+ * @url - useState works similarly to - https://react.dev/reference/react/useState
463
+ * @returns {Array} [state, setState]
464
+ * @example
465
+ * let [count, setCount] = this.useState('count', 0, () => {
466
+ * console.log('count has been updated')
467
+ * })
468
+ *
469
+ * setCount(count + 1)
470
+ */
471
+ useState(key, initialValue, callback) {
472
+ if (!this.states[key]) {
473
+ this.states[key] = initialValue;
474
+ }
475
+ return [
476
+ this.states[key],
477
+ (value) => {
478
+ this.states[key] = value;
479
+ this.updateComponent();
480
+ typeof callback === "function" ? callback() : null;
481
+ },
482
+ ];
483
+ }
461
484
 
462
485
  /**
463
- * @function runEffects
464
- * @returns {null}
486
+ * @function useEffect
487
+ * @param {*} effectFn
488
+ * @param {*} dependencies
465
489
  * @description Allows you to run side effects
490
+ * @deprecated - this is no longer suggested please use vader signals instead
491
+ * @returns {Object} {cleanup}
466
492
  */
467
- const runEffects = () => {
468
- if (!executedEffects[name] && effects[name]) {
469
- effects[name].forEach((effectFn) => effectFn());
470
- executedEffects[name] = true;
493
+ useEffect(effectFn, dependencies) {
494
+ if (!this.effects[this.name]) {
495
+ this.effects[this.name] = [];
471
496
  }
472
- };
473
- window.useState = useState;
474
- window.setState = setState;
475
- window.useEffect = useEffect;
476
- window.useAuth = useAuth;
477
- window.useSyncStore = useSyncStore;
478
- window.useReducer = useReducer;
479
- window.runEffects = runEffects;
480
- window.signal = signal;
481
-
482
- const updateComponent = async () => {
483
- const componentContainer = document.querySelector(
484
- `[data-component="${name}"]`
485
- );
486
- const newHtml = await options.render(states, storedProps);
487
- if (componentContainer && newHtml !== componentContainer.innerHTML) {
488
- // only update the chunk of DOM that has changed
489
- let newDom = new DOMParser().parseFromString(newHtml, "text/html");
490
- let oldDom = new DOMParser().parseFromString(
491
- componentContainer.innerHTML,
492
- "text/html"
493
- );
494
- let html = newDom.body.firstChild;
495
- let oldHtml = oldDom.body.firstChild;
496
- if (!html.isEqualNode(oldHtml)) {
497
- // make shadow
498
- let shadow = document.createElement("div");
499
- shadow.innerHTML = newHtml;
500
- let newHtmlDom = shadow.firstChild;
501
- let oldHtmlDom = componentContainer.firstChild;
502
- // replace old html with new html
503
- componentContainer.replaceChild(newHtmlDom, oldHtmlDom);
504
- }
505
- if (!componentMounted) {
506
- componentMounted = true;
497
+ this.effects[this.name].push(effectFn);
507
498
 
508
- // Execute the "component did mount" code here
509
- if (
510
- options.componentDidMount &&
511
- typeof options.componentDidMount === "function"
512
- ) {
513
- options.componentUpdate ? options.componentDidMount() : null;
499
+ if (dependencies.length > 0) {
500
+ dependencies.forEach((d) => {
501
+ if (d.set) {
502
+ throw new Error(
503
+ "signal found, do not use effect and signals at the same time - signals are more efficient"
504
+ );
514
505
  }
515
- }
506
+ });
507
+ } else if (!this.hasMounted) {
508
+ effectFn();
509
+ this.hasMounted = true;
516
510
  }
517
- };
518
511
 
512
+ return{
513
+ cleanup: () => {
514
+ this.effects[this.name] = this.effects[this.name].filter(
515
+ (effect) => effect !== effectFn
516
+ );
517
+ },
518
+ }
519
+ }
519
520
  /**
520
- * @function render
521
- * @param {*} states
522
- * @param {*} props
523
- * @description Allows you to render component to DOM
524
- * @returns {HTMLcContent}
525
- * @returns
521
+ * @method $Function
522
+ * @description Allows you to create a function in global scope
523
+ * @returns {Function}
524
+ * @example
525
+ * let func = this.$Function(function add(e, a){
526
+ * return e + a
527
+ * })
528
+ * @param {*} fn
526
529
  */
530
+ $Function(fn) {
531
+ // @ts-ignore
532
+ if (!typeof fn === "function") {
533
+ throw new Error("fn must be a function");
534
+ }
535
+ let name = fn.name;
536
+ if (!name) {
537
+ name = "anonymous";
538
+ }
539
+ window[name] = fn;
540
+ // @ts-ignore
541
+ return `window.${name}()`;
542
+ }
527
543
 
528
- const render = async (props) => {
529
- let data = await vhtml(
530
- `<div data-component="${name}">${await options.render(
531
- states,
532
- props
533
- )}</div>`
534
- );
535
- storedProps = props;
544
+ // Add other methods like render, useEffect, useReducer, useAuth, etc.
536
545
 
537
- setTimeout(() => {
538
- options.componentDidMount ? options.componentDidMount() : null;
539
- }, 0);
546
+ updateComponent() {
547
+ Object.keys(components).forEach(async (component) => {
548
+ const { name } = components[component];
549
+ const componentContainer = document.querySelector(
550
+ `[data-component="${name}"]`
551
+ );
540
552
 
541
- return data;
542
- };
553
+ if (!componentContainer) return;
554
+ const newHtml = await new Function(
555
+ "useState",
556
+ "useEffect",
557
+ "useAuth",
558
+ "useReducer",
559
+ "useSyncStore",
560
+ "signal",
561
+ "rf",
562
+ "props",
563
+ "render",
564
+ "return `" + (await this.render()) + "`;"
565
+ )(
566
+ this.useState,
567
+ this.useEffect,
568
+ this.useAuth,
569
+ this.useReducer,
570
+ this.useSyncStore,
571
+ this.signal,
572
+ this.render
573
+ );
574
+ this.componentDidMount();
543
575
 
544
- return {
545
- render,
546
- setState,
547
- useState,
548
- useEffect,
549
- useAuth,
550
- useSyncStore,
551
- useReducer,
552
- runEffects,
553
- signal,
554
- };
576
+ if (newHtml && newHtml !== componentContainer.innerHTML) {
577
+ componentContainer.outerHTML = newHtml;
578
+ }
579
+ });
580
+ }
581
+ html(strings, ...args) {
582
+ let result = "";
583
+ for (let i = 0; i < strings.length; i++) {
584
+ result += strings[i];
585
+ if (i < args.length) {
586
+ result += args[i];
587
+ }
588
+ }
589
+ // add ref to all elements
590
+ let dom = new DOMParser().parseFromString(result, "text/html");
591
+ let elements = dom.body.querySelectorAll("*");
592
+ elements.forEach((element) => {
593
+ if (element.hasAttribute("ref")) {
594
+ dom[element.getAttribute("ref")] = element;
595
+ }
596
+ });
597
+ this.Componentcontent = result;
598
+ if (!result.includes("<div data-component")) {
599
+ result = `<div data-component="${this.name}">${result}</div>`;
600
+ }
601
+
602
+ return result;
603
+ }
604
+ async render() {
605
+ this.componentMounted = true;
606
+ this.componentDidMount();
607
+ return await new Function(`return \`${this.Componentcontent}\`;`)();
608
+ }
555
609
  }
556
610
 
611
+ /**
612
+ * @object Vader
613
+ * @property {class} Component
614
+ * @property {function} useRef
615
+ * @description Allows you to create a component
616
+ * @example
617
+ * import { Vader } from "../../dist/vader/vader.js";
618
+ * export class Home extends Vader.Component {
619
+ * constructor() {
620
+ * super('Home');
621
+ * }
622
+ * async render() {
623
+ * return this.html(`
624
+ * <div className="hero p-5">
625
+ * <h1>Home</h1>
626
+ * </div>
627
+ * `);
628
+ * }
629
+ */
630
+ export const Vader = {
631
+ Component: Component,
632
+ useRef: useRef,
633
+ };
634
+ export const component = (name) => {
635
+ return new Component()
636
+ };
637
+
557
638
  /**
558
639
  * @function rf
559
640
  * @param {*} name
560
641
  * @param {*} fn
561
- * @returns {null}
642
+ * @returns {void}
643
+ * @deprecated - rf has been replaced with Vader.Component.$Function
562
644
  * @description Allows you to register function in global scope
563
645
  */
564
646
  export const rf = (name, fn) => {
565
647
  window[name] = fn;
566
648
  };
649
+ let cache = {};
567
650
  /**
568
651
  * @function include
569
652
  * @description Allows you to include html file
570
653
  * @returns - modified string with html content
571
654
  * @param {string} path
655
+ * @param {Object} options
572
656
  */
573
657
 
574
- let cache = {};
575
658
  export const include = (path, options) => {
576
659
  if (cache[path]) {
577
660
  return new Function(`return \`${cache[path]}\`;`)();
@@ -585,8 +668,26 @@ export const include = (path, options) => {
585
668
  return res.text();
586
669
  })
587
670
  .then((data) => {
588
- cache[path] = data;
671
+ // Handle includes
672
+ let includes = data.match(/<include src="(.*)"\/>/gs);
673
+ if (includes) {
674
+ // Use Promise.all to fetch all includes concurrently
675
+ const includePromises = includes.map((e) => {
676
+ let includePath = e.match(/<include src="(.*)"\/>/)[1];
677
+ return include(includePath).then((includeData) => {
678
+ // Replace the include tag with the fetched data
679
+ data = data.replace(e, includeData);
680
+ });
681
+ });
589
682
 
590
- return new Function(`return \`${data}\`;`)();
683
+ // Wait for all includes to be fetched and replaced
684
+ return Promise.all(includePromises).then(() => {
685
+ cache[path] = data;
686
+ return new Function(`return \`${data}\`;`)();
687
+ });
688
+ } else {
689
+ cache[path] = data;
690
+ return new Function(`return \`${data}\`;`)();
691
+ }
591
692
  });
592
- };
693
+ };