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/.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 +8 -12
- package/logo.png +0 -0
- package/package.json +8 -4
- package/readme.md +144 -113
- package/vader.js +532 -431
- package/vaderRouter.js +62 -88
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
|
-
|
|
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");
|
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
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
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
* @
|
|
146
|
-
* @description
|
|
125
|
+
* @method setState
|
|
126
|
+
* @description Allows you to set state
|
|
147
127
|
* @param {String} key
|
|
148
|
-
* @param {
|
|
149
|
-
* @returns
|
|
128
|
+
* @param {*} value
|
|
129
|
+
* @returns {void}
|
|
150
130
|
* @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
|
-
};
|
|
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
|
-
* @
|
|
200
|
-
* @
|
|
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
|
-
* @
|
|
221
|
-
* @
|
|
222
|
-
* @
|
|
223
|
-
* @
|
|
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
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
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
|
-
}
|
|
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
|
-
*
|
|
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
|
-
|
|
296
|
-
|
|
198
|
+
this.$_signal_subscribe = (fn, runonce) => {
|
|
199
|
+
this.$_signal_subscribers.push({
|
|
200
|
+
function: fn,
|
|
201
|
+
runonce: runonce,
|
|
202
|
+
});
|
|
297
203
|
};
|
|
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);
|
|
204
|
+
this.$_signal_cleanup = (fn) => {
|
|
205
|
+
this.$_signal_subscribers = this.$_signal_subscribers.filter(
|
|
206
|
+
(subscriber) => subscriber.function !== fn
|
|
207
|
+
);
|
|
311
208
|
};
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
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
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
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
|
-
*
|
|
332
|
-
*
|
|
333
|
-
* @param {
|
|
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
|
-
|
|
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
|
-
}
|
|
235
|
+
this.$_signal_set = (detail) => {
|
|
236
|
+
setState(detail);
|
|
343
237
|
};
|
|
344
238
|
|
|
345
239
|
return {
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
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
|
-
* @
|
|
355
|
-
* @
|
|
356
|
-
* @param {
|
|
357
|
-
* @
|
|
358
|
-
* @
|
|
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
|
-
|
|
363
|
-
|
|
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
|
-
*
|
|
390
|
-
*
|
|
391
|
-
* @param {
|
|
392
|
-
* @returns {
|
|
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
|
-
*
|
|
399
|
-
*
|
|
400
|
-
* @param {
|
|
401
|
-
* @
|
|
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
|
|
464
|
-
* @
|
|
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
|
-
|
|
468
|
-
if (!
|
|
469
|
-
effects[name]
|
|
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
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
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
|
-
* @
|
|
521
|
-
* @
|
|
522
|
-
* @
|
|
523
|
-
* @
|
|
524
|
-
*
|
|
525
|
-
*
|
|
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
|
-
|
|
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
|
-
|
|
538
|
-
|
|
539
|
-
|
|
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
|
-
|
|
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
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
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 {
|
|
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
|
-
|
|
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
|
-
|
|
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
|
+
};
|