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.
@@ -47,11 +47,6 @@ export class DataFrameComponent extends BaseComponent {
47
47
  this._uploadAccept = '.csv,.tsv,.txt,.xlsx,.xls';
48
48
  this._uploadDescription = '';
49
49
  this._showUploadIcon = true;
50
- // ✅ Collapsible state
51
- this._collapsible = false;
52
- this._collapsed = false;
53
- this._summaryTemplate = null;
54
- this._detailsElement = null;
55
50
  this._settingsModal = null;
56
51
  this._driver = new TabularDriver(options.dbName ?? 'jux-dataframes', options.storeName ?? 'frames');
57
52
  this._showStatus = options.showStatus ?? true;
@@ -70,10 +65,6 @@ export class DataFrameComponent extends BaseComponent {
70
65
  this._showReshapeWarning = options.showReshapeWarning ?? true;
71
66
  this._persistToIndexedDB = options.persistToIndexedDB ?? false;
72
67
  this._clearStorageOnFileRemove = options.clearStorageOnFileRemove ?? true;
73
- // ✅ Collapsible options
74
- this._collapsible = options.collapsible ?? false;
75
- this._collapsed = options.collapsed ?? false;
76
- this._summaryTemplate = options.summaryTemplate ?? null;
77
68
  }
78
69
  getTriggerEvents() { return TRIGGER_EVENTS; }
79
70
  getCallbackEvents() { return CALLBACK_EVENTS; }
@@ -149,18 +140,12 @@ export class DataFrameComponent extends BaseComponent {
149
140
  this._uploadButtonIcon = icon;
150
141
  return this;
151
142
  }
152
- /**
153
- * Set upload button label
154
- */
155
143
  uploadLabel(label) {
156
144
  this._uploadButtonLabel = label;
157
145
  if (this._inlineUpload)
158
146
  this._inlineUpload.label = label;
159
147
  return this;
160
148
  }
161
- /**
162
- * Set upload button icon
163
- */
164
149
  uploadIcon(icon) {
165
150
  this._uploadButtonIcon = icon;
166
151
  this._showUploadIcon = !!icon;
@@ -168,49 +153,31 @@ export class DataFrameComponent extends BaseComponent {
168
153
  this._inlineUpload.icon = icon;
169
154
  return this;
170
155
  }
171
- /**
172
- * Set upload button variant (outline, primary, ghost, etc.)
173
- */
174
156
  uploadVariant(variant) {
175
157
  this._uploadButtonVariant = variant;
176
158
  return this;
177
159
  }
178
- /**
179
- * Set accepted file types
180
- */
181
160
  uploadAccept(accept) {
182
161
  this._uploadAccept = accept;
183
162
  if (this._inlineUpload)
184
163
  this._inlineUpload.accept = accept;
185
164
  return this;
186
165
  }
187
- /**
188
- * Set description text below the upload button
189
- */
190
166
  uploadDescription(description) {
191
167
  this._uploadDescription = description;
192
168
  return this;
193
169
  }
194
- /**
195
- * Show/hide upload icon
196
- */
197
170
  showUploadIcon(show) {
198
171
  this._showUploadIcon = show;
199
172
  return this;
200
173
  }
201
174
  /* ═══════════════════════════════════════════════════
202
- * STORAGE OPTIONS (fluent API)
175
+ * STORAGE OPTIONS
203
176
  * ═══════════════════════════════════════════════════ */
204
- /**
205
- * Enable/disable IndexedDB persistence (default: false = session only)
206
- */
207
177
  persistToIndexedDB(enabled) {
208
178
  this._persistToIndexedDB = enabled;
209
179
  return this;
210
180
  }
211
- /**
212
- * Clear stored data when file is removed (default: true)
213
- */
214
181
  clearStorageOnFileRemove(enabled) {
215
182
  this._clearStorageOnFileRemove = enabled;
216
183
  return this;
@@ -218,11 +185,7 @@ export class DataFrameComponent extends BaseComponent {
218
185
  /* ═══════════════════════════════════════════════════
219
186
  * CLEAR / RESET
220
187
  * ═══════════════════════════════════════════════════ */
221
- /**
222
- * Clear the current data and reset the table
223
- */
224
188
  async clear() {
225
- // Clear storage if enabled
226
189
  if (this._clearStorageOnFileRemove && this._persistToIndexedDB && this._rawFileData?.file) {
227
190
  try {
228
191
  const tables = await this._driver.list();
@@ -235,7 +198,6 @@ export class DataFrameComponent extends BaseComponent {
235
198
  console.warn('[DataFrame] Failed to clear storage:', err);
236
199
  }
237
200
  }
238
- // Reset state
239
201
  this._df = null;
240
202
  this._rawFileData = null;
241
203
  this._sheets.clear();
@@ -243,24 +205,16 @@ export class DataFrameComponent extends BaseComponent {
243
205
  this.state.sourceName = '';
244
206
  this.state.rowCount = 0;
245
207
  this.state.colCount = 0;
246
- // Clear table display
247
208
  if (this._table) {
248
209
  this._table.columns([]).rows([]);
249
210
  }
250
- // Remove tabs if present
251
211
  const wrapper = document.getElementById(this._id);
252
212
  if (wrapper) {
253
213
  const existingTabs = wrapper.querySelector('.jux-tabs');
254
214
  if (existingTabs)
255
215
  existingTabs.remove();
256
216
  }
257
- // ✅ Hide data view, show upload
258
217
  this._hideDataView();
259
- // ✅ Hide collapsible details
260
- if (this._collapsible && this._detailsElement) {
261
- this._detailsElement.style.display = 'none';
262
- }
263
- // ✅ Clear file from upload component
264
218
  if (this._uploadRef) {
265
219
  this._uploadRef.clear();
266
220
  }
@@ -321,7 +275,7 @@ export class DataFrameComponent extends BaseComponent {
321
275
  return this._driver.store(name, this._df);
322
276
  }
323
277
  /* ═══════════════════════════════════════════════════
324
- * TABLE OPTIONS (fluent, pre-render)
278
+ * TABLE OPTIONS
325
279
  * ═══════════════════════════════════════════════════ */
326
280
  striped(v) { this._tableOptions.striped = v; return this; }
327
281
  hoverable(v) { this._tableOptions.hoverable = v; return this; }
@@ -333,76 +287,7 @@ export class DataFrameComponent extends BaseComponent {
333
287
  sheetChunkSize(v) { this._sheetChunkSize = v; return this; }
334
288
  maxFileSize(mb) { this._maxFileSize = mb; return this; }
335
289
  /* ═══════════════════════════════════════════════════
336
- * COLLAPSIBLE OPTIONS (fluent API)
337
- * ═══════════════════════════════════════════════════ */
338
- /**
339
- * Enable/disable collapsible mode
340
- */
341
- collapsible(enabled) {
342
- this._collapsible = enabled;
343
- return this;
344
- }
345
- /**
346
- * Set initial collapsed state (true = collapsed, false = expanded)
347
- */
348
- collapsed(value) {
349
- this._collapsed = value;
350
- if (this._detailsElement) {
351
- this._detailsElement.open = !value;
352
- }
353
- return this;
354
- }
355
- /**
356
- * Expand the details (show table)
357
- */
358
- expand() {
359
- this._collapsed = false;
360
- if (this._detailsElement) {
361
- this._detailsElement.open = true;
362
- }
363
- return this;
364
- }
365
- /**
366
- * Collapse the details (hide table)
367
- */
368
- collapse() {
369
- this._collapsed = true;
370
- if (this._detailsElement) {
371
- this._detailsElement.open = false;
372
- }
373
- return this;
374
- }
375
- /**
376
- * Check if collapsible mode is enabled
377
- */
378
- isCollapsible() {
379
- return this._collapsible;
380
- }
381
- /**
382
- * Toggle collapsed state
383
- */
384
- toggle() {
385
- this._collapsed = !this._collapsed;
386
- if (this._detailsElement) {
387
- this._detailsElement.open = !this._collapsed;
388
- }
389
- return this;
390
- }
391
- /**
392
- * Set custom summary template
393
- */
394
- summaryTemplate(fn) {
395
- this._summaryTemplate = fn;
396
- return this;
397
- }
398
- /**
399
- * Get current collapsed state
400
- */
401
- isCollapsed() {
402
- return this._collapsed;
403
- }
404
- /* ═══════════════════════════════════════════════════
405
- * FILE HANDLING (modified to use storage options)
290
+ * FILE HANDLING
406
291
  * ═══════════════════════════════════════════════════ */
407
292
  async _handleFile(file) {
408
293
  const fileSizeMB = file.size / (1024 * 1024);
@@ -431,7 +316,6 @@ export class DataFrameComponent extends BaseComponent {
431
316
  this.state.loading = false;
432
317
  return;
433
318
  }
434
- // ✅ Only store if persistence is enabled
435
319
  if (this._persistToIndexedDB) {
436
320
  await this._driver.store(file.name, sheets[sheetNames[0]], { source: file.name });
437
321
  }
@@ -449,7 +333,6 @@ export class DataFrameComponent extends BaseComponent {
449
333
  autoDetectDelimiter: true,
450
334
  hasHeader: true
451
335
  });
452
- // ✅ Only store if persistence is enabled
453
336
  if (this._persistToIndexedDB) {
454
337
  await this._driver.store(file.name, df, { source: file.name });
455
338
  }
@@ -481,7 +364,6 @@ export class DataFrameComponent extends BaseComponent {
481
364
  this._sheets.set(name, df);
482
365
  });
483
366
  const sheetNames = Object.keys(sheets);
484
- // Sanitize sheet names for use as DOM IDs
485
367
  const sanitizeId = (name) => name.replace(/[^a-zA-Z0-9_-]/g, '_');
486
368
  const tabDefs = sheetNames.map(name => ({
487
369
  id: sanitizeId(name),
@@ -492,26 +374,28 @@ export class DataFrameComponent extends BaseComponent {
492
374
  tabs: tabDefs,
493
375
  activeTab: sanitizeId(sheetNames[0])
494
376
  });
495
- // Map sanitized IDs back to original sheet names
496
377
  const idToSheetName = new Map();
497
378
  sheetNames.forEach(name => idToSheetName.set(sanitizeId(name), name));
498
379
  this._tabs.bind('tabChange', (tabId) => {
499
380
  const originalName = idToSheetName.get(tabId) || tabId;
500
381
  this._df = this._sheets.get(originalName) || null;
501
382
  });
383
+ const dataContainer = wrapper.querySelector('.jux-dataframe-data');
384
+ if (dataContainer) {
385
+ dataContainer.style.display = '';
386
+ }
502
387
  const tabsContainer = document.createElement('div');
503
388
  tabsContainer.className = 'jux-dataframe-tabs';
504
- wrapper.appendChild(tabsContainer);
389
+ if (dataContainer) {
390
+ dataContainer.appendChild(tabsContainer);
391
+ }
392
+ else {
393
+ wrapper.appendChild(tabsContainer);
394
+ }
505
395
  this._tabs.render(tabsContainer);
506
396
  sheetNames.forEach((sheetName) => {
507
397
  const df = sheets[sheetName];
508
398
  const safeId = sanitizeId(sheetName);
509
- // ✅ DEBUG: Log table options
510
- console.log(`[DataFrame] Creating table for sheet "${sheetName}" with options:`, {
511
- filterable: this._tableOptions.filterable,
512
- paginated: this._tableOptions.paginated,
513
- sortable: this._tableOptions.sortable
514
- });
515
399
  const table = new Table(`${this._id}-table-${safeId}`, {
516
400
  striped: this._tableOptions.striped,
517
401
  hoverable: this._tableOptions.hoverable,
@@ -532,6 +416,7 @@ export class DataFrameComponent extends BaseComponent {
532
416
  });
533
417
  const totalRows = Object.values(sheets).reduce((sum, df) => sum + df.height, 0);
534
418
  this._updateStatus(`${sourceName} — ${sheetNames.length} sheets, ${totalRows} total rows`, 'success');
419
+ this._showDataView();
535
420
  this._df = sheets[sheetNames[0]];
536
421
  this._triggerCallback('load', this._df, null, this);
537
422
  }
@@ -542,7 +427,6 @@ export class DataFrameComponent extends BaseComponent {
542
427
  const el = document.getElementById(`${this._id}-status`);
543
428
  if (!el)
544
429
  return;
545
- // ✅ Only show status during loading/error states
546
430
  if (type === 'loading' || type === 'error') {
547
431
  el.style.display = '';
548
432
  el.className = 'jux-dataframe-status';
@@ -561,7 +445,6 @@ export class DataFrameComponent extends BaseComponent {
561
445
  el.appendChild(span);
562
446
  }
563
447
  else {
564
- // ✅ Hide status after successful load - table speaks for itself
565
448
  el.style.display = 'none';
566
449
  }
567
450
  }
@@ -572,7 +455,6 @@ export class DataFrameComponent extends BaseComponent {
572
455
  this.state.sourceName = sourceName;
573
456
  this.state.rowCount = df.height;
574
457
  this.state.colCount = df.width;
575
- // Only strip __EMPTY columns that are ENTIRELY null/empty
576
458
  const cols = df.columns;
577
459
  const rows = df.toRows();
578
460
  const emptyColumns = cols.filter(c => {
@@ -593,58 +475,37 @@ export class DataFrameComponent extends BaseComponent {
593
475
  const columnDefs = this._df.columns.map(col => ({ key: col, label: col }));
594
476
  this._table.columns(columnDefs).rows(this._df.toRows());
595
477
  }
596
- // ✅ Show the table container, hide upload area
597
478
  this._showDataView();
598
- // ✅ Update collapsible summary if enabled
599
- if (this._collapsible && this._detailsElement) {
600
- this._detailsElement.style.display = '';
601
- this._updateSummary();
602
- }
603
- // ✅ Hide status - data is loaded
604
479
  this._updateStatus('', 'success');
605
480
  this._triggerCallback('load', this._df, null, this);
606
481
  }
607
- /**
608
- * Show the data view (table + settings gear), hide upload area
609
- */
610
482
  _showDataView() {
611
483
  const wrapper = document.getElementById(this._id);
612
484
  if (!wrapper)
613
485
  return;
614
- // Hide upload area
615
486
  const uploadArea = wrapper.querySelector('.jux-dataframe-upload-area');
616
487
  if (uploadArea) {
617
488
  uploadArea.style.display = 'none';
618
489
  }
619
- // Show data container (table + toolbar)
620
490
  const dataContainer = wrapper.querySelector('.jux-dataframe-data');
621
491
  if (dataContainer) {
622
492
  dataContainer.style.display = '';
623
493
  }
624
- // Update settings gear with file info
625
494
  this._updateSettingsGear();
626
495
  }
627
- /**
628
- * Hide data view, show upload area
629
- */
630
496
  _hideDataView() {
631
497
  const wrapper = document.getElementById(this._id);
632
498
  if (!wrapper)
633
499
  return;
634
- // Show upload area
635
500
  const uploadArea = wrapper.querySelector('.jux-dataframe-upload-area');
636
501
  if (uploadArea) {
637
502
  uploadArea.style.display = '';
638
503
  }
639
- // Hide data container
640
504
  const dataContainer = wrapper.querySelector('.jux-dataframe-data');
641
505
  if (dataContainer) {
642
506
  dataContainer.style.display = 'none';
643
507
  }
644
508
  }
645
- /**
646
- * Update the settings gear tooltip/info
647
- */
648
509
  _updateSettingsGear() {
649
510
  const gear = document.getElementById(`${this._id}-settings-gear`);
650
511
  if (!gear || !this._df)
@@ -659,9 +520,6 @@ export class DataFrameComponent extends BaseComponent {
659
520
  gear.title = `${this.state.sourceName} — ${this._df.height} rows × ${this._df.width} cols`;
660
521
  }
661
522
  }
662
- /**
663
- * Show the unified settings modal
664
- */
665
523
  _showSettingsModal() {
666
524
  this._cleanupReshapeModal();
667
525
  const fileInfo = this._rawFileData?.file;
@@ -676,7 +534,6 @@ export class DataFrameComponent extends BaseComponent {
676
534
  const fileName = fileInfo?.name || this.state.sourceName || 'Unknown';
677
535
  let contentHTML = `
678
536
  <div class="jux-dataframe-settings-content">
679
- <!-- File Info Section -->
680
537
  <div class="jux-dataframe-settings-section">
681
538
  <div class="jux-dataframe-settings-label">Source</div>
682
539
  <div class="jux-dataframe-settings-value">
@@ -684,8 +541,6 @@ export class DataFrameComponent extends BaseComponent {
684
541
  ${fileInfo ? `<span class="jux-muted" style="margin-left: 8px;">${fileSizeKB} KB</span>` : ''}
685
542
  </div>
686
543
  </div>
687
-
688
- <!-- Data Info Section -->
689
544
  <div class="jux-dataframe-settings-section">
690
545
  <div class="jux-dataframe-settings-label">Data</div>
691
546
  <div class="jux-dataframe-settings-value">
@@ -694,7 +549,6 @@ export class DataFrameComponent extends BaseComponent {
694
549
  </div>
695
550
  </div>
696
551
  `;
697
- // Import Settings button (only if we have raw file data)
698
552
  if (this._rawFileData) {
699
553
  contentHTML += `
700
554
  <div class="jux-dataframe-settings-section">
@@ -707,9 +561,7 @@ export class DataFrameComponent extends BaseComponent {
707
561
  </div>
708
562
  `;
709
563
  }
710
- contentHTML += `
711
- </div>
712
- `;
564
+ contentHTML += `</div>`;
713
565
  this._settingsModal
714
566
  .content(contentHTML)
715
567
  .actions([
@@ -729,7 +581,6 @@ export class DataFrameComponent extends BaseComponent {
729
581
  ]);
730
582
  this._settingsModal.render(document.body);
731
583
  this._settingsModal.open();
732
- // Wire up import settings button
733
584
  requestAnimationFrame(() => {
734
585
  const adjustBtn = document.getElementById(`${this._id}-adjust-import`);
735
586
  if (adjustBtn) {
@@ -740,79 +591,9 @@ export class DataFrameComponent extends BaseComponent {
740
591
  }
741
592
  });
742
593
  }
743
- /**
744
- * Update the collapsible summary text
745
- */
746
- _updateSummary(isMalformed) {
747
- if (!this._collapsible || !this._detailsElement)
748
- return;
749
- const summaryEl = this._detailsElement.querySelector('.jux-dataframe-summary');
750
- if (!summaryEl)
751
- return;
752
- const malformed = isMalformed ?? (this._df ? this._detectMalformedData(this._df) : false);
753
- const summaryTextEl = summaryEl.querySelector('.jux-dataframe-summary-text');
754
- if (summaryTextEl) {
755
- if (!this._df) {
756
- summaryTextEl.textContent = 'No data loaded';
757
- }
758
- else if (this._summaryTemplate) {
759
- summaryTextEl.textContent = this._summaryTemplate(this._df);
760
- }
761
- else {
762
- const suffix = malformed ? ' ⚠️' : '';
763
- summaryTextEl.textContent = `${this.state.sourceName || 'Data'} — ${this._df.height} rows × ${this._df.width} cols${suffix}`;
764
- }
765
- }
766
- // ✅ Add/update settings gear in summary
767
- let gearBtn = summaryEl.querySelector('.jux-dataframe-summary-gear');
768
- if (this._df) {
769
- if (!gearBtn) {
770
- gearBtn = document.createElement('button');
771
- gearBtn.className = 'jux-dataframe-summary-gear';
772
- gearBtn.type = 'button';
773
- gearBtn.innerHTML = '⚙️';
774
- gearBtn.addEventListener('click', (e) => {
775
- e.stopPropagation();
776
- this._showSettingsModal();
777
- });
778
- summaryEl.appendChild(gearBtn);
779
- }
780
- if (malformed) {
781
- gearBtn.classList.add('jux-dataframe-gear-warning');
782
- }
783
- else {
784
- gearBtn.classList.remove('jux-dataframe-gear-warning');
785
- }
786
- }
787
- else if (gearBtn) {
788
- gearBtn.remove();
789
- }
790
- }
791
594
  /* ═══════════════════════════════════════════════════
792
595
  * MALFORMED DATA DETECTION
793
596
  * ═══════════════════════════════════════════════════ */
794
- _appendSettingsButton(label, variant) {
795
- const statusEl = document.getElementById(`${this._id}-status`);
796
- if (!statusEl)
797
- return;
798
- // Remove existing settings button if any
799
- const existing = statusEl.querySelector('.jux-dataframe-settings-btn');
800
- if (existing)
801
- existing.remove();
802
- const btn = document.createElement('button');
803
- btn.className = `jux-dataframe-settings-btn jux-dataframe-settings-btn-${variant}`;
804
- btn.type = 'button';
805
- btn.textContent = `⚙️ ${label}`;
806
- btn.style.marginLeft = '8px';
807
- btn.style.cursor = 'pointer';
808
- btn.style.fontSize = '0.8rem';
809
- btn.style.padding = '2px 8px';
810
- btn.style.borderRadius = 'var(--radius, 4px)';
811
- btn.style.border = '1px solid hsl(var(--border))';
812
- btn.style.background = variant === 'warning' ? 'hsl(38 92% 50% / 0.15)' : 'transparent';
813
- btn.addEventListener('click', () => this._showReshapeModal());
814
- statusEl.appendChild(btn);
815
- }
816
597
  _detectMalformedData(df) {
817
598
  const columns = df.columns;
818
599
  const rows = df.toRows();
@@ -862,11 +643,6 @@ export class DataFrameComponent extends BaseComponent {
862
643
  div.textContent = text;
863
644
  return div.innerHTML;
864
645
  }
865
- /**
866
- * Build a clickable preview table from raw row data.
867
- * Each row stores its actual sheet row index via data-sheet-row attribute.
868
- * Returns the table HTML string.
869
- */
870
646
  _buildClickablePreviewHTML(rawRows, selectedSheetRow) {
871
647
  let html = '<table style="width: 100%; border-collapse: collapse; font-size: 12px;">';
872
648
  for (const { sheetRow, values } of rawRows) {
@@ -880,7 +656,6 @@ export class DataFrameComponent extends BaseComponent {
880
656
  rowStyle += 'background: hsl(var(--muted) / 0.4); color: hsl(var(--muted-foreground)); font-style: italic; opacity: 0.7;';
881
657
  }
882
658
  html += `<tr data-sheet-row="${sheetRow}" style="${rowStyle}" onmouseover="this.style.outline='2px solid hsl(142 71% 45% / 0.5)'" onmouseout="this.style.outline=''">`;
883
- // Row index cell
884
659
  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;">`;
885
660
  if (isHeader) {
886
661
  html += `<span style="color: hsl(142 71% 45%);">▶ ${sheetRow}</span>`;
@@ -889,7 +664,6 @@ export class DataFrameComponent extends BaseComponent {
889
664
  html += `${sheetRow}`;
890
665
  }
891
666
  html += '</td>';
892
- // Show first 6 columns
893
667
  const displayCols = values.slice(0, 6);
894
668
  displayCols.forEach(val => {
895
669
  const displayVal = val != null ? String(val).substring(0, 20) : '';
@@ -901,7 +675,6 @@ export class DataFrameComponent extends BaseComponent {
901
675
  if (values.length > 6) {
902
676
  html += `<td style="padding: 8px 12px; color: hsl(var(--muted-foreground));">…</td>`;
903
677
  }
904
- // Status badge
905
678
  html += `<td style="padding: 8px 12px; text-align: right; white-space: nowrap; user-select: none;">`;
906
679
  if (isHeader) {
907
680
  html += '<span style="background: hsl(142 71% 45%); color: white; padding: 3px 8px; border-radius: 4px; font-size: 10px; font-weight: 600;">HEADER</span>';
@@ -921,22 +694,17 @@ export class DataFrameComponent extends BaseComponent {
921
694
  if (!this._rawFileData?.file)
922
695
  return;
923
696
  this._cleanupReshapeModal();
924
- // ✅ Use the SAME cell-reading method as the parser
925
697
  const rawRows = await this._driver.readRawExcelRows(this._rawFileData.file, 15);
926
698
  if (rawRows.length === 0)
927
699
  return;
928
- // Log what we got so we can verify alignment
929
- console.log('[DataFrame Preview] Raw rows from readRawExcelRows:');
930
- rawRows.forEach(r => console.log(` sheetRow ${r.sheetRow}:`, r.values.slice(0, 5)));
931
- // Auto-detect best header row
932
700
  let selectedSheetRow = rawRows[0].sheetRow;
933
701
  for (const { sheetRow, values } of rawRows) {
934
702
  const nonEmpty = values.filter(v => v !== null && v !== undefined && String(v).trim() !== '');
935
703
  if (nonEmpty.length < values.length * 0.5)
936
704
  continue;
937
705
  const nonNumeric = nonEmpty.filter(v => {
938
- const trimmed = v.trim();
939
- return isNaN(Number(trimmed)) && trimmed !== '';
706
+ const str = String(v).trim();
707
+ return isNaN(Number(str)) && str !== '';
940
708
  }).length;
941
709
  if (nonNumeric >= nonEmpty.length * 0.7) {
942
710
  selectedSheetRow = sheetRow;
@@ -973,7 +741,6 @@ export class DataFrameComponent extends BaseComponent {
973
741
  click: async () => {
974
742
  const input = document.getElementById(`${this._id}-header-row`);
975
743
  const headerRow = parseInt(input.value) || 0;
976
- console.log(`[DataFrame] Apply clicked: headerRow=${headerRow}`);
977
744
  this.state.loading = true;
978
745
  this._updateStatus('Re-parsing with new settings...', 'loading');
979
746
  try {
@@ -1031,7 +798,6 @@ export class DataFrameComponent extends BaseComponent {
1031
798
  tr.addEventListener('click', () => {
1032
799
  const rowIdx = parseInt(tr.dataset.sheetRow);
1033
800
  hiddenInput.value = String(rowIdx);
1034
- console.log(`[DataFrame Preview] Clicked sheetRow=${rowIdx}`);
1035
801
  updateHint(rowIdx);
1036
802
  renderPreview(rowIdx);
1037
803
  });
@@ -1047,7 +813,6 @@ export class DataFrameComponent extends BaseComponent {
1047
813
  this._cleanupReshapeModal();
1048
814
  const text = this._rawFileData.text;
1049
815
  const detected = this._driver._detectDelimiter(text);
1050
- // Parse raw lines
1051
816
  const lines = text.replace(/\r\n/g, '\n').replace(/\r/g, '\n').split('\n');
1052
817
  const rawRows = [];
1053
818
  const maxPreviewRows = Math.min(lines.length, 15);
@@ -1059,7 +824,6 @@ export class DataFrameComponent extends BaseComponent {
1059
824
  const values = this._driver._parseLine(lines[i], detected);
1060
825
  rawRows.push({ sheetRow: i, values });
1061
826
  }
1062
- // Auto-detect header row
1063
827
  let selectedRow = 0;
1064
828
  for (const { sheetRow, values } of rawRows) {
1065
829
  const nonEmpty = values.filter((v) => v.trim() !== '');
@@ -1176,7 +940,6 @@ export class DataFrameComponent extends BaseComponent {
1176
940
  tr.addEventListener('click', () => {
1177
941
  const rowIdx = parseInt(tr.dataset.sheetRow);
1178
942
  hiddenInput.value = String(rowIdx);
1179
- console.log(`[DataFrame Preview] Clicked sheetRow=${rowIdx}`);
1180
943
  updateHint(rowIdx);
1181
944
  renderPreview(rowIdx);
1182
945
  });
@@ -1196,7 +959,7 @@ export class DataFrameComponent extends BaseComponent {
1196
959
  });
1197
960
  }
1198
961
  /* ═══════════════════════════════════════════════════
1199
- * UPDATE & RENDER (modified for better upload styling)
962
+ * UPDATE & RENDER
1200
963
  * ═══════════════════════════════════════════════════ */
1201
964
  update(_prop, _value) { }
1202
965
  render(targetId) {
@@ -1209,9 +972,7 @@ export class DataFrameComponent extends BaseComponent {
1209
972
  wrapper.className += ` ${className}`;
1210
973
  if (style)
1211
974
  wrapper.setAttribute('style', style);
1212
- // ═══════════════════════════════════════════════════
1213
- // UPLOAD AREA (shown initially, hidden after data loads)
1214
- // ═══════════════════════════════════════════════════
975
+ // Upload area
1215
976
  if (this._inlineUpload) {
1216
977
  const uploadArea = document.createElement('div');
1217
978
  uploadArea.className = 'jux-dataframe-upload-area';
@@ -1239,7 +1000,6 @@ export class DataFrameComponent extends BaseComponent {
1239
1000
  uploadContainer.className = 'jux-dataframe-upload';
1240
1001
  uploadContainer.id = `${this._id}-upload-container`;
1241
1002
  uploadArea.appendChild(uploadContainer);
1242
- // Status bar (for loading/error states only)
1243
1003
  if (this._showStatus) {
1244
1004
  const statusBar = document.createElement('div');
1245
1005
  statusBar.className = 'jux-dataframe-status';
@@ -1260,13 +1020,10 @@ export class DataFrameComponent extends BaseComponent {
1260
1020
  else {
1261
1021
  container.appendChild(wrapper);
1262
1022
  }
1263
- // ═══════════════════════════════════════════════════
1264
- // DATA CONTAINER (hidden initially, shown after data loads)
1265
- // ═══════════════════════════════════════════════════
1023
+ // Data container
1266
1024
  const dataContainer = document.createElement('div');
1267
1025
  dataContainer.className = 'jux-dataframe-data';
1268
- dataContainer.style.display = 'none'; // Hidden until data loads
1269
- // ✅ Toolbar with settings gear
1026
+ dataContainer.style.display = 'none';
1270
1027
  const toolbar = document.createElement('div');
1271
1028
  toolbar.className = 'jux-dataframe-toolbar';
1272
1029
  const settingsGear = document.createElement('button');
@@ -1278,42 +1035,8 @@ export class DataFrameComponent extends BaseComponent {
1278
1035
  settingsGear.addEventListener('click', () => this._showSettingsModal());
1279
1036
  toolbar.appendChild(settingsGear);
1280
1037
  dataContainer.appendChild(toolbar);
1281
- // ═══════════════════════════════════════════════════
1282
- // TABLE CONTAINER (inside data container or collapsible)
1283
- // ═══════════════════════════════════════════════════
1284
- let tableContainer;
1285
- if (this._collapsible) {
1286
- const details = document.createElement('details');
1287
- details.className = 'jux-dataframe-details';
1288
- details.open = !this._collapsed;
1289
- details.style.display = 'none'; // Hidden until data loads
1290
- this._detailsElement = details;
1291
- const summary = document.createElement('summary');
1292
- summary.className = 'jux-dataframe-summary';
1293
- const chevron = document.createElement('span');
1294
- chevron.className = 'jux-dataframe-summary-chevron';
1295
- chevron.innerHTML = '▶';
1296
- summary.appendChild(chevron);
1297
- const textSpan = document.createElement('span');
1298
- textSpan.className = 'jux-dataframe-summary-text';
1299
- summary.appendChild(textSpan);
1300
- details.appendChild(summary);
1301
- const content = document.createElement('div');
1302
- content.className = 'jux-dataframe-details-content';
1303
- details.appendChild(content);
1304
- dataContainer.appendChild(details);
1305
- tableContainer = content;
1306
- details.addEventListener('toggle', () => {
1307
- this._collapsed = !details.open;
1308
- });
1309
- }
1310
- else {
1311
- tableContainer = dataContainer;
1312
- }
1313
1038
  wrapper.appendChild(dataContainer);
1314
- // ═══════════════════════════════════════════════════
1315
- // TABLE
1316
- // ═══════════════════════════════════════════════════
1039
+ // Table
1317
1040
  const tbl = new Table(`${this._id}-table`, {
1318
1041
  striped: this._tableOptions.striped,
1319
1042
  hoverable: this._tableOptions.hoverable,
@@ -1322,7 +1045,7 @@ export class DataFrameComponent extends BaseComponent {
1322
1045
  paginated: this._tableOptions.paginated,
1323
1046
  rowsPerPage: this._tableOptions.rowsPerPage
1324
1047
  });
1325
- tbl.render(tableContainer);
1048
+ tbl.render(dataContainer);
1326
1049
  this._table = tbl;
1327
1050
  if (this._pendingSource) {
1328
1051
  const fn = this._pendingSource;