canvasframework 0.3.14 → 0.3.16
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/components/Accordion.js +135 -122
- package/components/BottomSheet.js +104 -244
- package/components/Checkbox.js +135 -149
- package/components/CircularProgress.js +213 -29
- package/components/DatePicker.js +7 -0
- package/components/Dialog.js +252 -282
- package/components/IOSDatePickerWheel.js +257 -95
- package/components/SegmentedControl.js +240 -85
- package/components/TextField.js +109 -289
- package/core/CanvasFramework.js +3 -7
- package/core/Component.js +90 -0
- package/index.js +2 -1
- package/package.json +1 -1
- package/utils/CryptoManager.js +303 -0
|
@@ -1,26 +1,11 @@
|
|
|
1
1
|
import Component from '../core/Component.js';
|
|
2
|
+
|
|
2
3
|
/**
|
|
3
|
-
*
|
|
4
|
+
* BottomSheet avec styles Material et Cupertino
|
|
4
5
|
* @class
|
|
5
6
|
* @extends Component
|
|
6
|
-
* @param {Framework} framework - Instance du framework
|
|
7
|
-
* @param {Object} [options={}] - Options de configuration
|
|
8
|
-
* @param {number} [options.height=framework.height * 0.6] - Hauteur du bottom sheet
|
|
9
|
-
* @param {boolean} [options.dragHandle=true] - Afficher la poignée de drag
|
|
10
|
-
* @param {boolean} [options.closeOnOverlayClick=true] - Fermer au clic sur l'overlay
|
|
11
|
-
* @param {string} [options.bgColor='#FFFFFF'] - Couleur de fond
|
|
12
|
-
* @param {number} [options.borderRadius=16] - Rayon des coins arrondis
|
|
13
|
-
* @example
|
|
14
|
-
* const bottomSheet = new BottomSheet(framework, {
|
|
15
|
-
* height: 400,
|
|
16
|
-
* bgColor: '#F5F5F5',
|
|
17
|
-
* borderRadius: 20
|
|
18
|
-
* });
|
|
19
7
|
*/
|
|
20
8
|
class BottomSheet extends Component {
|
|
21
|
-
/**
|
|
22
|
-
* @constructs BottomSheet
|
|
23
|
-
*/
|
|
24
9
|
constructor(framework, options = {}) {
|
|
25
10
|
super(framework, {
|
|
26
11
|
x: 0,
|
|
@@ -29,65 +14,53 @@ class BottomSheet extends Component {
|
|
|
29
14
|
height: options.height || framework.height * 0.6,
|
|
30
15
|
visible: false
|
|
31
16
|
});
|
|
32
|
-
|
|
33
|
-
|
|
17
|
+
|
|
18
|
+
this.platform = framework.platform; // material / cupertino
|
|
19
|
+
|
|
34
20
|
this.children = [];
|
|
35
|
-
/** @type {boolean} */
|
|
36
21
|
this.dragHandle = options.dragHandle !== false;
|
|
37
|
-
/** @type {boolean} */
|
|
38
22
|
this.closeOnOverlayClick = options.closeOnOverlayClick !== false;
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
23
|
+
|
|
24
|
+
// Styles plateforme
|
|
25
|
+
if (this.platform === 'material') {
|
|
26
|
+
this.bgColor = options.bgColor || '#FFFFFF';
|
|
27
|
+
this.overlayColor = 'rgba(0,0,0,0.5)';
|
|
28
|
+
this.shadowBlur = 20;
|
|
29
|
+
this.shadowOffsetY = -5;
|
|
30
|
+
this.borderRadius = 11;
|
|
31
|
+
} else { // cupertino
|
|
32
|
+
this.bgColor = options.bgColor || 'rgba(255,255,255,0.95)';
|
|
33
|
+
this.overlayColor = 'rgba(0,0,0,0.2)';
|
|
34
|
+
this.shadowBlur = 0;
|
|
35
|
+
this.shadowOffsetY = 0;
|
|
36
|
+
this.borderRadius = options.borderRadius || 20;
|
|
37
|
+
}
|
|
38
|
+
|
|
44
39
|
this.targetY = framework.height;
|
|
45
|
-
/** @type {boolean} */
|
|
46
40
|
this.isOpen = false;
|
|
47
|
-
/** @type {boolean} */
|
|
48
41
|
this.animating = false;
|
|
49
|
-
/** @type {boolean} */
|
|
50
42
|
this.dragging = false;
|
|
51
|
-
/** @type {number} */
|
|
52
43
|
this.dragStartY = 0;
|
|
53
|
-
/** @type {number} */
|
|
54
44
|
this.dragOffset = 0;
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
// IMPORTANT: Supprimer les bindings ici et les gérer différemment
|
|
45
|
+
this.lastClickTime = 0;
|
|
46
|
+
|
|
59
47
|
this.onPress = this.handlePress.bind(this);
|
|
60
48
|
this.onMove = this.handleMove.bind(this);
|
|
61
49
|
this.onRelease = this.handleRelease.bind(this);
|
|
62
|
-
|
|
63
|
-
// Pour suivre le dernier clic
|
|
64
|
-
/** @type {number} */
|
|
65
|
-
this.lastClickTime = 0;
|
|
66
50
|
}
|
|
67
|
-
|
|
68
|
-
/**
|
|
69
|
-
* Ajoute un enfant au bottom sheet
|
|
70
|
-
* @param {Component} child - Composant enfant à ajouter
|
|
71
|
-
* @returns {Component} L'enfant ajouté
|
|
72
|
-
*/
|
|
51
|
+
|
|
73
52
|
add(child) {
|
|
74
53
|
this.children.push(child);
|
|
75
54
|
return child;
|
|
76
55
|
}
|
|
77
|
-
|
|
78
|
-
/**
|
|
79
|
-
* Ouvre le bottom sheet avec animation
|
|
80
|
-
*/
|
|
56
|
+
|
|
81
57
|
open() {
|
|
82
58
|
this.visible = true;
|
|
83
59
|
this.isOpen = true;
|
|
84
60
|
this.targetY = this.framework.height - this.height;
|
|
85
61
|
this.animate();
|
|
86
62
|
}
|
|
87
|
-
|
|
88
|
-
/**
|
|
89
|
-
* Ferme le bottom sheet avec animation
|
|
90
|
-
*/
|
|
63
|
+
|
|
91
64
|
close() {
|
|
92
65
|
this.isOpen = false;
|
|
93
66
|
this.targetY = this.framework.height;
|
|
@@ -95,237 +68,140 @@ class BottomSheet extends Component {
|
|
|
95
68
|
this.visible = false;
|
|
96
69
|
});
|
|
97
70
|
}
|
|
98
|
-
|
|
99
|
-
/**
|
|
100
|
-
* Anime le bottom sheet vers sa position cible
|
|
101
|
-
* @param {Function} [callback] - Callback appelé à la fin de l'animation
|
|
102
|
-
* @private
|
|
103
|
-
*/
|
|
71
|
+
|
|
104
72
|
animate(callback) {
|
|
105
73
|
if (this.animating) return;
|
|
106
74
|
this.animating = true;
|
|
107
|
-
|
|
75
|
+
|
|
108
76
|
const step = () => {
|
|
109
|
-
|
|
110
|
-
|
|
77
|
+
let diff = this.targetY - this.y;
|
|
78
|
+
|
|
111
79
|
if (Math.abs(diff) < 1) {
|
|
112
80
|
this.y = this.targetY;
|
|
113
|
-
this.overlayOpacity = this.isOpen ? 0.5 : 0;
|
|
114
81
|
this.animating = false;
|
|
115
82
|
if (callback) callback();
|
|
116
83
|
return;
|
|
117
84
|
}
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
85
|
+
|
|
86
|
+
// Animation type spring pour iOS, easing pour Material
|
|
87
|
+
if (this.platform === 'cupertino') {
|
|
88
|
+
diff *= 0.15; // spring
|
|
89
|
+
} else {
|
|
90
|
+
diff *= 0.2; // easing
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
this.y += diff;
|
|
94
|
+
|
|
125
95
|
requestAnimationFrame(step);
|
|
126
96
|
};
|
|
127
|
-
|
|
97
|
+
|
|
128
98
|
step();
|
|
129
99
|
}
|
|
130
|
-
|
|
131
|
-
/**
|
|
132
|
-
* Gère le clic/touch sur le bottom sheet
|
|
133
|
-
* @param {number} x - Position X du clic
|
|
134
|
-
* @param {number} y - Position Y du clic
|
|
135
|
-
*/
|
|
100
|
+
|
|
136
101
|
handlePress(x, y) {
|
|
137
|
-
// Empêcher les doubles clics rapides
|
|
138
102
|
const now = Date.now();
|
|
139
103
|
if (now - this.lastClickTime < 300) return;
|
|
140
104
|
this.lastClickTime = now;
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
const adjustedY = y; // Pas d'ajustement de scroll pour le BottomSheet
|
|
144
|
-
|
|
145
|
-
// Clic sur l'overlay (zone sombre)
|
|
146
|
-
if (adjustedY < this.y && this.closeOnOverlayClick) {
|
|
105
|
+
|
|
106
|
+
if (y < this.y && this.closeOnOverlayClick) {
|
|
147
107
|
this.close();
|
|
148
108
|
return;
|
|
149
109
|
}
|
|
150
|
-
|
|
151
|
-
//
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
// Vérifier les clics sur les enfants
|
|
110
|
+
|
|
111
|
+
// Clic sur la poignée
|
|
112
|
+
if (this.dragHandle && y >= this.y && y <= this.y + 40) {
|
|
113
|
+
// ✅ Fermer le bottom sheet immédiatement
|
|
114
|
+
this.close();
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// Gestion clic enfants
|
|
161
119
|
const contentY = this.y + (this.dragHandle ? 40 : 16);
|
|
162
|
-
|
|
163
|
-
// Parcourir les enfants dans l'ordre inverse (du dernier au premier)
|
|
164
120
|
for (let i = this.children.length - 1; i >= 0; i--) {
|
|
165
121
|
const child = this.children[i];
|
|
166
|
-
|
|
167
122
|
if (!child.visible) continue;
|
|
168
|
-
|
|
169
|
-
// Calculer les coordonnées absolues de l'enfant
|
|
123
|
+
|
|
170
124
|
const childAbsX = this.x + 16 + child.x;
|
|
171
125
|
const childAbsY = contentY + child.y;
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
x >= childAbsX &&
|
|
177
|
-
x <= childAbsX + child.width) {
|
|
178
|
-
|
|
179
|
-
// Si l'enfant a un onClick, le déclencher
|
|
180
|
-
if (child.onClick) {
|
|
181
|
-
child.onClick();
|
|
182
|
-
return;
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
// Si l'enfant a un onPress, le déclencher
|
|
186
|
-
if (child.onPress) {
|
|
187
|
-
// Calculer les coordonnées relatives pour l'enfant
|
|
188
|
-
const relativeX = x - childAbsX;
|
|
189
|
-
const relativeY = adjustedY - childAbsY;
|
|
190
|
-
child.onPress(relativeX, relativeY);
|
|
191
|
-
return;
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
// Marquer l'enfant comme pressé pour l'effet visuel
|
|
195
|
-
child.pressed = true;
|
|
196
|
-
|
|
197
|
-
// Si c'est un bouton, déclencher son onClick après un délai
|
|
198
|
-
if (child instanceof Button || child instanceof FAB) {
|
|
199
|
-
setTimeout(() => {
|
|
200
|
-
if (child.onClick) child.onClick();
|
|
201
|
-
child.pressed = false;
|
|
202
|
-
}, 150);
|
|
203
|
-
}
|
|
126
|
+
|
|
127
|
+
if (x >= childAbsX && x <= childAbsX + child.width &&
|
|
128
|
+
y >= childAbsY && y <= childAbsY + child.height) {
|
|
129
|
+
if (child.onClick) child.onClick();
|
|
204
130
|
return;
|
|
205
131
|
}
|
|
206
132
|
}
|
|
207
133
|
}
|
|
208
|
-
|
|
209
|
-
/**
|
|
210
|
-
* Gère le déplacement pendant le drag
|
|
211
|
-
* @param {number} x - Position X actuelle
|
|
212
|
-
* @param {number} y - Position Y actuelle
|
|
213
|
-
*/
|
|
134
|
+
|
|
214
135
|
handleMove(x, y) {
|
|
215
|
-
if (this.dragging)
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
// Limiter le drag vers le haut
|
|
220
|
-
const newY = (this.framework.height - this.height) + this.dragOffset;
|
|
221
|
-
if (newY >= this.framework.height - this.height) {
|
|
222
|
-
this.y = newY;
|
|
223
|
-
|
|
224
|
-
// Mettre à jour l'opacité de l'overlay
|
|
225
|
-
const progress = 1 - ((this.y - (this.framework.height - this.height)) / this.height);
|
|
226
|
-
this.overlayOpacity = Math.max(0, Math.min(0.5, progress * 0.5));
|
|
227
|
-
}
|
|
228
|
-
}
|
|
136
|
+
if (!this.dragging) return;
|
|
137
|
+
this.dragOffset = y - this.dragStartY;
|
|
138
|
+
let newY = (this.framework.height - this.height) + this.dragOffset;
|
|
139
|
+
if (newY >= this.framework.height - this.height) this.y = newY;
|
|
229
140
|
}
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
this.
|
|
239
|
-
this.
|
|
240
|
-
|
|
241
|
-
// Si on a dragué plus de 30% vers le bas, fermer
|
|
242
|
-
if (this.dragOffset > this.height * 0.3) {
|
|
243
|
-
this.close();
|
|
244
|
-
} else {
|
|
245
|
-
// Sinon, revenir à la position ouverte
|
|
246
|
-
this.targetY = this.framework.height - this.height;
|
|
247
|
-
this.animate();
|
|
248
|
-
}
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
// Réinitialiser l'état pressed pour tous les enfants
|
|
252
|
-
for (let child of this.children) {
|
|
253
|
-
child.pressed = false;
|
|
141
|
+
|
|
142
|
+
handleRelease() {
|
|
143
|
+
if (!this.dragging) return;
|
|
144
|
+
this.dragging = false;
|
|
145
|
+
this.framework.activeComponent = null;
|
|
146
|
+
|
|
147
|
+
if (this.dragOffset > this.height * 0.3) this.close();
|
|
148
|
+
else {
|
|
149
|
+
this.targetY = this.framework.height - this.height;
|
|
150
|
+
this.animate();
|
|
254
151
|
}
|
|
255
152
|
}
|
|
256
|
-
|
|
257
|
-
/**
|
|
258
|
-
* Dessine le bottom sheet
|
|
259
|
-
* @param {CanvasRenderingContext2D} ctx - Contexte de dessin
|
|
260
|
-
*/
|
|
153
|
+
|
|
261
154
|
draw(ctx) {
|
|
262
155
|
if (!this.visible) return;
|
|
263
|
-
|
|
264
156
|
ctx.save();
|
|
265
|
-
|
|
266
|
-
// Overlay
|
|
267
|
-
ctx.fillStyle =
|
|
157
|
+
|
|
158
|
+
// Overlay
|
|
159
|
+
ctx.fillStyle = this.overlayColor;
|
|
268
160
|
ctx.fillRect(0, 0, this.framework.width, this.framework.height);
|
|
269
|
-
|
|
270
|
-
//
|
|
161
|
+
|
|
162
|
+
// Sheet
|
|
271
163
|
ctx.fillStyle = this.bgColor;
|
|
272
|
-
ctx.shadowColor = 'rgba(0,
|
|
273
|
-
ctx.shadowBlur =
|
|
274
|
-
ctx.shadowOffsetY =
|
|
275
|
-
|
|
164
|
+
ctx.shadowColor = this.platform === 'material' ? 'rgba(0,0,0,0.3)' : 'transparent';
|
|
165
|
+
ctx.shadowBlur = this.shadowBlur;
|
|
166
|
+
ctx.shadowOffsetY = this.shadowOffsetY;
|
|
167
|
+
|
|
276
168
|
ctx.beginPath();
|
|
277
169
|
this.roundRectTop(ctx, this.x, this.y, this.width, this.height, this.borderRadius);
|
|
278
170
|
ctx.fill();
|
|
279
|
-
|
|
280
171
|
ctx.shadowColor = 'transparent';
|
|
281
|
-
|
|
282
|
-
// Drag
|
|
172
|
+
|
|
173
|
+
// Drag handle
|
|
283
174
|
if (this.dragHandle) {
|
|
284
|
-
ctx.fillStyle = '#CCCCCC';
|
|
175
|
+
ctx.fillStyle = this.platform === 'material' ? '#CCCCCC' : '#E0E0E0';
|
|
285
176
|
ctx.beginPath();
|
|
286
177
|
this.roundRect(ctx, this.width / 2 - 20, this.y + 12, 40, 4, 2);
|
|
287
178
|
ctx.fill();
|
|
288
179
|
}
|
|
289
|
-
|
|
290
|
-
//
|
|
180
|
+
|
|
181
|
+
// Enfants
|
|
291
182
|
const contentY = this.y + (this.dragHandle ? 40 : 16);
|
|
292
183
|
const contentHeight = this.height - (this.dragHandle ? 40 : 16);
|
|
293
|
-
|
|
184
|
+
|
|
294
185
|
ctx.save();
|
|
295
186
|
ctx.beginPath();
|
|
296
187
|
ctx.rect(this.x, contentY, this.width, contentHeight);
|
|
297
188
|
ctx.clip();
|
|
298
|
-
|
|
299
|
-
// Dessiner les enfants
|
|
189
|
+
|
|
300
190
|
for (let child of this.children) {
|
|
301
|
-
if (child.visible)
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
child.x = originalX;
|
|
311
|
-
child.y = originalY;
|
|
312
|
-
}
|
|
191
|
+
if (!child.visible) continue;
|
|
192
|
+
const origX = child.x;
|
|
193
|
+
const origY = child.y;
|
|
194
|
+
child.x = this.x + 16 + origX;
|
|
195
|
+
child.y = contentY + origY;
|
|
196
|
+
child.draw(ctx);
|
|
197
|
+
child.x = origX;
|
|
198
|
+
child.y = origY;
|
|
313
199
|
}
|
|
314
|
-
|
|
200
|
+
|
|
315
201
|
ctx.restore();
|
|
316
202
|
ctx.restore();
|
|
317
203
|
}
|
|
318
|
-
|
|
319
|
-
/**
|
|
320
|
-
* Dessine un rectangle avec seulement les coins supérieurs arrondis
|
|
321
|
-
* @param {CanvasRenderingContext2D} ctx - Contexte de dessin
|
|
322
|
-
* @param {number} x - Position X
|
|
323
|
-
* @param {number} y - Position Y
|
|
324
|
-
* @param {number} width - Largeur
|
|
325
|
-
* @param {number} height - Hauteur
|
|
326
|
-
* @param {number} radius - Rayon des coins
|
|
327
|
-
* @private
|
|
328
|
-
*/
|
|
204
|
+
|
|
329
205
|
roundRectTop(ctx, x, y, width, height, radius) {
|
|
330
206
|
ctx.moveTo(x + radius, y);
|
|
331
207
|
ctx.lineTo(x + width - radius, y);
|
|
@@ -335,19 +211,9 @@ class BottomSheet extends Component {
|
|
|
335
211
|
ctx.lineTo(x, y + radius);
|
|
336
212
|
ctx.quadraticCurveTo(x, y, x + radius, y);
|
|
337
213
|
}
|
|
338
|
-
|
|
339
|
-
/**
|
|
340
|
-
* Dessine un rectangle avec des coins arrondis
|
|
341
|
-
* @param {CanvasRenderingContext2D} ctx - Contexte de dessin
|
|
342
|
-
* @param {number} x - Position X
|
|
343
|
-
* @param {number} y - Position Y
|
|
344
|
-
* @param {number} width - Largeur
|
|
345
|
-
* @param {number} height - Hauteur
|
|
346
|
-
* @param {number} radius - Rayon des coins
|
|
347
|
-
* @private
|
|
348
|
-
*/
|
|
214
|
+
|
|
349
215
|
roundRect(ctx, x, y, width, height, radius) {
|
|
350
|
-
ctx.
|
|
216
|
+
if (radius === 0) return ctx.rect(x, y, width, height);
|
|
351
217
|
ctx.moveTo(x + radius, y);
|
|
352
218
|
ctx.lineTo(x + width - radius, y);
|
|
353
219
|
ctx.quadraticCurveTo(x + width, y, x + width, y + radius);
|
|
@@ -359,16 +225,10 @@ class BottomSheet extends Component {
|
|
|
359
225
|
ctx.quadraticCurveTo(x, y, x + radius, y);
|
|
360
226
|
ctx.closePath();
|
|
361
227
|
}
|
|
362
|
-
|
|
363
|
-
/**
|
|
364
|
-
* Vérifie si un point est à l'intérieur du composant
|
|
365
|
-
* @param {number} x - Position X
|
|
366
|
-
* @param {number} y - Position Y
|
|
367
|
-
* @returns {boolean} Toujours true si visible (le composant occupe tout l'écran)
|
|
368
|
-
*/
|
|
228
|
+
|
|
369
229
|
isPointInside(x, y) {
|
|
370
230
|
return this.visible;
|
|
371
231
|
}
|
|
372
232
|
}
|
|
373
233
|
|
|
374
|
-
export default BottomSheet;
|
|
234
|
+
export default BottomSheet;
|