juxscript 1.1.194 → 1.1.196

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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"}
@@ -3,7 +3,6 @@ import { DataFrame } from '../storage/DataFrame.js';
3
3
  import { TabularDriver } from '../storage/TabularDriver.js';
4
4
  import { FileUpload } from './fileupload.js';
5
5
  import { Table } from './table.js';
6
- import { Tabs } from './tabs.js';
7
6
  import { Modal } from './modal.js';
8
7
  import { renderIcon } from './icons.js';
9
8
  const TRIGGER_EVENTS = [];
@@ -253,25 +252,62 @@ export class DataFrameComponent extends BaseComponent {
253
252
  const existingTable = wrapper.querySelector('.jux-table-wrapper');
254
253
  if (existingTable)
255
254
  existingTable.remove();
255
+ const existingTabs = wrapper.querySelector('.jux-dataframe-tabs');
256
+ if (existingTabs)
257
+ existingTabs.remove();
256
258
  Object.entries(sheets).forEach(([name, df]) => {
257
259
  this._sheets.set(name, df);
258
260
  });
259
261
  const sheetNames = Object.keys(sheets);
260
- const tabs = new Tabs(`${this._id}-tabs`, {
261
- tabs: sheetNames.map(name => ({
262
- id: name,
263
- label: name,
264
- content: ''
265
- })),
266
- activeTab: sheetNames[0]
267
- });
268
- this._tabs = tabs;
262
+ // Build custom tab bar with settings cogs
269
263
  const tabsContainer = document.createElement('div');
270
264
  tabsContainer.className = 'jux-dataframe-tabs';
265
+ const tabList = document.createElement('div');
266
+ tabList.className = 'jux-tabs-list';
267
+ sheetNames.forEach((sheetName, idx) => {
268
+ const tabBtn = document.createElement('button');
269
+ tabBtn.className = 'jux-tabs-button' + (idx === 0 ? ' jux-tabs-button-active' : '');
270
+ tabBtn.setAttribute('data-sheet', sheetName);
271
+ const labelSpan = document.createElement('span');
272
+ labelSpan.textContent = sheetName;
273
+ tabBtn.appendChild(labelSpan);
274
+ // Add settings cog icon
275
+ const cogBtn = document.createElement('button');
276
+ cogBtn.className = 'jux-dataframe-tab-settings';
277
+ cogBtn.title = 'Import settings for ' + sheetName;
278
+ cogBtn.innerHTML = `<span class="iconify" data-icon="lucide:settings" style="width:14px;height:14px;"></span>`;
279
+ cogBtn.addEventListener('click', (e) => {
280
+ e.stopPropagation();
281
+ this._showReshapeModal();
282
+ });
283
+ tabBtn.appendChild(cogBtn);
284
+ tabBtn.addEventListener('click', () => {
285
+ // Switch active tab
286
+ tabList.querySelectorAll('.jux-tabs-button').forEach(btn => {
287
+ btn.classList.remove('jux-tabs-button-active');
288
+ });
289
+ tabBtn.classList.add('jux-tabs-button-active');
290
+ // Show/hide panels
291
+ wrapper.querySelectorAll('.jux-tabs-panel').forEach(panel => {
292
+ panel.style.display = 'none';
293
+ });
294
+ const panel = document.getElementById(`${this._id}-panel-${sheetName}`);
295
+ if (panel)
296
+ panel.style.display = 'block';
297
+ // Update current df reference
298
+ this._df = this._sheets.get(sheetName) || null;
299
+ });
300
+ tabList.appendChild(tabBtn);
301
+ });
302
+ tabsContainer.appendChild(tabList);
271
303
  wrapper.appendChild(tabsContainer);
272
- tabs.render(tabsContainer);
273
- sheetNames.forEach(sheetName => {
304
+ // Create panels for each sheet
305
+ sheetNames.forEach((sheetName, idx) => {
274
306
  const df = sheets[sheetName];
307
+ const panel = document.createElement('div');
308
+ panel.className = 'jux-tabs-panel';
309
+ panel.id = `${this._id}-panel-${sheetName}`;
310
+ panel.style.display = idx === 0 ? 'block' : 'none';
275
311
  const table = new Table(`${this._id}-table-${sheetName}`, {
276
312
  striped: this._tableOptions.striped,
277
313
  hoverable: this._tableOptions.hoverable,
@@ -282,10 +318,8 @@ export class DataFrameComponent extends BaseComponent {
282
318
  });
283
319
  const columnDefs = df.columns.map(col => ({ key: col, label: col }));
284
320
  table.columns(columnDefs).rows(df.toRows());
285
- const tabPanel = document.getElementById(`${this._id}-tabs-${sheetName}-panel`);
286
- if (!tabPanel)
287
- return;
288
- table.render(tabPanel);
321
+ wrapper.appendChild(panel);
322
+ table.render(panel);
289
323
  if (this._tableOptions.filterable) {
290
324
  const filterContainer = document.createElement('div');
291
325
  filterContainer.className = 'jux-dataframe-filter';
@@ -310,12 +344,18 @@ export class DataFrameComponent extends BaseComponent {
310
344
  const filtered = df.filter((row) => Object.values(row).some(v => v !== null && v !== undefined && String(v).toLowerCase().includes(text)));
311
345
  table.rows(filtered.toRows());
312
346
  });
313
- const tableWrapper = tabPanel.querySelector('.jux-table-wrapper');
347
+ const tableWrapper = panel.querySelector('.jux-table-wrapper');
314
348
  if (tableWrapper) {
315
- tabPanel.insertBefore(filterContainer, tableWrapper);
349
+ panel.insertBefore(filterContainer, tableWrapper);
316
350
  }
317
351
  }
318
352
  });
353
+ // Ensure Iconify renders the cog icons
354
+ requestAnimationFrame(() => {
355
+ if (window.Iconify) {
356
+ window.Iconify.scan();
357
+ }
358
+ });
319
359
  const totalRows = Object.values(sheets).reduce((sum, df) => sum + df.height, 0);
320
360
  this._updateStatus(`${sourceName} — ${sheetNames.length} sheets, ${totalRows} total rows`, 'success');
321
361
  this._df = sheets[sheetNames[0]];
@@ -486,14 +526,14 @@ export class DataFrameComponent extends BaseComponent {
486
526
  max="50"
487
527
  style="width: 100%;"
488
528
  />
489
- <div id="${this._id}-reshape-hint" class="jux-reshape-hint">
529
+ <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;">
490
530
  </div>
491
531
  </div>
492
532
  <div class="jux-reshape-preview-container">
493
533
  <div style="font-weight: 600; margin-bottom: 0.5rem; color: hsl(var(--foreground));">
494
534
  Preview
495
535
  </div>
496
- <div id="${this._id}-preview" class="jux-reshape-preview"></div>
536
+ <div id="${this._id}-preview" style="font-family: monospace; font-size: 0.75rem; background: hsl(var(--muted) / 0.3); border: 1px solid hsl(var(--border)); border-radius: var(--radius); padding: 1rem; overflow-x: auto; white-space: pre; max-height: 400px; overflow-y: auto;"></div>
497
537
  </div>
498
538
  `;
499
539
  this._reshapeModal
@@ -543,62 +583,84 @@ export class DataFrameComponent extends BaseComponent {
543
583
  const updateHint = (headerRow) => {
544
584
  if (!hintDiv)
545
585
  return;
546
- hintDiv.innerHTML = headerRow > 0
547
- ? `Row <strong>${headerRow}</strong> will be used as column headers. ` +
548
- `The <strong>${headerRow}</strong> row${headerRow > 1 ? 's' : ''} above will be skipped.`
549
- : `Row <strong>0</strong> (first row) will be used as column headers.`;
586
+ if (headerRow > 0) {
587
+ hintDiv.innerHTML = `Row <strong>${headerRow}</strong> will be used as column headers. ` +
588
+ `Rows <strong>0–${headerRow - 1}</strong> will be skipped (not imported).`;
589
+ }
590
+ else {
591
+ hintDiv.innerHTML = `Row <strong>0</strong> (first row) will be used as column headers. No rows will be skipped.`;
592
+ }
550
593
  };
551
594
  const updatePreview = async () => {
552
595
  const headerRow = parseInt(headerRowInput?.value) || 0;
553
596
  updateHint(headerRow);
554
597
  try {
555
- // First, get raw data (headerRow=0) to show skipped rows
598
+ // Get raw data (headerRow=0) to show ALL rows including skipped ones
556
599
  const rawSheets = await this._driver.streamFileMultiSheet(this._rawFileData.file, {
557
600
  headerRow: 0,
558
- maxSheetSize: headerRow + 15
601
+ maxSheetSize: headerRow + 12
559
602
  });
560
603
  const rawSheet = Object.values(rawSheets)[0];
561
- // Then, get parsed data with the chosen header row
562
- const parsedSheets = await this._driver.streamFileMultiSheet(this._rawFileData.file, {
563
- headerRow,
564
- maxSheetSize: headerRow + 15
565
- });
566
- const parsedSheet = Object.values(parsedSheets)[0];
567
- if (!rawSheet && !parsedSheet) {
604
+ if (!rawSheet) {
568
605
  if (previewDiv)
569
606
  previewDiv.textContent = 'No data found';
570
607
  return;
571
608
  }
572
- const colWidth = 22;
573
- const idxWidth = 6;
609
+ const rawCols = rawSheet.columns;
610
+ const rawRows = rawSheet.toRows();
611
+ const colWidth = 20;
574
612
  const lines = [];
575
- // Show skipped rows (rows before the header)
576
- if (rawSheet && headerRow > 0) {
577
- const rawRows = rawSheet.toRows();
578
- // Row 0 in raw parse is the raw header, rows after are data
579
- // Show raw header as row 0
580
- const rawCols = rawSheet.columns;
581
- lines.push(`${'Idx'.padEnd(idxWidth)}${rawCols.map(c => String(c).substring(0, colWidth - 2).padEnd(colWidth)).join('| ')}`);
582
- const skippedCount = Math.min(headerRow - 1, rawRows.length);
583
- for (let i = 0; i < skippedCount; i++) {
613
+ // === SKIPPED ROWS SECTION ===
614
+ if (headerRow > 0) {
615
+ lines.push('┌─────────────────────────────────────────────────────────────────────────────────┐');
616
+ lines.push(`│ SKIPPED: Rows 0–${headerRow - 1} will NOT be imported │`);
617
+ lines.push('└─────────────────────────────────────────────────────────────────────────────────┘');
618
+ lines.push('');
619
+ // Row 0 is the raw file header (becomes column names when headerRow=0)
620
+ const row0Line = rawCols.slice(0, 5).map(c => String(c ?? '').substring(0, colWidth - 1).padEnd(colWidth)).join('│');
621
+ lines.push(` [0] ${row0Line}${rawCols.length > 5 ? ' ...' : ''}`);
622
+ // Rows 1 to headerRow-1 are skipped data rows
623
+ for (let i = 0; i < Math.min(headerRow - 1, rawRows.length); i++) {
584
624
  const row = rawRows[i];
585
- const rowIdx = String(i + 1).padEnd(idxWidth);
586
- const cols = Object.values(row).map(v => String(v ?? '').substring(0, colWidth - 2).padEnd(colWidth)).join('| ');
587
- lines.push(`${rowIdx}${cols}`);
625
+ const vals = Object.values(row).slice(0, 5).map(v => String(v ?? '').substring(0, colWidth - 1).padEnd(colWidth)).join('│');
626
+ lines.push(` [${i + 1}] ${vals}${Object.values(row).length > 5 ? ' ...' : ''}`);
588
627
  }
589
- // Separator: everything above is skipped
590
- lines.push(`${'─'.repeat(6)}${'── skipped '.padEnd(colWidth * Math.min(rawCols.length, 5), '─')}`);
628
+ lines.push('');
591
629
  }
592
- // Show header row and data rows from parsed result
593
- if (parsedSheet) {
594
- const headerLine = `${'▶'.padEnd(idxWidth)}${parsedSheet.columns.map(c => String(c).substring(0, colWidth - 2).padEnd(colWidth)).join('| ')}`;
595
- lines.push(headerLine);
596
- lines.push(''.repeat(Math.min(headerLine.length, 140)));
597
- const dataRows = parsedSheet.toRows().slice(0, 8);
630
+ // === HEADER ROW (will become column names) ===
631
+ lines.push('╔═════════════════════════════════════════════════════════════════════════════════╗');
632
+ lines.push(`║ HEADER ROW ${headerRow} These values become your column names ║`);
633
+ lines.push('╚═════════════════════════════════════════════════════════════════════════════════╝');
634
+ lines.push('');
635
+ // The header row content - if headerRow=0, it's rawCols, else it's rawRows[headerRow-1]
636
+ let headerValues;
637
+ if (headerRow === 0) {
638
+ headerValues = rawCols;
639
+ }
640
+ else if (headerRow - 1 < rawRows.length) {
641
+ headerValues = Object.values(rawRows[headerRow - 1]).map(v => String(v ?? ''));
642
+ }
643
+ else {
644
+ headerValues = rawCols; // fallback
645
+ }
646
+ const headerLine = headerValues.slice(0, 5).map(c => String(c ?? '').substring(0, colWidth - 1).padEnd(colWidth)).join('│');
647
+ lines.push(`▶ [${headerRow}] ${headerLine}${headerValues.length > 5 ? ' ...' : ''}`);
648
+ lines.push('');
649
+ // === DATA ROWS ===
650
+ lines.push('────────────────────────────────────────────────────────────────────────────────────');
651
+ lines.push(' DATA ROWS (will be imported):');
652
+ lines.push('');
653
+ // Data starts at rawRows[headerRow] (since rawRows is 0-indexed after the header)
654
+ const dataStartIdx = headerRow;
655
+ const dataRows = rawRows.slice(dataStartIdx, dataStartIdx + 6);
656
+ if (dataRows.length === 0) {
657
+ lines.push(' (No data rows found after header)');
658
+ }
659
+ else {
598
660
  dataRows.forEach((row, i) => {
599
- const rowIdx = String(headerRow + 1 + i).padEnd(idxWidth);
600
- const cols = Object.values(row).map(v => String(v ?? '').substring(0, colWidth - 2).padEnd(colWidth)).join('| ');
601
- lines.push(`${rowIdx}${cols}`);
661
+ const rowIdx = headerRow + 1 + i;
662
+ const vals = Object.values(row).slice(0, 5).map(v => String(v ?? '').substring(0, colWidth - 1).padEnd(colWidth)).join('');
663
+ lines.push(` [${rowIdx}] ${vals}${Object.values(row).length > 5 ? ' ...' : ''}`);
602
664
  });
603
665
  }
604
666
  if (previewDiv) {
@@ -736,17 +798,26 @@ export class DataFrameComponent extends BaseComponent {
736
798
  hasHeader: true,
737
799
  maxRows: totalOffset + 1
738
800
  });
801
+ lines.push('');
802
+ lines.push(` ┌${'─'.repeat(100)}┐`);
803
+ lines.push(` │ ROWS 0-${totalOffset - 1} WILL BE SKIPPED (not imported)`.padEnd(101) + '│');
804
+ lines.push(` └${'─'.repeat(100)}┘`);
805
+ lines.push('');
739
806
  const rawCols = rawDf.columns;
740
- lines.push(`${'Idx'.padEnd(idxWidth)}${rawCols.map(c => String(c).substring(0, colWidth - 2).padEnd(colWidth)).join('| ')}`);
807
+ lines.push(`${'[0]'.padEnd(idxWidth)}${rawCols.map(c => String(c).substring(0, colWidth - 2).padEnd(colWidth)).join(' ')} ← skipped`);
741
808
  const rawRows = rawDf.toRows();
742
809
  const skippedCount = Math.min(totalOffset - 1, rawRows.length);
743
810
  for (let i = 0; i < skippedCount; i++) {
744
811
  const row = rawRows[i];
745
- const rowIdx = String(i + 1).padEnd(idxWidth);
746
- const cols = Object.values(row).map(v => String(v ?? '').substring(0, colWidth - 2).padEnd(colWidth)).join('| ');
747
- lines.push(`${rowIdx}${cols}`);
812
+ const rowIdx = `[${i + 1}]`.padEnd(idxWidth);
813
+ const cols = Object.values(row).map(v => String(v ?? '').substring(0, colWidth - 2).padEnd(colWidth)).join(' ');
814
+ lines.push(`${rowIdx}${cols} ← skipped`);
748
815
  }
749
- lines.push(`${''.repeat(6)}${'── skipped '.padEnd(colWidth * Math.min(rawCols.length, 5), '─')}`);
816
+ lines.push('');
817
+ lines.push(` ╔${'═'.repeat(100)}╗`);
818
+ lines.push(` ║ ▼ DATA STARTS HERE (Row ${totalOffset} = Column Headers)`.padEnd(101) + '║');
819
+ lines.push(` ╚${'═'.repeat(100)}╝`);
820
+ lines.push('');
750
821
  }
751
822
  // Parse with actual settings for header + data rows
752
823
  const df = this._driver.parseCSV(this._rawFileData.text, {
@@ -756,13 +827,13 @@ export class DataFrameComponent extends BaseComponent {
756
827
  hasHeader: true,
757
828
  maxRows: 8
758
829
  });
759
- const headerLine = `${''.padEnd(idxWidth)}${df.columns.map(c => String(c).substring(0, colWidth - 2).padEnd(colWidth)).join('| ')}`;
830
+ const headerLine = `${'HDR'.padEnd(idxWidth)}${df.columns.map(c => String(c).substring(0, colWidth - 2).padEnd(colWidth)).join(' ')} ◀ HEADERS`;
760
831
  lines.push(headerLine);
761
- lines.push(''.repeat(Math.min(headerLine.length, 140)));
832
+ lines.push(`${'───'.padEnd(idxWidth)}${'─'.repeat(Math.min(colWidth * df.columns.length, 120))}`);
762
833
  const dataRows = df.toRows();
763
834
  dataRows.forEach((row, i) => {
764
- const rowIdx = String(totalOffset + 1 + i).padEnd(idxWidth);
765
- const cols = Object.values(row).map(v => String(v ?? '').substring(0, colWidth - 2).padEnd(colWidth)).join('| ');
835
+ const rowIdx = `[${totalOffset + 1 + i}]`.padEnd(idxWidth);
836
+ const cols = Object.values(row).map(v => String(v ?? '').substring(0, colWidth - 2).padEnd(colWidth)).join(' ');
766
837
  lines.push(`${rowIdx}${cols}`);
767
838
  });
768
839
  if (previewDiv) {
@@ -314,32 +314,75 @@ export class DataFrameComponent extends BaseComponent<DataFrameState> {
314
314
  const existingTable = wrapper.querySelector('.jux-table-wrapper');
315
315
  if (existingTable) existingTable.remove();
316
316
 
317
+ const existingTabs = wrapper.querySelector('.jux-dataframe-tabs');
318
+ if (existingTabs) existingTabs.remove();
319
+
317
320
  Object.entries(sheets).forEach(([name, df]) => {
318
321
  this._sheets.set(name, df);
319
322
  });
320
323
 
321
324
  const sheetNames = Object.keys(sheets);
322
325
 
323
- const tabs = new Tabs(`${this._id}-tabs`, {
324
- tabs: sheetNames.map(name => ({
325
- id: name,
326
- label: name,
327
- content: ''
328
- })),
329
- activeTab: sheetNames[0]
330
- });
331
-
332
- this._tabs = tabs;
333
-
326
+ // Build custom tab bar with settings cogs
334
327
  const tabsContainer = document.createElement('div');
335
328
  tabsContainer.className = 'jux-dataframe-tabs';
336
- wrapper.appendChild(tabsContainer);
337
329
 
338
- tabs.render(tabsContainer);
330
+ const tabList = document.createElement('div');
331
+ tabList.className = 'jux-tabs-list';
332
+
333
+ sheetNames.forEach((sheetName, idx) => {
334
+ const tabBtn = document.createElement('button');
335
+ tabBtn.className = 'jux-tabs-button' + (idx === 0 ? ' jux-tabs-button-active' : '');
336
+ tabBtn.setAttribute('data-sheet', sheetName);
337
+
338
+ const labelSpan = document.createElement('span');
339
+ labelSpan.textContent = sheetName;
340
+ tabBtn.appendChild(labelSpan);
341
+
342
+ // Add settings cog icon
343
+ const cogBtn = document.createElement('button');
344
+ cogBtn.className = 'jux-dataframe-tab-settings';
345
+ cogBtn.title = 'Import settings for ' + sheetName;
346
+ cogBtn.innerHTML = `<span class="iconify" data-icon="lucide:settings" style="width:14px;height:14px;"></span>`;
347
+ cogBtn.addEventListener('click', (e) => {
348
+ e.stopPropagation();
349
+ this._showReshapeModal();
350
+ });
351
+ tabBtn.appendChild(cogBtn);
352
+
353
+ tabBtn.addEventListener('click', () => {
354
+ // Switch active tab
355
+ tabList.querySelectorAll('.jux-tabs-button').forEach(btn => {
356
+ btn.classList.remove('jux-tabs-button-active');
357
+ });
358
+ tabBtn.classList.add('jux-tabs-button-active');
359
+
360
+ // Show/hide panels
361
+ wrapper.querySelectorAll('.jux-tabs-panel').forEach(panel => {
362
+ (panel as HTMLElement).style.display = 'none';
363
+ });
364
+ const panel = document.getElementById(`${this._id}-panel-${sheetName}`);
365
+ if (panel) panel.style.display = 'block';
339
366
 
340
- sheetNames.forEach(sheetName => {
367
+ // Update current df reference
368
+ this._df = this._sheets.get(sheetName) || null;
369
+ });
370
+
371
+ tabList.appendChild(tabBtn);
372
+ });
373
+
374
+ tabsContainer.appendChild(tabList);
375
+ wrapper.appendChild(tabsContainer);
376
+
377
+ // Create panels for each sheet
378
+ sheetNames.forEach((sheetName, idx) => {
341
379
  const df = sheets[sheetName];
342
380
 
381
+ const panel = document.createElement('div');
382
+ panel.className = 'jux-tabs-panel';
383
+ panel.id = `${this._id}-panel-${sheetName}`;
384
+ panel.style.display = idx === 0 ? 'block' : 'none';
385
+
343
386
  const table = new Table(`${this._id}-table-${sheetName}`, {
344
387
  striped: this._tableOptions.striped,
345
388
  hoverable: this._tableOptions.hoverable,
@@ -352,10 +395,8 @@ export class DataFrameComponent extends BaseComponent<DataFrameState> {
352
395
  const columnDefs = df.columns.map(col => ({ key: col, label: col }));
353
396
  table.columns(columnDefs).rows(df.toRows());
354
397
 
355
- const tabPanel = document.getElementById(`${this._id}-tabs-${sheetName}-panel`);
356
- if (!tabPanel) return;
357
-
358
- table.render(tabPanel);
398
+ wrapper.appendChild(panel);
399
+ table.render(panel);
359
400
 
360
401
  if (this._tableOptions.filterable) {
361
402
  const filterContainer = document.createElement('div');
@@ -388,13 +429,20 @@ export class DataFrameComponent extends BaseComponent<DataFrameState> {
388
429
  table.rows(filtered.toRows());
389
430
  });
390
431
 
391
- const tableWrapper = tabPanel.querySelector('.jux-table-wrapper');
432
+ const tableWrapper = panel.querySelector('.jux-table-wrapper');
392
433
  if (tableWrapper) {
393
- tabPanel.insertBefore(filterContainer, tableWrapper);
434
+ panel.insertBefore(filterContainer, tableWrapper);
394
435
  }
395
436
  }
396
437
  });
397
438
 
439
+ // Ensure Iconify renders the cog icons
440
+ requestAnimationFrame(() => {
441
+ if ((window as any).Iconify) {
442
+ (window as any).Iconify.scan();
443
+ }
444
+ });
445
+
398
446
  const totalRows = Object.values(sheets).reduce((sum, df) => sum + df.height, 0);
399
447
  this._updateStatus(
400
448
  `${sourceName} — ${sheetNames.length} sheets, ${totalRows} total rows`,
@@ -604,14 +652,14 @@ export class DataFrameComponent extends BaseComponent<DataFrameState> {
604
652
  max="50"
605
653
  style="width: 100%;"
606
654
  />
607
- <div id="${this._id}-reshape-hint" class="jux-reshape-hint">
655
+ <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;">
608
656
  </div>
609
657
  </div>
610
658
  <div class="jux-reshape-preview-container">
611
659
  <div style="font-weight: 600; margin-bottom: 0.5rem; color: hsl(var(--foreground));">
612
660
  Preview
613
661
  </div>
614
- <div id="${this._id}-preview" class="jux-reshape-preview"></div>
662
+ <div id="${this._id}-preview" style="font-family: monospace; font-size: 0.75rem; background: hsl(var(--muted) / 0.3); border: 1px solid hsl(var(--border)); border-radius: var(--radius); padding: 1rem; overflow-x: auto; white-space: pre; max-height: 400px; overflow-y: auto;"></div>
615
663
  </div>
616
664
  `;
617
665
 
@@ -668,10 +716,12 @@ export class DataFrameComponent extends BaseComponent<DataFrameState> {
668
716
 
669
717
  const updateHint = (headerRow: number) => {
670
718
  if (!hintDiv) return;
671
- hintDiv.innerHTML = headerRow > 0
672
- ? `Row <strong>${headerRow}</strong> will be used as column headers. ` +
673
- `The <strong>${headerRow}</strong> row${headerRow > 1 ? 's' : ''} above will be skipped.`
674
- : `Row <strong>0</strong> (first row) will be used as column headers.`;
719
+ if (headerRow > 0) {
720
+ hintDiv.innerHTML = `Row <strong>${headerRow}</strong> will be used as column headers. ` +
721
+ `Rows <strong>0–${headerRow - 1}</strong> will be skipped (not imported).`;
722
+ } else {
723
+ hintDiv.innerHTML = `Row <strong>0</strong> (first row) will be used as column headers. No rows will be skipped.`;
724
+ }
675
725
  };
676
726
 
677
727
  const updatePreview = async () => {
@@ -679,60 +729,80 @@ export class DataFrameComponent extends BaseComponent<DataFrameState> {
679
729
  updateHint(headerRow);
680
730
 
681
731
  try {
682
- // First, get raw data (headerRow=0) to show skipped rows
732
+ // Get raw data (headerRow=0) to show ALL rows including skipped ones
683
733
  const rawSheets = await this._driver.streamFileMultiSheet(this._rawFileData!.file, {
684
734
  headerRow: 0,
685
- maxSheetSize: headerRow + 15
735
+ maxSheetSize: headerRow + 12
686
736
  });
687
737
  const rawSheet = Object.values(rawSheets)[0];
688
738
 
689
- // Then, get parsed data with the chosen header row
690
- const parsedSheets = await this._driver.streamFileMultiSheet(this._rawFileData!.file, {
691
- headerRow,
692
- maxSheetSize: headerRow + 15
693
- });
694
- const parsedSheet = Object.values(parsedSheets)[0];
695
-
696
- if (!rawSheet && !parsedSheet) {
739
+ if (!rawSheet) {
697
740
  if (previewDiv) previewDiv.textContent = 'No data found';
698
741
  return;
699
742
  }
700
743
 
701
- const colWidth = 22;
702
- const idxWidth = 6;
744
+ const rawCols = rawSheet.columns;
745
+ const rawRows = rawSheet.toRows();
746
+ const colWidth = 20;
703
747
  const lines: string[] = [];
704
748
 
705
- // Show skipped rows (rows before the header)
706
- if (rawSheet && headerRow > 0) {
707
- const rawRows = rawSheet.toRows();
708
- // Row 0 in raw parse is the raw header, rows after are data
709
- // Show raw header as row 0
710
- const rawCols = rawSheet.columns;
711
- lines.push(`${'Idx'.padEnd(idxWidth)}${rawCols.map(c => String(c).substring(0, colWidth - 2).padEnd(colWidth)).join('| ')}`);
749
+ // === SKIPPED ROWS SECTION ===
750
+ if (headerRow > 0) {
751
+ lines.push('┌─────────────────────────────────────────────────────────────────────────────────┐');
752
+ lines.push(`│ SKIPPED: Rows 0–${headerRow - 1} will NOT be imported │`);
753
+ lines.push('└─────────────────────────────────────────────────────────────────────────────────┘');
754
+ lines.push('');
712
755
 
713
- const skippedCount = Math.min(headerRow - 1, rawRows.length);
714
- for (let i = 0; i < skippedCount; i++) {
756
+ // Row 0 is the raw file header (becomes column names when headerRow=0)
757
+ const row0Line = rawCols.slice(0, 5).map(c => String(c ?? '').substring(0, colWidth - 1).padEnd(colWidth)).join('│');
758
+ lines.push(` [0] ${row0Line}${rawCols.length > 5 ? ' ...' : ''}`);
759
+
760
+ // Rows 1 to headerRow-1 are skipped data rows
761
+ for (let i = 0; i < Math.min(headerRow - 1, rawRows.length); i++) {
715
762
  const row = rawRows[i];
716
- const rowIdx = String(i + 1).padEnd(idxWidth);
717
- const cols = Object.values(row).map(v => String(v ?? '').substring(0, colWidth - 2).padEnd(colWidth)).join('| ');
718
- lines.push(`${rowIdx}${cols}`);
763
+ const vals = Object.values(row).slice(0, 5).map(v => String(v ?? '').substring(0, colWidth - 1).padEnd(colWidth)).join('│');
764
+ lines.push(` [${i + 1}] ${vals}${Object.values(row).length > 5 ? ' ...' : ''}`);
719
765
  }
720
766
 
721
- // Separator: everything above is skipped
722
- lines.push(`${'─'.repeat(6)}${'── skipped '.padEnd(colWidth * Math.min(rawCols.length, 5), '─')}`);
767
+ lines.push('');
723
768
  }
724
769
 
725
- // Show header row and data rows from parsed result
726
- if (parsedSheet) {
727
- const headerLine = `${'▶'.padEnd(idxWidth)}${parsedSheet.columns.map(c => String(c).substring(0, colWidth - 2).padEnd(colWidth)).join('| ')}`;
728
- lines.push(headerLine);
729
- lines.push(''.repeat(Math.min(headerLine.length, 140)));
770
+ // === HEADER ROW (will become column names) ===
771
+ lines.push('╔═════════════════════════════════════════════════════════════════════════════════╗');
772
+ lines.push(`║ HEADER ROW ${headerRow} These values become your column names ║`);
773
+ lines.push('╚═════════════════════════════════════════════════════════════════════════════════╝');
774
+ lines.push('');
775
+
776
+ // The header row content - if headerRow=0, it's rawCols, else it's rawRows[headerRow-1]
777
+ let headerValues: string[];
778
+ if (headerRow === 0) {
779
+ headerValues = rawCols;
780
+ } else if (headerRow - 1 < rawRows.length) {
781
+ headerValues = Object.values(rawRows[headerRow - 1]).map(v => String(v ?? ''));
782
+ } else {
783
+ headerValues = rawCols; // fallback
784
+ }
785
+
786
+ const headerLine = headerValues.slice(0, 5).map(c => String(c ?? '').substring(0, colWidth - 1).padEnd(colWidth)).join('│');
787
+ lines.push(`▶ [${headerRow}] ${headerLine}${headerValues.length > 5 ? ' ...' : ''}`);
788
+ lines.push('');
789
+
790
+ // === DATA ROWS ===
791
+ lines.push('────────────────────────────────────────────────────────────────────────────────────');
792
+ lines.push(' DATA ROWS (will be imported):');
793
+ lines.push('');
730
794
 
731
- const dataRows = parsedSheet.toRows().slice(0, 8);
795
+ // Data starts at rawRows[headerRow] (since rawRows is 0-indexed after the header)
796
+ const dataStartIdx = headerRow;
797
+ const dataRows = rawRows.slice(dataStartIdx, dataStartIdx + 6);
798
+
799
+ if (dataRows.length === 0) {
800
+ lines.push(' (No data rows found after header)');
801
+ } else {
732
802
  dataRows.forEach((row, i) => {
733
- const rowIdx = String(headerRow + 1 + i).padEnd(idxWidth);
734
- const cols = Object.values(row).map(v => String(v ?? '').substring(0, colWidth - 2).padEnd(colWidth)).join('| ');
735
- lines.push(`${rowIdx}${cols}`);
803
+ const rowIdx = headerRow + 1 + i;
804
+ const vals = Object.values(row).slice(0, 5).map(v => String(v ?? '').substring(0, colWidth - 1).padEnd(colWidth)).join('');
805
+ lines.push(` [${rowIdx}] ${vals}${Object.values(row).length > 5 ? ' ...' : ''}`);
736
806
  });
737
807
  }
738
808
 
@@ -885,19 +955,29 @@ export class DataFrameComponent extends BaseComponent<DataFrameState> {
885
955
  maxRows: totalOffset + 1
886
956
  });
887
957
 
958
+ lines.push('');
959
+ lines.push(` ┌${'─'.repeat(100)}┐`);
960
+ lines.push(` │ ROWS 0-${totalOffset - 1} WILL BE SKIPPED (not imported)`.padEnd(101) + '│');
961
+ lines.push(` └${'─'.repeat(100)}┘`);
962
+ lines.push('');
963
+
888
964
  const rawCols = rawDf.columns;
889
- lines.push(`${'Idx'.padEnd(idxWidth)}${rawCols.map(c => String(c).substring(0, colWidth - 2).padEnd(colWidth)).join('| ')}`);
965
+ lines.push(`${'[0]'.padEnd(idxWidth)}${rawCols.map(c => String(c).substring(0, colWidth - 2).padEnd(colWidth)).join(' ')} ← skipped`);
890
966
 
891
967
  const rawRows = rawDf.toRows();
892
968
  const skippedCount = Math.min(totalOffset - 1, rawRows.length);
893
969
  for (let i = 0; i < skippedCount; i++) {
894
970
  const row = rawRows[i];
895
- const rowIdx = String(i + 1).padEnd(idxWidth);
896
- const cols = Object.values(row).map(v => String(v ?? '').substring(0, colWidth - 2).padEnd(colWidth)).join('| ');
897
- lines.push(`${rowIdx}${cols}`);
971
+ const rowIdx = `[${i + 1}]`.padEnd(idxWidth);
972
+ const cols = Object.values(row).map(v => String(v ?? '').substring(0, colWidth - 2).padEnd(colWidth)).join(' ');
973
+ lines.push(`${rowIdx}${cols} ← skipped`);
898
974
  }
899
975
 
900
- lines.push(`${''.repeat(6)}${'── skipped '.padEnd(colWidth * Math.min(rawCols.length, 5), '─')}`);
976
+ lines.push('');
977
+ lines.push(` ╔${'═'.repeat(100)}╗`);
978
+ lines.push(` ║ ▼ DATA STARTS HERE (Row ${totalOffset} = Column Headers)`.padEnd(101) + '║');
979
+ lines.push(` ╚${'═'.repeat(100)}╝`);
980
+ lines.push('');
901
981
  }
902
982
 
903
983
  // Parse with actual settings for header + data rows
@@ -909,14 +989,14 @@ export class DataFrameComponent extends BaseComponent<DataFrameState> {
909
989
  maxRows: 8
910
990
  });
911
991
 
912
- const headerLine = `${''.padEnd(idxWidth)}${df.columns.map(c => String(c).substring(0, colWidth - 2).padEnd(colWidth)).join('| ')}`;
992
+ const headerLine = `${'HDR'.padEnd(idxWidth)}${df.columns.map(c => String(c).substring(0, colWidth - 2).padEnd(colWidth)).join(' ')} ◀ HEADERS`;
913
993
  lines.push(headerLine);
914
- lines.push(''.repeat(Math.min(headerLine.length, 140)));
994
+ lines.push(`${'───'.padEnd(idxWidth)}${'─'.repeat(Math.min(colWidth * df.columns.length, 120))}`);
915
995
 
916
996
  const dataRows = df.toRows();
917
997
  dataRows.forEach((row, i) => {
918
- const rowIdx = String(totalOffset + 1 + i).padEnd(idxWidth);
919
- const cols = Object.values(row).map(v => String(v ?? '').substring(0, colWidth - 2).padEnd(colWidth)).join('| ');
998
+ const rowIdx = `[${totalOffset + 1 + i}]`.padEnd(idxWidth);
999
+ const cols = Object.values(row).map(v => String(v ?? '').substring(0, colWidth - 2).padEnd(colWidth)).join(' ');
920
1000
  lines.push(`${rowIdx}${cols}`);
921
1001
  });
922
1002
 
@@ -589,49 +589,47 @@ code {
589
589
  .jux-modal-position-fullscreen .jux-modal { width: 100%; max-width: 100%; height: 100vh; max-height: 100vh; border-radius: 0; }
590
590
 
591
591
  /* ═══════════════════════════════════════════════════════════════════
592
- * RESHAPE MODAL (DataFrame Import Settings)
592
+ * DATAFRAME TAB SETTINGS COG
593
593
  * ═══════════════════════════════════════════════════════════════════ */
594
594
 
595
- .jux-reshape-hint {
596
- font-size: 0.8125rem;
595
+ .jux-dataframe-tab-settings {
596
+ display: inline-flex;
597
+ align-items: center;
598
+ justify-content: center;
599
+ background: transparent;
600
+ border: none;
601
+ padding: 0.125rem;
602
+ margin-left: 0.375rem;
603
+ border-radius: 0.25rem;
604
+ cursor: pointer;
597
605
  color: hsl(var(--muted-foreground));
598
- margin-top: 0.5rem;
599
- padding: 0.625rem 0.75rem;
600
- background: hsl(var(--warning) / 0.08);
601
- border-radius: var(--radius);
602
- border: 1px solid hsl(var(--warning) / 0.2);
603
- line-height: 1.5;
606
+ opacity: 0.5;
607
+ transition: all 0.15s;
604
608
  }
605
609
 
606
- .jux-reshape-hint strong {
610
+ .jux-dataframe-tab-settings:hover {
611
+ opacity: 1;
612
+ background: hsl(var(--muted) / 0.5);
607
613
  color: hsl(var(--foreground));
608
614
  }
609
615
 
610
- .jux-reshape-preview-container {
611
- border: 1px solid hsl(var(--border));
612
- border-radius: var(--radius);
613
- padding: 1rem;
614
- background: hsl(var(--muted) / 0.3);
615
- max-height: 400px;
616
- overflow: auto;
616
+ .jux-tabs-button-active .jux-dataframe-tab-settings {
617
+ opacity: 0.7;
617
618
  }
618
619
 
619
- .jux-reshape-preview {
620
- font-family: 'JetBrains Mono', 'Cascadia Code', 'Fira Code', 'Courier New', monospace;
621
- font-size: 0.6875rem;
622
- white-space: pre;
623
- color: hsl(var(--foreground));
624
- line-height: 1.6;
625
- tab-size: 4;
620
+ .jux-tabs-button-active .jux-dataframe-tab-settings:hover {
621
+ opacity: 1;
626
622
  }
627
623
 
628
- /* ═══════════════════════════════════════════════════════════════════
629
- * BUTTON SIZES
630
- * ═══════════════════════════════════════════════════════════════════ */
624
+ /* Make tabs list scrollable if many sheets */
625
+ .jux-dataframe-tabs .jux-tabs-list {
626
+ overflow-x: auto;
627
+ flex-wrap: nowrap;
628
+ max-width: 100%;
629
+ }
631
630
 
632
- .jux-button-sm {
633
- height: 1.75rem;
634
- padding: 0 0.625rem;
635
- font-size: 0.75rem;
636
- border-radius: calc(var(--radius) - 2px);
631
+ .jux-dataframe-tabs .jux-tabs-button {
632
+ display: inline-flex;
633
+ align-items: center;
634
+ flex-shrink: 0;
637
635
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "juxscript",
3
- "version": "1.1.194",
3
+ "version": "1.1.196",
4
4
  "type": "module",
5
5
  "description": "A JavaScript UX authorship platform",
6
6
  "main": "index.js",