claude-mpm 4.2.13__py3-none-any.whl → 4.2.15__py3-none-any.whl
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.
- claude_mpm/cli/commands/analyze_code.py +0 -1
- claude_mpm/cli/commands/dashboard.py +0 -1
- claude_mpm/cli/commands/monitor.py +0 -1
- claude_mpm/core/constants.py +65 -0
- claude_mpm/core/error_handler.py +625 -0
- claude_mpm/core/file_utils.py +770 -0
- claude_mpm/core/logging_utils.py +502 -0
- claude_mpm/dashboard/static/dist/components/code-tree.js +1 -1
- claude_mpm/dashboard/static/dist/components/file-viewer.js +1 -1
- claude_mpm/dashboard/static/dist/dashboard.js +1 -1
- claude_mpm/dashboard/static/dist/socket-client.js +1 -1
- claude_mpm/dashboard/static/js/components/code-simple.js +44 -1
- claude_mpm/dashboard/static/js/components/code-tree/tree-breadcrumb.js +353 -0
- claude_mpm/dashboard/static/js/components/code-tree/tree-constants.js +235 -0
- claude_mpm/dashboard/static/js/components/code-tree/tree-search.js +409 -0
- claude_mpm/dashboard/static/js/components/code-tree/tree-utils.js +435 -0
- claude_mpm/dashboard/static/js/components/code-tree.js +29 -5
- claude_mpm/dashboard/static/js/components/file-viewer.js +69 -27
- claude_mpm/dashboard/static/js/components/session-manager.js +7 -7
- claude_mpm/dashboard/static/js/components/working-directory.js +18 -9
- claude_mpm/dashboard/static/js/dashboard.js +91 -9
- claude_mpm/dashboard/static/js/shared/dom-helpers.js +396 -0
- claude_mpm/dashboard/static/js/shared/event-bus.js +330 -0
- claude_mpm/dashboard/static/js/shared/logger.js +385 -0
- claude_mpm/dashboard/static/js/shared/tooltip-service.js +253 -0
- claude_mpm/dashboard/static/js/socket-client.js +18 -0
- claude_mpm/dashboard/templates/index.html +22 -9
- claude_mpm/services/cli/unified_dashboard_manager.py +2 -1
- claude_mpm/services/monitor/handlers/__init__.py +2 -1
- claude_mpm/services/monitor/handlers/file.py +263 -0
- claude_mpm/services/monitor/handlers/hooks.py +25 -1
- claude_mpm/services/monitor/server.py +111 -1
- claude_mpm/services/socketio/handlers/file.py +40 -5
- {claude_mpm-4.2.13.dist-info → claude_mpm-4.2.15.dist-info}/METADATA +1 -1
- {claude_mpm-4.2.13.dist-info → claude_mpm-4.2.15.dist-info}/RECORD +39 -27
- {claude_mpm-4.2.13.dist-info → claude_mpm-4.2.15.dist-info}/WHEEL +0 -0
- {claude_mpm-4.2.13.dist-info → claude_mpm-4.2.15.dist-info}/entry_points.txt +0 -0
- {claude_mpm-4.2.13.dist-info → claude_mpm-4.2.15.dist-info}/licenses/LICENSE +0 -0
- {claude_mpm-4.2.13.dist-info → claude_mpm-4.2.15.dist-info}/top_level.txt +0 -0
|
@@ -355,12 +355,21 @@ class WorkingDirectoryManager {
|
|
|
355
355
|
// Fallback to a reasonable default - try to get the current project directory
|
|
356
356
|
// This should be set when the dashboard initializes
|
|
357
357
|
|
|
358
|
-
// Try getting from
|
|
359
|
-
if (window.
|
|
360
|
-
//
|
|
361
|
-
const
|
|
362
|
-
|
|
363
|
-
|
|
358
|
+
// Try getting from events that have a working directory
|
|
359
|
+
if (window.socketClient && window.socketClient.events) {
|
|
360
|
+
// Look for the most recent event with a working directory
|
|
361
|
+
const eventsWithDir = window.socketClient.events
|
|
362
|
+
.filter(e => e.data && (e.data.working_directory || e.data.cwd || e.data.working_dir))
|
|
363
|
+
.reverse();
|
|
364
|
+
|
|
365
|
+
if (eventsWithDir.length > 0) {
|
|
366
|
+
const recentEvent = eventsWithDir[0];
|
|
367
|
+
const dir = recentEvent.data.working_directory ||
|
|
368
|
+
recentEvent.data.cwd ||
|
|
369
|
+
recentEvent.data.working_dir;
|
|
370
|
+
console.log('[WORKING-DIR-DEBUG] Using working directory from recent event:', dir);
|
|
371
|
+
return dir;
|
|
372
|
+
}
|
|
364
373
|
}
|
|
365
374
|
const workingDirPath = document.getElementById('working-dir-path');
|
|
366
375
|
if (workingDirPath?.textContent?.trim()) {
|
|
@@ -372,9 +381,9 @@ class WorkingDirectoryManager {
|
|
|
372
381
|
}
|
|
373
382
|
}
|
|
374
383
|
|
|
375
|
-
// Final fallback to
|
|
376
|
-
const fallback =
|
|
377
|
-
console.log('[WORKING-DIR-DEBUG] Using
|
|
384
|
+
// Final fallback to a generic path
|
|
385
|
+
const fallback = window.location.hostname === 'localhost' ? '/' : '/';
|
|
386
|
+
console.log('[WORKING-DIR-DEBUG] Using generic fallback directory:', this.repr(fallback));
|
|
378
387
|
return fallback;
|
|
379
388
|
}
|
|
380
389
|
|
|
@@ -60,6 +60,9 @@ class Dashboard {
|
|
|
60
60
|
console.log('Initializing refactored Claude MPM Dashboard...');
|
|
61
61
|
|
|
62
62
|
try {
|
|
63
|
+
// Fetch server configuration first
|
|
64
|
+
this.fetchServerConfig();
|
|
65
|
+
|
|
63
66
|
// Initialize modules in dependency order
|
|
64
67
|
this.initializeSocketManager();
|
|
65
68
|
this.initializeCoreComponents();
|
|
@@ -86,6 +89,39 @@ class Dashboard {
|
|
|
86
89
|
}
|
|
87
90
|
}
|
|
88
91
|
|
|
92
|
+
/**
|
|
93
|
+
* Fetch server configuration for dashboard initialization
|
|
94
|
+
*/
|
|
95
|
+
fetchServerConfig() {
|
|
96
|
+
fetch('/api/config')
|
|
97
|
+
.then(response => response.json())
|
|
98
|
+
.then(config => {
|
|
99
|
+
// Store config globally for other components
|
|
100
|
+
window.dashboardConfig = config;
|
|
101
|
+
|
|
102
|
+
// Update initial UI elements if they exist
|
|
103
|
+
const workingDirEl = document.getElementById('working-dir-path');
|
|
104
|
+
if (workingDirEl && config.workingDirectory) {
|
|
105
|
+
workingDirEl.textContent = config.workingDirectory;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const gitBranchEl = document.getElementById('footer-git-branch');
|
|
109
|
+
if (gitBranchEl && config.gitBranch) {
|
|
110
|
+
gitBranchEl.textContent = config.gitBranch;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
console.log('Dashboard configuration loaded:', config);
|
|
114
|
+
})
|
|
115
|
+
.catch(error => {
|
|
116
|
+
console.warn('Failed to fetch server config:', error);
|
|
117
|
+
// Set default config as fallback
|
|
118
|
+
window.dashboardConfig = {
|
|
119
|
+
workingDirectory: '.',
|
|
120
|
+
gitBranch: 'Unknown'
|
|
121
|
+
};
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
|
|
89
125
|
/**
|
|
90
126
|
* Validate that all critical components are initialized
|
|
91
127
|
* WHY: Ensures dashboard is in a valid state after initialization
|
|
@@ -1214,38 +1250,61 @@ async function updateFileViewerModal(modal, filePath, workingDir) {
|
|
|
1214
1250
|
|
|
1215
1251
|
try {
|
|
1216
1252
|
// Get the Socket.IO client
|
|
1217
|
-
const socket = window.socket || window.dashboard?.socketClient?.socket;
|
|
1253
|
+
const socket = window.socket || window.dashboard?.socketClient?.socket || window.socketClient?.socket;
|
|
1254
|
+
|
|
1255
|
+
console.log('[FileViewer] Socket search results:', {
|
|
1256
|
+
'window.socket': !!window.socket,
|
|
1257
|
+
'window.socket.connected': window.socket?.connected,
|
|
1258
|
+
'dashboard.socketClient.socket': !!window.dashboard?.socketClient?.socket,
|
|
1259
|
+
'dashboard.socketClient.socket.connected': window.dashboard?.socketClient?.socket?.connected,
|
|
1260
|
+
'window.socketClient.socket': !!window.socketClient?.socket,
|
|
1261
|
+
'window.socketClient.socket.connected': window.socketClient?.socket?.connected
|
|
1262
|
+
});
|
|
1263
|
+
|
|
1218
1264
|
if (!socket) {
|
|
1219
|
-
throw new Error('No socket connection available');
|
|
1265
|
+
throw new Error('No socket connection available. Please ensure the dashboard is connected.');
|
|
1220
1266
|
}
|
|
1267
|
+
|
|
1268
|
+
if (!socket.connected) {
|
|
1269
|
+
console.warn('[FileViewer] Socket found but not connected, attempting to use anyway...');
|
|
1270
|
+
}
|
|
1271
|
+
|
|
1272
|
+
console.log('[FileViewer] Socket found, setting up listener for file_content_response');
|
|
1221
1273
|
|
|
1222
1274
|
// Set up one-time listener for file content response
|
|
1223
1275
|
const responsePromise = new Promise((resolve, reject) => {
|
|
1224
1276
|
const responseHandler = (data) => {
|
|
1277
|
+
console.log('[FileViewer] Received file_content_response:', data);
|
|
1225
1278
|
if (data.file_path === filePath) {
|
|
1226
1279
|
socket.off('file_content_response', responseHandler);
|
|
1227
1280
|
if (data.success) {
|
|
1281
|
+
console.log('[FileViewer] File content loaded successfully');
|
|
1228
1282
|
resolve(data);
|
|
1229
1283
|
} else {
|
|
1284
|
+
console.error('[FileViewer] File read failed:', data.error);
|
|
1230
1285
|
reject(new Error(data.error || 'Failed to read file'));
|
|
1231
1286
|
}
|
|
1232
1287
|
}
|
|
1233
1288
|
};
|
|
1234
1289
|
|
|
1235
1290
|
socket.on('file_content_response', responseHandler);
|
|
1291
|
+
console.log('[FileViewer] Listener registered for file_content_response');
|
|
1236
1292
|
|
|
1237
1293
|
// Timeout after 10 seconds
|
|
1238
1294
|
setTimeout(() => {
|
|
1239
1295
|
socket.off('file_content_response', responseHandler);
|
|
1240
|
-
|
|
1296
|
+
console.error('[FileViewer] Request timeout after 10 seconds');
|
|
1297
|
+
reject(new Error('Request timeout - server did not respond'));
|
|
1241
1298
|
}, 10000);
|
|
1242
1299
|
});
|
|
1243
1300
|
|
|
1244
1301
|
// Send file read request
|
|
1245
|
-
|
|
1302
|
+
const requestData = {
|
|
1246
1303
|
file_path: filePath,
|
|
1247
1304
|
working_dir: workingDir
|
|
1248
|
-
}
|
|
1305
|
+
};
|
|
1306
|
+
console.log('[FileViewer] Emitting read_file event with data:', requestData);
|
|
1307
|
+
socket.emit('read_file', requestData);
|
|
1249
1308
|
|
|
1250
1309
|
// File viewer request sent
|
|
1251
1310
|
|
|
@@ -1505,15 +1564,19 @@ function formatFileSize(bytes) {
|
|
|
1505
1564
|
|
|
1506
1565
|
// File Viewer Modal Functions
|
|
1507
1566
|
window.showFileViewerModal = async function(filePath) {
|
|
1567
|
+
console.log('[FileViewer] Opening file:', filePath);
|
|
1568
|
+
|
|
1508
1569
|
// Use the dashboard's current working directory
|
|
1509
1570
|
let workingDir = '';
|
|
1510
1571
|
if (window.dashboard && window.dashboard.currentWorkingDir) {
|
|
1511
1572
|
workingDir = window.dashboard.currentWorkingDir;
|
|
1573
|
+
console.log('[FileViewer] Using working directory:', workingDir);
|
|
1512
1574
|
}
|
|
1513
1575
|
|
|
1514
1576
|
// Create modal if it doesn't exist
|
|
1515
1577
|
let modal = document.getElementById('file-viewer-modal');
|
|
1516
1578
|
if (!modal) {
|
|
1579
|
+
console.log('[FileViewer] Creating new modal');
|
|
1517
1580
|
modal = createFileViewerModal();
|
|
1518
1581
|
document.body.appendChild(modal);
|
|
1519
1582
|
|
|
@@ -1521,14 +1584,16 @@ window.showFileViewerModal = async function(filePath) {
|
|
|
1521
1584
|
await new Promise(resolve => setTimeout(resolve, 10));
|
|
1522
1585
|
}
|
|
1523
1586
|
|
|
1587
|
+
// Show the modal as flex container first (ensures proper rendering)
|
|
1588
|
+
modal.style.display = 'flex';
|
|
1589
|
+
document.body.style.overflow = 'hidden'; // Prevent background scrolling
|
|
1590
|
+
|
|
1524
1591
|
// Update modal content
|
|
1525
1592
|
updateFileViewerModal(modal, filePath, workingDir).catch(error => {
|
|
1526
1593
|
console.error('Error updating file viewer modal:', error);
|
|
1594
|
+
// Show error in the modal
|
|
1595
|
+
displayFileContentError(modal, { error: error.message });
|
|
1527
1596
|
});
|
|
1528
|
-
|
|
1529
|
-
// Show the modal as flex container
|
|
1530
|
-
modal.style.display = 'flex';
|
|
1531
|
-
document.body.style.overflow = 'hidden'; // Prevent background scrolling
|
|
1532
1597
|
};
|
|
1533
1598
|
|
|
1534
1599
|
window.hideFileViewerModal = function() {
|
|
@@ -1589,6 +1654,19 @@ function displayFileContentError(modal, result) {
|
|
|
1589
1654
|
const errorArea = modal.querySelector('.file-viewer-error');
|
|
1590
1655
|
const messageElement = modal.querySelector('.error-message');
|
|
1591
1656
|
const suggestionsElement = modal.querySelector('.error-suggestions');
|
|
1657
|
+
const loadingElement = modal.querySelector('.file-viewer-loading');
|
|
1658
|
+
const contentArea = modal.querySelector('.file-viewer-content-area');
|
|
1659
|
+
|
|
1660
|
+
// Hide loading and content areas, show error
|
|
1661
|
+
if (loadingElement) {
|
|
1662
|
+
loadingElement.style.display = 'none';
|
|
1663
|
+
}
|
|
1664
|
+
if (contentArea) {
|
|
1665
|
+
contentArea.style.display = 'none';
|
|
1666
|
+
}
|
|
1667
|
+
if (errorArea) {
|
|
1668
|
+
errorArea.style.display = 'flex';
|
|
1669
|
+
}
|
|
1592
1670
|
|
|
1593
1671
|
// Create user-friendly error messages
|
|
1594
1672
|
let errorMessage = result.error || 'Unknown error occurred';
|
|
@@ -1599,6 +1677,10 @@ function displayFileContentError(modal, result) {
|
|
|
1599
1677
|
errorMessage = '🔒 Permission denied accessing this file';
|
|
1600
1678
|
} else if (errorMessage.includes('too large')) {
|
|
1601
1679
|
errorMessage = '📏 File is too large to display';
|
|
1680
|
+
} else if (errorMessage.includes('socket connection')) {
|
|
1681
|
+
errorMessage = '🔌 Not connected to the server. Please check your connection.';
|
|
1682
|
+
} else if (errorMessage.includes('timeout')) {
|
|
1683
|
+
errorMessage = '⏱️ Request timed out. The server may be busy or unresponsive.';
|
|
1602
1684
|
} else if (!errorMessage.includes('📁') && !errorMessage.includes('🔒') && !errorMessage.includes('📏')) {
|
|
1603
1685
|
errorMessage = `⚠️ ${errorMessage}`;
|
|
1604
1686
|
}
|
|
@@ -0,0 +1,396 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DOM Helper Utilities
|
|
3
|
+
*
|
|
4
|
+
* Common DOM manipulation utilities for dashboard components.
|
|
5
|
+
* Provides safe, consistent methods for element creation and manipulation.
|
|
6
|
+
*
|
|
7
|
+
* @module dom-helpers
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
const domHelpers = {
|
|
11
|
+
/**
|
|
12
|
+
* Create an element with attributes and content
|
|
13
|
+
* @param {string} tag - Element tag name
|
|
14
|
+
* @param {Object} attrs - Attributes to set
|
|
15
|
+
* @param {string|Element|Array} content - Element content
|
|
16
|
+
* @returns {HTMLElement} Created element
|
|
17
|
+
*/
|
|
18
|
+
createElement(tag, attrs = {}, content = null) {
|
|
19
|
+
const element = document.createElement(tag);
|
|
20
|
+
|
|
21
|
+
// Set attributes
|
|
22
|
+
for (const [key, value] of Object.entries(attrs)) {
|
|
23
|
+
if (key === 'className') {
|
|
24
|
+
element.className = value;
|
|
25
|
+
} else if (key === 'style' && typeof value === 'object') {
|
|
26
|
+
Object.assign(element.style, value);
|
|
27
|
+
} else if (key === 'dataset' && typeof value === 'object') {
|
|
28
|
+
for (const [dataKey, dataValue] of Object.entries(value)) {
|
|
29
|
+
element.dataset[dataKey] = dataValue;
|
|
30
|
+
}
|
|
31
|
+
} else if (key.startsWith('on') && typeof value === 'function') {
|
|
32
|
+
const eventName = key.slice(2).toLowerCase();
|
|
33
|
+
element.addEventListener(eventName, value);
|
|
34
|
+
} else {
|
|
35
|
+
element.setAttribute(key, value);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Add content
|
|
40
|
+
if (content !== null) {
|
|
41
|
+
this.setContent(element, content);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
return element;
|
|
45
|
+
},
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Set element content (supports text, HTML, elements, and arrays)
|
|
49
|
+
* @param {HTMLElement} element - Target element
|
|
50
|
+
* @param {string|Element|Array} content - Content to set
|
|
51
|
+
*/
|
|
52
|
+
setContent(element, content) {
|
|
53
|
+
if (typeof content === 'string') {
|
|
54
|
+
element.textContent = content;
|
|
55
|
+
} else if (content instanceof HTMLElement) {
|
|
56
|
+
element.appendChild(content);
|
|
57
|
+
} else if (Array.isArray(content)) {
|
|
58
|
+
content.forEach(item => {
|
|
59
|
+
if (typeof item === 'string') {
|
|
60
|
+
element.appendChild(document.createTextNode(item));
|
|
61
|
+
} else if (item instanceof HTMLElement) {
|
|
62
|
+
element.appendChild(item);
|
|
63
|
+
}
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
},
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Safely query selector with null check
|
|
70
|
+
* @param {string} selector - CSS selector
|
|
71
|
+
* @param {Element} context - Context element (default: document)
|
|
72
|
+
* @returns {Element|null} Found element or null
|
|
73
|
+
*/
|
|
74
|
+
query(selector, context = document) {
|
|
75
|
+
try {
|
|
76
|
+
return context.querySelector(selector);
|
|
77
|
+
} catch (e) {
|
|
78
|
+
console.error(`Invalid selector: ${selector}`, e);
|
|
79
|
+
return null;
|
|
80
|
+
}
|
|
81
|
+
},
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Safely query all matching elements
|
|
85
|
+
* @param {string} selector - CSS selector
|
|
86
|
+
* @param {Element} context - Context element (default: document)
|
|
87
|
+
* @returns {Array} Array of elements
|
|
88
|
+
*/
|
|
89
|
+
queryAll(selector, context = document) {
|
|
90
|
+
try {
|
|
91
|
+
return Array.from(context.querySelectorAll(selector));
|
|
92
|
+
} catch (e) {
|
|
93
|
+
console.error(`Invalid selector: ${selector}`, e);
|
|
94
|
+
return [];
|
|
95
|
+
}
|
|
96
|
+
},
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Add classes to element
|
|
100
|
+
* @param {HTMLElement} element - Target element
|
|
101
|
+
* @param {...string} classes - Classes to add
|
|
102
|
+
*/
|
|
103
|
+
addClass(element, ...classes) {
|
|
104
|
+
if (element && element.classList) {
|
|
105
|
+
element.classList.add(...classes.filter(c => c));
|
|
106
|
+
}
|
|
107
|
+
},
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Remove classes from element
|
|
111
|
+
* @param {HTMLElement} element - Target element
|
|
112
|
+
* @param {...string} classes - Classes to remove
|
|
113
|
+
*/
|
|
114
|
+
removeClass(element, ...classes) {
|
|
115
|
+
if (element && element.classList) {
|
|
116
|
+
element.classList.remove(...classes);
|
|
117
|
+
}
|
|
118
|
+
},
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Toggle classes on element
|
|
122
|
+
* @param {HTMLElement} element - Target element
|
|
123
|
+
* @param {string} className - Class to toggle
|
|
124
|
+
* @param {boolean} force - Force add (true) or remove (false)
|
|
125
|
+
* @returns {boolean} Whether class is now present
|
|
126
|
+
*/
|
|
127
|
+
toggleClass(element, className, force) {
|
|
128
|
+
if (element && element.classList) {
|
|
129
|
+
return element.classList.toggle(className, force);
|
|
130
|
+
}
|
|
131
|
+
return false;
|
|
132
|
+
},
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Check if element has class
|
|
136
|
+
* @param {HTMLElement} element - Target element
|
|
137
|
+
* @param {string} className - Class to check
|
|
138
|
+
* @returns {boolean} Whether element has class
|
|
139
|
+
*/
|
|
140
|
+
hasClass(element, className) {
|
|
141
|
+
return element && element.classList && element.classList.contains(className);
|
|
142
|
+
},
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Set multiple styles on element
|
|
146
|
+
* @param {HTMLElement} element - Target element
|
|
147
|
+
* @param {Object} styles - Style properties and values
|
|
148
|
+
*/
|
|
149
|
+
setStyles(element, styles) {
|
|
150
|
+
if (element && element.style && styles) {
|
|
151
|
+
Object.assign(element.style, styles);
|
|
152
|
+
}
|
|
153
|
+
},
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Get computed style value
|
|
157
|
+
* @param {HTMLElement} element - Target element
|
|
158
|
+
* @param {string} property - CSS property name
|
|
159
|
+
* @returns {string} Computed style value
|
|
160
|
+
*/
|
|
161
|
+
getStyle(element, property) {
|
|
162
|
+
if (element) {
|
|
163
|
+
return window.getComputedStyle(element).getPropertyValue(property);
|
|
164
|
+
}
|
|
165
|
+
return '';
|
|
166
|
+
},
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Show element (removes display: none)
|
|
170
|
+
* @param {HTMLElement} element - Element to show
|
|
171
|
+
* @param {string} displayValue - Display value to use (default: '')
|
|
172
|
+
*/
|
|
173
|
+
show(element, displayValue = '') {
|
|
174
|
+
if (element && element.style) {
|
|
175
|
+
element.style.display = displayValue;
|
|
176
|
+
}
|
|
177
|
+
},
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Hide element (sets display: none)
|
|
181
|
+
* @param {HTMLElement} element - Element to hide
|
|
182
|
+
*/
|
|
183
|
+
hide(element) {
|
|
184
|
+
if (element && element.style) {
|
|
185
|
+
element.style.display = 'none';
|
|
186
|
+
}
|
|
187
|
+
},
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Toggle element visibility
|
|
191
|
+
* @param {HTMLElement} element - Element to toggle
|
|
192
|
+
* @param {boolean} show - Force show (true) or hide (false)
|
|
193
|
+
*/
|
|
194
|
+
toggle(element, show) {
|
|
195
|
+
if (element) {
|
|
196
|
+
if (show === undefined) {
|
|
197
|
+
show = element.style.display === 'none';
|
|
198
|
+
}
|
|
199
|
+
if (show) {
|
|
200
|
+
this.show(element);
|
|
201
|
+
} else {
|
|
202
|
+
this.hide(element);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
},
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* Remove element from DOM
|
|
209
|
+
* @param {HTMLElement} element - Element to remove
|
|
210
|
+
*/
|
|
211
|
+
remove(element) {
|
|
212
|
+
if (element && element.parentNode) {
|
|
213
|
+
element.parentNode.removeChild(element);
|
|
214
|
+
}
|
|
215
|
+
},
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* Empty element content
|
|
219
|
+
* @param {HTMLElement} element - Element to empty
|
|
220
|
+
*/
|
|
221
|
+
empty(element) {
|
|
222
|
+
if (element) {
|
|
223
|
+
while (element.firstChild) {
|
|
224
|
+
element.removeChild(element.firstChild);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
},
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* Insert element after reference element
|
|
231
|
+
* @param {HTMLElement} newElement - Element to insert
|
|
232
|
+
* @param {HTMLElement} referenceElement - Reference element
|
|
233
|
+
*/
|
|
234
|
+
insertAfter(newElement, referenceElement) {
|
|
235
|
+
if (referenceElement && referenceElement.parentNode) {
|
|
236
|
+
referenceElement.parentNode.insertBefore(newElement, referenceElement.nextSibling);
|
|
237
|
+
}
|
|
238
|
+
},
|
|
239
|
+
|
|
240
|
+
/**
|
|
241
|
+
* Wrap element with wrapper element
|
|
242
|
+
* @param {HTMLElement} element - Element to wrap
|
|
243
|
+
* @param {HTMLElement} wrapper - Wrapper element
|
|
244
|
+
*/
|
|
245
|
+
wrap(element, wrapper) {
|
|
246
|
+
if (element && element.parentNode) {
|
|
247
|
+
element.parentNode.insertBefore(wrapper, element);
|
|
248
|
+
wrapper.appendChild(element);
|
|
249
|
+
}
|
|
250
|
+
},
|
|
251
|
+
|
|
252
|
+
/**
|
|
253
|
+
* Get element dimensions
|
|
254
|
+
* @param {HTMLElement} element - Target element
|
|
255
|
+
* @returns {Object} Width and height
|
|
256
|
+
*/
|
|
257
|
+
getDimensions(element) {
|
|
258
|
+
if (element) {
|
|
259
|
+
return {
|
|
260
|
+
width: element.offsetWidth,
|
|
261
|
+
height: element.offsetHeight,
|
|
262
|
+
innerWidth: element.clientWidth,
|
|
263
|
+
innerHeight: element.clientHeight
|
|
264
|
+
};
|
|
265
|
+
}
|
|
266
|
+
return { width: 0, height: 0, innerWidth: 0, innerHeight: 0 };
|
|
267
|
+
},
|
|
268
|
+
|
|
269
|
+
/**
|
|
270
|
+
* Get element position relative to viewport
|
|
271
|
+
* @param {HTMLElement} element - Target element
|
|
272
|
+
* @returns {Object} Position coordinates
|
|
273
|
+
*/
|
|
274
|
+
getPosition(element) {
|
|
275
|
+
if (element) {
|
|
276
|
+
const rect = element.getBoundingClientRect();
|
|
277
|
+
return {
|
|
278
|
+
top: rect.top,
|
|
279
|
+
right: rect.right,
|
|
280
|
+
bottom: rect.bottom,
|
|
281
|
+
left: rect.left,
|
|
282
|
+
x: rect.x,
|
|
283
|
+
y: rect.y,
|
|
284
|
+
width: rect.width,
|
|
285
|
+
height: rect.height
|
|
286
|
+
};
|
|
287
|
+
}
|
|
288
|
+
return { top: 0, right: 0, bottom: 0, left: 0, x: 0, y: 0, width: 0, height: 0 };
|
|
289
|
+
},
|
|
290
|
+
|
|
291
|
+
/**
|
|
292
|
+
* Check if element is visible in viewport
|
|
293
|
+
* @param {HTMLElement} element - Element to check
|
|
294
|
+
* @param {boolean} partial - Allow partial visibility
|
|
295
|
+
* @returns {boolean} Whether element is visible
|
|
296
|
+
*/
|
|
297
|
+
isInViewport(element, partial = false) {
|
|
298
|
+
if (!element) return false;
|
|
299
|
+
|
|
300
|
+
const rect = element.getBoundingClientRect();
|
|
301
|
+
const windowHeight = window.innerHeight || document.documentElement.clientHeight;
|
|
302
|
+
const windowWidth = window.innerWidth || document.documentElement.clientWidth;
|
|
303
|
+
|
|
304
|
+
const vertInView = partial
|
|
305
|
+
? rect.top < windowHeight && rect.bottom > 0
|
|
306
|
+
: rect.top >= 0 && rect.bottom <= windowHeight;
|
|
307
|
+
|
|
308
|
+
const horInView = partial
|
|
309
|
+
? rect.left < windowWidth && rect.right > 0
|
|
310
|
+
: rect.left >= 0 && rect.right <= windowWidth;
|
|
311
|
+
|
|
312
|
+
return vertInView && horInView;
|
|
313
|
+
},
|
|
314
|
+
|
|
315
|
+
/**
|
|
316
|
+
* Smoothly scroll element into view
|
|
317
|
+
* @param {HTMLElement} element - Element to scroll to
|
|
318
|
+
* @param {Object} options - Scroll options
|
|
319
|
+
*/
|
|
320
|
+
scrollIntoView(element, options = {}) {
|
|
321
|
+
if (element && element.scrollIntoView) {
|
|
322
|
+
const defaultOptions = {
|
|
323
|
+
behavior: 'smooth',
|
|
324
|
+
block: 'nearest',
|
|
325
|
+
inline: 'nearest'
|
|
326
|
+
};
|
|
327
|
+
element.scrollIntoView({ ...defaultOptions, ...options });
|
|
328
|
+
}
|
|
329
|
+
},
|
|
330
|
+
|
|
331
|
+
/**
|
|
332
|
+
* Create DocumentFragment from HTML string
|
|
333
|
+
* @param {string} html - HTML string
|
|
334
|
+
* @returns {DocumentFragment} Document fragment
|
|
335
|
+
*/
|
|
336
|
+
createFragment(html) {
|
|
337
|
+
const template = document.createElement('template');
|
|
338
|
+
template.innerHTML = html.trim();
|
|
339
|
+
return template.content;
|
|
340
|
+
},
|
|
341
|
+
|
|
342
|
+
/**
|
|
343
|
+
* Escape HTML special characters
|
|
344
|
+
* @param {string} text - Text to escape
|
|
345
|
+
* @returns {string} Escaped text
|
|
346
|
+
*/
|
|
347
|
+
escapeHtml(text) {
|
|
348
|
+
const div = document.createElement('div');
|
|
349
|
+
div.textContent = text;
|
|
350
|
+
return div.innerHTML;
|
|
351
|
+
},
|
|
352
|
+
|
|
353
|
+
/**
|
|
354
|
+
* Debounce function calls
|
|
355
|
+
* @param {Function} func - Function to debounce
|
|
356
|
+
* @param {number} wait - Wait time in ms
|
|
357
|
+
* @returns {Function} Debounced function
|
|
358
|
+
*/
|
|
359
|
+
debounce(func, wait) {
|
|
360
|
+
let timeout;
|
|
361
|
+
return function executedFunction(...args) {
|
|
362
|
+
const later = () => {
|
|
363
|
+
clearTimeout(timeout);
|
|
364
|
+
func(...args);
|
|
365
|
+
};
|
|
366
|
+
clearTimeout(timeout);
|
|
367
|
+
timeout = setTimeout(later, wait);
|
|
368
|
+
};
|
|
369
|
+
},
|
|
370
|
+
|
|
371
|
+
/**
|
|
372
|
+
* Throttle function calls
|
|
373
|
+
* @param {Function} func - Function to throttle
|
|
374
|
+
* @param {number} limit - Time limit in ms
|
|
375
|
+
* @returns {Function} Throttled function
|
|
376
|
+
*/
|
|
377
|
+
throttle(func, limit) {
|
|
378
|
+
let inThrottle;
|
|
379
|
+
return function(...args) {
|
|
380
|
+
if (!inThrottle) {
|
|
381
|
+
func.apply(this, args);
|
|
382
|
+
inThrottle = true;
|
|
383
|
+
setTimeout(() => inThrottle = false, limit);
|
|
384
|
+
}
|
|
385
|
+
};
|
|
386
|
+
}
|
|
387
|
+
};
|
|
388
|
+
|
|
389
|
+
// Support both module and global usage
|
|
390
|
+
if (typeof module !== 'undefined' && module.exports) {
|
|
391
|
+
module.exports = domHelpers;
|
|
392
|
+
} else if (typeof define === 'function' && define.amd) {
|
|
393
|
+
define([], () => domHelpers);
|
|
394
|
+
} else {
|
|
395
|
+
window.domHelpers = domHelpers;
|
|
396
|
+
}
|