canvasframework 0.5.16 → 0.5.18
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/dist/canvasframework.js +2 -0
- package/dist/canvasframework.js.LICENSE.txt +1 -0
- package/package.json +18 -17
- package/components/Accordion.js +0 -265
- package/components/AndroidDatePickerDialog.js +0 -406
- package/components/AppBar.js +0 -398
- package/components/AudioPlayer.js +0 -611
- package/components/Avatar.js +0 -202
- package/components/Banner.js +0 -342
- package/components/BottomNavigationBar.js +0 -433
- package/components/BottomSheet.js +0 -234
- package/components/Button.js +0 -360
- package/components/Camera.js +0 -644
- package/components/Card.js +0 -193
- package/components/Chart.js +0 -700
- package/components/Checkbox.js +0 -166
- package/components/Chip.js +0 -212
- package/components/CircularProgress.js +0 -327
- package/components/ContextMenu.js +0 -116
- package/components/DatePicker.js +0 -298
- package/components/Dialog.js +0 -337
- package/components/Divider.js +0 -125
- package/components/Drawer.js +0 -276
- package/components/FAB.js +0 -270
- package/components/FileUpload.js +0 -315
- package/components/FloatedCamera.js +0 -644
- package/components/IOSDatePickerWheel.js +0 -430
- package/components/ImageCarousel.js +0 -219
- package/components/ImageComponent.js +0 -223
- package/components/Input.js +0 -831
- package/components/InputDatalist.js +0 -723
- package/components/InputTags.js +0 -624
- package/components/List.js +0 -95
- package/components/ListItem.js +0 -269
- package/components/Modal.js +0 -364
- package/components/MorphingFAB.js +0 -428
- package/components/MultiSelectDialog.js +0 -206
- package/components/NumberInput.js +0 -271
- package/components/PasswordInput.js +0 -462
- package/components/ProgressBar.js +0 -88
- package/components/QRCodeReader.js +0 -539
- package/components/RadioButton.js +0 -151
- package/components/SearchInput.js +0 -315
- package/components/SegmentedControl.js +0 -357
- package/components/Select.js +0 -199
- package/components/SelectDialog.js +0 -255
- package/components/Slider.js +0 -113
- package/components/SliverAppBar.js +0 -139
- package/components/Snackbar.js +0 -243
- package/components/SpeedDialFAB.js +0 -397
- package/components/Stepper.js +0 -281
- package/components/SwipeableListItem.js +0 -327
- package/components/Switch.js +0 -147
- package/components/Table.js +0 -492
- package/components/Tabs.js +0 -423
- package/components/Text.js +0 -141
- package/components/TextField.js +0 -151
- package/components/TimePicker.js +0 -934
- package/components/Toast.js +0 -236
- package/components/TreeView.js +0 -420
- package/components/Video.js +0 -397
- package/components/View.js +0 -140
- package/components/VirtualList.js +0 -120
- package/core/CanvasFramework.js +0 -3034
- package/core/Component.js +0 -243
- package/core/ThemeManager.js +0 -358
- package/core/UIBuilder.js +0 -267
- package/core/WebGLCanvasAdapter.js +0 -782
- package/features/Column.js +0 -43
- package/features/Grid.js +0 -47
- package/features/LayoutComponent.js +0 -43
- package/features/OpenStreetMap.js +0 -310
- package/features/Positioned.js +0 -33
- package/features/PullToRefresh.js +0 -328
- package/features/Row.js +0 -40
- package/features/SignaturePad.js +0 -257
- package/features/Skeleton.js +0 -193
- package/features/Stack.js +0 -21
- package/index.js +0 -119
- package/manager/AccessibilityManager.js +0 -107
- package/manager/ErrorHandler.js +0 -59
- package/manager/FeatureFlags.js +0 -60
- package/manager/MemoryManager.js +0 -107
- package/manager/PerformanceMonitor.js +0 -84
- package/manager/SecurityManager.js +0 -54
- package/utils/AnimationEngine.js +0 -734
- package/utils/CryptoManager.js +0 -303
- package/utils/DataStore.js +0 -403
- package/utils/DevTools.js +0 -1618
- package/utils/DevToolsConsole.js +0 -201
- package/utils/EventBus.js +0 -407
- package/utils/FetchClient.js +0 -74
- package/utils/FirebaseAuth.js +0 -653
- package/utils/FirebaseCore.js +0 -246
- package/utils/FirebaseFirestore.js +0 -581
- package/utils/FirebaseFunctions.js +0 -97
- package/utils/FirebaseRealtimeDB.js +0 -498
- package/utils/FirebaseStorage.js +0 -612
- package/utils/FormValidator.js +0 -355
- package/utils/GeoLocationService.js +0 -62
- package/utils/I18n.js +0 -207
- package/utils/IndexedDBManager.js +0 -273
- package/utils/InspectionOverlay.js +0 -308
- package/utils/NotificationManager.js +0 -60
- package/utils/OfflineSyncManager.js +0 -342
- package/utils/PayPalPayment.js +0 -678
- package/utils/QueryBuilder.js +0 -478
- package/utils/SafeArea.js +0 -64
- package/utils/SecureStorage.js +0 -289
- package/utils/StateManager.js +0 -207
- package/utils/StripePayment.js +0 -552
- package/utils/WebSocketClient.js +0 -66
package/components/Tabs.js
DELETED
|
@@ -1,423 +0,0 @@
|
|
|
1
|
-
import Component from '../core/Component.js';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Onglets de navigation avec support Material & Cupertino
|
|
5
|
-
* @class
|
|
6
|
-
* @extends Component
|
|
7
|
-
*/
|
|
8
|
-
class Tabs extends Component {
|
|
9
|
-
constructor(framework, options = {}) {
|
|
10
|
-
super(framework, options);
|
|
11
|
-
|
|
12
|
-
this.tabs = options.tabs || [];
|
|
13
|
-
this.selectedIndex = options.selectedIndex || 0;
|
|
14
|
-
this.onChange = options.onChange;
|
|
15
|
-
this.platform = framework.platform;
|
|
16
|
-
this.height = options.height || 56;
|
|
17
|
-
|
|
18
|
-
this.indicatorColor = options.indicatorColor ||
|
|
19
|
-
(this.platform === 'material' ? '#6200EE' : '#007AFF');
|
|
20
|
-
this.textColor = options.textColor ||
|
|
21
|
-
(this.platform === 'material' ? '#000000' : '#8E8E93');
|
|
22
|
-
this.selectedTextColor = options.selectedTextColor || this.indicatorColor;
|
|
23
|
-
|
|
24
|
-
// Ripple pour Material
|
|
25
|
-
this.ripples = [];
|
|
26
|
-
this.animationFrame = null;
|
|
27
|
-
this.lastAnimationTime = 0;
|
|
28
|
-
|
|
29
|
-
// Animation pour Cupertino
|
|
30
|
-
this.pressedTabIndex = -1;
|
|
31
|
-
this.pressAnimation = 0;
|
|
32
|
-
|
|
33
|
-
// ✅ Structure: tableau de tableaux d'enfants
|
|
34
|
-
// tabChildren[0] = [enfants du tab 0]
|
|
35
|
-
// tabChildren[1] = [enfants du tab 1]
|
|
36
|
-
this.tabChildren = this.tabs.map(() => []);
|
|
37
|
-
|
|
38
|
-
// ✅ Configuration: nombre d'enfants par tab
|
|
39
|
-
// Si défini, distribue automatiquement les enfants
|
|
40
|
-
// Ex: childrenPerTab = [3, 2] => 3 enfants pour tab 0, 2 pour tab 1
|
|
41
|
-
this.childrenPerTab = options.childrenPerTab || null;
|
|
42
|
-
this.currentTabIndex = 0;
|
|
43
|
-
this.childAddCount = 0; // Compteur d'enfants ajoutés
|
|
44
|
-
|
|
45
|
-
// Gestionnaire de clic
|
|
46
|
-
this.onPress = this.handlePress.bind(this);
|
|
47
|
-
|
|
48
|
-
// Position par défaut
|
|
49
|
-
this.position = options.position || (this.platform === 'cupertino' ? 'bottom' : 'top');
|
|
50
|
-
|
|
51
|
-
if (this.position === 'bottom' && !options.y) {
|
|
52
|
-
this.y = framework.height - this.height;
|
|
53
|
-
} else if (this.position === 'top' && !options.y) {
|
|
54
|
-
this.y = options.appbar || 0;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
// Zone de contenu (sous les tabs)
|
|
58
|
-
this.contentY = this.y + this.height;
|
|
59
|
-
this.contentHeight = framework.height - this.height;
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
/**
|
|
63
|
-
* ✅ Définit le tab actuel pour l'ajout d'enfants (appelé par UIBuilder)
|
|
64
|
-
* @param {number} tabIndex - Index du tab
|
|
65
|
-
*/
|
|
66
|
-
setCurrentTab(tabIndex) {
|
|
67
|
-
if (tabIndex >= 0 && tabIndex < this.tabChildren.length) {
|
|
68
|
-
this.currentTabIndex = tabIndex;
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
/**
|
|
73
|
-
* ✅ Ajoute un enfant au tab en cours
|
|
74
|
-
* Distribution automatique: divise les enfants équitablement entre les tabs
|
|
75
|
-
* @param {Component} child - Composant enfant
|
|
76
|
-
* @returns {Component} L'enfant ajouté
|
|
77
|
-
*/
|
|
78
|
-
add(child) {
|
|
79
|
-
// Coordonnées relatives à la zone de contenu
|
|
80
|
-
child.x = child.x || 0;
|
|
81
|
-
child.y = child.y || 0;
|
|
82
|
-
|
|
83
|
-
// Dimensions par défaut
|
|
84
|
-
if (!child.width) child.width = this.framework.width;
|
|
85
|
-
|
|
86
|
-
// Marquer l'enfant comme appartenant à ce Tabs
|
|
87
|
-
child.parentTabs = this;
|
|
88
|
-
|
|
89
|
-
// ✅ Calculer quel tab doit recevoir cet enfant
|
|
90
|
-
// On distribue équitablement les enfants entre les tabs
|
|
91
|
-
const totalChildren = this.tabChildren.reduce((sum, arr) => sum + arr.length, 0);
|
|
92
|
-
const childrenPerTab = Math.ceil(totalChildren / this.tabs.length);
|
|
93
|
-
|
|
94
|
-
// Trouver le premier tab qui n'est pas encore plein
|
|
95
|
-
let targetTabIndex = 0;
|
|
96
|
-
for (let i = 0; i < this.tabChildren.length; i++) {
|
|
97
|
-
if (this.tabChildren[i].length < childrenPerTab) {
|
|
98
|
-
targetTabIndex = i;
|
|
99
|
-
break;
|
|
100
|
-
}
|
|
101
|
-
// Si tous les tabs ont childrenPerTab enfants, recommencer à 0
|
|
102
|
-
if (i === this.tabChildren.length - 1) {
|
|
103
|
-
targetTabIndex = totalChildren % this.tabs.length;
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
// Ajouter au tableau du tab calculé
|
|
108
|
-
this.tabChildren[targetTabIndex].push(child);
|
|
109
|
-
|
|
110
|
-
// Visibilité selon le tab sélectionné
|
|
111
|
-
child.visible = (targetTabIndex === this.selectedIndex);
|
|
112
|
-
|
|
113
|
-
return child;
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
/**
|
|
117
|
-
* ✅ Met à jour la visibilité des enfants selon l'onglet sélectionné
|
|
118
|
-
*/
|
|
119
|
-
updateChildrenVisibility() {
|
|
120
|
-
this.tabChildren.forEach((children, tabIdx) => {
|
|
121
|
-
const isVisible = (tabIdx === this.selectedIndex);
|
|
122
|
-
children.forEach(child => {
|
|
123
|
-
child.visible = isVisible;
|
|
124
|
-
});
|
|
125
|
-
});
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
/**
|
|
129
|
-
* ✅ Retourne tous les enfants du tab sélectionné
|
|
130
|
-
*/
|
|
131
|
-
getActiveChildren() {
|
|
132
|
-
return this.tabChildren[this.selectedIndex] || [];
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
handlePress(x, y) {
|
|
136
|
-
// D'abord vérifier les clics sur les enfants
|
|
137
|
-
if (y > this.y + this.height && this.checkChildClick(x, y)) {
|
|
138
|
-
return;
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
// Ensuite vérifier les clics sur la barre de tabs
|
|
142
|
-
if (!this.isPointInside(x, y)) return;
|
|
143
|
-
|
|
144
|
-
const tabWidth = this.width / this.tabs.length;
|
|
145
|
-
const index = Math.floor((x - this.x) / tabWidth);
|
|
146
|
-
|
|
147
|
-
if (index < 0 || index >= this.tabs.length) return;
|
|
148
|
-
|
|
149
|
-
// Ripple pour Material
|
|
150
|
-
if (this.platform === 'material') {
|
|
151
|
-
const rippleCenterX = this.x + index * tabWidth + tabWidth / 2;
|
|
152
|
-
const maxRippleRadius = Math.min(tabWidth * 0.6, this.height * 0.8);
|
|
153
|
-
|
|
154
|
-
this.ripples.push({
|
|
155
|
-
x: rippleCenterX,
|
|
156
|
-
y: this.y + this.height / 2,
|
|
157
|
-
radius: 0,
|
|
158
|
-
maxRadius: maxRippleRadius,
|
|
159
|
-
opacity: 1
|
|
160
|
-
});
|
|
161
|
-
|
|
162
|
-
if (!this.animationFrame) this.startRippleAnimation();
|
|
163
|
-
}
|
|
164
|
-
// Animation Cupertino
|
|
165
|
-
else if (this.platform === 'cupertino') {
|
|
166
|
-
this.pressedTabIndex = index;
|
|
167
|
-
this.pressAnimation = 1;
|
|
168
|
-
this.requestRender();
|
|
169
|
-
setTimeout(() => this.animatePressRelease(), 100);
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
// Changement d'onglet
|
|
173
|
-
if (index !== this.selectedIndex) {
|
|
174
|
-
this.selectedIndex = index;
|
|
175
|
-
this.updateChildrenVisibility();
|
|
176
|
-
if (this.onChange) this.onChange(index, this.tabs[index]);
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
this.requestRender();
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
/**
|
|
183
|
-
* ✅ Vérifie les clics sur les enfants du tab actif
|
|
184
|
-
*/
|
|
185
|
-
checkChildClick(x, y) {
|
|
186
|
-
const adjustedY = y - (this.framework.scrollOffset || 0);
|
|
187
|
-
const activeChildren = this.getActiveChildren();
|
|
188
|
-
|
|
189
|
-
// Parcourir en ordre inverse (derniers ajoutés = au dessus)
|
|
190
|
-
for (let i = activeChildren.length - 1; i >= 0; i--) {
|
|
191
|
-
const child = activeChildren[i];
|
|
192
|
-
|
|
193
|
-
if (!child.visible) continue;
|
|
194
|
-
|
|
195
|
-
// Calculer les coordonnées absolues de l'enfant
|
|
196
|
-
const childX = this.x + child.x;
|
|
197
|
-
const childY = this.contentY + child.y;
|
|
198
|
-
|
|
199
|
-
// Vérifier si le clic est dans l'enfant
|
|
200
|
-
if (adjustedY >= childY &&
|
|
201
|
-
adjustedY <= childY + child.height &&
|
|
202
|
-
x >= childX &&
|
|
203
|
-
x <= childX + child.width) {
|
|
204
|
-
|
|
205
|
-
// Si l'enfant a un onClick ou onPress, le déclencher
|
|
206
|
-
if (child.onClick) {
|
|
207
|
-
child.onClick();
|
|
208
|
-
return true;
|
|
209
|
-
} else if (child.onPress) {
|
|
210
|
-
child.onPress(x, adjustedY);
|
|
211
|
-
return true;
|
|
212
|
-
}
|
|
213
|
-
}
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
return false;
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
animatePressRelease() {
|
|
220
|
-
let startTime = null;
|
|
221
|
-
const duration = 150;
|
|
222
|
-
|
|
223
|
-
const animate = (timestamp) => {
|
|
224
|
-
if (!startTime) startTime = timestamp;
|
|
225
|
-
const progress = Math.min((timestamp - startTime) / duration, 1);
|
|
226
|
-
|
|
227
|
-
this.pressAnimation = 1 - progress;
|
|
228
|
-
this.requestRender();
|
|
229
|
-
|
|
230
|
-
if (progress < 1) requestAnimationFrame(animate);
|
|
231
|
-
else {
|
|
232
|
-
this.pressAnimation = 0;
|
|
233
|
-
this.pressedTabIndex = -1;
|
|
234
|
-
}
|
|
235
|
-
};
|
|
236
|
-
|
|
237
|
-
requestAnimationFrame(animate);
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
isPointInside(x, y) {
|
|
241
|
-
return x >= this.x && x <= this.x + this.width &&
|
|
242
|
-
y >= this.y && y <= this.y + this.height;
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
startRippleAnimation() {
|
|
246
|
-
const animate = (timestamp) => {
|
|
247
|
-
if (!this.lastAnimationTime) this.lastAnimationTime = timestamp;
|
|
248
|
-
const deltaTime = timestamp - this.lastAnimationTime;
|
|
249
|
-
this.lastAnimationTime = timestamp;
|
|
250
|
-
|
|
251
|
-
let needsUpdate = false;
|
|
252
|
-
|
|
253
|
-
for (let i = this.ripples.length - 1; i >= 0; i--) {
|
|
254
|
-
const ripple = this.ripples[i];
|
|
255
|
-
|
|
256
|
-
if (ripple.radius < ripple.maxRadius)
|
|
257
|
-
ripple.radius += (ripple.maxRadius / 300) * deltaTime;
|
|
258
|
-
|
|
259
|
-
if (ripple.radius >= ripple.maxRadius * 0.4) {
|
|
260
|
-
ripple.opacity -= (0.003 * deltaTime);
|
|
261
|
-
if (ripple.opacity < 0) ripple.opacity = 0;
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
if (ripple.opacity <= 0 && ripple.radius >= ripple.maxRadius * 0.95)
|
|
265
|
-
this.ripples.splice(i, 1);
|
|
266
|
-
|
|
267
|
-
needsUpdate = true;
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
if (needsUpdate) this.requestRender();
|
|
271
|
-
|
|
272
|
-
if (this.ripples.length > 0)
|
|
273
|
-
this.animationFrame = requestAnimationFrame(animate);
|
|
274
|
-
else
|
|
275
|
-
this.animationFrame = null;
|
|
276
|
-
};
|
|
277
|
-
|
|
278
|
-
if (this.ripples.length && !this.animationFrame)
|
|
279
|
-
this.animationFrame = requestAnimationFrame(animate);
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
requestRender() {
|
|
283
|
-
if (this.framework && this.framework.requestRender)
|
|
284
|
-
this.framework.requestRender();
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
/**
|
|
288
|
-
* ✅ Dessine les tabs et les enfants du tab actif
|
|
289
|
-
*/
|
|
290
|
-
draw(ctx) {
|
|
291
|
-
ctx.save();
|
|
292
|
-
|
|
293
|
-
// ===== DESSINER LA BARRE DE TABS =====
|
|
294
|
-
|
|
295
|
-
// Background
|
|
296
|
-
ctx.fillStyle = this.platform === 'material' ? '#FFF' : '#F2F2F7';
|
|
297
|
-
ctx.fillRect(this.x, this.y, this.width, this.height);
|
|
298
|
-
|
|
299
|
-
if (this.platform === 'material') {
|
|
300
|
-
ctx.strokeStyle = '#E0E0E0';
|
|
301
|
-
ctx.lineWidth = 1;
|
|
302
|
-
ctx.beginPath();
|
|
303
|
-
ctx.moveTo(this.x, this.y + this.height);
|
|
304
|
-
ctx.lineTo(this.x + this.width, this.y + this.height);
|
|
305
|
-
ctx.stroke();
|
|
306
|
-
}
|
|
307
|
-
|
|
308
|
-
const tabWidth = this.width / this.tabs.length;
|
|
309
|
-
|
|
310
|
-
// Ripples
|
|
311
|
-
if (this.platform === 'material') this.drawRipples(ctx, tabWidth);
|
|
312
|
-
|
|
313
|
-
for (let i = 0; i < this.tabs.length; i++) {
|
|
314
|
-
const tab = this.tabs[i];
|
|
315
|
-
const tabX = this.x + i * tabWidth;
|
|
316
|
-
const isSelected = i === this.selectedIndex;
|
|
317
|
-
|
|
318
|
-
// Cupertino pressed effect
|
|
319
|
-
if (this.platform === 'cupertino' && i === this.pressedTabIndex) {
|
|
320
|
-
ctx.fillStyle = `rgba(0,122,255,${0.1 * this.pressAnimation})`;
|
|
321
|
-
ctx.fillRect(tabX, this.y, tabWidth, this.height);
|
|
322
|
-
}
|
|
323
|
-
|
|
324
|
-
// Indicators
|
|
325
|
-
if (this.platform === 'cupertino' && isSelected) {
|
|
326
|
-
ctx.fillStyle = '#007AFF';
|
|
327
|
-
ctx.fillRect(tabX + tabWidth/2 - 15, this.y + this.height - 2, 30, 2);
|
|
328
|
-
}
|
|
329
|
-
|
|
330
|
-
const color = isSelected ? this.selectedTextColor : this.textColor;
|
|
331
|
-
|
|
332
|
-
// Icon
|
|
333
|
-
if (tab.icon) {
|
|
334
|
-
ctx.font = '24px -apple-system, sans-serif';
|
|
335
|
-
ctx.textAlign = 'center';
|
|
336
|
-
ctx.textBaseline = 'middle';
|
|
337
|
-
ctx.fillStyle = color;
|
|
338
|
-
const iconY = this.platform === 'material' ? this.y + 18 : this.y + 20;
|
|
339
|
-
ctx.fillText(tab.icon, tabX + tabWidth/2, iconY);
|
|
340
|
-
}
|
|
341
|
-
|
|
342
|
-
// Label
|
|
343
|
-
const fontSize = this.platform === 'material' ? 14 : 12;
|
|
344
|
-
const fontWeight = isSelected ? '600' : '400';
|
|
345
|
-
ctx.font = `${fontWeight} ${fontSize}px -apple-system, Roboto, sans-serif`;
|
|
346
|
-
ctx.fillStyle = color;
|
|
347
|
-
ctx.textAlign = 'center';
|
|
348
|
-
ctx.textBaseline = 'middle';
|
|
349
|
-
|
|
350
|
-
const labelY = this.platform === 'material'
|
|
351
|
-
? (tab.icon ? this.y + 36 : this.y + this.height / 2)
|
|
352
|
-
: (tab.icon ? this.y + 42 : this.y + this.height / 2);
|
|
353
|
-
|
|
354
|
-
ctx.fillText(tab.label, tabX + tabWidth/2, labelY);
|
|
355
|
-
|
|
356
|
-
// Material indicator
|
|
357
|
-
if (isSelected && this.platform === 'material') {
|
|
358
|
-
ctx.fillStyle = this.indicatorColor;
|
|
359
|
-
ctx.fillRect(tabX, this.y + this.height - 3, tabWidth, 3);
|
|
360
|
-
}
|
|
361
|
-
}
|
|
362
|
-
|
|
363
|
-
ctx.restore();
|
|
364
|
-
|
|
365
|
-
// ===== DESSINER LES ENFANTS DU TAB ACTIF =====
|
|
366
|
-
// ===== DESSINER LES ENFANTS DU TAB ACTIF =====
|
|
367
|
-
const activeChildren = this.getActiveChildren();
|
|
368
|
-
const scrollOffset = this.framework.scrollOffset || 0;
|
|
369
|
-
|
|
370
|
-
ctx.save();
|
|
371
|
-
|
|
372
|
-
// ✅ clip de la zone de contenu
|
|
373
|
-
ctx.beginPath();
|
|
374
|
-
ctx.rect(this.x, this.contentY, this.width, this.contentHeight);
|
|
375
|
-
ctx.clip();
|
|
376
|
-
|
|
377
|
-
for (let child of activeChildren) {
|
|
378
|
-
if (!child.visible) continue;
|
|
379
|
-
|
|
380
|
-
ctx.save();
|
|
381
|
-
|
|
382
|
-
const originalX = child.x;
|
|
383
|
-
const originalY = child.y;
|
|
384
|
-
|
|
385
|
-
// ✅ appliquer le scroll
|
|
386
|
-
child.x = this.x + originalX;
|
|
387
|
-
child.y = this.contentY + originalY - scrollOffset;
|
|
388
|
-
|
|
389
|
-
child.draw(ctx);
|
|
390
|
-
|
|
391
|
-
child.x = originalX;
|
|
392
|
-
child.y = originalY;
|
|
393
|
-
|
|
394
|
-
ctx.restore();
|
|
395
|
-
}
|
|
396
|
-
|
|
397
|
-
ctx.restore();
|
|
398
|
-
}
|
|
399
|
-
|
|
400
|
-
drawRipples(ctx, tabWidth) {
|
|
401
|
-
ctx.save();
|
|
402
|
-
ctx.beginPath();
|
|
403
|
-
ctx.rect(this.x, this.y, this.width, this.height);
|
|
404
|
-
ctx.clip();
|
|
405
|
-
|
|
406
|
-
for (let ripple of this.ripples) {
|
|
407
|
-
ctx.globalAlpha = ripple.opacity;
|
|
408
|
-
ctx.fillStyle = this.indicatorColor;
|
|
409
|
-
ctx.beginPath();
|
|
410
|
-
ctx.arc(ripple.x, ripple.y, ripple.radius, 0, Math.PI*2);
|
|
411
|
-
ctx.fill();
|
|
412
|
-
}
|
|
413
|
-
|
|
414
|
-
ctx.restore();
|
|
415
|
-
}
|
|
416
|
-
|
|
417
|
-
destroy() {
|
|
418
|
-
if (this.animationFrame) cancelAnimationFrame(this.animationFrame);
|
|
419
|
-
if (super.destroy) super.destroy();
|
|
420
|
-
}
|
|
421
|
-
}
|
|
422
|
-
|
|
423
|
-
export default Tabs;
|
package/components/Text.js
DELETED
|
@@ -1,141 +0,0 @@
|
|
|
1
|
-
import Component from '../core/Component.js';
|
|
2
|
-
/**
|
|
3
|
-
* Composant texte
|
|
4
|
-
* @class
|
|
5
|
-
* @extends Component
|
|
6
|
-
* @property {string} text - Texte à afficher
|
|
7
|
-
* @property {number} fontSize - Taille de police
|
|
8
|
-
* @property {string} color - Couleur
|
|
9
|
-
* @property {string} align - Alignement ('left', 'center', 'right')
|
|
10
|
-
* @property {boolean} bold - Gras
|
|
11
|
-
* @property {number|null} maxWidth - Largeur maximale
|
|
12
|
-
* @property {boolean} wrap - Retour à la ligne
|
|
13
|
-
* @property {number} lineHeight - Hauteur de ligne
|
|
14
|
-
* @property {string[]|null} wrappedLines - Lignes après wrap
|
|
15
|
-
*/
|
|
16
|
-
class Text extends Component {
|
|
17
|
-
/**
|
|
18
|
-
* Crée une instance de Text
|
|
19
|
-
* @param {CanvasFramework} framework - Framework parent
|
|
20
|
-
* @param {Object} [options={}] - Options de configuration
|
|
21
|
-
* @param {string} [options.text=''] - Texte
|
|
22
|
-
* @param {number} [options.fontSize=16] - Taille de police
|
|
23
|
-
* @param {string} [options.color='#000000'] - Couleur
|
|
24
|
-
* @param {string} [options.align='left'] - Alignement
|
|
25
|
-
* @param {boolean} [options.bold=false] - Gras
|
|
26
|
-
* @param {number} [options.maxWidth] - Largeur maximale
|
|
27
|
-
* @param {boolean} [options.wrap=false] - Retour à la ligne
|
|
28
|
-
* @param {number} [options.lineHeight] - Hauteur de ligne
|
|
29
|
-
*/
|
|
30
|
-
constructor(framework, options = {}) {
|
|
31
|
-
super(framework, options);
|
|
32
|
-
this.text = options.text || '';
|
|
33
|
-
this.fontSize = options.fontSize || 16;
|
|
34
|
-
this.color = options.color || '#000000';
|
|
35
|
-
this.align = options.align || 'left';
|
|
36
|
-
this.bold = options.bold || false;
|
|
37
|
-
this.maxWidth = options.maxWidth || null; // Nouvelle option: largeur maximale
|
|
38
|
-
this.wrap = options.wrap || false; // Nouvelle option: retour à la ligne
|
|
39
|
-
this.lineHeight = options.lineHeight || this.fontSize * 1.2;
|
|
40
|
-
|
|
41
|
-
// Calculer la hauteur en fonction du texte
|
|
42
|
-
if (this.wrap && this.maxWidth && this.text) {
|
|
43
|
-
this.calculateWrappedHeight();
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
/**
|
|
48
|
-
* Calcule la hauteur avec wrap
|
|
49
|
-
* @private
|
|
50
|
-
*/
|
|
51
|
-
calculateWrappedHeight() {
|
|
52
|
-
// Cette méthode sera appelée dans draw quand on a le contexte
|
|
53
|
-
// Pour l'instant, on initialise juste
|
|
54
|
-
this.wrappedLines = null;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
/**
|
|
58
|
-
* Dessine le texte
|
|
59
|
-
* @param {CanvasRenderingContext2D} ctx - Contexte de dessin
|
|
60
|
-
*/
|
|
61
|
-
draw(ctx) {
|
|
62
|
-
ctx.save();
|
|
63
|
-
ctx.fillStyle = this.color;
|
|
64
|
-
ctx.font = `${this.bold ? 'bold ' : ''}${this.fontSize}px -apple-system, BlinkMacSystemFont, 'Roboto', sans-serif`;
|
|
65
|
-
ctx.textAlign = this.align;
|
|
66
|
-
ctx.textBaseline = 'top';
|
|
67
|
-
|
|
68
|
-
let lines = [this.text];
|
|
69
|
-
|
|
70
|
-
// Si wrap est activé et on a une largeur max, on divise le texte
|
|
71
|
-
if (this.wrap && this.maxWidth && this.text) {
|
|
72
|
-
lines = this.wrapText(ctx, this.text, this.maxWidth);
|
|
73
|
-
} else if (this.maxWidth && this.text) {
|
|
74
|
-
// Sinon, on tronque le texte avec des points de suspension
|
|
75
|
-
const ellipsis = '...';
|
|
76
|
-
let text = this.text;
|
|
77
|
-
while (ctx.measureText(text).width > this.maxWidth && text.length > 3) {
|
|
78
|
-
text = text.substring(0, text.length - 1);
|
|
79
|
-
}
|
|
80
|
-
if (text !== this.text && text.length > 3) {
|
|
81
|
-
text = text.substring(0, text.length - 3) + ellipsis;
|
|
82
|
-
}
|
|
83
|
-
lines = [text];
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
// Calculer la position x en fonction de l'alignement
|
|
87
|
-
const x = this.align === 'center' ? this.x + (this.maxWidth || this.width) / 2 :
|
|
88
|
-
this.align === 'right' ? this.x + (this.maxWidth || this.width) : this.x;
|
|
89
|
-
|
|
90
|
-
// Dessiner chaque ligne
|
|
91
|
-
for (let i = 0; i < lines.length; i++) {
|
|
92
|
-
const line = lines[i];
|
|
93
|
-
const y = this.y + (i * this.lineHeight);
|
|
94
|
-
ctx.fillText(line, x, y);
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
// Ajuster la hauteur si on a plusieurs lignes
|
|
98
|
-
if (lines.length > 1) {
|
|
99
|
-
this.height = lines.length * this.lineHeight;
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
ctx.restore();
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
/**
|
|
106
|
-
* Divise le texte en plusieurs lignes
|
|
107
|
-
* @param {CanvasRenderingContext2D} ctx - Contexte de dessin
|
|
108
|
-
* @param {string} text - Texte à diviser
|
|
109
|
-
* @param {number} maxWidth - Largeur maximale
|
|
110
|
-
* @returns {string[]} Tableau de lignes
|
|
111
|
-
* @private
|
|
112
|
-
*/
|
|
113
|
-
wrapText(ctx, text, maxWidth) {
|
|
114
|
-
const words = text.split(' ');
|
|
115
|
-
const lines = [];
|
|
116
|
-
let currentLine = words[0];
|
|
117
|
-
|
|
118
|
-
for (let i = 1; i < words.length; i++) {
|
|
119
|
-
const word = words[i];
|
|
120
|
-
const width = ctx.measureText(currentLine + " " + word).width;
|
|
121
|
-
if (width < maxWidth) {
|
|
122
|
-
currentLine += " " + word;
|
|
123
|
-
} else {
|
|
124
|
-
lines.push(currentLine);
|
|
125
|
-
currentLine = word;
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
lines.push(currentLine);
|
|
129
|
-
return lines;
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
/**
|
|
133
|
-
* Vérifie si un point est dans les limites
|
|
134
|
-
* @returns {boolean} False (non cliquable)
|
|
135
|
-
*/
|
|
136
|
-
isPointInside() {
|
|
137
|
-
return false;
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
export default Text;
|
package/components/TextField.js
DELETED
|
@@ -1,151 +0,0 @@
|
|
|
1
|
-
import Input from './Input.js';
|
|
2
|
-
|
|
3
|
-
class TextField extends Input {
|
|
4
|
-
constructor(framework, options = {}) {
|
|
5
|
-
super(framework, options);
|
|
6
|
-
|
|
7
|
-
this.label = options.label || '';
|
|
8
|
-
this.helperText = options.helperText || '';
|
|
9
|
-
this.errorText = options.errorText || '';
|
|
10
|
-
this.error = options.error || false;
|
|
11
|
-
|
|
12
|
-
// Label position (PLUS HAUT que placeholder)
|
|
13
|
-
this.labelRestY = 14; // label quand vide / pas focus, légèrement au-dessus du placeholder
|
|
14
|
-
this.labelFloatY = 4; // label flottant quand focus / value
|
|
15
|
-
this.labelY = this.value ? this.labelFloatY : this.labelRestY;
|
|
16
|
-
this.labelFontSize = this.value ? 12 : 16;
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
this.labelRestSize = 15;
|
|
20
|
-
this.labelFontSize = this.value
|
|
21
|
-
? this.labelFloatSize
|
|
22
|
-
: this.labelRestSize;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
animateLabel() {
|
|
26
|
-
const float = this.focused || this.value;
|
|
27
|
-
|
|
28
|
-
this.labelY = float ? this.labelFloatY : this.labelRestY;
|
|
29
|
-
this.labelFontSize = float
|
|
30
|
-
? this.labelFloatSize
|
|
31
|
-
: this.labelRestSize;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
onFocus() {
|
|
35
|
-
super.onFocus();
|
|
36
|
-
this.animateLabel();
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
onBlur() {
|
|
40
|
-
super.onBlur();
|
|
41
|
-
this.animateLabel();
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
draw(ctx) {
|
|
45
|
-
ctx.save();
|
|
46
|
-
|
|
47
|
-
const inputY = this.y + 28;
|
|
48
|
-
const inputHeight = 42;
|
|
49
|
-
|
|
50
|
-
/* ================= MATERIAL ================= */
|
|
51
|
-
if (this.platform === 'material') {
|
|
52
|
-
// Label
|
|
53
|
-
ctx.fillStyle = this.error
|
|
54
|
-
? '#B00020'
|
|
55
|
-
: this.focused
|
|
56
|
-
? '#6200EE'
|
|
57
|
-
: '#757575';
|
|
58
|
-
|
|
59
|
-
ctx.font = `${this.labelFontSize}px Roboto, sans-serif`;
|
|
60
|
-
ctx.textBaseline = 'top';
|
|
61
|
-
ctx.fillText(this.label, this.x, this.y + this.labelY);
|
|
62
|
-
|
|
63
|
-
// Ligne
|
|
64
|
-
ctx.strokeStyle = this.error
|
|
65
|
-
? '#B00020'
|
|
66
|
-
: this.focused
|
|
67
|
-
? '#6200EE'
|
|
68
|
-
: '#CCCCCC';
|
|
69
|
-
|
|
70
|
-
ctx.lineWidth = this.focused ? 2 : 1;
|
|
71
|
-
ctx.beginPath();
|
|
72
|
-
ctx.moveTo(this.x, inputY + inputHeight);
|
|
73
|
-
ctx.lineTo(this.x + this.width, inputY + inputHeight);
|
|
74
|
-
ctx.stroke();
|
|
75
|
-
|
|
76
|
-
// Texte / placeholder
|
|
77
|
-
const showPlaceholder = !this.value && !this.focused;
|
|
78
|
-
ctx.fillStyle = showPlaceholder ? '#9E9E9E' : '#000';
|
|
79
|
-
ctx.font = `${this.fontSize}px Roboto, sans-serif`;
|
|
80
|
-
ctx.textBaseline = 'middle';
|
|
81
|
-
|
|
82
|
-
ctx.fillText(
|
|
83
|
-
showPlaceholder ? this.placeholder : this.value,
|
|
84
|
-
this.x,
|
|
85
|
-
inputY + inputHeight / 2
|
|
86
|
-
);
|
|
87
|
-
|
|
88
|
-
// Curseur
|
|
89
|
-
if (this.focused && this.cursorVisible) {
|
|
90
|
-
const w = ctx.measureText(this.value).width;
|
|
91
|
-
ctx.fillStyle = '#6200EE';
|
|
92
|
-
ctx.fillRect(this.x + w + 2, inputY + 10, 2, inputHeight - 20);
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
/* ================= CUPERTINO ================= */
|
|
97
|
-
else {
|
|
98
|
-
// Label (toujours visible)
|
|
99
|
-
ctx.fillStyle = '#6D6D72';
|
|
100
|
-
ctx.font = '12px -apple-system, sans-serif';
|
|
101
|
-
ctx.textBaseline = 'top';
|
|
102
|
-
ctx.fillText(this.label, this.x, this.y);
|
|
103
|
-
|
|
104
|
-
// Box
|
|
105
|
-
ctx.strokeStyle = this.error
|
|
106
|
-
? '#FF3B30'
|
|
107
|
-
: this.focused
|
|
108
|
-
? '#007AFF'
|
|
109
|
-
: '#C7C7CC';
|
|
110
|
-
|
|
111
|
-
ctx.lineWidth = 1;
|
|
112
|
-
ctx.beginPath();
|
|
113
|
-
this.roundRect(
|
|
114
|
-
ctx,
|
|
115
|
-
this.x,
|
|
116
|
-
inputY,
|
|
117
|
-
this.width,
|
|
118
|
-
inputHeight,
|
|
119
|
-
10
|
|
120
|
-
);
|
|
121
|
-
ctx.stroke();
|
|
122
|
-
|
|
123
|
-
// Texte / placeholder
|
|
124
|
-
ctx.fillStyle = this.value ? '#000' : '#8E8E93';
|
|
125
|
-
ctx.font = `${this.fontSize}px -apple-system, sans-serif`;
|
|
126
|
-
ctx.textBaseline = 'middle';
|
|
127
|
-
|
|
128
|
-
ctx.fillText(
|
|
129
|
-
this.value || this.placeholder,
|
|
130
|
-
this.x + 12,
|
|
131
|
-
inputY + inputHeight / 2
|
|
132
|
-
);
|
|
133
|
-
|
|
134
|
-
// Curseur
|
|
135
|
-
if (this.focused && this.cursorVisible) {
|
|
136
|
-
const w = ctx.measureText(this.value).width;
|
|
137
|
-
ctx.fillStyle = '#007AFF';
|
|
138
|
-
ctx.fillRect(
|
|
139
|
-
this.x + 12 + w + 2,
|
|
140
|
-
inputY + 10,
|
|
141
|
-
2,
|
|
142
|
-
inputHeight - 20
|
|
143
|
-
);
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
ctx.restore();
|
|
148
|
-
}
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
export default TextField;
|