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/Accordion.js
CHANGED
|
@@ -1,35 +1,11 @@
|
|
|
1
1
|
import Component from '../core/Component.js';
|
|
2
|
+
|
|
2
3
|
/**
|
|
3
|
-
*
|
|
4
|
+
* Accordion (section extensible) avec styles Material & Cupertino + Ripple centré Android
|
|
4
5
|
* @class
|
|
5
6
|
* @extends Component
|
|
6
|
-
* @property {string} title - Titre
|
|
7
|
-
* @property {string} content - Contenu
|
|
8
|
-
* @property {string|null} icon - Icône
|
|
9
|
-
* @property {boolean} expanded - Déplié
|
|
10
|
-
* @property {string} platform - Plateforme
|
|
11
|
-
* @property {number} headerHeight - Hauteur de l'en-tête
|
|
12
|
-
* @property {number} contentPadding - Padding du contenu
|
|
13
|
-
* @property {string} bgColor - Couleur de fond
|
|
14
|
-
* @property {string} borderColor - Couleur de la bordure
|
|
15
|
-
* @property {Function} onToggle - Callback au toggle
|
|
16
|
-
* @property {boolean} animating - En cours d'animation
|
|
17
|
-
* @property {number} animProgress - Progression de l'animation
|
|
18
|
-
* @property {number} contentHeight - Hauteur du contenu
|
|
19
7
|
*/
|
|
20
8
|
class Accordion extends Component {
|
|
21
|
-
/**
|
|
22
|
-
* Crée une instance de Accordion
|
|
23
|
-
* @param {CanvasFramework} framework - Framework parent
|
|
24
|
-
* @param {Object} [options={}] - Options de configuration
|
|
25
|
-
* @param {string} [options.title=''] - Titre
|
|
26
|
-
* @param {string} [options.content=''] - Contenu
|
|
27
|
-
* @param {string} [options.icon] - Icône
|
|
28
|
-
* @param {boolean} [options.expanded=false] - Déplié initialement
|
|
29
|
-
* @param {Function} [options.onToggle] - Callback au toggle
|
|
30
|
-
* @param {string} [options.bgColor='#FFFFFF'] - Couleur de fond
|
|
31
|
-
* @param {string} [options.borderColor='#E0E0E0'] - Couleur de bordure
|
|
32
|
-
*/
|
|
33
9
|
constructor(framework, options = {}) {
|
|
34
10
|
super(framework, options);
|
|
35
11
|
this.title = options.title || '';
|
|
@@ -44,58 +20,47 @@ class Accordion extends Component {
|
|
|
44
20
|
this.onToggle = options.onToggle;
|
|
45
21
|
this.animating = false;
|
|
46
22
|
this.animProgress = this.expanded ? 1 : 0;
|
|
47
|
-
|
|
48
|
-
// Calculer la hauteur du contenu
|
|
23
|
+
|
|
49
24
|
this.calculateContentHeight();
|
|
50
25
|
this.height = this.headerHeight + (this.expanded ? this.contentHeight : 0);
|
|
51
|
-
|
|
52
|
-
//
|
|
26
|
+
|
|
27
|
+
// Pour les ripples Material
|
|
28
|
+
this.ripples = [];
|
|
29
|
+
this.rippleColor = 'rgba(1,0,0,0.2)';
|
|
30
|
+
|
|
31
|
+
// Clic
|
|
53
32
|
this.onClick = () => {
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
33
|
+
if (this.animating) return;
|
|
34
|
+
|
|
35
|
+
// Ripple centré Material
|
|
36
|
+
if (this.platform === 'material') {
|
|
37
|
+
this.addRipple();
|
|
57
38
|
}
|
|
39
|
+
|
|
58
40
|
this.toggle();
|
|
59
41
|
};
|
|
60
42
|
}
|
|
61
|
-
|
|
62
|
-
/**
|
|
63
|
-
* Calcule la hauteur du contenu
|
|
64
|
-
* @private
|
|
65
|
-
*/
|
|
43
|
+
|
|
66
44
|
calculateContentHeight() {
|
|
67
45
|
const ctx = this.framework.ctx;
|
|
68
46
|
ctx.save();
|
|
69
47
|
ctx.font = '14px -apple-system, sans-serif';
|
|
70
|
-
|
|
71
|
-
// Diviser le contenu en lignes
|
|
72
|
-
const maxWidth = this.width - (this.contentPadding * 2);
|
|
48
|
+
const maxWidth = this.width - this.contentPadding * 2;
|
|
73
49
|
const lines = this.wrapText(ctx, this.content, maxWidth);
|
|
74
|
-
const lineHeight = 20;
|
|
75
|
-
|
|
76
50
|
ctx.restore();
|
|
77
|
-
|
|
51
|
+
const lineHeight = 20;
|
|
52
|
+
this.contentHeight = lines.length * lineHeight + this.contentPadding * 2;
|
|
78
53
|
}
|
|
79
|
-
|
|
80
|
-
/**
|
|
81
|
-
* Divise le texte en lignes
|
|
82
|
-
* @param {CanvasRenderingContext2D} ctx - Contexte de dessin
|
|
83
|
-
* @param {string} text - Texte
|
|
84
|
-
* @param {number} maxWidth - Largeur maximale
|
|
85
|
-
* @returns {string[]} Tableau de lignes
|
|
86
|
-
* @private
|
|
87
|
-
*/
|
|
54
|
+
|
|
88
55
|
wrapText(ctx, text, maxWidth) {
|
|
89
56
|
const words = text.split(' ');
|
|
90
57
|
const lines = [];
|
|
91
58
|
let currentLine = words[0] || '';
|
|
92
|
-
|
|
93
59
|
for (let i = 1; i < words.length; i++) {
|
|
94
60
|
const word = words[i];
|
|
95
|
-
const width = ctx.measureText(currentLine +
|
|
96
|
-
if (width < maxWidth)
|
|
97
|
-
|
|
98
|
-
} else {
|
|
61
|
+
const width = ctx.measureText(currentLine + ' ' + word).width;
|
|
62
|
+
if (width < maxWidth) currentLine += ' ' + word;
|
|
63
|
+
else {
|
|
99
64
|
lines.push(currentLine);
|
|
100
65
|
currentLine = word;
|
|
101
66
|
}
|
|
@@ -103,64 +68,124 @@ class Accordion extends Component {
|
|
|
103
68
|
lines.push(currentLine);
|
|
104
69
|
return lines;
|
|
105
70
|
}
|
|
106
|
-
|
|
107
|
-
/**
|
|
108
|
-
* Alterne l'état déplié/replié
|
|
109
|
-
*/
|
|
71
|
+
|
|
110
72
|
toggle() {
|
|
111
|
-
// Empêcher les toggles multiples pendant l'animation
|
|
112
73
|
if (this.animating) return;
|
|
113
|
-
|
|
114
74
|
this.expanded = !this.expanded;
|
|
115
75
|
if (this.onToggle) this.onToggle(this.expanded);
|
|
116
76
|
this.animate();
|
|
117
77
|
}
|
|
118
|
-
|
|
119
|
-
/**
|
|
120
|
-
* Anime le toggle
|
|
121
|
-
* @private
|
|
122
|
-
*/
|
|
78
|
+
|
|
123
79
|
animate() {
|
|
124
80
|
if (this.animating) return;
|
|
125
81
|
this.animating = true;
|
|
126
|
-
|
|
127
82
|
const target = this.expanded ? 1 : 0;
|
|
128
83
|
const step = 0.1;
|
|
129
|
-
|
|
84
|
+
|
|
130
85
|
const doAnimate = () => {
|
|
131
86
|
if (Math.abs(this.animProgress - target) < 0.01) {
|
|
132
87
|
this.animProgress = target;
|
|
133
|
-
this.height = this.headerHeight +
|
|
88
|
+
this.height = this.headerHeight + this.contentHeight * this.animProgress;
|
|
134
89
|
this.animating = false;
|
|
135
90
|
return;
|
|
136
91
|
}
|
|
137
|
-
|
|
138
92
|
this.animProgress += this.animProgress < target ? step : -step;
|
|
139
|
-
this.height = this.headerHeight +
|
|
93
|
+
this.height = this.headerHeight + this.contentHeight * this.animProgress;
|
|
140
94
|
requestAnimationFrame(doAnimate);
|
|
141
95
|
};
|
|
142
|
-
|
|
143
96
|
doAnimate();
|
|
144
97
|
}
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
98
|
+
|
|
99
|
+
addRipple() {
|
|
100
|
+
const ripple = {
|
|
101
|
+
x: this.width / 2,
|
|
102
|
+
y: this.headerHeight / 2,
|
|
103
|
+
radius: 0,
|
|
104
|
+
maxRadius: Math.max(this.width, this.headerHeight) * 1.5,
|
|
105
|
+
opacity: 0.3
|
|
106
|
+
};
|
|
107
|
+
this.ripples.push(ripple);
|
|
108
|
+
this.animateRipples();
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
animateRipples() {
|
|
112
|
+
const animate = () => {
|
|
113
|
+
let active = false;
|
|
114
|
+
for (let ripple of this.ripples) {
|
|
115
|
+
if (ripple.radius < ripple.maxRadius) {
|
|
116
|
+
ripple.radius += ripple.maxRadius / 15;
|
|
117
|
+
ripple.opacity -= 0.03;
|
|
118
|
+
active = true;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
this.ripples = this.ripples.filter(r => r.opacity > 0);
|
|
122
|
+
if (active) requestAnimationFrame(animate);
|
|
123
|
+
};
|
|
124
|
+
animate();
|
|
125
|
+
}
|
|
126
|
+
|
|
150
127
|
draw(ctx) {
|
|
151
128
|
ctx.save();
|
|
152
|
-
|
|
129
|
+
|
|
130
|
+
let headerBg = '#FFFFFF';
|
|
131
|
+
let headerTextColor = '#000000';
|
|
132
|
+
let borderColor = this.borderColor;
|
|
133
|
+
let shadowBlur = 0;
|
|
134
|
+
let chevronWidth = 2;
|
|
135
|
+
|
|
136
|
+
if (this.platform === 'material') {
|
|
137
|
+
headerBg = '#F5F5F5';
|
|
138
|
+
headerTextColor = '#212121';
|
|
139
|
+
shadowBlur = 4;
|
|
140
|
+
chevronWidth = 3;
|
|
141
|
+
} else if (this.platform === 'cupertino') {
|
|
142
|
+
headerBg = '#FFFFFF';
|
|
143
|
+
headerTextColor = '#000000';
|
|
144
|
+
borderColor = '#C7C7CC';
|
|
145
|
+
chevronWidth = 1.5;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// Ombre Material
|
|
149
|
+
if (shadowBlur > 0) {
|
|
150
|
+
ctx.shadowColor = 'rgba(0,0,0,0.2)';
|
|
151
|
+
ctx.shadowBlur = shadowBlur;
|
|
152
|
+
ctx.shadowOffsetX = 0;
|
|
153
|
+
ctx.shadowOffsetY = 2;
|
|
154
|
+
}
|
|
155
|
+
|
|
153
156
|
// Background
|
|
154
157
|
ctx.fillStyle = this.bgColor;
|
|
155
158
|
ctx.fillRect(this.x, this.y, this.width, this.height);
|
|
156
|
-
|
|
157
|
-
// Bordure
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
159
|
+
|
|
160
|
+
// Bordure Cupertino
|
|
161
|
+
if (this.platform === 'cupertino') {
|
|
162
|
+
ctx.strokeStyle = borderColor;
|
|
163
|
+
ctx.lineWidth = 1;
|
|
164
|
+
ctx.strokeRect(this.x, this.y, this.width, this.height);
|
|
165
|
+
}
|
|
166
|
+
|
|
162
167
|
// Header
|
|
163
|
-
|
|
168
|
+
ctx.fillStyle = headerBg;
|
|
169
|
+
ctx.fillRect(this.x, this.y, this.width, this.headerHeight);
|
|
170
|
+
|
|
171
|
+
// Ripple centré Material
|
|
172
|
+
if (this.platform === 'material' && this.ripples.length) {
|
|
173
|
+
ctx.save();
|
|
174
|
+
ctx.beginPath();
|
|
175
|
+
ctx.rect(this.x, this.y, this.width, this.headerHeight);
|
|
176
|
+
ctx.clip();
|
|
177
|
+
for (let ripple of this.ripples) {
|
|
178
|
+
ctx.globalAlpha = ripple.opacity;
|
|
179
|
+
ctx.fillStyle = this.rippleColor;
|
|
180
|
+
ctx.beginPath();
|
|
181
|
+
ctx.arc(this.x + ripple.x, this.y + ripple.y, ripple.radius, 0, Math.PI * 2);
|
|
182
|
+
ctx.fill();
|
|
183
|
+
}
|
|
184
|
+
ctx.restore();
|
|
185
|
+
ctx.globalAlpha = 1;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// Icône
|
|
164
189
|
if (this.icon) {
|
|
165
190
|
ctx.font = '20px sans-serif';
|
|
166
191
|
ctx.fillStyle = '#666666';
|
|
@@ -168,26 +193,27 @@ class Accordion extends Component {
|
|
|
168
193
|
ctx.textBaseline = 'middle';
|
|
169
194
|
ctx.fillText(this.icon, this.x + 16, this.y + this.headerHeight / 2);
|
|
170
195
|
}
|
|
171
|
-
|
|
196
|
+
|
|
172
197
|
// Titre
|
|
173
|
-
ctx.fillStyle =
|
|
174
|
-
ctx.font =
|
|
198
|
+
ctx.fillStyle = headerTextColor;
|
|
199
|
+
ctx.font =
|
|
200
|
+
this.platform === 'material'
|
|
201
|
+
? 'bold 16px Roboto, sans-serif'
|
|
202
|
+
: 'bold 16px -apple-system, sans-serif';
|
|
175
203
|
ctx.textAlign = 'left';
|
|
176
204
|
ctx.textBaseline = 'middle';
|
|
177
|
-
const titleX = this.
|
|
205
|
+
const titleX = this.icon ? this.x + 56 : this.x + 16;
|
|
178
206
|
ctx.fillText(this.title, titleX, this.y + this.headerHeight / 2);
|
|
179
|
-
|
|
180
|
-
// Chevron
|
|
207
|
+
|
|
208
|
+
// Chevron
|
|
181
209
|
const chevronX = this.x + this.width - 30;
|
|
182
210
|
const chevronY = this.y + this.headerHeight / 2;
|
|
183
211
|
const chevronRotation = this.animProgress * Math.PI;
|
|
184
|
-
|
|
185
212
|
ctx.save();
|
|
186
213
|
ctx.translate(chevronX, chevronY);
|
|
187
214
|
ctx.rotate(chevronRotation);
|
|
188
|
-
|
|
189
215
|
ctx.strokeStyle = '#666666';
|
|
190
|
-
ctx.lineWidth =
|
|
216
|
+
ctx.lineWidth = chevronWidth;
|
|
191
217
|
ctx.lineCap = 'round';
|
|
192
218
|
ctx.lineJoin = 'round';
|
|
193
219
|
ctx.beginPath();
|
|
@@ -195,58 +221,45 @@ class Accordion extends Component {
|
|
|
195
221
|
ctx.lineTo(0, 3);
|
|
196
222
|
ctx.lineTo(6, -3);
|
|
197
223
|
ctx.stroke();
|
|
198
|
-
|
|
199
224
|
ctx.restore();
|
|
200
|
-
|
|
201
|
-
// Contenu
|
|
225
|
+
|
|
226
|
+
// Contenu
|
|
202
227
|
if (this.animProgress > 0) {
|
|
203
228
|
ctx.save();
|
|
204
|
-
|
|
205
|
-
// Clipping pour l'animation
|
|
206
229
|
ctx.beginPath();
|
|
207
230
|
ctx.rect(this.x, this.y + this.headerHeight, this.width, this.contentHeight * this.animProgress);
|
|
208
231
|
ctx.clip();
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
ctx.strokeStyle = this.borderColor;
|
|
232
|
+
|
|
233
|
+
ctx.strokeStyle = borderColor;
|
|
212
234
|
ctx.lineWidth = 1;
|
|
213
235
|
ctx.beginPath();
|
|
214
236
|
ctx.moveTo(this.x, this.y + this.headerHeight);
|
|
215
237
|
ctx.lineTo(this.x + this.width, this.y + this.headerHeight);
|
|
216
238
|
ctx.stroke();
|
|
217
|
-
|
|
218
|
-
// Texte du contenu
|
|
239
|
+
|
|
219
240
|
ctx.fillStyle = '#666666';
|
|
220
241
|
ctx.font = '14px -apple-system, sans-serif';
|
|
221
242
|
ctx.textAlign = 'left';
|
|
222
243
|
ctx.textBaseline = 'top';
|
|
223
|
-
|
|
224
244
|
const contentX = this.x + this.contentPadding;
|
|
225
245
|
const contentY = this.y + this.headerHeight + this.contentPadding;
|
|
226
|
-
const maxWidth = this.width -
|
|
246
|
+
const maxWidth = this.width - this.contentPadding * 2;
|
|
227
247
|
const lines = this.wrapText(ctx, this.content, maxWidth);
|
|
228
248
|
const lineHeight = 20;
|
|
229
|
-
|
|
230
249
|
lines.forEach((line, index) => {
|
|
231
|
-
ctx.fillText(line, contentX, contentY +
|
|
250
|
+
ctx.fillText(line, contentX, contentY + index * lineHeight);
|
|
232
251
|
});
|
|
233
|
-
|
|
252
|
+
|
|
234
253
|
ctx.restore();
|
|
235
254
|
}
|
|
236
|
-
|
|
255
|
+
|
|
237
256
|
ctx.restore();
|
|
238
257
|
}
|
|
239
|
-
|
|
240
|
-
/**
|
|
241
|
-
* Vérifie si un point est dans les limites
|
|
242
|
-
* @param {number} x - Coordonnée X
|
|
243
|
-
* @param {number} y - Coordonnée Y
|
|
244
|
-
* @returns {boolean} True si le point est dans l'en-tête
|
|
245
|
-
*/
|
|
258
|
+
|
|
246
259
|
isPointInside(x, y) {
|
|
247
|
-
return x >= this.x && x <= this.x + this.width &&
|
|
260
|
+
return x >= this.x && x <= this.x + this.width &&
|
|
248
261
|
y >= this.y && y <= this.y + this.headerHeight;
|
|
249
262
|
}
|
|
250
263
|
}
|
|
251
264
|
|
|
252
|
-
export default Accordion;
|
|
265
|
+
export default Accordion;
|