juxscript 1.1.175 → 1.1.177
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.
- package/lib/components/dataframe.d.ts +6 -0
- package/lib/components/dataframe.d.ts.map +1 -1
- package/lib/components/dataframe.js +145 -3
- package/lib/components/dataframe.ts +177 -3
- package/lib/storage/TabularDriver.d.ts +6 -0
- package/lib/storage/TabularDriver.d.ts.map +1 -1
- package/lib/storage/TabularDriver.js +26 -0
- package/lib/storage/TabularDriver.ts +31 -0
- package/package.json +1 -1
|
@@ -27,6 +27,8 @@ export declare class DataFrameComponent extends BaseComponent<DataFrameState> {
|
|
|
27
27
|
private _df;
|
|
28
28
|
private _driver;
|
|
29
29
|
private _table;
|
|
30
|
+
private _tabs;
|
|
31
|
+
private _sheets;
|
|
30
32
|
private _tableOptions;
|
|
31
33
|
private _uploadRef;
|
|
32
34
|
private _storageKey;
|
|
@@ -66,6 +68,10 @@ export declare class DataFrameComponent extends BaseComponent<DataFrameState> {
|
|
|
66
68
|
filterable(v: boolean): this;
|
|
67
69
|
paginated(v: boolean): this;
|
|
68
70
|
rowsPerPage(v: number): this;
|
|
71
|
+
/**
|
|
72
|
+
* ✅ NEW: Render multiple Excel sheets as tabs
|
|
73
|
+
*/
|
|
74
|
+
private _renderMultiSheet;
|
|
69
75
|
private _updateStatus;
|
|
70
76
|
private _setDataFrame;
|
|
71
77
|
private _updateTable;
|
|
@@ -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;
|
|
1
|
+
{"version":3,"file":"dataframe.d.ts","sourceRoot":"","sources":["dataframe.ts"],"names":[],"mappings":"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"}
|
|
@@ -3,6 +3,7 @@ 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';
|
|
6
7
|
import { renderIcon } from './icons.js';
|
|
7
8
|
const TRIGGER_EVENTS = [];
|
|
8
9
|
const CALLBACK_EVENTS = ['load', 'error', 'transform'];
|
|
@@ -21,6 +22,8 @@ export class DataFrameComponent extends BaseComponent {
|
|
|
21
22
|
});
|
|
22
23
|
this._df = null;
|
|
23
24
|
this._table = null;
|
|
25
|
+
this._tabs = null; // ✅ NEW: Tabs for multi-sheet Excel
|
|
26
|
+
this._sheets = new Map(); // ✅ NEW: Store all sheets
|
|
24
27
|
this._uploadRef = null;
|
|
25
28
|
this._storageKey = null;
|
|
26
29
|
this._pendingSource = null;
|
|
@@ -83,9 +86,29 @@ export class DataFrameComponent extends BaseComponent {
|
|
|
83
86
|
this.state.loading = true;
|
|
84
87
|
this._updateStatus('⏳ Parsing ' + file.name + '...', 'loading');
|
|
85
88
|
try {
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
+
// ✅ FIX: Check if multi-sheet Excel
|
|
90
|
+
const isExcel = file.name.toLowerCase().endsWith('.xlsx') ||
|
|
91
|
+
file.name.toLowerCase().endsWith('.xls');
|
|
92
|
+
if (isExcel) {
|
|
93
|
+
const sheets = await this._driver.streamFileMultiSheet(file);
|
|
94
|
+
const sheetNames = Object.keys(sheets);
|
|
95
|
+
// Store first sheet to IndexedDB
|
|
96
|
+
await this._driver.store(file.name, sheets[sheetNames[0]], { source: file.name });
|
|
97
|
+
if (sheetNames.length > 1) {
|
|
98
|
+
// Multi-sheet: render tabs
|
|
99
|
+
this._renderMultiSheet(sheets, file.name);
|
|
100
|
+
}
|
|
101
|
+
else {
|
|
102
|
+
// Single sheet: render normally
|
|
103
|
+
this._setDataFrame(sheets[sheetNames[0]], file.name);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
else {
|
|
107
|
+
// CSV/TSV: single sheet
|
|
108
|
+
const df = await this._driver.streamFile(file);
|
|
109
|
+
await this._driver.store(file.name, df, { source: file.name });
|
|
110
|
+
this._setDataFrame(df, file.name);
|
|
111
|
+
}
|
|
89
112
|
}
|
|
90
113
|
catch (err) {
|
|
91
114
|
this._triggerCallback('error', err.message, null, this);
|
|
@@ -185,6 +208,106 @@ export class DataFrameComponent extends BaseComponent {
|
|
|
185
208
|
filterable(v) { this._tableOptions.filterable = v; return this; }
|
|
186
209
|
paginated(v) { this._tableOptions.paginated = v; return this; }
|
|
187
210
|
rowsPerPage(v) { this._tableOptions.rowsPerPage = v; return this; }
|
|
211
|
+
/* ═══════════════════════════════════════════════════
|
|
212
|
+
* MULTI-SHEET RENDERING
|
|
213
|
+
* ═══════════════════════════════════════════════════ */
|
|
214
|
+
/**
|
|
215
|
+
* ✅ NEW: Render multiple Excel sheets as tabs
|
|
216
|
+
*/
|
|
217
|
+
_renderMultiSheet(sheets, sourceName) {
|
|
218
|
+
this.state.loading = false;
|
|
219
|
+
this._sheets.clear();
|
|
220
|
+
const wrapper = document.getElementById(this._id);
|
|
221
|
+
if (!wrapper)
|
|
222
|
+
return;
|
|
223
|
+
// Clear existing table if any
|
|
224
|
+
const existingTable = wrapper.querySelector('.jux-table-wrapper');
|
|
225
|
+
if (existingTable)
|
|
226
|
+
existingTable.remove();
|
|
227
|
+
// Store all sheets
|
|
228
|
+
Object.entries(sheets).forEach(([name, df]) => {
|
|
229
|
+
this._sheets.set(name, df);
|
|
230
|
+
});
|
|
231
|
+
// Create tabs
|
|
232
|
+
const sheetNames = Object.keys(sheets);
|
|
233
|
+
const tabs = new Tabs(`${this._id}-tabs`, {
|
|
234
|
+
tabs: sheetNames.map(name => ({
|
|
235
|
+
id: name,
|
|
236
|
+
label: name,
|
|
237
|
+
content: '' // We'll populate after render
|
|
238
|
+
})),
|
|
239
|
+
activeTab: sheetNames[0]
|
|
240
|
+
});
|
|
241
|
+
this._tabs = tabs;
|
|
242
|
+
// Render tabs into wrapper
|
|
243
|
+
const tabsContainer = document.createElement('div');
|
|
244
|
+
tabsContainer.className = 'jux-dataframe-tabs';
|
|
245
|
+
wrapper.appendChild(tabsContainer);
|
|
246
|
+
tabs.render(tabsContainer);
|
|
247
|
+
// Populate each tab with its DataFrame table
|
|
248
|
+
sheetNames.forEach(sheetName => {
|
|
249
|
+
const df = sheets[sheetName];
|
|
250
|
+
const table = new Table(`${this._id}-table-${sheetName}`, {
|
|
251
|
+
striped: this._tableOptions.striped,
|
|
252
|
+
hoverable: this._tableOptions.hoverable,
|
|
253
|
+
sortable: this._tableOptions.sortable,
|
|
254
|
+
filterable: false,
|
|
255
|
+
paginated: this._tableOptions.paginated,
|
|
256
|
+
rowsPerPage: this._tableOptions.rowsPerPage
|
|
257
|
+
});
|
|
258
|
+
// ✅ Convert columns to ColumnDef[]
|
|
259
|
+
const columnDefs = df.columns.map(col => ({
|
|
260
|
+
key: col,
|
|
261
|
+
label: col
|
|
262
|
+
}));
|
|
263
|
+
table.columns(columnDefs).rows(df.toRows());
|
|
264
|
+
// Add table to tab
|
|
265
|
+
tabs.addTabContent(sheetName, table);
|
|
266
|
+
// Add filter if enabled
|
|
267
|
+
if (this._tableOptions.filterable) {
|
|
268
|
+
const filterContainer = document.createElement('div');
|
|
269
|
+
filterContainer.className = 'jux-dataframe-filter';
|
|
270
|
+
const input = document.createElement('input');
|
|
271
|
+
input.type = 'text';
|
|
272
|
+
input.placeholder = `Filter ${sheetName}...`;
|
|
273
|
+
input.className = 'jux-input-element jux-dataframe-filter-input';
|
|
274
|
+
const iconEl = renderIcon('search');
|
|
275
|
+
iconEl.style.width = '16px';
|
|
276
|
+
iconEl.style.height = '16px';
|
|
277
|
+
const iconWrap = document.createElement('span');
|
|
278
|
+
iconWrap.className = 'jux-dataframe-filter-icon';
|
|
279
|
+
iconWrap.appendChild(iconEl);
|
|
280
|
+
filterContainer.appendChild(iconWrap);
|
|
281
|
+
filterContainer.appendChild(input);
|
|
282
|
+
input.addEventListener('input', () => {
|
|
283
|
+
const text = input.value.toLowerCase();
|
|
284
|
+
if (!text) {
|
|
285
|
+
table.rows(df.toRows());
|
|
286
|
+
return;
|
|
287
|
+
}
|
|
288
|
+
const filtered = df.filter((row) => {
|
|
289
|
+
return Object.values(row).some(v => v !== null && v !== undefined && String(v).toLowerCase().includes(text));
|
|
290
|
+
});
|
|
291
|
+
table.rows(filtered.toRows());
|
|
292
|
+
});
|
|
293
|
+
// Insert filter before table in tab
|
|
294
|
+
const tabPanel = document.getElementById(`${this._id}-tabs-${sheetName}-panel`);
|
|
295
|
+
if (tabPanel) {
|
|
296
|
+
const tableWrapper = tabPanel.querySelector('.jux-table-wrapper');
|
|
297
|
+
if (tableWrapper) {
|
|
298
|
+
tabPanel.insertBefore(filterContainer, tableWrapper);
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
});
|
|
303
|
+
// Update status
|
|
304
|
+
const totalRows = Object.values(sheets).reduce((sum, df) => sum + df.height, 0);
|
|
305
|
+
this._updateStatus(`${sourceName} — ${sheetNames.length} sheets, ${totalRows} total rows`, 'success');
|
|
306
|
+
// Set first sheet as active DataFrame
|
|
307
|
+
this._df = sheets[sheetNames[0]];
|
|
308
|
+
this._table = tabs._tabsWrapper?.querySelector(`#${this._id}-table-${sheetNames[0]}`);
|
|
309
|
+
this._triggerCallback('load', this._df, null, this);
|
|
310
|
+
}
|
|
188
311
|
/* ═══════════════════════════════════════════════════
|
|
189
312
|
* INTERNAL
|
|
190
313
|
* ═══════════════════════════════════════════════════ */
|
|
@@ -234,7 +357,26 @@ export class DataFrameComponent extends BaseComponent {
|
|
|
234
357
|
key: col,
|
|
235
358
|
label: col // Use column name as label
|
|
236
359
|
}));
|
|
360
|
+
// ✅ Update both columns AND rows (force table rebuild)
|
|
237
361
|
this._table.columns(columnDefs).rows(this._df.toRows());
|
|
362
|
+
// ✅ Force table to re-render header and body
|
|
363
|
+
const tableElement = this._table['_tableElement']; // Access private property
|
|
364
|
+
if (tableElement) {
|
|
365
|
+
const wrapper = tableElement.closest('.jux-table-wrapper');
|
|
366
|
+
if (wrapper) {
|
|
367
|
+
// Clear and rebuild the entire table
|
|
368
|
+
tableElement.innerHTML = '';
|
|
369
|
+
// Rebuild header
|
|
370
|
+
const thead = this._table._buildTableHeader();
|
|
371
|
+
tableElement.appendChild(thead);
|
|
372
|
+
// Rebuild body
|
|
373
|
+
const tbody = document.createElement('tbody');
|
|
374
|
+
this._table._renderTableBody(tbody);
|
|
375
|
+
tableElement.appendChild(tbody);
|
|
376
|
+
// Re-wire events
|
|
377
|
+
this._table._wireTriggerEvents(tbody);
|
|
378
|
+
}
|
|
379
|
+
}
|
|
238
380
|
}
|
|
239
381
|
_showFilterInput() {
|
|
240
382
|
const wrapper = document.getElementById(this._id);
|
|
@@ -3,6 +3,7 @@ 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';
|
|
6
7
|
import { renderIcon } from './icons.js';
|
|
7
8
|
|
|
8
9
|
const TRIGGER_EVENTS = [] as const;
|
|
@@ -34,6 +35,8 @@ export class DataFrameComponent extends BaseComponent<DataFrameState> {
|
|
|
34
35
|
private _df: DataFrame | null = null;
|
|
35
36
|
private _driver: TabularDriver;
|
|
36
37
|
private _table: Table | null = null;
|
|
38
|
+
private _tabs: Tabs | null = null; // ✅ NEW: Tabs for multi-sheet Excel
|
|
39
|
+
private _sheets: Map<string, DataFrame> = new Map(); // ✅ NEW: Store all sheets
|
|
37
40
|
private _tableOptions: {
|
|
38
41
|
striped: boolean;
|
|
39
42
|
hoverable: boolean;
|
|
@@ -119,10 +122,32 @@ export class DataFrameComponent extends BaseComponent<DataFrameState> {
|
|
|
119
122
|
const file = files[0];
|
|
120
123
|
this.state.loading = true;
|
|
121
124
|
this._updateStatus('⏳ Parsing ' + file.name + '...', 'loading');
|
|
125
|
+
|
|
122
126
|
try {
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
127
|
+
// ✅ FIX: Check if multi-sheet Excel
|
|
128
|
+
const isExcel = file.name.toLowerCase().endsWith('.xlsx') ||
|
|
129
|
+
file.name.toLowerCase().endsWith('.xls');
|
|
130
|
+
|
|
131
|
+
if (isExcel) {
|
|
132
|
+
const sheets = await this._driver.streamFileMultiSheet(file);
|
|
133
|
+
const sheetNames = Object.keys(sheets);
|
|
134
|
+
|
|
135
|
+
// Store first sheet to IndexedDB
|
|
136
|
+
await this._driver.store(file.name, sheets[sheetNames[0]], { source: file.name });
|
|
137
|
+
|
|
138
|
+
if (sheetNames.length > 1) {
|
|
139
|
+
// Multi-sheet: render tabs
|
|
140
|
+
this._renderMultiSheet(sheets, file.name);
|
|
141
|
+
} else {
|
|
142
|
+
// Single sheet: render normally
|
|
143
|
+
this._setDataFrame(sheets[sheetNames[0]], file.name);
|
|
144
|
+
}
|
|
145
|
+
} else {
|
|
146
|
+
// CSV/TSV: single sheet
|
|
147
|
+
const df = await this._driver.streamFile(file);
|
|
148
|
+
await this._driver.store(file.name, df, { source: file.name });
|
|
149
|
+
this._setDataFrame(df, file.name);
|
|
150
|
+
}
|
|
126
151
|
} catch (err: any) {
|
|
127
152
|
this._triggerCallback('error', err.message, null, this);
|
|
128
153
|
this.state.loading = false;
|
|
@@ -232,6 +257,131 @@ export class DataFrameComponent extends BaseComponent<DataFrameState> {
|
|
|
232
257
|
paginated(v: boolean): this { this._tableOptions.paginated = v; return this; }
|
|
233
258
|
rowsPerPage(v: number): this { this._tableOptions.rowsPerPage = v; return this; }
|
|
234
259
|
|
|
260
|
+
/* ═══════════════════════════════════════════════════
|
|
261
|
+
* MULTI-SHEET RENDERING
|
|
262
|
+
* ═══════════════════════════════════════════════════ */
|
|
263
|
+
|
|
264
|
+
/**
|
|
265
|
+
* ✅ NEW: Render multiple Excel sheets as tabs
|
|
266
|
+
*/
|
|
267
|
+
private _renderMultiSheet(sheets: Record<string, DataFrame>, sourceName: string): void {
|
|
268
|
+
this.state.loading = false;
|
|
269
|
+
this._sheets.clear();
|
|
270
|
+
|
|
271
|
+
const wrapper = document.getElementById(this._id);
|
|
272
|
+
if (!wrapper) return;
|
|
273
|
+
|
|
274
|
+
// Clear existing table if any
|
|
275
|
+
const existingTable = wrapper.querySelector('.jux-table-wrapper');
|
|
276
|
+
if (existingTable) existingTable.remove();
|
|
277
|
+
|
|
278
|
+
// Store all sheets
|
|
279
|
+
Object.entries(sheets).forEach(([name, df]) => {
|
|
280
|
+
this._sheets.set(name, df);
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
// Create tabs
|
|
284
|
+
const sheetNames = Object.keys(sheets);
|
|
285
|
+
const tabs = new Tabs(`${this._id}-tabs`, {
|
|
286
|
+
tabs: sheetNames.map(name => ({
|
|
287
|
+
id: name,
|
|
288
|
+
label: name,
|
|
289
|
+
content: '' // We'll populate after render
|
|
290
|
+
})),
|
|
291
|
+
activeTab: sheetNames[0]
|
|
292
|
+
});
|
|
293
|
+
|
|
294
|
+
this._tabs = tabs;
|
|
295
|
+
|
|
296
|
+
// Render tabs into wrapper
|
|
297
|
+
const tabsContainer = document.createElement('div');
|
|
298
|
+
tabsContainer.className = 'jux-dataframe-tabs';
|
|
299
|
+
wrapper.appendChild(tabsContainer);
|
|
300
|
+
tabs.render(tabsContainer);
|
|
301
|
+
|
|
302
|
+
// Populate each tab with its DataFrame table
|
|
303
|
+
sheetNames.forEach(sheetName => {
|
|
304
|
+
const df = sheets[sheetName];
|
|
305
|
+
const table = new Table(`${this._id}-table-${sheetName}`, {
|
|
306
|
+
striped: this._tableOptions.striped,
|
|
307
|
+
hoverable: this._tableOptions.hoverable,
|
|
308
|
+
sortable: this._tableOptions.sortable,
|
|
309
|
+
filterable: false,
|
|
310
|
+
paginated: this._tableOptions.paginated,
|
|
311
|
+
rowsPerPage: this._tableOptions.rowsPerPage
|
|
312
|
+
});
|
|
313
|
+
|
|
314
|
+
// ✅ Convert columns to ColumnDef[]
|
|
315
|
+
const columnDefs = df.columns.map(col => ({
|
|
316
|
+
key: col,
|
|
317
|
+
label: col
|
|
318
|
+
}));
|
|
319
|
+
|
|
320
|
+
table.columns(columnDefs).rows(df.toRows());
|
|
321
|
+
|
|
322
|
+
// Add table to tab
|
|
323
|
+
tabs.addTabContent(sheetName, table);
|
|
324
|
+
|
|
325
|
+
// Add filter if enabled
|
|
326
|
+
if (this._tableOptions.filterable) {
|
|
327
|
+
const filterContainer = document.createElement('div');
|
|
328
|
+
filterContainer.className = 'jux-dataframe-filter';
|
|
329
|
+
|
|
330
|
+
const input = document.createElement('input');
|
|
331
|
+
input.type = 'text';
|
|
332
|
+
input.placeholder = `Filter ${sheetName}...`;
|
|
333
|
+
input.className = 'jux-input-element jux-dataframe-filter-input';
|
|
334
|
+
|
|
335
|
+
const iconEl = renderIcon('search');
|
|
336
|
+
iconEl.style.width = '16px';
|
|
337
|
+
iconEl.style.height = '16px';
|
|
338
|
+
|
|
339
|
+
const iconWrap = document.createElement('span');
|
|
340
|
+
iconWrap.className = 'jux-dataframe-filter-icon';
|
|
341
|
+
iconWrap.appendChild(iconEl);
|
|
342
|
+
|
|
343
|
+
filterContainer.appendChild(iconWrap);
|
|
344
|
+
filterContainer.appendChild(input);
|
|
345
|
+
|
|
346
|
+
input.addEventListener('input', () => {
|
|
347
|
+
const text = input.value.toLowerCase();
|
|
348
|
+
if (!text) {
|
|
349
|
+
table.rows(df.toRows());
|
|
350
|
+
return;
|
|
351
|
+
}
|
|
352
|
+
const filtered = df.filter((row) => {
|
|
353
|
+
return Object.values(row).some(v =>
|
|
354
|
+
v !== null && v !== undefined && String(v).toLowerCase().includes(text)
|
|
355
|
+
);
|
|
356
|
+
});
|
|
357
|
+
table.rows(filtered.toRows());
|
|
358
|
+
});
|
|
359
|
+
|
|
360
|
+
// Insert filter before table in tab
|
|
361
|
+
const tabPanel = document.getElementById(`${this._id}-tabs-${sheetName}-panel`);
|
|
362
|
+
if (tabPanel) {
|
|
363
|
+
const tableWrapper = tabPanel.querySelector('.jux-table-wrapper');
|
|
364
|
+
if (tableWrapper) {
|
|
365
|
+
tabPanel.insertBefore(filterContainer, tableWrapper);
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
});
|
|
370
|
+
|
|
371
|
+
// Update status
|
|
372
|
+
const totalRows = Object.values(sheets).reduce((sum, df) => sum + df.height, 0);
|
|
373
|
+
this._updateStatus(
|
|
374
|
+
`${sourceName} — ${sheetNames.length} sheets, ${totalRows} total rows`,
|
|
375
|
+
'success'
|
|
376
|
+
);
|
|
377
|
+
|
|
378
|
+
// Set first sheet as active DataFrame
|
|
379
|
+
this._df = sheets[sheetNames[0]];
|
|
380
|
+
this._table = (tabs as any)._tabsWrapper?.querySelector(`#${this._id}-table-${sheetNames[0]}`);
|
|
381
|
+
|
|
382
|
+
this._triggerCallback('load', this._df, null, this);
|
|
383
|
+
}
|
|
384
|
+
|
|
235
385
|
/* ═══════════════════════════════════════════════════
|
|
236
386
|
* INTERNAL
|
|
237
387
|
* ═══════════════════════════════════════════════════ */
|
|
@@ -294,7 +444,30 @@ export class DataFrameComponent extends BaseComponent<DataFrameState> {
|
|
|
294
444
|
label: col // Use column name as label
|
|
295
445
|
}));
|
|
296
446
|
|
|
447
|
+
// ✅ Update both columns AND rows (force table rebuild)
|
|
297
448
|
this._table.columns(columnDefs).rows(this._df.toRows());
|
|
449
|
+
|
|
450
|
+
// ✅ Force table to re-render header and body
|
|
451
|
+
const tableElement = this._table['_tableElement']; // Access private property
|
|
452
|
+
if (tableElement) {
|
|
453
|
+
const wrapper = tableElement.closest('.jux-table-wrapper');
|
|
454
|
+
if (wrapper) {
|
|
455
|
+
// Clear and rebuild the entire table
|
|
456
|
+
tableElement.innerHTML = '';
|
|
457
|
+
|
|
458
|
+
// Rebuild header
|
|
459
|
+
const thead = (this._table as any)._buildTableHeader();
|
|
460
|
+
tableElement.appendChild(thead);
|
|
461
|
+
|
|
462
|
+
// Rebuild body
|
|
463
|
+
const tbody = document.createElement('tbody');
|
|
464
|
+
(this._table as any)._renderTableBody(tbody);
|
|
465
|
+
tableElement.appendChild(tbody);
|
|
466
|
+
|
|
467
|
+
// Re-wire events
|
|
468
|
+
(this._table as any)._wireTriggerEvents(tbody);
|
|
469
|
+
}
|
|
470
|
+
}
|
|
298
471
|
}
|
|
299
472
|
|
|
300
473
|
private _showFilterInput(): void {
|
|
@@ -378,6 +551,7 @@ export class DataFrameComponent extends BaseComponent<DataFrameState> {
|
|
|
378
551
|
const file = files[0];
|
|
379
552
|
this.state.loading = true;
|
|
380
553
|
this._updateStatus('⏳ Parsing ' + file.name + '...', 'loading');
|
|
554
|
+
|
|
381
555
|
try {
|
|
382
556
|
const df = await this._driver.streamFile(file);
|
|
383
557
|
await this._driver.store(file.name, df, { source: file.name });
|
|
@@ -73,6 +73,12 @@ export declare class TabularDriver {
|
|
|
73
73
|
* Fetch and stream-parse a remote CSV/TSV file
|
|
74
74
|
*/
|
|
75
75
|
fetch(url: string, options?: ParseOptions): Promise<DataFrame>;
|
|
76
|
+
/**
|
|
77
|
+
* ✅ NEW: Stream Excel file and return all sheets
|
|
78
|
+
* @param file - Excel file (.xlsx or .xls)
|
|
79
|
+
* @returns Record<sheetName, DataFrame>
|
|
80
|
+
*/
|
|
81
|
+
streamFileMultiSheet(file: File): Promise<Record<string, DataFrame>>;
|
|
76
82
|
private _splitLines;
|
|
77
83
|
private _parseLine;
|
|
78
84
|
private _autoType;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"TabularDriver.d.ts","sourceRoot":"","sources":["TabularDriver.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAE3C,MAAM,WAAW,WAAW;IACxB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,IAAI,EAAE,GAAG,EAAE,EAAE,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;CAClC;AAED,MAAM,WAAW,YAAY;IACzB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,UAAU,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI,KAAK,IAAI,CAAC;IAC5D,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IACnB,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;CAC3B;AAED,qBAAa,aAAa;IACtB,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,UAAU,CAAS;IAC3B,OAAO,CAAC,GAAG,CAA4B;gBAE3B,MAAM,GAAE,MAAsB,EAAE,SAAS,GAAE,MAAiB;IAKlE,IAAI,IAAI,OAAO,CAAC,WAAW,CAAC;IA4BlC;;OAEG;IACH,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,GAAE,YAAiB,GAAG,SAAS;IA6C7D;;;OAGG;IACG,UAAU,CAAC,IAAI,EAAE,IAAI,EAAE,OAAO,GAAE,YAAiB,GAAG,OAAO,CAAC,SAAS,CAAC;IAoG5E;;OAEG;YACW,UAAU;IAuExB;;OAEG;IACG,aAAa,CAAC,IAAI,EAAE,IAAI,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC;IAiBlD;;OAEG;IACG,KAAK,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,SAAS,EAAE,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,OAAO,CAAC,MAAM,CAAC;IAuBzF;;OAEG;IACG,IAAI,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,SAAS,GAAG,IAAI,CAAC;IAwBjD;;OAEG;IACG,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,SAAS,GAAG,IAAI,CAAC;IA4BzD;;OAEG;IACG,IAAI,IAAI,OAAO,CAAC,KAAK,CAAC;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,EAAE,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAqBlH;;OAEG;IACG,MAAM,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAYjC,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAgB5B;;OAEG;IACG,KAAK,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,GAAE,YAAiB,GAAG,OAAO,CAAC,SAAS,CAAC;
|
|
1
|
+
{"version":3,"file":"TabularDriver.d.ts","sourceRoot":"","sources":["TabularDriver.ts"],"names":[],"mappings":"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"}
|
|
@@ -418,6 +418,32 @@ export class TabularDriver {
|
|
|
418
418
|
df = df.select(...selectCols);
|
|
419
419
|
return df;
|
|
420
420
|
}
|
|
421
|
+
/**
|
|
422
|
+
* ✅ NEW: Stream Excel file and return all sheets
|
|
423
|
+
* @param file - Excel file (.xlsx or .xls)
|
|
424
|
+
* @returns Record<sheetName, DataFrame>
|
|
425
|
+
*/
|
|
426
|
+
async streamFileMultiSheet(file) {
|
|
427
|
+
// ✅ FIX: Dynamic import XLSX here
|
|
428
|
+
let XLSX;
|
|
429
|
+
try {
|
|
430
|
+
XLSX = await import('xlsx');
|
|
431
|
+
}
|
|
432
|
+
catch {
|
|
433
|
+
throw new Error('XLSX support requires the "xlsx" package. Install it with: npm install xlsx');
|
|
434
|
+
}
|
|
435
|
+
const buffer = await file.arrayBuffer();
|
|
436
|
+
const workbook = XLSX.read(buffer, { type: 'array' });
|
|
437
|
+
const sheets = {};
|
|
438
|
+
workbook.SheetNames.forEach((sheetName) => {
|
|
439
|
+
const worksheet = workbook.Sheets[sheetName];
|
|
440
|
+
const jsonData = XLSX.utils.sheet_to_json(worksheet, { defval: null });
|
|
441
|
+
if (jsonData.length > 0) {
|
|
442
|
+
sheets[sheetName] = new DataFrame(jsonData);
|
|
443
|
+
}
|
|
444
|
+
});
|
|
445
|
+
return sheets;
|
|
446
|
+
}
|
|
421
447
|
/* ═══════════════════════════════════════════════════
|
|
422
448
|
* INTERNAL PARSING HELPERS
|
|
423
449
|
* ═══════════════════════════════════════════════════ */
|
|
@@ -522,6 +522,37 @@ export class TabularDriver {
|
|
|
522
522
|
return df;
|
|
523
523
|
}
|
|
524
524
|
|
|
525
|
+
/**
|
|
526
|
+
* ✅ NEW: Stream Excel file and return all sheets
|
|
527
|
+
* @param file - Excel file (.xlsx or .xls)
|
|
528
|
+
* @returns Record<sheetName, DataFrame>
|
|
529
|
+
*/
|
|
530
|
+
async streamFileMultiSheet(file: File): Promise<Record<string, DataFrame>> {
|
|
531
|
+
// ✅ FIX: Dynamic import XLSX here
|
|
532
|
+
let XLSX: any;
|
|
533
|
+
try {
|
|
534
|
+
XLSX = await import('xlsx');
|
|
535
|
+
} catch {
|
|
536
|
+
throw new Error('XLSX support requires the "xlsx" package. Install it with: npm install xlsx');
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
const buffer = await file.arrayBuffer();
|
|
540
|
+
const workbook = XLSX.read(buffer, { type: 'array' });
|
|
541
|
+
|
|
542
|
+
const sheets: Record<string, DataFrame> = {};
|
|
543
|
+
|
|
544
|
+
workbook.SheetNames.forEach((sheetName: string) => {
|
|
545
|
+
const worksheet = workbook.Sheets[sheetName];
|
|
546
|
+
const jsonData: Record<string, any>[] = XLSX.utils.sheet_to_json(worksheet, { defval: null });
|
|
547
|
+
|
|
548
|
+
if (jsonData.length > 0) {
|
|
549
|
+
sheets[sheetName] = new DataFrame(jsonData);
|
|
550
|
+
}
|
|
551
|
+
});
|
|
552
|
+
|
|
553
|
+
return sheets;
|
|
554
|
+
}
|
|
555
|
+
|
|
525
556
|
/* ═══════════════════════════════════════════════════
|
|
526
557
|
* INTERNAL PARSING HELPERS
|
|
527
558
|
* ═══════════════════════════════════════════════════ */
|