canvasframework 0.3.6
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.
- package/README.md +554 -0
- package/components/Accordion.js +252 -0
- package/components/AndroidDatePickerDialog.js +398 -0
- package/components/AppBar.js +225 -0
- package/components/Avatar.js +202 -0
- package/components/BottomNavigationBar.js +205 -0
- package/components/BottomSheet.js +374 -0
- package/components/Button.js +225 -0
- package/components/Card.js +193 -0
- package/components/Checkbox.js +180 -0
- package/components/Chip.js +212 -0
- package/components/CircularProgress.js +143 -0
- package/components/ContextMenu.js +116 -0
- package/components/DatePicker.js +257 -0
- package/components/Dialog.js +367 -0
- package/components/Divider.js +125 -0
- package/components/Drawer.js +261 -0
- package/components/FAB.js +270 -0
- package/components/FileUpload.js +315 -0
- package/components/IOSDatePickerWheel.js +268 -0
- package/components/ImageCarousel.js +193 -0
- package/components/ImageComponent.js +223 -0
- package/components/Input.js +309 -0
- package/components/List.js +94 -0
- package/components/ListItem.js +223 -0
- package/components/Modal.js +364 -0
- package/components/MultiSelectDialog.js +206 -0
- package/components/NumberInput.js +271 -0
- package/components/ProgressBar.js +88 -0
- package/components/RadioButton.js +142 -0
- package/components/SearchInput.js +315 -0
- package/components/SegmentedControl.js +202 -0
- package/components/Select.js +199 -0
- package/components/SelectDialog.js +255 -0
- package/components/Slider.js +113 -0
- package/components/Snackbar.js +243 -0
- package/components/Stepper.js +281 -0
- package/components/SwipeableListItem.js +179 -0
- package/components/Switch.js +147 -0
- package/components/Table.js +492 -0
- package/components/Tabs.js +125 -0
- package/components/Text.js +141 -0
- package/components/TextField.js +331 -0
- package/components/Toast.js +236 -0
- package/components/TreeView.js +420 -0
- package/components/Video.js +397 -0
- package/components/View.js +140 -0
- package/components/VirtualList.js +120 -0
- package/core/CanvasFramework.js +1271 -0
- package/core/CanvasWork.js +32 -0
- package/core/Component.js +153 -0
- package/core/LogicWorker.js +25 -0
- package/core/WebGLCanvasAdapter.js +1369 -0
- package/features/Column.js +43 -0
- package/features/Grid.js +47 -0
- package/features/LayoutComponent.js +43 -0
- package/features/OpenStreetMap.js +310 -0
- package/features/Positioned.js +33 -0
- package/features/PullToRefresh.js +328 -0
- package/features/Row.js +40 -0
- package/features/SignaturePad.js +257 -0
- package/features/Skeleton.js +84 -0
- package/features/Stack.js +21 -0
- package/index.js +101 -0
- package/manager/AccessibilityManager.js +107 -0
- package/manager/ErrorHandler.js +59 -0
- package/manager/FeatureFlags.js +60 -0
- package/manager/MemoryManager.js +107 -0
- package/manager/PerformanceMonitor.js +84 -0
- package/manager/SecurityManager.js +54 -0
- package/package.json +28 -0
- package/utils/AnimationEngine.js +428 -0
- package/utils/DataStore.js +403 -0
- package/utils/EventBus.js +407 -0
- package/utils/FetchClient.js +74 -0
- package/utils/FormValidator.js +355 -0
- package/utils/GeoLocationService.js +62 -0
- package/utils/I18n.js +207 -0
- package/utils/IndexedDBManager.js +273 -0
- package/utils/OfflineSyncManager.js +342 -0
- package/utils/QueryBuilder.js +478 -0
- package/utils/SafeArea.js +64 -0
- package/utils/SecureStorage.js +289 -0
- package/utils/StateManager.js +207 -0
- package/utils/WebSocketClient.js +66 -0
|
@@ -0,0 +1,492 @@
|
|
|
1
|
+
import Component from '../core/Component.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Tableau avec tri, pagination et sélection
|
|
5
|
+
* @class
|
|
6
|
+
* @extends Component
|
|
7
|
+
* @property {Array} columns - Définition des colonnes
|
|
8
|
+
* @property {Array} data - Données du tableau
|
|
9
|
+
* @property {boolean} sortable - Activation du tri
|
|
10
|
+
* @property {boolean} paginated - Activation de la pagination
|
|
11
|
+
* @property {number} rowsPerPage - Lignes par page
|
|
12
|
+
* @property {number} currentPage - Page actuelle
|
|
13
|
+
* @property {string} sortColumn - Colonne de tri
|
|
14
|
+
* @property {string} sortDirection - Direction du tri
|
|
15
|
+
* @property {Set} selectedRows - Lignes sélectionnées
|
|
16
|
+
* @property {boolean} selectable - Sélection activée
|
|
17
|
+
* @property {number} headerHeight - Hauteur de l'en-tête
|
|
18
|
+
* @property {number} rowHeight - Hauteur des lignes
|
|
19
|
+
* @property {string} headerBg - Couleur fond en-tête
|
|
20
|
+
* @property {string} rowBg - Couleur fond ligne
|
|
21
|
+
* @property {string} rowAltBg - Couleur fond ligne alternée
|
|
22
|
+
* @property {Function} onRowClick - Callback clic ligne
|
|
23
|
+
* @property {Function} onSelectionChange - Callback sélection
|
|
24
|
+
*/
|
|
25
|
+
class Table extends Component {
|
|
26
|
+
/**
|
|
27
|
+
* Crée une instance de Table
|
|
28
|
+
* @param {CanvasFramework} framework - Framework parent
|
|
29
|
+
* @param {Object} [options={}] - Options de configuration
|
|
30
|
+
* @param {Array} [options.columns=[]] - Colonnes [{key, label, width, sortable}]
|
|
31
|
+
* @param {Array} [options.data=[]] - Données
|
|
32
|
+
* @param {boolean} [options.sortable=true] - Tri activé
|
|
33
|
+
* @param {boolean} [options.paginated=false] - Pagination
|
|
34
|
+
* @param {number} [options.rowsPerPage=10] - Lignes par page
|
|
35
|
+
* @param {boolean} [options.selectable=false] - Sélection
|
|
36
|
+
* @param {number} [options.headerHeight=48] - Hauteur en-tête
|
|
37
|
+
* @param {number} [options.rowHeight=48] - Hauteur ligne
|
|
38
|
+
* @param {Function} [options.onRowClick] - Callback clic
|
|
39
|
+
* @param {Function} [options.onSelectionChange] - Callback sélection
|
|
40
|
+
*/
|
|
41
|
+
constructor(framework, options = {}) {
|
|
42
|
+
super(framework, options);
|
|
43
|
+
|
|
44
|
+
this.columns = options.columns || [];
|
|
45
|
+
this.data = options.data || [];
|
|
46
|
+
this.sortable = options.sortable !== false;
|
|
47
|
+
this.paginated = options.paginated || false;
|
|
48
|
+
this.rowsPerPage = options.rowsPerPage || 10;
|
|
49
|
+
this.currentPage = 0;
|
|
50
|
+
this.sortColumn = null;
|
|
51
|
+
this.sortDirection = 'asc';
|
|
52
|
+
this.selectedRows = new Set();
|
|
53
|
+
this.selectable = options.selectable || false;
|
|
54
|
+
this.headerHeight = options.headerHeight || 48;
|
|
55
|
+
this.rowHeight = options.rowHeight || 48;
|
|
56
|
+
|
|
57
|
+
this.onRowClick = options.onRowClick || null;
|
|
58
|
+
this.onSelectionChange = options.onSelectionChange || null;
|
|
59
|
+
|
|
60
|
+
const platform = framework.platform;
|
|
61
|
+
|
|
62
|
+
// Styles selon la plateforme
|
|
63
|
+
if (platform === 'material') {
|
|
64
|
+
this.headerBg = '#FAFAFA';
|
|
65
|
+
this.rowBg = '#FFFFFF';
|
|
66
|
+
this.rowAltBg = '#F5F5F5';
|
|
67
|
+
this.borderColor = 'rgba(0, 0, 0, 0.12)';
|
|
68
|
+
this.textColor = '#000000';
|
|
69
|
+
this.headerTextColor = '#616161';
|
|
70
|
+
this.selectedBg = 'rgba(98, 0, 238, 0.08)';
|
|
71
|
+
this.hoverBg = 'rgba(0, 0, 0, 0.04)';
|
|
72
|
+
} else {
|
|
73
|
+
this.headerBg = '#F2F2F7';
|
|
74
|
+
this.rowBg = '#FFFFFF';
|
|
75
|
+
this.rowAltBg = '#F9F9F9';
|
|
76
|
+
this.borderColor = 'rgba(60, 60, 67, 0.29)';
|
|
77
|
+
this.textColor = '#000000';
|
|
78
|
+
this.headerTextColor = '#6C6C70';
|
|
79
|
+
this.selectedBg = 'rgba(0, 122, 255, 0.1)';
|
|
80
|
+
this.hoverBg = 'rgba(0, 0, 0, 0.03)';
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Calculer les largeurs de colonnes
|
|
84
|
+
this.calculateColumnWidths();
|
|
85
|
+
|
|
86
|
+
// Calculer la hauteur totale
|
|
87
|
+
this.updateHeight();
|
|
88
|
+
|
|
89
|
+
// Hover state
|
|
90
|
+
this.hoveredRow = -1;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Calcule les largeurs de colonnes
|
|
95
|
+
* @private
|
|
96
|
+
*/
|
|
97
|
+
calculateColumnWidths() {
|
|
98
|
+
const totalWidth = this.width - (this.selectable ? 48 : 0);
|
|
99
|
+
const definedWidth = this.columns.reduce((sum, col) => sum + (col.width || 0), 0);
|
|
100
|
+
const undefinedCols = this.columns.filter(col => !col.width).length;
|
|
101
|
+
const autoWidth = undefinedCols > 0 ? (totalWidth - definedWidth) / undefinedCols : 0;
|
|
102
|
+
|
|
103
|
+
this.columns.forEach(col => {
|
|
104
|
+
if (!col.width) col.width = autoWidth;
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Met à jour la hauteur du tableau
|
|
110
|
+
* @private
|
|
111
|
+
*/
|
|
112
|
+
updateHeight() {
|
|
113
|
+
const visibleRows = this.paginated ?
|
|
114
|
+
Math.min(this.rowsPerPage, this.data.length) :
|
|
115
|
+
this.data.length;
|
|
116
|
+
this.height = this.headerHeight + (visibleRows * this.rowHeight) +
|
|
117
|
+
(this.paginated ? 48 : 0); // Pagination bar
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Trie les données
|
|
122
|
+
* @param {string} columnKey - Clé de la colonne
|
|
123
|
+
* @private
|
|
124
|
+
*/
|
|
125
|
+
sortData(columnKey) {
|
|
126
|
+
if (this.sortColumn === columnKey) {
|
|
127
|
+
this.sortDirection = this.sortDirection === 'asc' ? 'desc' : 'asc';
|
|
128
|
+
} else {
|
|
129
|
+
this.sortColumn = columnKey;
|
|
130
|
+
this.sortDirection = 'asc';
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
this.data.sort((a, b) => {
|
|
134
|
+
const aVal = a[columnKey];
|
|
135
|
+
const bVal = b[columnKey];
|
|
136
|
+
|
|
137
|
+
if (typeof aVal === 'number' && typeof bVal === 'number') {
|
|
138
|
+
return this.sortDirection === 'asc' ? aVal - bVal : bVal - aVal;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
const aStr = String(aVal).toLowerCase();
|
|
142
|
+
const bStr = String(bVal).toLowerCase();
|
|
143
|
+
|
|
144
|
+
if (this.sortDirection === 'asc') {
|
|
145
|
+
return aStr.localeCompare(bStr);
|
|
146
|
+
} else {
|
|
147
|
+
return bStr.localeCompare(aStr);
|
|
148
|
+
}
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Change de page
|
|
154
|
+
* @param {number} page - Numéro de page
|
|
155
|
+
* @private
|
|
156
|
+
*/
|
|
157
|
+
changePage(page) {
|
|
158
|
+
const maxPage = Math.ceil(this.data.length / this.rowsPerPage) - 1;
|
|
159
|
+
this.currentPage = Math.max(0, Math.min(page, maxPage));
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Obtient les lignes visibles
|
|
164
|
+
* @returns {Array} Lignes visibles
|
|
165
|
+
* @private
|
|
166
|
+
*/
|
|
167
|
+
getVisibleRows() {
|
|
168
|
+
if (!this.paginated) return this.data;
|
|
169
|
+
const start = this.currentPage * this.rowsPerPage;
|
|
170
|
+
return this.data.slice(start, start + this.rowsPerPage);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Dessine le tableau
|
|
175
|
+
* @param {CanvasRenderingContext2D} ctx - Contexte de dessin
|
|
176
|
+
*/
|
|
177
|
+
draw(ctx) {
|
|
178
|
+
ctx.save();
|
|
179
|
+
|
|
180
|
+
// Bordure du tableau
|
|
181
|
+
ctx.strokeStyle = this.borderColor;
|
|
182
|
+
ctx.lineWidth = 1;
|
|
183
|
+
ctx.strokeRect(this.x, this.y, this.width, this.height);
|
|
184
|
+
|
|
185
|
+
// En-tête
|
|
186
|
+
this.drawHeader(ctx);
|
|
187
|
+
|
|
188
|
+
// Lignes
|
|
189
|
+
const visibleRows = this.getVisibleRows();
|
|
190
|
+
visibleRows.forEach((row, index) => {
|
|
191
|
+
this.drawRow(ctx, row, index);
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
// Pagination
|
|
195
|
+
if (this.paginated) {
|
|
196
|
+
this.drawPagination(ctx);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
ctx.restore();
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* Dessine l'en-tête
|
|
204
|
+
* @param {CanvasRenderingContext2D} ctx - Contexte de dessin
|
|
205
|
+
* @private
|
|
206
|
+
*/
|
|
207
|
+
drawHeader(ctx) {
|
|
208
|
+
// Fond
|
|
209
|
+
ctx.fillStyle = this.headerBg;
|
|
210
|
+
ctx.fillRect(this.x, this.y, this.width, this.headerHeight);
|
|
211
|
+
|
|
212
|
+
// Bordure inférieure
|
|
213
|
+
ctx.strokeStyle = this.borderColor;
|
|
214
|
+
ctx.lineWidth = 1;
|
|
215
|
+
ctx.beginPath();
|
|
216
|
+
ctx.moveTo(this.x, this.y + this.headerHeight);
|
|
217
|
+
ctx.lineTo(this.x + this.width, this.y + this.headerHeight);
|
|
218
|
+
ctx.stroke();
|
|
219
|
+
|
|
220
|
+
let currentX = this.x;
|
|
221
|
+
|
|
222
|
+
// Checkbox pour sélectionner tout
|
|
223
|
+
if (this.selectable) {
|
|
224
|
+
const checkboxSize = 20;
|
|
225
|
+
const checkboxX = currentX + 14;
|
|
226
|
+
const checkboxY = this.y + this.headerHeight / 2 - checkboxSize / 2;
|
|
227
|
+
|
|
228
|
+
const allSelected = this.data.length > 0 &&
|
|
229
|
+
this.selectedRows.size === this.data.length;
|
|
230
|
+
|
|
231
|
+
ctx.strokeStyle = this.borderColor;
|
|
232
|
+
ctx.lineWidth = 2;
|
|
233
|
+
ctx.strokeRect(checkboxX, checkboxY, checkboxSize, checkboxSize);
|
|
234
|
+
|
|
235
|
+
if (allSelected) {
|
|
236
|
+
ctx.fillStyle = '#6200EE';
|
|
237
|
+
ctx.fillRect(checkboxX, checkboxY, checkboxSize, checkboxSize);
|
|
238
|
+
|
|
239
|
+
ctx.strokeStyle = '#FFFFFF';
|
|
240
|
+
ctx.lineWidth = 2;
|
|
241
|
+
ctx.beginPath();
|
|
242
|
+
ctx.moveTo(checkboxX + 5, checkboxY + 10);
|
|
243
|
+
ctx.lineTo(checkboxX + 8, checkboxY + 13);
|
|
244
|
+
ctx.lineTo(checkboxX + 15, checkboxY + 6);
|
|
245
|
+
ctx.stroke();
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
currentX += 48;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// Colonnes
|
|
252
|
+
ctx.fillStyle = this.headerTextColor;
|
|
253
|
+
ctx.font = 'bold 14px -apple-system, BlinkMacSystemFont, Roboto, sans-serif';
|
|
254
|
+
ctx.textAlign = 'left';
|
|
255
|
+
ctx.textBaseline = 'middle';
|
|
256
|
+
|
|
257
|
+
this.columns.forEach((col, index) => {
|
|
258
|
+
// Texte
|
|
259
|
+
ctx.fillText(
|
|
260
|
+
col.label,
|
|
261
|
+
currentX + 16,
|
|
262
|
+
this.y + this.headerHeight / 2
|
|
263
|
+
);
|
|
264
|
+
|
|
265
|
+
// Indicateur de tri
|
|
266
|
+
if (this.sortable && col.sortable !== false && this.sortColumn === col.key) {
|
|
267
|
+
const arrowX = currentX + ctx.measureText(col.label).width + 24;
|
|
268
|
+
const arrowY = this.y + this.headerHeight / 2;
|
|
269
|
+
|
|
270
|
+
ctx.beginPath();
|
|
271
|
+
if (this.sortDirection === 'asc') {
|
|
272
|
+
ctx.moveTo(arrowX, arrowY + 3);
|
|
273
|
+
ctx.lineTo(arrowX + 4, arrowY - 3);
|
|
274
|
+
ctx.lineTo(arrowX + 8, arrowY + 3);
|
|
275
|
+
} else {
|
|
276
|
+
ctx.moveTo(arrowX, arrowY - 3);
|
|
277
|
+
ctx.lineTo(arrowX + 4, arrowY + 3);
|
|
278
|
+
ctx.lineTo(arrowX + 8, arrowY - 3);
|
|
279
|
+
}
|
|
280
|
+
ctx.stroke();
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
// Bordure verticale
|
|
284
|
+
if (index < this.columns.length - 1) {
|
|
285
|
+
ctx.strokeStyle = this.borderColor;
|
|
286
|
+
ctx.beginPath();
|
|
287
|
+
ctx.moveTo(currentX + col.width, this.y);
|
|
288
|
+
ctx.lineTo(currentX + col.width, this.y + this.headerHeight);
|
|
289
|
+
ctx.stroke();
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
currentX += col.width;
|
|
293
|
+
});
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
/**
|
|
297
|
+
* Dessine une ligne
|
|
298
|
+
* @param {CanvasRenderingContext2D} ctx - Contexte de dessin
|
|
299
|
+
* @param {Object} row - Données de la ligne
|
|
300
|
+
* @param {number} index - Index de la ligne
|
|
301
|
+
* @private
|
|
302
|
+
*/
|
|
303
|
+
drawRow(ctx, row, index) {
|
|
304
|
+
const rowY = this.y + this.headerHeight + (index * this.rowHeight);
|
|
305
|
+
const isSelected = this.selectedRows.has(row);
|
|
306
|
+
const isHovered = this.hoveredRow === index;
|
|
307
|
+
|
|
308
|
+
// Fond
|
|
309
|
+
let bgColor = this.rowBg;
|
|
310
|
+
if (isSelected) {
|
|
311
|
+
bgColor = this.selectedBg;
|
|
312
|
+
} else if (isHovered) {
|
|
313
|
+
bgColor = this.hoverBg;
|
|
314
|
+
} else if (index % 2 === 1) {
|
|
315
|
+
bgColor = this.rowAltBg;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
ctx.fillStyle = bgColor;
|
|
319
|
+
ctx.fillRect(this.x, rowY, this.width, this.rowHeight);
|
|
320
|
+
|
|
321
|
+
// Bordure inférieure
|
|
322
|
+
ctx.strokeStyle = this.borderColor;
|
|
323
|
+
ctx.lineWidth = 1;
|
|
324
|
+
ctx.beginPath();
|
|
325
|
+
ctx.moveTo(this.x, rowY + this.rowHeight);
|
|
326
|
+
ctx.lineTo(this.x + this.width, rowY + this.rowHeight);
|
|
327
|
+
ctx.stroke();
|
|
328
|
+
|
|
329
|
+
let currentX = this.x;
|
|
330
|
+
|
|
331
|
+
// Checkbox
|
|
332
|
+
if (this.selectable) {
|
|
333
|
+
const checkboxSize = 20;
|
|
334
|
+
const checkboxX = currentX + 14;
|
|
335
|
+
const checkboxY = rowY + this.rowHeight / 2 - checkboxSize / 2;
|
|
336
|
+
|
|
337
|
+
ctx.strokeStyle = this.borderColor;
|
|
338
|
+
ctx.lineWidth = 2;
|
|
339
|
+
ctx.strokeRect(checkboxX, checkboxY, checkboxSize, checkboxSize);
|
|
340
|
+
|
|
341
|
+
if (isSelected) {
|
|
342
|
+
ctx.fillStyle = '#6200EE';
|
|
343
|
+
ctx.fillRect(checkboxX, checkboxY, checkboxSize, checkboxSize);
|
|
344
|
+
|
|
345
|
+
ctx.strokeStyle = '#FFFFFF';
|
|
346
|
+
ctx.lineWidth = 2;
|
|
347
|
+
ctx.beginPath();
|
|
348
|
+
ctx.moveTo(checkboxX + 5, checkboxY + 10);
|
|
349
|
+
ctx.lineTo(checkboxX + 8, checkboxY + 13);
|
|
350
|
+
ctx.lineTo(checkboxX + 15, checkboxY + 6);
|
|
351
|
+
ctx.stroke();
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
currentX += 48;
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
// Cellules
|
|
358
|
+
ctx.fillStyle = this.textColor;
|
|
359
|
+
ctx.font = '14px -apple-system, BlinkMacSystemFont, Roboto, sans-serif';
|
|
360
|
+
ctx.textAlign = 'left';
|
|
361
|
+
ctx.textBaseline = 'middle';
|
|
362
|
+
|
|
363
|
+
this.columns.forEach((col, colIndex) => {
|
|
364
|
+
const cellValue = String(row[col.key] || '');
|
|
365
|
+
const maxWidth = col.width - 32;
|
|
366
|
+
|
|
367
|
+
// Tronquer le texte si nécessaire
|
|
368
|
+
let displayText = cellValue;
|
|
369
|
+
if (ctx.measureText(displayText).width > maxWidth) {
|
|
370
|
+
while (ctx.measureText(displayText + '...').width > maxWidth && displayText.length > 0) {
|
|
371
|
+
displayText = displayText.slice(0, -1);
|
|
372
|
+
}
|
|
373
|
+
displayText += '...';
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
ctx.fillText(
|
|
377
|
+
displayText,
|
|
378
|
+
currentX + 16,
|
|
379
|
+
rowY + this.rowHeight / 2
|
|
380
|
+
);
|
|
381
|
+
|
|
382
|
+
// Bordure verticale
|
|
383
|
+
if (colIndex < this.columns.length - 1) {
|
|
384
|
+
ctx.strokeStyle = this.borderColor;
|
|
385
|
+
ctx.beginPath();
|
|
386
|
+
ctx.moveTo(currentX + col.width, rowY);
|
|
387
|
+
ctx.lineTo(currentX + col.width, rowY + this.rowHeight);
|
|
388
|
+
ctx.stroke();
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
currentX += col.width;
|
|
392
|
+
});
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
/**
|
|
396
|
+
* Dessine la pagination
|
|
397
|
+
* @param {CanvasRenderingContext2D} ctx - Contexte de dessin
|
|
398
|
+
* @private
|
|
399
|
+
*/
|
|
400
|
+
drawPagination(ctx) {
|
|
401
|
+
const paginationY = this.y + this.height - 48;
|
|
402
|
+
const totalPages = Math.ceil(this.data.length / this.rowsPerPage);
|
|
403
|
+
|
|
404
|
+
// Fond
|
|
405
|
+
ctx.fillStyle = this.headerBg;
|
|
406
|
+
ctx.fillRect(this.x, paginationY, this.width, 48);
|
|
407
|
+
|
|
408
|
+
// Bordure supérieure
|
|
409
|
+
ctx.strokeStyle = this.borderColor;
|
|
410
|
+
ctx.lineWidth = 1;
|
|
411
|
+
ctx.beginPath();
|
|
412
|
+
ctx.moveTo(this.x, paginationY);
|
|
413
|
+
ctx.lineTo(this.x + this.width, paginationY);
|
|
414
|
+
ctx.stroke();
|
|
415
|
+
|
|
416
|
+
// Texte
|
|
417
|
+
ctx.fillStyle = this.textColor;
|
|
418
|
+
ctx.font = '14px -apple-system, BlinkMacSystemFont, Roboto, sans-serif';
|
|
419
|
+
ctx.textAlign = 'center';
|
|
420
|
+
ctx.textBaseline = 'middle';
|
|
421
|
+
|
|
422
|
+
const start = this.currentPage * this.rowsPerPage + 1;
|
|
423
|
+
const end = Math.min((this.currentPage + 1) * this.rowsPerPage, this.data.length);
|
|
424
|
+
const text = `${start}-${end} of ${this.data.length}`;
|
|
425
|
+
|
|
426
|
+
ctx.fillText(text, this.x + this.width / 2, paginationY + 24);
|
|
427
|
+
|
|
428
|
+
// Boutons prev/next (simples flèches)
|
|
429
|
+
const arrowSize = 20;
|
|
430
|
+
const prevX = this.x + this.width - 100;
|
|
431
|
+
const nextX = this.x + this.width - 50;
|
|
432
|
+
const arrowY = paginationY + 24;
|
|
433
|
+
|
|
434
|
+
// Prev
|
|
435
|
+
ctx.strokeStyle = this.currentPage > 0 ? this.textColor : this.borderColor;
|
|
436
|
+
ctx.lineWidth = 2;
|
|
437
|
+
ctx.beginPath();
|
|
438
|
+
ctx.moveTo(prevX + 8, arrowY - 6);
|
|
439
|
+
ctx.lineTo(prevX, arrowY);
|
|
440
|
+
ctx.lineTo(prevX + 8, arrowY + 6);
|
|
441
|
+
ctx.stroke();
|
|
442
|
+
|
|
443
|
+
// Next
|
|
444
|
+
ctx.strokeStyle = this.currentPage < totalPages - 1 ? this.textColor : this.borderColor;
|
|
445
|
+
ctx.beginPath();
|
|
446
|
+
ctx.moveTo(nextX - 8, arrowY - 6);
|
|
447
|
+
ctx.lineTo(nextX, arrowY);
|
|
448
|
+
ctx.lineTo(nextX - 8, arrowY + 6);
|
|
449
|
+
ctx.stroke();
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
/**
|
|
453
|
+
* Vérifie si un point est dans les limites
|
|
454
|
+
* @param {number} x - Coordonnée X
|
|
455
|
+
* @param {number} y - Coordonnée Y
|
|
456
|
+
* @returns {boolean} True si le point est dans le tableau
|
|
457
|
+
*/
|
|
458
|
+
isPointInside(x, y) {
|
|
459
|
+
return x >= this.x &&
|
|
460
|
+
x <= this.x + this.width &&
|
|
461
|
+
y >= this.y &&
|
|
462
|
+
y <= this.y + this.height;
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
/**
|
|
466
|
+
* Gère le clic
|
|
467
|
+
* @param {number} x - Coordonnée X
|
|
468
|
+
* @param {number} y - Coordonnée Y
|
|
469
|
+
*/
|
|
470
|
+
onClick() {
|
|
471
|
+
// Implémenté dans checkComponentsAtPosition
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
/**
|
|
475
|
+
* Gère le mouvement
|
|
476
|
+
* @param {number} x - Coordonnée X
|
|
477
|
+
* @param {number} y - Coordonnée Y
|
|
478
|
+
*/
|
|
479
|
+
onMove(x, y) {
|
|
480
|
+
const relY = y - this.y - this.headerHeight;
|
|
481
|
+
const rowIndex = Math.floor(relY / this.rowHeight);
|
|
482
|
+
|
|
483
|
+
const visibleRows = this.getVisibleRows();
|
|
484
|
+
if (rowIndex >= 0 && rowIndex < visibleRows.length) {
|
|
485
|
+
this.hoveredRow = rowIndex;
|
|
486
|
+
} else {
|
|
487
|
+
this.hoveredRow = -1;
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
export default Table;
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import Component from '../core/Component.js';
|
|
2
|
+
/**
|
|
3
|
+
* Onglets de navigation
|
|
4
|
+
* @class
|
|
5
|
+
* @extends Component
|
|
6
|
+
* @property {Array} tabs - Onglets [{label, icon}]
|
|
7
|
+
* @property {number} selectedIndex - Onglet sélectionné
|
|
8
|
+
* @property {Function} onChange - Callback au changement
|
|
9
|
+
* @property {string} platform - Plateforme
|
|
10
|
+
* @property {string} indicatorColor - Couleur de l'indicateur
|
|
11
|
+
* @property {string} textColor - Couleur du texte
|
|
12
|
+
* @property {string} selectedTextColor - Couleur du texte sélectionné
|
|
13
|
+
*/
|
|
14
|
+
class Tabs extends Component {
|
|
15
|
+
/**
|
|
16
|
+
* Crée une instance de Tabs
|
|
17
|
+
* @param {CanvasFramework} framework - Framework parent
|
|
18
|
+
* @param {Object} [options={}] - Options de configuration
|
|
19
|
+
* @param {Array} [options.tabs=[]] - Onglets
|
|
20
|
+
* @param {number} [options.selectedIndex=0] - Onglet sélectionné
|
|
21
|
+
* @param {Function} [options.onChange] - Callback au changement
|
|
22
|
+
* @param {number} [options.height=48] - Hauteur
|
|
23
|
+
* @param {string} [options.indicatorColor] - Couleur indicateur (auto selon platform)
|
|
24
|
+
* @param {string} [options.textColor='#000000'] - Couleur texte
|
|
25
|
+
* @param {string} [options.selectedTextColor] - Couleur texte sélectionné (auto selon indicatorColor)
|
|
26
|
+
*/
|
|
27
|
+
constructor(framework, options = {}) {
|
|
28
|
+
super(framework, options);
|
|
29
|
+
this.tabs = options.tabs || []; // [{label: 'Tab 1', icon: '📱'}, ...]
|
|
30
|
+
this.selectedIndex = options.selectedIndex || 0;
|
|
31
|
+
this.onChange = options.onChange;
|
|
32
|
+
this.platform = framework.platform;
|
|
33
|
+
this.height = options.height || 48;
|
|
34
|
+
this.indicatorColor = options.indicatorColor || (framework.platform === 'material' ? '#6200EE' : '#007AFF');
|
|
35
|
+
this.textColor = options.textColor || '#000000';
|
|
36
|
+
this.selectedTextColor = options.selectedTextColor || this.indicatorColor;
|
|
37
|
+
|
|
38
|
+
this.onPress = this.handlePress.bind(this);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Dessine les onglets
|
|
43
|
+
* @param {CanvasRenderingContext2D} ctx - Contexte de dessin
|
|
44
|
+
*/
|
|
45
|
+
draw(ctx) {
|
|
46
|
+
ctx.save();
|
|
47
|
+
|
|
48
|
+
// Background
|
|
49
|
+
ctx.fillStyle = '#FFFFFF';
|
|
50
|
+
ctx.fillRect(this.x, this.y, this.width, this.height);
|
|
51
|
+
|
|
52
|
+
// Bordure inférieure
|
|
53
|
+
ctx.strokeStyle = '#E0E0E0';
|
|
54
|
+
ctx.lineWidth = 1;
|
|
55
|
+
ctx.beginPath();
|
|
56
|
+
ctx.moveTo(this.x, this.y + this.height);
|
|
57
|
+
ctx.lineTo(this.x + this.width, this.y + this.height);
|
|
58
|
+
ctx.stroke();
|
|
59
|
+
|
|
60
|
+
const tabWidth = this.width / this.tabs.length;
|
|
61
|
+
|
|
62
|
+
for (let i = 0; i < this.tabs.length; i++) {
|
|
63
|
+
const tab = this.tabs[i];
|
|
64
|
+
const tabX = this.x + i * tabWidth;
|
|
65
|
+
const isSelected = i === this.selectedIndex;
|
|
66
|
+
const color = isSelected ? this.selectedTextColor : this.textColor;
|
|
67
|
+
|
|
68
|
+
// Icône (si présente)
|
|
69
|
+
if (tab.icon) {
|
|
70
|
+
ctx.font = '20px sans-serif';
|
|
71
|
+
ctx.textAlign = 'center';
|
|
72
|
+
ctx.textBaseline = 'middle';
|
|
73
|
+
ctx.fillStyle = color;
|
|
74
|
+
ctx.fillText(tab.icon, tabX + tabWidth / 2, this.y + 16);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Label
|
|
78
|
+
ctx.font = `${isSelected ? 'bold ' : ''}14px -apple-system, Roboto, sans-serif`;
|
|
79
|
+
ctx.fillStyle = color;
|
|
80
|
+
ctx.textAlign = 'center';
|
|
81
|
+
ctx.textBaseline = 'middle';
|
|
82
|
+
const labelY = tab.icon ? this.y + 36 : this.y + this.height / 2;
|
|
83
|
+
ctx.fillText(tab.label, tabX + tabWidth / 2, labelY);
|
|
84
|
+
|
|
85
|
+
// Indicateur (Material uniquement)
|
|
86
|
+
if (isSelected && this.platform === 'material') {
|
|
87
|
+
ctx.fillStyle = this.indicatorColor;
|
|
88
|
+
ctx.fillRect(tabX, this.y + this.height - 2, tabWidth, 2);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
ctx.restore();
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Gère la pression (clic)
|
|
97
|
+
* @param {number} x - Coordonnée X
|
|
98
|
+
* @param {number} y - Coordonnée Y
|
|
99
|
+
* @private
|
|
100
|
+
*/
|
|
101
|
+
handlePress(x, y) {
|
|
102
|
+
const tabWidth = this.width / this.tabs.length;
|
|
103
|
+
const index = Math.floor((x - this.x) / tabWidth);
|
|
104
|
+
|
|
105
|
+
if (index >= 0 && index < this.tabs.length && index !== this.selectedIndex) {
|
|
106
|
+
this.selectedIndex = index;
|
|
107
|
+
if (this.onChange) {
|
|
108
|
+
this.onChange(index, this.tabs[index]);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Vérifie si un point est dans les limites
|
|
115
|
+
* @param {number} x - Coordonnée X
|
|
116
|
+
* @param {number} y - Coordonnée Y
|
|
117
|
+
* @returns {boolean} True si le point est dans les onglets
|
|
118
|
+
*/
|
|
119
|
+
isPointInside(x, y) {
|
|
120
|
+
return x >= this.x && x <= this.x + this.width &&
|
|
121
|
+
y >= this.y && y <= this.y + this.height;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
export default Tabs;
|