juxscript 1.1.176 → 1.1.178
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 +160 -12
- package/lib/components/dataframe.ts +191 -13
- 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
|
+
// ✅ 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
|
* ═══════════════════════════════════════════════════ */
|
|
@@ -229,19 +352,19 @@ export class DataFrameComponent extends BaseComponent {
|
|
|
229
352
|
_updateTable() {
|
|
230
353
|
if (!this._table || !this._df)
|
|
231
354
|
return;
|
|
232
|
-
// ✅
|
|
355
|
+
// ✅ Convert string[] columns to ColumnDef[] with labels
|
|
233
356
|
const columnDefs = this._df.columns.map(col => ({
|
|
234
357
|
key: col,
|
|
235
|
-
label: col
|
|
358
|
+
label: col
|
|
236
359
|
}));
|
|
237
|
-
// ✅ Update
|
|
360
|
+
// ✅ Update columns and rows
|
|
238
361
|
this._table.columns(columnDefs).rows(this._df.toRows());
|
|
239
|
-
// ✅ Force table
|
|
240
|
-
const tableElement = this._table['_tableElement'];
|
|
362
|
+
// ✅ FIX: Force full table rebuild (including pagination)
|
|
363
|
+
const tableElement = this._table['_tableElement'];
|
|
241
364
|
if (tableElement) {
|
|
242
365
|
const wrapper = tableElement.closest('.jux-table-wrapper');
|
|
243
366
|
if (wrapper) {
|
|
244
|
-
// Clear
|
|
367
|
+
// Clear table content
|
|
245
368
|
tableElement.innerHTML = '';
|
|
246
369
|
// Rebuild header
|
|
247
370
|
const thead = this._table._buildTableHeader();
|
|
@@ -252,6 +375,10 @@ export class DataFrameComponent extends BaseComponent {
|
|
|
252
375
|
tableElement.appendChild(tbody);
|
|
253
376
|
// Re-wire events
|
|
254
377
|
this._table._wireTriggerEvents(tbody);
|
|
378
|
+
// ✅ FIX: Re-build pagination controls
|
|
379
|
+
if (this._tableOptions.paginated) {
|
|
380
|
+
this._table._updatePagination(wrapper, tbody);
|
|
381
|
+
}
|
|
255
382
|
}
|
|
256
383
|
}
|
|
257
384
|
}
|
|
@@ -320,6 +447,7 @@ export class DataFrameComponent extends BaseComponent {
|
|
|
320
447
|
}
|
|
321
448
|
const upload = new FileUpload(`${this._id}-upload`, uploadOpts);
|
|
322
449
|
this._uploadRef = upload;
|
|
450
|
+
// ✅ FIX: Use the SAME logic as fromUpload() to handle multi-sheet
|
|
323
451
|
this._pendingSource = async () => {
|
|
324
452
|
upload.bind('change', async (files) => {
|
|
325
453
|
if (!files || files.length === 0)
|
|
@@ -328,9 +456,29 @@ export class DataFrameComponent extends BaseComponent {
|
|
|
328
456
|
this.state.loading = true;
|
|
329
457
|
this._updateStatus('⏳ Parsing ' + file.name + '...', 'loading');
|
|
330
458
|
try {
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
459
|
+
// ✅ Check if multi-sheet Excel
|
|
460
|
+
const isExcel = file.name.toLowerCase().endsWith('.xlsx') ||
|
|
461
|
+
file.name.toLowerCase().endsWith('.xls');
|
|
462
|
+
if (isExcel) {
|
|
463
|
+
const sheets = await this._driver.streamFileMultiSheet(file);
|
|
464
|
+
const sheetNames = Object.keys(sheets);
|
|
465
|
+
// Store first sheet to IndexedDB
|
|
466
|
+
await this._driver.store(file.name, sheets[sheetNames[0]], { source: file.name });
|
|
467
|
+
if (sheetNames.length > 1) {
|
|
468
|
+
// ✅ Multi-sheet: render tabs
|
|
469
|
+
this._renderMultiSheet(sheets, file.name);
|
|
470
|
+
}
|
|
471
|
+
else {
|
|
472
|
+
// Single sheet: render normally
|
|
473
|
+
this._setDataFrame(sheets[sheetNames[0]], file.name);
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
else {
|
|
477
|
+
// CSV/TSV: single sheet
|
|
478
|
+
const df = await this._driver.streamFile(file);
|
|
479
|
+
await this._driver.store(file.name, df, { source: file.name });
|
|
480
|
+
this._setDataFrame(df, file.name);
|
|
481
|
+
}
|
|
334
482
|
}
|
|
335
483
|
catch (err) {
|
|
336
484
|
this._triggerCallback('error', err.message, null, this);
|
|
@@ -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
|
+
// ✅ 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
|
* ═══════════════════════════════════════════════════ */
|
|
@@ -288,21 +438,21 @@ export class DataFrameComponent extends BaseComponent<DataFrameState> {
|
|
|
288
438
|
private _updateTable(): void {
|
|
289
439
|
if (!this._table || !this._df) return;
|
|
290
440
|
|
|
291
|
-
// ✅
|
|
441
|
+
// ✅ Convert string[] columns to ColumnDef[] with labels
|
|
292
442
|
const columnDefs = this._df.columns.map(col => ({
|
|
293
443
|
key: col,
|
|
294
|
-
label: col
|
|
444
|
+
label: col
|
|
295
445
|
}));
|
|
296
446
|
|
|
297
|
-
// ✅ Update
|
|
447
|
+
// ✅ Update columns and rows
|
|
298
448
|
this._table.columns(columnDefs).rows(this._df.toRows());
|
|
299
449
|
|
|
300
|
-
// ✅ Force table
|
|
301
|
-
const tableElement = this._table['_tableElement'];
|
|
450
|
+
// ✅ FIX: Force full table rebuild (including pagination)
|
|
451
|
+
const tableElement = this._table['_tableElement'];
|
|
302
452
|
if (tableElement) {
|
|
303
|
-
const wrapper = tableElement.closest('.jux-table-wrapper');
|
|
453
|
+
const wrapper = tableElement.closest('.jux-table-wrapper') as HTMLElement;
|
|
304
454
|
if (wrapper) {
|
|
305
|
-
// Clear
|
|
455
|
+
// Clear table content
|
|
306
456
|
tableElement.innerHTML = '';
|
|
307
457
|
|
|
308
458
|
// Rebuild header
|
|
@@ -316,6 +466,11 @@ export class DataFrameComponent extends BaseComponent<DataFrameState> {
|
|
|
316
466
|
|
|
317
467
|
// Re-wire events
|
|
318
468
|
(this._table as any)._wireTriggerEvents(tbody);
|
|
469
|
+
|
|
470
|
+
// ✅ FIX: Re-build pagination controls
|
|
471
|
+
if (this._tableOptions.paginated) {
|
|
472
|
+
(this._table as any)._updatePagination(wrapper, tbody);
|
|
473
|
+
}
|
|
319
474
|
}
|
|
320
475
|
}
|
|
321
476
|
}
|
|
@@ -395,16 +550,39 @@ export class DataFrameComponent extends BaseComponent<DataFrameState> {
|
|
|
395
550
|
const upload = new FileUpload(`${this._id}-upload`, uploadOpts);
|
|
396
551
|
|
|
397
552
|
this._uploadRef = upload;
|
|
553
|
+
// ✅ FIX: Use the SAME logic as fromUpload() to handle multi-sheet
|
|
398
554
|
this._pendingSource = async () => {
|
|
399
555
|
upload.bind('change', async (files: File[]) => {
|
|
400
556
|
if (!files || files.length === 0) return;
|
|
401
557
|
const file = files[0];
|
|
402
558
|
this.state.loading = true;
|
|
403
559
|
this._updateStatus('⏳ Parsing ' + file.name + '...', 'loading');
|
|
560
|
+
|
|
404
561
|
try {
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
562
|
+
// ✅ Check if multi-sheet Excel
|
|
563
|
+
const isExcel = file.name.toLowerCase().endsWith('.xlsx') ||
|
|
564
|
+
file.name.toLowerCase().endsWith('.xls');
|
|
565
|
+
|
|
566
|
+
if (isExcel) {
|
|
567
|
+
const sheets = await this._driver.streamFileMultiSheet(file);
|
|
568
|
+
const sheetNames = Object.keys(sheets);
|
|
569
|
+
|
|
570
|
+
// Store first sheet to IndexedDB
|
|
571
|
+
await this._driver.store(file.name, sheets[sheetNames[0]], { source: file.name });
|
|
572
|
+
|
|
573
|
+
if (sheetNames.length > 1) {
|
|
574
|
+
// ✅ Multi-sheet: render tabs
|
|
575
|
+
this._renderMultiSheet(sheets, file.name);
|
|
576
|
+
} else {
|
|
577
|
+
// Single sheet: render normally
|
|
578
|
+
this._setDataFrame(sheets[sheetNames[0]], file.name);
|
|
579
|
+
}
|
|
580
|
+
} else {
|
|
581
|
+
// CSV/TSV: single sheet
|
|
582
|
+
const df = await this._driver.streamFile(file);
|
|
583
|
+
await this._driver.store(file.name, df, { source: file.name });
|
|
584
|
+
this._setDataFrame(df, file.name);
|
|
585
|
+
}
|
|
408
586
|
} catch (err: any) {
|
|
409
587
|
this._triggerCallback('error', err.message, null, this);
|
|
410
588
|
this.state.loading = false;
|
|
@@ -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
|
* ═══════════════════════════════════════════════════ */
|