vaderjs 1.3.6 → 1.3.7-alpha-1

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 DELETED
@@ -1,1141 +0,0 @@
1
- // @ts-nocheck
2
- let dom = [];
3
- let states = {};
4
-
5
- let worker = new Worker(new URL("./worker.js", import.meta.url));
6
-
7
- /**
8
- * @function useRef
9
- * @description Allows you to get reference to DOM element
10
- * @param {String} ref
11
- * @returns {void | Object} {current, update}
12
- */
13
-
14
- export const useRef = (ref) => {
15
- const element = document.querySelector(`[ref="${ref}"]`);
16
- const getElement = () => element;
17
-
18
- const update = (data) => {
19
- const newDom = new DOMParser().parseFromString(data, "text/html");
20
- const newElement = newDom.body.firstChild;
21
-
22
- if (element) {
23
- // @ts-ignore
24
- const isDifferent = !newElement.isEqualNode(element);
25
- if (isDifferent) {
26
- // @ts-ignore
27
- element.parentNode.replaceChild(newElement, element);
28
- }
29
- }
30
- };
31
-
32
- return {
33
- current: getElement(),
34
- update
35
- };
36
- };
37
-
38
- let components = [];
39
- /**
40
- * @class Component
41
- * @description Allows you to create a component
42
- * @returns {void}
43
- * @example
44
- * import { Vader } from "../../dist/vader/index.js";
45
- * export class Home extends Vader.Component {
46
- * constructor() {
47
- * super();
48
- * }
49
- * async render() {
50
- * return this.html(`
51
- * <div className="hero p-5">
52
- * <h1>Home</h1>
53
- * </div>
54
- * `);
55
- * }
56
- * }
57
- */
58
- export class Component {
59
- constructor() {
60
- this.states = {};
61
- //@ts-ignore
62
- this.name = this.constructor.name;
63
- this.executedEffects = {};
64
- this.storedProps = {};
65
- this.componentMounted = false;
66
- this.hasMounted = false;
67
- this.$_signal_subscribers = [];
68
- /**
69
- * @property {Array} $_signal_subscribers_ran
70
- * @description Allows you to keep track of signal subscribers
71
- * @private
72
- */
73
- this.$_signal_subscribers_ran = [];
74
- this.effects = {};
75
- this.$_useStore_subscribers = [];
76
- this.init();
77
- this.Componentcontent = null;
78
- this.$_signal_dispatch_event = new CustomEvent("SignalDispatch", {
79
- detail: {
80
- hasUpdated: false,
81
- state: null
82
- }
83
- });
84
- /**
85
- * @property {Object} $_signal_dispatch_cleanup_event
86
- * @description Allows you to dispatch a signal cleanup event
87
- * @private
88
- */
89
- this.$_signal_dispatch_cleanup_event = new CustomEvent(
90
- "Signal_Cleanup_Dispatch",
91
- {
92
- detail: {
93
- state: null,
94
- lastState: null
95
- }
96
- }
97
- );
98
- /**
99
- * @property {Array} snapshots
100
- * @private
101
- */
102
- this.snapshots = [];
103
- /**
104
- * @property {Object} dom
105
- * @description Allows you to get reference to DOM element
106
- * @returns {void | HTMLElement}
107
- *
108
- */
109
- this.dom = [];
110
-
111
- /**
112
- * @property {Boolean} cfr
113
- * @description Allows you to compile html code on the fly - client fly rendering
114
- *
115
- */
116
- this.cfr = false;
117
- /**
118
- * @property {Boolean} worker
119
- * @description Allows you to use a web worker to compile html code on the fly - client fly rendering
120
-
121
- */
122
- }
123
-
124
- /**
125
- * @method adapter
126
- * @description Allows you to create an adapter - this is used to create custom logic
127
- *
128
- *
129
- */
130
- adapter(options) {
131
- // allow you to override the compoent logic
132
- }
133
- init() {
134
- this.registerComponent();
135
- }
136
-
137
- registerComponent() {
138
- components.push(this);
139
- }
140
-
141
- /**
142
- * @method setState
143
- * @description Allows you to set state
144
- * @param {String} key
145
- * @param {*} value
146
- * @returns {void}
147
- * @example
148
- * this.setState('count', 1)
149
- * */
150
- setState(key, value) {
151
- this.states[key] = value;
152
- this.updateComponent();
153
- }
154
- /**
155
- * @method componentUnmount
156
- * @description Allows you to run code after component has unmounted
157
- * @type {VoidFunction}
158
- * @returns {void}
159
- */
160
- unmount() {
161
- this.componentMounted = false;
162
- this.componentWillUnmount();
163
- // @ts-ignore
164
- document.querySelector(`[data-component="${this.name}"]`).remove();
165
- if (!document.querySelector(`[data-component="${this.name}"]`)) {
166
- components = components.filter(
167
- (component) => component.name !== this.name
168
- );
169
- }
170
- }
171
-
172
- /**
173
- * @method componentUpdate
174
- * @description Allows you to run code after component has updated
175
- * @param {Object} prev_state
176
- * @param {Object} prev_props
177
- * @param {Object} snapshot
178
- * @returns {void}
179
- * @example
180
- * componentUpdate(prev_state, prev_props, snapshot) {
181
- * console.log(prev_state, prev_props, snapshot)
182
- * }
183
- * */
184
- componentUpdate(prev_state, prev_props, snapshot) {}
185
- /**
186
- * @method componentDidMount
187
- * @description Allows you to run code after component has mounted
188
- */
189
- componentDidMount() {}
190
-
191
- /**
192
- * @method componentWillUnmount
193
- * @description Allows you to run code before component unmounts
194
- * @type {VoidFunction}
195
- * @returns {void}
196
- */
197
- componentWillUnmount() {}
198
-
199
- /**
200
- * @method signal
201
- * @description Allows you to create a signal
202
- * @param {String} key
203
- * @param {any} initialState
204
- * @returns {Object} {subscribe, cleanup, dispatch, call, set, get}
205
- * @example
206
- * let signal = this.signal('count', 0);
207
- * signal.subscribe((value) => {
208
- * console.log(value)
209
- * }, false) // false means it will run every time
210
- * signal.subscribe((value) => {
211
- * console.log(value)
212
- * }, true) // true means it will run once
213
- * signal.call() // this will call all subscribers
214
- * signal.set(1) // this will set the value of the signal
215
- * signal.get() // this will get the value of the signal
216
- * signal.cleanup() // this will remove all subscribers
217
- */
218
- signal = (key, initialState) => {
219
- let hasCaller = false;
220
- let [state, setState] = this.useState(key, initialState, () => {
221
- if (this.$_signal_subscribers.length > 0) {
222
- for (var i = 0; i < this.$_signal_subscribers.length; i++) {
223
- if (!hasCaller) {
224
- if (
225
- this.$_signal_subscribers[i].runonce &&
226
- // @ts-ignore
227
- this.$_signal_subscribers_ran.includes(
228
- this.$_signal_subscribers[i]
229
- )
230
- ) {
231
- break;
232
- } else {
233
- this.$_signal_subscribers[i].function(state);
234
- this.$_signal_subscribers_ran.push(this.$_signal_subscribers[i]);
235
- return;
236
- }
237
- }
238
- }
239
- } else {
240
- this.$_signal_dispatch_event.detail.hasUpdated = true;
241
- this.$_signal_dispatch_event.detail.state = state;
242
- window.dispatchEvent(this.$_signal_dispatch_event);
243
- }
244
- });
245
- /**
246
- * @function $_signal_subscribe
247
- * @description Allows you to subscribe to a signal
248
- * @param {*} fn
249
- * @param {*} runonce
250
- * @returns {void}
251
- *
252
- */
253
- this.$_signal_subscribe = (fn, runonce) => {
254
- this.$_signal_subscribers.push({
255
- function: fn,
256
- runonce: runonce
257
- });
258
- return fn;
259
- };
260
- this.$_signal_cleanup = (fn) => {
261
- this.lastState = state;
262
- this.$_signal_subscribers = this.$_signal_subscribers.filter(
263
- (subscriber) => subscriber.function !== fn
264
- );
265
- // @ts-ignore
266
- this.$_signal_dispatch_cleanup_event.detail.state = this.states;
267
- // @ts-ignore
268
- this.$_signal_dispatch_cleanup_event.detail.lastState = this.lastState;
269
- window.dispatchEvent(this.$_signal_dispatch_event);
270
- };
271
- this.$_signal_dispatch = () => {
272
- for (var i = 0; i < this.$_signal_subscribers.length; i++) {
273
- if (
274
- this.$_signal_subscribers[i].runonce &&
275
- // @ts-ignore
276
- this.$_signal_subscribers_ran.includes(this.$_signal_subscribers[i])
277
- ) {
278
- break;
279
- } else {
280
- this.$_signal_subscribers[i].function(state);
281
- this.$_signal_subscribers_ran.push(this.$_signal_subscribers[i]);
282
- }
283
- }
284
- };
285
- this.$_signal_get = () => {
286
- return state;
287
- };
288
- this.$_signal_call = () => {
289
- hasCaller = true;
290
- // @ts-ignore
291
- this.$_signal_dispatch();
292
- };
293
- /**
294
- * @function $_signal_set
295
- * @description Allows you to set the value of a signal
296
- * @param {*} detail
297
- */
298
- this.$_signal_set = (detail) => {
299
- setState(detail);
300
- };
301
-
302
- return {
303
- /**
304
- * @function subscribe
305
- * @description Allows you to subscribe to a signal
306
- * @param {*} fn
307
- * @param {*} runonce
308
- */
309
- subscribe: this.$_signal_subscribe,
310
- /**
311
- * @function cleanup
312
- * @description Allows you to cleanup a signal
313
- * @param {*} fn
314
- * @returns {null}
315
- */
316
- cleanup: this.$_signal_cleanup,
317
- /**
318
- * @function dispatch
319
- * @description Allows you to dispatch a signal
320
- * @returns {null}
321
- */
322
- dispatch: this.$_signal_dispatch,
323
- /**
324
- * @function call
325
- * @description Allows you to call a signal
326
- * @returns {null}
327
- */
328
- call: this.$_signal_call,
329
- /**
330
- * @function set
331
- * @description Allows you to set the value of a signal
332
- * @param {*} detail
333
- * @returns {null}
334
- */
335
- set: this.$_signal_set,
336
- /**
337
- * @function get
338
- * @readonly
339
- * @description Allows you to get the value of a signal
340
- * @returns {any}
341
- */
342
- get: this.$_signal_get
343
- };
344
- };
345
- /**
346
- * @method useAuth
347
- * @description Allows you to create an auth object
348
- * @param {Object} options
349
- * @param {Array} options.rulesets
350
- * @param {Object} options.user
351
- * @returns {Object} {can, hasRole, canWithRole, assignRule, revokeRule, canAnyOf, canAllOf, canGroup}
352
- * @example
353
- * let auth = this.useAuth({
354
- * rulesets: [
355
- * {
356
- * action: 'create',
357
- * condition: (user) => {
358
- * return user.role === 'admin'
359
- * }
360
- * }
361
- * ],
362
- * user: {
363
- * role: 'admin'
364
- * }
365
- * })
366
- * auth.can('create') // true
367
- */
368
- useAuth(options) {
369
- /**@type {Array}**/
370
- let rules = options.rulesets;
371
- if (!rules) {
372
- throw new Error("No rulesets provided");
373
- }
374
- /**@type {Object}**/
375
- let user = options.user;
376
- let auth = {
377
- can: (action) => {
378
- let can = false;
379
- rules.forEach((rule) => {
380
- if (rule.action === action) {
381
- if (rule.condition(user)) {
382
- can = true;
383
- }
384
- }
385
- });
386
- return can;
387
- },
388
- /**
389
- * @function hasRole
390
- * @description Allows you to check if user has a role
391
- * @param {String} role
392
- * @returns {Boolean}
393
- */
394
- hasRole: (role) => {
395
- return user.role && user.role.includes(role);
396
- },
397
- /**
398
- * @function canWithRole
399
- * @param {String} action
400
- * @param {String} role
401
- * @returns
402
- */
403
- canWithRole: (action, role) => {
404
- return auth.can(action) && auth.hasRole(role);
405
- },
406
- assignRule: (rule) => {
407
- if (
408
- !rules.some((existingRule) => existingRule.action === rule.action)
409
- ) {
410
- rules.push(rule);
411
- }
412
- },
413
- revokeRule: (action) => {
414
- rules = rules.filter((rule) => rule.action !== action);
415
- },
416
- canAnyOf: (actions) => {
417
- return actions.some((action) => auth.can(action));
418
- },
419
- canAllOf: (actions) => {
420
- return actions.every((action) => auth.can(action));
421
- },
422
- canGroup: (actions, logicalOperator = "any") => {
423
- return logicalOperator === "any"
424
- ? auth.canAnyOf(actions)
425
- : auth.canAllOf(actions);
426
- }
427
- };
428
- return auth;
429
- }
430
- /**
431
- * @method useReducer
432
- * @description Allows you to create a reducer
433
- * @param {*} key
434
- * @param {*} reducer
435
- * @param {*} initialState
436
- * @url - useReducer works similarly to - https://react.dev/reference/react/useReducer
437
- * @returns {Array} [state, dispatch]
438
- * @example
439
- * let [count, setCount] = this.useReducer('count', (state, action) => {
440
- * switch (action.type) {
441
- * case 'increment':
442
- * return state + 1
443
- * case 'decrement':
444
- * return state - 1
445
- * default:
446
- * throw new Error()
447
- * }
448
- * }, 0)
449
- * setCount({type: 'increment'})
450
- */
451
- useReducer(key, reducer, initialState) {
452
- if (!this.states[key]) {
453
- this.states[key] = initialState;
454
- }
455
- return [
456
- this.states[key],
457
- /**
458
- * @function dispatch
459
- * @description Allows you to dispatch a reducer
460
- * @param {*} action
461
- * @returns {void}
462
- */
463
- (action) => {
464
- this.states[key] = reducer(action);
465
- this.updateComponent();
466
- }
467
- ];
468
- }
469
-
470
- runEffects() {
471
- Object.keys(this.effects).forEach((component) => {
472
- this.effects[component].forEach((effect) => {
473
- if (!this.executedEffects[effect]) {
474
- effect();
475
- this.executedEffects[effect] = true;
476
- }
477
- });
478
- });
479
- }
480
-
481
- /**
482
- * @method useSyncStore
483
- * @description Allows you to create a store
484
- * @param {String} storeName
485
- * @param {*} initialState
486
- * @returns {Object} {getField, setField, subscribe, clear}
487
- * @example
488
- * let store = this.useSyncStore('store', {count: 0})
489
- * store.setField('count', 1)
490
- * store.getField('count') // 1
491
- *
492
- */
493
- useSyncStore(storeName, initialState) {
494
- let [storedState, setStoredState] = this.useState(
495
- storeName,
496
- initialState ||
497
- // @ts-ignore
498
- localStorage.getItem(`$_vader_${storeName}`, (s) => {
499
- localStorage.setItem(`$_vader_${storeName}`, JSON.stringify(s));
500
- this.$_useStore_subscribers.forEach((subscriber) => {
501
- subscriber(s);
502
- });
503
- }) ||
504
- {}
505
- );
506
-
507
- const getField = (fieldName) => {
508
- return storedState[fieldName];
509
- };
510
- const setField = (fieldName, value) => {
511
- const newState = { ...storedState, [fieldName]: value };
512
- setStoredState(newState);
513
- };
514
- const subscribe = (subscriber) => {
515
- return this.$_useStore_subscribers.push(subscriber);
516
- };
517
-
518
- const clear = (fieldName) => {
519
- let newState = storedState;
520
- delete newState[fieldName];
521
- setStoredState(newState);
522
- };
523
- return {
524
- getField,
525
- setField,
526
- subscribe,
527
- clear
528
- };
529
- }
530
- /**
531
- * @method useState
532
- * @description Allows you to create a state
533
- * @param {String} key
534
- * @param {*} initialValue
535
- * @param {*} callback
536
- * @url - useState works similarly to - https://react.dev/reference/react/useState
537
- * @returns {Array} [state, setState]
538
- * @example
539
- * let [count, setCount] = this.useState('count', 0, () => {
540
- * console.log('count has been updated')
541
- * })
542
- *
543
- * setCount(count + 1)
544
- */
545
- useState(key, initialValue, callback = null) {
546
- if (!this.states[key]) {
547
- this.states[key] = initialValue;
548
- }
549
-
550
- return [
551
- this.states[key],
552
- /**
553
- * @function setState
554
- * @description Allows you to set state
555
- * @param {*} value
556
- * @returns {void}
557
- */
558
- (value) => {
559
- this.states[key] = value;
560
- this.updateComponent();
561
- // @ts-ignore
562
- typeof callback === "function" ? callback() : null;
563
- }
564
- ];
565
- }
566
- /**
567
- * @method useRef
568
- * @memberof Component
569
- * @param {string} ref
570
- * @description Allows you to get reference to DOM elements from the dom array
571
- * @returns {Object} {current, update}
572
- * @example
573
- * let ref = this.useRef('ref')
574
- * ref.current // returns the DOM element
575
-
576
- */
577
-
578
- useRef(ref) {
579
- // get ref from array
580
- const element = this.dom[ref];
581
-
582
- const getElement = element;
583
-
584
- const update = (data) => {
585
- const newDom = new DOMParser().parseFromString(data, "text/html");
586
- const newElement = newDom.body.firstChild;
587
-
588
- if (element) {
589
- // @ts-ignore
590
- const isDifferent = !newElement.isEqualNode(element);
591
- if (isDifferent) {
592
- // @ts-ignore
593
- element.parentNode.replaceChild(newElement, element);
594
- }
595
- }
596
- };
597
-
598
- return {
599
- /**@type {HTMLElement} */
600
- // @ts-ignore
601
- current: getElement,
602
- /**@type {Function} */
603
- update
604
- };
605
- }
606
-
607
- /**
608
- *
609
- * @function useEffect
610
- * @param {*} effectFn
611
- * @param {*} dependencies
612
- * @description Allows you to run side effects
613
- * @deprecated - this is no longer suggested please use vader signals instead
614
- * @returns {Object} {cleanup}
615
- */
616
- useEffect(effectFn, dependencies) {
617
- if (!this.effects[this.name]) {
618
- this.effects[this.name] = [];
619
- }
620
- this.effects[this.name].push(effectFn);
621
-
622
- if (dependencies.length > 0) {
623
- dependencies.forEach((d) => {
624
- if (d.set) {
625
- throw new Error(
626
- "signal found, do not use effect and signals at the same time - signals are more efficient"
627
- );
628
- }
629
- });
630
- } else if (!this.hasMounted) {
631
- effectFn();
632
- this.hasMounted = true;
633
- }
634
-
635
- return {
636
- cleanup: () => {
637
- // @ts-ignore
638
- this.effects[this.name] = this.effects[this.name].filter(
639
- // @ts-ignore
640
- (effect) => effect !== effectFn
641
- );
642
- }
643
- };
644
- }
645
- /**
646
- * @method $Function
647
- * @description Allows you to create a function in global scope
648
- * @returns {Function}
649
- * @example
650
- * let func = this.$Function(function add(e, a){
651
- * return e + a
652
- * })
653
- * @param {*} fn
654
- */
655
- $Function(fn) {
656
- // @ts-ignore
657
- if (!typeof fn === "function") {
658
- throw new Error("fn must be a function");
659
- }
660
- let name = fn.name;
661
- if (!name) {
662
- name = "anonymous" + Math.floor(Math.random() * 100000000000000000);
663
- }
664
- window[name] = fn;
665
- // @ts-ignore
666
- return `window.${name}()`;
667
- }
668
-
669
- // Add other methods like render, useEffect, useReducer, useAuth, etc.
670
-
671
- updateComponent() {
672
- const fragment = document.createDocumentFragment();
673
- Object.keys(components).forEach(async (component) => {
674
- const { name } = components[component];
675
-
676
- let componentContainer = document.querySelector(
677
- `[data-component="${name}"]`
678
- );
679
- let time = new Date().getTime();
680
- /**
681
- * @property {Object} snapshot
682
- * @description Allows you to keep track of component snapshots
683
- * @private
684
- * @returns {Object} {name, time, prev_state, prev_props, content}
685
- */
686
- let snapshot = {
687
- name: name,
688
- time: time,
689
- prev_state: this.states,
690
- prev_props: this.storedProps,
691
- // @ts-ignore
692
- content: componentContainer.innerHTML
693
- };
694
-
695
- if (!componentContainer) return;
696
- const useState = this.useState.bind(this); // Bind the component's context
697
- const useEffect = this.useEffect.bind(this); // Bind the component's context
698
- const useReducer = this.useReducer.bind(this); // Bind the component's context
699
- const useAuth = this.useAuth.bind(this); // Bind the component's context
700
- const useSyncStore = this.useSyncStore.bind(this); // Bind the component's context
701
- const signal = this.signal.bind(this); // Bind the component's context
702
- const $Function = this.$Function.bind(this); // Bind the component's context
703
- const useRef = this.useRef.bind(this); // Bind the component's context
704
-
705
-
706
-
707
- let newHtml = new Function(
708
- "useState",
709
- "useEffect",
710
- "useAuth",
711
- "useReducer",
712
- "useSyncStore",
713
- "signal",
714
- "$Function",
715
- "dom",
716
- "render",
717
- "state",
718
- "useRef",
719
- "return" + "`" + await this.render() + "`"
720
- )(
721
- useState,
722
- useEffect,
723
- useAuth,
724
- useReducer,
725
- useSyncStore,
726
- signal,
727
- $Function,
728
- dom,
729
- this.render,
730
- this.states,
731
- useRef
732
- );
733
-
734
- // compare new html with old html
735
- let hdom = new DOMParser().parseFromString(newHtml, "text/html");
736
- if (hdom.body.innerHTML !== componentContainer.innerHTML) {
737
- if (this.snapshots.length > 0) {
738
- let lastSnapshot = this.snapshots[this.snapshots.length - 1];
739
- if (lastSnapshot !== snapshot) {
740
- this.snapshots.push(snapshot);
741
- }
742
- } else {
743
- this.snapshots.push(snapshot);
744
- }
745
- this.componentUpdate(
746
- snapshot.prev_state,
747
- snapshot.prev_props,
748
- snapshot.content
749
- );
750
- // batch updates
751
- fragment.appendChild(
752
- document.createRange().createContextualFragment(newHtml)
753
- );
754
- componentContainer.innerHTML = "";
755
- componentContainer.replaceWith(fragment);
756
- this.runEffects();
757
- }
758
- });
759
- }
760
- /**
761
- * @method validateClassName
762
- * @param {String} className
763
- * @private
764
- * @returns {Boolean}
765
- */
766
- validateClassName(className) {
767
- // validate classNames ensure they are camelCase but also allow for - and _
768
- return /^[a-zA-Z0-9-_]+$/.test(className);
769
- }
770
-
771
- /**
772
- * The `html` method generates and processes HTML content for a component, performing various validations and tasks.
773
- *
774
- * @param {String} strings - The HTML content to be processed.
775
- * @param {...any} args - Dynamic values to be inserted into the template.
776
- * @returns {string} - The processed HTML content as a string.
777
- *
778
- * @throws {SyntaxError} - Throws a `SyntaxError` if image-related attributes are missing or invalid.
779
- * @throws {Error} - Throws an `Error` if there are issues with class names or relative paths.
780
- *
781
- * @example
782
- * // Example usage within a component:
783
- * const myComponent = new Component();
784
- * const htmlContent = myComponent.html`
785
- * <div>
786
- * <img src="/images/example.jpg" alt="Example Image" />
787
- * </div>
788
- * `;
789
- * document.body.innerHTML = htmlContent;
790
- *
791
- * @remarks
792
- * The `html` method is a core function used in component rendering. It allows you to define and generate HTML content within your component while enforcing best practices and accessibility standards. The method performs several essential tasks:
793
- *
794
- * 1. **Image Validation**: It checks images for the presence of 'alt' attributes and their validity.
795
- * - Throws a `SyntaxError` if an image is missing the 'alt' attribute.
796
- * - Throws a `SyntaxError` if the 'alt' attribute is empty.
797
- * - Checks for an 'aria-hidden' attribute for image elements.
798
- *
799
- * 2. **Class Attribute Handling**: It enforces class attribute usage and allows optional configuration via comments.
800
- * - Throws an `Error` if 'class' attributes are used without permission.
801
- * - Supports 'className' attributes for class definitions.
802
- * - Allows or disallows class-related comments based on your configuration.
803
- *
804
- * 3. **Relative Path Handling**: It processes relative paths in 'href' and 'src' attributes, ensuring proper routing.
805
- * - Converts relative 'href' attributes to anchor links with appropriate routing.
806
- * - Converts relative 'src' attributes to absolute paths with 'public' directories.
807
- *
808
- * 4. **Custom Component Attributes**: It supports adding a 'data-component' attribute to the root element.
809
- * - Ensures that the 'data-component' attribute is present for component identification.
810
- *
811
- * 5. **Lifecycle Method Invocation**: It invokes the `componentDidMount` method if called from a 'render' context.
812
- * - Executes `componentDidMount` to handle component initialization once the DOM is ready.
813
- *
814
- * @see {@link Component}
815
- * @see {@link Component#componentDidMount}
816
- */
817
-
818
- html(strings, ...args) {
819
- // @ts-ignore
820
- let timer = setInterval(() => {
821
- if (document.querySelector(`[data-component="${this.name}"]`)) {
822
- clearInterval(timer);
823
- this.componentMounted = true;
824
-
825
- document
826
- .querySelector(`[data-component="${this.name}"]`)
827
- ?.querySelectorAll("*")
828
- .forEach((element) => {
829
- if (element.hasAttribute("ref")) {
830
- // @ts-ignore
831
- this.dom[element.getAttribute("ref")] = element;
832
- }
833
- });
834
- this.componentDidMount();
835
- }
836
- }, 100);
837
- let script = document.createElement("script");
838
- script.setAttribute("type", "text/javascript");
839
- script.setAttribute(`data-component-script`, this.name);
840
-
841
- worker.postMessage({
842
- strings,
843
- args,
844
- location:
845
- window.location.origin +
846
- window.location.pathname.replace(/\/[^\/]*$/, "") +
847
- "/public/",
848
- name: this.name
849
- });
850
- let promise = new Promise((resolve, reject) => {
851
- worker.onmessage = (e) => {
852
- if (e.data.error) {
853
- throw new Error(e.data.error);
854
- }
855
- const dom = this.dom; // Assuming this.dom is an object
856
- let js = e.data.js;
857
- let template = e.data.template;
858
- // Bind the component's context
859
-
860
- const useState = this.useState.bind(this); // Bind the component's context
861
- const useEffect = this.useEffect.bind(this); // Bind the component's context
862
- const useReducer = this.useReducer.bind(this); // Bind the component's context
863
- const useAuth = this.useAuth.bind(this); // Bind the component's context
864
- const useSyncStore = this.useSyncStore.bind(this); // Bind the component's context
865
- const signal = this.signal.bind(this); // Bind the component's context
866
- const $Function = this.$Function.bind(this); // Bind the component's context
867
- let states = this.states;
868
- const useRef = this.useRef.bind(this); // Bind the component's context
869
-
870
-
871
- let interval = setInterval(() => {
872
- if(this.componentMounted){
873
- clearInterval(interval)
874
- new Function(
875
- "useState",
876
- "useEffect",
877
- "useAuth",
878
- "useReducer",
879
- "useSyncStore",
880
- "signal",
881
- "$Function",
882
- "dom",
883
- "render",
884
- "state",
885
- "useRef",
886
- js
887
- )(
888
- useState,
889
- useEffect,
890
- useAuth,
891
- useReducer,
892
- useSyncStore,
893
- signal,
894
- $Function,
895
- dom,
896
- this.render,
897
- this.states,
898
- useRef
899
- );
900
- }
901
- }, 100);
902
-
903
- resolve(
904
- handletemplate( new Function(
905
- "useRef",
906
- "state",
907
- "signal",
908
- "useState",
909
- "useReducer",
910
- "useAuth",
911
- "useSyncStore",
912
- "useRef",
913
- "$Function",
914
- "return" + "`" + template + "`"
915
- )(
916
- useRef,
917
- states,
918
- signal,
919
- useState,
920
- useReducer,
921
- useAuth,
922
- useSyncStore,
923
- useRef,
924
- $Function
925
- ))
926
- );
927
- };
928
- worker.onerror = (e) => {
929
- reject(e);
930
- };
931
- });
932
- // @ts-ignore
933
- return promise;
934
- }
935
- // write types to ensure it returns a string
936
- /**
937
- * @method render
938
- * @description Allows you to render html
939
- * @returns {Promise <any>}
940
- * @example
941
- * async render() {
942
- * return this.html(`
943
- * <div className="hero p-5">
944
- * <h1>Home</h1>
945
- * </div>
946
- * `);
947
- */
948
- async render(props) {}
949
- }
950
-
951
- /**
952
- * @object Vader
953
- * @property {class} Component
954
- * @property {function} useRef
955
- * @description Allows you to create a component
956
- * @example
957
- * import { Vader } from "../../dist/vader/vader.js";
958
- * export class Home extends Vader.Component {
959
- * constructor() {
960
- * super('Home');
961
- * }
962
- * async render() {
963
- * return this.html(`
964
- * <div className="hero p-5">
965
- * <h1>Home</h1>
966
- * </div>
967
- * `);
968
- * }
969
- */
970
- const Vader = {
971
- /**
972
- * @class Component
973
- * @description Allows you to create a component
974
- * @returns {void}
975
- * @memberof {Vader}
976
- * @example
977
- * import { Vader } from "../../dist/vader/index.js";
978
- * export class Home extends Vader.Component {
979
- * constructor() {
980
- * super();
981
- * }
982
- * async render() {
983
- * return this.html(`
984
- * <div className="hero p-5">
985
- * <h1>Home</h1>
986
- * </div>
987
- * `);
988
- * }
989
- * }
990
- */
991
- Component: Component,
992
- useRef: useRef
993
- };
994
- /**
995
- * @function component
996
- * @description Allows you to create a component
997
- * @returns {Component}
998
- */
999
- export const component = () => {
1000
- return new Component();
1001
- };
1002
-
1003
- /**
1004
- * @function rf
1005
- * @param {*} name
1006
- * @param {*} fn
1007
- * @returns {void}
1008
- * @deprecated - rf has been replaced with Vader.Component.$Function
1009
- * @description Allows you to register function in global scope
1010
- */
1011
- export const rf = (name, fn) => {
1012
- window[name] = fn;
1013
- };
1014
- let cache = {};
1015
- async function handletemplate(data) {
1016
- let dom = new DOMParser().parseFromString(data, "text/html");
1017
- let elements = dom.documentElement.querySelectorAll("*");
1018
-
1019
- if (elements.length > 0) {
1020
- for (var i = 0; i < elements.length; i++) {
1021
- if (elements[i].nodeName === "INCLUDE") {
1022
- if (
1023
- !elements[i].getAttribute("src") ||
1024
- elements[i].getAttribute("src") === ""
1025
- ) {
1026
- throw new Error("Include tag must have src attribute");
1027
- }
1028
-
1029
- let componentName = elements[i]
1030
- .getAttribute("src")
1031
- ?.split("/")
1032
- .pop()
1033
- ?.split(".")[0];
1034
- // @ts-ignore
1035
- let filedata = await include(elements[i].getAttribute("src"));
1036
- // replace ` with \`\` to allow for template literals
1037
- filedata = filedata.replace(/`/g, "\\`");
1038
- cache[elements[i].getAttribute("src")] = filedata;
1039
- filedata = new Function(`return \`${filedata}\`;`)();
1040
- let newdom = new DOMParser().parseFromString(filedata, "text/html");
1041
-
1042
- newdom.querySelectorAll("include").forEach((el) => {
1043
- el.remove();
1044
- });
1045
- // @ts-ignore
1046
-
1047
- let els = dom.querySelectorAll(componentName);
1048
-
1049
- els.forEach((el) => {
1050
- if (el.attributes.length > 0) {
1051
- for (var i = 0; i < el.attributes.length; i++) {
1052
- // @ts-ignore
1053
- let t = "{{" + el.attributes[i].name + "}}";
1054
- if (newdom.body.innerHTML.includes(t)) {
1055
- // @ts-ignore
1056
- newdom.body.innerHTML = newdom.body.innerHTML.replaceAll(
1057
- t,
1058
- el.attributes[i].value
1059
- );
1060
- }
1061
- }
1062
- }
1063
- if (el.children.length > 0 && newdom.body.querySelector("slot")) {
1064
- for (var i = 0; i < el.children.length; i++) {
1065
- let slots = newdom.body.querySelectorAll("slot");
1066
- slots.forEach((slot) => {
1067
- let id = slot.getAttribute("id");
1068
-
1069
- if (
1070
- (el.hasAttribute("key") && el.getAttribute("key") === id) ||
1071
- (!el.hasAttribute("key") && el.nodeName === id)
1072
- ) {
1073
- if (el.children[i].innerHTML.length > 0) {
1074
- slot.outerHTML = el.children[i].innerHTML;
1075
- }
1076
- }
1077
- });
1078
- }
1079
- }
1080
-
1081
- dom.body.querySelectorAll("include").forEach((el) => {
1082
- el.remove();
1083
- });
1084
- // replace ` with \`\` to allow for template literals
1085
- dom.body.outerHTML = dom.body.outerHTML.replace(/`/g, "\\`");
1086
- dom.body.outerHTML = dom.body.outerHTML.replace(
1087
- el.outerHTML,
1088
- new Function(`return \`${newdom.body.outerHTML}\`;`)()
1089
- );
1090
- });
1091
- }
1092
- }
1093
- }
1094
-
1095
- // replace ` with \`\` to allow for template literals
1096
- dom.body.outerHTML = dom.body.outerHTML.replace(/`/g, "\\`");
1097
- data = new Function(`return \`${dom.body.outerHTML}\`;`)();
1098
-
1099
- return data;
1100
- }
1101
- /**
1102
- * @function include
1103
- * @description Allows you to include html file
1104
- * @returns {Promise} - modified string with html content
1105
- * @param {string} path
1106
- */
1107
-
1108
- export const include = async (path) => {
1109
- if (
1110
- path.startsWith("/") &&
1111
- !path.includes("/src/") &&
1112
- !document.documentElement.outerHTML
1113
- .trim()
1114
- .includes("<!-- #vader-disable_relative-paths -->")
1115
- ) {
1116
- path = "/src/" + path;
1117
- }
1118
- // @ts-ignore
1119
- if (cache[path]) {
1120
- // @ts-ignore
1121
- return cache[path];
1122
- } else {
1123
- return fetch(`./${path}`)
1124
- .then((res) => {
1125
- if (res.status === 404) {
1126
- throw new Error(`No file found at ${path}`);
1127
- }
1128
- return res.text();
1129
- })
1130
- .then(async (data) => {
1131
- // @ts-ignore
1132
- cache[path] = data;
1133
-
1134
-
1135
-
1136
- return data;
1137
- });
1138
- }
1139
- };
1140
-
1141
- export default Vader