juxscript 1.1.227 → 1.1.228

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.
@@ -30,10 +30,6 @@ export interface DataFrameOptions {
30
30
  class?: string;
31
31
  persistToIndexedDB?: boolean;
32
32
  clearStorageOnFileRemove?: boolean;
33
- // ✅ Collapsible options
34
- collapsible?: boolean;
35
- collapsed?: boolean;
36
- summaryTemplate?: (df: DataFrame) => string;
37
33
  }
38
34
 
39
35
  type DataFrameState = BaseState & {
@@ -78,11 +74,6 @@ export class DataFrameComponent extends BaseComponent<DataFrameState> {
78
74
  private _uploadAccept: string = '.csv,.tsv,.txt,.xlsx,.xls';
79
75
  private _uploadDescription: string = '';
80
76
  private _showUploadIcon: boolean = true;
81
- // ✅ Collapsible state
82
- private _collapsible: boolean = false;
83
- private _collapsed: boolean = false;
84
- private _summaryTemplate: ((df: DataFrame) => string) | null = null;
85
- private _detailsElement: HTMLDetailsElement | null = null;
86
77
  private _settingsModal: Modal | null = null;
87
78
 
88
79
  constructor(id: string, options: DataFrameOptions = {}) {
@@ -120,10 +111,6 @@ export class DataFrameComponent extends BaseComponent<DataFrameState> {
120
111
  this._showReshapeWarning = options.showReshapeWarning ?? true;
121
112
  this._persistToIndexedDB = options.persistToIndexedDB ?? false;
122
113
  this._clearStorageOnFileRemove = options.clearStorageOnFileRemove ?? true;
123
- // ✅ Collapsible options
124
- this._collapsible = options.collapsible ?? false;
125
- this._collapsed = options.collapsed ?? false;
126
- this._summaryTemplate = options.summaryTemplate ?? null;
127
114
  }
128
115
 
129
116
  protected getTriggerEvents(): readonly string[] { return TRIGGER_EVENTS; }
@@ -197,18 +184,12 @@ export class DataFrameComponent extends BaseComponent<DataFrameState> {
197
184
  return this;
198
185
  }
199
186
 
200
- /**
201
- * Set upload button label
202
- */
203
187
  uploadLabel(label: string): this {
204
188
  this._uploadButtonLabel = label;
205
189
  if (this._inlineUpload) this._inlineUpload.label = label;
206
190
  return this;
207
191
  }
208
192
 
209
- /**
210
- * Set upload button icon
211
- */
212
193
  uploadIcon(icon: string): this {
213
194
  this._uploadButtonIcon = icon;
214
195
  this._showUploadIcon = !!icon;
@@ -216,54 +197,36 @@ export class DataFrameComponent extends BaseComponent<DataFrameState> {
216
197
  return this;
217
198
  }
218
199
 
219
- /**
220
- * Set upload button variant (outline, primary, ghost, etc.)
221
- */
222
200
  uploadVariant(variant: string): this {
223
201
  this._uploadButtonVariant = variant;
224
202
  return this;
225
203
  }
226
204
 
227
- /**
228
- * Set accepted file types
229
- */
230
205
  uploadAccept(accept: string): this {
231
206
  this._uploadAccept = accept;
232
207
  if (this._inlineUpload) this._inlineUpload.accept = accept;
233
208
  return this;
234
209
  }
235
210
 
236
- /**
237
- * Set description text below the upload button
238
- */
239
211
  uploadDescription(description: string): this {
240
212
  this._uploadDescription = description;
241
213
  return this;
242
214
  }
243
215
 
244
- /**
245
- * Show/hide upload icon
246
- */
247
216
  showUploadIcon(show: boolean): this {
248
217
  this._showUploadIcon = show;
249
218
  return this;
250
219
  }
251
220
 
252
221
  /* ═══════════════════════════════════════════════════
253
- * STORAGE OPTIONS (fluent API)
222
+ * STORAGE OPTIONS
254
223
  * ═══════════════════════════════════════════════════ */
255
224
 
256
- /**
257
- * Enable/disable IndexedDB persistence (default: false = session only)
258
- */
259
225
  persistToIndexedDB(enabled: boolean): this {
260
226
  this._persistToIndexedDB = enabled;
261
227
  return this;
262
228
  }
263
229
 
264
- /**
265
- * Clear stored data when file is removed (default: true)
266
- */
267
230
  clearStorageOnFileRemove(enabled: boolean): this {
268
231
  this._clearStorageOnFileRemove = enabled;
269
232
  return this;
@@ -273,11 +236,7 @@ export class DataFrameComponent extends BaseComponent<DataFrameState> {
273
236
  * CLEAR / RESET
274
237
  * ═══════════════════════════════════════════════════ */
275
238
 
276
- /**
277
- * Clear the current data and reset the table
278
- */
279
239
  async clear(): Promise<this> {
280
- // Clear storage if enabled
281
240
  if (this._clearStorageOnFileRemove && this._persistToIndexedDB && this._rawFileData?.file) {
282
241
  try {
283
242
  const tables = await this._driver.list();
@@ -290,7 +249,6 @@ export class DataFrameComponent extends BaseComponent<DataFrameState> {
290
249
  }
291
250
  }
292
251
 
293
- // Reset state
294
252
  this._df = null;
295
253
  this._rawFileData = null;
296
254
  this._sheets.clear();
@@ -299,27 +257,18 @@ export class DataFrameComponent extends BaseComponent<DataFrameState> {
299
257
  this.state.rowCount = 0;
300
258
  this.state.colCount = 0;
301
259
 
302
- // Clear table display
303
260
  if (this._table) {
304
261
  this._table.columns([]).rows([]);
305
262
  }
306
263
 
307
- // Remove tabs if present
308
264
  const wrapper = document.getElementById(this._id);
309
265
  if (wrapper) {
310
266
  const existingTabs = wrapper.querySelector('.jux-tabs');
311
267
  if (existingTabs) existingTabs.remove();
312
268
  }
313
269
 
314
- // ✅ Hide data view, show upload
315
270
  this._hideDataView();
316
271
 
317
- // ✅ Hide collapsible details
318
- if (this._collapsible && this._detailsElement) {
319
- this._detailsElement.style.display = 'none';
320
- }
321
-
322
- // ✅ Clear file from upload component
323
272
  if (this._uploadRef) {
324
273
  this._uploadRef.clear();
325
274
  }
@@ -394,7 +343,7 @@ export class DataFrameComponent extends BaseComponent<DataFrameState> {
394
343
  }
395
344
 
396
345
  /* ═══════════════════════════════════════════════════
397
- * TABLE OPTIONS (fluent, pre-render)
346
+ * TABLE OPTIONS
398
347
  * ═══════════════════════════════════════════════════ */
399
348
 
400
349
  striped(v: boolean): this { this._tableOptions.striped = v; return this; }
@@ -408,85 +357,7 @@ export class DataFrameComponent extends BaseComponent<DataFrameState> {
408
357
  maxFileSize(mb: number): this { this._maxFileSize = mb; return this; }
409
358
 
410
359
  /* ═══════════════════════════════════════════════════
411
- * COLLAPSIBLE OPTIONS (fluent API)
412
- * ═══════════════════════════════════════════════════ */
413
-
414
- /**
415
- * Enable/disable collapsible mode
416
- */
417
- collapsible(enabled: boolean): this {
418
- this._collapsible = enabled;
419
- return this;
420
- }
421
-
422
- /**
423
- * Set initial collapsed state (true = collapsed, false = expanded)
424
- */
425
- collapsed(value: boolean): this {
426
- this._collapsed = value;
427
- if (this._detailsElement) {
428
- this._detailsElement.open = !value;
429
- }
430
- return this;
431
- }
432
-
433
- /**
434
- * Expand the details (show table)
435
- */
436
- expand(): this {
437
- this._collapsed = false;
438
- if (this._detailsElement) {
439
- this._detailsElement.open = true;
440
- }
441
- return this;
442
- }
443
-
444
- /**
445
- * Collapse the details (hide table)
446
- */
447
- collapse(): this {
448
- this._collapsed = true;
449
- if (this._detailsElement) {
450
- this._detailsElement.open = false;
451
- }
452
- return this;
453
- }
454
-
455
- /**
456
- * Check if collapsible mode is enabled
457
- */
458
- isCollapsible(): boolean {
459
- return this._collapsible;
460
- }
461
-
462
- /**
463
- * Toggle collapsed state
464
- */
465
- toggle(): this {
466
- this._collapsed = !this._collapsed;
467
- if (this._detailsElement) {
468
- this._detailsElement.open = !this._collapsed;
469
- }
470
- return this;
471
- }
472
-
473
- /**
474
- * Set custom summary template
475
- */
476
- summaryTemplate(fn: (df: DataFrame) => string): this {
477
- this._summaryTemplate = fn;
478
- return this;
479
- }
480
-
481
- /**
482
- * Get current collapsed state
483
- */
484
- isCollapsed(): boolean {
485
- return this._collapsed;
486
- }
487
-
488
- /* ═══════════════════════════════════════════════════
489
- * FILE HANDLING (modified to use storage options)
360
+ * FILE HANDLING
490
361
  * ═══════════════════════════════════════════════════ */
491
362
 
492
363
  private async _handleFile(file: File): Promise<void> {
@@ -523,7 +394,6 @@ export class DataFrameComponent extends BaseComponent<DataFrameState> {
523
394
  return;
524
395
  }
525
396
 
526
- // ✅ Only store if persistence is enabled
527
397
  if (this._persistToIndexedDB) {
528
398
  await this._driver.store(file.name, sheets[sheetNames[0]], { source: file.name });
529
399
  }
@@ -542,7 +412,6 @@ export class DataFrameComponent extends BaseComponent<DataFrameState> {
542
412
  hasHeader: true
543
413
  });
544
414
 
545
- // ✅ Only store if persistence is enabled
546
415
  if (this._persistToIndexedDB) {
547
416
  await this._driver.store(file.name, df, { source: file.name });
548
417
  }
@@ -578,8 +447,6 @@ export class DataFrameComponent extends BaseComponent<DataFrameState> {
578
447
  });
579
448
 
580
449
  const sheetNames = Object.keys(sheets);
581
-
582
- // Sanitize sheet names for use as DOM IDs
583
450
  const sanitizeId = (name: string) => name.replace(/[^a-zA-Z0-9_-]/g, '_');
584
451
 
585
452
  const tabDefs = sheetNames.map(name => ({
@@ -593,7 +460,6 @@ export class DataFrameComponent extends BaseComponent<DataFrameState> {
593
460
  activeTab: sanitizeId(sheetNames[0])
594
461
  });
595
462
 
596
- // Map sanitized IDs back to original sheet names
597
463
  const idToSheetName = new Map<string, string>();
598
464
  sheetNames.forEach(name => idToSheetName.set(sanitizeId(name), name));
599
465
 
@@ -602,9 +468,19 @@ export class DataFrameComponent extends BaseComponent<DataFrameState> {
602
468
  this._df = this._sheets.get(originalName) || null;
603
469
  });
604
470
 
471
+ const dataContainer = wrapper.querySelector('.jux-dataframe-data') as HTMLElement;
472
+ if (dataContainer) {
473
+ dataContainer.style.display = '';
474
+ }
475
+
605
476
  const tabsContainer = document.createElement('div');
606
477
  tabsContainer.className = 'jux-dataframe-tabs';
607
- wrapper.appendChild(tabsContainer);
478
+
479
+ if (dataContainer) {
480
+ dataContainer.appendChild(tabsContainer);
481
+ } else {
482
+ wrapper.appendChild(tabsContainer);
483
+ }
608
484
 
609
485
  this._tabs.render(tabsContainer);
610
486
 
@@ -612,13 +488,6 @@ export class DataFrameComponent extends BaseComponent<DataFrameState> {
612
488
  const df = sheets[sheetName];
613
489
  const safeId = sanitizeId(sheetName);
614
490
 
615
- // ✅ DEBUG: Log table options
616
- console.log(`[DataFrame] Creating table for sheet "${sheetName}" with options:`, {
617
- filterable: this._tableOptions.filterable,
618
- paginated: this._tableOptions.paginated,
619
- sortable: this._tableOptions.sortable
620
- });
621
-
622
491
  const table = new Table(`${this._id}-table-${safeId}`, {
623
492
  striped: this._tableOptions.striped,
624
493
  hoverable: this._tableOptions.hoverable,
@@ -647,6 +516,8 @@ export class DataFrameComponent extends BaseComponent<DataFrameState> {
647
516
  'success'
648
517
  );
649
518
 
519
+ this._showDataView();
520
+
650
521
  this._df = sheets[sheetNames[0]];
651
522
  this._triggerCallback('load', this._df, null, this);
652
523
  }
@@ -659,7 +530,6 @@ export class DataFrameComponent extends BaseComponent<DataFrameState> {
659
530
  const el = document.getElementById(`${this._id}-status`);
660
531
  if (!el) return;
661
532
 
662
- // ✅ Only show status during loading/error states
663
533
  if (type === 'loading' || type === 'error') {
664
534
  el.style.display = '';
665
535
  el.className = 'jux-dataframe-status';
@@ -679,7 +549,6 @@ export class DataFrameComponent extends BaseComponent<DataFrameState> {
679
549
  span.textContent = text;
680
550
  el.appendChild(span);
681
551
  } else {
682
- // ✅ Hide status after successful load - table speaks for itself
683
552
  el.style.display = 'none';
684
553
  }
685
554
  }
@@ -692,7 +561,6 @@ export class DataFrameComponent extends BaseComponent<DataFrameState> {
692
561
  this.state.rowCount = df.height;
693
562
  this.state.colCount = df.width;
694
563
 
695
- // Only strip __EMPTY columns that are ENTIRELY null/empty
696
564
  const cols = df.columns;
697
565
  const rows = df.toRows();
698
566
  const emptyColumns = cols.filter(c => {
@@ -715,67 +583,43 @@ export class DataFrameComponent extends BaseComponent<DataFrameState> {
715
583
  this._table.columns(columnDefs).rows(this._df.toRows());
716
584
  }
717
585
 
718
- // ✅ Show the table container, hide upload area
719
586
  this._showDataView();
720
-
721
- // ✅ Update collapsible summary if enabled
722
- if (this._collapsible && this._detailsElement) {
723
- this._detailsElement.style.display = '';
724
- this._updateSummary();
725
- }
726
-
727
- // ✅ Hide status - data is loaded
728
587
  this._updateStatus('', 'success');
729
-
730
588
  this._triggerCallback('load', this._df, null, this);
731
589
  }
732
590
 
733
- /**
734
- * Show the data view (table + settings gear), hide upload area
735
- */
736
591
  private _showDataView(): void {
737
592
  const wrapper = document.getElementById(this._id);
738
593
  if (!wrapper) return;
739
594
 
740
- // Hide upload area
741
595
  const uploadArea = wrapper.querySelector('.jux-dataframe-upload-area') as HTMLElement;
742
596
  if (uploadArea) {
743
597
  uploadArea.style.display = 'none';
744
598
  }
745
599
 
746
- // Show data container (table + toolbar)
747
600
  const dataContainer = wrapper.querySelector('.jux-dataframe-data') as HTMLElement;
748
601
  if (dataContainer) {
749
602
  dataContainer.style.display = '';
750
603
  }
751
604
 
752
- // Update settings gear with file info
753
605
  this._updateSettingsGear();
754
606
  }
755
607
 
756
- /**
757
- * Hide data view, show upload area
758
- */
759
608
  private _hideDataView(): void {
760
609
  const wrapper = document.getElementById(this._id);
761
610
  if (!wrapper) return;
762
611
 
763
- // Show upload area
764
612
  const uploadArea = wrapper.querySelector('.jux-dataframe-upload-area') as HTMLElement;
765
613
  if (uploadArea) {
766
614
  uploadArea.style.display = '';
767
615
  }
768
616
 
769
- // Hide data container
770
617
  const dataContainer = wrapper.querySelector('.jux-dataframe-data') as HTMLElement;
771
618
  if (dataContainer) {
772
619
  dataContainer.style.display = 'none';
773
620
  }
774
621
  }
775
622
 
776
- /**
777
- * Update the settings gear tooltip/info
778
- */
779
623
  private _updateSettingsGear(): void {
780
624
  const gear = document.getElementById(`${this._id}-settings-gear`);
781
625
  if (!gear || !this._df) return;
@@ -791,9 +635,6 @@ export class DataFrameComponent extends BaseComponent<DataFrameState> {
791
635
  }
792
636
  }
793
637
 
794
- /**
795
- * Show the unified settings modal
796
- */
797
638
  private _showSettingsModal(): void {
798
639
  this._cleanupReshapeModal();
799
640
 
@@ -812,7 +653,6 @@ export class DataFrameComponent extends BaseComponent<DataFrameState> {
812
653
 
813
654
  let contentHTML = `
814
655
  <div class="jux-dataframe-settings-content">
815
- <!-- File Info Section -->
816
656
  <div class="jux-dataframe-settings-section">
817
657
  <div class="jux-dataframe-settings-label">Source</div>
818
658
  <div class="jux-dataframe-settings-value">
@@ -820,8 +660,6 @@ export class DataFrameComponent extends BaseComponent<DataFrameState> {
820
660
  ${fileInfo ? `<span class="jux-muted" style="margin-left: 8px;">${fileSizeKB} KB</span>` : ''}
821
661
  </div>
822
662
  </div>
823
-
824
- <!-- Data Info Section -->
825
663
  <div class="jux-dataframe-settings-section">
826
664
  <div class="jux-dataframe-settings-label">Data</div>
827
665
  <div class="jux-dataframe-settings-value">
@@ -831,7 +669,6 @@ export class DataFrameComponent extends BaseComponent<DataFrameState> {
831
669
  </div>
832
670
  `;
833
671
 
834
- // Import Settings button (only if we have raw file data)
835
672
  if (this._rawFileData) {
836
673
  contentHTML += `
837
674
  <div class="jux-dataframe-settings-section">
@@ -845,9 +682,7 @@ export class DataFrameComponent extends BaseComponent<DataFrameState> {
845
682
  `;
846
683
  }
847
684
 
848
- contentHTML += `
849
- </div>
850
- `;
685
+ contentHTML += `</div>`;
851
686
 
852
687
  this._settingsModal
853
688
  .content(contentHTML)
@@ -870,7 +705,6 @@ export class DataFrameComponent extends BaseComponent<DataFrameState> {
870
705
  this._settingsModal.render(document.body);
871
706
  this._settingsModal.open();
872
707
 
873
- // Wire up import settings button
874
708
  requestAnimationFrame(() => {
875
709
  const adjustBtn = document.getElementById(`${this._id}-adjust-import`);
876
710
  if (adjustBtn) {
@@ -882,82 +716,10 @@ export class DataFrameComponent extends BaseComponent<DataFrameState> {
882
716
  });
883
717
  }
884
718
 
885
- /**
886
- * Update the collapsible summary text
887
- */
888
- private _updateSummary(isMalformed?: boolean): void {
889
- if (!this._collapsible || !this._detailsElement) return;
890
-
891
- const summaryEl = this._detailsElement.querySelector('.jux-dataframe-summary');
892
- if (!summaryEl) return;
893
-
894
- const malformed = isMalformed ?? (this._df ? this._detectMalformedData(this._df) : false);
895
-
896
- const summaryTextEl = summaryEl.querySelector('.jux-dataframe-summary-text');
897
- if (summaryTextEl) {
898
- if (!this._df) {
899
- summaryTextEl.textContent = 'No data loaded';
900
- } else if (this._summaryTemplate) {
901
- summaryTextEl.textContent = this._summaryTemplate(this._df);
902
- } else {
903
- const suffix = malformed ? ' ⚠️' : '';
904
- summaryTextEl.textContent = `${this.state.sourceName || 'Data'} — ${this._df.height} rows × ${this._df.width} cols${suffix}`;
905
- }
906
- }
907
-
908
- // ✅ Add/update settings gear in summary
909
- let gearBtn = summaryEl.querySelector('.jux-dataframe-summary-gear') as HTMLButtonElement;
910
-
911
- if (this._df) {
912
- if (!gearBtn) {
913
- gearBtn = document.createElement('button');
914
- gearBtn.className = 'jux-dataframe-summary-gear';
915
- gearBtn.type = 'button';
916
- gearBtn.innerHTML = '⚙️';
917
- gearBtn.addEventListener('click', (e) => {
918
- e.stopPropagation();
919
- this._showSettingsModal();
920
- });
921
- summaryEl.appendChild(gearBtn);
922
- }
923
-
924
- if (malformed) {
925
- gearBtn.classList.add('jux-dataframe-gear-warning');
926
- } else {
927
- gearBtn.classList.remove('jux-dataframe-gear-warning');
928
- }
929
- } else if (gearBtn) {
930
- gearBtn.remove();
931
- }
932
- }
933
-
934
719
  /* ═══════════════════════════════════════════════════
935
720
  * MALFORMED DATA DETECTION
936
721
  * ═══════════════════════════════════════════════════ */
937
722
 
938
- private _appendSettingsButton(label: string, variant: string): void {
939
- const statusEl = document.getElementById(`${this._id}-status`);
940
- if (!statusEl) return;
941
-
942
- // Remove existing settings button if any
943
- const existing = statusEl.querySelector('.jux-dataframe-settings-btn');
944
- if (existing) existing.remove();
945
-
946
- const btn = document.createElement('button');
947
- btn.className = `jux-dataframe-settings-btn jux-dataframe-settings-btn-${variant}`;
948
- btn.type = 'button';
949
- btn.textContent = `⚙️ ${label}`;
950
- btn.style.marginLeft = '8px';
951
- btn.style.cursor = 'pointer';
952
- btn.style.fontSize = '0.8rem';
953
- btn.style.padding = '2px 8px';
954
- btn.style.borderRadius = 'var(--radius, 4px)';
955
- btn.style.border = '1px solid hsl(var(--border))';
956
- btn.style.background = variant === 'warning' ? 'hsl(38 92% 50% / 0.15)' : 'transparent';
957
- btn.addEventListener('click', () => this._showReshapeModal());
958
- statusEl.appendChild(btn);
959
- }
960
-
961
723
  private _detectMalformedData(df: DataFrame): boolean {
962
724
  const columns = df.columns;
963
725
  const rows = df.toRows();
@@ -1016,11 +778,6 @@ export class DataFrameComponent extends BaseComponent<DataFrameState> {
1016
778
  return div.innerHTML;
1017
779
  }
1018
780
 
1019
- /**
1020
- * Build a clickable preview table from raw row data.
1021
- * Each row stores its actual sheet row index via data-sheet-row attribute.
1022
- * Returns the table HTML string.
1023
- */
1024
781
  private _buildClickablePreviewHTML(
1025
782
  rawRows: { sheetRow: number; values: any[] }[],
1026
783
  selectedSheetRow: number
@@ -1041,7 +798,6 @@ export class DataFrameComponent extends BaseComponent<DataFrameState> {
1041
798
 
1042
799
  html += `<tr data-sheet-row="${sheetRow}" style="${rowStyle}" onmouseover="this.style.outline='2px solid hsl(142 71% 45% / 0.5)'" onmouseout="this.style.outline=''">`;
1043
800
 
1044
- // Row index cell
1045
801
  html += `<td style="padding: 8px 12px; width: 60px; font-weight: 600; color: hsl(var(--muted-foreground)); border-right: 1px solid hsl(var(--border)); text-align: center; user-select: none;">`;
1046
802
  if (isHeader) {
1047
803
  html += `<span style="color: hsl(142 71% 45%);">▶ ${sheetRow}</span>`;
@@ -1050,7 +806,6 @@ export class DataFrameComponent extends BaseComponent<DataFrameState> {
1050
806
  }
1051
807
  html += '</td>';
1052
808
 
1053
- // Show first 6 columns
1054
809
  const displayCols = values.slice(0, 6);
1055
810
  displayCols.forEach(val => {
1056
811
  const displayVal = val != null ? String(val).substring(0, 20) : '';
@@ -1064,7 +819,6 @@ export class DataFrameComponent extends BaseComponent<DataFrameState> {
1064
819
  html += `<td style="padding: 8px 12px; color: hsl(var(--muted-foreground));">…</td>`;
1065
820
  }
1066
821
 
1067
- // Status badge
1068
822
  html += `<td style="padding: 8px 12px; text-align: right; white-space: nowrap; user-select: none;">`;
1069
823
  if (isHeader) {
1070
824
  html += '<span style="background: hsl(142 71% 45%); color: white; padding: 3px 8px; border-radius: 4px; font-size: 10px; font-weight: 600;">HEADER</span>';
@@ -1085,23 +839,17 @@ export class DataFrameComponent extends BaseComponent<DataFrameState> {
1085
839
 
1086
840
  this._cleanupReshapeModal();
1087
841
 
1088
- // ✅ Use the SAME cell-reading method as the parser
1089
842
  const rawRows = await this._driver.readRawExcelRows(this._rawFileData.file, 15);
1090
843
 
1091
844
  if (rawRows.length === 0) return;
1092
845
 
1093
- // Log what we got so we can verify alignment
1094
- console.log('[DataFrame Preview] Raw rows from readRawExcelRows:');
1095
- rawRows.forEach(r => console.log(` sheetRow ${r.sheetRow}:`, r.values.slice(0, 5)));
1096
-
1097
- // Auto-detect best header row
1098
846
  let selectedSheetRow = rawRows[0].sheetRow;
1099
847
  for (const { sheetRow, values } of rawRows) {
1100
848
  const nonEmpty = values.filter(v => v !== null && v !== undefined && String(v).trim() !== '');
1101
849
  if (nonEmpty.length < values.length * 0.5) continue;
1102
850
  const nonNumeric = nonEmpty.filter(v => {
1103
- const trimmed = v.trim();
1104
- return isNaN(Number(trimmed)) && trimmed !== '';
851
+ const str = String(v).trim();
852
+ return isNaN(Number(str)) && str !== '';
1105
853
  }).length;
1106
854
  if (nonNumeric >= nonEmpty.length * 0.7) {
1107
855
  selectedSheetRow = sheetRow;
@@ -1142,8 +890,6 @@ export class DataFrameComponent extends BaseComponent<DataFrameState> {
1142
890
  const input = document.getElementById(`${this._id}-header-row`) as HTMLInputElement;
1143
891
  const headerRow = parseInt(input.value) || 0;
1144
892
 
1145
- console.log(`[DataFrame] Apply clicked: headerRow=${headerRow}`);
1146
-
1147
893
  this.state.loading = true;
1148
894
  this._updateStatus('Re-parsing with new settings...', 'loading');
1149
895
 
@@ -1208,7 +954,6 @@ export class DataFrameComponent extends BaseComponent<DataFrameState> {
1208
954
  tr.addEventListener('click', () => {
1209
955
  const rowIdx = parseInt((tr as HTMLElement).dataset.sheetRow!);
1210
956
  hiddenInput.value = String(rowIdx);
1211
- console.log(`[DataFrame Preview] Clicked sheetRow=${rowIdx}`);
1212
957
  updateHint(rowIdx);
1213
958
  renderPreview(rowIdx);
1214
959
  });
@@ -1228,7 +973,6 @@ export class DataFrameComponent extends BaseComponent<DataFrameState> {
1228
973
  const text = this._rawFileData.text;
1229
974
  const detected = (this._driver as any)._detectDelimiter(text);
1230
975
 
1231
- // Parse raw lines
1232
976
  const lines = text.replace(/\r\n/g, '\n').replace(/\r/g, '\n').split('\n');
1233
977
  const rawRows: { sheetRow: number; values: any[] }[] = [];
1234
978
  const maxPreviewRows = Math.min(lines.length, 15);
@@ -1242,7 +986,6 @@ export class DataFrameComponent extends BaseComponent<DataFrameState> {
1242
986
  rawRows.push({ sheetRow: i, values });
1243
987
  }
1244
988
 
1245
- // Auto-detect header row
1246
989
  let selectedRow = 0;
1247
990
  for (const { sheetRow, values } of rawRows) {
1248
991
  const nonEmpty = values.filter((v: string) => v.trim() !== '');
@@ -1367,7 +1110,6 @@ export class DataFrameComponent extends BaseComponent<DataFrameState> {
1367
1110
  tr.addEventListener('click', () => {
1368
1111
  const rowIdx = parseInt((tr as HTMLElement).dataset.sheetRow!);
1369
1112
  hiddenInput.value = String(rowIdx);
1370
- console.log(`[DataFrame Preview] Clicked sheetRow=${rowIdx}`);
1371
1113
  updateHint(rowIdx);
1372
1114
  renderPreview(rowIdx);
1373
1115
  });
@@ -1390,7 +1132,7 @@ export class DataFrameComponent extends BaseComponent<DataFrameState> {
1390
1132
  }
1391
1133
 
1392
1134
  /* ═══════════════════════════════════════════════════
1393
- * UPDATE & RENDER (modified for better upload styling)
1135
+ * UPDATE & RENDER
1394
1136
  * ═══════════════════════════════════════════════════ */
1395
1137
 
1396
1138
  update(_prop: string, _value: any): void { }
@@ -1405,9 +1147,7 @@ export class DataFrameComponent extends BaseComponent<DataFrameState> {
1405
1147
  if (className) wrapper.className += ` ${className}`;
1406
1148
  if (style) wrapper.setAttribute('style', style);
1407
1149
 
1408
- // ═══════════════════════════════════════════════════
1409
- // UPLOAD AREA (shown initially, hidden after data loads)
1410
- // ═══════════════════════════════════════════════════
1150
+ // Upload area
1411
1151
  if (this._inlineUpload) {
1412
1152
  const uploadArea = document.createElement('div');
1413
1153
  uploadArea.className = 'jux-dataframe-upload-area';
@@ -1440,7 +1180,6 @@ export class DataFrameComponent extends BaseComponent<DataFrameState> {
1440
1180
  uploadContainer.id = `${this._id}-upload-container`;
1441
1181
  uploadArea.appendChild(uploadContainer);
1442
1182
 
1443
- // Status bar (for loading/error states only)
1444
1183
  if (this._showStatus) {
1445
1184
  const statusBar = document.createElement('div');
1446
1185
  statusBar.className = 'jux-dataframe-status';
@@ -1463,14 +1202,11 @@ export class DataFrameComponent extends BaseComponent<DataFrameState> {
1463
1202
  container.appendChild(wrapper);
1464
1203
  }
1465
1204
 
1466
- // ═══════════════════════════════════════════════════
1467
- // DATA CONTAINER (hidden initially, shown after data loads)
1468
- // ═══════════════════════════════════════════════════
1205
+ // Data container
1469
1206
  const dataContainer = document.createElement('div');
1470
1207
  dataContainer.className = 'jux-dataframe-data';
1471
- dataContainer.style.display = 'none'; // Hidden until data loads
1208
+ dataContainer.style.display = 'none';
1472
1209
 
1473
- // ✅ Toolbar with settings gear
1474
1210
  const toolbar = document.createElement('div');
1475
1211
  toolbar.className = 'jux-dataframe-toolbar';
1476
1212
 
@@ -1484,52 +1220,9 @@ export class DataFrameComponent extends BaseComponent<DataFrameState> {
1484
1220
  toolbar.appendChild(settingsGear);
1485
1221
 
1486
1222
  dataContainer.appendChild(toolbar);
1487
-
1488
- // ═══════════════════════════════════════════════════
1489
- // TABLE CONTAINER (inside data container or collapsible)
1490
- // ═══════════════════════════════════════════════════
1491
- let tableContainer: HTMLElement;
1492
-
1493
- if (this._collapsible) {
1494
- const details = document.createElement('details');
1495
- details.className = 'jux-dataframe-details';
1496
- details.open = !this._collapsed;
1497
- details.style.display = 'none'; // Hidden until data loads
1498
- this._detailsElement = details;
1499
-
1500
- const summary = document.createElement('summary');
1501
- summary.className = 'jux-dataframe-summary';
1502
-
1503
- const chevron = document.createElement('span');
1504
- chevron.className = 'jux-dataframe-summary-chevron';
1505
- chevron.innerHTML = '▶';
1506
- summary.appendChild(chevron);
1507
-
1508
- const textSpan = document.createElement('span');
1509
- textSpan.className = 'jux-dataframe-summary-text';
1510
- summary.appendChild(textSpan);
1511
-
1512
- details.appendChild(summary);
1513
-
1514
- const content = document.createElement('div');
1515
- content.className = 'jux-dataframe-details-content';
1516
- details.appendChild(content);
1517
-
1518
- dataContainer.appendChild(details);
1519
- tableContainer = content;
1520
-
1521
- details.addEventListener('toggle', () => {
1522
- this._collapsed = !details.open;
1523
- });
1524
- } else {
1525
- tableContainer = dataContainer;
1526
- }
1527
-
1528
1223
  wrapper.appendChild(dataContainer);
1529
1224
 
1530
- // ═══════════════════════════════════════════════════
1531
- // TABLE
1532
- // ═══════════════════════════════════════════════════
1225
+ // Table
1533
1226
  const tbl = new Table(`${this._id}-table`, {
1534
1227
  striped: this._tableOptions.striped,
1535
1228
  hoverable: this._tableOptions.hoverable,
@@ -1538,7 +1231,7 @@ export class DataFrameComponent extends BaseComponent<DataFrameState> {
1538
1231
  paginated: this._tableOptions.paginated,
1539
1232
  rowsPerPage: this._tableOptions.rowsPerPage
1540
1233
  });
1541
- tbl.render(tableContainer);
1234
+ tbl.render(dataContainer);
1542
1235
  this._table = tbl;
1543
1236
 
1544
1237
  if (this._pendingSource) {