canvasframework 0.5.42 → 0.5.44

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.
@@ -1,265 +1,271 @@
1
1
  import Component from '../core/Component.js';
2
2
 
3
- /**
4
- * Accordion (section extensible) avec styles Material & Cupertino + Ripple centré Android
5
- * @class
6
- * @extends Component
7
- */
8
3
  class Accordion extends Component {
4
+
9
5
  constructor(framework, options = {}) {
10
6
  super(framework, options);
7
+
11
8
  this.title = options.title || '';
12
9
  this.content = options.content || '';
13
10
  this.icon = options.icon || null;
14
11
  this.expanded = options.expanded || false;
12
+
15
13
  this.platform = framework.platform;
14
+
15
+ this.borderColor = options.borderColor || '#E5E5EA'
16
+
17
+ if(this.platform === 'material'){
18
+ this.headerBg = options.headerBg || '#F8F9FF'; // Material 3 tonal surface
19
+ this.textColor = options.textColor || '#1C1B1F';
20
+ }else{
21
+ this.headerBg = options.headerBg || '#FFFFFF';
22
+ this.textColor = options.textColor || '#000000';
23
+ }
24
+
16
25
  this.headerHeight = 56;
17
26
  this.contentPadding = 16;
18
- this.bgColor = options.bgColor || '#FFFFFF';
19
- this.borderColor = options.borderColor || '#E0E0E0';
20
- this.onToggle = options.onToggle;
21
- this.animating = false;
22
- this.animProgress = this.expanded ? 1 : 0;
23
27
 
24
- this.calculateContentHeight();
25
- this.height = this.headerHeight + (this.expanded ? this.contentHeight : 0);
28
+ this.animProgress = this.expanded ? 1 : 0;
29
+ this.animating = false;
26
30
 
27
- // Pour les ripples Material
28
31
  this.ripples = [];
29
- this.rippleColor = 'rgba(1,0,0,0.2)';
30
-
31
- // Clic
32
- this.onClick = () => {
33
- if (this.animating) return;
32
+
33
+ this.rippleColor = options.rippleColor || 'rgba(98,0,238,0.12)';
34
34
 
35
- // Ripple centré Material
36
- if (this.platform === 'material') {
37
- this.addRipple();
38
- }
35
+ this.calculateContentHeight();
36
+ this.height = this.headerHeight + (this.expanded ? this.contentHeight : 0);
39
37
 
40
- this.toggle();
41
- };
42
38
  }
43
39
 
44
40
  calculateContentHeight() {
45
41
  const ctx = this.framework.ctx;
46
42
  ctx.save();
47
43
  ctx.font = '14px -apple-system, sans-serif';
44
+
48
45
  const maxWidth = this.width - this.contentPadding * 2;
49
- const lines = this.wrapText(ctx, this.content, maxWidth);
50
- ctx.restore();
51
- const lineHeight = 20;
52
- this.contentHeight = lines.length * lineHeight + this.contentPadding * 2;
53
- }
54
46
 
55
- wrapText(ctx, text, maxWidth) {
56
- const words = text.split(' ');
57
- const lines = [];
58
- let currentLine = words[0] || '';
59
- for (let i = 1; i < words.length; i++) {
60
- const word = words[i];
61
- const width = ctx.measureText(currentLine + ' ' + word).width;
62
- if (width < maxWidth) currentLine += ' ' + word;
63
- else {
64
- lines.push(currentLine);
65
- currentLine = word;
47
+ const words = this.content.split(' ');
48
+ let lines = [];
49
+ let line = '';
50
+
51
+ words.forEach(word => {
52
+
53
+ const test = line + word + ' ';
54
+ if (ctx.measureText(test).width < maxWidth) {
55
+ line = test;
56
+ } else {
57
+ lines.push(line);
58
+ line = word + ' ';
66
59
  }
67
- }
68
- lines.push(currentLine);
69
- return lines;
60
+
61
+ });
62
+
63
+ lines.push(line);
64
+ this.lines = lines;
65
+
66
+ ctx.restore();
67
+
68
+ this.contentHeight = lines.length * 20 + this.contentPadding * 2;
70
69
  }
71
70
 
72
71
  toggle() {
72
+
73
73
  if (this.animating) return;
74
+
74
75
  this.expanded = !this.expanded;
75
- if (this.onToggle) this.onToggle(this.expanded);
76
- this.animate();
77
- }
78
76
 
79
- animate() {
80
- if (this.animating) return;
81
- this.animating = true;
82
77
  const target = this.expanded ? 1 : 0;
83
- const step = 0.1;
84
78
 
85
- const doAnimate = () => {
86
- if (Math.abs(this.animProgress - target) < 0.01) {
79
+ this.animating = true;
80
+
81
+ const animate = () => {
82
+
83
+ this.animProgress += (target - this.animProgress) * 0.2;
84
+
85
+ if (Math.abs(target - this.animProgress) < 0.01) {
87
86
  this.animProgress = target;
88
- this.height = this.headerHeight + this.contentHeight * this.animProgress;
89
87
  this.animating = false;
90
- return;
88
+ } else {
89
+ requestAnimationFrame(animate);
91
90
  }
92
- this.animProgress += this.animProgress < target ? step : -step;
91
+
93
92
  this.height = this.headerHeight + this.contentHeight * this.animProgress;
94
- requestAnimationFrame(doAnimate);
93
+
95
94
  };
96
- doAnimate();
95
+
96
+ animate();
97
97
  }
98
98
 
99
- addRipple() {
100
- const ripple = {
101
- x: this.width / 2,
102
- y: this.headerHeight / 2,
99
+ addRipple(localX, localY) {
100
+
101
+ if (this.platform !== 'material') return;
102
+
103
+ this.ripples.push({
104
+ x: localX,
105
+ y: localY,
103
106
  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
- }
107
+ opacity: 0.25,
108
+ maxRadius: Math.max(this.width, this.headerHeight)
109
+ });
110
110
 
111
- animateRipples() {
112
111
  const animate = () => {
112
+
113
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;
114
+
115
+ this.ripples.forEach(r => {
116
+
117
+ if (r.radius < r.maxRadius) {
118
+
119
+ r.radius += r.maxRadius / 12;
120
+ r.opacity *= 0.9;
118
121
  active = true;
122
+
119
123
  }
120
- }
121
- this.ripples = this.ripples.filter(r => r.opacity > 0);
124
+
125
+ });
126
+
127
+ this.ripples = this.ripples.filter(r => r.opacity > 0.02);
128
+
122
129
  if (active) requestAnimationFrame(animate);
130
+
123
131
  };
132
+
124
133
  animate();
125
134
  }
126
135
 
127
- draw(ctx) {
136
+ handleClick(x,y){
137
+
138
+ const localX = x - this.x;
139
+ const localY = y - this.y;
140
+
141
+ if(localY <= this.headerHeight){
142
+
143
+ this.addRipple(localX, localY);
144
+ this.toggle();
145
+
146
+ }
147
+
148
+ }
149
+
150
+ draw(ctx){
151
+
128
152
  ctx.save();
129
153
 
130
- let headerBg = '#FFFFFF';
131
- let headerTextColor = '#000000';
154
+ let headerBg;
155
+ let textColor;
132
156
  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
157
 
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;
158
+ if(this.platform === 'material'){
159
+ headerBg = this.headerBg; // Material 3 tonal surface
160
+ textColor = this.textColor ;
161
+ }else{
162
+ headerBg = this.headerBg;
163
+ textColor = this.textColor ;
154
164
  }
155
165
 
156
- // Background
157
- ctx.fillStyle = this.bgColor;
158
- ctx.fillRect(this.x, this.y, this.width, this.height);
166
+ // background
167
+ ctx.fillStyle = '#FFFFFF';
168
+ ctx.fillRect(this.x,this.y,this.width,this.height);
169
+
170
+ // header
171
+ ctx.fillStyle = headerBg;
172
+ ctx.fillRect(this.x,this.y,this.width,this.headerHeight);
159
173
 
160
- // Bordure Cupertino
161
- if (this.platform === 'cupertino') {
174
+ // Cupertino separator
175
+ if(this.platform === 'cupertino'){
162
176
  ctx.strokeStyle = borderColor;
163
- ctx.lineWidth = 1;
164
- ctx.strokeRect(this.x, this.y, this.width, this.height);
177
+ ctx.beginPath();
178
+ ctx.moveTo(this.x,this.y+this.headerHeight);
179
+ ctx.lineTo(this.x+this.width,this.y+this.headerHeight);
180
+ ctx.stroke();
165
181
  }
166
182
 
167
- // Header
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) {
183
+ // Ripple Material
184
+ if(this.platform === 'material'){
173
185
  ctx.save();
186
+
174
187
  ctx.beginPath();
175
- ctx.rect(this.x, this.y, this.width, this.headerHeight);
188
+ ctx.rect(this.x,this.y,this.width,this.headerHeight);
176
189
  ctx.clip();
177
- for (let ripple of this.ripples) {
178
- ctx.globalAlpha = ripple.opacity;
190
+
191
+ this.ripples.forEach(r=>{
192
+
193
+ ctx.globalAlpha = r.opacity;
179
194
  ctx.fillStyle = this.rippleColor;
180
195
  ctx.beginPath();
181
- ctx.arc(this.x + ripple.x, this.y + ripple.y, ripple.radius, 0, Math.PI * 2);
196
+ ctx.arc(this.x+r.x,this.y+r.y,r.radius,0,Math.PI*2);
182
197
  ctx.fill();
183
- }
198
+
199
+ });
200
+
184
201
  ctx.restore();
185
202
  ctx.globalAlpha = 1;
186
203
  }
187
204
 
188
- // Icône
189
- if (this.icon) {
190
- ctx.font = '20px sans-serif';
191
- ctx.fillStyle = '#666666';
192
- ctx.textAlign = 'left';
193
- ctx.textBaseline = 'middle';
194
- ctx.fillText(this.icon, this.x + 16, this.y + this.headerHeight / 2);
195
- }
205
+ // Title
206
+ ctx.fillStyle = textColor;
207
+ ctx.font = this.platform==='material'
208
+ ? '500 16px Roboto'
209
+ : '600 16px -apple-system';
210
+
211
+ ctx.textAlign='left';
212
+ ctx.textBaseline='middle';
196
213
 
197
- // Titre
198
- ctx.fillStyle = headerTextColor;
199
- ctx.font =
200
- this.platform === 'material'
201
- ? 'bold 16px Roboto, sans-serif'
202
- : 'bold 16px -apple-system, sans-serif';
203
- ctx.textAlign = 'left';
204
- ctx.textBaseline = 'middle';
205
- const titleX = this.icon ? this.x + 56 : this.x + 16;
206
- ctx.fillText(this.title, titleX, this.y + this.headerHeight / 2);
214
+ ctx.fillText(
215
+ this.title,
216
+ this.x+16,
217
+ this.y+this.headerHeight/2
218
+ );
207
219
 
208
220
  // Chevron
209
- const chevronX = this.x + this.width - 30;
210
- const chevronY = this.y + this.headerHeight / 2;
211
- const chevronRotation = this.animProgress * Math.PI;
212
221
  ctx.save();
213
- ctx.translate(chevronX, chevronY);
214
- ctx.rotate(chevronRotation);
215
- ctx.strokeStyle = '#666666';
216
- ctx.lineWidth = chevronWidth;
217
- ctx.lineCap = 'round';
218
- ctx.lineJoin = 'round';
222
+
223
+ ctx.translate(this.x+this.width-24,this.y+this.headerHeight/2);
224
+ ctx.rotate(this.animProgress*Math.PI);
225
+
226
+ ctx.strokeStyle='#666';
227
+ ctx.lineWidth = this.platform==='material'?2:1.3;
228
+
219
229
  ctx.beginPath();
220
- ctx.moveTo(-6, -3);
221
- ctx.lineTo(0, 3);
222
- ctx.lineTo(6, -3);
230
+ ctx.moveTo(-5,-3);
231
+ ctx.lineTo(0,3);
232
+ ctx.lineTo(5,-3);
223
233
  ctx.stroke();
234
+
224
235
  ctx.restore();
225
236
 
226
- // Contenu
227
- if (this.animProgress > 0) {
237
+ // content
238
+ if(this.animProgress>0){
239
+
228
240
  ctx.save();
241
+
229
242
  ctx.beginPath();
230
- ctx.rect(this.x, this.y + this.headerHeight, this.width, this.contentHeight * this.animProgress);
243
+ ctx.rect(
244
+ this.x,
245
+ this.y+this.headerHeight,
246
+ this.width,
247
+ this.contentHeight*this.animProgress
248
+ );
231
249
  ctx.clip();
232
250
 
233
- ctx.strokeStyle = borderColor;
234
- ctx.lineWidth = 1;
235
- ctx.beginPath();
236
- ctx.moveTo(this.x, this.y + this.headerHeight);
237
- ctx.lineTo(this.x + this.width, this.y + this.headerHeight);
238
- ctx.stroke();
251
+ ctx.fillStyle='#666';
252
+ ctx.font='14px -apple-system';
253
+
254
+ let y=this.y+this.headerHeight+this.contentPadding;
239
255
 
240
- ctx.fillStyle = '#666666';
241
- ctx.font = '14px -apple-system, sans-serif';
242
- ctx.textAlign = 'left';
243
- ctx.textBaseline = 'top';
244
- const contentX = this.x + this.contentPadding;
245
- const contentY = this.y + this.headerHeight + this.contentPadding;
246
- const maxWidth = this.width - this.contentPadding * 2;
247
- const lines = this.wrapText(ctx, this.content, maxWidth);
248
- const lineHeight = 20;
249
- lines.forEach((line, index) => {
250
- ctx.fillText(line, contentX, contentY + index * lineHeight);
256
+ this.lines.forEach(line=>{
257
+ ctx.fillText(line,this.x+this.contentPadding,y);
258
+ y+=20;
251
259
  });
252
260
 
253
261
  ctx.restore();
262
+
254
263
  }
255
264
 
256
265
  ctx.restore();
257
- }
258
266
 
259
- isPointInside(x, y) {
260
- return x >= this.x && x <= this.x + this.width &&
261
- y >= this.y && y <= this.y + this.headerHeight;
262
267
  }
268
+
263
269
  }
264
270
 
265
271
  export default Accordion;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "canvasframework",
3
- "version": "0.5.42",
3
+ "version": "0.5.44",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "https://github.com/beyons/CanvasFramework.git"