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.
Files changed (39) hide show
  1. claude_mpm/cli/commands/analyze_code.py +0 -1
  2. claude_mpm/cli/commands/dashboard.py +0 -1
  3. claude_mpm/cli/commands/monitor.py +0 -1
  4. claude_mpm/core/constants.py +65 -0
  5. claude_mpm/core/error_handler.py +625 -0
  6. claude_mpm/core/file_utils.py +770 -0
  7. claude_mpm/core/logging_utils.py +502 -0
  8. claude_mpm/dashboard/static/dist/components/code-tree.js +1 -1
  9. claude_mpm/dashboard/static/dist/components/file-viewer.js +1 -1
  10. claude_mpm/dashboard/static/dist/dashboard.js +1 -1
  11. claude_mpm/dashboard/static/dist/socket-client.js +1 -1
  12. claude_mpm/dashboard/static/js/components/code-simple.js +44 -1
  13. claude_mpm/dashboard/static/js/components/code-tree/tree-breadcrumb.js +353 -0
  14. claude_mpm/dashboard/static/js/components/code-tree/tree-constants.js +235 -0
  15. claude_mpm/dashboard/static/js/components/code-tree/tree-search.js +409 -0
  16. claude_mpm/dashboard/static/js/components/code-tree/tree-utils.js +435 -0
  17. claude_mpm/dashboard/static/js/components/code-tree.js +29 -5
  18. claude_mpm/dashboard/static/js/components/file-viewer.js +69 -27
  19. claude_mpm/dashboard/static/js/components/session-manager.js +7 -7
  20. claude_mpm/dashboard/static/js/components/working-directory.js +18 -9
  21. claude_mpm/dashboard/static/js/dashboard.js +91 -9
  22. claude_mpm/dashboard/static/js/shared/dom-helpers.js +396 -0
  23. claude_mpm/dashboard/static/js/shared/event-bus.js +330 -0
  24. claude_mpm/dashboard/static/js/shared/logger.js +385 -0
  25. claude_mpm/dashboard/static/js/shared/tooltip-service.js +253 -0
  26. claude_mpm/dashboard/static/js/socket-client.js +18 -0
  27. claude_mpm/dashboard/templates/index.html +22 -9
  28. claude_mpm/services/cli/unified_dashboard_manager.py +2 -1
  29. claude_mpm/services/monitor/handlers/__init__.py +2 -1
  30. claude_mpm/services/monitor/handlers/file.py +263 -0
  31. claude_mpm/services/monitor/handlers/hooks.py +25 -1
  32. claude_mpm/services/monitor/server.py +111 -1
  33. claude_mpm/services/socketio/handlers/file.py +40 -5
  34. {claude_mpm-4.2.13.dist-info → claude_mpm-4.2.15.dist-info}/METADATA +1 -1
  35. {claude_mpm-4.2.13.dist-info → claude_mpm-4.2.15.dist-info}/RECORD +39 -27
  36. {claude_mpm-4.2.13.dist-info → claude_mpm-4.2.15.dist-info}/WHEEL +0 -0
  37. {claude_mpm-4.2.13.dist-info → claude_mpm-4.2.15.dist-info}/entry_points.txt +0 -0
  38. {claude_mpm-4.2.13.dist-info → claude_mpm-4.2.15.dist-info}/licenses/LICENSE +0 -0
  39. {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 the browser's URL or any other hint about the current project
359
- if (window.location.pathname.includes('claude-mpm')) {
360
- // We can infer we're in a claude-mpm project
361
- const cwdFallback = '/Users/masa/Projects/claude-mpm';
362
- console.log('[WORKING-DIR-DEBUG] Using inferred project path as fallback:', cwdFallback);
363
- return cwdFallback;
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 current directory indicator
376
- const fallback = process?.cwd?.() || '/Users/masa/Projects/claude-mpm';
377
- console.log('[WORKING-DIR-DEBUG] Using hard-coded fallback directory:', this.repr(fallback));
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
- reject(new Error('Request timeout'));
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
- socket.emit('read_file', {
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
+ }