juxscript 1.1.211 → 1.1.212

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.
@@ -20,6 +20,8 @@ export interface DataFrameOptions {
20
20
  showReshapeWarning?: boolean;
21
21
  style?: string;
22
22
  class?: string;
23
+ persistToIndexedDB?: boolean;
24
+ clearStorageOnFileRemove?: boolean;
23
25
  }
24
26
  type DataFrameState = BaseState & {
25
27
  loaded: boolean;
@@ -47,6 +49,14 @@ export declare class DataFrameComponent extends BaseComponent<DataFrameState> {
47
49
  private _rawFileData;
48
50
  private _reshapeModal;
49
51
  private _reshapeModalRendered;
52
+ private _persistToIndexedDB;
53
+ private _clearStorageOnFileRemove;
54
+ private _uploadButtonLabel;
55
+ private _uploadButtonIcon;
56
+ private _uploadButtonVariant;
57
+ private _uploadAccept;
58
+ private _uploadDescription;
59
+ private _showUploadIcon;
50
60
  constructor(id: string, options?: DataFrameOptions);
51
61
  protected getTriggerEvents(): readonly string[];
52
62
  protected getCallbackEvents(): readonly string[];
@@ -54,6 +64,42 @@ export declare class DataFrameComponent extends BaseComponent<DataFrameState> {
54
64
  fromUpload(upload: FileUpload): this;
55
65
  fromData(data: Record<string, any>[] | Record<string, any[]>): this;
56
66
  withUpload(label?: string, accept?: string, icon?: string): this;
67
+ /**
68
+ * Set upload button label
69
+ */
70
+ uploadLabel(label: string): this;
71
+ /**
72
+ * Set upload button icon
73
+ */
74
+ uploadIcon(icon: string): this;
75
+ /**
76
+ * Set upload button variant (outline, primary, ghost, etc.)
77
+ */
78
+ uploadVariant(variant: string): this;
79
+ /**
80
+ * Set accepted file types
81
+ */
82
+ uploadAccept(accept: string): this;
83
+ /**
84
+ * Set description text below the upload button
85
+ */
86
+ uploadDescription(description: string): this;
87
+ /**
88
+ * Show/hide upload icon
89
+ */
90
+ showUploadIcon(show: boolean): this;
91
+ /**
92
+ * Enable/disable IndexedDB persistence (default: false = session only)
93
+ */
94
+ persistToIndexedDB(enabled: boolean): this;
95
+ /**
96
+ * Clear stored data when file is removed (default: true)
97
+ */
98
+ clearStorageOnFileRemove(enabled: boolean): this;
99
+ /**
100
+ * Clear the current data and reset the table
101
+ */
102
+ clear(): Promise<this>;
57
103
  showStatus(v: boolean): this;
58
104
  statusIcon(v: string): this;
59
105
  apply(fn: (df: DataFrame) => DataFrame): this;
@@ -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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"}
@@ -39,6 +39,16 @@ export class DataFrameComponent extends BaseComponent {
39
39
  this._rawFileData = null;
40
40
  this._reshapeModal = null;
41
41
  this._reshapeModalRendered = false;
42
+ // ✅ NEW: Storage configuration
43
+ this._persistToIndexedDB = false;
44
+ this._clearStorageOnFileRemove = true;
45
+ // ✅ NEW: Upload button styling
46
+ this._uploadButtonLabel = 'Upload File';
47
+ this._uploadButtonIcon = 'upload';
48
+ this._uploadButtonVariant = 'outline';
49
+ this._uploadAccept = '.csv,.tsv,.txt,.xlsx,.xls';
50
+ this._uploadDescription = '';
51
+ this._showUploadIcon = true;
42
52
  this._driver = new TabularDriver(options.dbName ?? 'jux-dataframes', options.storeName ?? 'frames');
43
53
  this._showStatus = options.showStatus ?? true;
44
54
  this._icon = options.icon ?? '';
@@ -54,6 +64,9 @@ export class DataFrameComponent extends BaseComponent {
54
64
  this._sheetChunkSize = options.sheetChunkSize ?? 10000;
55
65
  this._maxFileSize = options.maxFileSize ?? 50;
56
66
  this._showReshapeWarning = options.showReshapeWarning ?? true;
67
+ // ✅ NEW: Storage options
68
+ this._persistToIndexedDB = options.persistToIndexedDB ?? false;
69
+ this._clearStorageOnFileRemove = options.clearStorageOnFileRemove ?? true;
57
70
  }
58
71
  getTriggerEvents() { return TRIGGER_EVENTS; }
59
72
  getCallbackEvents() { return CALLBACK_EVENTS; }
@@ -124,6 +137,118 @@ export class DataFrameComponent extends BaseComponent {
124
137
  }
125
138
  withUpload(label = 'Upload File', accept = '.csv,.tsv,.txt,.xlsx,.xls', icon = 'upload') {
126
139
  this._inlineUpload = { label, accept, icon };
140
+ this._uploadButtonLabel = label;
141
+ this._uploadAccept = accept;
142
+ this._uploadButtonIcon = icon;
143
+ return this;
144
+ }
145
+ /**
146
+ * Set upload button label
147
+ */
148
+ uploadLabel(label) {
149
+ this._uploadButtonLabel = label;
150
+ if (this._inlineUpload)
151
+ this._inlineUpload.label = label;
152
+ return this;
153
+ }
154
+ /**
155
+ * Set upload button icon
156
+ */
157
+ uploadIcon(icon) {
158
+ this._uploadButtonIcon = icon;
159
+ this._showUploadIcon = !!icon;
160
+ if (this._inlineUpload)
161
+ this._inlineUpload.icon = icon;
162
+ return this;
163
+ }
164
+ /**
165
+ * Set upload button variant (outline, primary, ghost, etc.)
166
+ */
167
+ uploadVariant(variant) {
168
+ this._uploadButtonVariant = variant;
169
+ return this;
170
+ }
171
+ /**
172
+ * Set accepted file types
173
+ */
174
+ uploadAccept(accept) {
175
+ this._uploadAccept = accept;
176
+ if (this._inlineUpload)
177
+ this._inlineUpload.accept = accept;
178
+ return this;
179
+ }
180
+ /**
181
+ * Set description text below the upload button
182
+ */
183
+ uploadDescription(description) {
184
+ this._uploadDescription = description;
185
+ return this;
186
+ }
187
+ /**
188
+ * Show/hide upload icon
189
+ */
190
+ showUploadIcon(show) {
191
+ this._showUploadIcon = show;
192
+ return this;
193
+ }
194
+ /* ═══════════════════════════════════════════════════
195
+ * STORAGE OPTIONS (fluent API)
196
+ * ═══════════════════════════════════════════════════ */
197
+ /**
198
+ * Enable/disable IndexedDB persistence (default: false = session only)
199
+ */
200
+ persistToIndexedDB(enabled) {
201
+ this._persistToIndexedDB = enabled;
202
+ return this;
203
+ }
204
+ /**
205
+ * Clear stored data when file is removed (default: true)
206
+ */
207
+ clearStorageOnFileRemove(enabled) {
208
+ this._clearStorageOnFileRemove = enabled;
209
+ return this;
210
+ }
211
+ /* ═══════════════════════════════════════════════════
212
+ * CLEAR / RESET
213
+ * ═══════════════════════════════════════════════════ */
214
+ /**
215
+ * Clear the current data and reset the table
216
+ */
217
+ async clear() {
218
+ // Clear storage if enabled
219
+ if (this._clearStorageOnFileRemove && this._persistToIndexedDB && this._rawFileData?.file) {
220
+ try {
221
+ const tables = await this._driver.list();
222
+ const matchingTables = tables.filter(t => t.name === this._rawFileData.file.name);
223
+ for (const table of matchingTables) {
224
+ await this._driver.delete(table.id);
225
+ }
226
+ }
227
+ catch (err) {
228
+ console.warn('[DataFrame] Failed to clear storage:', err);
229
+ }
230
+ }
231
+ // Reset state
232
+ this._df = null;
233
+ this._rawFileData = null;
234
+ this._sheets.clear();
235
+ this.state.loaded = false;
236
+ this.state.sourceName = '';
237
+ this.state.rowCount = 0;
238
+ this.state.colCount = 0;
239
+ // Clear table display
240
+ if (this._table) {
241
+ this._table.columns([]).rows([]);
242
+ }
243
+ // Remove tabs if present
244
+ const wrapper = document.getElementById(this._id);
245
+ if (wrapper) {
246
+ const existingTabs = wrapper.querySelector('.jux-tabs');
247
+ if (existingTabs)
248
+ existingTabs.remove();
249
+ }
250
+ // Reset status
251
+ this._updateStatus('No data loaded.', 'empty');
127
252
  return this;
128
253
  }
129
254
  /* ═══════════════════════════════════════════════════
@@ -193,7 +318,7 @@ export class DataFrameComponent extends BaseComponent {
193
318
  sheetChunkSize(v) { this._sheetChunkSize = v; return this; }
194
319
  maxFileSize(mb) { this._maxFileSize = mb; return this; }
195
320
  /* ═══════════════════════════════════════════════════
196
- * FILE HANDLING
321
+ * FILE HANDLING (modified to use storage options)
197
322
  * ═══════════════════════════════════════════════════ */
198
323
  async _handleFile(file) {
199
324
  const fileSizeMB = file.size / (1024 * 1024);
@@ -222,7 +347,10 @@ export class DataFrameComponent extends BaseComponent {
222
347
  this.state.loading = false;
223
348
  return;
224
349
  }
225
- await this._driver.store(file.name, sheets[sheetNames[0]], { source: file.name });
350
+ // ✅ Only store if persistence is enabled
351
+ if (this._persistToIndexedDB) {
352
+ await this._driver.store(file.name, sheets[sheetNames[0]], { source: file.name });
353
+ }
226
354
  if (sheetNames.length > 1) {
227
355
  this._renderMultiSheet(sheets, file.name);
228
356
  }
@@ -237,7 +365,10 @@ export class DataFrameComponent extends BaseComponent {
237
365
  autoDetectDelimiter: true,
238
366
  hasHeader: true
239
367
  });
240
- await this._driver.store(file.name, df, { source: file.name });
368
+ // ✅ Only store if persistence is enabled
369
+ if (this._persistToIndexedDB) {
370
+ await this._driver.store(file.name, df, { source: file.name });
371
+ }
241
372
  this._setDataFrame(df, file.name);
242
373
  }
243
374
  }
@@ -784,7 +915,7 @@ export class DataFrameComponent extends BaseComponent {
784
915
  });
785
916
  }
786
917
  /* ═══════════════════════════════════════════════════
787
- * UPDATE & RENDER
918
+ * UPDATE & RENDER (modified for better upload styling)
788
919
  * ═══════════════════════════════════════════════════ */
789
920
  update(_prop, _value) { }
790
921
  render(targetId) {
@@ -798,26 +929,43 @@ export class DataFrameComponent extends BaseComponent {
798
929
  if (style)
799
930
  wrapper.setAttribute('style', style);
800
931
  if (this._inlineUpload) {
932
+ // ✅ NEW: Enhanced upload area with better styling
933
+ const uploadArea = document.createElement('div');
934
+ uploadArea.className = 'jux-dataframe-upload-area';
935
+ uploadArea.id = `${this._id}-upload-area`;
801
936
  const uploadOpts = {
802
- label: this._inlineUpload.label,
803
- accept: this._inlineUpload.accept,
937
+ label: this._uploadButtonLabel,
938
+ accept: this._uploadAccept,
939
+ variant: this._uploadButtonVariant,
804
940
  };
805
- if (this._inlineUpload.icon) {
806
- uploadOpts.icon = this._inlineUpload.icon;
941
+ if (this._showUploadIcon && this._uploadButtonIcon) {
942
+ uploadOpts.icon = this._uploadButtonIcon;
807
943
  }
808
944
  const upload = new FileUpload(`${this._id}-upload`, uploadOpts);
809
945
  this._uploadRef = upload;
946
+ // ✅ Handle file change AND file clear
810
947
  this._pendingSource = async () => {
811
948
  upload.bind('change', async (files) => {
812
- if (!files || files.length === 0)
949
+ if (!files || files.length === 0) {
950
+ // File was cleared - reset the table
951
+ await this.clear();
813
952
  return;
953
+ }
814
954
  await this._handleFile(files[0]);
815
955
  });
816
956
  };
817
957
  const uploadContainer = document.createElement('div');
818
958
  uploadContainer.className = 'jux-dataframe-upload';
819
959
  uploadContainer.id = `${this._id}-upload-container`;
820
- wrapper.appendChild(uploadContainer);
960
+ uploadArea.appendChild(uploadContainer);
961
+ // ✅ Optional description text
962
+ if (this._uploadDescription) {
963
+ const descEl = document.createElement('div');
964
+ descEl.className = 'jux-dataframe-upload-description';
965
+ descEl.textContent = this._uploadDescription;
966
+ uploadArea.appendChild(descEl);
967
+ }
968
+ wrapper.appendChild(uploadArea);
821
969
  container.appendChild(wrapper);
822
970
  upload.render(uploadContainer);
823
971
  }
@@ -28,6 +28,9 @@ export interface DataFrameOptions {
28
28
  showReshapeWarning?: boolean;
29
29
  style?: string;
30
30
  class?: string;
31
+ // ✅ NEW: Storage options
32
+ persistToIndexedDB?: boolean; // Default: false (session only)
33
+ clearStorageOnFileRemove?: boolean; // Default: true
31
34
  }
32
35
 
33
36
  type DataFrameState = BaseState & {
@@ -64,6 +67,16 @@ export class DataFrameComponent extends BaseComponent<DataFrameState> {
64
67
  private _rawFileData: { file: File; text?: string; isExcel?: boolean } | null = null;
65
68
  private _reshapeModal: Modal | null = null;
66
69
  private _reshapeModalRendered: boolean = false;
70
+ // ✅ NEW: Storage configuration
71
+ private _persistToIndexedDB: boolean = false;
72
+ private _clearStorageOnFileRemove: boolean = true;
73
+ // ✅ NEW: Upload button styling
74
+ private _uploadButtonLabel: string = 'Upload File';
75
+ private _uploadButtonIcon: string = 'upload';
76
+ private _uploadButtonVariant: string = 'outline';
77
+ private _uploadAccept: string = '.csv,.tsv,.txt,.xlsx,.xls';
78
+ private _uploadDescription: string = '';
79
+ private _showUploadIcon: boolean = true;
67
80
 
68
81
  constructor(id: string, options: DataFrameOptions = {}) {
69
82
  super(id, {
@@ -98,6 +111,9 @@ export class DataFrameComponent extends BaseComponent<DataFrameState> {
98
111
  this._sheetChunkSize = options.sheetChunkSize ?? 10000;
99
112
  this._maxFileSize = options.maxFileSize ?? 50;
100
113
  this._showReshapeWarning = options.showReshapeWarning ?? true;
114
+ // ✅ NEW: Storage options
115
+ this._persistToIndexedDB = options.persistToIndexedDB ?? false;
116
+ this._clearStorageOnFileRemove = options.clearStorageOnFileRemove ?? true;
101
117
  }
102
118
 
103
119
  protected getTriggerEvents(): readonly string[] { return TRIGGER_EVENTS; }
@@ -159,8 +175,135 @@ export class DataFrameComponent extends BaseComponent<DataFrameState> {
159
175
  return this;
160
176
  }
161
177
 
162
- withUpload(label: string = 'Upload File', accept: string = '.csv,.tsv,.txt,.xlsx,.xls', icon: string = 'upload'): this {
178
+ withUpload(
179
+ label: string = 'Upload File',
180
+ accept: string = '.csv,.tsv,.txt,.xlsx,.xls',
181
+ icon: string = 'upload'
182
+ ): this {
163
183
  this._inlineUpload = { label, accept, icon };
184
+ this._uploadButtonLabel = label;
185
+ this._uploadAccept = accept;
186
+ this._uploadButtonIcon = icon;
187
+ return this;
188
+ }
189
+
190
+ /**
191
+ * Set upload button label
192
+ */
193
+ uploadLabel(label: string): this {
194
+ this._uploadButtonLabel = label;
195
+ if (this._inlineUpload) this._inlineUpload.label = label;
196
+ return this;
197
+ }
198
+
199
+ /**
200
+ * Set upload button icon
201
+ */
202
+ uploadIcon(icon: string): this {
203
+ this._uploadButtonIcon = icon;
204
+ this._showUploadIcon = !!icon;
205
+ if (this._inlineUpload) this._inlineUpload.icon = icon;
206
+ return this;
207
+ }
208
+
209
+ /**
210
+ * Set upload button variant (outline, primary, ghost, etc.)
211
+ */
212
+ uploadVariant(variant: string): this {
213
+ this._uploadButtonVariant = variant;
214
+ return this;
215
+ }
216
+
217
+ /**
218
+ * Set accepted file types
219
+ */
220
+ uploadAccept(accept: string): this {
221
+ this._uploadAccept = accept;
222
+ if (this._inlineUpload) this._inlineUpload.accept = accept;
223
+ return this;
224
+ }
225
+
226
+ /**
227
+ * Set description text below the upload button
228
+ */
229
+ uploadDescription(description: string): this {
230
+ this._uploadDescription = description;
231
+ return this;
232
+ }
233
+
234
+ /**
235
+ * Show/hide upload icon
236
+ */
237
+ showUploadIcon(show: boolean): this {
238
+ this._showUploadIcon = show;
239
+ return this;
240
+ }
241
+
242
+ /* ═══════════════════════════════════════════════════
243
+ * STORAGE OPTIONS (fluent API)
244
+ * ═══════════════════════════════════════════════════ */
245
+
246
+ /**
247
+ * Enable/disable IndexedDB persistence (default: false = session only)
248
+ */
249
+ persistToIndexedDB(enabled: boolean): this {
250
+ this._persistToIndexedDB = enabled;
251
+ return this;
252
+ }
253
+
254
+ /**
255
+ * Clear stored data when file is removed (default: true)
256
+ */
257
+ clearStorageOnFileRemove(enabled: boolean): this {
258
+ this._clearStorageOnFileRemove = enabled;
259
+ return this;
260
+ }
261
+
262
+ /* ═══════════════════════════════════════════════════
263
+ * CLEAR / RESET
264
+ * ═══════════════════════════════════════════════════ */
265
+
266
+ /**
267
+ * Clear the current data and reset the table
268
+ */
269
+ async clear(): Promise<this> {
270
+ // Clear storage if enabled
271
+ if (this._clearStorageOnFileRemove && this._persistToIndexedDB && this._rawFileData?.file) {
272
+ try {
273
+ const tables = await this._driver.list();
274
+ const matchingTables = tables.filter(t => t.name === this._rawFileData!.file.name);
275
+ for (const table of matchingTables) {
276
+ await this._driver.delete(table.id);
277
+ }
278
+ } catch (err) {
279
+ console.warn('[DataFrame] Failed to clear storage:', err);
280
+ }
281
+ }
282
+
283
+ // Reset state
284
+ this._df = null;
285
+ this._rawFileData = null;
286
+ this._sheets.clear();
287
+ this.state.loaded = false;
288
+ this.state.sourceName = '';
289
+ this.state.rowCount = 0;
290
+ this.state.colCount = 0;
291
+
292
+ // Clear table display
293
+ if (this._table) {
294
+ this._table.columns([]).rows([]);
295
+ }
296
+
297
+ // Remove tabs if present
298
+ const wrapper = document.getElementById(this._id);
299
+ if (wrapper) {
300
+ const existingTabs = wrapper.querySelector('.jux-tabs');
301
+ if (existingTabs) existingTabs.remove();
302
+ }
303
+
304
+ // Reset status
305
+ this._updateStatus('No data loaded.', 'empty');
306
+
164
307
  return this;
165
308
  }
166
309
 
@@ -245,7 +388,7 @@ export class DataFrameComponent extends BaseComponent<DataFrameState> {
245
388
  maxFileSize(mb: number): this { this._maxFileSize = mb; return this; }
246
389
 
247
390
  /* ═══════════════════════════════════════════════════
248
- * FILE HANDLING
391
+ * FILE HANDLING (modified to use storage options)
249
392
  * ═══════════════════════════════════════════════════ */
250
393
 
251
394
  private async _handleFile(file: File): Promise<void> {
@@ -282,7 +425,10 @@ export class DataFrameComponent extends BaseComponent<DataFrameState> {
282
425
  return;
283
426
  }
284
427
 
285
- await this._driver.store(file.name, sheets[sheetNames[0]], { source: file.name });
428
+ // ✅ Only store if persistence is enabled
429
+ if (this._persistToIndexedDB) {
430
+ await this._driver.store(file.name, sheets[sheetNames[0]], { source: file.name });
431
+ }
286
432
 
287
433
  if (sheetNames.length > 1) {
288
434
  this._renderMultiSheet(sheets, file.name);
@@ -298,7 +444,11 @@ export class DataFrameComponent extends BaseComponent<DataFrameState> {
298
444
  hasHeader: true
299
445
  });
300
446
 
301
- await this._driver.store(file.name, df, { source: file.name });
447
+ // ✅ Only store if persistence is enabled
448
+ if (this._persistToIndexedDB) {
449
+ await this._driver.store(file.name, df, { source: file.name });
450
+ }
451
+
302
452
  this._setDataFrame(df, file.name);
303
453
  }
304
454
  } catch (err: any) {
@@ -931,7 +1081,7 @@ export class DataFrameComponent extends BaseComponent<DataFrameState> {
931
1081
  }
932
1082
 
933
1083
  /* ═══════════════════════════════════════════════════
934
- * UPDATE & RENDER
1084
+ * UPDATE & RENDER (modified for better upload styling)
935
1085
  * ═══════════════════════════════════════════════════ */
936
1086
 
937
1087
  update(_prop: string, _value: any): void { }
@@ -947,20 +1097,31 @@ export class DataFrameComponent extends BaseComponent<DataFrameState> {
947
1097
  if (style) wrapper.setAttribute('style', style);
948
1098
 
949
1099
  if (this._inlineUpload) {
1100
+ // ✅ NEW: Enhanced upload area with better styling
1101
+ const uploadArea = document.createElement('div');
1102
+ uploadArea.className = 'jux-dataframe-upload-area';
1103
+ uploadArea.id = `${this._id}-upload-area`;
1104
+
950
1105
  const uploadOpts: any = {
951
- label: this._inlineUpload.label,
952
- accept: this._inlineUpload.accept,
1106
+ label: this._uploadButtonLabel,
1107
+ accept: this._uploadAccept,
1108
+ variant: this._uploadButtonVariant,
953
1109
  };
954
- if (this._inlineUpload.icon) {
955
- uploadOpts.icon = this._inlineUpload.icon;
1110
+ if (this._showUploadIcon && this._uploadButtonIcon) {
1111
+ uploadOpts.icon = this._uploadButtonIcon;
956
1112
  }
957
1113
 
958
1114
  const upload = new FileUpload(`${this._id}-upload`, uploadOpts);
959
1115
  this._uploadRef = upload;
960
1116
 
1117
+ // ✅ Handle file change AND file clear
961
1118
  this._pendingSource = async () => {
962
1119
  upload.bind('change', async (files: File[]) => {
963
- if (!files || files.length === 0) return;
1120
+ if (!files || files.length === 0) {
1121
+ // File was cleared - reset the table
1122
+ await this.clear();
1123
+ return;
1124
+ }
964
1125
  await this._handleFile(files[0]);
965
1126
  });
966
1127
  };
@@ -968,7 +1129,17 @@ export class DataFrameComponent extends BaseComponent<DataFrameState> {
968
1129
  const uploadContainer = document.createElement('div');
969
1130
  uploadContainer.className = 'jux-dataframe-upload';
970
1131
  uploadContainer.id = `${this._id}-upload-container`;
971
- wrapper.appendChild(uploadContainer);
1132
+ uploadArea.appendChild(uploadContainer);
1133
+
1134
+ // ✅ Optional description text
1135
+ if (this._uploadDescription) {
1136
+ const descEl = document.createElement('div');
1137
+ descEl.className = 'jux-dataframe-upload-description';
1138
+ descEl.textContent = this._uploadDescription;
1139
+ uploadArea.appendChild(descEl);
1140
+ }
1141
+
1142
+ wrapper.appendChild(uploadArea);
972
1143
  container.appendChild(wrapper);
973
1144
  upload.render(uploadContainer);
974
1145
  } else {
@@ -4,22 +4,31 @@ export interface FileUploadOptions {
4
4
  label?: string;
5
5
  accept?: string;
6
6
  multiple?: boolean;
7
- disabled?: boolean;
8
- name?: string;
9
7
  icon?: string;
8
+ variant?: 'outline' | 'primary' | 'ghost' | 'secondary';
9
+ showFileList?: boolean;
10
+ maxFiles?: number;
11
+ maxFileSize?: number;
12
+ disabled?: boolean;
10
13
  required?: boolean;
11
- style?: string;
12
- class?: string;
14
+ name?: string;
13
15
  onValidate?: (files: File[]) => boolean | string;
14
16
  storage?: FileStorage;
15
17
  metadata?: Record<string, any>;
18
+ style?: string;
19
+ class?: string;
16
20
  }
17
- interface FileUploadState extends BaseState {
18
- files: File[];
21
+ type FileUploadState = BaseState & {
22
+ label: string;
19
23
  accept: string;
20
24
  multiple: boolean;
21
25
  icon: string;
22
- }
26
+ variant: string;
27
+ showFileList: boolean;
28
+ maxFiles: number;
29
+ maxFileSize: number;
30
+ files: File[];
31
+ };
23
32
  export declare class FileUpload extends BaseComponent<FileUploadState> {
24
33
  private _fileListElement;
25
34
  private _storage;
@@ -30,16 +39,18 @@ export declare class FileUpload extends BaseComponent<FileUploadState> {
30
39
  accept(value: string): this;
31
40
  multiple(value: boolean): this;
32
41
  icon(value: string): this;
42
+ variant(value: 'outline' | 'primary' | 'ghost' | 'secondary'): this;
33
43
  storage(store: FileStorage, metadata?: Record<string, any>): this;
34
44
  clear(): this;
45
+ clearFiles(): this;
35
46
  getValue(): File[];
36
47
  setValue(files: File[]): this;
37
48
  validate(): boolean;
38
49
  isValid(): boolean;
39
50
  protected _validateValue(files: File[]): boolean | string;
40
- protected _buildInputElement(): HTMLElement;
41
51
  private _updateFileList;
42
52
  private _formatFileSize;
53
+ update(prop: string, value: any): void;
43
54
  render(targetId?: string | HTMLElement | BaseComponent<any>): this;
44
55
  }
45
56
  export declare function fileupload(id: string, options?: FileUploadOptions): FileUpload;
@@ -1 +1 @@
1
- {"version":3,"file":"fileupload.d.ts","sourceRoot":"","sources":["fileupload.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,SAAS,EAAE,MAAM,yBAAyB,CAAC;AAGnE,OAAO,EAAE,WAAW,EAAE,MAAM,2BAA2B,CAAC;AAMxD,MAAM,WAAW,iBAAiB;IAC9B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,UAAU,CAAC,EAAE,CAAC,KAAK,EAAE,IAAI,EAAE,KAAK,OAAO,GAAG,MAAM,CAAC;IACjD,OAAO,CAAC,EAAE,WAAW,CAAC;IACtB,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;CAClC;AAED,UAAU,eAAgB,SAAQ,SAAS;IACvC,KAAK,EAAE,IAAI,EAAE,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,OAAO,CAAC;IAClB,IAAI,EAAE,MAAM,CAAC;CAChB;AAED,qBAAa,UAAW,SAAQ,aAAa,CAAC,eAAe,CAAC;IAC1D,OAAO,CAAC,gBAAgB,CAA4B;IACpD,OAAO,CAAC,QAAQ,CAA4B;IAC5C,OAAO,CAAC,SAAS,CAAkC;gBAEvC,EAAE,EAAE,MAAM,EAAE,OAAO,GAAE,iBAAsB;IA0BvD,SAAS,CAAC,gBAAgB,IAAI,SAAS,MAAM,EAAE;IAI/C,SAAS,CAAC,iBAAiB,IAAI,SAAS,MAAM,EAAE;IAQhD,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI;IAK3B,QAAQ,CAAC,KAAK,EAAE,OAAO,GAAG,IAAI;IAK9B,IAAI,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI;IAKzB,OAAO,CAAC,KAAK,EAAE,WAAW,EAAE,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,IAAI;IAMjE,KAAK,IAAI,IAAI;IAgBb,QAAQ,IAAI,IAAI,EAAE;IAIlB,QAAQ,CAAC,KAAK,EAAE,IAAI,EAAE,GAAG,IAAI;IAM7B,QAAQ,IAAI,OAAO;IAcnB,OAAO,IAAI,OAAO;IAKlB,SAAS,CAAC,cAAc,CAAC,KAAK,EAAE,IAAI,EAAE,GAAG,OAAO,GAAG,MAAM;IAiBzD,SAAS,CAAC,kBAAkB,IAAI,WAAW;IAiB3C,OAAO,CAAC,eAAe;IA6BvB,OAAO,CAAC,eAAe;IAYvB,MAAM,CAAC,QAAQ,CAAC,EAAE,MAAM,GAAG,WAAW,GAAG,aAAa,CAAC,GAAG,CAAC,GAAG,IAAI;CAiGrE;AAED,wBAAgB,UAAU,CAAC,EAAE,EAAE,MAAM,EAAE,OAAO,GAAE,iBAAsB,GAAG,UAAU,CAElF"}
1
+ {"version":3,"file":"fileupload.d.ts","sourceRoot":"","sources":["fileupload.ts"],"names":[],"mappings":"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"}
@@ -20,7 +20,11 @@ export class FileUpload extends BaseComponent {
20
20
  files: [],
21
21
  accept: options.accept ?? '',
22
22
  multiple: options.multiple ?? false,
23
- icon: options.icon ?? 'upload'
23
+ icon: options.icon ?? 'upload',
24
+ variant: options.variant ?? 'outline',
25
+ showFileList: options.showFileList ?? true,
26
+ maxFiles: options.maxFiles ?? 10,
27
+ maxFileSize: options.maxFileSize ?? 10,
24
28
  });
25
29
  this._fileListElement = null;
26
30
  this._storage = null;
@@ -51,6 +55,10 @@ export class FileUpload extends BaseComponent {
51
55
  this.state.icon = value;
52
56
  return this;
53
57
  }
58
+ variant(value) {
59
+ this.state.variant = value;
60
+ return this;
61
+ }
54
62
  storage(store, metadata) {
55
63
  this._storage = store;
56
64
  this._metadata = metadata;
@@ -67,6 +75,12 @@ export class FileUpload extends BaseComponent {
67
75
  this._triggerCallback('clear', null, null, this);
68
76
  return this;
69
77
  }
78
+ clearFiles() {
79
+ this.state.files = [];
80
+ this._updateFileList();
81
+ this._triggerCallback('change', [], null, this);
82
+ return this;
83
+ }
70
84
  /* ═════════════════════════════════════════════════════════════════
71
85
  * FORM INPUT IMPLEMENTATION
72
86
  * ═════════════════════════════════════════════════════════════════ */
@@ -108,20 +122,6 @@ export class FileUpload extends BaseComponent {
108
122
  }
109
123
  return true;
110
124
  }
111
- _buildInputElement() {
112
- const { accept, multiple, required, disabled, name } = this.state;
113
- const input = document.createElement('input');
114
- input.type = 'file';
115
- input.className = 'jux-fileupload-input';
116
- input.id = `${this._id}-input`;
117
- input.name = name;
118
- input.accept = accept;
119
- input.multiple = multiple;
120
- input.required = required;
121
- input.disabled = disabled;
122
- input.style.display = 'none';
123
- return input;
124
- }
125
125
  _updateFileList() {
126
126
  if (!this._fileListElement)
127
127
  return;
@@ -157,49 +157,58 @@ export class FileUpload extends BaseComponent {
157
157
  /* ═════════════════════════════════════════════════════════════════
158
158
  * RENDER
159
159
  * ═════════════════════════════════════════════════════════════════ */
160
+ update(prop, value) {
161
+ // No reactive updates needed
162
+ }
160
163
  render(targetId) {
161
164
  const container = this._setupContainer(targetId);
162
- const { label, accept, multiple, icon, style, class: className } = this.state;
165
+ const { label, accept, multiple, icon, variant, showFileList, style, class: className } = this.state;
163
166
  const wrapper = document.createElement('div');
164
- wrapper.className = 'jux-input jux-fileupload';
167
+ wrapper.className = 'jux-fileupload';
165
168
  wrapper.id = this._id;
166
169
  if (className)
167
170
  wrapper.className += ` ${className}`;
168
171
  if (style)
169
172
  wrapper.setAttribute('style', style);
170
- if (this.state.label) {
171
- wrapper.appendChild(this._renderLabel());
172
- }
173
- // File input
173
+ // Hidden file input
174
174
  const fileInput = document.createElement('input');
175
175
  fileInput.type = 'file';
176
- fileInput.id = `${this._id}-input`;
177
- fileInput.style.display = 'none';
178
- fileInput.multiple = multiple;
179
176
  fileInput.accept = accept;
177
+ fileInput.multiple = multiple;
178
+ fileInput.style.display = 'none';
179
+ fileInput.id = `${this._id}-input`;
180
180
  this._inputElement = fileInput;
181
- wrapper.appendChild(fileInput);
182
- const buttonContainer = document.createElement('div');
183
- buttonContainer.className = 'jux-fileupload-button-container';
181
+ // Button
182
+ const btn = document.createElement('button');
183
+ btn.className = `jux-fileupload-button jux-button-${variant}`;
184
+ btn.type = 'button';
184
185
  if (icon) {
185
186
  const iconEl = document.createElement('span');
186
187
  iconEl.className = 'jux-fileupload-icon';
187
188
  iconEl.appendChild(renderIcon(icon));
188
- buttonContainer.appendChild(iconEl);
189
+ btn.appendChild(iconEl);
189
190
  }
190
- const button = document.createElement('button');
191
- button.type = 'button';
192
- button.className = 'jux-fileupload-button';
193
- button.textContent = 'Choose File(s)';
194
- button.disabled = this.state.disabled;
195
- buttonContainer.appendChild(button);
191
+ const labelSpan = document.createElement('span');
192
+ labelSpan.textContent = label;
193
+ btn.appendChild(labelSpan);
194
+ // Button container
195
+ const buttonContainer = document.createElement('div');
196
+ buttonContainer.className = 'jux-fileupload-button-container';
197
+ buttonContainer.appendChild(fileInput);
198
+ buttonContainer.appendChild(btn);
196
199
  wrapper.appendChild(buttonContainer);
197
- const fileList = document.createElement('div');
198
- fileList.className = 'jux-fileupload-list';
199
- this._fileListElement = fileList;
200
- wrapper.appendChild(fileList);
200
+ // File list
201
+ if (showFileList) {
202
+ const fileList = document.createElement('div');
203
+ fileList.className = 'jux-fileupload-filelist';
204
+ this._fileListElement = fileList;
205
+ wrapper.appendChild(fileList);
206
+ }
207
+ // Error element
201
208
  wrapper.appendChild(this._renderError());
202
- button.addEventListener('click', () => fileInput.click());
209
+ // Wire up button click to trigger file input
210
+ btn.addEventListener('click', () => fileInput.click());
211
+ // Wire up file input change
203
212
  fileInput.addEventListener('change', (e) => {
204
213
  const files = Array.from(fileInput.files || []);
205
214
  this.state.files = files;
@@ -216,11 +225,13 @@ export class FileUpload extends BaseComponent {
216
225
  }
217
226
  });
218
227
  this._wireStandardEvents(wrapper);
228
+ // Validate on blur if already validated
219
229
  fileInput.addEventListener('blur', () => {
220
230
  if (this._hasBeenValidated) {
221
231
  this.validate();
222
232
  }
223
233
  });
234
+ // Wire sync bindings for label
224
235
  const labelSync = this._syncBindings.find(b => b.property === 'label');
225
236
  if (labelSync) {
226
237
  const transform = labelSync.toComponent || ((v) => String(v));
@@ -11,23 +11,32 @@ export interface FileUploadOptions {
11
11
  label?: string;
12
12
  accept?: string;
13
13
  multiple?: boolean;
14
- disabled?: boolean;
15
- name?: string;
16
14
  icon?: string;
15
+ variant?: 'outline' | 'primary' | 'ghost' | 'secondary';
16
+ showFileList?: boolean;
17
+ maxFiles?: number;
18
+ maxFileSize?: number;
19
+ disabled?: boolean;
17
20
  required?: boolean;
18
- style?: string;
19
- class?: string;
21
+ name?: string;
20
22
  onValidate?: (files: File[]) => boolean | string;
21
23
  storage?: FileStorage;
22
24
  metadata?: Record<string, any>;
25
+ style?: string;
26
+ class?: string;
23
27
  }
24
28
 
25
- interface FileUploadState extends BaseState {
26
- files: File[];
29
+ type FileUploadState = BaseState & {
30
+ label: string;
27
31
  accept: string;
28
32
  multiple: boolean;
29
33
  icon: string;
30
- }
34
+ variant: string;
35
+ showFileList: boolean;
36
+ maxFiles: number;
37
+ maxFileSize: number;
38
+ files: File[];
39
+ };
31
40
 
32
41
  export class FileUpload extends BaseComponent<FileUploadState> {
33
42
  private _fileListElement: HTMLElement | null = null;
@@ -49,7 +58,11 @@ export class FileUpload extends BaseComponent<FileUploadState> {
49
58
  files: [],
50
59
  accept: options.accept ?? '',
51
60
  multiple: options.multiple ?? false,
52
- icon: options.icon ?? 'upload'
61
+ icon: options.icon ?? 'upload',
62
+ variant: options.variant ?? 'outline',
63
+ showFileList: options.showFileList ?? true,
64
+ maxFiles: options.maxFiles ?? 10,
65
+ maxFileSize: options.maxFileSize ?? 10,
53
66
  });
54
67
 
55
68
  if (options.onValidate) {
@@ -87,6 +100,11 @@ export class FileUpload extends BaseComponent<FileUploadState> {
87
100
  return this;
88
101
  }
89
102
 
103
+ variant(value: 'outline' | 'primary' | 'ghost' | 'secondary'): this {
104
+ this.state.variant = value;
105
+ return this;
106
+ }
107
+
90
108
  storage(store: FileStorage, metadata?: Record<string, any>): this {
91
109
  this._storage = store;
92
110
  this._metadata = metadata;
@@ -105,6 +123,13 @@ export class FileUpload extends BaseComponent<FileUploadState> {
105
123
  return this;
106
124
  }
107
125
 
126
+ clearFiles(): this {
127
+ this.state.files = [];
128
+ this._updateFileList();
129
+ this._triggerCallback('change', [], null, this);
130
+ return this;
131
+ }
132
+
108
133
  /* ═════════════════════════════════════════════════════════════════
109
134
  * FORM INPUT IMPLEMENTATION
110
135
  * ═════════════════════════════════════════════════════════════════ */
@@ -155,23 +180,6 @@ export class FileUpload extends BaseComponent<FileUploadState> {
155
180
  return true;
156
181
  }
157
182
 
158
- protected _buildInputElement(): HTMLElement {
159
- const { accept, multiple, required, disabled, name } = this.state;
160
-
161
- const input = document.createElement('input');
162
- input.type = 'file';
163
- input.className = 'jux-fileupload-input';
164
- input.id = `${this._id}-input`;
165
- input.name = name!;
166
- input.accept = accept;
167
- input.multiple = multiple;
168
- input.required = required!;
169
- input.disabled = disabled!;
170
- input.style.display = 'none';
171
-
172
- return input;
173
- }
174
-
175
183
  private _updateFileList(): void {
176
184
  if (!this._fileListElement) return;
177
185
 
@@ -213,59 +221,68 @@ export class FileUpload extends BaseComponent<FileUploadState> {
213
221
  * RENDER
214
222
  * ═════════════════════════════════════════════════════════════════ */
215
223
 
224
+ update(prop: string, value: any): void {
225
+ // No reactive updates needed
226
+ }
227
+
216
228
  render(targetId?: string | HTMLElement | BaseComponent<any>): this {
217
229
  const container = this._setupContainer(targetId);
218
- const { label, accept, multiple, icon, style, class: className } = this.state;
230
+
231
+ const { label, accept, multiple, icon, variant, showFileList, style, class: className } = this.state;
219
232
 
220
233
  const wrapper = document.createElement('div');
221
- wrapper.className = 'jux-input jux-fileupload';
234
+ wrapper.className = 'jux-fileupload';
222
235
  wrapper.id = this._id;
223
236
  if (className) wrapper.className += ` ${className}`;
224
237
  if (style) wrapper.setAttribute('style', style);
225
238
 
226
- if (this.state.label) {
227
- wrapper.appendChild(this._renderLabel());
228
- }
229
-
230
- // File input
239
+ // Hidden file input
231
240
  const fileInput = document.createElement('input');
232
241
  fileInput.type = 'file';
233
- fileInput.id = `${this._id}-input`;
234
- fileInput.style.display = 'none';
235
- fileInput.multiple = multiple;
236
242
  fileInput.accept = accept;
237
-
243
+ fileInput.multiple = multiple;
244
+ fileInput.style.display = 'none';
245
+ fileInput.id = `${this._id}-input`;
238
246
  this._inputElement = fileInput;
239
- wrapper.appendChild(fileInput);
240
247
 
241
- const buttonContainer = document.createElement('div');
242
- buttonContainer.className = 'jux-fileupload-button-container';
248
+ // Button
249
+ const btn = document.createElement('button');
250
+ btn.className = `jux-fileupload-button jux-button-${variant}`;
251
+ btn.type = 'button';
243
252
 
244
253
  if (icon) {
245
254
  const iconEl = document.createElement('span');
246
255
  iconEl.className = 'jux-fileupload-icon';
247
256
  iconEl.appendChild(renderIcon(icon));
248
- buttonContainer.appendChild(iconEl);
257
+ btn.appendChild(iconEl);
249
258
  }
250
259
 
251
- const button = document.createElement('button');
252
- button.type = 'button';
253
- button.className = 'jux-fileupload-button';
254
- button.textContent = 'Choose File(s)';
255
- button.disabled = this.state.disabled!;
260
+ const labelSpan = document.createElement('span');
261
+ labelSpan.textContent = label;
262
+ btn.appendChild(labelSpan);
256
263
 
257
- buttonContainer.appendChild(button);
264
+ // Button container
265
+ const buttonContainer = document.createElement('div');
266
+ buttonContainer.className = 'jux-fileupload-button-container';
267
+ buttonContainer.appendChild(fileInput);
268
+ buttonContainer.appendChild(btn);
258
269
  wrapper.appendChild(buttonContainer);
259
270
 
260
- const fileList = document.createElement('div');
261
- fileList.className = 'jux-fileupload-list';
262
- this._fileListElement = fileList;
263
- wrapper.appendChild(fileList);
271
+ // File list
272
+ if (showFileList) {
273
+ const fileList = document.createElement('div');
274
+ fileList.className = 'jux-fileupload-filelist';
275
+ this._fileListElement = fileList;
276
+ wrapper.appendChild(fileList);
277
+ }
264
278
 
279
+ // Error element
265
280
  wrapper.appendChild(this._renderError());
266
281
 
267
- button.addEventListener('click', () => fileInput.click());
282
+ // Wire up button click to trigger file input
283
+ btn.addEventListener('click', () => fileInput.click());
268
284
 
285
+ // Wire up file input change
269
286
  fileInput.addEventListener('change', (e) => {
270
287
  const files = Array.from(fileInput.files || []);
271
288
  this.state.files = files;
@@ -286,12 +303,14 @@ export class FileUpload extends BaseComponent<FileUploadState> {
286
303
 
287
304
  this._wireStandardEvents(wrapper);
288
305
 
306
+ // Validate on blur if already validated
289
307
  fileInput.addEventListener('blur', () => {
290
308
  if (this._hasBeenValidated) {
291
309
  this.validate();
292
310
  }
293
311
  });
294
312
 
313
+ // Wire sync bindings for label
295
314
  const labelSync = this._syncBindings.find(b => b.property === 'label');
296
315
  if (labelSync) {
297
316
  const transform = labelSync.toComponent || ((v: any) => String(v));
@@ -160,6 +160,75 @@ p { line-height: 1.75; color: hsl(var(--foreground)); }
160
160
  width: 100%;
161
161
  }
162
162
 
163
+ /* ✅ NEW: Enhanced upload area */
164
+ .jux-dataframe-upload-area {
165
+ display: flex;
166
+ flex-direction: column;
167
+ align-items: flex-start;
168
+ gap: 0.5rem;
169
+ padding: 1rem;
170
+ background: hsl(var(--muted) / 0.2);
171
+ border: 1px dashed hsl(var(--border));
172
+ border-radius: var(--radius);
173
+ transition: all 0.2s;
174
+ }
175
+
176
+ .jux-dataframe-upload-area:hover {
177
+ background: hsl(var(--muted) / 0.3);
178
+ border-color: hsl(var(--muted-foreground) / 0.3);
179
+ }
180
+
181
+ .jux-dataframe-upload-area:has(.jux-fileupload-filelist:not(:empty)) {
182
+ background: hsl(var(--success) / 0.05);
183
+ border-color: hsl(var(--success) / 0.2);
184
+ border-style: solid;
185
+ }
186
+
187
+ .jux-dataframe-upload {
188
+ display: flex;
189
+ align-items: center;
190
+ gap: 0.75rem;
191
+ }
192
+
193
+ .jux-dataframe-upload-description {
194
+ font-size: 0.8125rem;
195
+ color: hsl(var(--muted-foreground));
196
+ line-height: 1.4;
197
+ }
198
+
199
+ /* ✅ Style the fileupload button inside dataframe */
200
+ .jux-dataframe-upload .jux-fileupload {
201
+ display: flex;
202
+ align-items: center;
203
+ gap: 0.75rem;
204
+ }
205
+
206
+ .jux-dataframe-upload .jux-fileupload-button {
207
+ display: inline-flex;
208
+ align-items: center;
209
+ gap: 0.5rem;
210
+ padding: 0.5rem 1rem;
211
+ font-weight: 500;
212
+ border-radius: var(--radius);
213
+ transition: all 0.15s;
214
+ }
215
+
216
+ .jux-dataframe-upload .jux-fileupload-button:hover {
217
+ background: hsl(var(--accent));
218
+ }
219
+
220
+ /* ✅ File list styling inside dataframe */
221
+ .jux-dataframe-upload .jux-fileupload-filelist {
222
+ margin-top: 0;
223
+ }
224
+
225
+ .jux-dataframe-upload .jux-fileupload-item {
226
+ background: hsl(var(--background));
227
+ border: 1px solid hsl(var(--border));
228
+ padding: 0.5rem 0.75rem;
229
+ font-size: 0.8125rem;
230
+ }
231
+
163
232
  .jux-dataframe-status {
164
233
  display: flex;
165
234
  align-items: center;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "juxscript",
3
- "version": "1.1.211",
3
+ "version": "1.1.212",
4
4
  "type": "module",
5
5
  "description": "A JavaScript UX authorship platform",
6
6
  "main": "index.js",