juxscript 1.1.204 → 1.1.205

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.
@@ -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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"}
@@ -217,6 +217,11 @@ export class DataFrameComponent extends BaseComponent {
217
217
  }
218
218
  });
219
219
  const sheetNames = Object.keys(sheets);
220
+ if (sheetNames.length === 0) {
221
+ this._updateStatus('No data found in file', 'error');
222
+ this.state.loading = false;
223
+ return;
224
+ }
220
225
  await this._driver.store(file.name, sheets[sheetNames[0]], { source: file.name });
221
226
  if (sheetNames.length > 1) {
222
227
  this._renderMultiSheet(sheets, file.name);
@@ -251,7 +256,6 @@ export class DataFrameComponent extends BaseComponent {
251
256
  const wrapper = document.getElementById(this._id);
252
257
  if (!wrapper)
253
258
  return;
254
- // Clean up existing content
255
259
  const existingTable = wrapper.querySelector('.jux-table-wrapper');
256
260
  if (existingTable)
257
261
  existingTable.remove();
@@ -262,11 +266,10 @@ export class DataFrameComponent extends BaseComponent {
262
266
  this._sheets.set(name, df);
263
267
  });
264
268
  const sheetNames = Object.keys(sheets);
265
- // Build tabs using the Tabs component
266
269
  const tabDefs = sheetNames.map(name => ({
267
270
  id: name,
268
271
  label: name,
269
- content: '' // Content will be added after render
272
+ content: ''
270
273
  }));
271
274
  this._tabs = new Tabs(`${this._id}-tabs`, {
272
275
  tabs: tabDefs,
@@ -275,15 +278,12 @@ export class DataFrameComponent extends BaseComponent {
275
278
  this._tabs.bind('tabChange', (tabId) => {
276
279
  this._df = this._sheets.get(tabId) || null;
277
280
  });
278
- // Create container for tabs
279
281
  const tabsContainer = document.createElement('div');
280
282
  tabsContainer.className = 'jux-dataframe-tabs';
281
283
  wrapper.appendChild(tabsContainer);
282
284
  this._tabs.render(tabsContainer);
283
- // Now render tables into each tab panel
284
- sheetNames.forEach((sheetName, idx) => {
285
+ sheetNames.forEach((sheetName) => {
285
286
  const df = sheets[sheetName];
286
- const panelId = `${this._id}-tabs-${sheetName}-panel`;
287
287
  const table = new Table(`${this._id}-table-${sheetName}`, {
288
288
  striped: this._tableOptions.striped,
289
289
  hoverable: this._tableOptions.hoverable,
@@ -294,48 +294,13 @@ export class DataFrameComponent extends BaseComponent {
294
294
  });
295
295
  const columnDefs = df.columns.map(col => ({ key: col, label: col }));
296
296
  table.columns(columnDefs).rows(df.toRows());
297
- // Add settings button to tab panel
298
297
  const settingsBtn = new Button(`${this._id}-settings-${sheetName}`, {
299
298
  label: '⚙️ Import Settings',
300
299
  variant: 'ghost',
301
300
  size: 'small'
302
301
  });
303
302
  settingsBtn.bind('click', () => this._showReshapeModal());
304
- // Use addTabContent to add components
305
303
  this._tabs.addTabContent(sheetName, [settingsBtn, table]);
306
- if (this._tableOptions.filterable) {
307
- // Add filter input above table
308
- const panel = document.getElementById(panelId);
309
- if (panel) {
310
- const filterContainer = document.createElement('div');
311
- filterContainer.className = 'jux-dataframe-filter';
312
- const input = document.createElement('input');
313
- input.type = 'text';
314
- input.placeholder = `Filter ${sheetName}...`;
315
- input.className = 'jux-input-element jux-dataframe-filter-input';
316
- const iconEl = renderIcon('search');
317
- iconEl.style.width = '16px';
318
- iconEl.style.height = '16px';
319
- const iconWrap = document.createElement('span');
320
- iconWrap.className = 'jux-dataframe-filter-icon';
321
- iconWrap.appendChild(iconEl);
322
- filterContainer.appendChild(iconWrap);
323
- filterContainer.appendChild(input);
324
- input.addEventListener('input', () => {
325
- const text = input.value.toLowerCase();
326
- if (!text) {
327
- table.rows(df.toRows());
328
- return;
329
- }
330
- const filtered = df.filter((row) => Object.values(row).some(v => v !== null && v !== undefined && String(v).toLowerCase().includes(text)));
331
- table.rows(filtered.toRows());
332
- });
333
- const tableWrapper = panel.querySelector('.jux-table-wrapper');
334
- if (tableWrapper) {
335
- panel.insertBefore(filterContainer, tableWrapper);
336
- }
337
- }
338
- }
339
304
  });
340
305
  const totalRows = Object.values(sheets).reduce((sum, df) => sum + df.height, 0);
341
306
  this._updateStatus(`${sourceName} — ${sheetNames.length} sheets, ${totalRows} total rows`, 'success');
@@ -378,7 +343,6 @@ export class DataFrameComponent extends BaseComponent {
378
343
  const emptyColumns = cols.filter(c => {
379
344
  if (!c.startsWith('__EMPTY'))
380
345
  return false;
381
- // Check if ALL values in this column are null/empty
382
346
  return rows.every(row => {
383
347
  const val = row[c];
384
348
  return val === null || val === undefined || String(val).trim() === '';
@@ -411,7 +375,6 @@ export class DataFrameComponent extends BaseComponent {
411
375
  }
412
376
  else {
413
377
  this._updateStatus(`${sourceName} — ${this._df.height} rows × ${this._df.width} cols`, 'success');
414
- // Always show settings button if we have raw file data
415
378
  if (this._rawFileData) {
416
379
  requestAnimationFrame(() => {
417
380
  const statusEl = document.getElementById(`${this._id}-status`);
@@ -455,6 +418,10 @@ export class DataFrameComponent extends BaseComponent {
455
418
  }
456
419
  _detectLikelyHeaderRow(df) {
457
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;
458
425
  for (let i = 0; i < Math.min(rows.length, 10); i++) {
459
426
  const row = rows[i];
460
427
  const values = Object.values(row);
@@ -465,10 +432,8 @@ export class DataFrameComponent extends BaseComponent {
465
432
  const str = String(v).trim();
466
433
  return isNaN(Number(str)) && str !== '';
467
434
  }).length;
468
- if (nonNumericCount >= nonEmpty.length * 0.7 && i > 0) {
469
- // i is the index in toRows(). Row 0 of the file was already consumed
470
- // as the header during the initial parse, so the actual 0-based file
471
- // row index is i + 1.
435
+ if (nonNumericCount >= nonEmpty.length * 0.7) {
436
+ // toRows index i = file row (i + 1) since row 0 was used as headers
472
437
  return i + 1;
473
438
  }
474
439
  }
@@ -500,7 +465,6 @@ export class DataFrameComponent extends BaseComponent {
500
465
  if (!this._rawFileData?.file)
501
466
  return;
502
467
  this._cleanupReshapeModal();
503
- // Always detect from a fresh raw parse, not from current _df
504
468
  let suggestedRow = 0;
505
469
  try {
506
470
  const rawSheets = await this._driver.streamFileMultiSheet(this._rawFileData.file, {
@@ -526,22 +490,11 @@ export class DataFrameComponent extends BaseComponent {
526
490
  <label style="display: block; font-weight: 600; margin-bottom: 0.5rem; color: hsl(var(--foreground));">
527
491
  Header Row (0-based index)
528
492
  </label>
529
- <input
530
- type="number"
531
- id="${this._id}-header-row"
532
- class="jux-input-element"
533
- value="${suggestedRow}"
534
- min="0"
535
- max="50"
536
- style="width: 100%;"
537
- />
538
- <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;">
539
- </div>
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>
540
495
  </div>
541
496
  <div class="jux-reshape-preview-container">
542
- <div style="font-weight: 600; margin-bottom: 0.5rem; color: hsl(var(--foreground));">
543
- Preview
544
- </div>
497
+ <div style="font-weight: 600; margin-bottom: 0.5rem; color: hsl(var(--foreground));">Preview</div>
545
498
  <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>
546
499
  </div>
547
500
  `;
@@ -599,8 +552,7 @@ export class DataFrameComponent extends BaseComponent {
599
552
  if (!hintDiv)
600
553
  return;
601
554
  if (headerRow > 0) {
602
- hintDiv.innerHTML = `Row <strong>${headerRow}</strong> will be used as column headers. ` +
603
- `Rows <strong>0–${headerRow - 1}</strong> will be skipped.`;
555
+ hintDiv.innerHTML = `Row <strong>${headerRow}</strong> will be used as column headers. Rows <strong>0–${headerRow - 1}</strong> will be skipped.`;
604
556
  }
605
557
  else {
606
558
  hintDiv.innerHTML = `Row <strong>0</strong> (first row) will be used as column headers.`;
@@ -610,26 +562,46 @@ export class DataFrameComponent extends BaseComponent {
610
562
  const headerRow = parseInt(headerRowInput?.value) || 0;
611
563
  updateHint(headerRow);
612
564
  try {
613
- // ALWAYS parse with headerRow=0 to get raw file structure
614
- const rawSheets = await this._driver.streamFileMultiSheet(this._rawFileData.file, {
615
- headerRow: 0,
616
- maxSheetSize: Math.max(headerRow + 12, 15)
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
617
572
  });
618
- const rawSheet = Object.values(rawSheets)[0];
619
- if (!rawSheet) {
573
+ const sheetName = workbook.SheetNames[0];
574
+ const worksheet = workbook.Sheets[sheetName];
575
+ const ref = worksheet['!ref'];
576
+ if (!ref) {
620
577
  if (previewDiv)
621
578
  previewDiv.textContent = 'No data found';
622
579
  return;
623
580
  }
624
- // rawSheet.columns = row 0 values (when parsed with headerRow=0)
625
- // rawSheet.toRows() = rows 1+ (data rows when parsed with headerRow=0)
626
- const rawCols = rawSheet.columns;
627
- const rawRows = rawSheet.toRows();
628
- // Build HTML table showing raw file structure
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
+ };
629
603
  let html = '<table style="width: 100%; border-collapse: collapse; font-size: 12px;">';
630
- // We need to show rows 0 through headerRow+7 (or so)
631
- // Row 0 = rawCols, Row 1+ = rawRows[i-1]
632
- const totalRowsToShow = Math.min(headerRow + 8, rawRows.length + 1);
604
+ const totalRowsToShow = Math.min(headerRow + 8, endRow + 1);
633
605
  for (let fileRow = 0; fileRow < totalRowsToShow; fileRow++) {
634
606
  const isHeader = (fileRow === headerRow);
635
607
  const isSkipped = (fileRow < headerRow);
@@ -641,7 +613,6 @@ export class DataFrameComponent extends BaseComponent {
641
613
  rowStyle += 'background: hsl(var(--muted) / 0.4); color: hsl(var(--muted-foreground)); font-style: italic; opacity: 0.7;';
642
614
  }
643
615
  html += `<tr style="${rowStyle}">`;
644
- // Row index cell
645
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;">`;
646
617
  if (isHeader) {
647
618
  html += `<span style="color: hsl(142 71% 45%);">▶ ${fileRow}</span>`;
@@ -650,15 +621,7 @@ export class DataFrameComponent extends BaseComponent {
650
621
  html += `${fileRow}`;
651
622
  }
652
623
  html += '</td>';
653
- // Get values for this file row
654
- let values;
655
- if (fileRow === 0) {
656
- values = rawCols;
657
- }
658
- else {
659
- values = rawRows[fileRow - 1] ? Object.values(rawRows[fileRow - 1]) : [];
660
- }
661
- // Show first 6 columns
624
+ const values = readRow(fileRow);
662
625
  const displayCols = values.slice(0, 6);
663
626
  displayCols.forEach(val => {
664
627
  const displayVal = val != null ? String(val).substring(0, 20) : '';
@@ -668,26 +631,23 @@ export class DataFrameComponent extends BaseComponent {
668
631
  html += `<td style="${cellStyle}">${this._escapeHtml(displayVal)}</td>`;
669
632
  });
670
633
  if (values.length > 6) {
671
- html += `<td style="padding: 8px 12px; color: hsl(var(--muted-foreground));">…</td>`;
634
+ html += `<td style="padding: 8px 12px; color: hsl(var(--muted-foreground));">...</td>`;
672
635
  }
673
- // Status badge cell
674
636
  html += `<td style="padding: 8px 12px; text-align: right; white-space: nowrap;">`;
675
637
  if (isHeader) {
676
- html += '<span style="background: hsl(142 71% 45%); color: white; padding: 3px 8px; border-radius: 4px; font-size: 10px; font-weight: 600;">HEADER</span>';
638
+ html += '<span style="background: hsl(var(--primary)); color: white; padding: 2px 6px; border-radius: 4px;">HEADER</span>';
677
639
  }
678
640
  else if (isSkipped) {
679
- html += '<span style="color: hsl(var(--muted-foreground)); font-size: 10px;">skipped</span>';
641
+ html += '<span style="color: hsl(var(--muted-foreground));">skipped</span>';
680
642
  }
681
643
  else {
682
- html += '<span style="color: hsl(var(--muted-foreground)); font-size: 10px;">data</span>';
644
+ html += '<span style="color: hsl(var(--success));">data</span>';
683
645
  }
684
- html += '</td>';
685
- html += '</tr>';
646
+ html += '</td></tr>';
686
647
  }
687
648
  html += '</table>';
688
- if (previewDiv) {
649
+ if (previewDiv)
689
650
  previewDiv.innerHTML = html;
690
- }
691
651
  }
692
652
  catch (err) {
693
653
  if (previewDiv)
@@ -791,8 +751,7 @@ export class DataFrameComponent extends BaseComponent {
791
751
  return;
792
752
  const headerRow = parseInt(headerRowInput?.value) || 0;
793
753
  if (headerRow > 0) {
794
- hintDiv.innerHTML = `Row <strong>${headerRow}</strong> will be used as column headers. ` +
795
- `Rows <strong>0–${headerRow - 1}</strong> will be skipped.`;
754
+ hintDiv.innerHTML = `Row <strong>${headerRow}</strong> will be used as column headers. Rows <strong>0–${headerRow - 1}</strong> will be skipped.`;
796
755
  }
797
756
  else {
798
757
  hintDiv.innerHTML = `Row <strong>0</strong> (first row) will be used as column headers.`;
@@ -805,7 +764,6 @@ export class DataFrameComponent extends BaseComponent {
805
764
  const headerRow = parseInt(headerRowInput?.value) || 0;
806
765
  updateHint();
807
766
  try {
808
- // Parse raw to show all rows
809
767
  const rawDf = this._driver.parseCSV(this._rawFileData.text, {
810
768
  delimiter: delim,
811
769
  headerRow: 0,
@@ -814,7 +772,6 @@ export class DataFrameComponent extends BaseComponent {
814
772
  });
815
773
  const rawCols = rawDf.columns;
816
774
  const rawRows = rawDf.toRows();
817
- // Build HTML table
818
775
  let html = '<table style="width: 100%; border-collapse: collapse; font-size: 11px;">';
819
776
  const totalRows = Math.min(headerRow + 8, rawRows.length + 1);
820
777
  for (let i = 0; i < totalRows; i++) {
@@ -828,11 +785,9 @@ export class DataFrameComponent extends BaseComponent {
828
785
  rowStyle += 'background: hsl(var(--muted) / 0.3); color: hsl(var(--muted-foreground)); font-style: italic;';
829
786
  }
830
787
  html += `<tr style="${rowStyle}">`;
831
- // Row index
832
788
  html += `<td style="padding: 6px 8px; width: 50px; color: hsl(var(--muted-foreground)); font-weight: 500;">`;
833
789
  html += isHeader ? `<strong>→ ${i}</strong>` : `${i}`;
834
790
  html += '</td>';
835
- // Data
836
791
  let values;
837
792
  if (i === 0) {
838
793
  values = rawCols;
@@ -850,7 +805,6 @@ export class DataFrameComponent extends BaseComponent {
850
805
  if (values.length > 6) {
851
806
  html += `<td style="padding: 6px 8px; color: hsl(var(--muted-foreground));">...</td>`;
852
807
  }
853
- // Status
854
808
  html += `<td style="padding: 6px 8px; text-align: right; font-size: 10px;">`;
855
809
  if (isHeader) {
856
810
  html += '<span style="background: hsl(var(--primary)); color: white; padding: 2px 6px; border-radius: 4px;">HEADER</span>';
@@ -861,8 +815,7 @@ export class DataFrameComponent extends BaseComponent {
861
815
  else {
862
816
  html += '<span style="color: hsl(var(--success));">data</span>';
863
817
  }
864
- html += '</td>';
865
- html += '</tr>';
818
+ html += '</td></tr>';
866
819
  }
867
820
  html += '</table>';
868
821
  if (previewDiv)
@@ -275,6 +275,13 @@ export class DataFrameComponent extends BaseComponent<DataFrameState> {
275
275
  });
276
276
 
277
277
  const sheetNames = Object.keys(sheets);
278
+
279
+ if (sheetNames.length === 0) {
280
+ this._updateStatus('No data found in file', 'error');
281
+ this.state.loading = false;
282
+ return;
283
+ }
284
+
278
285
  await this._driver.store(file.name, sheets[sheetNames[0]], { source: file.name });
279
286
 
280
287
  if (sheetNames.length > 1) {
@@ -312,7 +319,6 @@ export class DataFrameComponent extends BaseComponent<DataFrameState> {
312
319
  const wrapper = document.getElementById(this._id);
313
320
  if (!wrapper) return;
314
321
 
315
- // Clean up existing content
316
322
  const existingTable = wrapper.querySelector('.jux-table-wrapper');
317
323
  if (existingTable) existingTable.remove();
318
324
 
@@ -325,11 +331,10 @@ export class DataFrameComponent extends BaseComponent<DataFrameState> {
325
331
 
326
332
  const sheetNames = Object.keys(sheets);
327
333
 
328
- // Build tabs using the Tabs component
329
334
  const tabDefs = sheetNames.map(name => ({
330
335
  id: name,
331
336
  label: name,
332
- content: '' // Content will be added after render
337
+ content: ''
333
338
  }));
334
339
 
335
340
  this._tabs = new Tabs(`${this._id}-tabs`, {
@@ -341,17 +346,14 @@ export class DataFrameComponent extends BaseComponent<DataFrameState> {
341
346
  this._df = this._sheets.get(tabId) || null;
342
347
  });
343
348
 
344
- // Create container for tabs
345
349
  const tabsContainer = document.createElement('div');
346
350
  tabsContainer.className = 'jux-dataframe-tabs';
347
351
  wrapper.appendChild(tabsContainer);
348
352
 
349
353
  this._tabs.render(tabsContainer);
350
354
 
351
- // Now render tables into each tab panel
352
- sheetNames.forEach((sheetName, idx) => {
355
+ sheetNames.forEach((sheetName) => {
353
356
  const df = sheets[sheetName];
354
- const panelId = `${this._id}-tabs-${sheetName}-panel`;
355
357
 
356
358
  const table = new Table(`${this._id}-table-${sheetName}`, {
357
359
  striped: this._tableOptions.striped,
@@ -365,7 +367,6 @@ export class DataFrameComponent extends BaseComponent<DataFrameState> {
365
367
  const columnDefs = df.columns.map(col => ({ key: col, label: col }));
366
368
  table.columns(columnDefs).rows(df.toRows());
367
369
 
368
- // Add settings button to tab panel
369
370
  const settingsBtn = new Button(`${this._id}-settings-${sheetName}`, {
370
371
  label: '⚙️ Import Settings',
371
372
  variant: 'ghost',
@@ -373,49 +374,7 @@ export class DataFrameComponent extends BaseComponent<DataFrameState> {
373
374
  });
374
375
  settingsBtn.bind('click', () => this._showReshapeModal());
375
376
 
376
- // Use addTabContent to add components
377
377
  this._tabs!.addTabContent(sheetName, [settingsBtn, table]);
378
-
379
- if (this._tableOptions.filterable) {
380
- // Add filter input above table
381
- const panel = document.getElementById(panelId);
382
- if (panel) {
383
- const filterContainer = document.createElement('div');
384
- filterContainer.className = 'jux-dataframe-filter';
385
-
386
- const input = document.createElement('input');
387
- input.type = 'text';
388
- input.placeholder = `Filter ${sheetName}...`;
389
- input.className = 'jux-input-element jux-dataframe-filter-input';
390
-
391
- const iconEl = renderIcon('search');
392
- iconEl.style.width = '16px';
393
- iconEl.style.height = '16px';
394
-
395
- const iconWrap = document.createElement('span');
396
- iconWrap.className = 'jux-dataframe-filter-icon';
397
- iconWrap.appendChild(iconEl);
398
-
399
- filterContainer.appendChild(iconWrap);
400
- filterContainer.appendChild(input);
401
-
402
- input.addEventListener('input', () => {
403
- const text = input.value.toLowerCase();
404
- if (!text) { table.rows(df.toRows()); return; }
405
- const filtered = df.filter((row) =>
406
- Object.values(row).some(v =>
407
- v !== null && v !== undefined && String(v).toLowerCase().includes(text)
408
- )
409
- );
410
- table.rows(filtered.toRows());
411
- });
412
-
413
- const tableWrapper = panel.querySelector('.jux-table-wrapper');
414
- if (tableWrapper) {
415
- panel.insertBefore(filterContainer, tableWrapper);
416
- }
417
- }
418
- }
419
378
  });
420
379
 
421
380
  const totalRows = Object.values(sheets).reduce((sum, df) => sum + df.height, 0);
@@ -468,7 +427,6 @@ export class DataFrameComponent extends BaseComponent<DataFrameState> {
468
427
  const rows = df.toRows();
469
428
  const emptyColumns = cols.filter(c => {
470
429
  if (!c.startsWith('__EMPTY')) return false;
471
- // Check if ALL values in this column are null/empty
472
430
  return rows.every(row => {
473
431
  const val = row[c];
474
432
  return val === null || val === undefined || String(val).trim() === '';
@@ -494,7 +452,6 @@ export class DataFrameComponent extends BaseComponent<DataFrameState> {
494
452
  `${sourceName} — ${this._df!.height} rows × ${this._df!.width} cols (Data may need reformatting)`,
495
453
  'warning'
496
454
  );
497
-
498
455
  requestAnimationFrame(() => {
499
456
  const statusEl = document.getElementById(`${this._id}-status`);
500
457
  if (statusEl) {
@@ -511,8 +468,6 @@ export class DataFrameComponent extends BaseComponent<DataFrameState> {
511
468
  `${sourceName} — ${this._df!.height} rows × ${this._df!.width} cols`,
512
469
  'success'
513
470
  );
514
-
515
- // Always show settings button if we have raw file data
516
471
  if (this._rawFileData) {
517
472
  requestAnimationFrame(() => {
518
473
  const statusEl = document.getElementById(`${this._id}-status`);
@@ -566,6 +521,13 @@ export class DataFrameComponent extends BaseComponent<DataFrameState> {
566
521
 
567
522
  private _detectLikelyHeaderRow(df: DataFrame): number {
568
523
  const rows = df.toRows();
524
+ const cols = df.columns;
525
+
526
+ const colsAreGeneric = cols.some(c =>
527
+ c.startsWith('__EMPTY') || c.match(/^_\d+$/) || c.match(/^col_\d+$/)
528
+ );
529
+
530
+ if (!colsAreGeneric) return 0;
569
531
 
570
532
  for (let i = 0; i < Math.min(rows.length, 10); i++) {
571
533
  const row = rows[i];
@@ -579,10 +541,8 @@ export class DataFrameComponent extends BaseComponent<DataFrameState> {
579
541
  return isNaN(Number(str)) && str !== '';
580
542
  }).length;
581
543
 
582
- if (nonNumericCount >= nonEmpty.length * 0.7 && i > 0) {
583
- // i is the index in toRows(). Row 0 of the file was already consumed
584
- // as the header during the initial parse, so the actual 0-based file
585
- // row index is i + 1.
544
+ if (nonNumericCount >= nonEmpty.length * 0.7) {
545
+ // toRows index i = file row (i + 1) since row 0 was used as headers
586
546
  return i + 1;
587
547
  }
588
548
  }
@@ -618,7 +578,6 @@ export class DataFrameComponent extends BaseComponent<DataFrameState> {
618
578
 
619
579
  this._cleanupReshapeModal();
620
580
 
621
- // Always detect from a fresh raw parse, not from current _df
622
581
  let suggestedRow = 0;
623
582
  try {
624
583
  const rawSheets = await this._driver.streamFileMultiSheet(this._rawFileData.file, {
@@ -645,22 +604,11 @@ export class DataFrameComponent extends BaseComponent<DataFrameState> {
645
604
  <label style="display: block; font-weight: 600; margin-bottom: 0.5rem; color: hsl(var(--foreground));">
646
605
  Header Row (0-based index)
647
606
  </label>
648
- <input
649
- type="number"
650
- id="${this._id}-header-row"
651
- class="jux-input-element"
652
- value="${suggestedRow}"
653
- min="0"
654
- max="50"
655
- style="width: 100%;"
656
- />
657
- <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;">
658
- </div>
607
+ <input type="number" id="${this._id}-header-row" class="jux-input-element" value="${suggestedRow}" min="0" max="50" style="width: 100%;" />
608
+ <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>
659
609
  </div>
660
610
  <div class="jux-reshape-preview-container">
661
- <div style="font-weight: 600; margin-bottom: 0.5rem; color: hsl(var(--foreground));">
662
- Preview
663
- </div>
611
+ <div style="font-weight: 600; margin-bottom: 0.5rem; color: hsl(var(--foreground));">Preview</div>
664
612
  <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>
665
613
  </div>
666
614
  `;
@@ -727,8 +675,7 @@ export class DataFrameComponent extends BaseComponent<DataFrameState> {
727
675
  const updateHint = (headerRow: number) => {
728
676
  if (!hintDiv) return;
729
677
  if (headerRow > 0) {
730
- hintDiv.innerHTML = `Row <strong>${headerRow}</strong> will be used as column headers. ` +
731
- `Rows <strong>0–${headerRow - 1}</strong> will be skipped.`;
678
+ hintDiv.innerHTML = `Row <strong>${headerRow}</strong> will be used as column headers. Rows <strong>0–${headerRow - 1}</strong> will be skipped.`;
732
679
  } else {
733
680
  hintDiv.innerHTML = `Row <strong>0</strong> (first row) will be used as column headers.`;
734
681
  }
@@ -739,36 +686,53 @@ export class DataFrameComponent extends BaseComponent<DataFrameState> {
739
686
  updateHint(headerRow);
740
687
 
741
688
  try {
742
- // ALWAYS parse with headerRow=0 to get raw file structure
743
- const rawSheets = await this._driver.streamFileMultiSheet(this._rawFileData!.file, {
744
- headerRow: 0,
745
- maxSheetSize: Math.max(headerRow + 12, 15)
689
+ // Read cells directly from XLSX to match parser behavior exactly
690
+ const XLSX = await import('xlsx');
691
+ const buffer = await this._rawFileData!.file.arrayBuffer();
692
+ const workbook = XLSX.read(buffer, {
693
+ type: 'array',
694
+ sheetRows: Math.max(headerRow + 12, 15),
695
+ dense: false
746
696
  });
747
- const rawSheet = Object.values(rawSheets)[0];
748
697
 
749
- if (!rawSheet) {
698
+ const sheetName = workbook.SheetNames[0];
699
+ const worksheet = workbook.Sheets[sheetName];
700
+ const ref = worksheet['!ref'];
701
+ if (!ref) {
750
702
  if (previewDiv) previewDiv.textContent = 'No data found';
751
703
  return;
752
704
  }
753
705
 
754
- // rawSheet.columns = row 0 values (when parsed with headerRow=0)
755
- // rawSheet.toRows() = rows 1+ (data rows when parsed with headerRow=0)
756
- const rawCols = rawSheet.columns;
757
- const rawRows = rawSheet.toRows();
706
+ const range = XLSX.utils.decode_range(ref);
707
+ const endRow = range.e.r;
708
+ const startCol = range.s.c;
709
+ const endCol = range.e.c;
710
+
711
+ const readCellValue = (r: number, c: number): any => {
712
+ const addr = XLSX.utils.encode_cell({ r, c });
713
+ const cell = worksheet[addr];
714
+ if (!cell) return null;
715
+ if (cell.w !== undefined) return cell.w;
716
+ if (cell.v !== undefined) return cell.v;
717
+ return null;
718
+ };
719
+
720
+ const readRow = (r: number): any[] => {
721
+ const vals: any[] = [];
722
+ for (let c = startCol; c <= endCol; c++) {
723
+ vals.push(readCellValue(r, c));
724
+ }
725
+ return vals;
726
+ };
758
727
 
759
- // Build HTML table showing raw file structure
760
728
  let html = '<table style="width: 100%; border-collapse: collapse; font-size: 12px;">';
761
-
762
- // We need to show rows 0 through headerRow+7 (or so)
763
- // Row 0 = rawCols, Row 1+ = rawRows[i-1]
764
- const totalRowsToShow = Math.min(headerRow + 8, rawRows.length + 1);
729
+ const totalRowsToShow = Math.min(headerRow + 8, endRow + 1);
765
730
 
766
731
  for (let fileRow = 0; fileRow < totalRowsToShow; fileRow++) {
767
732
  const isHeader = (fileRow === headerRow);
768
733
  const isSkipped = (fileRow < headerRow);
769
734
 
770
735
  let rowStyle = 'border-bottom: 1px solid hsl(var(--border));';
771
-
772
736
  if (isHeader) {
773
737
  rowStyle += 'background: hsl(142 71% 45% / 0.15); font-weight: 600;';
774
738
  } else if (isSkipped) {
@@ -776,8 +740,6 @@ export class DataFrameComponent extends BaseComponent<DataFrameState> {
776
740
  }
777
741
 
778
742
  html += `<tr style="${rowStyle}">`;
779
-
780
- // Row index cell
781
743
  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;">`;
782
744
  if (isHeader) {
783
745
  html += `<span style="color: hsl(142 71% 45%);">▶ ${fileRow}</span>`;
@@ -786,15 +748,7 @@ export class DataFrameComponent extends BaseComponent<DataFrameState> {
786
748
  }
787
749
  html += '</td>';
788
750
 
789
- // Get values for this file row
790
- let values: any[];
791
- if (fileRow === 0) {
792
- values = rawCols;
793
- } else {
794
- values = rawRows[fileRow - 1] ? Object.values(rawRows[fileRow - 1]) : [];
795
- }
796
-
797
- // Show first 6 columns
751
+ const values = readRow(fileRow);
798
752
  const displayCols = values.slice(0, 6);
799
753
  displayCols.forEach(val => {
800
754
  const displayVal = val != null ? String(val).substring(0, 20) : '';
@@ -805,28 +759,22 @@ export class DataFrameComponent extends BaseComponent<DataFrameState> {
805
759
  });
806
760
 
807
761
  if (values.length > 6) {
808
- html += `<td style="padding: 8px 12px; color: hsl(var(--muted-foreground));">…</td>`;
762
+ html += `<td style="padding: 8px 12px; color: hsl(var(--muted-foreground));">...</td>`;
809
763
  }
810
764
 
811
- // Status badge cell
812
765
  html += `<td style="padding: 8px 12px; text-align: right; white-space: nowrap;">`;
813
766
  if (isHeader) {
814
- html += '<span style="background: hsl(142 71% 45%); color: white; padding: 3px 8px; border-radius: 4px; font-size: 10px; font-weight: 600;">HEADER</span>';
767
+ html += '<span style="background: hsl(var(--primary)); color: white; padding: 2px 6px; border-radius: 4px;">HEADER</span>';
815
768
  } else if (isSkipped) {
816
- html += '<span style="color: hsl(var(--muted-foreground)); font-size: 10px;">skipped</span>';
769
+ html += '<span style="color: hsl(var(--muted-foreground));">skipped</span>';
817
770
  } else {
818
- html += '<span style="color: hsl(var(--muted-foreground)); font-size: 10px;">data</span>';
771
+ html += '<span style="color: hsl(var(--success));">data</span>';
819
772
  }
820
- html += '</td>';
821
-
822
- html += '</tr>';
773
+ html += '</td></tr>';
823
774
  }
824
775
 
825
776
  html += '</table>';
826
-
827
- if (previewDiv) {
828
- previewDiv.innerHTML = html;
829
- }
777
+ if (previewDiv) previewDiv.innerHTML = html;
830
778
  } catch (err: any) {
831
779
  if (previewDiv) previewDiv.textContent = `Error: ${err.message}`;
832
780
  }
@@ -834,7 +782,6 @@ export class DataFrameComponent extends BaseComponent<DataFrameState> {
834
782
 
835
783
  if (headerRowInput) headerRowInput.addEventListener('input', updatePreview);
836
784
  updatePreview();
837
-
838
785
  this._reshapeModal.open();
839
786
  }
840
787
 
@@ -909,7 +856,6 @@ export class DataFrameComponent extends BaseComponent<DataFrameState> {
909
856
 
910
857
  await this._driver.store(this._rawFileData.file.name, df, { source: this._rawFileData.file.name });
911
858
  this._setDataFrame(df, this._rawFileData.file.name);
912
-
913
859
  this._reshapeModal!.closeModal();
914
860
  } catch (err: any) {
915
861
  this._updateStatus(`Error: ${err.message}`, 'error');
@@ -940,8 +886,7 @@ export class DataFrameComponent extends BaseComponent<DataFrameState> {
940
886
  if (!hintDiv) return;
941
887
  const headerRow = parseInt(headerRowInput?.value) || 0;
942
888
  if (headerRow > 0) {
943
- hintDiv.innerHTML = `Row <strong>${headerRow}</strong> will be used as column headers. ` +
944
- `Rows <strong>0–${headerRow - 1}</strong> will be skipped.`;
889
+ hintDiv.innerHTML = `Row <strong>${headerRow}</strong> will be used as column headers. Rows <strong>0–${headerRow - 1}</strong> will be skipped.`;
945
890
  } else {
946
891
  hintDiv.innerHTML = `Row <strong>0</strong> (first row) will be used as column headers.`;
947
892
  }
@@ -952,11 +897,9 @@ export class DataFrameComponent extends BaseComponent<DataFrameState> {
952
897
 
953
898
  const delim = delimiterSelect?.value || ',';
954
899
  const headerRow = parseInt(headerRowInput?.value) || 0;
955
-
956
900
  updateHint();
957
901
 
958
902
  try {
959
- // Parse raw to show all rows
960
903
  const rawDf = this._driver.parseCSV(this._rawFileData.text, {
961
904
  delimiter: delim,
962
905
  headerRow: 0,
@@ -967,9 +910,7 @@ export class DataFrameComponent extends BaseComponent<DataFrameState> {
967
910
  const rawCols = rawDf.columns;
968
911
  const rawRows = rawDf.toRows();
969
912
 
970
- // Build HTML table
971
913
  let html = '<table style="width: 100%; border-collapse: collapse; font-size: 11px;">';
972
-
973
914
  const totalRows = Math.min(headerRow + 8, rawRows.length + 1);
974
915
 
975
916
  for (let i = 0; i < totalRows; i++) {
@@ -977,7 +918,6 @@ export class DataFrameComponent extends BaseComponent<DataFrameState> {
977
918
  const isSkipped = (i < headerRow);
978
919
 
979
920
  let rowStyle = 'border-bottom: 1px solid hsl(var(--border));';
980
-
981
921
  if (isHeader) {
982
922
  rowStyle += 'background: hsl(var(--primary) / 0.15); font-weight: bold;';
983
923
  } else if (isSkipped) {
@@ -985,13 +925,10 @@ export class DataFrameComponent extends BaseComponent<DataFrameState> {
985
925
  }
986
926
 
987
927
  html += `<tr style="${rowStyle}">`;
988
-
989
- // Row index
990
928
  html += `<td style="padding: 6px 8px; width: 50px; color: hsl(var(--muted-foreground)); font-weight: 500;">`;
991
929
  html += isHeader ? `<strong>→ ${i}</strong>` : `${i}`;
992
930
  html += '</td>';
993
931
 
994
- // Data
995
932
  let values: any[];
996
933
  if (i === 0) {
997
934
  values = rawCols;
@@ -1010,7 +947,6 @@ export class DataFrameComponent extends BaseComponent<DataFrameState> {
1010
947
  html += `<td style="padding: 6px 8px; color: hsl(var(--muted-foreground));">...</td>`;
1011
948
  }
1012
949
 
1013
- // Status
1014
950
  html += `<td style="padding: 6px 8px; text-align: right; font-size: 10px;">`;
1015
951
  if (isHeader) {
1016
952
  html += '<span style="background: hsl(var(--primary)); color: white; padding: 2px 6px; border-radius: 4px;">HEADER</span>';
@@ -1019,13 +955,10 @@ export class DataFrameComponent extends BaseComponent<DataFrameState> {
1019
955
  } else {
1020
956
  html += '<span style="color: hsl(var(--success));">data</span>';
1021
957
  }
1022
- html += '</td>';
1023
-
1024
- html += '</tr>';
958
+ html += '</td></tr>';
1025
959
  }
1026
960
 
1027
961
  html += '</table>';
1028
-
1029
962
  if (previewDiv) previewDiv.innerHTML = html;
1030
963
  } catch (err: any) {
1031
964
  if (previewDiv) previewDiv.textContent = `Error: ${err.message}`;
@@ -1 +1 @@
1
- {"version":3,"file":"TabularDriver.d.ts","sourceRoot":"","sources":["TabularDriver.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"TabularDriver.d.ts","sourceRoot":"","sources":["TabularDriver.ts"],"names":[],"mappings":"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"}
@@ -537,7 +537,6 @@ export class TabularDriver {
537
537
  const cell = worksheet[addr];
538
538
  if (!cell)
539
539
  return null;
540
- // Prefer formatted string (w) for display, fall back to raw value (v)
541
540
  if (cell.w !== undefined)
542
541
  return cell.w;
543
542
  if (cell.v !== undefined)
@@ -551,10 +550,14 @@ export class TabularDriver {
551
550
  }
552
551
  return vals;
553
552
  };
554
- // Map headerRow (0-based user index) to actual sheet row
555
- const headerSheetRow = startRow + headerRow;
553
+ // FIX: headerRow is the user's 0-based index counting from
554
+ // the first row of the file. Do NOT add startRow the user
555
+ // sees row 0 as the first row regardless of where the sheet
556
+ // range begins. We simply use headerRow as the absolute sheet
557
+ // row index.
558
+ const headerSheetRow = headerRow;
556
559
  console.log(`[TabularDriver] Sheet "${sheetName}": range=${ref}, startRow=${startRow}, endRow=${endRow}, startCol=${startCol}, endCol=${endCol}`);
557
- console.log(`[TabularDriver] headerRow=${headerRow}, headerSheetRow=${headerSheetRow}`);
560
+ console.log(`[TabularDriver] headerRow=${headerRow}, headerSheetRow=${headerSheetRow} (absolute, no startRow offset)`);
558
561
  if (headerSheetRow > endRow) {
559
562
  console.warn(`[TabularDriver] headerRow ${headerRow} (sheet row ${headerSheetRow}) exceeds endRow ${endRow}`);
560
563
  processedSheets++;
@@ -664,7 +664,6 @@ export class TabularDriver {
664
664
  const addr = XLSX.utils.encode_cell({ r, c });
665
665
  const cell = worksheet[addr];
666
666
  if (!cell) return null;
667
- // Prefer formatted string (w) for display, fall back to raw value (v)
668
667
  if (cell.w !== undefined) return cell.w;
669
668
  if (cell.v !== undefined) return cell.v;
670
669
  return null;
@@ -678,11 +677,15 @@ export class TabularDriver {
678
677
  return vals;
679
678
  };
680
679
 
681
- // Map headerRow (0-based user index) to actual sheet row
682
- const headerSheetRow = startRow + headerRow;
680
+ // FIX: headerRow is the user's 0-based index counting from
681
+ // the first row of the file. Do NOT add startRow the user
682
+ // sees row 0 as the first row regardless of where the sheet
683
+ // range begins. We simply use headerRow as the absolute sheet
684
+ // row index.
685
+ const headerSheetRow = headerRow;
683
686
 
684
687
  console.log(`[TabularDriver] Sheet "${sheetName}": range=${ref}, startRow=${startRow}, endRow=${endRow}, startCol=${startCol}, endCol=${endCol}`);
685
- console.log(`[TabularDriver] headerRow=${headerRow}, headerSheetRow=${headerSheetRow}`);
688
+ console.log(`[TabularDriver] headerRow=${headerRow}, headerSheetRow=${headerSheetRow} (absolute, no startRow offset)`);
686
689
 
687
690
  if (headerSheetRow > endRow) {
688
691
  console.warn(`[TabularDriver] headerRow ${headerRow} (sheet row ${headerSheetRow}) exceeds endRow ${endRow}`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "juxscript",
3
- "version": "1.1.204",
3
+ "version": "1.1.205",
4
4
  "type": "module",
5
5
  "description": "A JavaScript UX authorship platform",
6
6
  "main": "index.js",