vanillaforge 1.9.0

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.
@@ -0,0 +1,123 @@
1
+ /**
2
+ * Notification Utility
3
+ *
4
+ * Handles displaying messages to the user, such as toasts and modals.
5
+ */
6
+ export class Notification {
7
+ /**
8
+ * Show a toast notification
9
+ *
10
+ * @param {string} message - The message to display
11
+ * @param {string} type - The type of toast ('error', 'warning', 'success', 'info')
12
+ */
13
+ showToast(message, type = 'info') {
14
+ const toast = document.createElement('div');
15
+ toast.className = `toast toast-${type}`;
16
+ toast.textContent = message;
17
+
18
+ // Basic styling, can be moved to a CSS file
19
+ toast.style.cssText = `
20
+ position: fixed;
21
+ top: 20px;
22
+ right: 20px;
23
+ padding: 16px 20px;
24
+ background: #333;
25
+ color: #fff;
26
+ border-radius: 6px;
27
+ box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
28
+ z-index: 9999;
29
+ max-width: 300px;
30
+ font-size: 14px;
31
+ line-height: 1.4;
32
+ `;
33
+
34
+ switch (type) {
35
+ case 'error':
36
+ toast.style.background = '#fee2e2';
37
+ toast.style.color = '#991b1b';
38
+ toast.style.border = '1px solid #fecaca';
39
+ break;
40
+ case 'warning':
41
+ toast.style.background = '#fef3cd';
42
+ toast.style.color = '#92400e';
43
+ toast.style.border = '1px solid #fde68a';
44
+ break;
45
+ case 'success':
46
+ toast.style.background = '#dcfce7';
47
+ toast.style.color = '#166534';
48
+ toast.style.border = '1px solid #bbf7d0';
49
+ break;
50
+ }
51
+
52
+ document.body.appendChild(toast);
53
+
54
+ setTimeout(() => {
55
+ if (toast.parentNode) {
56
+ toast.remove();
57
+ }
58
+ }, 5000);
59
+ }
60
+
61
+ /**
62
+ * Show a modal dialog
63
+ *
64
+ * @param {string} title - The title of the modal
65
+ * @param {string} message - The message to display in the modal body
66
+ * @param {Object} [options={}] - Options for the modal (e.g., buttons)
67
+ */
68
+ showModal(title, message, options = {}) {
69
+ const modal = document.createElement('div');
70
+ modal.className = 'modal-overlay';
71
+ modal.innerHTML = `
72
+ <div class="modal">
73
+ <div class="modal-header">
74
+ <h3>${title}</h3>
75
+ <button class="modal-close">&times;</button>
76
+ </div>
77
+ <div class="modal-body">
78
+ <p>${message}</p>
79
+ ${options.details ? `
80
+ <details style="margin-top: 16px;">
81
+ <summary>Technical Details</summary>
82
+ <pre style="font-size: 12px; margin-top: 8px;">${options.details}</pre>
83
+ </details>
84
+ ` : ''}
85
+ </div>
86
+ <div class="modal-footer">
87
+ ${(options.buttons || [{ label: 'Close', action: 'close' }]).map(btn => `<button class="modal-btn-${btn.action}">${btn.label}</button>`).join('')}
88
+ </div>
89
+ </div>
90
+ `;
91
+
92
+ modal.style.cssText = `
93
+ position: fixed;
94
+ top: 0;
95
+ left: 0;
96
+ width: 100%;
97
+ height: 100%;
98
+ background: rgba(0, 0, 0, 0.5);
99
+ display: flex;
100
+ justify-content: center;
101
+ align-items: center;
102
+ z-index: 10000;
103
+ `;
104
+
105
+ modal.querySelector('.modal-close').onclick = () => modal.remove();
106
+
107
+ if (options.buttons) {
108
+ options.buttons.forEach(btn => {
109
+ modal.querySelector(`.modal-btn-${btn.action}`).onclick = () => {
110
+ if (btn.onClick) {
111
+ btn.onClick();
112
+ }
113
+ modal.remove();
114
+ };
115
+ });
116
+ } else {
117
+ modal.querySelector('.modal-btn-close').onclick = () => modal.remove();
118
+ }
119
+
120
+
121
+ document.body.appendChild(modal);
122
+ }
123
+ }
@@ -0,0 +1,281 @@
1
+ /**
2
+ * Performance optimization utilities for VanillaForge
3
+ *
4
+ * Provides tools for optimizing application performance including
5
+ * lazy loading, caching, and resource management.
6
+ */
7
+
8
+ /**
9
+ * Performance utilities class
10
+ */
11
+ import { Logger } from './logger.js';
12
+
13
+ export class PerformanceUtils {
14
+ constructor() {
15
+ this.cache = new Map();
16
+ this.observers = new Map();
17
+ this.loadingStates = new Map();
18
+ this.logger = new Logger('PerformanceUtils');
19
+ }
20
+
21
+ /**
22
+ * Debounce function execution
23
+ * @param {Function} func - Function to debounce
24
+ * @param {number} wait - Delay in milliseconds
25
+ * @param {boolean} immediate - Execute immediately on first call
26
+ * @returns {Function} Debounced function
27
+ */
28
+ debounce(func, wait, immediate = false) {
29
+ let timeout;
30
+ return function executedFunction(...args) {
31
+ const later = () => {
32
+ timeout = null;
33
+ if (!immediate) func.apply(this, args);
34
+ };
35
+ const callNow = immediate && !timeout;
36
+ clearTimeout(timeout);
37
+ timeout = setTimeout(later, wait);
38
+ if (callNow) func.apply(this, args);
39
+ };
40
+ }
41
+
42
+ /**
43
+ * Throttle function execution
44
+ * @param {Function} func - Function to throttle
45
+ * @param {number} limit - Minimum time between executions
46
+ * @returns {Function} Throttled function
47
+ */
48
+ throttle(func, limit) {
49
+ let inThrottle;
50
+ return function(...args) {
51
+ if (!inThrottle) {
52
+ func.apply(this, args);
53
+ inThrottle = true;
54
+ setTimeout(() => inThrottle = false, limit);
55
+ }
56
+ };
57
+ }
58
+
59
+ /**
60
+ * Lazy load a component with intersection observer
61
+ * @param {HTMLElement} element - Element to observe
62
+ * @param {Function} loadCallback - Function to call when element is visible
63
+ * @param {Object} options - Intersection observer options
64
+ */
65
+ lazyLoad(element, loadCallback, options = {}) {
66
+ const defaultOptions = {
67
+ root: null,
68
+ rootMargin: '50px',
69
+ threshold: 0.1
70
+ };
71
+
72
+ const observerOptions = { ...defaultOptions, ...options };
73
+
74
+ if ('IntersectionObserver' in window) {
75
+ const observer = new IntersectionObserver((entries) => {
76
+ entries.forEach(entry => {
77
+ if (entry.isIntersecting) {
78
+ loadCallback(entry.target);
79
+ observer.unobserve(entry.target);
80
+ }
81
+ });
82
+ }, observerOptions);
83
+
84
+ observer.observe(element);
85
+ this.observers.set(element, observer);
86
+ } else {
87
+ // Fallback for browsers without IntersectionObserver
88
+ loadCallback(element);
89
+ }
90
+ }
91
+
92
+ /**
93
+ * Cache data with expiration
94
+ * @param {string} key - Cache key
95
+ * @param {*} data - Data to cache
96
+ * @param {number} ttl - Time to live in milliseconds
97
+ */
98
+ setCache(key, data, ttl = 300000) { // 5 minutes default
99
+ const expiry = Date.now() + ttl;
100
+ this.cache.set(key, { data, expiry });
101
+ }
102
+
103
+ /**
104
+ * Get cached data
105
+ * @param {string} key - Cache key
106
+ * @returns {*} Cached data or null if expired/not found
107
+ */
108
+ getCache(key) {
109
+ const cached = this.cache.get(key);
110
+ if (!cached) return null;
111
+
112
+ if (Date.now() > cached.expiry) {
113
+ this.cache.delete(key);
114
+ return null;
115
+ }
116
+
117
+ return cached.data;
118
+ }
119
+
120
+ /**
121
+ * Clear expired cache entries
122
+ */
123
+ clearExpiredCache() {
124
+ const now = Date.now();
125
+ for (const [key, value] of this.cache.entries()) {
126
+ if (now > value.expiry) {
127
+ this.cache.delete(key);
128
+ }
129
+ }
130
+ }
131
+
132
+ /**
133
+ * Measure function execution time
134
+ * @param {Function} func - Function to measure
135
+ * @param {string} label - Label for the measurement
136
+ * @returns {Promise|*} Function result
137
+ */
138
+ measure(func, label = 'Function') {
139
+ const start = performance.now();
140
+ const result = func();
141
+
142
+ if (result instanceof Promise) {
143
+ return result.finally(() => {
144
+ const end = performance.now();
145
+ this.logger.info(`${label} execution time`, { duration: `${(end - start).toFixed(2)}ms` });
146
+ });
147
+ } else {
148
+ const end = performance.now();
149
+ this.logger.info(`${label} execution time`, { duration: `${(end - start).toFixed(2)}ms` });
150
+ return result;
151
+ }
152
+ }
153
+
154
+ /**
155
+ * Batch DOM operations to avoid layout thrashing
156
+ * @param {Function[]} operations - Array of DOM operations
157
+ */
158
+ batchDOMOperations(operations) {
159
+ requestAnimationFrame(() => {
160
+ operations.forEach(operation => operation());
161
+ });
162
+ }
163
+
164
+ /**
165
+ * Preload resources
166
+ * @param {string[]} urls - Array of resource URLs
167
+ * @param {string} type - Resource type ('image', 'script', 'style')
168
+ * @returns {Promise} Promise that resolves when all resources are loaded
169
+ */
170
+ async preloadResources(resources) {
171
+ const promises = resources.map(resource => {
172
+ return new Promise((resolve, reject) => {
173
+ let element;
174
+ const { url, type } = resource;
175
+
176
+ switch (type) {
177
+ case 'image':
178
+ element = new Image();
179
+ element.src = url;
180
+ break;
181
+ case 'script':
182
+ element = document.createElement('script');
183
+ element.src = url;
184
+ element.async = true;
185
+ document.head.appendChild(element);
186
+ break;
187
+ case 'style':
188
+ element = document.createElement('link');
189
+ element.rel = 'stylesheet';
190
+ element.href = url;
191
+ document.head.appendChild(element);
192
+ break;
193
+ default:
194
+ return reject(new Error(`Unsupported resource type: ${type}`));
195
+ }
196
+
197
+ element.onload = () => resolve({ url, type, status: 'loaded' });
198
+ element.onerror = () => reject({ url, type, status: 'failed' });
199
+ });
200
+ });
201
+
202
+ return Promise.allSettled(promises);
203
+ }
204
+
205
+
206
+ /**
207
+ * Monitor memory usage
208
+ * @param {Function} callback - Callback function to receive memory stats
209
+ * @param {number} interval - Monitoring interval in milliseconds
210
+ * @returns {number} Interval ID
211
+ */
212
+ monitorMemory(callback, interval = 5000) {
213
+ if (!performance.memory) {
214
+ console.warn('Memory monitoring not available in this browser');
215
+ return null;
216
+ }
217
+
218
+ return setInterval(() => {
219
+ const memory = {
220
+ used: Math.round(performance.memory.usedJSHeapSize / 1024 / 1024),
221
+ total: Math.round(performance.memory.totalJSHeapSize / 1024 / 1024),
222
+ limit: Math.round(performance.memory.jsHeapSizeLimit / 1024 / 1024)
223
+ };
224
+ callback(memory);
225
+ }, interval);
226
+ }
227
+
228
+ /**
229
+ * Cleanup observers and resources
230
+ */
231
+ cleanup() {
232
+ // Clear all intersection observers
233
+ this.observers.forEach((observer, element) => {
234
+ observer.unobserve(element);
235
+ observer.disconnect();
236
+ });
237
+ this.observers.clear();
238
+
239
+ // Clear cache
240
+ this.cache.clear();
241
+
242
+ // Clear loading states
243
+ this.loadingStates.clear();
244
+ }
245
+
246
+ /**
247
+ * Create a performance mark
248
+ * @param {string} name - Mark name
249
+ */
250
+ mark(name) {
251
+ if (performance.mark) {
252
+ performance.mark(name);
253
+ }
254
+ }
255
+
256
+ /**
257
+ * Measure performance between two marks
258
+ * @param {string} name - Measure name
259
+ * @param {string} startMark - Start mark name
260
+ * @param {string} endMark - End mark name
261
+ */
262
+ measureBetween(name, startMark, endMark) {
263
+ if (performance.measure) {
264
+ performance.measure(name, startMark, endMark);
265
+ }
266
+ }
267
+
268
+ /**
269
+ * Get performance entries
270
+ * @param {string} type - Entry type (mark, measure, navigation, etc.)
271
+ * @returns {Array} Performance entries
272
+ */
273
+ getPerformanceEntries(type) {
274
+ if (performance.getEntriesByType) {
275
+ return performance.getEntriesByType(type);
276
+ }
277
+ return [];
278
+ }
279
+ }
280
+
281
+ export const performanceUtils = new PerformanceUtils();
@@ -0,0 +1,86 @@
1
+ /**
2
+ * Storage Adapters
3
+ *
4
+ * Provides a consistent interface for key-value storage, abstracting
5
+ * away the underlying implementation (e.g., localStorage, in-memory).
6
+ */
7
+
8
+ /**
9
+ * Base class for storage adapters
10
+ */
11
+ class StorageAdapter {
12
+ getItem(_key) {
13
+ throw new Error('Not implemented');
14
+ }
15
+
16
+ setItem(_key, _value) {
17
+ throw new Error('Not implemented');
18
+ }
19
+
20
+ removeItem(_key) {
21
+ throw new Error('Not implemented');
22
+ }
23
+
24
+ getLogs(key) {
25
+ try {
26
+ const logs = this.getItem(key) || '[]';
27
+ return JSON.parse(logs);
28
+ } catch (error) {
29
+ console.error('Failed to retrieve logs:', error);
30
+ return [];
31
+ }
32
+ }
33
+
34
+ saveLogs(key, logs, maxLogs = 100) {
35
+ try {
36
+ if (logs.length > maxLogs) {
37
+ logs.splice(0, logs.length - maxLogs);
38
+ }
39
+ this.setItem(key, JSON.stringify(logs));
40
+ } catch (error) {
41
+ console.error('Failed to save logs:', error);
42
+ }
43
+ }
44
+ }
45
+
46
+ /**
47
+ * localStorage adapter (for browser environments)
48
+ */
49
+ export class LocalStorageAdapter extends StorageAdapter {
50
+ getItem(key) {
51
+ if (typeof localStorage === 'undefined') return null;
52
+ return localStorage.getItem(key);
53
+ }
54
+
55
+ setItem(key, value) {
56
+ if (typeof localStorage === 'undefined') return;
57
+ localStorage.setItem(key, value);
58
+ }
59
+
60
+ removeItem(key) {
61
+ if (typeof localStorage === 'undefined') return;
62
+ localStorage.removeItem(key);
63
+ }
64
+ }
65
+
66
+ /**
67
+ * In-memory storage adapter (for testing or non-browser environments)
68
+ */
69
+ export class InMemoryStorageAdapter extends StorageAdapter {
70
+ constructor() {
71
+ super();
72
+ this.storage = new Map();
73
+ }
74
+
75
+ getItem(key) {
76
+ return this.storage.get(key) || null;
77
+ }
78
+
79
+ setItem(key, value) {
80
+ this.storage.set(key, value);
81
+ }
82
+
83
+ removeItem(key) {
84
+ this.storage.delete(key);
85
+ }
86
+ }
@@ -0,0 +1,84 @@
1
+ /**
2
+ * SweetAlert2 Utility
3
+ *
4
+ * Wrapper for SweetAlert2 to ensure it's available and provide consistent styling
5
+ *
6
+ * @author VanillaForge Team
7
+ * @version 3.0.0
8
+ * @since 2025-06-14
9
+ */
10
+
11
+ export class SweetAlert {
12
+ constructor(swalInstance) {
13
+ if (!swalInstance) {
14
+ throw new Error('SweetAlert2 instance is required.');
15
+ }
16
+ this.swal = swalInstance;
17
+ }
18
+
19
+ fire(options) {
20
+ const defaultOptions = {
21
+ customClass: {
22
+ popup: 'swal-custom-popup',
23
+ title: 'swal-custom-title',
24
+ content: 'swal-custom-content',
25
+ confirmButton: 'swal-custom-confirm',
26
+ cancelButton: 'swal-custom-cancel'
27
+ },
28
+ buttonsStyling: false
29
+ };
30
+
31
+ return this.swal.fire({
32
+ ...defaultOptions,
33
+ ...options
34
+ });
35
+ }
36
+
37
+ success(title, text = '', options = {}) {
38
+ return this.fire({
39
+ icon: 'success',
40
+ title,
41
+ text,
42
+ ...options
43
+ });
44
+ }
45
+
46
+ error(title, text = '', options = {}) {
47
+ return this.fire({
48
+ icon: 'error',
49
+ title,
50
+ text,
51
+ ...options
52
+ });
53
+ }
54
+
55
+ warning(title, text = '', options = {}) {
56
+ return this.fire({
57
+ icon: 'warning',
58
+ title,
59
+ text,
60
+ ...options
61
+ });
62
+ }
63
+
64
+ info(title, text = '', options = {}) {
65
+ return this.fire({
66
+ icon: 'info',
67
+ title,
68
+ text,
69
+ ...options
70
+ });
71
+ }
72
+
73
+ confirm(title, text = '', options = {}) {
74
+ return this.fire({
75
+ icon: 'question',
76
+ title,
77
+ text,
78
+ showCancelButton: true,
79
+ confirmButtonText: 'Yes',
80
+ cancelButtonText: 'No',
81
+ ...options
82
+ });
83
+ }
84
+ }
@@ -0,0 +1,70 @@
1
+ /**
2
+ * Validation Utilities
3
+ *
4
+ * This module provides comprehensive validation functions for the VanillaForge.
5
+ * It includes validation for user input, data integrity, business rules, and security.
6
+ *
7
+ * Features:
8
+ * - Email validation
9
+ * - Phone number validation
10
+ * - Currency amount validation
11
+ * - Date validation
12
+ * - String sanitization
13
+ * - Business rule validation
14
+ * - Security validation
15
+ *
16
+ * @author VanillaForge Team
17
+ * @version 3.0.0
18
+ * @since 2024-06-14
19
+ */
20
+
21
+ import { Logger } from './logger.js';
22
+ import * as Validators from './validators.js';
23
+
24
+ export class ValidationUtils {
25
+ constructor(logger) {
26
+ this.logger = logger || new Logger('ValidationUtils');
27
+ this.validators = {
28
+ email: Validators.validateEmail,
29
+ phone: Validators.validatePhoneNumber,
30
+ currency: Validators.validateCurrencyAmount,
31
+ // Add other validators here
32
+ };
33
+ }
34
+
35
+ validate(type, value, options) {
36
+ const validator = this.validators[type];
37
+ if (!validator) {
38
+ this.logger.warn(`Validator for type "${type}" not found.`);
39
+ return { isValid: true, sanitized: value, errors: [] };
40
+ }
41
+ try {
42
+ return validator(value, options);
43
+ } catch (error) {
44
+ this.logger.error(`Validation failed for type "${type}"`, { error, value });
45
+ return { isValid: false, errors: ['Validation failed due to an unexpected error.'] };
46
+ }
47
+ }
48
+
49
+ validateFields(data, rules) {
50
+ const result = {
51
+ isValid: true,
52
+ errors: {},
53
+ sanitized: {}
54
+ };
55
+
56
+ for (const [field, rule] of Object.entries(rules)) {
57
+ const value = data[field];
58
+ const fieldResult = this.validate(rule.type, value, rule.options);
59
+
60
+ if (!fieldResult.isValid) {
61
+ result.isValid = false;
62
+ result.errors[field] = fieldResult.errors;
63
+ } else {
64
+ result.sanitized[field] = fieldResult.sanitized;
65
+ }
66
+ }
67
+
68
+ return result;
69
+ }
70
+ }