juxscript 1.1.165 → 1.1.166

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.
@@ -29,6 +29,7 @@ export declare class DataFrameComponent extends BaseComponent<DataFrameState> {
29
29
  private _uploadRef;
30
30
  private _storageKey;
31
31
  private _pendingSource;
32
+ private _inlineUpload;
32
33
  constructor(id: string, options?: DataFrameOptions);
33
34
  protected getTriggerEvents(): readonly string[];
34
35
  protected getCallbackEvents(): readonly string[];
@@ -44,6 +45,14 @@ export declare class DataFrameComponent extends BaseComponent<DataFrameState> {
44
45
  * Load from raw data — array of objects or Record<string, any[]>
45
46
  */
46
47
  fromData(data: Record<string, any>[] | Record<string, any[]>): this;
48
+ /**
49
+ * Add an inline file upload control above the table.
50
+ * Auto-wires parsing, storage, and display — zero callbacks needed.
51
+ *
52
+ * @param label - Button/label text (default: 'Upload File')
53
+ * @param accept - File types (default: '.csv,.tsv,.txt,.xlsx,.xls')
54
+ */
55
+ withUpload(label?: string, accept?: string): this;
47
56
  /**
48
57
  * Apply a transform to the current DataFrame and update the table
49
58
  */
@@ -100,6 +109,7 @@ export declare class DataFrameComponent extends BaseComponent<DataFrameState> {
100
109
  filterable(v: boolean): this;
101
110
  paginated(v: boolean): this;
102
111
  rowsPerPage(v: number): this;
112
+ private _updateStatus;
103
113
  private _setDataFrame;
104
114
  private _updateTable;
105
115
  update(prop: string, value: any): void;
@@ -1 +1 @@
1
- {"version":3,"file":"dataframe.d.ts","sourceRoot":"","sources":["dataframe.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"dataframe.d.ts","sourceRoot":"","sources":["dataframe.ts"],"names":[],"mappings":"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"}
@@ -1,6 +1,7 @@
1
1
  import { BaseComponent } from './base/BaseComponent.js';
2
2
  import { DataFrame } from '../storage/DataFrame.js';
3
3
  import { TabularDriver } from '../storage/TabularDriver.js';
4
+ import { FileUpload } from './fileupload.js';
4
5
  import { Table } from './table.js';
5
6
  const TRIGGER_EVENTS = [];
6
7
  const CALLBACK_EVENTS = ['load', 'error', 'transform'];
@@ -22,6 +23,7 @@ export class DataFrameComponent extends BaseComponent {
22
23
  this._uploadRef = null;
23
24
  this._storageKey = null;
24
25
  this._pendingSource = null;
26
+ this._inlineUpload = null;
25
27
  this._driver = new TabularDriver(options.dbName ?? 'jux-dataframes', options.storeName ?? 'frames');
26
28
  this._tableOptions = {
27
29
  striped: options.striped ?? true,
@@ -103,6 +105,17 @@ export class DataFrameComponent extends BaseComponent {
103
105
  };
104
106
  return this;
105
107
  }
108
+ /**
109
+ * Add an inline file upload control above the table.
110
+ * Auto-wires parsing, storage, and display — zero callbacks needed.
111
+ *
112
+ * @param label - Button/label text (default: 'Upload File')
113
+ * @param accept - File types (default: '.csv,.tsv,.txt,.xlsx,.xls')
114
+ */
115
+ withUpload(label = 'Upload File', accept = '.csv,.tsv,.txt,.xlsx,.xls') {
116
+ this._inlineUpload = { label, accept };
117
+ return this;
118
+ }
106
119
  /* ═══════════════════════════════════════════════════
107
120
  * TRANSFORM API (returns new DataFrameComponent view)
108
121
  * ═══════════════════════════════════════════════════ */
@@ -207,6 +220,11 @@ export class DataFrameComponent extends BaseComponent {
207
220
  /* ═══════════════════════════════════════════════════
208
221
  * INTERNAL
209
222
  * ═══════════════════════════════════════════════════ */
223
+ _updateStatus(text) {
224
+ const el = document.getElementById(`${this._id}-status`);
225
+ if (el)
226
+ el.textContent = text;
227
+ }
210
228
  _setDataFrame(df, sourceName) {
211
229
  this._df = df;
212
230
  this.state.loaded = true;
@@ -214,12 +232,12 @@ export class DataFrameComponent extends BaseComponent {
214
232
  this.state.sourceName = sourceName;
215
233
  this.state.rowCount = df.height;
216
234
  this.state.colCount = df.width;
217
- // Clean __EMPTY columns from xlsx artifacts
218
235
  const cleanCols = df.columns.filter(c => !c.startsWith('__EMPTY'));
219
236
  if (cleanCols.length < df.columns.length) {
220
237
  this._df = df.select(...cleanCols);
221
238
  }
222
239
  this._updateTable();
240
+ this._updateStatus(`✅ ${sourceName} — ${this._df.height} rows × ${this._df.width} cols`);
223
241
  this._triggerCallback('load', this._df, null, this);
224
242
  }
225
243
  _updateTable() {
@@ -241,6 +259,44 @@ export class DataFrameComponent extends BaseComponent {
241
259
  wrapper.className += ` ${className}`;
242
260
  if (style)
243
261
  wrapper.setAttribute('style', style);
262
+ // Inline upload (if withUpload was called)
263
+ if (this._inlineUpload) {
264
+ const upload = new FileUpload(`${this._id}-upload`, {
265
+ label: this._inlineUpload.label,
266
+ accept: this._inlineUpload.accept
267
+ });
268
+ // Wire it as a fromUpload source
269
+ this._uploadRef = upload;
270
+ this._pendingSource = async () => {
271
+ upload.bind('change', async (files) => {
272
+ if (!files || files.length === 0)
273
+ return;
274
+ const file = files[0];
275
+ this.state.loading = true;
276
+ this._updateStatus('⏳ Parsing ' + file.name + '...');
277
+ try {
278
+ const df = await this._driver.streamFile(file);
279
+ await this._driver.store(file.name, df, { source: file.name });
280
+ this._setDataFrame(df, file.name);
281
+ }
282
+ catch (err) {
283
+ this._triggerCallback('error', err.message, null, this);
284
+ this.state.loading = false;
285
+ this._updateStatus('❌ ' + err.message);
286
+ }
287
+ });
288
+ };
289
+ // Render upload into wrapper before status/table
290
+ const uploadContainer = document.createElement('div');
291
+ uploadContainer.className = 'jux-dataframe-upload';
292
+ uploadContainer.id = `${this._id}-upload-container`;
293
+ wrapper.appendChild(uploadContainer);
294
+ container.appendChild(wrapper);
295
+ upload.render(uploadContainer);
296
+ }
297
+ else {
298
+ container.appendChild(wrapper);
299
+ }
244
300
  // Status bar
245
301
  const statusBar = document.createElement('div');
246
302
  statusBar.className = 'jux-dataframe-status';
@@ -248,9 +304,8 @@ export class DataFrameComponent extends BaseComponent {
248
304
  statusBar.style.cssText = 'font-size:13px;color:#888;margin-bottom:8px;';
249
305
  statusBar.textContent = 'No data loaded.';
250
306
  wrapper.appendChild(statusBar);
251
- container.appendChild(wrapper);
252
307
  // Create internal table
253
- this._table = new Table(`${this._id}-table`, {
308
+ const tbl = new Table(`${this._id}-table`, {
254
309
  striped: this._tableOptions.striped,
255
310
  hoverable: this._tableOptions.hoverable,
256
311
  sortable: this._tableOptions.sortable,
@@ -258,30 +313,13 @@ export class DataFrameComponent extends BaseComponent {
258
313
  paginated: this._tableOptions.paginated,
259
314
  rowsPerPage: this._tableOptions.rowsPerPage
260
315
  });
261
- this._table.render(wrapper);
262
- // Watch state for status updates
263
- const origUpdate = this.update.bind(this);
264
- this.update = (prop, value) => {
265
- origUpdate(prop, value);
266
- if (prop === 'loaded' || prop === 'loading' || prop === 'rowCount' || prop === 'colCount' || prop === 'sourceName') {
267
- const el = document.getElementById(`${this._id}-status`);
268
- if (el) {
269
- if (this.state.loading) {
270
- el.textContent = '⏳ Loading...';
271
- }
272
- else if (this.state.loaded) {
273
- el.textContent = `✅ ${this.state.sourceName} — ${this.state.rowCount} rows × ${this.state.colCount} cols`;
274
- }
275
- else {
276
- el.textContent = 'No data loaded.';
277
- }
278
- }
279
- }
280
- };
316
+ tbl.render(wrapper);
317
+ this._table = tbl;
281
318
  // Execute pending data source
282
319
  if (this._pendingSource) {
283
- this._pendingSource();
320
+ const fn = this._pendingSource;
284
321
  this._pendingSource = null;
322
+ fn();
285
323
  }
286
324
  this._wireStandardEvents(wrapper);
287
325
  return this;
@@ -42,6 +42,7 @@ export class DataFrameComponent extends BaseComponent<DataFrameState> {
42
42
  private _uploadRef: FileUpload | null = null;
43
43
  private _storageKey: string | null = null;
44
44
  private _pendingSource: (() => Promise<void>) | null = null;
45
+ private _inlineUpload: { label: string; accept: string } | null = null;
45
46
 
46
47
  constructor(id: string, options: DataFrameOptions = {}) {
47
48
  super(id, {
@@ -144,6 +145,18 @@ export class DataFrameComponent extends BaseComponent<DataFrameState> {
144
145
  return this;
145
146
  }
146
147
 
148
+ /**
149
+ * Add an inline file upload control above the table.
150
+ * Auto-wires parsing, storage, and display — zero callbacks needed.
151
+ *
152
+ * @param label - Button/label text (default: 'Upload File')
153
+ * @param accept - File types (default: '.csv,.tsv,.txt,.xlsx,.xls')
154
+ */
155
+ withUpload(label: string = 'Upload File', accept: string = '.csv,.tsv,.txt,.xlsx,.xls'): this {
156
+ this._inlineUpload = { label, accept };
157
+ return this;
158
+ }
159
+
147
160
  /* ═══════════════════════════════════════════════════
148
161
  * TRANSFORM API (returns new DataFrameComponent view)
149
162
  * ═══════════════════════════════════════════════════ */
@@ -268,6 +281,11 @@ export class DataFrameComponent extends BaseComponent<DataFrameState> {
268
281
  * INTERNAL
269
282
  * ═══════════════════════════════════════════════════ */
270
283
 
284
+ private _updateStatus(text: string): void {
285
+ const el = document.getElementById(`${this._id}-status`);
286
+ if (el) el.textContent = text;
287
+ }
288
+
271
289
  private _setDataFrame(df: DataFrame, sourceName: string): void {
272
290
  this._df = df;
273
291
  this.state.loaded = true;
@@ -276,13 +294,13 @@ export class DataFrameComponent extends BaseComponent<DataFrameState> {
276
294
  this.state.rowCount = df.height;
277
295
  this.state.colCount = df.width;
278
296
 
279
- // Clean __EMPTY columns from xlsx artifacts
280
297
  const cleanCols = df.columns.filter(c => !c.startsWith('__EMPTY'));
281
298
  if (cleanCols.length < df.columns.length) {
282
299
  this._df = df.select(...cleanCols);
283
300
  }
284
301
 
285
302
  this._updateTable();
303
+ this._updateStatus(`✅ ${sourceName} — ${this._df!.height} rows × ${this._df!.width} cols`);
286
304
  this._triggerCallback('load', this._df, null, this);
287
305
  }
288
306
 
@@ -307,6 +325,45 @@ export class DataFrameComponent extends BaseComponent<DataFrameState> {
307
325
  if (className) wrapper.className += ` ${className}`;
308
326
  if (style) wrapper.setAttribute('style', style);
309
327
 
328
+ // Inline upload (if withUpload was called)
329
+ if (this._inlineUpload) {
330
+ const upload = new FileUpload(`${this._id}-upload`, {
331
+ label: this._inlineUpload.label,
332
+ accept: this._inlineUpload.accept
333
+ });
334
+
335
+ // Wire it as a fromUpload source
336
+ this._uploadRef = upload;
337
+ this._pendingSource = async () => {
338
+ upload.bind('change', async (files: File[]) => {
339
+ if (!files || files.length === 0) return;
340
+ const file = files[0];
341
+ this.state.loading = true;
342
+ this._updateStatus('⏳ Parsing ' + file.name + '...');
343
+
344
+ try {
345
+ const df = await this._driver.streamFile(file);
346
+ await this._driver.store(file.name, df, { source: file.name });
347
+ this._setDataFrame(df, file.name);
348
+ } catch (err: any) {
349
+ this._triggerCallback('error', err.message, null, this);
350
+ this.state.loading = false;
351
+ this._updateStatus('❌ ' + err.message);
352
+ }
353
+ });
354
+ };
355
+
356
+ // Render upload into wrapper before status/table
357
+ const uploadContainer = document.createElement('div');
358
+ uploadContainer.className = 'jux-dataframe-upload';
359
+ uploadContainer.id = `${this._id}-upload-container`;
360
+ wrapper.appendChild(uploadContainer);
361
+ container.appendChild(wrapper);
362
+ upload.render(uploadContainer);
363
+ } else {
364
+ container.appendChild(wrapper);
365
+ }
366
+
310
367
  // Status bar
311
368
  const statusBar = document.createElement('div');
312
369
  statusBar.className = 'jux-dataframe-status';
@@ -315,10 +372,8 @@ export class DataFrameComponent extends BaseComponent<DataFrameState> {
315
372
  statusBar.textContent = 'No data loaded.';
316
373
  wrapper.appendChild(statusBar);
317
374
 
318
- container.appendChild(wrapper);
319
-
320
375
  // Create internal table
321
- this._table = new Table(`${this._id}-table`, {
376
+ const tbl = new Table(`${this._id}-table`, {
322
377
  striped: this._tableOptions.striped,
323
378
  hoverable: this._tableOptions.hoverable,
324
379
  sortable: this._tableOptions.sortable,
@@ -326,30 +381,14 @@ export class DataFrameComponent extends BaseComponent<DataFrameState> {
326
381
  paginated: this._tableOptions.paginated,
327
382
  rowsPerPage: this._tableOptions.rowsPerPage
328
383
  });
329
- this._table.render(wrapper);
330
-
331
- // Watch state for status updates
332
- const origUpdate = this.update.bind(this);
333
- this.update = (prop: string, value: any) => {
334
- origUpdate(prop, value);
335
- if (prop === 'loaded' || prop === 'loading' || prop === 'rowCount' || prop === 'colCount' || prop === 'sourceName') {
336
- const el = document.getElementById(`${this._id}-status`);
337
- if (el) {
338
- if (this.state.loading) {
339
- el.textContent = '⏳ Loading...';
340
- } else if (this.state.loaded) {
341
- el.textContent = `✅ ${this.state.sourceName} — ${this.state.rowCount} rows × ${this.state.colCount} cols`;
342
- } else {
343
- el.textContent = 'No data loaded.';
344
- }
345
- }
346
- }
347
- };
384
+ tbl.render(wrapper);
385
+ this._table = tbl;
348
386
 
349
387
  // Execute pending data source
350
388
  if (this._pendingSource) {
351
- this._pendingSource();
389
+ const fn = this._pendingSource;
352
390
  this._pendingSource = null;
391
+ fn();
353
392
  }
354
393
 
355
394
  this._wireStandardEvents(wrapper);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "juxscript",
3
- "version": "1.1.165",
3
+ "version": "1.1.166",
4
4
  "type": "module",
5
5
  "description": "A JavaScript UX authorship platform",
6
6
  "main": "index.js",