juxscript 1.1.193 → 1.1.195

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]];
@@ -491,7 +531,7 @@ export class DataFrameComponent extends BaseComponent {
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
- Preview (first 10 rows)
534
+ Preview
495
535
  </div>
496
536
  <div id="${this._id}-preview" class="jux-reshape-preview"></div>
497
537
  </div>
@@ -543,35 +583,73 @@ export class DataFrameComponent extends BaseComponent {
543
583
  const updateHint = (headerRow) => {
544
584
  if (!hintDiv)
545
585
  return;
546
- hintDiv.innerHTML = `The data starting at <strong>row ${headerRow}</strong> will be used as column headers. ` +
547
- `Rows before it will be skipped. The preview below shows the row index as the first column.`;
586
+ hintDiv.innerHTML = headerRow > 0
587
+ ? `Row <strong>${headerRow}</strong> will be used as column headers. ` +
588
+ `The <strong>${headerRow}</strong> row${headerRow > 1 ? 's' : ''} above will be skipped.`
589
+ : `Row <strong>0</strong> (first row) will be used as column headers.`;
548
590
  };
549
591
  const updatePreview = async () => {
550
592
  const headerRow = parseInt(headerRowInput?.value) || 0;
551
593
  updateHint(headerRow);
552
594
  try {
553
- const sheets = await this._driver.streamFileMultiSheet(this._rawFileData.file, {
595
+ // First, get raw data (headerRow=0) to show skipped rows
596
+ const rawSheets = await this._driver.streamFileMultiSheet(this._rawFileData.file, {
597
+ headerRow: 0,
598
+ maxSheetSize: headerRow + 15
599
+ });
600
+ const rawSheet = Object.values(rawSheets)[0];
601
+ // Then, get parsed data with the chosen header row
602
+ const parsedSheets = await this._driver.streamFileMultiSheet(this._rawFileData.file, {
554
603
  headerRow,
555
- maxSheetSize: headerRow + 20
604
+ maxSheetSize: headerRow + 15
556
605
  });
557
- const firstSheet = Object.values(sheets)[0];
558
- if (!firstSheet) {
606
+ const parsedSheet = Object.values(parsedSheets)[0];
607
+ if (!rawSheet && !parsedSheet) {
559
608
  if (previewDiv)
560
609
  previewDiv.textContent = 'No data found';
561
610
  return;
562
611
  }
563
- const dataRows = firstSheet.toRows().slice(0, 10);
564
- const idxWidth = 6;
565
612
  const colWidth = 22;
566
- const headerLine = 'Idx'.padEnd(idxWidth) + firstSheet.columns.map(c => String(c).substring(0, colWidth - 2).padEnd(colWidth)).join('| ');
567
- const separator = '─'.repeat(Math.min(headerLine.length, 140));
568
- const preview = dataRows.map((row, i) => {
569
- const rowIdx = String(headerRow + 1 + i).padEnd(idxWidth);
570
- const cols = Object.values(row).map(v => String(v ?? '').substring(0, colWidth - 2).padEnd(colWidth)).join('| ');
571
- return `${rowIdx}${cols}`;
572
- }).join('\n');
613
+ const idxWidth = 6;
614
+ const lines = [];
615
+ // Show skipped rows (rows before the header)
616
+ if (rawSheet && headerRow > 0) {
617
+ lines.push('');
618
+ lines.push(` ┌${'─'.repeat(100)}┐`);
619
+ lines.push(` │ ROWS 0-${headerRow - 1} WILL BE SKIPPED (not imported)`.padEnd(101) + '');
620
+ lines.push(` └${'─'.repeat(100)}┘`);
621
+ lines.push('');
622
+ const rawRows = rawSheet.toRows();
623
+ const rawCols = rawSheet.columns;
624
+ // Show raw header (row 0) - dimmed
625
+ lines.push(`${'[0]'.padEnd(idxWidth)}${rawCols.map(c => String(c).substring(0, colWidth - 2).padEnd(colWidth)).join('│ ')} ← skipped`);
626
+ const skippedCount = Math.min(headerRow - 1, rawRows.length);
627
+ for (let i = 0; i < skippedCount; i++) {
628
+ const row = rawRows[i];
629
+ const rowIdx = `[${i + 1}]`.padEnd(idxWidth);
630
+ const cols = Object.values(row).map(v => String(v ?? '').substring(0, colWidth - 2).padEnd(colWidth)).join('│ ');
631
+ lines.push(`${rowIdx}${cols} ← skipped`);
632
+ }
633
+ lines.push('');
634
+ lines.push(` ╔${'═'.repeat(100)}╗`);
635
+ lines.push(` ║ ▼ DATA STARTS HERE (Row ${headerRow} = Column Headers)`.padEnd(101) + '║');
636
+ lines.push(` ╚${'═'.repeat(100)}╝`);
637
+ lines.push('');
638
+ }
639
+ // Show header row and data rows from parsed result
640
+ if (parsedSheet) {
641
+ const headerLine = `${'HDR'.padEnd(idxWidth)}${parsedSheet.columns.map(c => String(c).substring(0, colWidth - 2).padEnd(colWidth)).join('│ ')} ◀ HEADERS`;
642
+ lines.push(headerLine);
643
+ lines.push(`${'───'.padEnd(idxWidth)}${'─'.repeat(Math.min(colWidth * parsedSheet.columns.length, 120))}`);
644
+ const dataRows = parsedSheet.toRows().slice(0, 8);
645
+ dataRows.forEach((row, i) => {
646
+ const rowIdx = `[${headerRow + 1 + i}]`.padEnd(idxWidth);
647
+ const cols = Object.values(row).map(v => String(v ?? '').substring(0, colWidth - 2).padEnd(colWidth)).join('│ ');
648
+ lines.push(`${rowIdx}${cols}`);
649
+ });
650
+ }
573
651
  if (previewDiv) {
574
- previewDiv.textContent = `${headerLine}\n${separator}\n${preview}`;
652
+ previewDiv.textContent = lines.join('\n');
575
653
  }
576
654
  }
577
655
  catch (err) {
@@ -678,9 +756,11 @@ export class DataFrameComponent extends BaseComponent {
678
756
  return;
679
757
  const headerRow = parseInt(headerRowInput?.value) || 0;
680
758
  const skipRows = parseInt(skipRowsInput?.value) || 0;
681
- hintDiv.innerHTML = `Using <strong>row ${headerRow}</strong> as column headers` +
682
- (skipRows > 0 ? ` (skipping ${skipRows} rows before it)` : '') +
683
- `. The Idx column shows the file row index.`;
759
+ const totalSkipped = headerRow + skipRows;
760
+ hintDiv.innerHTML = totalSkipped > 0
761
+ ? `Row <strong>${headerRow + skipRows}</strong> will be used as column headers. ` +
762
+ `<strong>${totalSkipped}</strong> row${totalSkipped > 1 ? 's' : ''} above will be skipped.`
763
+ : `Row <strong>0</strong> (first row) will be used as column headers.`;
684
764
  };
685
765
  const updatePreview = () => {
686
766
  if (!this._rawFileData?.text)
@@ -690,25 +770,59 @@ export class DataFrameComponent extends BaseComponent {
690
770
  const skipRows = parseInt(skipRowsInput?.value) || 0;
691
771
  updateHint();
692
772
  try {
773
+ const colWidth = 22;
774
+ const idxWidth = 6;
775
+ const lines = [];
776
+ const totalOffset = headerRow + skipRows;
777
+ // Parse raw (no header offset) to show skipped rows
778
+ if (totalOffset > 0) {
779
+ const rawDf = this._driver.parseCSV(this._rawFileData.text, {
780
+ delimiter: delim,
781
+ headerRow: 0,
782
+ skipRows: 0,
783
+ hasHeader: true,
784
+ maxRows: totalOffset + 1
785
+ });
786
+ lines.push('');
787
+ lines.push(` ┌${'─'.repeat(100)}┐`);
788
+ lines.push(` │ ROWS 0-${totalOffset - 1} WILL BE SKIPPED (not imported)`.padEnd(101) + '│');
789
+ lines.push(` └${'─'.repeat(100)}┘`);
790
+ lines.push('');
791
+ const rawCols = rawDf.columns;
792
+ lines.push(`${'[0]'.padEnd(idxWidth)}${rawCols.map(c => String(c).substring(0, colWidth - 2).padEnd(colWidth)).join('│ ')} ← skipped`);
793
+ const rawRows = rawDf.toRows();
794
+ const skippedCount = Math.min(totalOffset - 1, rawRows.length);
795
+ for (let i = 0; i < skippedCount; i++) {
796
+ const row = rawRows[i];
797
+ const rowIdx = `[${i + 1}]`.padEnd(idxWidth);
798
+ const cols = Object.values(row).map(v => String(v ?? '').substring(0, colWidth - 2).padEnd(colWidth)).join('│ ');
799
+ lines.push(`${rowIdx}${cols} ← skipped`);
800
+ }
801
+ lines.push('');
802
+ lines.push(` ╔${'═'.repeat(100)}╗`);
803
+ lines.push(` ║ ▼ DATA STARTS HERE (Row ${totalOffset} = Column Headers)`.padEnd(101) + '║');
804
+ lines.push(` ╚${'═'.repeat(100)}╝`);
805
+ lines.push('');
806
+ }
807
+ // Parse with actual settings for header + data rows
693
808
  const df = this._driver.parseCSV(this._rawFileData.text, {
694
809
  delimiter: delim,
695
810
  headerRow,
696
811
  skipRows,
697
812
  hasHeader: true,
698
- maxRows: 10
813
+ maxRows: 8
699
814
  });
815
+ const headerLine = `${'HDR'.padEnd(idxWidth)}${df.columns.map(c => String(c).substring(0, colWidth - 2).padEnd(colWidth)).join('│ ')} ◀ HEADERS`;
816
+ lines.push(headerLine);
817
+ lines.push(`${'───'.padEnd(idxWidth)}${'─'.repeat(Math.min(colWidth * df.columns.length, 120))}`);
700
818
  const dataRows = df.toRows();
701
- const idxWidth = 6;
702
- const colWidth = 22;
703
- const headerLine = 'Idx'.padEnd(idxWidth) + df.columns.map(c => String(c).substring(0, colWidth - 2).padEnd(colWidth)).join('| ');
704
- const separator = '─'.repeat(Math.min(headerLine.length, 140));
705
- const preview = dataRows.map((row, i) => {
706
- const rowIdx = String(headerRow + skipRows + 1 + i).padEnd(idxWidth);
707
- const cols = Object.values(row).map(v => String(v ?? '').substring(0, colWidth - 2).padEnd(colWidth)).join('| ');
708
- return `${rowIdx}${cols}`;
709
- }).join('\n');
819
+ dataRows.forEach((row, i) => {
820
+ const rowIdx = `[${totalOffset + 1 + i}]`.padEnd(idxWidth);
821
+ const cols = Object.values(row).map(v => String(v ?? '').substring(0, colWidth - 2).padEnd(colWidth)).join(' ');
822
+ lines.push(`${rowIdx}${cols}`);
823
+ });
710
824
  if (previewDiv) {
711
- previewDiv.textContent = `${headerLine}\n${separator}\n${preview}`;
825
+ previewDiv.textContent = lines.join('\n');
712
826
  }
713
827
  }
714
828
  catch (err) {
@@ -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';
366
+
367
+ // Update current df reference
368
+ this._df = this._sheets.get(sheetName) || null;
369
+ });
370
+
371
+ tabList.appendChild(tabBtn);
372
+ });
339
373
 
340
- sheetNames.forEach(sheetName => {
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`,
@@ -609,7 +657,7 @@ export class DataFrameComponent extends BaseComponent<DataFrameState> {
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
- Preview (first 10 rows)
660
+ Preview
613
661
  </div>
614
662
  <div id="${this._id}-preview" class="jux-reshape-preview"></div>
615
663
  </div>
@@ -668,42 +716,85 @@ export class DataFrameComponent extends BaseComponent<DataFrameState> {
668
716
 
669
717
  const updateHint = (headerRow: number) => {
670
718
  if (!hintDiv) return;
671
- hintDiv.innerHTML = `The data starting at <strong>row ${headerRow}</strong> will be used as column headers. ` +
672
- `Rows before it will be skipped. The preview below shows the row index as the first column.`;
719
+ hintDiv.innerHTML = headerRow > 0
720
+ ? `Row <strong>${headerRow}</strong> will be used as column headers. ` +
721
+ `The <strong>${headerRow}</strong> row${headerRow > 1 ? 's' : ''} above will be skipped.`
722
+ : `Row <strong>0</strong> (first row) will be used as column headers.`;
673
723
  };
674
724
 
675
725
  const updatePreview = async () => {
676
726
  const headerRow = parseInt(headerRowInput?.value) || 0;
677
-
678
727
  updateHint(headerRow);
679
728
 
680
729
  try {
681
- const sheets = await this._driver.streamFileMultiSheet(this._rawFileData!.file, {
730
+ // First, get raw data (headerRow=0) to show skipped rows
731
+ const rawSheets = await this._driver.streamFileMultiSheet(this._rawFileData!.file, {
732
+ headerRow: 0,
733
+ maxSheetSize: headerRow + 15
734
+ });
735
+ const rawSheet = Object.values(rawSheets)[0];
736
+
737
+ // Then, get parsed data with the chosen header row
738
+ const parsedSheets = await this._driver.streamFileMultiSheet(this._rawFileData!.file, {
682
739
  headerRow,
683
- maxSheetSize: headerRow + 20
740
+ maxSheetSize: headerRow + 15
684
741
  });
742
+ const parsedSheet = Object.values(parsedSheets)[0];
685
743
 
686
- const firstSheet = Object.values(sheets)[0];
687
- if (!firstSheet) {
744
+ if (!rawSheet && !parsedSheet) {
688
745
  if (previewDiv) previewDiv.textContent = 'No data found';
689
746
  return;
690
747
  }
691
748
 
692
- const dataRows = firstSheet.toRows().slice(0, 10);
693
- const idxWidth = 6;
694
749
  const colWidth = 22;
750
+ const idxWidth = 6;
751
+ const lines: string[] = [];
752
+
753
+ // Show skipped rows (rows before the header)
754
+ if (rawSheet && headerRow > 0) {
755
+ lines.push('');
756
+ lines.push(` ┌${'─'.repeat(100)}┐`);
757
+ lines.push(` │ ROWS 0-${headerRow - 1} WILL BE SKIPPED (not imported)`.padEnd(101) + '│');
758
+ lines.push(` └${'─'.repeat(100)}┘`);
759
+ lines.push('');
760
+
761
+ const rawRows = rawSheet.toRows();
762
+ const rawCols = rawSheet.columns;
763
+
764
+ // Show raw header (row 0) - dimmed
765
+ lines.push(`${'[0]'.padEnd(idxWidth)}${rawCols.map(c => String(c).substring(0, colWidth - 2).padEnd(colWidth)).join('│ ')} ← skipped`);
766
+
767
+ const skippedCount = Math.min(headerRow - 1, rawRows.length);
768
+ for (let i = 0; i < skippedCount; i++) {
769
+ const row = rawRows[i];
770
+ const rowIdx = `[${i + 1}]`.padEnd(idxWidth);
771
+ const cols = Object.values(row).map(v => String(v ?? '').substring(0, colWidth - 2).padEnd(colWidth)).join('│ ');
772
+ lines.push(`${rowIdx}${cols} ← skipped`);
773
+ }
695
774
 
696
- const headerLine = 'Idx'.padEnd(idxWidth) + firstSheet.columns.map(c => String(c).substring(0, colWidth - 2).padEnd(colWidth)).join('| ');
697
- const separator = ''.repeat(Math.min(headerLine.length, 140));
775
+ lines.push('');
776
+ lines.push(` ╔${''.repeat(100)}╗`);
777
+ lines.push(` ║ ▼ DATA STARTS HERE (Row ${headerRow} = Column Headers)`.padEnd(101) + '║');
778
+ lines.push(` ╚${'═'.repeat(100)}╝`);
779
+ lines.push('');
780
+ }
698
781
 
699
- const preview = dataRows.map((row, i) => {
700
- const rowIdx = String(headerRow + 1 + i).padEnd(idxWidth);
701
- const cols = Object.values(row).map(v => String(v ?? '').substring(0, colWidth - 2).padEnd(colWidth)).join('| ');
702
- return `${rowIdx}${cols}`;
703
- }).join('\n');
782
+ // Show header row and data rows from parsed result
783
+ if (parsedSheet) {
784
+ const headerLine = `${'HDR'.padEnd(idxWidth)}${parsedSheet.columns.map(c => String(c).substring(0, colWidth - 2).padEnd(colWidth)).join(' ')} ◀ HEADERS`;
785
+ lines.push(headerLine);
786
+ lines.push(`${'───'.padEnd(idxWidth)}${'─'.repeat(Math.min(colWidth * parsedSheet.columns.length, 120))}`);
787
+
788
+ const dataRows = parsedSheet.toRows().slice(0, 8);
789
+ dataRows.forEach((row, i) => {
790
+ const rowIdx = `[${headerRow + 1 + i}]`.padEnd(idxWidth);
791
+ const cols = Object.values(row).map(v => String(v ?? '').substring(0, colWidth - 2).padEnd(colWidth)).join('│ ');
792
+ lines.push(`${rowIdx}${cols}`);
793
+ });
794
+ }
704
795
 
705
796
  if (previewDiv) {
706
- previewDiv.textContent = `${headerLine}\n${separator}\n${preview}`;
797
+ previewDiv.textContent = lines.join('\n');
707
798
  }
708
799
  } catch (err: any) {
709
800
  if (previewDiv) previewDiv.textContent = `Error: ${err.message}`;
@@ -819,9 +910,11 @@ export class DataFrameComponent extends BaseComponent<DataFrameState> {
819
910
  if (!hintDiv) return;
820
911
  const headerRow = parseInt(headerRowInput?.value) || 0;
821
912
  const skipRows = parseInt(skipRowsInput?.value) || 0;
822
- hintDiv.innerHTML = `Using <strong>row ${headerRow}</strong> as column headers` +
823
- (skipRows > 0 ? ` (skipping ${skipRows} rows before it)` : '') +
824
- `. The Idx column shows the file row index.`;
913
+ const totalSkipped = headerRow + skipRows;
914
+ hintDiv.innerHTML = totalSkipped > 0
915
+ ? `Row <strong>${headerRow + skipRows}</strong> will be used as column headers. ` +
916
+ `<strong>${totalSkipped}</strong> row${totalSkipped > 1 ? 's' : ''} above will be skipped.`
917
+ : `Row <strong>0</strong> (first row) will be used as column headers.`;
825
918
  };
826
919
 
827
920
  const updatePreview = () => {
@@ -834,29 +927,68 @@ export class DataFrameComponent extends BaseComponent<DataFrameState> {
834
927
  updateHint();
835
928
 
836
929
  try {
930
+ const colWidth = 22;
931
+ const idxWidth = 6;
932
+ const lines: string[] = [];
933
+ const totalOffset = headerRow + skipRows;
934
+
935
+ // Parse raw (no header offset) to show skipped rows
936
+ if (totalOffset > 0) {
937
+ const rawDf = this._driver.parseCSV(this._rawFileData.text, {
938
+ delimiter: delim,
939
+ headerRow: 0,
940
+ skipRows: 0,
941
+ hasHeader: true,
942
+ maxRows: totalOffset + 1
943
+ });
944
+
945
+ lines.push('');
946
+ lines.push(` ┌${'─'.repeat(100)}┐`);
947
+ lines.push(` │ ROWS 0-${totalOffset - 1} WILL BE SKIPPED (not imported)`.padEnd(101) + '│');
948
+ lines.push(` └${'─'.repeat(100)}┘`);
949
+ lines.push('');
950
+
951
+ const rawCols = rawDf.columns;
952
+ lines.push(`${'[0]'.padEnd(idxWidth)}${rawCols.map(c => String(c).substring(0, colWidth - 2).padEnd(colWidth)).join('│ ')} ← skipped`);
953
+
954
+ const rawRows = rawDf.toRows();
955
+ const skippedCount = Math.min(totalOffset - 1, rawRows.length);
956
+ for (let i = 0; i < skippedCount; i++) {
957
+ const row = rawRows[i];
958
+ const rowIdx = `[${i + 1}]`.padEnd(idxWidth);
959
+ const cols = Object.values(row).map(v => String(v ?? '').substring(0, colWidth - 2).padEnd(colWidth)).join('│ ');
960
+ lines.push(`${rowIdx}${cols} ← skipped`);
961
+ }
962
+
963
+ lines.push('');
964
+ lines.push(` ╔${'═'.repeat(100)}╗`);
965
+ lines.push(` ║ ▼ DATA STARTS HERE (Row ${totalOffset} = Column Headers)`.padEnd(101) + '║');
966
+ lines.push(` ╚${'═'.repeat(100)}╝`);
967
+ lines.push('');
968
+ }
969
+
970
+ // Parse with actual settings for header + data rows
837
971
  const df = this._driver.parseCSV(this._rawFileData.text, {
838
972
  delimiter: delim,
839
973
  headerRow,
840
974
  skipRows,
841
975
  hasHeader: true,
842
- maxRows: 10
976
+ maxRows: 8
843
977
  });
844
978
 
845
- const dataRows = df.toRows();
846
- const idxWidth = 6;
847
- const colWidth = 22;
848
-
849
- const headerLine = 'Idx'.padEnd(idxWidth) + df.columns.map(c => String(c).substring(0, colWidth - 2).padEnd(colWidth)).join('| ');
850
- const separator = '─'.repeat(Math.min(headerLine.length, 140));
979
+ const headerLine = `${'HDR'.padEnd(idxWidth)}${df.columns.map(c => String(c).substring(0, colWidth - 2).padEnd(colWidth)).join('│ ')} ◀ HEADERS`;
980
+ lines.push(headerLine);
981
+ lines.push(`${'───'.padEnd(idxWidth)}${'─'.repeat(Math.min(colWidth * df.columns.length, 120))}`);
851
982
 
852
- const preview = dataRows.map((row, i) => {
853
- const rowIdx = String(headerRow + skipRows + 1 + i).padEnd(idxWidth);
854
- const cols = Object.values(row).map(v => String(v ?? '').substring(0, colWidth - 2).padEnd(colWidth)).join('| ');
855
- return `${rowIdx}${cols}`;
856
- }).join('\n');
983
+ const dataRows = df.toRows();
984
+ dataRows.forEach((row, i) => {
985
+ const rowIdx = `[${totalOffset + 1 + i}]`.padEnd(idxWidth);
986
+ const cols = Object.values(row).map(v => String(v ?? '').substring(0, colWidth - 2).padEnd(colWidth)).join('│ ');
987
+ lines.push(`${rowIdx}${cols}`);
988
+ });
857
989
 
858
990
  if (previewDiv) {
859
- previewDiv.textContent = `${headerLine}\n${separator}\n${preview}`;
991
+ previewDiv.textContent = lines.join('\n');
860
992
  }
861
993
  } catch (err: any) {
862
994
  if (previewDiv) previewDiv.textContent = `Error: ${err.message}`;
@@ -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.193",
3
+ "version": "1.1.195",
4
4
  "type": "module",
5
5
  "description": "A JavaScript UX authorship platform",
6
6
  "main": "index.js",