juxscript 1.1.227 → 1.1.228
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/lib/components/dataframe.d.ts +0 -88
- package/lib/components/dataframe.d.ts.map +1 -1
- package/lib/components/dataframe.js +23 -300
- package/lib/components/dataframe.ts +25 -332
- package/lib/styles/shadcn.css +0 -100
- package/package.json +1 -1
|
@@ -47,11 +47,6 @@ export class DataFrameComponent extends BaseComponent {
|
|
|
47
47
|
this._uploadAccept = '.csv,.tsv,.txt,.xlsx,.xls';
|
|
48
48
|
this._uploadDescription = '';
|
|
49
49
|
this._showUploadIcon = true;
|
|
50
|
-
// ✅ Collapsible state
|
|
51
|
-
this._collapsible = false;
|
|
52
|
-
this._collapsed = false;
|
|
53
|
-
this._summaryTemplate = null;
|
|
54
|
-
this._detailsElement = null;
|
|
55
50
|
this._settingsModal = null;
|
|
56
51
|
this._driver = new TabularDriver(options.dbName ?? 'jux-dataframes', options.storeName ?? 'frames');
|
|
57
52
|
this._showStatus = options.showStatus ?? true;
|
|
@@ -70,10 +65,6 @@ export class DataFrameComponent extends BaseComponent {
|
|
|
70
65
|
this._showReshapeWarning = options.showReshapeWarning ?? true;
|
|
71
66
|
this._persistToIndexedDB = options.persistToIndexedDB ?? false;
|
|
72
67
|
this._clearStorageOnFileRemove = options.clearStorageOnFileRemove ?? true;
|
|
73
|
-
// ✅ Collapsible options
|
|
74
|
-
this._collapsible = options.collapsible ?? false;
|
|
75
|
-
this._collapsed = options.collapsed ?? false;
|
|
76
|
-
this._summaryTemplate = options.summaryTemplate ?? null;
|
|
77
68
|
}
|
|
78
69
|
getTriggerEvents() { return TRIGGER_EVENTS; }
|
|
79
70
|
getCallbackEvents() { return CALLBACK_EVENTS; }
|
|
@@ -149,18 +140,12 @@ export class DataFrameComponent extends BaseComponent {
|
|
|
149
140
|
this._uploadButtonIcon = icon;
|
|
150
141
|
return this;
|
|
151
142
|
}
|
|
152
|
-
/**
|
|
153
|
-
* Set upload button label
|
|
154
|
-
*/
|
|
155
143
|
uploadLabel(label) {
|
|
156
144
|
this._uploadButtonLabel = label;
|
|
157
145
|
if (this._inlineUpload)
|
|
158
146
|
this._inlineUpload.label = label;
|
|
159
147
|
return this;
|
|
160
148
|
}
|
|
161
|
-
/**
|
|
162
|
-
* Set upload button icon
|
|
163
|
-
*/
|
|
164
149
|
uploadIcon(icon) {
|
|
165
150
|
this._uploadButtonIcon = icon;
|
|
166
151
|
this._showUploadIcon = !!icon;
|
|
@@ -168,49 +153,31 @@ export class DataFrameComponent extends BaseComponent {
|
|
|
168
153
|
this._inlineUpload.icon = icon;
|
|
169
154
|
return this;
|
|
170
155
|
}
|
|
171
|
-
/**
|
|
172
|
-
* Set upload button variant (outline, primary, ghost, etc.)
|
|
173
|
-
*/
|
|
174
156
|
uploadVariant(variant) {
|
|
175
157
|
this._uploadButtonVariant = variant;
|
|
176
158
|
return this;
|
|
177
159
|
}
|
|
178
|
-
/**
|
|
179
|
-
* Set accepted file types
|
|
180
|
-
*/
|
|
181
160
|
uploadAccept(accept) {
|
|
182
161
|
this._uploadAccept = accept;
|
|
183
162
|
if (this._inlineUpload)
|
|
184
163
|
this._inlineUpload.accept = accept;
|
|
185
164
|
return this;
|
|
186
165
|
}
|
|
187
|
-
/**
|
|
188
|
-
* Set description text below the upload button
|
|
189
|
-
*/
|
|
190
166
|
uploadDescription(description) {
|
|
191
167
|
this._uploadDescription = description;
|
|
192
168
|
return this;
|
|
193
169
|
}
|
|
194
|
-
/**
|
|
195
|
-
* Show/hide upload icon
|
|
196
|
-
*/
|
|
197
170
|
showUploadIcon(show) {
|
|
198
171
|
this._showUploadIcon = show;
|
|
199
172
|
return this;
|
|
200
173
|
}
|
|
201
174
|
/* ═══════════════════════════════════════════════════
|
|
202
|
-
* STORAGE OPTIONS
|
|
175
|
+
* STORAGE OPTIONS
|
|
203
176
|
* ═══════════════════════════════════════════════════ */
|
|
204
|
-
/**
|
|
205
|
-
* Enable/disable IndexedDB persistence (default: false = session only)
|
|
206
|
-
*/
|
|
207
177
|
persistToIndexedDB(enabled) {
|
|
208
178
|
this._persistToIndexedDB = enabled;
|
|
209
179
|
return this;
|
|
210
180
|
}
|
|
211
|
-
/**
|
|
212
|
-
* Clear stored data when file is removed (default: true)
|
|
213
|
-
*/
|
|
214
181
|
clearStorageOnFileRemove(enabled) {
|
|
215
182
|
this._clearStorageOnFileRemove = enabled;
|
|
216
183
|
return this;
|
|
@@ -218,11 +185,7 @@ export class DataFrameComponent extends BaseComponent {
|
|
|
218
185
|
/* ═══════════════════════════════════════════════════
|
|
219
186
|
* CLEAR / RESET
|
|
220
187
|
* ═══════════════════════════════════════════════════ */
|
|
221
|
-
/**
|
|
222
|
-
* Clear the current data and reset the table
|
|
223
|
-
*/
|
|
224
188
|
async clear() {
|
|
225
|
-
// Clear storage if enabled
|
|
226
189
|
if (this._clearStorageOnFileRemove && this._persistToIndexedDB && this._rawFileData?.file) {
|
|
227
190
|
try {
|
|
228
191
|
const tables = await this._driver.list();
|
|
@@ -235,7 +198,6 @@ export class DataFrameComponent extends BaseComponent {
|
|
|
235
198
|
console.warn('[DataFrame] Failed to clear storage:', err);
|
|
236
199
|
}
|
|
237
200
|
}
|
|
238
|
-
// Reset state
|
|
239
201
|
this._df = null;
|
|
240
202
|
this._rawFileData = null;
|
|
241
203
|
this._sheets.clear();
|
|
@@ -243,24 +205,16 @@ export class DataFrameComponent extends BaseComponent {
|
|
|
243
205
|
this.state.sourceName = '';
|
|
244
206
|
this.state.rowCount = 0;
|
|
245
207
|
this.state.colCount = 0;
|
|
246
|
-
// Clear table display
|
|
247
208
|
if (this._table) {
|
|
248
209
|
this._table.columns([]).rows([]);
|
|
249
210
|
}
|
|
250
|
-
// Remove tabs if present
|
|
251
211
|
const wrapper = document.getElementById(this._id);
|
|
252
212
|
if (wrapper) {
|
|
253
213
|
const existingTabs = wrapper.querySelector('.jux-tabs');
|
|
254
214
|
if (existingTabs)
|
|
255
215
|
existingTabs.remove();
|
|
256
216
|
}
|
|
257
|
-
// ✅ Hide data view, show upload
|
|
258
217
|
this._hideDataView();
|
|
259
|
-
// ✅ Hide collapsible details
|
|
260
|
-
if (this._collapsible && this._detailsElement) {
|
|
261
|
-
this._detailsElement.style.display = 'none';
|
|
262
|
-
}
|
|
263
|
-
// ✅ Clear file from upload component
|
|
264
218
|
if (this._uploadRef) {
|
|
265
219
|
this._uploadRef.clear();
|
|
266
220
|
}
|
|
@@ -321,7 +275,7 @@ export class DataFrameComponent extends BaseComponent {
|
|
|
321
275
|
return this._driver.store(name, this._df);
|
|
322
276
|
}
|
|
323
277
|
/* ═══════════════════════════════════════════════════
|
|
324
|
-
* TABLE OPTIONS
|
|
278
|
+
* TABLE OPTIONS
|
|
325
279
|
* ═══════════════════════════════════════════════════ */
|
|
326
280
|
striped(v) { this._tableOptions.striped = v; return this; }
|
|
327
281
|
hoverable(v) { this._tableOptions.hoverable = v; return this; }
|
|
@@ -333,76 +287,7 @@ export class DataFrameComponent extends BaseComponent {
|
|
|
333
287
|
sheetChunkSize(v) { this._sheetChunkSize = v; return this; }
|
|
334
288
|
maxFileSize(mb) { this._maxFileSize = mb; return this; }
|
|
335
289
|
/* ═══════════════════════════════════════════════════
|
|
336
|
-
*
|
|
337
|
-
* ═══════════════════════════════════════════════════ */
|
|
338
|
-
/**
|
|
339
|
-
* Enable/disable collapsible mode
|
|
340
|
-
*/
|
|
341
|
-
collapsible(enabled) {
|
|
342
|
-
this._collapsible = enabled;
|
|
343
|
-
return this;
|
|
344
|
-
}
|
|
345
|
-
/**
|
|
346
|
-
* Set initial collapsed state (true = collapsed, false = expanded)
|
|
347
|
-
*/
|
|
348
|
-
collapsed(value) {
|
|
349
|
-
this._collapsed = value;
|
|
350
|
-
if (this._detailsElement) {
|
|
351
|
-
this._detailsElement.open = !value;
|
|
352
|
-
}
|
|
353
|
-
return this;
|
|
354
|
-
}
|
|
355
|
-
/**
|
|
356
|
-
* Expand the details (show table)
|
|
357
|
-
*/
|
|
358
|
-
expand() {
|
|
359
|
-
this._collapsed = false;
|
|
360
|
-
if (this._detailsElement) {
|
|
361
|
-
this._detailsElement.open = true;
|
|
362
|
-
}
|
|
363
|
-
return this;
|
|
364
|
-
}
|
|
365
|
-
/**
|
|
366
|
-
* Collapse the details (hide table)
|
|
367
|
-
*/
|
|
368
|
-
collapse() {
|
|
369
|
-
this._collapsed = true;
|
|
370
|
-
if (this._detailsElement) {
|
|
371
|
-
this._detailsElement.open = false;
|
|
372
|
-
}
|
|
373
|
-
return this;
|
|
374
|
-
}
|
|
375
|
-
/**
|
|
376
|
-
* Check if collapsible mode is enabled
|
|
377
|
-
*/
|
|
378
|
-
isCollapsible() {
|
|
379
|
-
return this._collapsible;
|
|
380
|
-
}
|
|
381
|
-
/**
|
|
382
|
-
* Toggle collapsed state
|
|
383
|
-
*/
|
|
384
|
-
toggle() {
|
|
385
|
-
this._collapsed = !this._collapsed;
|
|
386
|
-
if (this._detailsElement) {
|
|
387
|
-
this._detailsElement.open = !this._collapsed;
|
|
388
|
-
}
|
|
389
|
-
return this;
|
|
390
|
-
}
|
|
391
|
-
/**
|
|
392
|
-
* Set custom summary template
|
|
393
|
-
*/
|
|
394
|
-
summaryTemplate(fn) {
|
|
395
|
-
this._summaryTemplate = fn;
|
|
396
|
-
return this;
|
|
397
|
-
}
|
|
398
|
-
/**
|
|
399
|
-
* Get current collapsed state
|
|
400
|
-
*/
|
|
401
|
-
isCollapsed() {
|
|
402
|
-
return this._collapsed;
|
|
403
|
-
}
|
|
404
|
-
/* ═══════════════════════════════════════════════════
|
|
405
|
-
* FILE HANDLING (modified to use storage options)
|
|
290
|
+
* FILE HANDLING
|
|
406
291
|
* ═══════════════════════════════════════════════════ */
|
|
407
292
|
async _handleFile(file) {
|
|
408
293
|
const fileSizeMB = file.size / (1024 * 1024);
|
|
@@ -431,7 +316,6 @@ export class DataFrameComponent extends BaseComponent {
|
|
|
431
316
|
this.state.loading = false;
|
|
432
317
|
return;
|
|
433
318
|
}
|
|
434
|
-
// ✅ Only store if persistence is enabled
|
|
435
319
|
if (this._persistToIndexedDB) {
|
|
436
320
|
await this._driver.store(file.name, sheets[sheetNames[0]], { source: file.name });
|
|
437
321
|
}
|
|
@@ -449,7 +333,6 @@ export class DataFrameComponent extends BaseComponent {
|
|
|
449
333
|
autoDetectDelimiter: true,
|
|
450
334
|
hasHeader: true
|
|
451
335
|
});
|
|
452
|
-
// ✅ Only store if persistence is enabled
|
|
453
336
|
if (this._persistToIndexedDB) {
|
|
454
337
|
await this._driver.store(file.name, df, { source: file.name });
|
|
455
338
|
}
|
|
@@ -481,7 +364,6 @@ export class DataFrameComponent extends BaseComponent {
|
|
|
481
364
|
this._sheets.set(name, df);
|
|
482
365
|
});
|
|
483
366
|
const sheetNames = Object.keys(sheets);
|
|
484
|
-
// Sanitize sheet names for use as DOM IDs
|
|
485
367
|
const sanitizeId = (name) => name.replace(/[^a-zA-Z0-9_-]/g, '_');
|
|
486
368
|
const tabDefs = sheetNames.map(name => ({
|
|
487
369
|
id: sanitizeId(name),
|
|
@@ -492,26 +374,28 @@ export class DataFrameComponent extends BaseComponent {
|
|
|
492
374
|
tabs: tabDefs,
|
|
493
375
|
activeTab: sanitizeId(sheetNames[0])
|
|
494
376
|
});
|
|
495
|
-
// Map sanitized IDs back to original sheet names
|
|
496
377
|
const idToSheetName = new Map();
|
|
497
378
|
sheetNames.forEach(name => idToSheetName.set(sanitizeId(name), name));
|
|
498
379
|
this._tabs.bind('tabChange', (tabId) => {
|
|
499
380
|
const originalName = idToSheetName.get(tabId) || tabId;
|
|
500
381
|
this._df = this._sheets.get(originalName) || null;
|
|
501
382
|
});
|
|
383
|
+
const dataContainer = wrapper.querySelector('.jux-dataframe-data');
|
|
384
|
+
if (dataContainer) {
|
|
385
|
+
dataContainer.style.display = '';
|
|
386
|
+
}
|
|
502
387
|
const tabsContainer = document.createElement('div');
|
|
503
388
|
tabsContainer.className = 'jux-dataframe-tabs';
|
|
504
|
-
|
|
389
|
+
if (dataContainer) {
|
|
390
|
+
dataContainer.appendChild(tabsContainer);
|
|
391
|
+
}
|
|
392
|
+
else {
|
|
393
|
+
wrapper.appendChild(tabsContainer);
|
|
394
|
+
}
|
|
505
395
|
this._tabs.render(tabsContainer);
|
|
506
396
|
sheetNames.forEach((sheetName) => {
|
|
507
397
|
const df = sheets[sheetName];
|
|
508
398
|
const safeId = sanitizeId(sheetName);
|
|
509
|
-
// ✅ DEBUG: Log table options
|
|
510
|
-
console.log(`[DataFrame] Creating table for sheet "${sheetName}" with options:`, {
|
|
511
|
-
filterable: this._tableOptions.filterable,
|
|
512
|
-
paginated: this._tableOptions.paginated,
|
|
513
|
-
sortable: this._tableOptions.sortable
|
|
514
|
-
});
|
|
515
399
|
const table = new Table(`${this._id}-table-${safeId}`, {
|
|
516
400
|
striped: this._tableOptions.striped,
|
|
517
401
|
hoverable: this._tableOptions.hoverable,
|
|
@@ -532,6 +416,7 @@ export class DataFrameComponent extends BaseComponent {
|
|
|
532
416
|
});
|
|
533
417
|
const totalRows = Object.values(sheets).reduce((sum, df) => sum + df.height, 0);
|
|
534
418
|
this._updateStatus(`${sourceName} — ${sheetNames.length} sheets, ${totalRows} total rows`, 'success');
|
|
419
|
+
this._showDataView();
|
|
535
420
|
this._df = sheets[sheetNames[0]];
|
|
536
421
|
this._triggerCallback('load', this._df, null, this);
|
|
537
422
|
}
|
|
@@ -542,7 +427,6 @@ export class DataFrameComponent extends BaseComponent {
|
|
|
542
427
|
const el = document.getElementById(`${this._id}-status`);
|
|
543
428
|
if (!el)
|
|
544
429
|
return;
|
|
545
|
-
// ✅ Only show status during loading/error states
|
|
546
430
|
if (type === 'loading' || type === 'error') {
|
|
547
431
|
el.style.display = '';
|
|
548
432
|
el.className = 'jux-dataframe-status';
|
|
@@ -561,7 +445,6 @@ export class DataFrameComponent extends BaseComponent {
|
|
|
561
445
|
el.appendChild(span);
|
|
562
446
|
}
|
|
563
447
|
else {
|
|
564
|
-
// ✅ Hide status after successful load - table speaks for itself
|
|
565
448
|
el.style.display = 'none';
|
|
566
449
|
}
|
|
567
450
|
}
|
|
@@ -572,7 +455,6 @@ export class DataFrameComponent extends BaseComponent {
|
|
|
572
455
|
this.state.sourceName = sourceName;
|
|
573
456
|
this.state.rowCount = df.height;
|
|
574
457
|
this.state.colCount = df.width;
|
|
575
|
-
// Only strip __EMPTY columns that are ENTIRELY null/empty
|
|
576
458
|
const cols = df.columns;
|
|
577
459
|
const rows = df.toRows();
|
|
578
460
|
const emptyColumns = cols.filter(c => {
|
|
@@ -593,58 +475,37 @@ export class DataFrameComponent extends BaseComponent {
|
|
|
593
475
|
const columnDefs = this._df.columns.map(col => ({ key: col, label: col }));
|
|
594
476
|
this._table.columns(columnDefs).rows(this._df.toRows());
|
|
595
477
|
}
|
|
596
|
-
// ✅ Show the table container, hide upload area
|
|
597
478
|
this._showDataView();
|
|
598
|
-
// ✅ Update collapsible summary if enabled
|
|
599
|
-
if (this._collapsible && this._detailsElement) {
|
|
600
|
-
this._detailsElement.style.display = '';
|
|
601
|
-
this._updateSummary();
|
|
602
|
-
}
|
|
603
|
-
// ✅ Hide status - data is loaded
|
|
604
479
|
this._updateStatus('', 'success');
|
|
605
480
|
this._triggerCallback('load', this._df, null, this);
|
|
606
481
|
}
|
|
607
|
-
/**
|
|
608
|
-
* Show the data view (table + settings gear), hide upload area
|
|
609
|
-
*/
|
|
610
482
|
_showDataView() {
|
|
611
483
|
const wrapper = document.getElementById(this._id);
|
|
612
484
|
if (!wrapper)
|
|
613
485
|
return;
|
|
614
|
-
// Hide upload area
|
|
615
486
|
const uploadArea = wrapper.querySelector('.jux-dataframe-upload-area');
|
|
616
487
|
if (uploadArea) {
|
|
617
488
|
uploadArea.style.display = 'none';
|
|
618
489
|
}
|
|
619
|
-
// Show data container (table + toolbar)
|
|
620
490
|
const dataContainer = wrapper.querySelector('.jux-dataframe-data');
|
|
621
491
|
if (dataContainer) {
|
|
622
492
|
dataContainer.style.display = '';
|
|
623
493
|
}
|
|
624
|
-
// Update settings gear with file info
|
|
625
494
|
this._updateSettingsGear();
|
|
626
495
|
}
|
|
627
|
-
/**
|
|
628
|
-
* Hide data view, show upload area
|
|
629
|
-
*/
|
|
630
496
|
_hideDataView() {
|
|
631
497
|
const wrapper = document.getElementById(this._id);
|
|
632
498
|
if (!wrapper)
|
|
633
499
|
return;
|
|
634
|
-
// Show upload area
|
|
635
500
|
const uploadArea = wrapper.querySelector('.jux-dataframe-upload-area');
|
|
636
501
|
if (uploadArea) {
|
|
637
502
|
uploadArea.style.display = '';
|
|
638
503
|
}
|
|
639
|
-
// Hide data container
|
|
640
504
|
const dataContainer = wrapper.querySelector('.jux-dataframe-data');
|
|
641
505
|
if (dataContainer) {
|
|
642
506
|
dataContainer.style.display = 'none';
|
|
643
507
|
}
|
|
644
508
|
}
|
|
645
|
-
/**
|
|
646
|
-
* Update the settings gear tooltip/info
|
|
647
|
-
*/
|
|
648
509
|
_updateSettingsGear() {
|
|
649
510
|
const gear = document.getElementById(`${this._id}-settings-gear`);
|
|
650
511
|
if (!gear || !this._df)
|
|
@@ -659,9 +520,6 @@ export class DataFrameComponent extends BaseComponent {
|
|
|
659
520
|
gear.title = `${this.state.sourceName} — ${this._df.height} rows × ${this._df.width} cols`;
|
|
660
521
|
}
|
|
661
522
|
}
|
|
662
|
-
/**
|
|
663
|
-
* Show the unified settings modal
|
|
664
|
-
*/
|
|
665
523
|
_showSettingsModal() {
|
|
666
524
|
this._cleanupReshapeModal();
|
|
667
525
|
const fileInfo = this._rawFileData?.file;
|
|
@@ -676,7 +534,6 @@ export class DataFrameComponent extends BaseComponent {
|
|
|
676
534
|
const fileName = fileInfo?.name || this.state.sourceName || 'Unknown';
|
|
677
535
|
let contentHTML = `
|
|
678
536
|
<div class="jux-dataframe-settings-content">
|
|
679
|
-
<!-- File Info Section -->
|
|
680
537
|
<div class="jux-dataframe-settings-section">
|
|
681
538
|
<div class="jux-dataframe-settings-label">Source</div>
|
|
682
539
|
<div class="jux-dataframe-settings-value">
|
|
@@ -684,8 +541,6 @@ export class DataFrameComponent extends BaseComponent {
|
|
|
684
541
|
${fileInfo ? `<span class="jux-muted" style="margin-left: 8px;">${fileSizeKB} KB</span>` : ''}
|
|
685
542
|
</div>
|
|
686
543
|
</div>
|
|
687
|
-
|
|
688
|
-
<!-- Data Info Section -->
|
|
689
544
|
<div class="jux-dataframe-settings-section">
|
|
690
545
|
<div class="jux-dataframe-settings-label">Data</div>
|
|
691
546
|
<div class="jux-dataframe-settings-value">
|
|
@@ -694,7 +549,6 @@ export class DataFrameComponent extends BaseComponent {
|
|
|
694
549
|
</div>
|
|
695
550
|
</div>
|
|
696
551
|
`;
|
|
697
|
-
// Import Settings button (only if we have raw file data)
|
|
698
552
|
if (this._rawFileData) {
|
|
699
553
|
contentHTML += `
|
|
700
554
|
<div class="jux-dataframe-settings-section">
|
|
@@ -707,9 +561,7 @@ export class DataFrameComponent extends BaseComponent {
|
|
|
707
561
|
</div>
|
|
708
562
|
`;
|
|
709
563
|
}
|
|
710
|
-
contentHTML +=
|
|
711
|
-
</div>
|
|
712
|
-
`;
|
|
564
|
+
contentHTML += `</div>`;
|
|
713
565
|
this._settingsModal
|
|
714
566
|
.content(contentHTML)
|
|
715
567
|
.actions([
|
|
@@ -729,7 +581,6 @@ export class DataFrameComponent extends BaseComponent {
|
|
|
729
581
|
]);
|
|
730
582
|
this._settingsModal.render(document.body);
|
|
731
583
|
this._settingsModal.open();
|
|
732
|
-
// Wire up import settings button
|
|
733
584
|
requestAnimationFrame(() => {
|
|
734
585
|
const adjustBtn = document.getElementById(`${this._id}-adjust-import`);
|
|
735
586
|
if (adjustBtn) {
|
|
@@ -740,79 +591,9 @@ export class DataFrameComponent extends BaseComponent {
|
|
|
740
591
|
}
|
|
741
592
|
});
|
|
742
593
|
}
|
|
743
|
-
/**
|
|
744
|
-
* Update the collapsible summary text
|
|
745
|
-
*/
|
|
746
|
-
_updateSummary(isMalformed) {
|
|
747
|
-
if (!this._collapsible || !this._detailsElement)
|
|
748
|
-
return;
|
|
749
|
-
const summaryEl = this._detailsElement.querySelector('.jux-dataframe-summary');
|
|
750
|
-
if (!summaryEl)
|
|
751
|
-
return;
|
|
752
|
-
const malformed = isMalformed ?? (this._df ? this._detectMalformedData(this._df) : false);
|
|
753
|
-
const summaryTextEl = summaryEl.querySelector('.jux-dataframe-summary-text');
|
|
754
|
-
if (summaryTextEl) {
|
|
755
|
-
if (!this._df) {
|
|
756
|
-
summaryTextEl.textContent = 'No data loaded';
|
|
757
|
-
}
|
|
758
|
-
else if (this._summaryTemplate) {
|
|
759
|
-
summaryTextEl.textContent = this._summaryTemplate(this._df);
|
|
760
|
-
}
|
|
761
|
-
else {
|
|
762
|
-
const suffix = malformed ? ' ⚠️' : '';
|
|
763
|
-
summaryTextEl.textContent = `${this.state.sourceName || 'Data'} — ${this._df.height} rows × ${this._df.width} cols${suffix}`;
|
|
764
|
-
}
|
|
765
|
-
}
|
|
766
|
-
// ✅ Add/update settings gear in summary
|
|
767
|
-
let gearBtn = summaryEl.querySelector('.jux-dataframe-summary-gear');
|
|
768
|
-
if (this._df) {
|
|
769
|
-
if (!gearBtn) {
|
|
770
|
-
gearBtn = document.createElement('button');
|
|
771
|
-
gearBtn.className = 'jux-dataframe-summary-gear';
|
|
772
|
-
gearBtn.type = 'button';
|
|
773
|
-
gearBtn.innerHTML = '⚙️';
|
|
774
|
-
gearBtn.addEventListener('click', (e) => {
|
|
775
|
-
e.stopPropagation();
|
|
776
|
-
this._showSettingsModal();
|
|
777
|
-
});
|
|
778
|
-
summaryEl.appendChild(gearBtn);
|
|
779
|
-
}
|
|
780
|
-
if (malformed) {
|
|
781
|
-
gearBtn.classList.add('jux-dataframe-gear-warning');
|
|
782
|
-
}
|
|
783
|
-
else {
|
|
784
|
-
gearBtn.classList.remove('jux-dataframe-gear-warning');
|
|
785
|
-
}
|
|
786
|
-
}
|
|
787
|
-
else if (gearBtn) {
|
|
788
|
-
gearBtn.remove();
|
|
789
|
-
}
|
|
790
|
-
}
|
|
791
594
|
/* ═══════════════════════════════════════════════════
|
|
792
595
|
* MALFORMED DATA DETECTION
|
|
793
596
|
* ═══════════════════════════════════════════════════ */
|
|
794
|
-
_appendSettingsButton(label, variant) {
|
|
795
|
-
const statusEl = document.getElementById(`${this._id}-status`);
|
|
796
|
-
if (!statusEl)
|
|
797
|
-
return;
|
|
798
|
-
// Remove existing settings button if any
|
|
799
|
-
const existing = statusEl.querySelector('.jux-dataframe-settings-btn');
|
|
800
|
-
if (existing)
|
|
801
|
-
existing.remove();
|
|
802
|
-
const btn = document.createElement('button');
|
|
803
|
-
btn.className = `jux-dataframe-settings-btn jux-dataframe-settings-btn-${variant}`;
|
|
804
|
-
btn.type = 'button';
|
|
805
|
-
btn.textContent = `⚙️ ${label}`;
|
|
806
|
-
btn.style.marginLeft = '8px';
|
|
807
|
-
btn.style.cursor = 'pointer';
|
|
808
|
-
btn.style.fontSize = '0.8rem';
|
|
809
|
-
btn.style.padding = '2px 8px';
|
|
810
|
-
btn.style.borderRadius = 'var(--radius, 4px)';
|
|
811
|
-
btn.style.border = '1px solid hsl(var(--border))';
|
|
812
|
-
btn.style.background = variant === 'warning' ? 'hsl(38 92% 50% / 0.15)' : 'transparent';
|
|
813
|
-
btn.addEventListener('click', () => this._showReshapeModal());
|
|
814
|
-
statusEl.appendChild(btn);
|
|
815
|
-
}
|
|
816
597
|
_detectMalformedData(df) {
|
|
817
598
|
const columns = df.columns;
|
|
818
599
|
const rows = df.toRows();
|
|
@@ -862,11 +643,6 @@ export class DataFrameComponent extends BaseComponent {
|
|
|
862
643
|
div.textContent = text;
|
|
863
644
|
return div.innerHTML;
|
|
864
645
|
}
|
|
865
|
-
/**
|
|
866
|
-
* Build a clickable preview table from raw row data.
|
|
867
|
-
* Each row stores its actual sheet row index via data-sheet-row attribute.
|
|
868
|
-
* Returns the table HTML string.
|
|
869
|
-
*/
|
|
870
646
|
_buildClickablePreviewHTML(rawRows, selectedSheetRow) {
|
|
871
647
|
let html = '<table style="width: 100%; border-collapse: collapse; font-size: 12px;">';
|
|
872
648
|
for (const { sheetRow, values } of rawRows) {
|
|
@@ -880,7 +656,6 @@ export class DataFrameComponent extends BaseComponent {
|
|
|
880
656
|
rowStyle += 'background: hsl(var(--muted) / 0.4); color: hsl(var(--muted-foreground)); font-style: italic; opacity: 0.7;';
|
|
881
657
|
}
|
|
882
658
|
html += `<tr data-sheet-row="${sheetRow}" style="${rowStyle}" onmouseover="this.style.outline='2px solid hsl(142 71% 45% / 0.5)'" onmouseout="this.style.outline=''">`;
|
|
883
|
-
// Row index cell
|
|
884
659
|
html += `<td style="padding: 8px 12px; width: 60px; font-weight: 600; color: hsl(var(--muted-foreground)); border-right: 1px solid hsl(var(--border)); text-align: center; user-select: none;">`;
|
|
885
660
|
if (isHeader) {
|
|
886
661
|
html += `<span style="color: hsl(142 71% 45%);">▶ ${sheetRow}</span>`;
|
|
@@ -889,7 +664,6 @@ export class DataFrameComponent extends BaseComponent {
|
|
|
889
664
|
html += `${sheetRow}`;
|
|
890
665
|
}
|
|
891
666
|
html += '</td>';
|
|
892
|
-
// Show first 6 columns
|
|
893
667
|
const displayCols = values.slice(0, 6);
|
|
894
668
|
displayCols.forEach(val => {
|
|
895
669
|
const displayVal = val != null ? String(val).substring(0, 20) : '';
|
|
@@ -901,7 +675,6 @@ export class DataFrameComponent extends BaseComponent {
|
|
|
901
675
|
if (values.length > 6) {
|
|
902
676
|
html += `<td style="padding: 8px 12px; color: hsl(var(--muted-foreground));">…</td>`;
|
|
903
677
|
}
|
|
904
|
-
// Status badge
|
|
905
678
|
html += `<td style="padding: 8px 12px; text-align: right; white-space: nowrap; user-select: none;">`;
|
|
906
679
|
if (isHeader) {
|
|
907
680
|
html += '<span style="background: hsl(142 71% 45%); color: white; padding: 3px 8px; border-radius: 4px; font-size: 10px; font-weight: 600;">HEADER</span>';
|
|
@@ -921,22 +694,17 @@ export class DataFrameComponent extends BaseComponent {
|
|
|
921
694
|
if (!this._rawFileData?.file)
|
|
922
695
|
return;
|
|
923
696
|
this._cleanupReshapeModal();
|
|
924
|
-
// ✅ Use the SAME cell-reading method as the parser
|
|
925
697
|
const rawRows = await this._driver.readRawExcelRows(this._rawFileData.file, 15);
|
|
926
698
|
if (rawRows.length === 0)
|
|
927
699
|
return;
|
|
928
|
-
// Log what we got so we can verify alignment
|
|
929
|
-
console.log('[DataFrame Preview] Raw rows from readRawExcelRows:');
|
|
930
|
-
rawRows.forEach(r => console.log(` sheetRow ${r.sheetRow}:`, r.values.slice(0, 5)));
|
|
931
|
-
// Auto-detect best header row
|
|
932
700
|
let selectedSheetRow = rawRows[0].sheetRow;
|
|
933
701
|
for (const { sheetRow, values } of rawRows) {
|
|
934
702
|
const nonEmpty = values.filter(v => v !== null && v !== undefined && String(v).trim() !== '');
|
|
935
703
|
if (nonEmpty.length < values.length * 0.5)
|
|
936
704
|
continue;
|
|
937
705
|
const nonNumeric = nonEmpty.filter(v => {
|
|
938
|
-
const
|
|
939
|
-
return isNaN(Number(
|
|
706
|
+
const str = String(v).trim();
|
|
707
|
+
return isNaN(Number(str)) && str !== '';
|
|
940
708
|
}).length;
|
|
941
709
|
if (nonNumeric >= nonEmpty.length * 0.7) {
|
|
942
710
|
selectedSheetRow = sheetRow;
|
|
@@ -973,7 +741,6 @@ export class DataFrameComponent extends BaseComponent {
|
|
|
973
741
|
click: async () => {
|
|
974
742
|
const input = document.getElementById(`${this._id}-header-row`);
|
|
975
743
|
const headerRow = parseInt(input.value) || 0;
|
|
976
|
-
console.log(`[DataFrame] Apply clicked: headerRow=${headerRow}`);
|
|
977
744
|
this.state.loading = true;
|
|
978
745
|
this._updateStatus('Re-parsing with new settings...', 'loading');
|
|
979
746
|
try {
|
|
@@ -1031,7 +798,6 @@ export class DataFrameComponent extends BaseComponent {
|
|
|
1031
798
|
tr.addEventListener('click', () => {
|
|
1032
799
|
const rowIdx = parseInt(tr.dataset.sheetRow);
|
|
1033
800
|
hiddenInput.value = String(rowIdx);
|
|
1034
|
-
console.log(`[DataFrame Preview] Clicked sheetRow=${rowIdx}`);
|
|
1035
801
|
updateHint(rowIdx);
|
|
1036
802
|
renderPreview(rowIdx);
|
|
1037
803
|
});
|
|
@@ -1047,7 +813,6 @@ export class DataFrameComponent extends BaseComponent {
|
|
|
1047
813
|
this._cleanupReshapeModal();
|
|
1048
814
|
const text = this._rawFileData.text;
|
|
1049
815
|
const detected = this._driver._detectDelimiter(text);
|
|
1050
|
-
// Parse raw lines
|
|
1051
816
|
const lines = text.replace(/\r\n/g, '\n').replace(/\r/g, '\n').split('\n');
|
|
1052
817
|
const rawRows = [];
|
|
1053
818
|
const maxPreviewRows = Math.min(lines.length, 15);
|
|
@@ -1059,7 +824,6 @@ export class DataFrameComponent extends BaseComponent {
|
|
|
1059
824
|
const values = this._driver._parseLine(lines[i], detected);
|
|
1060
825
|
rawRows.push({ sheetRow: i, values });
|
|
1061
826
|
}
|
|
1062
|
-
// Auto-detect header row
|
|
1063
827
|
let selectedRow = 0;
|
|
1064
828
|
for (const { sheetRow, values } of rawRows) {
|
|
1065
829
|
const nonEmpty = values.filter((v) => v.trim() !== '');
|
|
@@ -1176,7 +940,6 @@ export class DataFrameComponent extends BaseComponent {
|
|
|
1176
940
|
tr.addEventListener('click', () => {
|
|
1177
941
|
const rowIdx = parseInt(tr.dataset.sheetRow);
|
|
1178
942
|
hiddenInput.value = String(rowIdx);
|
|
1179
|
-
console.log(`[DataFrame Preview] Clicked sheetRow=${rowIdx}`);
|
|
1180
943
|
updateHint(rowIdx);
|
|
1181
944
|
renderPreview(rowIdx);
|
|
1182
945
|
});
|
|
@@ -1196,7 +959,7 @@ export class DataFrameComponent extends BaseComponent {
|
|
|
1196
959
|
});
|
|
1197
960
|
}
|
|
1198
961
|
/* ═══════════════════════════════════════════════════
|
|
1199
|
-
* UPDATE & RENDER
|
|
962
|
+
* UPDATE & RENDER
|
|
1200
963
|
* ═══════════════════════════════════════════════════ */
|
|
1201
964
|
update(_prop, _value) { }
|
|
1202
965
|
render(targetId) {
|
|
@@ -1209,9 +972,7 @@ export class DataFrameComponent extends BaseComponent {
|
|
|
1209
972
|
wrapper.className += ` ${className}`;
|
|
1210
973
|
if (style)
|
|
1211
974
|
wrapper.setAttribute('style', style);
|
|
1212
|
-
//
|
|
1213
|
-
// UPLOAD AREA (shown initially, hidden after data loads)
|
|
1214
|
-
// ═══════════════════════════════════════════════════
|
|
975
|
+
// Upload area
|
|
1215
976
|
if (this._inlineUpload) {
|
|
1216
977
|
const uploadArea = document.createElement('div');
|
|
1217
978
|
uploadArea.className = 'jux-dataframe-upload-area';
|
|
@@ -1239,7 +1000,6 @@ export class DataFrameComponent extends BaseComponent {
|
|
|
1239
1000
|
uploadContainer.className = 'jux-dataframe-upload';
|
|
1240
1001
|
uploadContainer.id = `${this._id}-upload-container`;
|
|
1241
1002
|
uploadArea.appendChild(uploadContainer);
|
|
1242
|
-
// Status bar (for loading/error states only)
|
|
1243
1003
|
if (this._showStatus) {
|
|
1244
1004
|
const statusBar = document.createElement('div');
|
|
1245
1005
|
statusBar.className = 'jux-dataframe-status';
|
|
@@ -1260,13 +1020,10 @@ export class DataFrameComponent extends BaseComponent {
|
|
|
1260
1020
|
else {
|
|
1261
1021
|
container.appendChild(wrapper);
|
|
1262
1022
|
}
|
|
1263
|
-
//
|
|
1264
|
-
// DATA CONTAINER (hidden initially, shown after data loads)
|
|
1265
|
-
// ═══════════════════════════════════════════════════
|
|
1023
|
+
// Data container
|
|
1266
1024
|
const dataContainer = document.createElement('div');
|
|
1267
1025
|
dataContainer.className = 'jux-dataframe-data';
|
|
1268
|
-
dataContainer.style.display = 'none';
|
|
1269
|
-
// ✅ Toolbar with settings gear
|
|
1026
|
+
dataContainer.style.display = 'none';
|
|
1270
1027
|
const toolbar = document.createElement('div');
|
|
1271
1028
|
toolbar.className = 'jux-dataframe-toolbar';
|
|
1272
1029
|
const settingsGear = document.createElement('button');
|
|
@@ -1278,42 +1035,8 @@ export class DataFrameComponent extends BaseComponent {
|
|
|
1278
1035
|
settingsGear.addEventListener('click', () => this._showSettingsModal());
|
|
1279
1036
|
toolbar.appendChild(settingsGear);
|
|
1280
1037
|
dataContainer.appendChild(toolbar);
|
|
1281
|
-
// ═══════════════════════════════════════════════════
|
|
1282
|
-
// TABLE CONTAINER (inside data container or collapsible)
|
|
1283
|
-
// ═══════════════════════════════════════════════════
|
|
1284
|
-
let tableContainer;
|
|
1285
|
-
if (this._collapsible) {
|
|
1286
|
-
const details = document.createElement('details');
|
|
1287
|
-
details.className = 'jux-dataframe-details';
|
|
1288
|
-
details.open = !this._collapsed;
|
|
1289
|
-
details.style.display = 'none'; // Hidden until data loads
|
|
1290
|
-
this._detailsElement = details;
|
|
1291
|
-
const summary = document.createElement('summary');
|
|
1292
|
-
summary.className = 'jux-dataframe-summary';
|
|
1293
|
-
const chevron = document.createElement('span');
|
|
1294
|
-
chevron.className = 'jux-dataframe-summary-chevron';
|
|
1295
|
-
chevron.innerHTML = '▶';
|
|
1296
|
-
summary.appendChild(chevron);
|
|
1297
|
-
const textSpan = document.createElement('span');
|
|
1298
|
-
textSpan.className = 'jux-dataframe-summary-text';
|
|
1299
|
-
summary.appendChild(textSpan);
|
|
1300
|
-
details.appendChild(summary);
|
|
1301
|
-
const content = document.createElement('div');
|
|
1302
|
-
content.className = 'jux-dataframe-details-content';
|
|
1303
|
-
details.appendChild(content);
|
|
1304
|
-
dataContainer.appendChild(details);
|
|
1305
|
-
tableContainer = content;
|
|
1306
|
-
details.addEventListener('toggle', () => {
|
|
1307
|
-
this._collapsed = !details.open;
|
|
1308
|
-
});
|
|
1309
|
-
}
|
|
1310
|
-
else {
|
|
1311
|
-
tableContainer = dataContainer;
|
|
1312
|
-
}
|
|
1313
1038
|
wrapper.appendChild(dataContainer);
|
|
1314
|
-
//
|
|
1315
|
-
// TABLE
|
|
1316
|
-
// ═══════════════════════════════════════════════════
|
|
1039
|
+
// Table
|
|
1317
1040
|
const tbl = new Table(`${this._id}-table`, {
|
|
1318
1041
|
striped: this._tableOptions.striped,
|
|
1319
1042
|
hoverable: this._tableOptions.hoverable,
|
|
@@ -1322,7 +1045,7 @@ export class DataFrameComponent extends BaseComponent {
|
|
|
1322
1045
|
paginated: this._tableOptions.paginated,
|
|
1323
1046
|
rowsPerPage: this._tableOptions.rowsPerPage
|
|
1324
1047
|
});
|
|
1325
|
-
tbl.render(
|
|
1048
|
+
tbl.render(dataContainer);
|
|
1326
1049
|
this._table = tbl;
|
|
1327
1050
|
if (this._pendingSource) {
|
|
1328
1051
|
const fn = this._pendingSource;
|