@trustquery/browser 0.2.10 → 0.3.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/dist/trustquery.js +1329 -3
- package/dist/trustquery.js.map +1 -1
- package/package.json +1 -1
- package/src/AttachmentManager.js +466 -0
- package/src/AttachmentStyleManager.js +174 -0
- package/src/CSVModalManager.js +417 -0
- package/src/CSVModalStyleManager.js +259 -0
- package/src/DropdownManager.js +8 -2
- package/src/TrustQuery.js +14 -0
|
@@ -0,0 +1,417 @@
|
|
|
1
|
+
// CSVModalManager - Handles CSV modal display with trigger matching
|
|
2
|
+
// Single responsibility: manage CSV modal lifecycle and rendering
|
|
3
|
+
|
|
4
|
+
export default class CSVModalManager {
|
|
5
|
+
/**
|
|
6
|
+
* Create CSV modal manager
|
|
7
|
+
* @param {Object} options - Configuration options
|
|
8
|
+
*/
|
|
9
|
+
constructor(options = {}) {
|
|
10
|
+
this.options = {
|
|
11
|
+
styleManager: options.styleManager || null,
|
|
12
|
+
commandScanner: options.commandScanner || null,
|
|
13
|
+
dropdownManager: options.dropdownManager || null,
|
|
14
|
+
onCellClick: options.onCellClick || null,
|
|
15
|
+
debug: options.debug || false,
|
|
16
|
+
...options
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
this.modal = null;
|
|
20
|
+
this.currentCSVData = null;
|
|
21
|
+
this.parsedData = null;
|
|
22
|
+
|
|
23
|
+
if (this.options.debug) {
|
|
24
|
+
console.log('[CSVModalManager] Initialized');
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Parse CSV text into 2D array
|
|
30
|
+
* @param {string} csvText - Raw CSV text
|
|
31
|
+
* @returns {Array} - 2D array of cells
|
|
32
|
+
*/
|
|
33
|
+
parseCSV(csvText) {
|
|
34
|
+
const lines = csvText.trim().split('\n');
|
|
35
|
+
return lines.map(line => {
|
|
36
|
+
// Simple CSV parsing (handles basic cases)
|
|
37
|
+
return line.split(',').map(cell => cell.trim());
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Check if a cell value matches any triggers
|
|
43
|
+
* @param {string} cellValue - Cell content
|
|
44
|
+
* @returns {Object|null} - Match data or null
|
|
45
|
+
*/
|
|
46
|
+
checkCellForTriggers(cellValue) {
|
|
47
|
+
if (!this.options.commandScanner || !cellValue) {
|
|
48
|
+
return null;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const commandMap = this.options.commandScanner.commandMap;
|
|
52
|
+
if (!commandMap || !commandMap['tql-triggers']) {
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Check all trigger states (error, warning, info)
|
|
57
|
+
const triggers = commandMap['tql-triggers'];
|
|
58
|
+
const allTriggers = [
|
|
59
|
+
...(triggers.error || []),
|
|
60
|
+
...(triggers.warning || []),
|
|
61
|
+
...(triggers.info || [])
|
|
62
|
+
];
|
|
63
|
+
|
|
64
|
+
// Check each trigger
|
|
65
|
+
for (const trigger of allTriggers) {
|
|
66
|
+
// Handle 'match' type triggers
|
|
67
|
+
if (trigger.type === 'match' && trigger.match) {
|
|
68
|
+
for (const matchText of trigger.match) {
|
|
69
|
+
if (cellValue.toLowerCase() === matchText.toLowerCase()) {
|
|
70
|
+
return {
|
|
71
|
+
text: cellValue,
|
|
72
|
+
trigger,
|
|
73
|
+
matchType: 'exact',
|
|
74
|
+
messageState: trigger.handler?.['message-state'] || 'info'
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Handle 'csv-match-column' type triggers (for CSV headers)
|
|
81
|
+
if (trigger.type === 'csv-match-column' && trigger.match) {
|
|
82
|
+
for (const matchText of trigger.match) {
|
|
83
|
+
if (cellValue.toLowerCase() === matchText.toLowerCase()) {
|
|
84
|
+
return {
|
|
85
|
+
text: cellValue,
|
|
86
|
+
trigger,
|
|
87
|
+
matchType: 'csv-column',
|
|
88
|
+
messageState: trigger.handler?.['message-state'] || 'info'
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// Handle 'regex' type triggers
|
|
95
|
+
if (trigger.type === 'regex' && trigger.regex) {
|
|
96
|
+
for (const pattern of trigger.regex) {
|
|
97
|
+
const regex = new RegExp(pattern);
|
|
98
|
+
if (regex.test(cellValue)) {
|
|
99
|
+
return {
|
|
100
|
+
text: cellValue,
|
|
101
|
+
trigger,
|
|
102
|
+
matchType: 'regex',
|
|
103
|
+
messageState: trigger.handler?.['message-state'] || 'info'
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
return null;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Create table cell with optional trigger highlighting
|
|
115
|
+
* @param {string} cellValue - Cell content
|
|
116
|
+
* @param {boolean} isHeader - Whether this is a header cell
|
|
117
|
+
* @param {number} rowIndex - Row index
|
|
118
|
+
* @param {number} colIndex - Column index
|
|
119
|
+
* @returns {HTMLElement} - Table cell element
|
|
120
|
+
*/
|
|
121
|
+
createCell(cellValue, isHeader, rowIndex, colIndex) {
|
|
122
|
+
const cell = document.createElement(isHeader ? 'th' : 'td');
|
|
123
|
+
|
|
124
|
+
// Check for trigger matches
|
|
125
|
+
const match = this.checkCellForTriggers(cellValue);
|
|
126
|
+
|
|
127
|
+
if (match) {
|
|
128
|
+
// Create highlighted span
|
|
129
|
+
const span = document.createElement('span');
|
|
130
|
+
span.className = 'tq-csv-match';
|
|
131
|
+
span.textContent = cellValue;
|
|
132
|
+
span.setAttribute('data-message-state', match.messageState);
|
|
133
|
+
span.setAttribute('data-row', rowIndex);
|
|
134
|
+
span.setAttribute('data-col', colIndex);
|
|
135
|
+
|
|
136
|
+
// Apply highlighting styles
|
|
137
|
+
if (this.options.styleManager) {
|
|
138
|
+
this.options.styleManager.applyCellMatchStyles(span, match.messageState);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// Add click handler to show dropdown
|
|
142
|
+
span.addEventListener('click', (e) => {
|
|
143
|
+
e.stopPropagation();
|
|
144
|
+
this.handleCellMatchClick(span, match, rowIndex, colIndex);
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
cell.appendChild(span);
|
|
148
|
+
} else {
|
|
149
|
+
cell.textContent = cellValue;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// Apply cell styles
|
|
153
|
+
if (this.options.styleManager) {
|
|
154
|
+
if (isHeader) {
|
|
155
|
+
this.options.styleManager.applyHeaderCellStyles(cell);
|
|
156
|
+
} else {
|
|
157
|
+
this.options.styleManager.applyDataCellStyles(cell);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
return cell;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Handle click on matched cell
|
|
166
|
+
* @param {HTMLElement} cellEl - Cell element
|
|
167
|
+
* @param {Object} match - Match data
|
|
168
|
+
* @param {number} rowIndex - Row index
|
|
169
|
+
* @param {number} colIndex - Column index
|
|
170
|
+
*/
|
|
171
|
+
handleCellMatchClick(cellEl, match, rowIndex, colIndex) {
|
|
172
|
+
if (!this.options.dropdownManager) {
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// Create match data for dropdown
|
|
177
|
+
const matchData = {
|
|
178
|
+
text: match.text,
|
|
179
|
+
command: {
|
|
180
|
+
id: `csv-cell-${rowIndex}-${colIndex}`,
|
|
181
|
+
matchType: match.matchType,
|
|
182
|
+
messageState: match.messageState,
|
|
183
|
+
category: match.trigger.category,
|
|
184
|
+
intent: {
|
|
185
|
+
category: match.trigger.category,
|
|
186
|
+
handler: match.trigger.handler
|
|
187
|
+
},
|
|
188
|
+
handler: match.trigger.handler
|
|
189
|
+
},
|
|
190
|
+
intent: {
|
|
191
|
+
category: match.trigger.category,
|
|
192
|
+
handler: match.trigger.handler
|
|
193
|
+
},
|
|
194
|
+
// Store cell position for updates
|
|
195
|
+
csvCellPosition: {
|
|
196
|
+
rowIndex,
|
|
197
|
+
colIndex,
|
|
198
|
+
isHeader: rowIndex === 0
|
|
199
|
+
}
|
|
200
|
+
};
|
|
201
|
+
|
|
202
|
+
// Show dropdown
|
|
203
|
+
this.options.dropdownManager.showDropdown(cellEl, matchData);
|
|
204
|
+
|
|
205
|
+
if (this.options.debug) {
|
|
206
|
+
console.log('[CSVModalManager] Showing dropdown for cell:', match.text, 'at', rowIndex, colIndex);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* Update column header with selected option
|
|
212
|
+
* @param {number} colIndex - Column index
|
|
213
|
+
* @param {string} appendText - Text to append (e.g., "/PST")
|
|
214
|
+
*/
|
|
215
|
+
updateColumnHeader(colIndex, appendText) {
|
|
216
|
+
if (!this.parsedData || !this.parsedData[0]) {
|
|
217
|
+
return;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// Update parsed data
|
|
221
|
+
const originalHeader = this.parsedData[0][colIndex];
|
|
222
|
+
|
|
223
|
+
// Remove any existing suffix (text after /)
|
|
224
|
+
const baseHeader = originalHeader.split('/')[0];
|
|
225
|
+
this.parsedData[0][colIndex] = baseHeader + appendText;
|
|
226
|
+
|
|
227
|
+
// Update the displayed table
|
|
228
|
+
this.refreshTable();
|
|
229
|
+
|
|
230
|
+
if (this.options.debug) {
|
|
231
|
+
console.log('[CSVModalManager] Updated column', colIndex, 'from', originalHeader, 'to', this.parsedData[0][colIndex]);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* Refresh the table display
|
|
237
|
+
*/
|
|
238
|
+
refreshTable() {
|
|
239
|
+
if (!this.modal || !this.parsedData) {
|
|
240
|
+
return;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// Find table container
|
|
244
|
+
const tableContainer = this.modal.querySelector('.tq-csv-table-container');
|
|
245
|
+
if (!tableContainer) {
|
|
246
|
+
return;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
// Clear and rebuild table
|
|
250
|
+
tableContainer.innerHTML = '';
|
|
251
|
+
const table = this.createTable(this.parsedData);
|
|
252
|
+
tableContainer.appendChild(table);
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
/**
|
|
256
|
+
* Create HTML table from parsed CSV data
|
|
257
|
+
* @param {Array} data - 2D array of cells
|
|
258
|
+
* @returns {HTMLElement} - Table element
|
|
259
|
+
*/
|
|
260
|
+
createTable(data) {
|
|
261
|
+
const table = document.createElement('table');
|
|
262
|
+
table.className = 'tq-csv-table';
|
|
263
|
+
|
|
264
|
+
// Create header row
|
|
265
|
+
if (data.length > 0) {
|
|
266
|
+
const thead = document.createElement('thead');
|
|
267
|
+
const headerRow = document.createElement('tr');
|
|
268
|
+
|
|
269
|
+
data[0].forEach((cellValue, colIndex) => {
|
|
270
|
+
const cell = this.createCell(cellValue, true, 0, colIndex);
|
|
271
|
+
headerRow.appendChild(cell);
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
thead.appendChild(headerRow);
|
|
275
|
+
table.appendChild(thead);
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
// Create data rows
|
|
279
|
+
if (data.length > 1) {
|
|
280
|
+
const tbody = document.createElement('tbody');
|
|
281
|
+
|
|
282
|
+
for (let i = 1; i < data.length; i++) {
|
|
283
|
+
const row = document.createElement('tr');
|
|
284
|
+
|
|
285
|
+
data[i].forEach((cellValue, colIndex) => {
|
|
286
|
+
const cell = this.createCell(cellValue, false, i, colIndex);
|
|
287
|
+
row.appendChild(cell);
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
tbody.appendChild(row);
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
table.appendChild(tbody);
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
// Apply table styles
|
|
297
|
+
if (this.options.styleManager) {
|
|
298
|
+
this.options.styleManager.applyTableStyles(table);
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
return table;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
/**
|
|
305
|
+
* Show CSV modal
|
|
306
|
+
* @param {Object} csvData - { file, text, metadata }
|
|
307
|
+
*/
|
|
308
|
+
show(csvData) {
|
|
309
|
+
// Close any existing modal
|
|
310
|
+
this.hide();
|
|
311
|
+
|
|
312
|
+
this.currentCSVData = csvData;
|
|
313
|
+
this.parsedData = this.parseCSV(csvData.text);
|
|
314
|
+
|
|
315
|
+
// Create modal backdrop
|
|
316
|
+
const backdrop = document.createElement('div');
|
|
317
|
+
backdrop.className = 'tq-csv-modal-backdrop';
|
|
318
|
+
|
|
319
|
+
// Create modal
|
|
320
|
+
const modal = document.createElement('div');
|
|
321
|
+
modal.className = 'tq-csv-modal';
|
|
322
|
+
|
|
323
|
+
// Create header
|
|
324
|
+
const header = document.createElement('div');
|
|
325
|
+
header.className = 'tq-csv-modal-header';
|
|
326
|
+
|
|
327
|
+
const title = document.createElement('div');
|
|
328
|
+
title.className = 'tq-csv-modal-title';
|
|
329
|
+
title.textContent = csvData.file.name;
|
|
330
|
+
|
|
331
|
+
const closeBtn = document.createElement('button');
|
|
332
|
+
closeBtn.className = 'tq-csv-modal-close';
|
|
333
|
+
closeBtn.innerHTML = '×';
|
|
334
|
+
closeBtn.onclick = () => this.hide();
|
|
335
|
+
|
|
336
|
+
header.appendChild(title);
|
|
337
|
+
header.appendChild(closeBtn);
|
|
338
|
+
|
|
339
|
+
// Create table container (scrollable)
|
|
340
|
+
const tableContainer = document.createElement('div');
|
|
341
|
+
tableContainer.className = 'tq-csv-table-container';
|
|
342
|
+
|
|
343
|
+
// Create table
|
|
344
|
+
const table = this.createTable(this.parsedData);
|
|
345
|
+
tableContainer.appendChild(table);
|
|
346
|
+
|
|
347
|
+
// Assemble modal
|
|
348
|
+
modal.appendChild(header);
|
|
349
|
+
modal.appendChild(tableContainer);
|
|
350
|
+
|
|
351
|
+
// Apply styles
|
|
352
|
+
if (this.options.styleManager) {
|
|
353
|
+
this.options.styleManager.applyBackdropStyles(backdrop);
|
|
354
|
+
this.options.styleManager.applyModalStyles(modal);
|
|
355
|
+
this.options.styleManager.applyHeaderStyles(header);
|
|
356
|
+
this.options.styleManager.applyTitleStyles(title);
|
|
357
|
+
this.options.styleManager.applyCloseButtonStyles(closeBtn);
|
|
358
|
+
this.options.styleManager.applyTableContainerStyles(tableContainer);
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
// Add to document
|
|
362
|
+
backdrop.appendChild(modal);
|
|
363
|
+
document.body.appendChild(backdrop);
|
|
364
|
+
|
|
365
|
+
this.modal = backdrop;
|
|
366
|
+
|
|
367
|
+
// Close on backdrop click
|
|
368
|
+
backdrop.addEventListener('click', (e) => {
|
|
369
|
+
if (e.target === backdrop) {
|
|
370
|
+
this.hide();
|
|
371
|
+
}
|
|
372
|
+
});
|
|
373
|
+
|
|
374
|
+
// Close on Escape key
|
|
375
|
+
this.escapeHandler = (e) => {
|
|
376
|
+
if (e.key === 'Escape') {
|
|
377
|
+
this.hide();
|
|
378
|
+
}
|
|
379
|
+
};
|
|
380
|
+
document.addEventListener('keydown', this.escapeHandler);
|
|
381
|
+
|
|
382
|
+
if (this.options.debug) {
|
|
383
|
+
console.log('[CSVModalManager] Modal shown for:', csvData.file.name);
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
/**
|
|
388
|
+
* Hide modal
|
|
389
|
+
*/
|
|
390
|
+
hide() {
|
|
391
|
+
if (this.modal) {
|
|
392
|
+
this.modal.remove();
|
|
393
|
+
this.modal = null;
|
|
394
|
+
this.currentCSVData = null;
|
|
395
|
+
this.parsedData = null;
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
if (this.escapeHandler) {
|
|
399
|
+
document.removeEventListener('keydown', this.escapeHandler);
|
|
400
|
+
this.escapeHandler = null;
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
if (this.options.debug) {
|
|
404
|
+
console.log('[CSVModalManager] Modal hidden');
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
/**
|
|
409
|
+
* Cleanup
|
|
410
|
+
*/
|
|
411
|
+
destroy() {
|
|
412
|
+
this.hide();
|
|
413
|
+
if (this.options.debug) {
|
|
414
|
+
console.log('[CSVModalManager] Destroyed');
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
}
|
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
// CSVModalStyleManager - Handles styling for CSV modal
|
|
2
|
+
// Single responsibility: apply consistent styles to CSV modal elements
|
|
3
|
+
|
|
4
|
+
export default class CSVModalStyleManager {
|
|
5
|
+
/**
|
|
6
|
+
* Create CSV modal style manager
|
|
7
|
+
* @param {Object} options - Style options
|
|
8
|
+
*/
|
|
9
|
+
constructor(options = {}) {
|
|
10
|
+
this.options = {
|
|
11
|
+
// Modal colors
|
|
12
|
+
backdropColor: options.backdropColor || 'rgba(0, 0, 0, 0.5)',
|
|
13
|
+
modalBackground: options.modalBackground || '#ffffff',
|
|
14
|
+
|
|
15
|
+
// Header colors
|
|
16
|
+
headerBackground: options.headerBackground || '#f8fafc',
|
|
17
|
+
headerBorder: options.headerBorder || '#e2e8f0',
|
|
18
|
+
|
|
19
|
+
// Table colors
|
|
20
|
+
tableBorder: options.tableBorder || '#e2e8f0',
|
|
21
|
+
headerCellBackground: options.headerCellBackground || '#f1f5f9',
|
|
22
|
+
cellBorder: options.cellBorder || '#e2e8f0',
|
|
23
|
+
|
|
24
|
+
// Highlight colors (matching textarea triggers)
|
|
25
|
+
errorHighlight: options.errorHighlight || '#fee2e2',
|
|
26
|
+
errorBorder: options.errorBorder || '#ef4444',
|
|
27
|
+
warningHighlight: options.warningHighlight || '#fef3c7',
|
|
28
|
+
warningBorder: options.warningBorder || '#f59e0b',
|
|
29
|
+
infoHighlight: options.infoHighlight || '#dbeafe',
|
|
30
|
+
infoBorder: options.infoBorder || '#3b82f6',
|
|
31
|
+
|
|
32
|
+
...options
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Apply backdrop styles
|
|
38
|
+
* @param {HTMLElement} backdrop - Backdrop element
|
|
39
|
+
*/
|
|
40
|
+
applyBackdropStyles(backdrop) {
|
|
41
|
+
Object.assign(backdrop.style, {
|
|
42
|
+
position: 'fixed',
|
|
43
|
+
top: '0',
|
|
44
|
+
left: '0',
|
|
45
|
+
width: '100%',
|
|
46
|
+
height: '100%',
|
|
47
|
+
backgroundColor: this.options.backdropColor,
|
|
48
|
+
display: 'flex',
|
|
49
|
+
alignItems: 'center',
|
|
50
|
+
justifyContent: 'center',
|
|
51
|
+
zIndex: '10000',
|
|
52
|
+
padding: '20px'
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Apply modal styles
|
|
58
|
+
* @param {HTMLElement} modal - Modal element
|
|
59
|
+
*/
|
|
60
|
+
applyModalStyles(modal) {
|
|
61
|
+
Object.assign(modal.style, {
|
|
62
|
+
backgroundColor: this.options.modalBackground,
|
|
63
|
+
borderRadius: '8px',
|
|
64
|
+
boxShadow: '0 10px 40px rgba(0, 0, 0, 0.2)',
|
|
65
|
+
maxWidth: '90vw',
|
|
66
|
+
maxHeight: '90vh',
|
|
67
|
+
width: '100%',
|
|
68
|
+
height: '100%',
|
|
69
|
+
display: 'flex',
|
|
70
|
+
flexDirection: 'column',
|
|
71
|
+
overflow: 'hidden'
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Apply header styles
|
|
77
|
+
* @param {HTMLElement} header - Header element
|
|
78
|
+
*/
|
|
79
|
+
applyHeaderStyles(header) {
|
|
80
|
+
Object.assign(header.style, {
|
|
81
|
+
display: 'flex',
|
|
82
|
+
alignItems: 'center',
|
|
83
|
+
justifyContent: 'space-between',
|
|
84
|
+
padding: '16px 20px',
|
|
85
|
+
borderBottom: `1px solid ${this.options.headerBorder}`,
|
|
86
|
+
backgroundColor: this.options.headerBackground,
|
|
87
|
+
flexShrink: '0'
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Apply title styles
|
|
93
|
+
* @param {HTMLElement} title - Title element
|
|
94
|
+
*/
|
|
95
|
+
applyTitleStyles(title) {
|
|
96
|
+
Object.assign(title.style, {
|
|
97
|
+
fontSize: '18px',
|
|
98
|
+
fontWeight: '600',
|
|
99
|
+
color: '#1e293b'
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Apply close button styles
|
|
105
|
+
* @param {HTMLElement} button - Close button element
|
|
106
|
+
*/
|
|
107
|
+
applyCloseButtonStyles(button) {
|
|
108
|
+
Object.assign(button.style, {
|
|
109
|
+
background: 'transparent',
|
|
110
|
+
border: 'none',
|
|
111
|
+
fontSize: '28px',
|
|
112
|
+
color: '#64748b',
|
|
113
|
+
cursor: 'pointer',
|
|
114
|
+
lineHeight: '1',
|
|
115
|
+
padding: '0',
|
|
116
|
+
width: '32px',
|
|
117
|
+
height: '32px',
|
|
118
|
+
display: 'flex',
|
|
119
|
+
alignItems: 'center',
|
|
120
|
+
justifyContent: 'center',
|
|
121
|
+
borderRadius: '4px',
|
|
122
|
+
transition: 'background-color 0.2s, color 0.2s'
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
button.addEventListener('mouseenter', () => {
|
|
126
|
+
button.style.backgroundColor = '#f1f5f9';
|
|
127
|
+
button.style.color = '#1e293b';
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
button.addEventListener('mouseleave', () => {
|
|
131
|
+
button.style.backgroundColor = 'transparent';
|
|
132
|
+
button.style.color = '#64748b';
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Apply table container styles (scrollable)
|
|
138
|
+
* @param {HTMLElement} container - Table container element
|
|
139
|
+
*/
|
|
140
|
+
applyTableContainerStyles(container) {
|
|
141
|
+
Object.assign(container.style, {
|
|
142
|
+
flex: '1',
|
|
143
|
+
overflow: 'auto',
|
|
144
|
+
padding: '20px'
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Apply table styles
|
|
150
|
+
* @param {HTMLElement} table - Table element
|
|
151
|
+
*/
|
|
152
|
+
applyTableStyles(table) {
|
|
153
|
+
Object.assign(table.style, {
|
|
154
|
+
borderCollapse: 'collapse',
|
|
155
|
+
width: '100%',
|
|
156
|
+
fontSize: '14px',
|
|
157
|
+
backgroundColor: '#ffffff'
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Apply header cell styles
|
|
163
|
+
* @param {HTMLElement} cell - Header cell element
|
|
164
|
+
*/
|
|
165
|
+
applyHeaderCellStyles(cell) {
|
|
166
|
+
Object.assign(cell.style, {
|
|
167
|
+
padding: '12px 16px',
|
|
168
|
+
textAlign: 'left',
|
|
169
|
+
fontWeight: '600',
|
|
170
|
+
backgroundColor: this.options.headerCellBackground,
|
|
171
|
+
border: `1px solid ${this.options.tableBorder}`,
|
|
172
|
+
color: '#1e293b',
|
|
173
|
+
position: 'sticky',
|
|
174
|
+
top: '0',
|
|
175
|
+
zIndex: '10',
|
|
176
|
+
whiteSpace: 'nowrap'
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Apply data cell styles
|
|
182
|
+
* @param {HTMLElement} cell - Data cell element
|
|
183
|
+
*/
|
|
184
|
+
applyDataCellStyles(cell) {
|
|
185
|
+
Object.assign(cell.style, {
|
|
186
|
+
padding: '10px 16px',
|
|
187
|
+
border: `1px solid ${this.options.cellBorder}`,
|
|
188
|
+
color: '#334155',
|
|
189
|
+
whiteSpace: 'nowrap'
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* Apply match highlighting styles to cell content
|
|
195
|
+
* @param {HTMLElement} span - Span element
|
|
196
|
+
* @param {string} messageState - Message state (error, warning, info)
|
|
197
|
+
*/
|
|
198
|
+
applyCellMatchStyles(span, messageState) {
|
|
199
|
+
const colorMap = {
|
|
200
|
+
'error': {
|
|
201
|
+
background: this.options.errorHighlight,
|
|
202
|
+
border: this.options.errorBorder
|
|
203
|
+
},
|
|
204
|
+
'warning': {
|
|
205
|
+
background: this.options.warningHighlight,
|
|
206
|
+
border: this.options.warningBorder
|
|
207
|
+
},
|
|
208
|
+
'info': {
|
|
209
|
+
background: this.options.infoHighlight,
|
|
210
|
+
border: this.options.infoBorder
|
|
211
|
+
}
|
|
212
|
+
};
|
|
213
|
+
|
|
214
|
+
const colors = colorMap[messageState] || colorMap.info;
|
|
215
|
+
|
|
216
|
+
Object.assign(span.style, {
|
|
217
|
+
backgroundColor: colors.background,
|
|
218
|
+
borderBottom: `2px solid ${colors.border}`,
|
|
219
|
+
padding: '2px 4px',
|
|
220
|
+
borderRadius: '3px',
|
|
221
|
+
cursor: 'pointer',
|
|
222
|
+
transition: 'background-color 0.2s',
|
|
223
|
+
display: 'inline-block'
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
// Hover effect
|
|
227
|
+
span.addEventListener('mouseenter', () => {
|
|
228
|
+
span.style.backgroundColor = this.adjustBrightness(colors.background, -10);
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
span.addEventListener('mouseleave', () => {
|
|
232
|
+
span.style.backgroundColor = colors.background;
|
|
233
|
+
});
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* Adjust color brightness
|
|
238
|
+
* @param {string} color - Hex color
|
|
239
|
+
* @param {number} amount - Amount to adjust (-255 to 255)
|
|
240
|
+
* @returns {string} - Adjusted color
|
|
241
|
+
*/
|
|
242
|
+
adjustBrightness(color, amount) {
|
|
243
|
+
// Simple brightness adjustment for hex colors
|
|
244
|
+
const hex = color.replace('#', '');
|
|
245
|
+
const num = parseInt(hex, 16);
|
|
246
|
+
const r = Math.max(0, Math.min(255, (num >> 16) + amount));
|
|
247
|
+
const g = Math.max(0, Math.min(255, ((num >> 8) & 0x00FF) + amount));
|
|
248
|
+
const b = Math.max(0, Math.min(255, (num & 0x0000FF) + amount));
|
|
249
|
+
return `#${((r << 16) | (g << 8) | b).toString(16).padStart(6, '0')}`;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
/**
|
|
253
|
+
* Update theme colors dynamically
|
|
254
|
+
* @param {Object} newColors - New color options
|
|
255
|
+
*/
|
|
256
|
+
updateTheme(newColors) {
|
|
257
|
+
Object.assign(this.options, newColors);
|
|
258
|
+
}
|
|
259
|
+
}
|
package/src/DropdownManager.js
CHANGED
|
@@ -102,8 +102,9 @@ export default class DropdownManager {
|
|
|
102
102
|
// Setup keyboard navigation
|
|
103
103
|
this.setupKeyboardHandlers();
|
|
104
104
|
|
|
105
|
-
// Close on click outside
|
|
105
|
+
// Close on click outside (using mousedown to match icon behavior)
|
|
106
106
|
setTimeout(() => {
|
|
107
|
+
document.addEventListener('mousedown', this.closeDropdownHandler);
|
|
107
108
|
document.addEventListener('click', this.closeDropdownHandler);
|
|
108
109
|
}, 0);
|
|
109
110
|
|
|
@@ -428,6 +429,7 @@ export default class DropdownManager {
|
|
|
428
429
|
this.dropdownOptions = null;
|
|
429
430
|
this.dropdownMatchData = null;
|
|
430
431
|
this.selectedDropdownIndex = 0;
|
|
432
|
+
document.removeEventListener('mousedown', this.closeDropdownHandler);
|
|
431
433
|
document.removeEventListener('click', this.closeDropdownHandler);
|
|
432
434
|
document.removeEventListener('keydown', this.keyboardHandler);
|
|
433
435
|
}
|
|
@@ -437,8 +439,12 @@ export default class DropdownManager {
|
|
|
437
439
|
* Close dropdown handler (bound to document)
|
|
438
440
|
*/
|
|
439
441
|
closeDropdownHandler = (e) => {
|
|
440
|
-
// Only close if clicking outside the dropdown
|
|
442
|
+
// Only close if clicking outside the dropdown AND not on the trigger element
|
|
441
443
|
if (this.activeDropdown && !this.activeDropdown.contains(e.target)) {
|
|
444
|
+
// Check if clicking on the trigger element itself - don't close in that case
|
|
445
|
+
if (this.activeDropdownMatch && (this.activeDropdownMatch === e.target || this.activeDropdownMatch.contains(e.target))) {
|
|
446
|
+
return;
|
|
447
|
+
}
|
|
442
448
|
this.hideDropdown();
|
|
443
449
|
}
|
|
444
450
|
}
|