@dodlhuat/basix 1.0.0 → 1.1.0

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.
Files changed (142) hide show
  1. package/README.md +1 -1
  2. package/css/accordion.scss +31 -22
  3. package/css/alert.scss +79 -27
  4. package/css/button.scss +151 -102
  5. package/css/card.scss +11 -12
  6. package/css/carousel.scss +123 -87
  7. package/css/chart.scss +9 -11
  8. package/css/chat-bubbles.scss +2 -2
  9. package/css/checkbox.scss +72 -55
  10. package/css/chips.scss +52 -52
  11. package/css/code-viewer.scss +73 -98
  12. package/css/datepicker.scss +20 -0
  13. package/css/dropdown.scss +151 -137
  14. package/css/editor.scss +9 -6
  15. package/css/file-uploader.scss +187 -195
  16. package/css/flyout-menu.scss +20 -13
  17. package/css/form.scss +168 -115
  18. package/css/gallery.scss +62 -63
  19. package/css/grid.scss +0 -1
  20. package/css/modal.scss +117 -72
  21. package/css/placeholder.scss +17 -12
  22. package/css/properties.scss +6 -0
  23. package/css/push-menu.scss +70 -23
  24. package/css/radiobutton.scss +86 -64
  25. package/css/range-slider.scss +136 -0
  26. package/css/scrollbar.scss +69 -69
  27. package/css/spinner.scss +41 -66
  28. package/css/style.css +4351 -3735
  29. package/css/style.css.map +1 -1
  30. package/css/style.scss +2 -1
  31. package/css/switch.scss +43 -42
  32. package/css/table.scss +61 -40
  33. package/css/tabs.scss +12 -7
  34. package/css/timeline.scss +72 -69
  35. package/css/timepicker.scss +151 -72
  36. package/css/toast.scss +49 -48
  37. package/css/tooltip.scss +112 -122
  38. package/css/tree.scss +135 -192
  39. package/css/typography.scss +70 -9
  40. package/css/virtual-dropdown.scss +201 -142
  41. package/js/carousel.js +45 -18
  42. package/js/carousel.ts +217 -173
  43. package/js/datepicker.js +505 -497
  44. package/js/datepicker.ts +9 -0
  45. package/js/editor.js +398 -415
  46. package/js/file-uploader.js +142 -128
  47. package/js/file-uploader.ts +364 -350
  48. package/js/gallery.js +22 -15
  49. package/js/gallery.ts +17 -12
  50. package/js/index.js +718 -720
  51. package/js/index.ts +7 -8
  52. package/js/push-menu.js +113 -101
  53. package/js/push-menu.ts +17 -2
  54. package/js/range-slider.js +26 -0
  55. package/js/range-slider.ts +33 -0
  56. package/js/timepicker.js +144 -98
  57. package/js/timepicker.ts +194 -131
  58. package/js/tree.js +56 -28
  59. package/js/tree.ts +239 -218
  60. package/package.json +1 -1
  61. package/css/accordion.css +0 -109
  62. package/css/accordion.css.map +0 -1
  63. package/css/alert.css +0 -57
  64. package/css/alert.css.map +0 -1
  65. package/css/button.css +0 -69
  66. package/css/button.css.map +0 -1
  67. package/css/card.css +0 -144
  68. package/css/card.css.map +0 -1
  69. package/css/carousel.css +0 -118
  70. package/css/carousel.css.map +0 -1
  71. package/css/chart.css +0 -159
  72. package/css/chart.css.map +0 -1
  73. package/css/chat-bubbles.css +0 -97
  74. package/css/chat-bubbles.css.map +0 -1
  75. package/css/checkbox.css +0 -77
  76. package/css/checkbox.css.map +0 -1
  77. package/css/chips.css +0 -72
  78. package/css/chips.css.map +0 -1
  79. package/css/code-viewer.css +0 -97
  80. package/css/code-viewer.css.map +0 -1
  81. package/css/colors.css +0 -63
  82. package/css/colors.css.map +0 -1
  83. package/css/datepicker.css +0 -264
  84. package/css/datepicker.css.map +0 -1
  85. package/css/defaults.css +0 -118
  86. package/css/defaults.css.map +0 -1
  87. package/css/dropdown.css +0 -146
  88. package/css/dropdown.css.map +0 -1
  89. package/css/editor.css +0 -413
  90. package/css/file-uploader.css +0 -194
  91. package/css/file-uploader.css.map +0 -1
  92. package/css/flyout-menu.css +0 -345
  93. package/css/flyout-menu.css.map +0 -1
  94. package/css/form-builder.css +0 -9
  95. package/css/form-builder.css.map +0 -1
  96. package/css/form-builder.scss +0 -11
  97. package/css/form.css +0 -130
  98. package/css/form.css.map +0 -1
  99. package/css/gallery.css +0 -91
  100. package/css/gallery.css.map +0 -1
  101. package/css/grid.css +0 -44
  102. package/css/grid.css.map +0 -1
  103. package/css/icons.css +0 -327
  104. package/css/icons.css.map +0 -1
  105. package/css/modal.css +0 -97
  106. package/css/modal.css.map +0 -1
  107. package/css/parameters.css +0 -1
  108. package/css/parameters.css.map +0 -1
  109. package/css/placeholder.css +0 -50
  110. package/css/placeholder.css.map +0 -1
  111. package/css/progress.css +0 -51
  112. package/css/progress.css.map +0 -1
  113. package/css/properties.css +0 -31
  114. package/css/properties.css.map +0 -1
  115. package/css/push-menu.css +0 -145
  116. package/css/push-menu.css.map +0 -1
  117. package/css/radiobutton.css +0 -91
  118. package/css/radiobutton.css.map +0 -1
  119. package/css/reset.css +0 -46
  120. package/css/reset.css.map +0 -1
  121. package/css/scrollbar.css +0 -91
  122. package/css/scrollbar.css.map +0 -1
  123. package/css/spinner.css +0 -118
  124. package/css/spinner.css.map +0 -1
  125. package/css/switch.css +0 -66
  126. package/css/switch.css.map +0 -1
  127. package/css/table.css +0 -201
  128. package/css/table.css.map +0 -1
  129. package/css/tabs.css +0 -135
  130. package/css/tabs.css.map +0 -1
  131. package/css/timeline.css +0 -69
  132. package/css/timeline.css.map +0 -1
  133. package/css/toast.css +0 -98
  134. package/css/toast.css.map +0 -1
  135. package/css/tooltip.css +0 -151
  136. package/css/tooltip.css.map +0 -1
  137. package/css/tree.css +0 -199
  138. package/css/tree.css.map +0 -1
  139. package/css/typography.css +0 -137
  140. package/css/typography.css.map +0 -1
  141. package/css/virtual-dropdown.css +0 -149
  142. package/css/virtual-dropdown.css.map +0 -1
@@ -25,7 +25,7 @@ class FileUploader {
25
25
  const target = e.target;
26
26
  if (target.files) {
27
27
  this.handleFiles(target.files);
28
- target.value = ''; // Reset input so same file can be selected again
28
+ target.value = '';
29
29
  }
30
30
  };
31
31
  this.handleUploadClick = async () => {
@@ -38,9 +38,10 @@ class FileUploader {
38
38
  this.uploadBtn.textContent = 'Upload Complete';
39
39
  setTimeout(() => {
40
40
  this.dispatchUploadCompletedEvent(results);
41
- this.cleanupAfterUpload();
42
- this.resetUploadState();
43
- }, 1000);
41
+ this.fileList.innerHTML = '';
42
+ this.files.clear();
43
+ this.updateUploadButton();
44
+ }, 1500);
44
45
  };
45
46
  const container = typeof elementOrSelector === 'string'
46
47
  ? document.querySelector(elementOrSelector)
@@ -68,17 +69,18 @@ class FileUploader {
68
69
  init() {
69
70
  this.setupEventListeners();
70
71
  }
72
+ fileKey(file) {
73
+ return `${file.name}-${file.size}-${file.lastModified}`;
74
+ }
71
75
  setupEventListeners() {
72
- // Drag & Drop - prevent default browser behavior
73
- ['dragenter', 'dragover', 'dragleave', 'drop'].forEach(eventName => {
74
- this.dropZone.addEventListener(eventName, this.preventDefaults);
76
+ ['dragenter', 'dragover', 'dragleave', 'drop'].forEach(event => {
77
+ this.dropZone.addEventListener(event, this.preventDefaults);
75
78
  });
76
- // Drag over effects
77
- ['dragenter', 'dragover'].forEach(eventName => {
78
- this.dropZone.addEventListener(eventName, this.handleDragEnter);
79
+ ['dragenter', 'dragover'].forEach(event => {
80
+ this.dropZone.addEventListener(event, this.handleDragEnter);
79
81
  });
80
- ['dragleave', 'drop'].forEach(eventName => {
81
- this.dropZone.addEventListener(eventName, this.handleDragLeave);
82
+ ['dragleave', 'drop'].forEach(event => {
83
+ this.dropZone.addEventListener(event, this.handleDragLeave);
82
84
  });
83
85
  this.dropZone.addEventListener('drop', this.handleDrop);
84
86
  this.dropZone.addEventListener('click', this.handleDropZoneClick);
@@ -87,159 +89,162 @@ class FileUploader {
87
89
  }
88
90
  handleFiles(fileList) {
89
91
  Array.from(fileList).forEach(file => {
90
- if (this.validateFile(file) && !this.files.has(file.name)) {
92
+ const key = this.fileKey(file);
93
+ if (this.validateFile(file) && !this.files.has(key)) {
91
94
  const element = this.addFileToUI(file);
92
- this.files.set(file.name, { file, element });
95
+ this.files.set(key, { file, element });
93
96
  }
94
97
  });
95
98
  this.updateUploadButton();
96
99
  }
97
100
  validateFile(file) {
98
101
  if (this.maxFileSize && file.size > this.maxFileSize) {
99
- console.warn(`File ${file.name} exceeds maximum size`);
102
+ this.container.dispatchEvent(new CustomEvent('file-validation-error', {
103
+ detail: { file, reason: 'size' },
104
+ bubbles: true,
105
+ }));
100
106
  return false;
101
107
  }
102
108
  if (this.allowedTypes && !this.allowedTypes.includes(file.type)) {
103
- console.warn(`File type ${file.type} is not allowed`);
109
+ this.container.dispatchEvent(new CustomEvent('file-validation-error', {
110
+ detail: { file, reason: 'type' },
111
+ bubbles: true,
112
+ }));
104
113
  return false;
105
114
  }
106
115
  return true;
107
116
  }
108
117
  addFileToUI(file) {
118
+ const key = this.fileKey(file);
109
119
  const item = document.createElement('div');
110
120
  item.className = 'file-item';
111
121
  const escapedFileName = this.escapeHtml(file.name);
112
- item.innerHTML = `
113
- <div class="file-item-header">
114
- <div class="file-info">
115
- <div class="file-icon">
116
- <svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
117
- <path d="M13 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V9z"></path>
118
- <polyline points="13 2 13 9 20 9"></polyline>
119
- </svg>
120
- </div>
121
- <div class="file-details">
122
- <span class="file-name" title="${escapedFileName}">${escapedFileName}</span>
123
- <span class="file-size">${this.formatSize(file.size)}</span>
124
- </div>
125
- </div>
126
- <button class="remove-btn" type="button" aria-label="Remove file">
127
- <svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
128
- <line x1="18" y1="6" x2="6" y2="18"></line>
129
- <line x1="6" y1="6" x2="18" y2="18"></line>
130
- </svg>
131
- </button>
132
- </div>
133
- <div class="progress-container" style="display: none;">
134
- <div class="progress-bar"></div>
135
- </div>
136
- <div class="status-text" style="display: none;">Waiting...</div>
122
+ item.innerHTML = `
123
+ <div class="file-item-header">
124
+ <div class="file-info">
125
+ <div class="file-icon">
126
+ <svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
127
+ <path d="M13 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V9z"></path>
128
+ <polyline points="13 2 13 9 20 9"></polyline>
129
+ </svg>
130
+ </div>
131
+ <div class="file-details">
132
+ <span class="file-name" title="${escapedFileName}">${escapedFileName}</span>
133
+ <span class="file-size">${this.formatSize(file.size)}</span>
134
+ </div>
135
+ </div>
136
+ <button class="remove-btn" type="button" aria-label="Remove file">
137
+ <svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
138
+ <line x1="18" y1="6" x2="6" y2="18"></line>
139
+ <line x1="6" y1="6" x2="18" y2="18"></line>
140
+ </svg>
141
+ </button>
142
+ </div>
143
+ <div class="progress-container">
144
+ <div class="progress-bar"></div>
145
+ </div>
146
+ <div class="status-text">Waiting...</div>
137
147
  `;
138
148
  const removeBtn = item.querySelector('.remove-btn');
139
149
  if (removeBtn) {
140
150
  removeBtn.addEventListener('click', (e) => {
141
151
  e.stopPropagation();
142
- this.removeFile(file.name);
152
+ this.removeFile(key);
143
153
  });
144
154
  }
145
155
  this.fileList.appendChild(item);
146
156
  return item;
147
157
  }
148
- async uploadFile(file, element) {
149
- const progressContainer = element.querySelector('.progress-container');
150
- const progressBar = element.querySelector('.progress-bar');
151
- const statusText = element.querySelector('.status-text');
152
- const removeBtn = element.querySelector('.remove-btn');
153
- if (!progressContainer || !progressBar || !statusText || !removeBtn) {
154
- throw new Error('Required UI elements not found');
155
- }
156
- // Show progress elements
157
- progressContainer.style.display = 'block';
158
- statusText.style.display = 'block';
159
- removeBtn.style.display = 'none';
160
- const abortController = new AbortController();
161
- this.abortControllers.set(file.name, abortController);
162
- try {
163
- const formData = new FormData();
164
- formData.append('file', file);
165
- const response = await fetch(this.uploadUrl, {
166
- method: 'POST',
167
- body: formData,
168
- signal: abortController.signal,
169
- });
170
- // Note: Fetch API doesn't support upload progress natively
171
- // For progress tracking, you'd need to use XMLHttpRequest or a library
172
- progressBar.style.width = '100%';
173
- statusText.textContent = '100%';
174
- if (response.ok) {
175
- statusText.textContent = 'Completed';
176
- statusText.classList.add('success');
177
- progressBar.style.backgroundColor = 'var(--success-color)';
178
- return await response.json();
179
- }
180
- else {
181
- throw new Error(`Upload failed: ${response.statusText}`);
158
+ uploadFile(file, element) {
159
+ return new Promise((resolve, reject) => {
160
+ const progressContainer = element.querySelector('.progress-container');
161
+ const progressBar = element.querySelector('.progress-bar');
162
+ const statusText = element.querySelector('.status-text');
163
+ const removeBtn = element.querySelector('.remove-btn');
164
+ if (!progressContainer || !progressBar || !statusText || !removeBtn) {
165
+ reject(new Error('Required UI elements not found'));
166
+ return;
182
167
  }
183
- }
184
- catch (error) {
185
- if (error instanceof Error && error.name === 'AbortError') {
168
+ progressContainer.style.display = 'block';
169
+ statusText.style.display = 'block';
170
+ statusText.textContent = '0%';
171
+ removeBtn.style.display = 'none';
172
+ const xhr = new XMLHttpRequest();
173
+ const key = this.fileKey(file);
174
+ this.abortControllers.set(key, () => xhr.abort());
175
+ xhr.upload.addEventListener('progress', (e) => {
176
+ if (e.lengthComputable) {
177
+ const pct = Math.round((e.loaded / e.total) * 100);
178
+ progressBar.style.width = pct + '%';
179
+ statusText.textContent = pct + '%';
180
+ }
181
+ });
182
+ xhr.addEventListener('load', () => {
183
+ this.abortControllers.delete(key);
184
+ if (xhr.status >= 200 && xhr.status < 300) {
185
+ progressBar.style.width = '100%';
186
+ progressBar.style.backgroundColor = 'var(--success)';
187
+ statusText.textContent = 'Completed';
188
+ statusText.classList.add('success');
189
+ try {
190
+ resolve(JSON.parse(xhr.responseText));
191
+ }
192
+ catch {
193
+ resolve(xhr.responseText);
194
+ }
195
+ }
196
+ else {
197
+ progressBar.style.backgroundColor = 'var(--error)';
198
+ statusText.textContent = 'Failed';
199
+ statusText.classList.add('error');
200
+ removeBtn.style.display = 'flex';
201
+ reject(new Error(`Upload failed: ${xhr.statusText}`));
202
+ }
203
+ });
204
+ xhr.addEventListener('error', () => {
205
+ this.abortControllers.delete(key);
206
+ progressBar.style.backgroundColor = 'var(--error)';
207
+ statusText.textContent = 'Network Error';
208
+ statusText.classList.add('error');
209
+ removeBtn.style.display = 'flex';
210
+ reject(new Error('Network error'));
211
+ });
212
+ xhr.addEventListener('abort', () => {
213
+ this.abortControllers.delete(key);
186
214
  statusText.textContent = 'Cancelled';
187
- }
188
- else {
189
- statusText.textContent = error instanceof Error ? 'Error' : 'Network Error';
190
- }
191
- statusText.classList.add('error');
192
- progressBar.style.backgroundColor = 'var(--error-color)';
193
- removeBtn.style.display = 'flex';
194
- throw error;
195
- }
196
- finally {
197
- this.abortControllers.delete(file.name);
198
- }
215
+ statusText.classList.add('error');
216
+ removeBtn.style.display = 'flex';
217
+ reject(new Error('Upload aborted'));
218
+ });
219
+ const formData = new FormData();
220
+ formData.append('file', file);
221
+ xhr.open('POST', this.uploadUrl);
222
+ xhr.send(formData);
223
+ });
199
224
  }
200
- removeFile(fileName) {
201
- // Cancel upload if in progress
202
- const abortController = this.abortControllers.get(fileName);
203
- if (abortController) {
204
- abortController.abort();
205
- }
206
- const fileData = this.files.get(fileName);
225
+ removeFile(key) {
226
+ const abort = this.abortControllers.get(key);
227
+ if (abort)
228
+ abort();
229
+ const fileData = this.files.get(key);
207
230
  if (fileData) {
208
231
  fileData.element.remove();
209
- this.files.delete(fileName);
232
+ this.files.delete(key);
210
233
  this.updateUploadButton();
211
234
  }
212
235
  }
213
236
  updateUploadButton() {
214
237
  this.uploadBtn.disabled = this.files.size === 0;
215
- this.uploadBtn.textContent =
216
- this.files.size > 0
217
- ? `Upload ${this.files.size} File${this.files.size === 1 ? '' : 's'}`
218
- : 'Upload Files';
238
+ this.uploadBtn.textContent = this.files.size > 0
239
+ ? `Upload ${this.files.size} File${this.files.size === 1 ? '' : 's'}`
240
+ : 'Upload Files';
219
241
  }
220
242
  dispatchUploadCompletedEvent(results) {
221
243
  const files = Array.from(this.files.values()).map(({ file }) => file);
222
- const event = new CustomEvent('upload-completed', {
223
- detail: {
224
- fileCount: this.files.size,
225
- files,
226
- results,
227
- },
244
+ this.container.dispatchEvent(new CustomEvent('upload-completed', {
245
+ detail: { fileCount: this.files.size, files, results },
228
246
  bubbles: true,
229
- });
230
- this.container.dispatchEvent(event);
231
- }
232
- cleanupAfterUpload() {
233
- const progressContainers = this.fileList.querySelectorAll('.progress-container');
234
- progressContainers.forEach(el => el.remove());
235
- const statusTexts = this.fileList.querySelectorAll('.status-text');
236
- statusTexts.forEach(el => el.remove());
237
- const removeBtns = this.fileList.querySelectorAll('.remove-btn');
238
- removeBtns.forEach(btn => (btn.style.display = 'flex'));
239
- }
240
- resetUploadState() {
241
- this.files.clear();
242
- this.updateUploadButton();
247
+ }));
243
248
  }
244
249
  formatSize(bytes) {
245
250
  if (bytes === 0)
@@ -255,13 +260,22 @@ class FileUploader {
255
260
  return div.innerHTML;
256
261
  }
257
262
  destroy() {
258
- // Cancel all ongoing uploads
259
- this.abortControllers.forEach(controller => controller.abort());
263
+ this.abortControllers.forEach(abort => abort());
260
264
  this.abortControllers.clear();
261
- // Clear files
265
+ ['dragenter', 'dragover', 'dragleave', 'drop'].forEach(event => {
266
+ this.dropZone.removeEventListener(event, this.preventDefaults);
267
+ });
268
+ ['dragenter', 'dragover'].forEach(event => {
269
+ this.dropZone.removeEventListener(event, this.handleDragEnter);
270
+ });
271
+ ['dragleave', 'drop'].forEach(event => {
272
+ this.dropZone.removeEventListener(event, this.handleDragLeave);
273
+ });
274
+ this.dropZone.removeEventListener('drop', this.handleDrop);
275
+ this.dropZone.removeEventListener('click', this.handleDropZoneClick);
276
+ this.fileInput.removeEventListener('change', this.handleFileInputChange);
277
+ this.uploadBtn.removeEventListener('click', this.handleUploadClick);
262
278
  this.files.clear();
263
- // Remove event listeners would require storing bound handlers
264
- // For now, removing elements will clean up
265
279
  this.fileList.innerHTML = '';
266
280
  }
267
281
  }