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/.vscode/settings.json +3 -1
- package/images/router.png +0 -0
- package/images/state.png +0 -0
- package/index.js +12 -0
- package/jsconfig.json +14 -11
- package/logo.png +0 -0
- package/package.json +7 -4
- package/readme.md +144 -113
- package/tsconfig.json +18 -0
- package/vader.js +534 -431
- package/vaderRouter.js +93 -97
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
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
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
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
|
-
|
|
77
|
-
|
|
78
|
-
|
|
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
|
-
* @
|
|
146
|
-
* @description
|
|
127
|
+
* @method setState
|
|
128
|
+
* @description Allows you to set state
|
|
147
129
|
* @param {String} key
|
|
148
|
-
* @param {
|
|
149
|
-
* @returns
|
|
130
|
+
* @param {*} value
|
|
131
|
+
* @returns {void}
|
|
150
132
|
* @example
|
|
151
|
-
*
|
|
152
|
-
*
|
|
153
|
-
|
|
154
|
-
|
|
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
|
-
* @
|
|
200
|
-
* @
|
|
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
|
-
* @
|
|
221
|
-
* @
|
|
222
|
-
* @
|
|
223
|
-
* @
|
|
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
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
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
|
-
}
|
|
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
|
-
*
|
|
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
|
-
|
|
296
|
-
|
|
200
|
+
this.$_signal_subscribe = (fn, runonce) => {
|
|
201
|
+
this.$_signal_subscribers.push({
|
|
202
|
+
function: fn,
|
|
203
|
+
runonce: runonce,
|
|
204
|
+
});
|
|
297
205
|
};
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
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
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
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
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
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
|
-
*
|
|
332
|
-
*
|
|
333
|
-
* @param {
|
|
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
|
-
|
|
337
|
-
|
|
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
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
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
|
-
* @
|
|
355
|
-
* @
|
|
356
|
-
* @param {
|
|
357
|
-
* @
|
|
358
|
-
* @
|
|
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
|
-
|
|
363
|
-
|
|
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
|
-
*
|
|
390
|
-
*
|
|
391
|
-
* @param {
|
|
392
|
-
* @returns {
|
|
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
|
-
*
|
|
399
|
-
*
|
|
400
|
-
* @param {
|
|
401
|
-
* @
|
|
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
|
|
464
|
-
* @
|
|
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
|
-
|
|
468
|
-
if (!
|
|
469
|
-
effects[name]
|
|
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
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
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
|
-
* @
|
|
521
|
-
* @
|
|
522
|
-
* @
|
|
523
|
-
* @
|
|
524
|
-
*
|
|
525
|
-
*
|
|
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
|
-
|
|
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
|
-
|
|
538
|
-
|
|
539
|
-
|
|
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
|
-
|
|
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
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
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 {
|
|
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
|
-
|
|
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
|
-
|
|
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
|
+
};
|