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
package/components/Checkbox.js
CHANGED
|
@@ -1,180 +1,166 @@
|
|
|
1
1
|
import Component from '../core/Component.js';
|
|
2
|
+
|
|
2
3
|
/**
|
|
3
|
-
*
|
|
4
|
-
* @class
|
|
5
|
-
* @extends Component
|
|
6
|
-
* @property {boolean} checked - État cochée
|
|
7
|
-
* @property {string} label - Texte du label
|
|
8
|
-
* @property {string} platform - Plateforme
|
|
9
|
-
* @property {number} boxWidth - Largeur de la case
|
|
10
|
-
* @property {number} boxHeight - Hauteur de la case
|
|
11
|
-
* @property {Function} onChange - Callback au changement
|
|
4
|
+
* Checkbox Material & Cupertino (iOS-like)
|
|
12
5
|
*/
|
|
13
6
|
class Checkbox extends Component {
|
|
14
|
-
/**
|
|
15
|
-
* Crée une instance de Checkbox
|
|
16
|
-
* @param {CanvasFramework} framework - Framework parent
|
|
17
|
-
* @param {Object} [options={}] - Options de configuration
|
|
18
|
-
* @param {boolean} [options.checked=false] - État initial
|
|
19
|
-
* @param {string} [options.label=''] - Texte du label
|
|
20
|
-
* @param {Function} [options.onChange] - Callback au changement
|
|
21
|
-
*/
|
|
22
7
|
constructor(framework, options = {}) {
|
|
23
8
|
super(framework, options);
|
|
24
|
-
|
|
9
|
+
|
|
10
|
+
this.checked = !!options.checked;
|
|
25
11
|
this.label = options.label || '';
|
|
26
12
|
this.platform = framework.platform;
|
|
27
|
-
this.boxWidth = 24; // Taille de la case
|
|
28
|
-
this.boxHeight = 24; // Taille de la case
|
|
29
13
|
this.onChange = options.onChange;
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
this.
|
|
33
|
-
|
|
34
|
-
this.
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
14
|
+
|
|
15
|
+
this.boxSize = 22;
|
|
16
|
+
this.padding = 10;
|
|
17
|
+
|
|
18
|
+
this.textWidth = this.label
|
|
19
|
+
? this.getTextWidth(this.label)
|
|
20
|
+
: 0;
|
|
21
|
+
|
|
22
|
+
// Largeur totale
|
|
23
|
+
this.width =
|
|
24
|
+
this.platform === 'material'
|
|
25
|
+
? this.boxSize + this.padding + this.textWidth
|
|
26
|
+
: this.textWidth + 28; // place pour checkmark iOS
|
|
27
|
+
|
|
28
|
+
this.height = 28;
|
|
29
|
+
|
|
30
|
+
this.onClick = () => {
|
|
31
|
+
this.checked = !this.checked;
|
|
32
|
+
this.onChange?.(this.checked);
|
|
33
|
+
};
|
|
38
34
|
}
|
|
39
|
-
|
|
40
|
-
/**
|
|
41
|
-
* Calcule la largeur du texte
|
|
42
|
-
* @param {string} text - Texte à mesurer
|
|
43
|
-
* @returns {number} Largeur du texte
|
|
44
|
-
* @private
|
|
45
|
-
*/
|
|
35
|
+
|
|
46
36
|
getTextWidth(text) {
|
|
47
|
-
// Utiliser le contexte temporaire pour mesurer le texte
|
|
48
37
|
const ctx = this.framework.ctx;
|
|
49
38
|
ctx.save();
|
|
50
|
-
ctx.font = '16px -apple-system, sans-serif';
|
|
51
|
-
const
|
|
39
|
+
ctx.font = '16px -apple-system, system-ui, sans-serif';
|
|
40
|
+
const w = ctx.measureText(text).width;
|
|
52
41
|
ctx.restore();
|
|
53
|
-
return
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
/**
|
|
57
|
-
* Gère le clic sur la checkbox
|
|
58
|
-
* @private
|
|
59
|
-
*/
|
|
60
|
-
handleClick() {
|
|
61
|
-
this.checked = !this.checked;
|
|
62
|
-
if (this.onChange) this.onChange(this.checked);
|
|
42
|
+
return w;
|
|
63
43
|
}
|
|
64
44
|
|
|
65
|
-
/**
|
|
66
|
-
* Dessine la checkbox
|
|
67
|
-
* @param {CanvasRenderingContext2D} ctx - Contexte de dessin
|
|
68
|
-
*/
|
|
69
45
|
draw(ctx) {
|
|
70
46
|
ctx.save();
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
const
|
|
75
|
-
|
|
76
|
-
const boxCenterY = boxY + this.boxHeight / 2;
|
|
77
|
-
|
|
47
|
+
ctx.font = '16px -apple-system, system-ui, sans-serif';
|
|
48
|
+
ctx.textBaseline = 'middle';
|
|
49
|
+
|
|
50
|
+
const centerY = this.y + this.height / 2;
|
|
51
|
+
|
|
78
52
|
if (this.platform === 'material') {
|
|
79
|
-
|
|
80
|
-
if (this.checked) {
|
|
81
|
-
// Case cochée
|
|
82
|
-
ctx.fillStyle = '#6200EE';
|
|
83
|
-
ctx.beginPath();
|
|
84
|
-
this.roundRect(ctx, boxX, boxY, this.boxWidth, this.boxHeight, 2);
|
|
85
|
-
ctx.fill();
|
|
86
|
-
|
|
87
|
-
// Coche
|
|
88
|
-
ctx.strokeStyle = '#FFFFFF';
|
|
89
|
-
ctx.lineWidth = 2;
|
|
90
|
-
ctx.beginPath();
|
|
91
|
-
ctx.moveTo(boxX + 6, boxY + 12);
|
|
92
|
-
ctx.lineTo(boxX + 10, boxY + 16);
|
|
93
|
-
ctx.lineTo(boxX + 18, boxY + 8);
|
|
94
|
-
ctx.stroke();
|
|
95
|
-
} else {
|
|
96
|
-
// Case non cochée
|
|
97
|
-
ctx.strokeStyle = '#666666';
|
|
98
|
-
ctx.lineWidth = 2;
|
|
99
|
-
ctx.beginPath();
|
|
100
|
-
this.roundRect(ctx, boxX, boxY, this.boxWidth, this.boxHeight, 2);
|
|
101
|
-
ctx.stroke();
|
|
102
|
-
}
|
|
53
|
+
this.drawMaterial(ctx, centerY);
|
|
103
54
|
} else {
|
|
104
|
-
|
|
105
|
-
if (this.checked) {
|
|
106
|
-
// Case cochée (iOS utilise plutôt un cercle)
|
|
107
|
-
ctx.fillStyle = '#007AFF';
|
|
108
|
-
ctx.beginPath();
|
|
109
|
-
ctx.arc(boxCenterX, boxCenterY, this.boxWidth/2, 0, Math.PI * 2);
|
|
110
|
-
ctx.fill();
|
|
111
|
-
|
|
112
|
-
// Coche
|
|
113
|
-
ctx.strokeStyle = '#FFFFFF';
|
|
114
|
-
ctx.lineWidth = 2;
|
|
115
|
-
ctx.beginPath();
|
|
116
|
-
ctx.moveTo(boxX + 6, boxCenterY);
|
|
117
|
-
ctx.lineTo(boxX + 10, boxCenterY + 4);
|
|
118
|
-
ctx.lineTo(boxX + 18, boxCenterY - 4);
|
|
119
|
-
ctx.stroke();
|
|
120
|
-
} else {
|
|
121
|
-
// Case non cochée
|
|
122
|
-
ctx.strokeStyle = '#C7C7CC';
|
|
123
|
-
ctx.lineWidth = 2;
|
|
124
|
-
ctx.beginPath();
|
|
125
|
-
ctx.arc(boxCenterX, boxCenterY, this.boxWidth/2, 0, Math.PI * 2);
|
|
126
|
-
ctx.stroke();
|
|
127
|
-
}
|
|
55
|
+
this.drawCupertino(ctx, centerY);
|
|
128
56
|
}
|
|
129
|
-
|
|
130
|
-
// Label
|
|
131
|
-
if (this.label) {
|
|
132
|
-
ctx.fillStyle = '#000000';
|
|
133
|
-
ctx.font = '16px -apple-system, sans-serif';
|
|
134
|
-
ctx.textAlign = 'left';
|
|
135
|
-
ctx.textBaseline = 'middle';
|
|
136
|
-
ctx.fillText(this.label, boxX + this.boxWidth + 8, boxCenterY);
|
|
137
|
-
}
|
|
138
|
-
|
|
57
|
+
|
|
139
58
|
ctx.restore();
|
|
140
59
|
}
|
|
141
60
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
61
|
+
/* ---------------- MATERIAL ---------------- */
|
|
62
|
+
|
|
63
|
+
drawMaterial(ctx, centerY) {
|
|
64
|
+
const x = this.x;
|
|
65
|
+
const y = centerY - this.boxSize / 2;
|
|
66
|
+
|
|
67
|
+
// Box
|
|
68
|
+
ctx.lineWidth = 2;
|
|
69
|
+
ctx.strokeStyle = this.checked ? '#6200EE' : '#757575';
|
|
70
|
+
ctx.fillStyle = this.checked ? '#6200EE' : 'transparent';
|
|
71
|
+
|
|
72
|
+
this.roundRect(ctx, x, y, this.boxSize, this.boxSize, 3);
|
|
73
|
+
if (this.checked) ctx.fill();
|
|
74
|
+
ctx.stroke();
|
|
75
|
+
|
|
76
|
+
// Check
|
|
77
|
+
if (this.checked) {
|
|
78
|
+
ctx.strokeStyle = '#FFF';
|
|
79
|
+
ctx.lineWidth = 2.4;
|
|
80
|
+
ctx.beginPath();
|
|
81
|
+
ctx.moveTo(x + 5, y + 12);
|
|
82
|
+
ctx.lineTo(x + 9, y + 16);
|
|
83
|
+
ctx.lineTo(x + 17, y + 7);
|
|
84
|
+
ctx.stroke();
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// Label
|
|
88
|
+
ctx.fillStyle = '#000';
|
|
89
|
+
ctx.fillText(
|
|
90
|
+
this.label,
|
|
91
|
+
x + this.boxSize + this.padding,
|
|
92
|
+
centerY
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/* ---------------- CUPERTINO ---------------- */
|
|
97
|
+
|
|
98
|
+
/* ---------------- CUPERTINO ---------------- */
|
|
99
|
+
|
|
100
|
+
drawCupertino(ctx, centerY) {
|
|
101
|
+
const radius = 10;
|
|
102
|
+
const circleX = this.x + radius;
|
|
103
|
+
const circleY = centerY;
|
|
104
|
+
|
|
105
|
+
// Cercle
|
|
106
|
+
if (this.checked) {
|
|
107
|
+
ctx.fillStyle = '#007AFF'; // Apple blue
|
|
153
108
|
ctx.beginPath();
|
|
154
|
-
ctx.
|
|
155
|
-
ctx.
|
|
156
|
-
|
|
157
|
-
ctx.
|
|
158
|
-
ctx.
|
|
159
|
-
ctx.
|
|
160
|
-
ctx.
|
|
161
|
-
ctx.
|
|
162
|
-
|
|
109
|
+
ctx.arc(circleX, circleY, radius, 0, Math.PI * 2);
|
|
110
|
+
ctx.fill();
|
|
111
|
+
} else {
|
|
112
|
+
ctx.strokeStyle = '#C7C7CC'; // iOS gray
|
|
113
|
+
ctx.lineWidth = 2;
|
|
114
|
+
ctx.beginPath();
|
|
115
|
+
ctx.arc(circleX, circleY, radius, 0, Math.PI * 2);
|
|
116
|
+
ctx.stroke();
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// Checkmark
|
|
120
|
+
if (this.checked) {
|
|
121
|
+
ctx.strokeStyle = '#FFFFFF';
|
|
122
|
+
ctx.lineWidth = 2.2;
|
|
123
|
+
ctx.lineCap = 'round';
|
|
124
|
+
ctx.lineJoin = 'round';
|
|
125
|
+
|
|
126
|
+
ctx.beginPath();
|
|
127
|
+
ctx.moveTo(circleX - 4, circleY);
|
|
128
|
+
ctx.lineTo(circleX - 1, circleY + 3);
|
|
129
|
+
ctx.lineTo(circleX + 5, circleY - 4);
|
|
130
|
+
ctx.stroke();
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// Label
|
|
134
|
+
ctx.fillStyle = '#000';
|
|
135
|
+
ctx.fillText(
|
|
136
|
+
this.label,
|
|
137
|
+
this.x + radius * 2 + this.padding,
|
|
138
|
+
centerY
|
|
139
|
+
);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
roundRect(ctx, x, y, w, h, r) {
|
|
143
|
+
ctx.beginPath();
|
|
144
|
+
ctx.moveTo(x + r, y);
|
|
145
|
+
ctx.lineTo(x + w - r, y);
|
|
146
|
+
ctx.quadraticCurveTo(x + w, y, x + w, y + r);
|
|
147
|
+
ctx.lineTo(x + w, y + h - r);
|
|
148
|
+
ctx.quadraticCurveTo(x + w, y + h, x + w - r, y + h);
|
|
149
|
+
ctx.lineTo(x + r, y + h);
|
|
150
|
+
ctx.quadraticCurveTo(x, y + h, x, y + h - r);
|
|
151
|
+
ctx.lineTo(x, y + r);
|
|
152
|
+
ctx.quadraticCurveTo(x, y, x + r, y);
|
|
163
153
|
ctx.closePath();
|
|
164
154
|
}
|
|
165
|
-
|
|
166
|
-
/**
|
|
167
|
-
* Vérifie si un point est dans les limites
|
|
168
|
-
* @param {number} x - Coordonnée X
|
|
169
|
-
* @param {number} y - Coordonnée Y
|
|
170
|
-
* @returns {boolean} True si le point est dans la checkbox
|
|
171
|
-
*/
|
|
155
|
+
|
|
172
156
|
isPointInside(x, y) {
|
|
173
|
-
return
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
157
|
+
return (
|
|
158
|
+
x >= this.x &&
|
|
159
|
+
x <= this.x + this.width &&
|
|
160
|
+
y >= this.y &&
|
|
161
|
+
y <= this.y + this.height
|
|
162
|
+
);
|
|
177
163
|
}
|
|
178
164
|
}
|
|
179
165
|
|
|
180
|
-
export default Checkbox;
|
|
166
|
+
export default Checkbox;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import Component from '../core/Component.js';
|
|
2
|
+
|
|
2
3
|
/**
|
|
3
|
-
* Spinner de chargement circulaire
|
|
4
|
+
* Spinner de chargement circulaire avec support Material et Cupertino
|
|
4
5
|
* @class
|
|
5
6
|
* @extends Component
|
|
6
7
|
* @property {number} size - Taille du spinner
|
|
@@ -21,8 +22,8 @@ class CircularProgress extends Component {
|
|
|
21
22
|
* @param {boolean} [options.indeterminate=true] - Mode indéterminé
|
|
22
23
|
* @param {number} [options.progress=0] - Progression (0-100)
|
|
23
24
|
* @param {string} [options.color] - Couleur (auto selon platform)
|
|
24
|
-
* @param {number} [options.lineWidth
|
|
25
|
-
* @param {number} [options.animationSpeed
|
|
25
|
+
* @param {number} [options.lineWidth] - Épaisseur (auto selon platform)
|
|
26
|
+
* @param {number} [options.animationSpeed] - Vitesse d'animation (auto selon platform)
|
|
26
27
|
*/
|
|
27
28
|
constructor(framework, options = {}) {
|
|
28
29
|
super(framework, options);
|
|
@@ -30,10 +31,32 @@ class CircularProgress extends Component {
|
|
|
30
31
|
this.indeterminate = options.indeterminate !== false;
|
|
31
32
|
this.progress = options.progress || 0; // 0-100
|
|
32
33
|
this.platform = framework.platform;
|
|
33
|
-
|
|
34
|
-
|
|
34
|
+
|
|
35
|
+
// Couleurs selon la plateforme
|
|
36
|
+
this.color = options.color || (
|
|
37
|
+
this.platform === 'material' ? '#6200EE' : '#8E8E93'
|
|
38
|
+
);
|
|
39
|
+
|
|
40
|
+
// Épaisseur selon la plateforme
|
|
41
|
+
this.lineWidth = options.lineWidth || (
|
|
42
|
+
this.platform === 'material' ? 4 : 2.5
|
|
43
|
+
);
|
|
44
|
+
|
|
45
|
+
// Vitesse d'animation selon la plateforme
|
|
46
|
+
this.animationSpeed = options.animationSpeed || (
|
|
47
|
+
this.platform === 'material' ? 0.05 : 0.08
|
|
48
|
+
);
|
|
49
|
+
|
|
35
50
|
this.rotation = 0;
|
|
36
|
-
|
|
51
|
+
|
|
52
|
+
// Pour l'animation Material (arc qui s'agrandit/rétrécit)
|
|
53
|
+
this.arcStart = 0;
|
|
54
|
+
this.arcEnd = 0;
|
|
55
|
+
this.arcGrowing = true;
|
|
56
|
+
|
|
57
|
+
// Pour l'animation Cupertino (12 traits qui tournent)
|
|
58
|
+
this.cupertinoLines = 12;
|
|
59
|
+
this.cupertinoOpacity = Array(this.cupertinoLines).fill(0);
|
|
37
60
|
|
|
38
61
|
this.width = this.size;
|
|
39
62
|
this.height = this.size;
|
|
@@ -49,54 +72,164 @@ class CircularProgress extends Component {
|
|
|
49
72
|
* @private
|
|
50
73
|
*/
|
|
51
74
|
startAnimation() {
|
|
52
|
-
|
|
75
|
+
let lastTime = performance.now();
|
|
76
|
+
|
|
77
|
+
const animate = (currentTime) => {
|
|
53
78
|
if (!this.visible || !this.indeterminate) return;
|
|
54
79
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
80
|
+
const deltaTime = currentTime - lastTime;
|
|
81
|
+
lastTime = currentTime;
|
|
82
|
+
|
|
83
|
+
if (this.platform === 'material') {
|
|
84
|
+
this.animateMaterial(deltaTime);
|
|
85
|
+
} else {
|
|
86
|
+
this.animateCupertino(deltaTime);
|
|
58
87
|
}
|
|
59
88
|
|
|
89
|
+
this.markDirty();
|
|
60
90
|
requestAnimationFrame(animate);
|
|
61
91
|
};
|
|
62
|
-
|
|
92
|
+
|
|
93
|
+
requestAnimationFrame(animate);
|
|
63
94
|
}
|
|
64
95
|
|
|
65
96
|
/**
|
|
66
|
-
*
|
|
67
|
-
* @
|
|
97
|
+
* Animation Material (arc qui tourne et change de taille)
|
|
98
|
+
* @private
|
|
68
99
|
*/
|
|
69
|
-
|
|
70
|
-
|
|
100
|
+
animateMaterial(deltaTime) {
|
|
101
|
+
// Rotation globale
|
|
102
|
+
this.rotation += this.animationSpeed;
|
|
103
|
+
if (this.rotation > Math.PI * 2) {
|
|
104
|
+
this.rotation -= Math.PI * 2;
|
|
105
|
+
}
|
|
71
106
|
|
|
72
|
-
|
|
73
|
-
const
|
|
74
|
-
const radius = (this.size - this.lineWidth) / 2;
|
|
107
|
+
// Animation de l'arc (grossit puis rétrécit)
|
|
108
|
+
const arcSpeed = 0.03;
|
|
75
109
|
|
|
110
|
+
if (this.arcGrowing) {
|
|
111
|
+
this.arcEnd += arcSpeed;
|
|
112
|
+
if (this.arcEnd > Math.PI * 1.5) {
|
|
113
|
+
this.arcGrowing = false;
|
|
114
|
+
}
|
|
115
|
+
} else {
|
|
116
|
+
this.arcStart += arcSpeed;
|
|
117
|
+
if (this.arcStart >= this.arcEnd) {
|
|
118
|
+
this.arcGrowing = true;
|
|
119
|
+
this.arcStart = 0;
|
|
120
|
+
this.arcEnd = 0;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Animation Cupertino (traits qui s'estompent)
|
|
127
|
+
* @private
|
|
128
|
+
*/
|
|
129
|
+
animateCupertino(deltaTime) {
|
|
130
|
+
this.rotation += this.animationSpeed;
|
|
131
|
+
if (this.rotation > Math.PI * 2) {
|
|
132
|
+
this.rotation -= Math.PI * 2;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// Calculer l'opacité de chaque trait (fade progressif)
|
|
136
|
+
const activeIndex = Math.floor((this.rotation / (Math.PI * 2)) * this.cupertinoLines);
|
|
137
|
+
|
|
138
|
+
for (let i = 0; i < this.cupertinoLines; i++) {
|
|
139
|
+
const distance = Math.abs(i - activeIndex);
|
|
140
|
+
const minDistance = Math.min(distance, this.cupertinoLines - distance);
|
|
141
|
+
this.cupertinoOpacity[i] = 1 - (minDistance / this.cupertinoLines) * 0.8;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Dessine le spinner Material
|
|
147
|
+
* @private
|
|
148
|
+
*/
|
|
149
|
+
drawMaterial(ctx, centerX, centerY, radius) {
|
|
76
150
|
if (this.indeterminate) {
|
|
77
|
-
//
|
|
151
|
+
// Track (cercle de base - très léger)
|
|
152
|
+
ctx.strokeStyle = 'rgba(98, 0, 238, 0.1)';
|
|
153
|
+
ctx.lineWidth = this.lineWidth;
|
|
154
|
+
ctx.beginPath();
|
|
155
|
+
ctx.arc(centerX, centerY, radius, 0, Math.PI * 2);
|
|
156
|
+
ctx.stroke();
|
|
157
|
+
|
|
158
|
+
// Arc animé qui tourne
|
|
159
|
+
ctx.save();
|
|
78
160
|
ctx.translate(centerX, centerY);
|
|
79
161
|
ctx.rotate(this.rotation);
|
|
80
|
-
ctx.translate(-centerX, -centerY);
|
|
81
162
|
|
|
82
|
-
|
|
83
|
-
ctx.strokeStyle = this.platform === 'material' ? '#E0E0E0' : '#E5E5EA';
|
|
84
|
-
ctx.lineWidth = this.lineWidth;
|
|
163
|
+
ctx.strokeStyle = this.color;
|
|
85
164
|
ctx.lineCap = 'round';
|
|
165
|
+
ctx.lineWidth = this.lineWidth;
|
|
166
|
+
ctx.beginPath();
|
|
167
|
+
ctx.arc(0, 0, radius, this.arcStart, this.arcEnd);
|
|
168
|
+
ctx.stroke();
|
|
169
|
+
|
|
170
|
+
ctx.restore();
|
|
171
|
+
} else {
|
|
172
|
+
// Progress circulaire déterminé
|
|
173
|
+
// Track
|
|
174
|
+
ctx.strokeStyle = '#E0E0E0';
|
|
175
|
+
ctx.lineWidth = this.lineWidth;
|
|
86
176
|
ctx.beginPath();
|
|
87
177
|
ctx.arc(centerX, centerY, radius, 0, Math.PI * 2);
|
|
88
178
|
ctx.stroke();
|
|
89
179
|
|
|
90
|
-
//
|
|
180
|
+
// Progress
|
|
181
|
+
const angle = (this.progress / 100) * Math.PI * 2;
|
|
91
182
|
ctx.strokeStyle = this.color;
|
|
183
|
+
ctx.lineCap = 'round';
|
|
92
184
|
ctx.beginPath();
|
|
93
|
-
ctx.arc(centerX, centerY, radius,
|
|
185
|
+
ctx.arc(centerX, centerY, radius, -Math.PI / 2, -Math.PI / 2 + angle);
|
|
94
186
|
ctx.stroke();
|
|
95
187
|
|
|
188
|
+
// Pourcentage au centre
|
|
189
|
+
if (this.progress > 0) {
|
|
190
|
+
ctx.fillStyle = this.color;
|
|
191
|
+
ctx.font = `bold ${this.size / 3}px -apple-system, sans-serif`;
|
|
192
|
+
ctx.textAlign = 'center';
|
|
193
|
+
ctx.textBaseline = 'middle';
|
|
194
|
+
ctx.fillText(`${Math.round(this.progress)}%`, centerX, centerY);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* Dessine le spinner Cupertino (style iOS)
|
|
201
|
+
* @private
|
|
202
|
+
*/
|
|
203
|
+
drawCupertino(ctx, centerX, centerY, radius) {
|
|
204
|
+
if (this.indeterminate) {
|
|
205
|
+
// Spinner iOS avec 12 traits
|
|
206
|
+
ctx.lineCap = 'round';
|
|
207
|
+
ctx.lineWidth = this.lineWidth;
|
|
208
|
+
|
|
209
|
+
for (let i = 0; i < this.cupertinoLines; i++) {
|
|
210
|
+
const angle = (i / this.cupertinoLines) * Math.PI * 2;
|
|
211
|
+
const opacity = this.cupertinoOpacity[i];
|
|
212
|
+
|
|
213
|
+
ctx.save();
|
|
214
|
+
ctx.translate(centerX, centerY);
|
|
215
|
+
ctx.rotate(angle);
|
|
216
|
+
|
|
217
|
+
// Trait avec opacité variable
|
|
218
|
+
const startRadius = radius * 0.6;
|
|
219
|
+
const endRadius = radius;
|
|
220
|
+
|
|
221
|
+
ctx.strokeStyle = this.hexToRgba(this.color, opacity);
|
|
222
|
+
ctx.beginPath();
|
|
223
|
+
ctx.moveTo(0, -startRadius);
|
|
224
|
+
ctx.lineTo(0, -endRadius);
|
|
225
|
+
ctx.stroke();
|
|
226
|
+
|
|
227
|
+
ctx.restore();
|
|
228
|
+
}
|
|
96
229
|
} else {
|
|
97
|
-
// Progress circulaire
|
|
230
|
+
// Progress circulaire iOS (plus fin et élégant)
|
|
98
231
|
// Track
|
|
99
|
-
ctx.strokeStyle =
|
|
232
|
+
ctx.strokeStyle = '#E5E5EA';
|
|
100
233
|
ctx.lineWidth = this.lineWidth;
|
|
101
234
|
ctx.beginPath();
|
|
102
235
|
ctx.arc(centerX, centerY, radius, 0, Math.PI * 2);
|
|
@@ -110,25 +243,76 @@ class CircularProgress extends Component {
|
|
|
110
243
|
ctx.arc(centerX, centerY, radius, -Math.PI / 2, -Math.PI / 2 + angle);
|
|
111
244
|
ctx.stroke();
|
|
112
245
|
|
|
113
|
-
//
|
|
246
|
+
// Pas de texte au centre pour iOS (plus minimaliste)
|
|
247
|
+
// Mais si tu veux, décommente :
|
|
248
|
+
/*
|
|
114
249
|
if (this.progress > 0) {
|
|
115
250
|
ctx.fillStyle = this.color;
|
|
116
|
-
ctx.font =
|
|
251
|
+
ctx.font = `${this.size / 4}px -apple-system, sans-serif`;
|
|
117
252
|
ctx.textAlign = 'center';
|
|
118
253
|
ctx.textBaseline = 'middle';
|
|
119
254
|
ctx.fillText(`${Math.round(this.progress)}%`, centerX, centerY);
|
|
120
255
|
}
|
|
256
|
+
*/
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
/**
|
|
261
|
+
* Dessine le spinner
|
|
262
|
+
* @param {CanvasRenderingContext2D} ctx - Contexte de dessin
|
|
263
|
+
*/
|
|
264
|
+
draw(ctx) {
|
|
265
|
+
ctx.save();
|
|
266
|
+
|
|
267
|
+
const centerX = this.x + this.size / 2;
|
|
268
|
+
const centerY = this.y + this.size / 2;
|
|
269
|
+
const radius = (this.size - this.lineWidth * 2) / 2;
|
|
270
|
+
|
|
271
|
+
if (this.platform === 'material') {
|
|
272
|
+
this.drawMaterial(ctx, centerX, centerY, radius);
|
|
273
|
+
} else {
|
|
274
|
+
this.drawCupertino(ctx, centerX, centerY, radius);
|
|
121
275
|
}
|
|
122
276
|
|
|
123
277
|
ctx.restore();
|
|
124
278
|
}
|
|
125
279
|
|
|
280
|
+
/**
|
|
281
|
+
* Convertit une couleur hex en rgba avec opacité
|
|
282
|
+
* @private
|
|
283
|
+
*/
|
|
284
|
+
hexToRgba(hex, alpha) {
|
|
285
|
+
// Si c'est déjà rgba, le retourner
|
|
286
|
+
if (hex.startsWith('rgba')) return hex;
|
|
287
|
+
if (hex.startsWith('rgb')) {
|
|
288
|
+
return hex.replace('rgb', 'rgba').replace(')', `, ${alpha})`);
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
// Convertir hex en rgba
|
|
292
|
+
const r = parseInt(hex.slice(1, 3), 16);
|
|
293
|
+
const g = parseInt(hex.slice(3, 5), 16);
|
|
294
|
+
const b = parseInt(hex.slice(5, 7), 16);
|
|
295
|
+
return `rgba(${r}, ${g}, ${b}, ${alpha})`;
|
|
296
|
+
}
|
|
297
|
+
|
|
126
298
|
/**
|
|
127
299
|
* Définit la progression
|
|
128
300
|
* @param {number} value - Valeur de progression (0-100)
|
|
129
301
|
*/
|
|
130
302
|
setProgress(value) {
|
|
131
303
|
this.progress = Math.max(0, Math.min(100, value));
|
|
304
|
+
this.indeterminate = false;
|
|
305
|
+
this.markDirty();
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
/**
|
|
309
|
+
* Active le mode indéterminé
|
|
310
|
+
*/
|
|
311
|
+
setIndeterminate() {
|
|
312
|
+
this.indeterminate = true;
|
|
313
|
+
if (!this._animating) {
|
|
314
|
+
this.startAnimation();
|
|
315
|
+
}
|
|
132
316
|
}
|
|
133
317
|
|
|
134
318
|
/**
|
package/components/DatePicker.js
CHANGED
|
@@ -1,4 +1,9 @@
|
|
|
1
1
|
import Component from '../core/Component.js';
|
|
2
|
+
import AndroidDatePickerDialog from '../components/AndroidDatePickerDialog.js';
|
|
3
|
+
import Modal from '../components/Modal.js';
|
|
4
|
+
import IOSDatePickerWheel from '../components/IOSDatePickerWheel.js';
|
|
5
|
+
import Button from '../components/Button.js';
|
|
6
|
+
|
|
2
7
|
/**
|
|
3
8
|
* Sélecteur de date (wrapper)
|
|
4
9
|
* @class
|
|
@@ -61,6 +66,8 @@ class DatePicker extends Component {
|
|
|
61
66
|
* Ouvre le sélecteur iOS
|
|
62
67
|
* @private
|
|
63
68
|
*/
|
|
69
|
+
|
|
70
|
+
|
|
64
71
|
openIOSPicker() {
|
|
65
72
|
// Créer un modal avec le picker iOS
|
|
66
73
|
const modal = new Modal(this.framework, {
|