vaderjs 1.1.4 → 1.1.6

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