juxscript 1.1.166 → 1.1.168

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.
@@ -12,6 +12,8 @@ export interface DataFrameOptions {
12
12
  filterable?: boolean;
13
13
  paginated?: boolean;
14
14
  rowsPerPage?: number;
15
+ showStatus?: boolean;
16
+ icon?: string;
15
17
  style?: string;
16
18
  class?: string;
17
19
  }
@@ -30,78 +32,41 @@ export declare class DataFrameComponent extends BaseComponent<DataFrameState> {
30
32
  private _storageKey;
31
33
  private _pendingSource;
32
34
  private _inlineUpload;
35
+ private _showStatus;
36
+ private _icon;
33
37
  constructor(id: string, options?: DataFrameOptions);
34
38
  protected getTriggerEvents(): readonly string[];
35
39
  protected getCallbackEvents(): readonly string[];
36
- /**
37
- * Load from IndexedDB by storage key
38
- */
39
40
  fromStorage(key: string): this;
40
- /**
41
- * Load from a FileUpload component — auto-wires change event
42
- */
43
41
  fromUpload(upload: FileUpload): this;
44
- /**
45
- * Load from raw data — array of objects or Record<string, any[]>
46
- */
47
42
  fromData(data: Record<string, any>[] | Record<string, any[]>): this;
48
43
  /**
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')
44
+ * Add an inline file upload control.
45
+ * @param label - Button label (default: 'Upload File')
53
46
  * @param accept - File types (default: '.csv,.tsv,.txt,.xlsx,.xls')
47
+ * @param icon - Upload icon name (default: 'upload'). Pass '' to hide icon.
54
48
  */
55
- withUpload(label?: string, accept?: string): this;
56
- /**
57
- * Apply a transform to the current DataFrame and update the table
58
- */
49
+ withUpload(label?: string, accept?: string, icon?: string): this;
50
+ /** Show/hide the status bar */
51
+ showStatus(v: boolean): this;
52
+ /** Set a custom icon for the status bar */
53
+ statusIcon(v: string): this;
59
54
  apply(fn: (df: DataFrame) => DataFrame): this;
60
- /**
61
- * Filter rows
62
- */
63
55
  filter(predicate: (row: Record<string, any>, index: number) => boolean): this;
64
- /**
65
- * Select columns
66
- */
67
56
  select(...cols: string[]): this;
68
- /**
69
- * Sort by column
70
- */
71
57
  sort(col: string, descending?: boolean): this;
72
- /**
73
- * Show first N rows
74
- */
75
58
  head(n?: number): this;
76
- /**
77
- * Show last N rows
78
- */
79
59
  tail(n?: number): this;
80
- /**
81
- * Add a computed column
82
- */
83
60
  withColumn(name: string, fn: (row: Record<string, any>, index: number) => any): this;
84
- /**
85
- * Where clause
86
- */
87
61
  where(col: string, op: '==' | '!=' | '>' | '<' | '>=' | '<=' | 'contains' | 'startsWith' | 'endsWith', value: any): this;
88
- /** Get the underlying DataFrame */
89
62
  get df(): DataFrame | null;
90
- /** Get the underlying TabularDriver */
91
63
  get driver(): TabularDriver;
92
- /** Get the internal Table component */
93
64
  get table(): Table | null;
94
- /** Get describe() stats */
95
65
  describe(): Record<string, any> | null;
96
- /** Export to CSV string */
97
66
  toCSV(delimiter?: string): string;
98
- /** Export to row objects */
99
67
  toRows(): Record<string, any>[];
100
- /** Get shape */
101
68
  get shape(): [number, number];
102
- /** Get column names */
103
69
  get columns(): string[];
104
- /** Save current DataFrame to IndexedDB */
105
70
  save(key?: string): Promise<string | null>;
106
71
  striped(v: boolean): this;
107
72
  hoverable(v: boolean): this;
@@ -112,6 +77,7 @@ export declare class DataFrameComponent extends BaseComponent<DataFrameState> {
112
77
  private _updateStatus;
113
78
  private _setDataFrame;
114
79
  private _updateTable;
80
+ private _showFilterInput;
115
81
  update(prop: string, value: any): void;
116
82
  render(targetId?: string | HTMLElement | BaseComponent<any>): this;
117
83
  }
@@ -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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"}
@@ -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 { renderIcon } from './icons.js';
6
7
  const TRIGGER_EVENTS = [];
7
8
  const CALLBACK_EVENTS = ['load', 'error', 'transform'];
8
9
  export class DataFrameComponent extends BaseComponent {
@@ -24,12 +25,16 @@ export class DataFrameComponent extends BaseComponent {
24
25
  this._storageKey = null;
25
26
  this._pendingSource = null;
26
27
  this._inlineUpload = null;
28
+ this._showStatus = true;
29
+ this._icon = '';
27
30
  this._driver = new TabularDriver(options.dbName ?? 'jux-dataframes', options.storeName ?? 'frames');
31
+ this._showStatus = options.showStatus ?? true;
32
+ this._icon = options.icon ?? '';
28
33
  this._tableOptions = {
29
34
  striped: options.striped ?? true,
30
35
  hoverable: options.hoverable ?? true,
31
36
  sortable: options.sortable ?? true,
32
- filterable: options.filterable ?? true,
37
+ filterable: options.filterable ?? false, // defer until data loaded
33
38
  paginated: options.paginated ?? true,
34
39
  rowsPerPage: options.rowsPerPage ?? 25
35
40
  };
@@ -39,18 +44,17 @@ export class DataFrameComponent extends BaseComponent {
39
44
  /* ═══════════════════════════════════════════════════
40
45
  * DATA SOURCES
41
46
  * ═══════════════════════════════════════════════════ */
42
- /**
43
- * Load from IndexedDB by storage key
44
- */
45
47
  fromStorage(key) {
46
48
  this._storageKey = key;
47
- this._pendingSource = async () => {
49
+ const loadFn = async () => {
48
50
  this.state.loading = true;
51
+ this._updateStatus('⏳ Loading...', 'loading');
49
52
  try {
50
- let df = await this._driver.loadByName(key);
53
+ const df = await this._driver.loadByName(key);
51
54
  if (!df) {
52
55
  this._triggerCallback('error', 'No table found with key: ' + key, null, this);
53
56
  this.state.loading = false;
57
+ this._updateStatus('No table found: ' + key, 'empty');
54
58
  return;
55
59
  }
56
60
  this._setDataFrame(df, 'storage: ' + key);
@@ -58,42 +62,44 @@ export class DataFrameComponent extends BaseComponent {
58
62
  catch (err) {
59
63
  this._triggerCallback('error', err.message, null, this);
60
64
  this.state.loading = false;
65
+ this._updateStatus('❌ ' + err.message, 'error');
61
66
  }
62
67
  };
68
+ if (this._table) {
69
+ loadFn();
70
+ }
71
+ else {
72
+ this._pendingSource = loadFn;
73
+ }
63
74
  return this;
64
75
  }
65
- /**
66
- * Load from a FileUpload component — auto-wires change event
67
- */
68
76
  fromUpload(upload) {
69
77
  this._uploadRef = upload;
70
78
  this._pendingSource = async () => {
71
- // Wire upload's change event to parse incoming files
72
79
  upload.bind('change', async (files) => {
73
80
  if (!files || files.length === 0)
74
81
  return;
75
82
  const file = files[0];
76
83
  this.state.loading = true;
84
+ this._updateStatus('⏳ Parsing ' + file.name + '...', 'loading');
77
85
  try {
78
86
  const df = await this._driver.streamFile(file);
79
- // Auto-persist to IndexedDB
80
87
  await this._driver.store(file.name, df, { source: file.name });
81
- this._setDataFrame(df, 'upload: ' + file.name);
88
+ this._setDataFrame(df, file.name);
82
89
  }
83
90
  catch (err) {
84
91
  this._triggerCallback('error', err.message, null, this);
85
92
  this.state.loading = false;
93
+ this._updateStatus('❌ ' + err.message, 'error');
86
94
  }
87
95
  });
88
96
  };
89
97
  return this;
90
98
  }
91
- /**
92
- * Load from raw data — array of objects or Record<string, any[]>
93
- */
94
99
  fromData(data) {
95
- this._pendingSource = async () => {
100
+ const loadFn = async () => {
96
101
  this.state.loading = true;
102
+ this._updateStatus('⏳ Loading data...', 'loading');
97
103
  try {
98
104
  const df = new DataFrame(data);
99
105
  this._setDataFrame(df, 'inline data');
@@ -101,27 +107,37 @@ export class DataFrameComponent extends BaseComponent {
101
107
  catch (err) {
102
108
  this._triggerCallback('error', err.message, null, this);
103
109
  this.state.loading = false;
110
+ this._updateStatus('❌ ' + err.message, 'error');
104
111
  }
105
112
  };
113
+ if (this._table) {
114
+ loadFn();
115
+ }
116
+ else {
117
+ this._pendingSource = loadFn;
118
+ }
106
119
  return this;
107
120
  }
108
121
  /**
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')
122
+ * Add an inline file upload control.
123
+ * @param label - Button label (default: 'Upload File')
113
124
  * @param accept - File types (default: '.csv,.tsv,.txt,.xlsx,.xls')
125
+ * @param icon - Upload icon name (default: 'upload'). Pass '' to hide icon.
114
126
  */
115
- withUpload(label = 'Upload File', accept = '.csv,.tsv,.txt,.xlsx,.xls') {
116
- this._inlineUpload = { label, accept };
127
+ withUpload(label = 'Upload File', accept = '.csv,.tsv,.txt,.xlsx,.xls', icon = 'upload') {
128
+ this._inlineUpload = { label, accept, icon };
117
129
  return this;
118
130
  }
119
131
  /* ═══════════════════════════════════════════════════
120
- * TRANSFORM API (returns new DataFrameComponent view)
132
+ * UI TOGGLES
133
+ * ═══════════════════════════════════════════════════ */
134
+ /** Show/hide the status bar */
135
+ showStatus(v) { this._showStatus = v; return this; }
136
+ /** Set a custom icon for the status bar */
137
+ statusIcon(v) { this._icon = v; return this; }
138
+ /* ═══════════════════════════════════════════════════
139
+ * TRANSFORM API
121
140
  * ═══════════════════════════════════════════════════ */
122
- /**
123
- * Apply a transform to the current DataFrame and update the table
124
- */
125
141
  apply(fn) {
126
142
  if (!this._df)
127
143
  return this;
@@ -130,78 +146,38 @@ export class DataFrameComponent extends BaseComponent {
130
146
  this._triggerCallback('transform', result, null, this);
131
147
  return this;
132
148
  }
133
- /**
134
- * Filter rows
135
- */
136
149
  filter(predicate) {
137
150
  return this.apply(df => df.filter(predicate));
138
151
  }
139
- /**
140
- * Select columns
141
- */
142
152
  select(...cols) {
143
153
  return this.apply(df => df.select(...cols));
144
154
  }
145
- /**
146
- * Sort by column
147
- */
148
155
  sort(col, descending) {
149
156
  return this.apply(df => df.sort(col, descending));
150
157
  }
151
- /**
152
- * Show first N rows
153
- */
154
158
  head(n = 5) {
155
159
  return this.apply(df => df.head(n));
156
160
  }
157
- /**
158
- * Show last N rows
159
- */
160
161
  tail(n = 5) {
161
162
  return this.apply(df => df.tail(n));
162
163
  }
163
- /**
164
- * Add a computed column
165
- */
166
164
  withColumn(name, fn) {
167
165
  return this.apply(df => df.withColumn(name, fn));
168
166
  }
169
- /**
170
- * Where clause
171
- */
172
167
  where(col, op, value) {
173
168
  return this.apply(df => df.where(col, op, value));
174
169
  }
175
170
  /* ═══════════════════════════════════════════════════
176
171
  * ACCESSORS
177
172
  * ═══════════════════════════════════════════════════ */
178
- /** Get the underlying DataFrame */
179
173
  get df() { return this._df; }
180
- /** Get the underlying TabularDriver */
181
174
  get driver() { return this._driver; }
182
- /** Get the internal Table component */
183
175
  get table() { return this._table; }
184
- /** Get describe() stats */
185
- describe() {
186
- return this._df?.describe() ?? null;
187
- }
188
- /** Export to CSV string */
189
- toCSV(delimiter) {
190
- return this._df?.toCSV(delimiter) ?? '';
191
- }
192
- /** Export to row objects */
193
- toRows() {
194
- return this._df?.toRows() ?? [];
195
- }
196
- /** Get shape */
197
- get shape() {
198
- return this._df?.shape ?? [0, 0];
199
- }
200
- /** Get column names */
201
- get columns() {
202
- return this._df?.columns ?? [];
203
- }
204
- /** Save current DataFrame to IndexedDB */
176
+ describe() { return this._df?.describe() ?? null; }
177
+ toCSV(delimiter) { return this._df?.toCSV(delimiter) ?? ''; }
178
+ toRows() { return this._df?.toRows() ?? []; }
179
+ get shape() { return this._df?.shape ?? [0, 0]; }
180
+ get columns() { return this._df?.columns ?? []; }
205
181
  async save(key) {
206
182
  if (!this._df)
207
183
  return null;
@@ -220,10 +196,26 @@ export class DataFrameComponent extends BaseComponent {
220
196
  /* ═══════════════════════════════════════════════════
221
197
  * INTERNAL
222
198
  * ═══════════════════════════════════════════════════ */
223
- _updateStatus(text) {
199
+ _updateStatus(text, type = 'empty') {
224
200
  const el = document.getElementById(`${this._id}-status`);
225
- if (el)
226
- el.textContent = text;
201
+ if (!el)
202
+ return;
203
+ el.className = 'jux-dataframe-status';
204
+ if (type)
205
+ el.classList.add(`jux-dataframe-status-${type}`);
206
+ // Clear and rebuild
207
+ el.innerHTML = '';
208
+ if (this._icon && type === 'success') {
209
+ const iconEl = renderIcon(this._icon);
210
+ iconEl.style.width = '16px';
211
+ iconEl.style.height = '16px';
212
+ iconEl.style.marginRight = '6px';
213
+ iconEl.style.verticalAlign = 'middle';
214
+ el.appendChild(iconEl);
215
+ }
216
+ const span = document.createElement('span');
217
+ span.textContent = text;
218
+ el.appendChild(span);
227
219
  }
228
220
  _setDataFrame(df, sourceName) {
229
221
  this._df = df;
@@ -237,7 +229,11 @@ export class DataFrameComponent extends BaseComponent {
237
229
  this._df = df.select(...cleanCols);
238
230
  }
239
231
  this._updateTable();
240
- this._updateStatus(`✅ ${sourceName} — ${this._df.height} rows × ${this._df.width} cols`);
232
+ this._updateStatus(`${sourceName} — ${this._df.height} rows × ${this._df.width} cols`, 'success');
233
+ // Enable filter now that data exists
234
+ if (this._tableOptions.filterable) {
235
+ this._showFilterInput();
236
+ }
241
237
  this._triggerCallback('load', this._df, null, this);
242
238
  }
243
239
  _updateTable() {
@@ -245,6 +241,49 @@ export class DataFrameComponent extends BaseComponent {
245
241
  return;
246
242
  this._table.columns(this._df.columns).rows(this._df.toRows());
247
243
  }
244
+ _showFilterInput() {
245
+ const wrapper = document.getElementById(this._id);
246
+ if (!wrapper)
247
+ return;
248
+ // Only add once
249
+ if (wrapper.querySelector('.jux-dataframe-filter'))
250
+ return;
251
+ const filterContainer = document.createElement('div');
252
+ filterContainer.className = 'jux-dataframe-filter';
253
+ const input = document.createElement('input');
254
+ input.type = 'text';
255
+ input.placeholder = 'Filter rows...';
256
+ input.className = 'jux-input-element jux-dataframe-filter-input';
257
+ const iconEl = renderIcon('search');
258
+ iconEl.style.width = '16px';
259
+ iconEl.style.height = '16px';
260
+ const iconWrap = document.createElement('span');
261
+ iconWrap.className = 'jux-dataframe-filter-icon';
262
+ iconWrap.appendChild(iconEl);
263
+ filterContainer.appendChild(iconWrap);
264
+ filterContainer.appendChild(input);
265
+ // Insert before the table
266
+ const tableWrapper = wrapper.querySelector('.jux-table-wrapper');
267
+ if (tableWrapper) {
268
+ wrapper.insertBefore(filterContainer, tableWrapper);
269
+ }
270
+ else {
271
+ wrapper.appendChild(filterContainer);
272
+ }
273
+ input.addEventListener('input', () => {
274
+ if (!this._df)
275
+ return;
276
+ const text = input.value.toLowerCase();
277
+ if (!text) {
278
+ this._table?.rows(this._df.toRows());
279
+ return;
280
+ }
281
+ const filtered = this._df.filter((row) => {
282
+ return Object.values(row).some(v => v !== null && v !== undefined && String(v).toLowerCase().includes(text));
283
+ });
284
+ this._table?.rows(filtered.toRows());
285
+ });
286
+ }
248
287
  update(prop, value) { }
249
288
  /* ═══════════════════════════════════════════════════
250
289
  * RENDER
@@ -259,13 +298,16 @@ export class DataFrameComponent extends BaseComponent {
259
298
  wrapper.className += ` ${className}`;
260
299
  if (style)
261
300
  wrapper.setAttribute('style', style);
262
- // Inline upload (if withUpload was called)
301
+ // Inline upload
263
302
  if (this._inlineUpload) {
264
- const upload = new FileUpload(`${this._id}-upload`, {
303
+ const uploadOpts = {
265
304
  label: this._inlineUpload.label,
266
- accept: this._inlineUpload.accept
267
- });
268
- // Wire it as a fromUpload source
305
+ accept: this._inlineUpload.accept,
306
+ };
307
+ if (this._inlineUpload.icon) {
308
+ uploadOpts.icon = this._inlineUpload.icon;
309
+ }
310
+ const upload = new FileUpload(`${this._id}-upload`, uploadOpts);
269
311
  this._uploadRef = upload;
270
312
  this._pendingSource = async () => {
271
313
  upload.bind('change', async (files) => {
@@ -273,7 +315,7 @@ export class DataFrameComponent extends BaseComponent {
273
315
  return;
274
316
  const file = files[0];
275
317
  this.state.loading = true;
276
- this._updateStatus('⏳ Parsing ' + file.name + '...');
318
+ this._updateStatus('⏳ Parsing ' + file.name + '...', 'loading');
277
319
  try {
278
320
  const df = await this._driver.streamFile(file);
279
321
  await this._driver.store(file.name, df, { source: file.name });
@@ -282,11 +324,10 @@ export class DataFrameComponent extends BaseComponent {
282
324
  catch (err) {
283
325
  this._triggerCallback('error', err.message, null, this);
284
326
  this.state.loading = false;
285
- this._updateStatus('❌ ' + err.message);
327
+ this._updateStatus('❌ ' + err.message, 'error');
286
328
  }
287
329
  });
288
330
  };
289
- // Render upload into wrapper before status/table
290
331
  const uploadContainer = document.createElement('div');
291
332
  uploadContainer.className = 'jux-dataframe-upload';
292
333
  uploadContainer.id = `${this._id}-upload-container`;
@@ -297,19 +338,20 @@ export class DataFrameComponent extends BaseComponent {
297
338
  else {
298
339
  container.appendChild(wrapper);
299
340
  }
300
- // Status bar
301
- const statusBar = document.createElement('div');
302
- statusBar.className = 'jux-dataframe-status';
303
- statusBar.id = `${this._id}-status`;
304
- statusBar.style.cssText = 'font-size:13px;color:#888;margin-bottom:8px;';
305
- statusBar.textContent = 'No data loaded.';
306
- wrapper.appendChild(statusBar);
307
- // Create internal table
341
+ // Status bar (conditional)
342
+ if (this._showStatus) {
343
+ const statusBar = document.createElement('div');
344
+ statusBar.className = 'jux-dataframe-status jux-dataframe-status-empty';
345
+ statusBar.id = `${this._id}-status`;
346
+ statusBar.textContent = 'No data loaded.';
347
+ wrapper.appendChild(statusBar);
348
+ }
349
+ // Table — filterable is false initially; we enable it after data loads
308
350
  const tbl = new Table(`${this._id}-table`, {
309
351
  striped: this._tableOptions.striped,
310
352
  hoverable: this._tableOptions.hoverable,
311
353
  sortable: this._tableOptions.sortable,
312
- filterable: this._tableOptions.filterable,
354
+ filterable: false, // we handle filtering ourselves
313
355
  paginated: this._tableOptions.paginated,
314
356
  rowsPerPage: this._tableOptions.rowsPerPage
315
357
  });