juxscript 1.1.184 → 1.1.186

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.
@@ -45,6 +45,7 @@ export declare class DataFrameComponent extends BaseComponent<DataFrameState> {
45
45
  private _maxFileSize;
46
46
  private _showReshapeWarning;
47
47
  private _rawFileData;
48
+ private _reshapeModal;
48
49
  constructor(id: string, options?: DataFrameOptions);
49
50
  protected getTriggerEvents(): readonly string[];
50
51
  protected getCallbackEvents(): readonly string[];
@@ -95,13 +96,23 @@ export declare class DataFrameComponent extends BaseComponent<DataFrameState> {
95
96
  private _renderMultiSheet;
96
97
  private _updateStatus;
97
98
  private _setDataFrame;
98
- private _updateTable;
99
- private _showFilterInput;
100
99
  /**
101
- * ✅ NEW: Show settings modal to reshape CSV data
100
+ * ✅ NEW: Detect if data looks malformed
101
+ */
102
+ private _detectMalformedData;
103
+ /**
104
+ * ✅ UPDATED: Show settings modal using Modal component
102
105
  */
103
106
  private _showReshapeModal;
104
- update(prop: string, value: any): void;
107
+ /**
108
+ * ✅ UPDATED: Excel reshape modal using Modal component
109
+ */
110
+ private _showExcelReshapeModal;
111
+ /**
112
+ * ✅ UPDATED: CSV reshape modal using Modal component
113
+ */
114
+ private _showCSVReshapeModal;
115
+ update(_prop: string, _value: any): void;
105
116
  render(targetId?: string | HTMLElement | BaseComponent<any>): this;
106
117
  }
107
118
  export declare function dataframe(id: string, options?: DataFrameOptions): DataFrameComponent;
@@ -1 +1 @@
1
- {"version":3,"file":"dataframe.d.ts","sourceRoot":"","sources":["dataframe.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"dataframe.d.ts","sourceRoot":"","sources":["dataframe.ts"],"names":[],"mappings":"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"}
@@ -4,6 +4,7 @@ import { TabularDriver } from '../storage/TabularDriver.js';
4
4
  import { FileUpload } from './fileupload.js';
5
5
  import { Table } from './table.js';
6
6
  import { Tabs } from './tabs.js';
7
+ import { Modal } from './modal.js'; // ✅ Import Modal
7
8
  import { renderIcon } from './icons.js';
8
9
  const TRIGGER_EVENTS = [];
9
10
  const CALLBACK_EVENTS = ['load', 'error', 'transform'];
@@ -22,9 +23,9 @@ export class DataFrameComponent extends BaseComponent {
22
23
  });
23
24
  this._df = null;
24
25
  this._table = null;
25
- this._tabs = null; // ✅ NEW: Tabs for multi-sheet Excel
26
+ this._tabs = null; // ✅ NEW: Tabs for multi-sheet Excel // @ts-ignore used for multi-sheet
26
27
  this._sheets = new Map(); // ✅ NEW: Store all sheets
27
- this._uploadRef = null;
28
+ this._uploadRef = null; // @ts-ignore used for reference tracking
28
29
  this._storageKey = null;
29
30
  this._pendingSource = null;
30
31
  this._inlineUpload = null;
@@ -34,7 +35,8 @@ export class DataFrameComponent extends BaseComponent {
34
35
  this._sheetChunkSize = 10000; // ✅ Default 10k chunk
35
36
  this._maxFileSize = 50; // ✅ Default 50MB
36
37
  this._showReshapeWarning = true;
37
- this._rawFileData = null; // ✅ Store for re-parsing
38
+ this._rawFileData = null;
39
+ this._reshapeModal = null; // ✅ ADD THIS LINE
38
40
  this._driver = new TabularDriver(options.dbName ?? 'jux-dataframes', options.storeName ?? 'frames');
39
41
  this._showStatus = options.showStatus ?? true;
40
42
  this._icon = options.icon ?? '';
@@ -104,6 +106,8 @@ export class DataFrameComponent extends BaseComponent {
104
106
  const isExcel = file.name.toLowerCase().endsWith('.xlsx') ||
105
107
  file.name.toLowerCase().endsWith('.xls');
106
108
  if (isExcel) {
109
+ // ✅ Store raw file for reshape
110
+ this._rawFileData = { file, isExcel: true };
107
111
  // ✅ Pass chunking options for large files
108
112
  const sheets = await this._driver.streamFileMultiSheet(file, {
109
113
  maxSheetSize: this._maxSheetSize,
@@ -125,7 +129,7 @@ export class DataFrameComponent extends BaseComponent {
125
129
  else {
126
130
  // ✅ CSV/TSV: Store raw text for reshaping
127
131
  const text = await file.text();
128
- this._rawFileData = { file, text };
132
+ this._rawFileData = { file, text, isExcel: false };
129
133
  const df = this._driver.parseCSV(text, {
130
134
  autoDetectDelimiter: true,
131
135
  hasHeader: true
@@ -368,7 +372,7 @@ export class DataFrameComponent extends BaseComponent {
368
372
  if (type)
369
373
  el.classList.add(`jux-dataframe-status-${type}`);
370
374
  el.innerHTML = '';
371
- if (this._icon && type === 'success') {
375
+ if (this._icon && (type === 'success' || type === 'warning')) {
372
376
  const iconEl = renderIcon(this._icon);
373
377
  iconEl.style.width = '16px';
374
378
  iconEl.style.height = '16px';
@@ -391,155 +395,307 @@ export class DataFrameComponent extends BaseComponent {
391
395
  if (cleanCols.length < df.columns.length) {
392
396
  this._df = df.select(...cleanCols);
393
397
  }
394
- this._updateTable();
395
- this._updateStatus(`${sourceName} ${this._df.height} rows × ${this._df.width} cols`, 'success');
396
- // Add reshape warning button if CSV and enabled
397
- if (this._showReshapeWarning && this._rawFileData?.text) {
398
+ // Update the table with new data
399
+ if (this._table && this._df) {
400
+ const columnDefs = this._df.columns.map(col => ({
401
+ key: col,
402
+ label: col
403
+ }));
404
+ this._table.columns(columnDefs).rows(this._df.toRows());
405
+ }
406
+ // ✅ Detect malformed data
407
+ const isMalformed = this._detectMalformedData(df);
408
+ // ✅ Show warning if malformed
409
+ if (isMalformed && this._showReshapeWarning && this._rawFileData) {
410
+ this._updateStatus(`⚠️ ${sourceName} — ${this._df.height} rows × ${this._df.width} cols (Data may be malformed)`, 'warning');
411
+ // Add Settings button
398
412
  const statusEl = document.getElementById(`${this._id}-status`);
399
413
  if (statusEl) {
400
414
  const settingsBtn = document.createElement('button');
401
- settingsBtn.textContent = 'Settings';
402
- settingsBtn.className = 'jux-button jux-button-sm jux-button-ghost';
415
+ settingsBtn.textContent = 'Fix Import Settings';
416
+ settingsBtn.className = 'jux-button jux-button-sm jux-button-warning';
403
417
  settingsBtn.style.marginLeft = '0.5rem';
404
418
  settingsBtn.addEventListener('click', () => this._showReshapeModal());
405
419
  statusEl.appendChild(settingsBtn);
406
420
  }
407
421
  }
422
+ else {
423
+ this._updateStatus(`${sourceName} — ${this._df.height} rows × ${this._df.width} cols`, 'success');
424
+ // ✅ Still add Settings button for manual adjustment
425
+ if (this._showReshapeWarning && this._rawFileData) {
426
+ const statusEl = document.getElementById(`${this._id}-status`);
427
+ if (statusEl) {
428
+ const settingsBtn = document.createElement('button');
429
+ settingsBtn.textContent = 'Settings';
430
+ settingsBtn.className = 'jux-button jux-button-sm jux-button-ghost';
431
+ settingsBtn.style.marginLeft = '0.5rem';
432
+ settingsBtn.addEventListener('click', () => this._showReshapeModal());
433
+ statusEl.appendChild(settingsBtn);
434
+ }
435
+ }
436
+ }
408
437
  this._triggerCallback('load', this._df, null, this);
409
438
  }
410
- _updateTable() {
411
- if (!this._table || !this._df)
412
- return;
413
- // ✅ Convert string[] columns to ColumnDef[] with labels
414
- const columnDefs = this._df.columns.map(col => ({
415
- key: col,
416
- label: col
417
- }));
418
- // ✅ Update columns and rows
419
- this._table.columns(columnDefs).rows(this._df.toRows());
420
- // ✅ FIX: Force full table rebuild (including pagination)
421
- const tableElement = this._table['_tableElement'];
422
- if (tableElement) {
423
- const wrapper = tableElement.closest('.jux-table-wrapper');
424
- if (wrapper) {
425
- // Clear table content
426
- tableElement.innerHTML = '';
427
- // Rebuild header
428
- const thead = this._table._buildTableHeader();
429
- tableElement.appendChild(thead);
430
- // Rebuild body
431
- const tbody = document.createElement('tbody');
432
- this._table._renderTableBody(tbody);
433
- tableElement.appendChild(tbody);
434
- // Re-wire events
435
- this._table._wireTriggerEvents(tbody);
436
- // FIX: Re-build pagination controls
437
- if (this._tableOptions.paginated) {
438
- this._table._updatePagination(wrapper, tbody);
439
- }
439
+ /**
440
+ * ✅ NEW: Detect if data looks malformed
441
+ */
442
+ _detectMalformedData(df) {
443
+ const columns = df.columns;
444
+ const rows = df.toRows();
445
+ // Check 1: Columns have generic names like "__EMPTY", "_1", "col_0"
446
+ const hasGenericColumns = columns.some(col => col.startsWith('__EMPTY') ||
447
+ col.startsWith('_') ||
448
+ col.match(/^col_\d+$/));
449
+ if (hasGenericColumns)
450
+ return true;
451
+ // Check 2: First row values look like metadata (e.g., "Exported On:", "12/4/2025")
452
+ if (rows.length > 0) {
453
+ const firstRow = rows[0];
454
+ const values = Object.values(firstRow);
455
+ const hasMetadata = values.some(v => String(v).includes('Exported') ||
456
+ String(v).includes('Generated') ||
457
+ String(v).includes('Report'));
458
+ if (hasMetadata)
459
+ return true;
460
+ }
461
+ // Check 3: Row 2 or 3 has values that look like headers (mostly strings, no numbers)
462
+ if (rows.length >= 3) {
463
+ const secondRow = rows[1];
464
+ const thirdRow = rows[2];
465
+ const checkRow = (row) => {
466
+ const values = Object.values(row);
467
+ const nonNumeric = values.filter(v => {
468
+ const str = String(v).trim();
469
+ return isNaN(Number(str)) && str !== '';
470
+ }).length;
471
+ return nonNumeric >= values.length * 0.7; // 70% non-numeric
472
+ };
473
+ if (checkRow(secondRow) || checkRow(thirdRow)) {
474
+ return true;
440
475
  }
441
476
  }
477
+ return false;
442
478
  }
443
- _showFilterInput() {
444
- const wrapper = document.getElementById(this._id);
445
- if (!wrapper)
446
- return;
447
- if (wrapper.querySelector('.jux-dataframe-filter'))
479
+ /**
480
+ * UPDATED: Show settings modal using Modal component
481
+ */
482
+ _showReshapeModal() {
483
+ if (!this._rawFileData)
448
484
  return;
449
- const filterContainer = document.createElement('div');
450
- filterContainer.className = 'jux-dataframe-filter';
451
- const input = document.createElement('input');
452
- input.type = 'text';
453
- input.placeholder = 'Filter rows...';
454
- input.className = 'jux-input-element jux-dataframe-filter-input';
455
- const iconEl = renderIcon('search');
456
- iconEl.style.width = '16px';
457
- iconEl.style.height = '16px';
458
- const iconWrap = document.createElement('span');
459
- iconWrap.className = 'jux-dataframe-filter-icon';
460
- iconWrap.appendChild(iconEl);
461
- filterContainer.appendChild(iconWrap);
462
- filterContainer.appendChild(input);
463
- const tableElement = wrapper.querySelector('.jux-table-wrapper table');
464
- if (tableElement && tableElement.parentElement) {
465
- tableElement.parentElement.insertBefore(filterContainer, tableElement);
485
+ const isExcel = this._rawFileData.isExcel;
486
+ if (isExcel) {
487
+ this._showExcelReshapeModal();
466
488
  }
467
489
  else {
468
- wrapper.appendChild(filterContainer);
490
+ this._showCSVReshapeModal();
469
491
  }
470
- input.addEventListener('input', () => {
471
- if (!this._df)
472
- return;
473
- const text = input.value.toLowerCase();
474
- if (!text) {
475
- this._table?.rows(this._df.toRows());
476
- return;
477
- }
478
- const filtered = this._df.filter((row) => {
479
- return Object.values(row).some(v => v !== null && v !== undefined && String(v).toLowerCase().includes(text));
480
- });
481
- this._table?.rows(filtered.toRows());
482
- });
483
492
  }
484
493
  /**
485
- * ✅ NEW: Show settings modal to reshape CSV data
494
+ * ✅ UPDATED: Excel reshape modal using Modal component
486
495
  */
487
- _showReshapeModal() {
488
- if (!this._rawFileData)
496
+ async _showExcelReshapeModal() {
497
+ if (!this._rawFileData?.file)
489
498
  return;
490
- const modal = document.createElement('div');
491
- modal.className = 'jux-modal-overlay';
492
- modal.innerHTML = `
493
- <div class="jux-modal" style="max-width: 800px;">
494
- <div class="jux-modal-header">
495
- <div class="jux-modal-header-title">Data Import Settings</div>
496
- <button class="jux-modal-close" id="${this._id}-modal-close">×</button>
497
- </div>
498
- <div class="jux-modal-content" style="padding: 1.5rem;">
499
- <div style="margin-bottom: 1rem;">
500
- <label style="display: block; font-weight: 600; margin-bottom: 0.5rem;">Delimiter</label>
501
- <select id="${this._id}-delimiter" class="jux-input-element" style="width: 100%;">
502
- <option value=",">Comma (,)</option>
503
- <option value="|">Pipe (|)</option>
504
- <option value="\t">Tab (\\t)</option>
505
- <option value=";">Semicolon (;)</option>
506
- </select>
507
- </div>
508
-
509
- <div style="margin-bottom: 1rem;">
510
- <label style="display: block; font-weight: 600; margin-bottom: 0.5rem;">Header Row (0-based)</label>
511
- <input type="number" id="${this._id}-header-row" class="jux-input-element" value="0" min="0" max="50" style="width: 100%;" />
512
- </div>
513
-
514
- <div style="margin-bottom: 1rem;">
515
- <label style="display: block; font-weight: 600; margin-bottom: 0.5rem;">Skip Rows Before Header</label>
516
- <input type="number" id="${this._id}-skip-rows" class="jux-input-element" value="0" min="0" max="50" style="width: 100%;" />
517
- </div>
518
-
519
- <div style="border: 1px solid hsl(var(--border)); border-radius: var(--radius); padding: 1rem; background: hsl(var(--muted) / 0.3); max-height: 300px; overflow-y: auto;">
520
- <div style="font-weight: 600; margin-bottom: 0.5rem; color: hsl(var(--foreground));">Preview</div>
521
- <div id="${this._id}-preview" style="font-family: monospace; font-size: 0.75rem; white-space: pre; color: hsl(var(--muted-foreground));"></div>
522
- </div>
499
+ // Create modal if not exists
500
+ if (!this._reshapeModal) {
501
+ this._reshapeModal = new Modal(`${this._id}-reshape-modal`, {
502
+ title: 'Excel Import Settings',
503
+ size: 'large',
504
+ close: true,
505
+ backdropClose: false
506
+ });
507
+ }
508
+ // Build modal content
509
+ const modalContent = document.createElement('div');
510
+ modalContent.innerHTML = `
511
+ <div style="margin-bottom: 1rem;">
512
+ <label style="display: block; font-weight: 600; margin-bottom: 0.5rem; color: hsl(var(--foreground));">
513
+ Header Row (0-based)
514
+ </label>
515
+ <input
516
+ type="number"
517
+ id="${this._id}-header-row"
518
+ class="jux-input-element"
519
+ value="0"
520
+ min="0"
521
+ max="50"
522
+ style="width: 100%;"
523
+ />
524
+ <div style="font-size: 0.875rem; color: hsl(var(--muted-foreground)); margin-top: 0.5rem; padding: 0.5rem; background: hsl(var(--warning) / 0.1); border-radius: var(--radius); border: 1px solid hsl(var(--warning) / 0.3);">
525
+ ⚠️ <strong>Detected issue:</strong> Row 0 contains metadata ("Exported On:"), row 2 contains actual headers ("340B ID"). Try setting this to <strong>2</strong>.
523
526
  </div>
524
- <div class="jux-modal-footer">
525
- <button id="${this._id}-cancel-reshape" class="jux-button jux-button-ghost">Cancel</button>
526
- <button id="${this._id}-apply-reshape" class="jux-button">Apply & Re-import</button>
527
+ </div>
528
+
529
+ <div style="border: 1px solid hsl(var(--border)); border-radius: var(--radius); padding: 1rem; background: hsl(var(--muted) / 0.3); max-height: 400px; overflow-y: auto;">
530
+ <div style="font-weight: 600; margin-bottom: 0.5rem; color: hsl(var(--foreground));">
531
+ Preview (first 10 rows)
527
532
  </div>
533
+ <div id="${this._id}-preview" style="font-family: 'JetBrains Mono', 'Courier New', monospace; font-size: 0.75rem; white-space: pre; color: hsl(var(--foreground)); line-height: 1.5;"></div>
534
+ </div>
535
+ `;
536
+ this._reshapeModal
537
+ .content(modalContent.innerHTML)
538
+ .actions([
539
+ {
540
+ label: 'Cancel',
541
+ variant: 'secondary',
542
+ click: () => this._reshapeModal.closeModal()
543
+ },
544
+ {
545
+ label: 'Apply & Re-import',
546
+ variant: 'primary',
547
+ click: async () => {
548
+ const headerRowInput = document.getElementById(`${this._id}-header-row`);
549
+ const headerRow = parseInt(headerRowInput.value) || 0;
550
+ this.state.loading = true;
551
+ this._updateStatus('⏳ Re-parsing with new settings...', 'loading');
552
+ try {
553
+ const sheets = await this._driver.streamFileMultiSheet(this._rawFileData.file, {
554
+ headerRow,
555
+ maxSheetSize: this._maxSheetSize,
556
+ sheetChunkSize: this._sheetChunkSize
557
+ });
558
+ const sheetNames = Object.keys(sheets);
559
+ await this._driver.store(this._rawFileData.file.name, sheets[sheetNames[0]], { source: this._rawFileData.file.name });
560
+ if (sheetNames.length > 1) {
561
+ this._renderMultiSheet(sheets, this._rawFileData.file.name);
562
+ }
563
+ else {
564
+ this._setDataFrame(sheets[sheetNames[0]], this._rawFileData.file.name);
565
+ }
566
+ this._reshapeModal.closeModal();
567
+ }
568
+ catch (err) {
569
+ this._updateStatus(`❌ ${err.message}`, 'error');
570
+ }
571
+ }
572
+ }
573
+ ])
574
+ .render(document.body);
575
+ const headerRowInput = document.getElementById(`${this._id}-header-row`);
576
+ const previewDiv = document.getElementById(`${this._id}-preview`);
577
+ // Update preview on header row change
578
+ const updatePreview = async () => {
579
+ const headerRow = parseInt(headerRowInput.value) || 0;
580
+ try {
581
+ const sheets = await this._driver.streamFileMultiSheet(this._rawFileData.file, {
582
+ headerRow,
583
+ maxSheetSize: 10
584
+ });
585
+ const firstSheet = Object.values(sheets)[0];
586
+ if (!firstSheet) {
587
+ previewDiv.textContent = '⚠️ No data found';
588
+ return;
589
+ }
590
+ const preview = firstSheet.toRows().slice(0, 10).map((row, i) => {
591
+ const cols = Object.values(row).map(v => String(v).padEnd(20)).join(' │ ');
592
+ return `${i === 0 ? '📌 ' : ' '}${cols}`;
593
+ }).join('\n');
594
+ previewDiv.textContent = `Columns: ${firstSheet.columns.join(' │ ')}\n${'─'.repeat(80)}\n${preview}`;
595
+ }
596
+ catch (err) {
597
+ previewDiv.textContent = `⚠️ Error: ${err.message}`;
598
+ }
599
+ };
600
+ headerRowInput.addEventListener('input', updatePreview);
601
+ updatePreview();
602
+ this._reshapeModal.open();
603
+ }
604
+ /**
605
+ * ✅ UPDATED: CSV reshape modal using Modal component
606
+ */
607
+ _showCSVReshapeModal() {
608
+ if (!this._rawFileData)
609
+ return;
610
+ // Create modal if not exists
611
+ if (!this._reshapeModal) {
612
+ this._reshapeModal = new Modal(`${this._id}-reshape-modal`, {
613
+ title: 'CSV Import Settings',
614
+ size: 'large',
615
+ close: true,
616
+ backdropClose: false
617
+ });
618
+ }
619
+ // Build modal content
620
+ const modalContent = document.createElement('div');
621
+ modalContent.innerHTML = `
622
+ <div style="margin-bottom: 1rem;">
623
+ <label style="display: block; font-weight: 600; margin-bottom: 0.5rem;">Delimiter</label>
624
+ <select id="${this._id}-delimiter" class="jux-input-element" style="width: 100%;">
625
+ <option value=",">Comma (,)</option>
626
+ <option value="|">Pipe (|)</option>
627
+ <option value="\t">Tab (\\t)</option>
628
+ <option value=";">Semicolon (;)</option>
629
+ </select>
630
+ </div>
631
+
632
+ <div style="margin-bottom: 1rem;">
633
+ <label style="display: block; font-weight: 600; margin-bottom: 0.5rem;">Header Row (0-based)</label>
634
+ <input type="number" id="${this._id}-header-row" class="jux-input-element" value="0" min="0" max="50" style="width: 100%;" />
635
+ </div>
636
+
637
+ <div style="margin-bottom: 1rem;">
638
+ <label style="display: block; font-weight: 600; margin-bottom: 0.5rem;">Skip Rows Before Header</label>
639
+ <input type="number" id="${this._id}-skip-rows" class="jux-input-element" value="0" min="0" max="50" style="width: 100%;" />
640
+ </div>
641
+
642
+ <div style="border: 1px solid hsl(var(--border)); border-radius: var(--radius); padding: 1rem; background: hsl(var(--muted) / 0.3); max-height: 400px; overflow-y: auto;">
643
+ <div style="font-weight: 600; margin-bottom: 0.5rem; color: hsl(var(--foreground));">Preview</div>
644
+ <div id="${this._id}-preview" style="font-family: 'JetBrains Mono', 'Courier New', monospace; font-size: 0.75rem; white-space: pre; color: hsl(var(--foreground)); line-height: 1.5;"></div>
528
645
  </div>
529
646
  `;
530
- document.body.appendChild(modal);
647
+ this._reshapeModal
648
+ .content(modalContent.innerHTML)
649
+ .actions([
650
+ {
651
+ label: 'Cancel',
652
+ variant: 'secondary',
653
+ click: () => this._reshapeModal.closeModal()
654
+ },
655
+ {
656
+ label: 'Apply & Re-import',
657
+ variant: 'primary',
658
+ click: async () => {
659
+ if (!this._rawFileData?.text)
660
+ return;
661
+ const delimiterSelect = document.getElementById(`${this._id}-delimiter`);
662
+ const headerRowInput = document.getElementById(`${this._id}-header-row`);
663
+ const skipRowsInput = document.getElementById(`${this._id}-skip-rows`);
664
+ const delim = delimiterSelect.value === '\\t' ? '\t' : delimiterSelect.value;
665
+ const headerRow = parseInt(headerRowInput.value) || 0;
666
+ const skipRows = parseInt(skipRowsInput.value) || 0;
667
+ this.state.loading = true;
668
+ this._updateStatus('⏳ Re-parsing with new settings...', 'loading');
669
+ try {
670
+ const df = this._driver.parseCSV(this._rawFileData.text, {
671
+ delimiter: delim,
672
+ headerRow,
673
+ skipRows,
674
+ hasHeader: true
675
+ });
676
+ await this._driver.store(this._rawFileData.file.name, df, { source: this._rawFileData.file.name });
677
+ this._setDataFrame(df, this._rawFileData.file.name);
678
+ this._reshapeModal.closeModal();
679
+ }
680
+ catch (err) {
681
+ this._updateStatus(`❌ ${err.message}`, 'error');
682
+ }
683
+ }
684
+ }
685
+ ])
686
+ .render(document.body);
531
687
  const delimiterSelect = document.getElementById(`${this._id}-delimiter`);
532
688
  const headerRowInput = document.getElementById(`${this._id}-header-row`);
533
689
  const skipRowsInput = document.getElementById(`${this._id}-skip-rows`);
534
690
  const previewDiv = document.getElementById(`${this._id}-preview`);
535
- // Auto-detect initial values
691
+ // Auto-detect initial values
536
692
  if (this._rawFileData.text) {
537
693
  const detected = this._driver._detectDelimiter(this._rawFileData.text);
538
694
  delimiterSelect.value = detected === '\t' ? '\\t' : detected;
539
695
  const headerRow = this._driver._detectHeaderRow(this._rawFileData.text, detected);
540
696
  headerRowInput.value = String(headerRow);
541
697
  }
542
- // Update preview on changes
698
+ // Update preview on changes
543
699
  const updatePreview = async () => {
544
700
  if (!this._rawFileData?.text)
545
701
  return;
@@ -552,13 +708,13 @@ export class DataFrameComponent extends BaseComponent {
552
708
  headerRow,
553
709
  skipRows,
554
710
  hasHeader: true,
555
- maxRows: 10 // Preview first 10 rows
711
+ maxRows: 10
556
712
  });
557
713
  const preview = df.toRows().map((row, i) => {
558
- const cols = Object.values(row).map(v => String(v).padEnd(20)).join(' | ');
714
+ const cols = Object.values(row).map(v => String(v).padEnd(20)).join(' ');
559
715
  return `${i === 0 ? '📌 ' : ' '}${cols}`;
560
716
  }).join('\n');
561
- previewDiv.textContent = `Columns: ${df.columns.join(' | ')}\n\n${preview}`;
717
+ previewDiv.textContent = `Columns: ${df.columns.join(' ')}\n${'─'.repeat(80)}\n${preview}`;
562
718
  }
563
719
  catch (err) {
564
720
  previewDiv.textContent = `⚠️ Error: ${err.message}`;
@@ -568,36 +724,9 @@ export class DataFrameComponent extends BaseComponent {
568
724
  headerRowInput.addEventListener('input', updatePreview);
569
725
  skipRowsInput.addEventListener('input', updatePreview);
570
726
  updatePreview();
571
- // ✅ Apply button
572
- document.getElementById(`${this._id}-apply-reshape`).addEventListener('click', async () => {
573
- if (!this._rawFileData?.text)
574
- return;
575
- const delim = delimiterSelect.value === '\\t' ? '\t' : delimiterSelect.value;
576
- const headerRow = parseInt(headerRowInput.value) || 0;
577
- const skipRows = parseInt(skipRowsInput.value) || 0;
578
- this.state.loading = true;
579
- this._updateStatus('⏳ Re-parsing with new settings...', 'loading');
580
- try {
581
- const df = this._driver.parseCSV(this._rawFileData.text, {
582
- delimiter: delim,
583
- headerRow,
584
- skipRows,
585
- hasHeader: true
586
- });
587
- await this._driver.store(this._rawFileData.file.name, df, { source: this._rawFileData.file.name });
588
- this._setDataFrame(df, this._rawFileData.file.name);
589
- modal.remove();
590
- }
591
- catch (err) {
592
- this._updateStatus(`❌ ${err.message}`, 'error');
593
- }
594
- });
595
- // ✅ Cancel/Close buttons
596
- const closeModal = () => modal.remove();
597
- document.getElementById(`${this._id}-cancel-reshape`).addEventListener('click', closeModal);
598
- document.getElementById(`${this._id}-modal-close`).addEventListener('click', closeModal);
727
+ this._reshapeModal.open();
599
728
  }
600
- update(prop, value) { }
729
+ update(_prop, _value) { }
601
730
  /* ═══════════════════════════════════════════════════
602
731
  * RENDER
603
732
  * ═══════════════════════════════════════════════════ */
@@ -633,6 +762,8 @@ export class DataFrameComponent extends BaseComponent {
633
762
  const isExcel = file.name.toLowerCase().endsWith('.xlsx') ||
634
763
  file.name.toLowerCase().endsWith('.xls');
635
764
  if (isExcel) {
765
+ // ✅ Store raw file for reshape
766
+ this._rawFileData = { file, isExcel: true };
636
767
  const sheets = await this._driver.streamFileMultiSheet(file, {
637
768
  maxSheetSize: this._maxSheetSize,
638
769
  sheetChunkSize: this._sheetChunkSize,