juxscript 1.1.186 → 1.1.187

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.
@@ -46,6 +46,7 @@ export declare class DataFrameComponent extends BaseComponent<DataFrameState> {
46
46
  private _showReshapeWarning;
47
47
  private _rawFileData;
48
48
  private _reshapeModal;
49
+ private _reshapeModalRendered;
49
50
  constructor(id: string, options?: DataFrameOptions);
50
51
  protected getTriggerEvents(): readonly string[];
51
52
  protected getCallbackEvents(): readonly string[];
@@ -1 +1 @@
1
- {"version":3,"file":"dataframe.d.ts","sourceRoot":"","sources":["dataframe.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,SAAS,EAAE,MAAM,yBAAyB,CAAC;AACnE,OAAO,EAAE,SAAS,EAAE,MAAM,yBAAyB,CAAC;AACpD,OAAO,EAAE,aAAa,EAAE,MAAM,6BAA6B,CAAC;AAC5D,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAC7C,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AASnC,MAAM,WAAW,gBAAgB;IAC7B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAC7B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,KAAK,cAAc,GAAG,SAAS,GAAG;IAC9B,MAAM,EAAE,OAAO,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;CACpB,CAAC;AAEF,qBAAa,kBAAmB,SAAQ,aAAa,CAAC,cAAc,CAAC;IACjE,OAAO,CAAC,GAAG,CAA0B;IACrC,OAAO,CAAC,OAAO,CAAgB;IAC/B,OAAO,CAAC,MAAM,CAAsB;IACpC,OAAO,CAAC,KAAK,CAAqB;IAClC,OAAO,CAAC,OAAO,CAAqC;IACpD,OAAO,CAAC,aAAa,CAOnB;IACF,OAAO,CAAC,UAAU,CAA2B;IAC7C,OAAO,CAAC,WAAW,CAAuB;IAC1C,OAAO,CAAC,cAAc,CAAsC;IAC5D,OAAO,CAAC,aAAa,CAAgE;IACrF,OAAO,CAAC,WAAW,CAAiB;IACpC,OAAO,CAAC,KAAK,CAAc;IAC3B,OAAO,CAAC,aAAa,CAAkB;IACvC,OAAO,CAAC,eAAe,CAAiB;IACxC,OAAO,CAAC,YAAY,CAAc;IAClC,OAAO,CAAC,mBAAmB,CAAiB;IAC5C,OAAO,CAAC,YAAY,CAAiE;IACrF,OAAO,CAAC,aAAa,CAAsB;gBAE/B,EAAE,EAAE,MAAM,EAAE,OAAO,GAAE,gBAAqB;IAmCtD,SAAS,CAAC,gBAAgB,IAAI,SAAS,MAAM,EAAE;IAC/C,SAAS,CAAC,iBAAiB,IAAI,SAAS,MAAM,EAAE;IAMhD,WAAW,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI;IAwB9B,UAAU,CAAC,MAAM,EAAE,UAAU,GAAG,IAAI;IAmEpC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAAE,GAAG,MAAM,CAAC,MAAM,EAAE,GAAG,EAAE,CAAC,GAAG,IAAI;IAiBnE,UAAU,CAAC,KAAK,GAAE,MAAsB,EAAE,MAAM,GAAE,MAAoC,EAAE,IAAI,GAAE,MAAiB,GAAG,IAAI;IAStH,UAAU,CAAC,CAAC,EAAE,OAAO,GAAG,IAAI;IAC5B,UAAU,CAAC,CAAC,EAAE,MAAM,GAAG,IAAI;IAM3B,KAAK,CAAC,EAAE,EAAE,CAAC,EAAE,EAAE,SAAS,KAAK,SAAS,GAAG,IAAI;IAQ7C,MAAM,CAAC,SAAS,EAAE,CAAC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAAE,KAAK,EAAE,MAAM,KAAK,OAAO,GAAG,IAAI;IAI7E,MAAM,CAAC,GAAG,IAAI,EAAE,MAAM,EAAE,GAAG,IAAI;IAI/B,IAAI,CAAC,GAAG,EAAE,MAAM,EAAE,UAAU,CAAC,EAAE,OAAO,GAAG,IAAI;IAI7C,IAAI,CAAC,CAAC,GAAE,MAAU,GAAG,IAAI;IAIzB,IAAI,CAAC,CAAC,GAAE,MAAU,GAAG,IAAI;IAIzB,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,CAAC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAAE,KAAK,EAAE,MAAM,KAAK,GAAG,GAAG,IAAI;IAIpF,KAAK,CAAC,GAAG,EAAE,MAAM,EAAE,EAAE,EAAE,IAAI,GAAG,IAAI,GAAG,GAAG,GAAG,GAAG,GAAG,IAAI,GAAG,IAAI,GAAG,UAAU,GAAG,YAAY,GAAG,UAAU,EAAE,KAAK,EAAE,GAAG,GAAG,IAAI;IAQxH,IAAI,EAAE,IAAI,SAAS,GAAG,IAAI,CAAqB;IAC/C,IAAI,MAAM,IAAI,aAAa,CAAyB;IACpD,IAAI,KAAK,IAAI,KAAK,GAAG,IAAI,CAAwB;IACjD,QAAQ,IAAI,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,IAAI;IACtC,KAAK,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,MAAM;IACjC,MAAM,IAAI,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAAE;IAC/B,IAAI,KAAK,IAAI,CAAC,MAAM,EAAE,MAAM,CAAC,CAAsC;IACnE,IAAI,OAAO,IAAI,MAAM,EAAE,CAAoC;IAErD,IAAI,CAAC,GAAG,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;IAUhD,OAAO,CAAC,CAAC,EAAE,OAAO,GAAG,IAAI;IACzB,SAAS,CAAC,CAAC,EAAE,OAAO,GAAG,IAAI;IAC3B,QAAQ,CAAC,CAAC,EAAE,OAAO,GAAG,IAAI;IAC1B,UAAU,CAAC,CAAC,EAAE,OAAO,GAAG,IAAI;IAC5B,SAAS,CAAC,CAAC,EAAE,OAAO,GAAG,IAAI;IAC3B,WAAW,CAAC,CAAC,EAAE,MAAM,GAAG,IAAI;IAE5B;;OAEG;IACH,YAAY,CAAC,CAAC,EAAE,MAAM,GAAG,IAAI;IAK7B;;OAEG;IACH,cAAc,CAAC,CAAC,EAAE,MAAM,GAAG,IAAI;IAK/B;;OAEG;IACH,WAAW,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI;IAS7B;;OAEG;IACH,OAAO,CAAC,iBAAiB;IAkIzB,OAAO,CAAC,aAAa;IAuBrB,OAAO,CAAC,aAAa;IAiErB;;OAEG;IACH,OAAO,CAAC,oBAAoB;IAiD5B;;OAEG;IACH,OAAO,CAAC,iBAAiB;IAYzB;;OAEG;YACW,sBAAsB;IAyHpC;;OAEG;IACH,OAAO,CAAC,oBAAoB;IA0I5B,MAAM,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,GAAG,GAAG,IAAI;IAMxC,MAAM,CAAC,QAAQ,CAAC,EAAE,MAAM,GAAG,WAAW,GAAG,aAAa,CAAC,GAAG,CAAC,GAAG,IAAI;CA4GrE;AAED,wBAAgB,SAAS,CAAC,EAAE,EAAE,MAAM,EAAE,OAAO,GAAE,gBAAqB,GAAG,kBAAkB,CAExF"}
1
+ {"version":3,"file":"dataframe.d.ts","sourceRoot":"","sources":["dataframe.ts"],"names":[],"mappings":"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"}
@@ -37,6 +37,7 @@ export class DataFrameComponent extends BaseComponent {
37
37
  this._showReshapeWarning = true;
38
38
  this._rawFileData = null;
39
39
  this._reshapeModal = null; // ✅ ADD THIS LINE
40
+ this._reshapeModalRendered = false; // Track if modal already rendered
40
41
  this._driver = new TabularDriver(options.dbName ?? 'jux-dataframes', options.storeName ?? 'frames');
41
42
  this._showStatus = options.showStatus ?? true;
42
43
  this._icon = options.icon ?? '';
@@ -404,34 +405,38 @@ export class DataFrameComponent extends BaseComponent {
404
405
  this._table.columns(columnDefs).rows(this._df.toRows());
405
406
  }
406
407
  // ✅ Detect malformed data
407
- const isMalformed = this._detectMalformedData(df);
408
+ const isMalformed = this._detectMalformedData(this._df);
408
409
  // ✅ Show warning if malformed
409
410
  if (isMalformed && this._showReshapeWarning && this._rawFileData) {
410
- this._updateStatus(`⚠️ ${sourceName} — ${this._df.height} rows × ${this._df.width} cols (Data may be malformed)`, 'warning');
411
- // Add Settings button
412
- const statusEl = document.getElementById(`${this._id}-status`);
413
- if (statusEl) {
414
- const settingsBtn = document.createElement('button');
415
- settingsBtn.textContent = 'Fix Import Settings';
416
- settingsBtn.className = 'jux-button jux-button-sm jux-button-warning';
417
- settingsBtn.style.marginLeft = '0.5rem';
418
- settingsBtn.addEventListener('click', () => this._showReshapeModal());
419
- statusEl.appendChild(settingsBtn);
420
- }
421
- }
422
- else {
423
- this._updateStatus(`${sourceName} — ${this._df.height} rows × ${this._df.width} cols`, 'success');
424
- // ✅ Still add Settings button for manual adjustment
425
- if (this._showReshapeWarning && this._rawFileData) {
411
+ this._updateStatus(`⚠️ ${sourceName} — ${this._df.height} rows × ${this._df.width} cols (Data may be malformed — headers may be on wrong row)`, 'warning');
412
+ // Add Fix Import Settings button after a tick to ensure status DOM is ready
413
+ requestAnimationFrame(() => {
426
414
  const statusEl = document.getElementById(`${this._id}-status`);
427
415
  if (statusEl) {
428
416
  const settingsBtn = document.createElement('button');
429
- settingsBtn.textContent = 'Settings';
430
- settingsBtn.className = 'jux-button jux-button-sm jux-button-ghost';
417
+ settingsBtn.textContent = '⚙️ Fix Import Settings';
418
+ settingsBtn.className = 'jux-button jux-button-sm jux-button-warning';
431
419
  settingsBtn.style.marginLeft = '0.5rem';
432
420
  settingsBtn.addEventListener('click', () => this._showReshapeModal());
433
421
  statusEl.appendChild(settingsBtn);
434
422
  }
423
+ });
424
+ }
425
+ else {
426
+ this._updateStatus(`${sourceName} — ${this._df.height} rows × ${this._df.width} cols`, 'success');
427
+ // ✅ Still add Settings button for manual adjustment
428
+ if (this._showReshapeWarning && this._rawFileData) {
429
+ requestAnimationFrame(() => {
430
+ const statusEl = document.getElementById(`${this._id}-status`);
431
+ if (statusEl) {
432
+ const settingsBtn = document.createElement('button');
433
+ settingsBtn.textContent = '⚙️ Settings';
434
+ settingsBtn.className = 'jux-button jux-button-sm jux-button-ghost';
435
+ settingsBtn.style.marginLeft = '0.5rem';
436
+ settingsBtn.addEventListener('click', () => this._showReshapeModal());
437
+ statusEl.appendChild(settingsBtn);
438
+ }
439
+ });
435
440
  }
436
441
  }
437
442
  this._triggerCallback('load', this._df, null, this);
@@ -496,15 +501,21 @@ export class DataFrameComponent extends BaseComponent {
496
501
  async _showExcelReshapeModal() {
497
502
  if (!this._rawFileData?.file)
498
503
  return;
499
- // Create modal if not exists
500
- if (!this._reshapeModal) {
501
- this._reshapeModal = new Modal(`${this._id}-reshape-modal`, {
502
- title: 'Excel Import Settings',
503
- size: 'large',
504
- close: true,
505
- backdropClose: false
506
- });
504
+ // Remove old modal from DOM if it was previously rendered
505
+ if (this._reshapeModal && this._reshapeModalRendered) {
506
+ const oldEl = document.getElementById(`${this._id}-reshape-modal`);
507
+ if (oldEl)
508
+ oldEl.remove();
509
+ this._reshapeModal = null;
510
+ this._reshapeModalRendered = false;
507
511
  }
512
+ // Create fresh modal
513
+ this._reshapeModal = new Modal(`${this._id}-reshape-modal`, {
514
+ title: 'Excel Import Settings',
515
+ size: 'large',
516
+ close: true,
517
+ backdropClose: false
518
+ });
508
519
  // Build modal content
509
520
  const modalContent = document.createElement('div');
510
521
  modalContent.innerHTML = `
@@ -561,6 +572,7 @@ export class DataFrameComponent extends BaseComponent {
561
572
  this._renderMultiSheet(sheets, this._rawFileData.file.name);
562
573
  }
563
574
  else {
575
+ this._showReshapeWarning = false; // Prevent recursive warning after manual fix
564
576
  this._setDataFrame(sheets[sheetNames[0]], this._rawFileData.file.name);
565
577
  }
566
578
  this._reshapeModal.closeModal();
@@ -570,13 +582,17 @@ export class DataFrameComponent extends BaseComponent {
570
582
  }
571
583
  }
572
584
  }
573
- ])
574
- .render(document.body);
585
+ ]);
586
+ // Render modal to document.body and open it
587
+ this._reshapeModal.render(document.body);
588
+ this._reshapeModalRendered = true;
589
+ // Wait a tick for DOM to update after render
590
+ await new Promise(resolve => requestAnimationFrame(resolve));
575
591
  const headerRowInput = document.getElementById(`${this._id}-header-row`);
576
592
  const previewDiv = document.getElementById(`${this._id}-preview`);
577
593
  // Update preview on header row change
578
594
  const updatePreview = async () => {
579
- const headerRow = parseInt(headerRowInput.value) || 0;
595
+ const headerRow = parseInt(headerRowInput?.value) || 0;
580
596
  try {
581
597
  const sheets = await this._driver.streamFileMultiSheet(this._rawFileData.file, {
582
598
  headerRow,
@@ -584,20 +600,24 @@ export class DataFrameComponent extends BaseComponent {
584
600
  });
585
601
  const firstSheet = Object.values(sheets)[0];
586
602
  if (!firstSheet) {
587
- previewDiv.textContent = '⚠️ No data found';
603
+ if (previewDiv)
604
+ previewDiv.textContent = '⚠️ No data found';
588
605
  return;
589
606
  }
590
607
  const preview = firstSheet.toRows().slice(0, 10).map((row, i) => {
591
608
  const cols = Object.values(row).map(v => String(v).padEnd(20)).join(' │ ');
592
609
  return `${i === 0 ? '📌 ' : ' '}${cols}`;
593
610
  }).join('\n');
594
- previewDiv.textContent = `Columns: ${firstSheet.columns.join(' │ ')}\n${'─'.repeat(80)}\n${preview}`;
611
+ if (previewDiv)
612
+ previewDiv.textContent = `Columns: ${firstSheet.columns.join(' │ ')}\n${'─'.repeat(80)}\n${preview}`;
595
613
  }
596
614
  catch (err) {
597
- previewDiv.textContent = `⚠️ Error: ${err.message}`;
615
+ if (previewDiv)
616
+ previewDiv.textContent = `⚠️ Error: ${err.message}`;
598
617
  }
599
618
  };
600
- headerRowInput.addEventListener('input', updatePreview);
619
+ if (headerRowInput)
620
+ headerRowInput.addEventListener('input', updatePreview);
601
621
  updatePreview();
602
622
  this._reshapeModal.open();
603
623
  }
@@ -607,15 +627,21 @@ export class DataFrameComponent extends BaseComponent {
607
627
  _showCSVReshapeModal() {
608
628
  if (!this._rawFileData)
609
629
  return;
610
- // Create modal if not exists
611
- if (!this._reshapeModal) {
612
- this._reshapeModal = new Modal(`${this._id}-reshape-modal`, {
613
- title: 'CSV Import Settings',
614
- size: 'large',
615
- close: true,
616
- backdropClose: false
617
- });
630
+ // Remove old modal from DOM if it was previously rendered
631
+ if (this._reshapeModal && this._reshapeModalRendered) {
632
+ const oldEl = document.getElementById(`${this._id}-reshape-modal`);
633
+ if (oldEl)
634
+ oldEl.remove();
635
+ this._reshapeModal = null;
636
+ this._reshapeModalRendered = false;
618
637
  }
638
+ // Create fresh modal
639
+ this._reshapeModal = new Modal(`${this._id}-reshape-modal`, {
640
+ title: 'CSV Import Settings',
641
+ size: 'large',
642
+ close: true,
643
+ backdropClose: false
644
+ });
619
645
  // Build modal content
620
646
  const modalContent = document.createElement('div');
621
647
  modalContent.innerHTML = `
@@ -674,6 +700,7 @@ export class DataFrameComponent extends BaseComponent {
674
700
  hasHeader: true
675
701
  });
676
702
  await this._driver.store(this._rawFileData.file.name, df, { source: this._rawFileData.file.name });
703
+ this._showReshapeWarning = false; // Prevent recursive warning after manual fix
677
704
  this._setDataFrame(df, this._rawFileData.file.name);
678
705
  this._reshapeModal.closeModal();
679
706
  }
@@ -682,49 +709,61 @@ export class DataFrameComponent extends BaseComponent {
682
709
  }
683
710
  }
684
711
  }
685
- ])
686
- .render(document.body);
687
- const delimiterSelect = document.getElementById(`${this._id}-delimiter`);
688
- const headerRowInput = document.getElementById(`${this._id}-header-row`);
689
- const skipRowsInput = document.getElementById(`${this._id}-skip-rows`);
690
- const previewDiv = document.getElementById(`${this._id}-preview`);
691
- // Auto-detect initial values
692
- if (this._rawFileData.text) {
693
- const detected = this._driver._detectDelimiter(this._rawFileData.text);
694
- delimiterSelect.value = detected === '\t' ? '\\t' : detected;
695
- const headerRow = this._driver._detectHeaderRow(this._rawFileData.text, detected);
696
- headerRowInput.value = String(headerRow);
697
- }
698
- // Update preview on changes
699
- const updatePreview = async () => {
700
- if (!this._rawFileData?.text)
701
- return;
702
- const delim = delimiterSelect.value === '\\t' ? '\t' : delimiterSelect.value;
703
- const headerRow = parseInt(headerRowInput.value) || 0;
704
- const skipRows = parseInt(skipRowsInput.value) || 0;
705
- try {
706
- const df = this._driver.parseCSV(this._rawFileData.text, {
707
- delimiter: delim,
708
- headerRow,
709
- skipRows,
710
- hasHeader: true,
711
- maxRows: 10
712
- });
713
- const preview = df.toRows().map((row, i) => {
714
- const cols = Object.values(row).map(v => String(v).padEnd(20)).join(' │ ');
715
- return `${i === 0 ? '📌 ' : ' '}${cols}`;
716
- }).join('\n');
717
- previewDiv.textContent = `Columns: ${df.columns.join(' │ ')}\n${'─'.repeat(80)}\n${preview}`;
718
- }
719
- catch (err) {
720
- previewDiv.textContent = `⚠️ Error: ${err.message}`;
712
+ ]);
713
+ // Render modal to document.body and open it
714
+ this._reshapeModal.render(document.body);
715
+ this._reshapeModalRendered = true;
716
+ // Use requestAnimationFrame to ensure DOM is ready
717
+ requestAnimationFrame(() => {
718
+ const delimiterSelect = document.getElementById(`${this._id}-delimiter`);
719
+ const headerRowInput = document.getElementById(`${this._id}-header-row`);
720
+ const skipRowsInput = document.getElementById(`${this._id}-skip-rows`);
721
+ const previewDiv = document.getElementById(`${this._id}-preview`);
722
+ // Auto-detect initial values
723
+ if (this._rawFileData?.text) {
724
+ const detected = this._driver._detectDelimiter(this._rawFileData.text);
725
+ if (delimiterSelect)
726
+ delimiterSelect.value = detected === '\t' ? '\\t' : detected;
727
+ const headerRow = this._driver._detectHeaderRow(this._rawFileData.text, detected);
728
+ if (headerRowInput)
729
+ headerRowInput.value = String(headerRow);
721
730
  }
722
- };
723
- delimiterSelect.addEventListener('change', updatePreview);
724
- headerRowInput.addEventListener('input', updatePreview);
725
- skipRowsInput.addEventListener('input', updatePreview);
726
- updatePreview();
727
- this._reshapeModal.open();
731
+ // Update preview on changes
732
+ const updatePreview = async () => {
733
+ if (!this._rawFileData?.text)
734
+ return;
735
+ const delim = delimiterSelect?.value === '\\t' ? '\t' : (delimiterSelect?.value || ',');
736
+ const headerRow = parseInt(headerRowInput?.value) || 0;
737
+ const skipRows = parseInt(skipRowsInput?.value) || 0;
738
+ try {
739
+ const df = this._driver.parseCSV(this._rawFileData.text, {
740
+ delimiter: delim,
741
+ headerRow,
742
+ skipRows,
743
+ hasHeader: true,
744
+ maxRows: 10
745
+ });
746
+ const preview = df.toRows().map((row, i) => {
747
+ const cols = Object.values(row).map(v => String(v).padEnd(20)).join(' │ ');
748
+ return `${i === 0 ? '📌 ' : ' '}${cols}`;
749
+ }).join('\n');
750
+ if (previewDiv)
751
+ previewDiv.textContent = `Columns: ${df.columns.join(' │ ')}\n${'─'.repeat(80)}\n${preview}`;
752
+ }
753
+ catch (err) {
754
+ if (previewDiv)
755
+ previewDiv.textContent = `⚠️ Error: ${err.message}`;
756
+ }
757
+ };
758
+ if (delimiterSelect)
759
+ delimiterSelect.addEventListener('change', updatePreview);
760
+ if (headerRowInput)
761
+ headerRowInput.addEventListener('input', updatePreview);
762
+ if (skipRowsInput)
763
+ skipRowsInput.addEventListener('input', updatePreview);
764
+ updatePreview();
765
+ this._reshapeModal.open();
766
+ });
728
767
  }
729
768
  update(_prop, _value) { }
730
769
  /* ═══════════════════════════════════════════════════
@@ -63,6 +63,7 @@ export class DataFrameComponent extends BaseComponent<DataFrameState> {
63
63
  private _showReshapeWarning: boolean = true;
64
64
  private _rawFileData: { file: File; text?: string; isExcel?: boolean } | null = null;
65
65
  private _reshapeModal: Modal | null = null; // ✅ ADD THIS LINE
66
+ private _reshapeModalRendered: boolean = false; // Track if modal already rendered
66
67
 
67
68
  constructor(id: string, options: DataFrameOptions = {}) {
68
69
  super(id, {
@@ -503,25 +504,27 @@ export class DataFrameComponent extends BaseComponent<DataFrameState> {
503
504
  }
504
505
 
505
506
  // ✅ Detect malformed data
506
- const isMalformed = this._detectMalformedData(df);
507
+ const isMalformed = this._detectMalformedData(this._df!);
507
508
 
508
509
  // ✅ Show warning if malformed
509
510
  if (isMalformed && this._showReshapeWarning && this._rawFileData) {
510
511
  this._updateStatus(
511
- `⚠️ ${sourceName} — ${this._df!.height} rows × ${this._df!.width} cols (Data may be malformed)`,
512
+ `⚠️ ${sourceName} — ${this._df!.height} rows × ${this._df!.width} cols (Data may be malformed — headers may be on wrong row)`,
512
513
  'warning'
513
514
  );
514
515
 
515
- // Add Settings button
516
- const statusEl = document.getElementById(`${this._id}-status`);
517
- if (statusEl) {
518
- const settingsBtn = document.createElement('button');
519
- settingsBtn.textContent = 'Fix Import Settings';
520
- settingsBtn.className = 'jux-button jux-button-sm jux-button-warning';
521
- settingsBtn.style.marginLeft = '0.5rem';
522
- settingsBtn.addEventListener('click', () => this._showReshapeModal());
523
- statusEl.appendChild(settingsBtn);
524
- }
516
+ // Add Fix Import Settings button after a tick to ensure status DOM is ready
517
+ requestAnimationFrame(() => {
518
+ const statusEl = document.getElementById(`${this._id}-status`);
519
+ if (statusEl) {
520
+ const settingsBtn = document.createElement('button');
521
+ settingsBtn.textContent = '⚙️ Fix Import Settings';
522
+ settingsBtn.className = 'jux-button jux-button-sm jux-button-warning';
523
+ settingsBtn.style.marginLeft = '0.5rem';
524
+ settingsBtn.addEventListener('click', () => this._showReshapeModal());
525
+ statusEl.appendChild(settingsBtn);
526
+ }
527
+ });
525
528
  } else {
526
529
  this._updateStatus(
527
530
  `${sourceName} — ${this._df!.height} rows × ${this._df!.width} cols`,
@@ -530,15 +533,17 @@ export class DataFrameComponent extends BaseComponent<DataFrameState> {
530
533
 
531
534
  // ✅ Still add Settings button for manual adjustment
532
535
  if (this._showReshapeWarning && this._rawFileData) {
533
- const statusEl = document.getElementById(`${this._id}-status`);
534
- if (statusEl) {
535
- const settingsBtn = document.createElement('button');
536
- settingsBtn.textContent = 'Settings';
537
- settingsBtn.className = 'jux-button jux-button-sm jux-button-ghost';
538
- settingsBtn.style.marginLeft = '0.5rem';
539
- settingsBtn.addEventListener('click', () => this._showReshapeModal());
540
- statusEl.appendChild(settingsBtn);
541
- }
536
+ requestAnimationFrame(() => {
537
+ const statusEl = document.getElementById(`${this._id}-status`);
538
+ if (statusEl) {
539
+ const settingsBtn = document.createElement('button');
540
+ settingsBtn.textContent = '⚙️ Settings';
541
+ settingsBtn.className = 'jux-button jux-button-sm jux-button-ghost';
542
+ settingsBtn.style.marginLeft = '0.5rem';
543
+ settingsBtn.addEventListener('click', () => this._showReshapeModal());
544
+ statusEl.appendChild(settingsBtn);
545
+ }
546
+ });
542
547
  }
543
548
  }
544
549
 
@@ -618,16 +623,22 @@ export class DataFrameComponent extends BaseComponent<DataFrameState> {
618
623
  private async _showExcelReshapeModal(): Promise<void> {
619
624
  if (!this._rawFileData?.file) return;
620
625
 
621
- // Create modal if not exists
622
- if (!this._reshapeModal) {
623
- this._reshapeModal = new Modal(`${this._id}-reshape-modal`, {
624
- title: 'Excel Import Settings',
625
- size: 'large',
626
- close: true,
627
- backdropClose: false
628
- });
626
+ // Remove old modal from DOM if it was previously rendered
627
+ if (this._reshapeModal && this._reshapeModalRendered) {
628
+ const oldEl = document.getElementById(`${this._id}-reshape-modal`);
629
+ if (oldEl) oldEl.remove();
630
+ this._reshapeModal = null;
631
+ this._reshapeModalRendered = false;
629
632
  }
630
633
 
634
+ // Create fresh modal
635
+ this._reshapeModal = new Modal(`${this._id}-reshape-modal`, {
636
+ title: 'Excel Import Settings',
637
+ size: 'large',
638
+ close: true,
639
+ backdropClose: false
640
+ });
641
+
631
642
  // Build modal content
632
643
  const modalContent = document.createElement('div');
633
644
  modalContent.innerHTML = `
@@ -688,6 +699,7 @@ export class DataFrameComponent extends BaseComponent<DataFrameState> {
688
699
  if (sheetNames.length > 1) {
689
700
  this._renderMultiSheet(sheets, this._rawFileData!.file.name);
690
701
  } else {
702
+ this._showReshapeWarning = false; // Prevent recursive warning after manual fix
691
703
  this._setDataFrame(sheets[sheetNames[0]], this._rawFileData!.file.name);
692
704
  }
693
705
 
@@ -697,15 +709,21 @@ export class DataFrameComponent extends BaseComponent<DataFrameState> {
697
709
  }
698
710
  }
699
711
  }
700
- ])
701
- .render(document.body);
712
+ ]);
713
+
714
+ // Render modal to document.body and open it
715
+ this._reshapeModal.render(document.body);
716
+ this._reshapeModalRendered = true;
717
+
718
+ // Wait a tick for DOM to update after render
719
+ await new Promise(resolve => requestAnimationFrame(resolve));
702
720
 
703
721
  const headerRowInput = document.getElementById(`${this._id}-header-row`) as HTMLInputElement;
704
722
  const previewDiv = document.getElementById(`${this._id}-preview`)!;
705
723
 
706
724
  // Update preview on header row change
707
725
  const updatePreview = async () => {
708
- const headerRow = parseInt(headerRowInput.value) || 0;
726
+ const headerRow = parseInt(headerRowInput?.value) || 0;
709
727
 
710
728
  try {
711
729
  const sheets = await this._driver.streamFileMultiSheet(this._rawFileData!.file, {
@@ -715,7 +733,7 @@ export class DataFrameComponent extends BaseComponent<DataFrameState> {
715
733
 
716
734
  const firstSheet = Object.values(sheets)[0];
717
735
  if (!firstSheet) {
718
- previewDiv.textContent = '⚠️ No data found';
736
+ if (previewDiv) previewDiv.textContent = '⚠️ No data found';
719
737
  return;
720
738
  }
721
739
 
@@ -724,13 +742,13 @@ export class DataFrameComponent extends BaseComponent<DataFrameState> {
724
742
  return `${i === 0 ? '📌 ' : ' '}${cols}`;
725
743
  }).join('\n');
726
744
 
727
- previewDiv.textContent = `Columns: ${firstSheet.columns.join(' │ ')}\n${'─'.repeat(80)}\n${preview}`;
745
+ if (previewDiv) previewDiv.textContent = `Columns: ${firstSheet.columns.join(' │ ')}\n${'─'.repeat(80)}\n${preview}`;
728
746
  } catch (err: any) {
729
- previewDiv.textContent = `⚠️ Error: ${err.message}`;
747
+ if (previewDiv) previewDiv.textContent = `⚠️ Error: ${err.message}`;
730
748
  }
731
749
  };
732
750
 
733
- headerRowInput.addEventListener('input', updatePreview);
751
+ if (headerRowInput) headerRowInput.addEventListener('input', updatePreview);
734
752
  updatePreview();
735
753
 
736
754
  this._reshapeModal.open();
@@ -742,16 +760,22 @@ export class DataFrameComponent extends BaseComponent<DataFrameState> {
742
760
  private _showCSVReshapeModal(): void {
743
761
  if (!this._rawFileData) return;
744
762
 
745
- // Create modal if not exists
746
- if (!this._reshapeModal) {
747
- this._reshapeModal = new Modal(`${this._id}-reshape-modal`, {
748
- title: 'CSV Import Settings',
749
- size: 'large',
750
- close: true,
751
- backdropClose: false
752
- });
763
+ // Remove old modal from DOM if it was previously rendered
764
+ if (this._reshapeModal && this._reshapeModalRendered) {
765
+ const oldEl = document.getElementById(`${this._id}-reshape-modal`);
766
+ if (oldEl) oldEl.remove();
767
+ this._reshapeModal = null;
768
+ this._reshapeModalRendered = false;
753
769
  }
754
770
 
771
+ // Create fresh modal
772
+ this._reshapeModal = new Modal(`${this._id}-reshape-modal`, {
773
+ title: 'CSV Import Settings',
774
+ size: 'large',
775
+ close: true,
776
+ backdropClose: false
777
+ });
778
+
755
779
  // Build modal content
756
780
  const modalContent = document.createElement('div');
757
781
  modalContent.innerHTML = `
@@ -815,6 +839,7 @@ export class DataFrameComponent extends BaseComponent<DataFrameState> {
815
839
  });
816
840
 
817
841
  await this._driver.store(this._rawFileData.file.name, df, { source: this._rawFileData.file.name });
842
+ this._showReshapeWarning = false; // Prevent recursive warning after manual fix
818
843
  this._setDataFrame(df, this._rawFileData.file.name);
819
844
 
820
845
  this._reshapeModal!.closeModal();
@@ -823,58 +848,64 @@ export class DataFrameComponent extends BaseComponent<DataFrameState> {
823
848
  }
824
849
  }
825
850
  }
826
- ])
827
- .render(document.body);
828
-
829
- const delimiterSelect = document.getElementById(`${this._id}-delimiter`) as HTMLSelectElement;
830
- const headerRowInput = document.getElementById(`${this._id}-header-row`) as HTMLInputElement;
831
- const skipRowsInput = document.getElementById(`${this._id}-skip-rows`) as HTMLInputElement;
832
- const previewDiv = document.getElementById(`${this._id}-preview`)!;
833
-
834
- // Auto-detect initial values
835
- if (this._rawFileData.text) {
836
- const detected = (this._driver as any)._detectDelimiter(this._rawFileData.text);
837
- delimiterSelect.value = detected === '\t' ? '\\t' : detected;
851
+ ]);
852
+
853
+ // Render modal to document.body and open it
854
+ this._reshapeModal.render(document.body);
855
+ this._reshapeModalRendered = true;
856
+
857
+ // Use requestAnimationFrame to ensure DOM is ready
858
+ requestAnimationFrame(() => {
859
+ const delimiterSelect = document.getElementById(`${this._id}-delimiter`) as HTMLSelectElement;
860
+ const headerRowInput = document.getElementById(`${this._id}-header-row`) as HTMLInputElement;
861
+ const skipRowsInput = document.getElementById(`${this._id}-skip-rows`) as HTMLInputElement;
862
+ const previewDiv = document.getElementById(`${this._id}-preview`)!;
863
+
864
+ // Auto-detect initial values
865
+ if (this._rawFileData?.text) {
866
+ const detected = (this._driver as any)._detectDelimiter(this._rawFileData.text);
867
+ if (delimiterSelect) delimiterSelect.value = detected === '\t' ? '\\t' : detected;
868
+
869
+ const headerRow = (this._driver as any)._detectHeaderRow(this._rawFileData.text, detected);
870
+ if (headerRowInput) headerRowInput.value = String(headerRow);
871
+ }
838
872
 
839
- const headerRow = (this._driver as any)._detectHeaderRow(this._rawFileData.text, detected);
840
- headerRowInput.value = String(headerRow);
841
- }
873
+ // Update preview on changes
874
+ const updatePreview = async () => {
875
+ if (!this._rawFileData?.text) return;
842
876
 
843
- // Update preview on changes
844
- const updatePreview = async () => {
845
- if (!this._rawFileData?.text) return;
877
+ const delim = delimiterSelect?.value === '\\t' ? '\t' : (delimiterSelect?.value || ',');
878
+ const headerRow = parseInt(headerRowInput?.value) || 0;
879
+ const skipRows = parseInt(skipRowsInput?.value) || 0;
846
880
 
847
- const delim = delimiterSelect.value === '\\t' ? '\t' : delimiterSelect.value;
848
- const headerRow = parseInt(headerRowInput.value) || 0;
849
- const skipRows = parseInt(skipRowsInput.value) || 0;
881
+ try {
882
+ const df = this._driver.parseCSV(this._rawFileData.text, {
883
+ delimiter: delim,
884
+ headerRow,
885
+ skipRows,
886
+ hasHeader: true,
887
+ maxRows: 10
888
+ });
850
889
 
851
- try {
852
- const df = this._driver.parseCSV(this._rawFileData.text, {
853
- delimiter: delim,
854
- headerRow,
855
- skipRows,
856
- hasHeader: true,
857
- maxRows: 10
858
- });
890
+ const preview = df.toRows().map((row, i) => {
891
+ const cols = Object.values(row).map(v => String(v).padEnd(20)).join(' │ ');
892
+ return `${i === 0 ? '📌 ' : ' '}${cols}`;
893
+ }).join('\n');
859
894
 
860
- const preview = df.toRows().map((row, i) => {
861
- const cols = Object.values(row).map(v => String(v).padEnd(20)).join(' │ ');
862
- return `${i === 0 ? '📌 ' : ' '}${cols}`;
863
- }).join('\n');
864
-
865
- previewDiv.textContent = `Columns: ${df.columns.join(' │ ')}\n${'─'.repeat(80)}\n${preview}`;
866
- } catch (err: any) {
867
- previewDiv.textContent = `⚠️ Error: ${err.message}`;
868
- }
869
- };
895
+ if (previewDiv) previewDiv.textContent = `Columns: ${df.columns.join(' │ ')}\n${'─'.repeat(80)}\n${preview}`;
896
+ } catch (err: any) {
897
+ if (previewDiv) previewDiv.textContent = `⚠️ Error: ${err.message}`;
898
+ }
899
+ };
870
900
 
871
- delimiterSelect.addEventListener('change', updatePreview);
872
- headerRowInput.addEventListener('input', updatePreview);
873
- skipRowsInput.addEventListener('input', updatePreview);
901
+ if (delimiterSelect) delimiterSelect.addEventListener('change', updatePreview);
902
+ if (headerRowInput) headerRowInput.addEventListener('input', updatePreview);
903
+ if (skipRowsInput) skipRowsInput.addEventListener('input', updatePreview);
874
904
 
875
- updatePreview();
905
+ updatePreview();
876
906
 
877
- this._reshapeModal.open();
907
+ this._reshapeModal!.open();
908
+ });
878
909
  }
879
910
 
880
911
  update(_prop: string, _value: any): void { }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "juxscript",
3
- "version": "1.1.186",
3
+ "version": "1.1.187",
4
4
  "type": "module",
5
5
  "description": "A JavaScript UX authorship platform",
6
6
  "main": "index.js",