snice 1.14.3 → 2.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 (185) hide show
  1. package/bin/templates/base/tsconfig.json +5 -4
  2. package/components/accordion/demo.html +403 -0
  3. package/components/accordion/snice-accordion-item.css +85 -0
  4. package/components/accordion/snice-accordion-item.ts +226 -0
  5. package/components/accordion/snice-accordion.css +31 -0
  6. package/components/accordion/snice-accordion.ts +182 -0
  7. package/components/accordion/snice-accordion.types.ts +32 -0
  8. package/components/alert/demo.html +445 -0
  9. package/components/alert/snice-alert.css +195 -0
  10. package/components/alert/snice-alert.ts +141 -0
  11. package/components/alert/snice-alert.types.ts +12 -0
  12. package/components/avatar/demo.html +598 -0
  13. package/components/avatar/snice-avatar.css +131 -0
  14. package/components/avatar/snice-avatar.ts +136 -0
  15. package/components/avatar/snice-avatar.types.ts +13 -0
  16. package/components/badge/demo.html +523 -0
  17. package/components/badge/snice-badge.css +161 -0
  18. package/components/badge/snice-badge.ts +117 -0
  19. package/components/badge/snice-badge.types.ts +16 -0
  20. package/components/breadcrumbs/demo.html +404 -0
  21. package/components/breadcrumbs/snice-breadcrumbs.css +133 -0
  22. package/components/breadcrumbs/snice-breadcrumbs.ts +191 -0
  23. package/components/breadcrumbs/snice-breadcrumbs.types.ts +26 -0
  24. package/components/breadcrumbs/snice-crumb.ts +26 -0
  25. package/components/button/demo.html +42 -0
  26. package/components/button/snice-button.css +230 -0
  27. package/components/button/snice-button.ts +169 -0
  28. package/components/button/snice-button.types.ts +25 -0
  29. package/components/card/demo.html +525 -0
  30. package/components/card/snice-card.css +140 -0
  31. package/components/card/snice-card.ts +102 -0
  32. package/components/card/snice-card.types.ts +10 -0
  33. package/components/checkbox/demo.html +253 -0
  34. package/components/checkbox/snice-checkbox.css +164 -0
  35. package/components/checkbox/snice-checkbox.ts +223 -0
  36. package/components/checkbox/snice-checkbox.types.ts +22 -0
  37. package/components/chip/demo.html +383 -0
  38. package/components/chip/snice-chip.css +195 -0
  39. package/components/chip/snice-chip.ts +139 -0
  40. package/components/chip/snice-chip.types.ts +15 -0
  41. package/components/date-picker/README.md +233 -0
  42. package/components/date-picker/demo.html +191 -0
  43. package/components/date-picker/snice-date-picker.css +330 -0
  44. package/components/date-picker/snice-date-picker.ts +777 -0
  45. package/components/date-picker/snice-date-picker.types.ts +83 -0
  46. package/components/divider/demo.html +233 -0
  47. package/components/divider/snice-divider.css +155 -0
  48. package/components/divider/snice-divider.ts +69 -0
  49. package/components/divider/snice-divider.types.ts +15 -0
  50. package/components/drawer/demo.html +328 -0
  51. package/components/drawer/snice-drawer.css +476 -0
  52. package/components/drawer/snice-drawer.ts +287 -0
  53. package/components/drawer/snice-drawer.types.ts +17 -0
  54. package/components/global.d.ts +14 -0
  55. package/components/input/demo.html +303 -0
  56. package/components/input/snice-input.css +257 -0
  57. package/components/input/snice-input.ts +442 -0
  58. package/components/input/snice-input.types.ts +59 -0
  59. package/components/input/test.html +77 -0
  60. package/components/layout/README.md +260 -0
  61. package/components/layout/demo.html +538 -0
  62. package/components/layout/snice-layout-blog.css +129 -0
  63. package/components/layout/snice-layout-blog.ts +48 -0
  64. package/components/layout/snice-layout-card.css +104 -0
  65. package/components/layout/snice-layout-card.ts +35 -0
  66. package/components/layout/snice-layout-centered.css +51 -0
  67. package/components/layout/snice-layout-centered.ts +22 -0
  68. package/components/layout/snice-layout-dashboard.css +98 -0
  69. package/components/layout/snice-layout-dashboard.ts +45 -0
  70. package/components/layout/snice-layout-fullscreen.css +72 -0
  71. package/components/layout/snice-layout-fullscreen.ts +34 -0
  72. package/components/layout/snice-layout-landing.css +92 -0
  73. package/components/layout/snice-layout-landing.ts +47 -0
  74. package/components/layout/snice-layout-minimal.css +16 -0
  75. package/components/layout/snice-layout-minimal.ts +19 -0
  76. package/components/layout/snice-layout-sidebar.css +117 -0
  77. package/components/layout/snice-layout-sidebar.ts +48 -0
  78. package/components/layout/snice-layout-split.css +103 -0
  79. package/components/layout/snice-layout-split.ts +29 -0
  80. package/components/layout/snice-layout.css +72 -0
  81. package/components/layout/snice-layout.ts +35 -0
  82. package/components/layout/snice-layout.types.ts +5 -0
  83. package/components/login/demo-auth-controller.ts +185 -0
  84. package/components/login/demo.html +470 -0
  85. package/components/login/snice-login.css +204 -0
  86. package/components/login/snice-login.ts +337 -0
  87. package/components/login/snice-login.types.ts +34 -0
  88. package/components/modal/demo.html +291 -0
  89. package/components/modal/snice-modal.css +203 -0
  90. package/components/modal/snice-modal.ts +233 -0
  91. package/components/modal/snice-modal.types.ts +21 -0
  92. package/components/pagination/demo.html +395 -0
  93. package/components/pagination/snice-pagination.ts +333 -0
  94. package/components/pagination/snice-pagination.types.ts +21 -0
  95. package/components/progress/demo.html +510 -0
  96. package/components/progress/snice-progress.css +267 -0
  97. package/components/progress/snice-progress.ts +247 -0
  98. package/components/progress/snice-progress.types.ts +19 -0
  99. package/components/radio/demo.html +287 -0
  100. package/components/radio/snice-radio.css +171 -0
  101. package/components/radio/snice-radio.ts +218 -0
  102. package/components/radio/snice-radio.types.ts +21 -0
  103. package/components/select/demo.html +511 -0
  104. package/components/select/snice-option.ts +52 -0
  105. package/components/select/snice-option.types.ts +14 -0
  106. package/components/select/snice-select.css +392 -0
  107. package/components/select/snice-select.ts +796 -0
  108. package/components/select/snice-select.types.ts +55 -0
  109. package/components/skeleton/demo.html +514 -0
  110. package/components/skeleton/snice-skeleton.css +109 -0
  111. package/components/skeleton/snice-skeleton.ts +126 -0
  112. package/components/skeleton/snice-skeleton.types.ts +11 -0
  113. package/components/switch/demo.html +284 -0
  114. package/components/switch/snice-switch.css +221 -0
  115. package/components/switch/snice-switch.ts +229 -0
  116. package/components/switch/snice-switch.types.ts +23 -0
  117. package/components/symbols.ts +23 -0
  118. package/components/table/demo-table-controller.ts +100 -0
  119. package/components/table/demo.html +480 -0
  120. package/components/table/snice-cell-boolean.ts +112 -0
  121. package/components/table/snice-cell-date.ts +210 -0
  122. package/components/table/snice-cell-duration.ts +91 -0
  123. package/components/table/snice-cell-filesize.ts +90 -0
  124. package/components/table/snice-cell-number.ts +165 -0
  125. package/components/table/snice-cell-progress.ts +83 -0
  126. package/components/table/snice-cell-rating.ts +82 -0
  127. package/components/table/snice-cell-sparkline.ts +253 -0
  128. package/components/table/snice-cell-text.ts +125 -0
  129. package/components/table/snice-cell.css +296 -0
  130. package/components/table/snice-cell.ts +473 -0
  131. package/components/table/snice-column.ts +353 -0
  132. package/components/table/snice-header.css +243 -0
  133. package/components/table/snice-header.ts +261 -0
  134. package/components/table/snice-progress.ts +66 -0
  135. package/components/table/snice-rating.ts +45 -0
  136. package/components/table/snice-row.css +255 -0
  137. package/components/table/snice-row.ts +331 -0
  138. package/components/table/snice-table.css +241 -0
  139. package/components/table/snice-table.ts +737 -0
  140. package/components/table/snice-table.types.ts +158 -0
  141. package/components/tabs/demo.html +487 -0
  142. package/components/tabs/snice-tab-panel.css +264 -0
  143. package/components/tabs/snice-tab-panel.ts +47 -0
  144. package/components/tabs/snice-tab.css +96 -0
  145. package/components/tabs/snice-tab.ts +65 -0
  146. package/components/tabs/snice-tabs.css +189 -0
  147. package/components/tabs/snice-tabs.ts +332 -0
  148. package/components/tabs/snice-tabs.types.ts +28 -0
  149. package/components/theme/theme.css +234 -0
  150. package/components/toast/demo.html +329 -0
  151. package/components/toast/snice-toast-container.ts +256 -0
  152. package/components/toast/snice-toast.css +213 -0
  153. package/components/toast/snice-toast.ts +276 -0
  154. package/components/toast/snice-toast.types.ts +35 -0
  155. package/components/tooltip/demo.html +350 -0
  156. package/components/tooltip/snice-tooltip-portal.css +79 -0
  157. package/components/tooltip/snice-tooltip.css +117 -0
  158. package/components/tooltip/snice-tooltip.ts +612 -0
  159. package/components/tooltip/snice-tooltip.types.ts +32 -0
  160. package/components/transitions.ts +94 -0
  161. package/components/tsconfig.json +18 -0
  162. package/dist/index.cjs +441 -329
  163. package/dist/index.cjs.map +1 -1
  164. package/dist/index.cjs.min.map +1 -1
  165. package/dist/index.esm.js +441 -329
  166. package/dist/index.esm.js.map +1 -1
  167. package/dist/index.esm.min.js +3 -3
  168. package/dist/index.esm.min.js.map +1 -1
  169. package/dist/index.iife.js +441 -329
  170. package/dist/index.iife.js.map +1 -1
  171. package/dist/index.iife.min.js +3 -3
  172. package/dist/index.iife.min.js.map +1 -1
  173. package/dist/symbols.esm.js +1 -1
  174. package/dist/transitions.esm.js +1 -1
  175. package/dist/types/controller.d.ts +1 -1
  176. package/dist/types/element.d.ts +10 -10
  177. package/dist/types/events.d.ts +2 -2
  178. package/dist/types/index.d.ts +1 -1
  179. package/dist/types/observe.d.ts +1 -1
  180. package/dist/types/request-response.d.ts +2 -3
  181. package/dist/types/router.d.ts +1 -1
  182. package/package.json +9 -3
  183. package/dist/index.cjs.min +0 -15
  184. package/dist/symbols.cjs +0 -103
  185. package/dist/transitions.cjs +0 -219
@@ -0,0 +1,473 @@
1
+ import { element, property, watch, ready, query } from 'snice';
2
+ import css from './snice-cell.css?inline';
3
+ import type {
4
+ SniceCellElement,
5
+ ColumnType,
6
+ ColumnAlign,
7
+ ColumnDefinition,
8
+ NumberFormat,
9
+ DateFormat,
10
+ BooleanFormat,
11
+ RatingFormat,
12
+ ProgressFormat,
13
+ SparklineFormat,
14
+ CellStyle,
15
+ ConditionalFormat
16
+ } from './snice-table.types';
17
+
18
+ @element('snice-cell')
19
+ export class SniceCell extends HTMLElement implements SniceCellElement {
20
+ @property({ reflect: true })
21
+ align: ColumnAlign = 'left';
22
+
23
+ @property({ reflect: true })
24
+ type: ColumnType = 'text';
25
+
26
+ @property()
27
+ value: any = '';
28
+
29
+ @property({ type: Object })
30
+ column: ColumnDefinition = {
31
+ key: '',
32
+ label: '',
33
+ type: 'text',
34
+ align: 'left'
35
+ };
36
+
37
+ @property({ type: Object })
38
+ rowData: any = null;
39
+
40
+ @query('.cell-content')
41
+ contentElement?: HTMLElement;
42
+
43
+ html() {
44
+ return `
45
+ <div class="cell-content" part="content">
46
+ ${this.formatValue()}
47
+ </div>
48
+ `;
49
+ }
50
+
51
+ css() {
52
+ return css;
53
+ }
54
+
55
+ @ready()
56
+ init() {
57
+ this.applyAlignment();
58
+ this.applyConditionalFormatting();
59
+ }
60
+
61
+ @watch('align')
62
+ updateAlignment() {
63
+ this.applyAlignment();
64
+ }
65
+
66
+ @watch('value', 'column')
67
+ updateContent() {
68
+ if (this.contentElement) {
69
+ this.contentElement.innerHTML = this.formatValue();
70
+ }
71
+ this.applyConditionalFormatting();
72
+ }
73
+
74
+ private applyAlignment() {
75
+ this.style.textAlign = this.align;
76
+ }
77
+
78
+ private applyConditionalFormatting() {
79
+ if (!this.column || !this.column.conditionalFormats) return;
80
+
81
+ // Reset any previous conditional formatting
82
+ this.removeAttribute('style');
83
+ this.className = 'snice-cell';
84
+ this.applyAlignment();
85
+
86
+ // Apply column base style if defined
87
+ if (this.column.style) {
88
+ this.applyStyle(this.column.style);
89
+ }
90
+
91
+ // Check and apply conditional formats
92
+ for (const format of this.column.conditionalFormats) {
93
+ if (format.condition(this.value, this.rowData)) {
94
+ if (format.style) {
95
+ this.applyStyle(format.style);
96
+ }
97
+ if (format.className) {
98
+ this.classList.add(format.className);
99
+ }
100
+ break; // Apply only the first matching format
101
+ }
102
+ }
103
+ }
104
+
105
+ private applyStyle(style: CellStyle) {
106
+ if (style.backgroundColor) this.style.backgroundColor = style.backgroundColor;
107
+ if (style.color) this.style.color = style.color;
108
+ if (style.fontWeight) this.style.fontWeight = style.fontWeight;
109
+ if (style.fontStyle) this.style.fontStyle = style.fontStyle;
110
+ if (style.fontSize) this.style.fontSize = style.fontSize;
111
+ if (style.textDecoration) this.style.textDecoration = style.textDecoration;
112
+ }
113
+
114
+ private formatValue(): string {
115
+ if (this.value === null || this.value === undefined) {
116
+ return '';
117
+ }
118
+
119
+ // Use custom formatter if provided
120
+ if (this.column.formatter) {
121
+ return this.column.formatter(this.value, this.rowData);
122
+ }
123
+
124
+ // Apply type-specific formatting
125
+ switch (this.type) {
126
+ case 'text':
127
+ return this.formatText();
128
+ case 'number':
129
+ return this.formatNumber();
130
+ case 'currency':
131
+ return this.formatCurrency();
132
+ case 'percent':
133
+ return this.formatPercent();
134
+ case 'accounting':
135
+ return this.formatAccounting();
136
+ case 'scientific':
137
+ return this.formatScientific();
138
+ case 'fraction':
139
+ return this.formatFraction();
140
+ case 'date':
141
+ return this.formatDate();
142
+ case 'boolean':
143
+ return this.formatBoolean();
144
+ case 'rating':
145
+ return this.formatRating();
146
+ case 'progress':
147
+ return this.formatProgress();
148
+ case 'sparkline':
149
+ return this.formatSparkline();
150
+ case 'duration':
151
+ return this.formatDuration();
152
+ case 'filesize':
153
+ return this.formatFilesize();
154
+ case 'custom':
155
+ return String(this.value);
156
+ default:
157
+ return String(this.value);
158
+ }
159
+ }
160
+
161
+ private formatText(): string {
162
+ return String(this.value);
163
+ }
164
+
165
+ private formatNumber(): string {
166
+ const format = this.column.numberFormat || {};
167
+ const num = Number(this.value);
168
+
169
+ if (isNaN(num)) return String(this.value);
170
+
171
+ let formatted = num.toFixed(format.decimals ?? 0);
172
+
173
+ if (format.thousandsSeparator) {
174
+ const parts = formatted.split('.');
175
+ parts[0] = parts[0].replace(/\B(?=(\d{3})+(?!\d))/g, ',');
176
+ formatted = parts.join('.');
177
+ }
178
+
179
+ if (num < 0 && format.negativeStyle === 'parentheses') {
180
+ formatted = `(${formatted.replace('-', '')})`;
181
+ }
182
+
183
+ if (format.prefix) formatted = format.prefix + formatted;
184
+ if (format.suffix) formatted = formatted + format.suffix;
185
+
186
+ return formatted;
187
+ }
188
+
189
+ private formatCurrency(): string {
190
+ const format = this.column.numberFormat || {};
191
+ const num = Number(this.value);
192
+
193
+ if (isNaN(num)) return String(this.value);
194
+
195
+ const currencyFormat: Intl.NumberFormatOptions = {
196
+ style: 'currency',
197
+ currency: 'USD',
198
+ minimumFractionDigits: format.decimals ?? 2,
199
+ maximumFractionDigits: format.decimals ?? 2
200
+ };
201
+
202
+ return new Intl.NumberFormat('en-US', currencyFormat).format(num);
203
+ }
204
+
205
+ private formatPercent(): string {
206
+ const format = this.column.numberFormat || {};
207
+ const num = Number(this.value);
208
+
209
+ if (isNaN(num)) return String(this.value);
210
+
211
+ const percentFormat: Intl.NumberFormatOptions = {
212
+ style: 'percent',
213
+ minimumFractionDigits: format.decimals ?? 1,
214
+ maximumFractionDigits: format.decimals ?? 1
215
+ };
216
+
217
+ return new Intl.NumberFormat('en-US', percentFormat).format(num);
218
+ }
219
+
220
+ private formatAccounting(): string {
221
+ const format = this.column.numberFormat || {};
222
+ const num = Number(this.value);
223
+
224
+ if (isNaN(num)) return String(this.value);
225
+
226
+ let formatted = Math.abs(num).toFixed(format.decimals ?? 2);
227
+
228
+ if (format.thousandsSeparator) {
229
+ const parts = formatted.split('.');
230
+ parts[0] = parts[0].replace(/\B(?=(\d{3})+(?!\d))/g, ',');
231
+ formatted = parts.join('.');
232
+ }
233
+
234
+ if (num < 0) {
235
+ formatted = `(${formatted})`;
236
+ } else {
237
+ formatted = ` ${formatted} `;
238
+ }
239
+
240
+ return formatted;
241
+ }
242
+
243
+ private formatScientific(): string {
244
+ const format = this.column.numberFormat || {};
245
+ const num = Number(this.value);
246
+
247
+ if (isNaN(num)) return String(this.value);
248
+
249
+ return num.toExponential(format.decimals ?? 2);
250
+ }
251
+
252
+ private formatFraction(): string {
253
+ const num = Number(this.value);
254
+ if (isNaN(num)) return String(this.value);
255
+
256
+ // Simple fraction conversion
257
+ const tolerance = 1e-6;
258
+ let h1 = 1, h2 = 0, k1 = 0, k2 = 1;
259
+ let b = num;
260
+
261
+ do {
262
+ const a = Math.floor(b);
263
+ let aux = h1;
264
+ h1 = a * h1 + h2;
265
+ h2 = aux;
266
+ aux = k1;
267
+ k1 = a * k1 + k2;
268
+ k2 = aux;
269
+ b = 1 / (b - a);
270
+ } while (Math.abs(num - h1 / k1) > num * tolerance);
271
+
272
+ return `${h1}/${k1}`;
273
+ }
274
+
275
+ private formatDate(): string {
276
+ const format = this.column.dateFormat || {};
277
+ const date = new Date(this.value);
278
+
279
+ if (isNaN(date.getTime())) return String(this.value);
280
+
281
+ if (format.customFormat) {
282
+ // Simple custom format implementation
283
+ return format.customFormat
284
+ .replace('YYYY', date.getFullYear().toString())
285
+ .replace('MM', (date.getMonth() + 1).toString().padStart(2, '0'))
286
+ .replace('DD', date.getDate().toString().padStart(2, '0'));
287
+ }
288
+
289
+ const options: Intl.DateTimeFormatOptions = {};
290
+ switch (format.format) {
291
+ case 'short':
292
+ options.dateStyle = 'short';
293
+ break;
294
+ case 'medium':
295
+ options.dateStyle = 'medium';
296
+ break;
297
+ case 'long':
298
+ options.dateStyle = 'long';
299
+ break;
300
+ case 'full':
301
+ options.dateStyle = 'full';
302
+ break;
303
+ default:
304
+ options.dateStyle = 'short';
305
+ }
306
+
307
+ return new Intl.DateTimeFormat(format.locale || 'en-US', options).format(date);
308
+ }
309
+
310
+ private formatBoolean(): string {
311
+ const format = this.column.booleanFormat || {};
312
+ const bool = Boolean(this.value);
313
+
314
+ if (format.useSymbols) {
315
+ return bool
316
+ ? (format.trueSymbol || '✓')
317
+ : (format.falseSymbol || '✗');
318
+ }
319
+
320
+ return bool
321
+ ? (format.trueValue || 'true')
322
+ : (format.falseValue || 'false');
323
+ }
324
+
325
+ private formatRating(): string {
326
+ const format = this.column.ratingFormat || {};
327
+ const rating = Number(this.value);
328
+ const max = format.max || 5;
329
+ const symbol = format.symbol || '★';
330
+ const emptySymbol = format.emptySymbol || '☆';
331
+
332
+ if (isNaN(rating)) return String(this.value);
333
+
334
+ let html = '';
335
+ for (let i = 0; i < max; i++) {
336
+ if (i < rating) {
337
+ html += `<span style="color: ${format.color || '#ffc107'}">${symbol}</span>`;
338
+ } else {
339
+ html += `<span style="color: #ddd">${emptySymbol}</span>`;
340
+ }
341
+ }
342
+ return html;
343
+ }
344
+
345
+ private formatProgress(): string {
346
+ const format = this.column.progressFormat || {};
347
+ const value = Number(this.value);
348
+ const max = format.max || 100;
349
+ const percentage = Math.min(100, Math.max(0, (value / max) * 100));
350
+
351
+ if (isNaN(value)) return String(this.value);
352
+
353
+ const color = format.color || '#007bff';
354
+ const bgColor = format.backgroundColor || '#e9ecef';
355
+ const height = format.height || '1rem'; // Using rem for height
356
+
357
+ let html = `
358
+ <div style="
359
+ width: 100%;
360
+ height: ${height};
361
+ background-color: ${bgColor};
362
+ border-radius: 0.25rem;
363
+ overflow: hidden;
364
+ position: relative;
365
+ ">
366
+ <div style="
367
+ width: ${percentage}%;
368
+ height: 100%;
369
+ background-color: ${color};
370
+ transition: width 0.3s ease;
371
+ "></div>
372
+ `;
373
+
374
+ if (format.showPercentage) {
375
+ html += `
376
+ <span style="
377
+ position: absolute;
378
+ top: 50%;
379
+ left: 50%;
380
+ transform: translate(-50%, -50%);
381
+ font-size: 0.75rem;
382
+ font-weight: bold;
383
+ color: ${percentage > 50 ? 'white' : 'black'};
384
+ ">${Math.round(percentage)}%</span>
385
+ `;
386
+ }
387
+
388
+ html += '</div>';
389
+ return html;
390
+ }
391
+
392
+ private formatSparkline(): string {
393
+ const format = this.column.sparklineFormat || {};
394
+ const data = Array.isArray(this.value) ? this.value : [];
395
+
396
+ if (data.length === 0) return '';
397
+
398
+ const width = format.width || 60;
399
+ const height = format.height || 20;
400
+ const color = format.color || '#007bff';
401
+ const type = format.type || 'line';
402
+
403
+ const max = Math.max(...data);
404
+ const min = Math.min(...data);
405
+ const range = max - min || 1;
406
+
407
+ if (type === 'line') {
408
+ const points = data.map((val, i) => {
409
+ const x = (i / (data.length - 1)) * width;
410
+ const y = height - ((val - min) / range) * height;
411
+ return `${x},${y}`;
412
+ }).join(' ');
413
+
414
+ return `
415
+ <svg width="${width}" height="${height}" style="display: block;">
416
+ <polyline
417
+ points="${points}"
418
+ fill="none"
419
+ stroke="${color}"
420
+ stroke-width="2"
421
+ />
422
+ </svg>
423
+ `;
424
+ } else if (type === 'bar') {
425
+ const barWidth = width / data.length;
426
+ const bars = data.map((val, i) => {
427
+ const barHeight = ((val - min) / range) * height;
428
+ const x = i * barWidth;
429
+ const y = height - barHeight;
430
+ return `<rect x="${x}" y="${y}" width="${barWidth - 1}" height="${barHeight}" fill="${color}" />`;
431
+ }).join('');
432
+
433
+ return `
434
+ <svg width="${width}" height="${height}" style="display: block;">
435
+ ${bars}
436
+ </svg>
437
+ `;
438
+ }
439
+
440
+ return String(this.value);
441
+ }
442
+
443
+ private formatDuration(): string {
444
+ const seconds = Number(this.value);
445
+ if (isNaN(seconds)) return String(this.value);
446
+
447
+ const hours = Math.floor(seconds / 3600);
448
+ const minutes = Math.floor((seconds % 3600) / 60);
449
+ const secs = seconds % 60;
450
+
451
+ if (hours > 0) {
452
+ return `${hours}:${minutes.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`;
453
+ } else {
454
+ return `${minutes}:${secs.toString().padStart(2, '0')}`;
455
+ }
456
+ }
457
+
458
+ private formatFilesize(): string {
459
+ const bytes = Number(this.value);
460
+ if (isNaN(bytes)) return String(this.value);
461
+
462
+ const units = ['B', 'KB', 'MB', 'GB', 'TB'];
463
+ let size = bytes;
464
+ let unitIndex = 0;
465
+
466
+ while (size >= 1024 && unitIndex < units.length - 1) {
467
+ size /= 1024;
468
+ unitIndex++;
469
+ }
470
+
471
+ return `${size.toFixed(unitIndex === 0 ? 0 : 1)} ${units[unitIndex]}`;
472
+ }
473
+ }