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