juxscript 1.1.205 → 1.1.207

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.
@@ -87,11 +87,16 @@ export declare class DataFrameComponent extends BaseComponent<DataFrameState> {
87
87
  private _updateStatus;
88
88
  private _setDataFrame;
89
89
  private _detectMalformedData;
90
- private _detectLikelyHeaderRow;
91
90
  private _showReshapeModal;
92
91
  private _cleanupReshapeModal;
93
- private _showExcelReshapeModal;
94
92
  private _escapeHtml;
93
+ /**
94
+ * Build a clickable preview table from raw row data.
95
+ * Each row stores its actual sheet row index via data-sheet-row attribute.
96
+ * Returns the table HTML string.
97
+ */
98
+ private _buildClickablePreviewHTML;
99
+ private _showExcelReshapeModal;
95
100
  private _showCSVReshapeModal;
96
101
  update(_prop: string, _value: any): void;
97
102
  render(targetId?: string | HTMLElement | BaseComponent<any>): this;
@@ -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":"AAAA,OAAO,EAAE,aAAa,EAAE,SAAS,EAAE,MAAM,yBAAyB,CAAC;AACnE,OAAO,EAAE,SAAS,EAAE,MAAM,yBAAyB,CAAC;AACpD,OAAO,EAAE,aAAa,EAAE,MAAM,6BAA6B,CAAC;AAC5D,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAC7C,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AASnC,MAAM,WAAW,gBAAgB;IAC7B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAC7B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,KAAK,cAAc,GAAG,SAAS,GAAG;IAC9B,MAAM,EAAE,OAAO,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;CACpB,CAAC;AAEF,qBAAa,kBAAmB,SAAQ,aAAa,CAAC,cAAc,CAAC;IACjE,OAAO,CAAC,GAAG,CAA0B;IACrC,OAAO,CAAC,OAAO,CAAgB;IAC/B,OAAO,CAAC,MAAM,CAAsB;IACpC,OAAO,CAAC,KAAK,CAAqB;IAClC,OAAO,CAAC,OAAO,CAAqC;IACpD,OAAO,CAAC,aAAa,CAOnB;IACF,OAAO,CAAC,UAAU,CAA2B;IAC7C,OAAO,CAAC,WAAW,CAAuB;IAC1C,OAAO,CAAC,cAAc,CAAsC;IAC5D,OAAO,CAAC,aAAa,CAAgE;IACrF,OAAO,CAAC,WAAW,CAAiB;IACpC,OAAO,CAAC,KAAK,CAAc;IAC3B,OAAO,CAAC,aAAa,CAAkB;IACvC,OAAO,CAAC,eAAe,CAAiB;IACxC,OAAO,CAAC,YAAY,CAAc;IAClC,OAAO,CAAC,mBAAmB,CAAiB;IAC5C,OAAO,CAAC,YAAY,CAAiE;IACrF,OAAO,CAAC,aAAa,CAAsB;IAC3C,OAAO,CAAC,qBAAqB,CAAkB;gBAEnC,EAAE,EAAE,MAAM,EAAE,OAAO,GAAE,gBAAqB;IAmCtD,SAAS,CAAC,gBAAgB,IAAI,SAAS,MAAM,EAAE;IAC/C,SAAS,CAAC,iBAAiB,IAAI,SAAS,MAAM,EAAE;IAMhD,WAAW,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI;IAwB9B,UAAU,CAAC,MAAM,EAAE,UAAU,GAAG,IAAI;IAWpC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAAE,GAAG,MAAM,CAAC,MAAM,EAAE,GAAG,EAAE,CAAC,GAAG,IAAI;IAiBnE,UAAU,CAAC,KAAK,GAAE,MAAsB,EAAE,MAAM,GAAE,MAAoC,EAAE,IAAI,GAAE,MAAiB,GAAG,IAAI;IAStH,UAAU,CAAC,CAAC,EAAE,OAAO,GAAG,IAAI;IAC5B,UAAU,CAAC,CAAC,EAAE,MAAM,GAAG,IAAI;IAM3B,KAAK,CAAC,EAAE,EAAE,CAAC,EAAE,EAAE,SAAS,KAAK,SAAS,GAAG,IAAI;IAQ7C,MAAM,CAAC,SAAS,EAAE,CAAC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAAE,KAAK,EAAE,MAAM,KAAK,OAAO,GAAG,IAAI;IAI7E,MAAM,CAAC,GAAG,IAAI,EAAE,MAAM,EAAE,GAAG,IAAI;IAI/B,IAAI,CAAC,GAAG,EAAE,MAAM,EAAE,UAAU,CAAC,EAAE,OAAO,GAAG,IAAI;IAI7C,IAAI,CAAC,CAAC,GAAE,MAAU,GAAG,IAAI;IAIzB,IAAI,CAAC,CAAC,GAAE,MAAU,GAAG,IAAI;IAIzB,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,CAAC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAAE,KAAK,EAAE,MAAM,KAAK,GAAG,GAAG,IAAI;IAIpF,KAAK,CAAC,GAAG,EAAE,MAAM,EAAE,EAAE,EAAE,IAAI,GAAG,IAAI,GAAG,GAAG,GAAG,GAAG,GAAG,IAAI,GAAG,IAAI,GAAG,UAAU,GAAG,YAAY,GAAG,UAAU,EAAE,KAAK,EAAE,GAAG,GAAG,IAAI;IAQxH,IAAI,EAAE,IAAI,SAAS,GAAG,IAAI,CAAqB;IAC/C,IAAI,MAAM,IAAI,aAAa,CAAyB;IACpD,IAAI,KAAK,IAAI,KAAK,GAAG,IAAI,CAAwB;IACjD,QAAQ,IAAI,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,IAAI;IACtC,KAAK,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,MAAM;IACjC,MAAM,IAAI,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAAE;IAC/B,IAAI,KAAK,IAAI,CAAC,MAAM,EAAE,MAAM,CAAC,CAAsC;IACnE,IAAI,OAAO,IAAI,MAAM,EAAE,CAAoC;IAErD,IAAI,CAAC,GAAG,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;IAUhD,OAAO,CAAC,CAAC,EAAE,OAAO,GAAG,IAAI;IACzB,SAAS,CAAC,CAAC,EAAE,OAAO,GAAG,IAAI;IAC3B,QAAQ,CAAC,CAAC,EAAE,OAAO,GAAG,IAAI;IAC1B,UAAU,CAAC,CAAC,EAAE,OAAO,GAAG,IAAI;IAC5B,SAAS,CAAC,CAAC,EAAE,OAAO,GAAG,IAAI;IAC3B,WAAW,CAAC,CAAC,EAAE,MAAM,GAAG,IAAI;IAC5B,YAAY,CAAC,CAAC,EAAE,MAAM,GAAG,IAAI;IAC7B,cAAc,CAAC,CAAC,EAAE,MAAM,GAAG,IAAI;IAC/B,WAAW,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI;YAMf,WAAW;IAgEzB,OAAO,CAAC,iBAAiB;IA+EzB,OAAO,CAAC,aAAa;IAuBrB,OAAO,CAAC,aAAa;IA4ErB,OAAO,CAAC,oBAAoB;IAiC5B,OAAO,CAAC,iBAAiB;IAUzB,OAAO,CAAC,oBAAoB;IAS5B,OAAO,CAAC,WAAW;IAMnB;;;;OAIG;IACH,OAAO,CAAC,0BAA0B;YA2DpB,sBAAsB;IA4IpC,OAAO,CAAC,oBAAoB;IA6K5B,MAAM,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,GAAG,GAAG,IAAI;IAExC,MAAM,CAAC,QAAQ,CAAC,EAAE,MAAM,GAAG,WAAW,GAAG,aAAa,CAAC,GAAG,CAAC,GAAG,IAAI;CAoErE;AAED,wBAAgB,SAAS,CAAC,EAAE,EAAE,MAAM,EAAE,OAAO,GAAE,gBAAqB,GAAG,kBAAkB,CAExF"}
@@ -416,29 +416,6 @@ export class DataFrameComponent extends BaseComponent {
416
416
  }
417
417
  return false;
418
418
  }
419
- _detectLikelyHeaderRow(df) {
420
- const rows = df.toRows();
421
- const cols = df.columns;
422
- const colsAreGeneric = cols.some(c => c.startsWith('__EMPTY') || c.match(/^_\d+$/) || c.match(/^col_\d+$/));
423
- if (!colsAreGeneric)
424
- return 0;
425
- for (let i = 0; i < Math.min(rows.length, 10); i++) {
426
- const row = rows[i];
427
- const values = Object.values(row);
428
- const nonEmpty = values.filter(v => v !== null && v !== undefined && String(v).trim() !== '');
429
- if (nonEmpty.length < values.length * 0.5)
430
- continue;
431
- const nonNumericCount = nonEmpty.filter(v => {
432
- const str = String(v).trim();
433
- return isNaN(Number(str)) && str !== '';
434
- }).length;
435
- if (nonNumericCount >= nonEmpty.length * 0.7) {
436
- // toRows index i = file row (i + 1) since row 0 was used as headers
437
- return i + 1;
438
- }
439
- }
440
- return 0;
441
- }
442
419
  /* ═══════════════════════════════════════════════════
443
420
  * RESHAPE MODAL
444
421
  * ═══════════════════════════════════════════════════ */
@@ -461,24 +438,92 @@ export class DataFrameComponent extends BaseComponent {
461
438
  this._reshapeModalRendered = false;
462
439
  }
463
440
  }
441
+ _escapeHtml(text) {
442
+ const div = document.createElement('div');
443
+ div.textContent = text;
444
+ return div.innerHTML;
445
+ }
446
+ /**
447
+ * Build a clickable preview table from raw row data.
448
+ * Each row stores its actual sheet row index via data-sheet-row attribute.
449
+ * Returns the table HTML string.
450
+ */
451
+ _buildClickablePreviewHTML(rawRows, selectedSheetRow) {
452
+ let html = '<table style="width: 100%; border-collapse: collapse; font-size: 12px;">';
453
+ for (const { sheetRow, values } of rawRows) {
454
+ const isHeader = (sheetRow === selectedSheetRow);
455
+ const isSkipped = (sheetRow < selectedSheetRow);
456
+ let rowStyle = 'border-bottom: 1px solid hsl(var(--border)); cursor: pointer; transition: background 0.1s;';
457
+ if (isHeader) {
458
+ rowStyle += 'background: hsl(142 71% 45% / 0.15); font-weight: 600;';
459
+ }
460
+ else if (isSkipped) {
461
+ rowStyle += 'background: hsl(var(--muted) / 0.4); color: hsl(var(--muted-foreground)); font-style: italic; opacity: 0.7;';
462
+ }
463
+ html += `<tr data-sheet-row="${sheetRow}" style="${rowStyle}" onmouseover="this.style.outline='2px solid hsl(142 71% 45% / 0.5)'" onmouseout="this.style.outline=''">`;
464
+ // Row index cell
465
+ 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;">`;
466
+ if (isHeader) {
467
+ html += `<span style="color: hsl(142 71% 45%);">▶ ${sheetRow}</span>`;
468
+ }
469
+ else {
470
+ html += `${sheetRow}`;
471
+ }
472
+ html += '</td>';
473
+ // Show first 6 columns
474
+ const displayCols = values.slice(0, 6);
475
+ displayCols.forEach(val => {
476
+ const displayVal = val != null ? String(val).substring(0, 20) : '';
477
+ const cellStyle = isHeader
478
+ ? 'padding: 8px 12px; font-weight: 600; color: hsl(var(--foreground));'
479
+ : 'padding: 8px 12px;';
480
+ html += `<td style="${cellStyle}">${this._escapeHtml(displayVal)}</td>`;
481
+ });
482
+ if (values.length > 6) {
483
+ html += `<td style="padding: 8px 12px; color: hsl(var(--muted-foreground));">…</td>`;
484
+ }
485
+ // Status badge
486
+ html += `<td style="padding: 8px 12px; text-align: right; white-space: nowrap; user-select: none;">`;
487
+ if (isHeader) {
488
+ html += '<span style="background: hsl(142 71% 45%); color: white; padding: 3px 8px; border-radius: 4px; font-size: 10px; font-weight: 600;">HEADER</span>';
489
+ }
490
+ else if (isSkipped) {
491
+ html += '<span style="color: hsl(var(--muted-foreground)); font-size: 10px;">skipped</span>';
492
+ }
493
+ else {
494
+ html += '<span style="color: hsl(var(--muted-foreground)); font-size: 10px;">data</span>';
495
+ }
496
+ html += '</td></tr>';
497
+ }
498
+ html += '</table>';
499
+ return html;
500
+ }
464
501
  async _showExcelReshapeModal() {
465
502
  if (!this._rawFileData?.file)
466
503
  return;
467
504
  this._cleanupReshapeModal();
468
- let suggestedRow = 0;
469
- try {
470
- const rawSheets = await this._driver.streamFileMultiSheet(this._rawFileData.file, {
471
- headerRow: 0,
472
- maxSheetSize: 20
473
- });
474
- const rawSheet = Object.values(rawSheets)[0];
475
- if (rawSheet) {
476
- suggestedRow = this._detectLikelyHeaderRow(rawSheet);
505
+ // Use the SAME cell-reading method as the parser
506
+ const rawRows = await this._driver.readRawExcelRows(this._rawFileData.file, 15);
507
+ if (rawRows.length === 0)
508
+ return;
509
+ // Log what we got so we can verify alignment
510
+ console.log('[DataFrame Preview] Raw rows from readRawExcelRows:');
511
+ rawRows.forEach(r => console.log(` sheetRow ${r.sheetRow}:`, r.values.slice(0, 5)));
512
+ // Auto-detect best header row
513
+ let selectedSheetRow = rawRows[0].sheetRow;
514
+ for (const { sheetRow, values } of rawRows) {
515
+ const nonEmpty = values.filter(v => v !== null && v !== undefined && String(v).trim() !== '');
516
+ if (nonEmpty.length < values.length * 0.5)
517
+ continue;
518
+ const nonNumeric = nonEmpty.filter(v => {
519
+ const str = String(v).trim();
520
+ return isNaN(Number(str)) && str !== '';
521
+ }).length;
522
+ if (nonNumeric >= nonEmpty.length * 0.7) {
523
+ selectedSheetRow = sheetRow;
524
+ break;
477
525
  }
478
526
  }
479
- catch {
480
- suggestedRow = 0;
481
- }
482
527
  this._reshapeModal = new Modal(`${this._id}-reshape-modal`, {
483
528
  title: 'Excel Import Settings',
484
529
  size: 'large',
@@ -487,14 +532,11 @@ export class DataFrameComponent extends BaseComponent {
487
532
  });
488
533
  const modalContentHTML = `
489
534
  <div style="margin-bottom: 1rem;">
490
- <label style="display: block; font-weight: 600; margin-bottom: 0.5rem; color: hsl(var(--foreground));">
491
- Header Row (0-based index)
492
- </label>
493
- <input type="number" id="${this._id}-header-row" class="jux-input-element" value="${suggestedRow}" min="0" max="50" style="width: 100%;" />
494
- <div id="${this._id}-reshape-hint" class="jux-reshape-hint" style="margin-top: 0.5rem; padding: 0.75rem; background: hsl(var(--muted) / 0.5); border-radius: var(--radius); font-size: 0.875rem;"></div>
535
+ <div id="${this._id}-reshape-hint" style="padding: 0.75rem; background: hsl(var(--muted) / 0.5); border-radius: var(--radius); font-size: 0.875rem;"></div>
536
+ <input type="hidden" id="${this._id}-header-row" value="${selectedSheetRow}" />
495
537
  </div>
496
- <div class="jux-reshape-preview-container">
497
- <div style="font-weight: 600; margin-bottom: 0.5rem; color: hsl(var(--foreground));">Preview</div>
538
+ <div>
539
+ <div style="font-weight: 600; margin-bottom: 0.5rem; color: hsl(var(--foreground));">Click a row to select it as the header:</div>
498
540
  <div id="${this._id}-preview" style="font-family: ui-monospace, monospace; font-size: 12px; background: hsl(var(--muted) / 0.3); border: 1px solid hsl(var(--border)); border-radius: var(--radius); padding: 0; overflow: hidden; max-height: 400px; overflow-y: auto;"></div>
499
541
  </div>
500
542
  `;
@@ -512,6 +554,7 @@ export class DataFrameComponent extends BaseComponent {
512
554
  click: async () => {
513
555
  const input = document.getElementById(`${this._id}-header-row`);
514
556
  const headerRow = parseInt(input.value) || 0;
557
+ console.log(`[DataFrame] Apply clicked: headerRow=${headerRow}`);
515
558
  this.state.loading = true;
516
559
  this._updateStatus('Re-parsing with new settings...', 'loading');
517
560
  try {
@@ -545,129 +588,73 @@ export class DataFrameComponent extends BaseComponent {
545
588
  this._reshapeModal.render(document.body);
546
589
  this._reshapeModalRendered = true;
547
590
  await new Promise(resolve => requestAnimationFrame(resolve));
548
- const headerRowInput = document.getElementById(`${this._id}-header-row`);
549
591
  const previewDiv = document.getElementById(`${this._id}-preview`);
550
592
  const hintDiv = document.getElementById(`${this._id}-reshape-hint`);
551
- const updateHint = (headerRow) => {
593
+ const hiddenInput = document.getElementById(`${this._id}-header-row`);
594
+ const updateHint = (row) => {
552
595
  if (!hintDiv)
553
596
  return;
554
- if (headerRow > 0) {
555
- hintDiv.innerHTML = `Row <strong>${headerRow}</strong> will be used as column headers. Rows <strong>0–${headerRow - 1}</strong> will be skipped.`;
597
+ const vals = rawRows.find(r => r.sheetRow === row)?.values ?? [];
598
+ const headerNames = vals.filter((v) => v != null && String(v).trim() !== '').map((v) => String(v).trim());
599
+ const preview = headerNames.slice(0, 4).join(', ') + (headerNames.length > 4 ? '…' : '');
600
+ if (row > rawRows[0].sheetRow) {
601
+ hintDiv.innerHTML = `Sheet row <strong>${row}</strong> selected as header. Columns: <code>${this._escapeHtml(preview)}</code>. Rows before it will be skipped.`;
556
602
  }
557
603
  else {
558
- hintDiv.innerHTML = `Row <strong>0</strong> (first row) will be used as column headers.`;
604
+ hintDiv.innerHTML = `Sheet row <strong>${row}</strong> (first row) selected as header. Columns: <code>${this._escapeHtml(preview)}</code>`;
559
605
  }
560
606
  };
561
- const updatePreview = async () => {
562
- const headerRow = parseInt(headerRowInput?.value) || 0;
563
- updateHint(headerRow);
564
- try {
565
- // Read cells directly from XLSX to match parser behavior exactly
566
- const XLSX = await import('xlsx');
567
- const buffer = await this._rawFileData.file.arrayBuffer();
568
- const workbook = XLSX.read(buffer, {
569
- type: 'array',
570
- sheetRows: Math.max(headerRow + 12, 15),
571
- dense: false
607
+ const renderPreview = (selected) => {
608
+ if (!previewDiv)
609
+ return;
610
+ previewDiv.innerHTML = this._buildClickablePreviewHTML(rawRows, selected);
611
+ previewDiv.querySelectorAll('tr[data-sheet-row]').forEach(tr => {
612
+ tr.addEventListener('click', () => {
613
+ const rowIdx = parseInt(tr.dataset.sheetRow);
614
+ hiddenInput.value = String(rowIdx);
615
+ console.log(`[DataFrame Preview] Clicked sheetRow=${rowIdx}`);
616
+ updateHint(rowIdx);
617
+ renderPreview(rowIdx);
572
618
  });
573
- const sheetName = workbook.SheetNames[0];
574
- const worksheet = workbook.Sheets[sheetName];
575
- const ref = worksheet['!ref'];
576
- if (!ref) {
577
- if (previewDiv)
578
- previewDiv.textContent = 'No data found';
579
- return;
580
- }
581
- const range = XLSX.utils.decode_range(ref);
582
- const endRow = range.e.r;
583
- const startCol = range.s.c;
584
- const endCol = range.e.c;
585
- const readCellValue = (r, c) => {
586
- const addr = XLSX.utils.encode_cell({ r, c });
587
- const cell = worksheet[addr];
588
- if (!cell)
589
- return null;
590
- if (cell.w !== undefined)
591
- return cell.w;
592
- if (cell.v !== undefined)
593
- return cell.v;
594
- return null;
595
- };
596
- const readRow = (r) => {
597
- const vals = [];
598
- for (let c = startCol; c <= endCol; c++) {
599
- vals.push(readCellValue(r, c));
600
- }
601
- return vals;
602
- };
603
- let html = '<table style="width: 100%; border-collapse: collapse; font-size: 12px;">';
604
- const totalRowsToShow = Math.min(headerRow + 8, endRow + 1);
605
- for (let fileRow = 0; fileRow < totalRowsToShow; fileRow++) {
606
- const isHeader = (fileRow === headerRow);
607
- const isSkipped = (fileRow < headerRow);
608
- let rowStyle = 'border-bottom: 1px solid hsl(var(--border));';
609
- if (isHeader) {
610
- rowStyle += 'background: hsl(142 71% 45% / 0.15); font-weight: 600;';
611
- }
612
- else if (isSkipped) {
613
- rowStyle += 'background: hsl(var(--muted) / 0.4); color: hsl(var(--muted-foreground)); font-style: italic; opacity: 0.7;';
614
- }
615
- html += `<tr style="${rowStyle}">`;
616
- 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;">`;
617
- if (isHeader) {
618
- html += `<span style="color: hsl(142 71% 45%);">▶ ${fileRow}</span>`;
619
- }
620
- else {
621
- html += `${fileRow}`;
622
- }
623
- html += '</td>';
624
- const values = readRow(fileRow);
625
- const displayCols = values.slice(0, 6);
626
- displayCols.forEach(val => {
627
- const displayVal = val != null ? String(val).substring(0, 20) : '';
628
- const cellStyle = isHeader
629
- ? 'padding: 8px 12px; font-weight: 600; color: hsl(var(--foreground));'
630
- : 'padding: 8px 12px;';
631
- html += `<td style="${cellStyle}">${this._escapeHtml(displayVal)}</td>`;
632
- });
633
- if (values.length > 6) {
634
- html += `<td style="padding: 8px 12px; color: hsl(var(--muted-foreground));">...</td>`;
635
- }
636
- html += `<td style="padding: 8px 12px; text-align: right; white-space: nowrap;">`;
637
- if (isHeader) {
638
- html += '<span style="background: hsl(var(--primary)); color: white; padding: 2px 6px; border-radius: 4px;">HEADER</span>';
639
- }
640
- else if (isSkipped) {
641
- html += '<span style="color: hsl(var(--muted-foreground));">skipped</span>';
642
- }
643
- else {
644
- html += '<span style="color: hsl(var(--success));">data</span>';
645
- }
646
- html += '</td></tr>';
647
- }
648
- html += '</table>';
649
- if (previewDiv)
650
- previewDiv.innerHTML = html;
651
- }
652
- catch (err) {
653
- if (previewDiv)
654
- previewDiv.textContent = `Error: ${err.message}`;
655
- }
619
+ });
656
620
  };
657
- if (headerRowInput)
658
- headerRowInput.addEventListener('input', updatePreview);
659
- updatePreview();
621
+ updateHint(selectedSheetRow);
622
+ renderPreview(selectedSheetRow);
660
623
  this._reshapeModal.open();
661
624
  }
662
- _escapeHtml(text) {
663
- const div = document.createElement('div');
664
- div.textContent = text;
665
- return div.innerHTML;
666
- }
667
625
  _showCSVReshapeModal() {
668
- if (!this._rawFileData)
626
+ if (!this._rawFileData?.text)
669
627
  return;
670
628
  this._cleanupReshapeModal();
629
+ const text = this._rawFileData.text;
630
+ const detected = this._driver._detectDelimiter(text);
631
+ // Parse raw lines
632
+ const lines = text.replace(/\r\n/g, '\n').replace(/\r/g, '\n').split('\n');
633
+ const rawRows = [];
634
+ const maxPreviewRows = Math.min(lines.length, 15);
635
+ for (let i = 0; i < maxPreviewRows; i++) {
636
+ if (!lines[i]) {
637
+ rawRows.push({ sheetRow: i, values: [''] });
638
+ continue;
639
+ }
640
+ const values = this._driver._parseLine(lines[i], detected);
641
+ rawRows.push({ sheetRow: i, values });
642
+ }
643
+ // Auto-detect header row
644
+ let selectedRow = 0;
645
+ for (const { sheetRow, values } of rawRows) {
646
+ const nonEmpty = values.filter((v) => v.trim() !== '');
647
+ if (nonEmpty.length < values.length * 0.5)
648
+ continue;
649
+ const nonNumeric = nonEmpty.filter((v) => {
650
+ const trimmed = v.trim();
651
+ return isNaN(Number(trimmed)) && trimmed !== '';
652
+ }).length;
653
+ if (nonNumeric >= nonEmpty.length * 0.7) {
654
+ selectedRow = sheetRow;
655
+ break;
656
+ }
657
+ }
671
658
  this._reshapeModal = new Modal(`${this._id}-reshape-modal`, {
672
659
  title: 'CSV Import Settings',
673
660
  size: 'large',
@@ -685,12 +672,11 @@ export class DataFrameComponent extends BaseComponent {
685
672
  </select>
686
673
  </div>
687
674
  <div style="margin-bottom: 1rem;">
688
- <label style="display: block; font-weight: 600; margin-bottom: 0.5rem;">Header Row (0-based index)</label>
689
- <input type="number" id="${this._id}-header-row" class="jux-input-element" value="0" min="0" max="50" style="width: 100%;" />
675
+ <div id="${this._id}-reshape-hint" style="padding: 0.75rem; background: hsl(var(--muted) / 0.5); border-radius: var(--radius); font-size: 0.875rem;"></div>
676
+ <input type="hidden" id="${this._id}-header-row" value="${selectedRow}" />
690
677
  </div>
691
- <div id="${this._id}-reshape-hint" class="jux-reshape-hint" style="margin-top: 0.5rem; margin-bottom: 1rem; padding: 0.75rem; background: hsl(var(--muted) / 0.5); border-radius: var(--radius); font-size: 0.875rem;"></div>
692
- <div class="jux-reshape-preview-container">
693
- <div style="font-weight: 600; margin-bottom: 0.5rem; color: hsl(var(--foreground));">Preview</div>
678
+ <div>
679
+ <div style="font-weight: 600; margin-bottom: 0.5rem; color: hsl(var(--foreground));">Click a row to select it as the header:</div>
694
680
  <div id="${this._id}-preview" style="font-family: monospace; font-size: 12px; background: hsl(var(--muted) / 0.3); border: 1px solid hsl(var(--border)); border-radius: var(--radius); padding: 0; overflow: hidden; max-height: 400px; overflow-y: auto;"></div>
695
681
  </div>
696
682
  `;
@@ -706,12 +692,10 @@ export class DataFrameComponent extends BaseComponent {
706
692
  label: 'Apply & Re-import',
707
693
  variant: 'primary',
708
694
  click: async () => {
709
- if (!this._rawFileData?.text)
710
- return;
711
695
  const delimiterSelect = document.getElementById(`${this._id}-delimiter`);
712
- const headerRowInput = document.getElementById(`${this._id}-header-row`);
696
+ const hiddenInput = document.getElementById(`${this._id}-header-row`);
713
697
  const delim = delimiterSelect.value;
714
- const headerRow = parseInt(headerRowInput.value) || 0;
698
+ const headerRow = parseInt(hiddenInput.value) || 0;
715
699
  this.state.loading = true;
716
700
  this._updateStatus('Re-parsing with new settings...', 'loading');
717
701
  try {
@@ -735,102 +719,60 @@ export class DataFrameComponent extends BaseComponent {
735
719
  this._reshapeModalRendered = true;
736
720
  requestAnimationFrame(() => {
737
721
  const delimiterSelect = document.getElementById(`${this._id}-delimiter`);
738
- const headerRowInput = document.getElementById(`${this._id}-header-row`);
739
722
  const previewDiv = document.getElementById(`${this._id}-preview`);
740
723
  const hintDiv = document.getElementById(`${this._id}-reshape-hint`);
741
- if (this._rawFileData?.text) {
742
- const detected = this._driver._detectDelimiter(this._rawFileData.text);
743
- if (delimiterSelect)
744
- delimiterSelect.value = detected;
745
- const detectedHeaderRow = this._driver._detectHeaderRow(this._rawFileData.text, detected);
746
- if (headerRowInput)
747
- headerRowInput.value = String(detectedHeaderRow);
748
- }
749
- const updateHint = () => {
724
+ const hiddenInput = document.getElementById(`${this._id}-header-row`);
725
+ if (delimiterSelect)
726
+ delimiterSelect.value = detected;
727
+ const updateHint = (row) => {
750
728
  if (!hintDiv)
751
729
  return;
752
- const headerRow = parseInt(headerRowInput?.value) || 0;
753
- if (headerRow > 0) {
754
- hintDiv.innerHTML = `Row <strong>${headerRow}</strong> will be used as column headers. Rows <strong>0–${headerRow - 1}</strong> will be skipped.`;
730
+ const vals = rawRows.find(r => r.sheetRow === row)?.values ?? [];
731
+ const headerNames = vals.filter((v) => v != null && String(v).trim() !== '').map((v) => String(v).trim());
732
+ const preview = headerNames.slice(0, 4).join(', ') + (headerNames.length > 4 ? '…' : '');
733
+ if (row > rawRows[0].sheetRow) {
734
+ hintDiv.innerHTML = `Sheet row <strong>${row}</strong> selected as header. Columns: <code>${this._escapeHtml(preview)}</code>. Rows before it will be skipped.`;
755
735
  }
756
736
  else {
757
- hintDiv.innerHTML = `Row <strong>0</strong> (first row) will be used as column headers.`;
737
+ hintDiv.innerHTML = `Sheet row <strong>${row}</strong> (first row) selected as header. Columns: <code>${this._escapeHtml(preview)}</code>`;
758
738
  }
759
739
  };
760
- const updatePreview = () => {
761
- if (!this._rawFileData?.text)
762
- return;
740
+ const reparse = () => {
763
741
  const delim = delimiterSelect?.value || ',';
764
- const headerRow = parseInt(headerRowInput?.value) || 0;
765
- updateHint();
766
- try {
767
- const rawDf = this._driver.parseCSV(this._rawFileData.text, {
768
- delimiter: delim,
769
- headerRow: 0,
770
- hasHeader: true,
771
- maxRows: headerRow + 10
772
- });
773
- const rawCols = rawDf.columns;
774
- const rawRows = rawDf.toRows();
775
- let html = '<table style="width: 100%; border-collapse: collapse; font-size: 11px;">';
776
- const totalRows = Math.min(headerRow + 8, rawRows.length + 1);
777
- for (let i = 0; i < totalRows; i++) {
778
- const isHeader = (i === headerRow);
779
- const isSkipped = (i < headerRow);
780
- let rowStyle = 'border-bottom: 1px solid hsl(var(--border));';
781
- if (isHeader) {
782
- rowStyle += 'background: hsl(var(--primary) / 0.15); font-weight: bold;';
783
- }
784
- else if (isSkipped) {
785
- rowStyle += 'background: hsl(var(--muted) / 0.3); color: hsl(var(--muted-foreground)); font-style: italic;';
786
- }
787
- html += `<tr style="${rowStyle}">`;
788
- html += `<td style="padding: 6px 8px; width: 50px; color: hsl(var(--muted-foreground)); font-weight: 500;">`;
789
- html += isHeader ? `<strong>→ ${i}</strong>` : `${i}`;
790
- html += '</td>';
791
- let values;
792
- if (i === 0) {
793
- values = rawCols;
794
- }
795
- else if (i - 1 < rawRows.length) {
796
- values = Object.values(rawRows[i - 1]);
797
- }
798
- else {
799
- values = [];
800
- }
801
- values.slice(0, 6).forEach(val => {
802
- const displayVal = val != null ? String(val).substring(0, 25) : '';
803
- html += `<td style="padding: 6px 8px;">${this._escapeHtml(displayVal)}</td>`;
804
- });
805
- if (values.length > 6) {
806
- html += `<td style="padding: 6px 8px; color: hsl(var(--muted-foreground));">...</td>`;
807
- }
808
- html += `<td style="padding: 6px 8px; text-align: right; font-size: 10px;">`;
809
- if (isHeader) {
810
- html += '<span style="background: hsl(var(--primary)); color: white; padding: 2px 6px; border-radius: 4px;">HEADER</span>';
811
- }
812
- else if (isSkipped) {
813
- html += '<span style="color: hsl(var(--muted-foreground));">skipped</span>';
814
- }
815
- else {
816
- html += '<span style="color: hsl(var(--success));">data</span>';
817
- }
818
- html += '</td></tr>';
742
+ rawRows.length = 0;
743
+ for (let i = 0; i < maxPreviewRows; i++) {
744
+ if (!lines[i]) {
745
+ rawRows.push({ sheetRow: i, values: [''] });
746
+ continue;
819
747
  }
820
- html += '</table>';
821
- if (previewDiv)
822
- previewDiv.innerHTML = html;
823
- }
824
- catch (err) {
825
- if (previewDiv)
826
- previewDiv.textContent = `Error: ${err.message}`;
748
+ const values = this._driver._parseLine(lines[i], delim);
749
+ rawRows.push({ sheetRow: i, values });
827
750
  }
828
751
  };
829
- if (delimiterSelect)
830
- delimiterSelect.addEventListener('change', updatePreview);
831
- if (headerRowInput)
832
- headerRowInput.addEventListener('input', updatePreview);
833
- updatePreview();
752
+ const renderPreview = (selected) => {
753
+ if (!previewDiv)
754
+ return;
755
+ previewDiv.innerHTML = this._buildClickablePreviewHTML(rawRows, selected);
756
+ previewDiv.querySelectorAll('tr[data-sheet-row]').forEach(tr => {
757
+ tr.addEventListener('click', () => {
758
+ const rowIdx = parseInt(tr.dataset.sheetRow);
759
+ hiddenInput.value = String(rowIdx);
760
+ console.log(`[DataFrame Preview] Clicked sheetRow=${rowIdx}`);
761
+ updateHint(rowIdx);
762
+ renderPreview(rowIdx);
763
+ });
764
+ });
765
+ };
766
+ if (delimiterSelect) {
767
+ delimiterSelect.addEventListener('change', () => {
768
+ reparse();
769
+ const current = parseInt(hiddenInput.value) || 0;
770
+ updateHint(current);
771
+ renderPreview(current);
772
+ });
773
+ }
774
+ updateHint(selectedRow);
775
+ renderPreview(selectedRow);
834
776
  this._reshapeModal.open();
835
777
  });
836
778
  }