canvasframework 0.3.16 → 0.3.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/components/Button.js +14 -0
- package/components/ImageCarousel.js +100 -74
- package/components/InputTags.js +50 -12
- package/components/List.js +36 -35
- package/components/SwipeableListItem.js +249 -81
- package/components/Tabs.js +183 -53
- package/core/CanvasFramework.js +63 -41
- package/index.js +10 -1
- package/package.json +1 -1
- package/utils/NotificationManager.js +60 -0
package/components/Button.js
CHANGED
|
@@ -93,6 +93,14 @@ class Button extends Component {
|
|
|
93
93
|
this.rippleColor = this.hexToRgba(baseColor, 0.3);
|
|
94
94
|
this.elevation = 0;
|
|
95
95
|
break;
|
|
96
|
+
|
|
97
|
+
default :
|
|
98
|
+
this.bgColor = options.bgColor || '#FFFFFF';
|
|
99
|
+
this.textColor = options.textColor || baseColor;
|
|
100
|
+
this.borderWidth = 0;
|
|
101
|
+
this.rippleColor = this.hexToRgba(baseColor, 0.2);
|
|
102
|
+
this.elevation = options.elevation || 4;
|
|
103
|
+
break;
|
|
96
104
|
}
|
|
97
105
|
}
|
|
98
106
|
|
|
@@ -127,6 +135,12 @@ class Button extends Component {
|
|
|
127
135
|
this.textColor = options.textColor || baseColor;
|
|
128
136
|
this.borderWidth = 0;
|
|
129
137
|
break;
|
|
138
|
+
|
|
139
|
+
default :
|
|
140
|
+
this.bgColor = 'transparent';
|
|
141
|
+
this.textColor = options.textColor || baseColor;
|
|
142
|
+
this.borderWidth = 0;
|
|
143
|
+
break;
|
|
130
144
|
}
|
|
131
145
|
}
|
|
132
146
|
|
|
@@ -3,22 +3,9 @@ import Component from '../core/Component.js';
|
|
|
3
3
|
/**
|
|
4
4
|
* Carousel / Slider d'images avec swipe horizontal et lazy load
|
|
5
5
|
* Compatible Material et Cupertino
|
|
6
|
-
*
|
|
7
|
-
* @extends Component
|
|
6
|
+
* Tout le scroll est géré par le composant
|
|
8
7
|
*/
|
|
9
8
|
class ImageCarousel extends Component {
|
|
10
|
-
/**
|
|
11
|
-
* @param {CanvasFramework} framework - Framework parent
|
|
12
|
-
* @param {Object} [options={}]
|
|
13
|
-
* @param {Array<string>} [options.images=[]] - URLs des images
|
|
14
|
-
* @param {number} [options.height=200] - Hauteur du carousel
|
|
15
|
-
* @param {number} [options.spacing=16] - Espacement entre images
|
|
16
|
-
* @param {number} [options.borderRadius=8] - Coins arrondis
|
|
17
|
-
* @param {number} [options.pageIndicatorSize=8] - Taille des dots
|
|
18
|
-
* @param {string} [options.pageIndicatorColor='#6200EE'] - Couleur dot actif
|
|
19
|
-
* @param {Function} [options.onSwipeEnd] - Callback quand la page change
|
|
20
|
-
* @param {Function} [options.onImageClick] - Callback clic sur image
|
|
21
|
-
*/
|
|
22
9
|
constructor(framework, options = {}) {
|
|
23
10
|
super(framework, options);
|
|
24
11
|
|
|
@@ -33,8 +20,9 @@ class ImageCarousel extends Component {
|
|
|
33
20
|
this.pageIndicatorColor = options.pageIndicatorColor || '#6200EE';
|
|
34
21
|
|
|
35
22
|
this.platform = framework.platform;
|
|
36
|
-
|
|
23
|
+
|
|
37
24
|
this.isDragging = false;
|
|
25
|
+
this.lastX = 0;
|
|
38
26
|
this.velocity = 0;
|
|
39
27
|
|
|
40
28
|
this.onSwipeEnd = options.onSwipeEnd || null;
|
|
@@ -42,98 +30,140 @@ class ImageCarousel extends Component {
|
|
|
42
30
|
|
|
43
31
|
this.loadedImages = Array(this.images.length).fill(null);
|
|
44
32
|
|
|
45
|
-
|
|
46
|
-
this.framework.addEventListener('touchstart', this.onTouchStart.bind(this));
|
|
47
|
-
this.framework.addEventListener('touchmove', this.onTouchMove.bind(this));
|
|
48
|
-
this.framework.addEventListener('touchend', this.onTouchEnd.bind(this));
|
|
49
|
-
|
|
33
|
+
this._setupEventHandlers();
|
|
50
34
|
this.animateScroll();
|
|
51
35
|
}
|
|
52
36
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
37
|
+
// --------------------------
|
|
38
|
+
// Event handlers
|
|
39
|
+
// --------------------------
|
|
40
|
+
_setupEventHandlers() {
|
|
41
|
+
const canvas = this.framework.canvas;
|
|
42
|
+
|
|
43
|
+
// TOUCH
|
|
44
|
+
canvas.addEventListener('touchstart', (e) => {
|
|
45
|
+
if (e.touches.length === 1 && this.isPointInsideTouch(e.touches[0])) {
|
|
46
|
+
this.isDragging = true;
|
|
47
|
+
this.lastX = e.touches[0].clientX;
|
|
48
|
+
this.velocity = 0;
|
|
49
|
+
e.preventDefault();
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
canvas.addEventListener('touchmove', (e) => {
|
|
54
|
+
if (this.isDragging && e.touches.length === 1) {
|
|
55
|
+
const delta = e.touches[0].clientX - this.lastX;
|
|
56
|
+
this.scrollX += delta;
|
|
57
|
+
this.velocity = delta;
|
|
58
|
+
this.lastX = e.touches[0].clientX;
|
|
59
|
+
|
|
60
|
+
this._clampScroll();
|
|
61
|
+
this._requestRedraw();
|
|
62
|
+
e.preventDefault();
|
|
63
|
+
}
|
|
64
|
+
});
|
|
61
65
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
66
|
+
canvas.addEventListener('touchend', () => this._endDrag());
|
|
67
|
+
|
|
68
|
+
// MOUSE
|
|
69
|
+
canvas.addEventListener('mousedown', (e) => {
|
|
70
|
+
if (this.isPointInside(e)) {
|
|
71
|
+
this.isDragging = true;
|
|
72
|
+
this.lastX = e.clientX;
|
|
73
|
+
this.velocity = 0;
|
|
74
|
+
e.preventDefault();
|
|
75
|
+
}
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
canvas.addEventListener('mousemove', (e) => {
|
|
79
|
+
if (this.isDragging) {
|
|
80
|
+
const delta = e.clientX - this.lastX;
|
|
81
|
+
this.scrollX += delta;
|
|
82
|
+
this.velocity = delta;
|
|
83
|
+
this.lastX = e.clientX;
|
|
84
|
+
|
|
85
|
+
this._clampScroll();
|
|
86
|
+
this._requestRedraw();
|
|
87
|
+
}
|
|
88
|
+
});
|
|
75
89
|
|
|
76
|
-
|
|
77
|
-
|
|
90
|
+
canvas.addEventListener('mouseup', () => this._endDrag());
|
|
91
|
+
canvas.addEventListener('mouseleave', () => this._endDrag());
|
|
92
|
+
}
|
|
78
93
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
94
|
+
_endDrag() {
|
|
95
|
+
if (this.isDragging) {
|
|
96
|
+
this.isDragging = false;
|
|
97
|
+
// Snap à la page la plus proche
|
|
98
|
+
const targetIndex = Math.round(-this.scrollX / (this.width + this.spacing));
|
|
99
|
+
this.currentIndex = Math.min(Math.max(targetIndex, 0), this.images.length - 1);
|
|
100
|
+
this.scrollX = -this.currentIndex * (this.width + this.spacing);
|
|
82
101
|
|
|
83
|
-
|
|
84
|
-
else if (this.velocity > 0.5) targetIndex = Math.max(this.currentIndex - 1, 0);
|
|
85
|
-
else {
|
|
86
|
-
const deltaIndex = Math.round(-this.scrollX / (this.width + this.spacing)) - this.currentIndex;
|
|
87
|
-
if (deltaIndex > 0) targetIndex = Math.min(this.currentIndex + 1, this.images.length - 1);
|
|
88
|
-
else if (deltaIndex < 0) targetIndex = Math.max(this.currentIndex - 1, 0);
|
|
102
|
+
if (this.onSwipeEnd) this.onSwipeEnd(this.currentIndex);
|
|
89
103
|
}
|
|
104
|
+
}
|
|
90
105
|
|
|
91
|
-
|
|
92
|
-
|
|
106
|
+
_clampScroll() {
|
|
107
|
+
const maxScroll = 0;
|
|
108
|
+
const minScroll = -(this.images.length - 1) * (this.width + this.spacing);
|
|
109
|
+
if (this.scrollX > maxScroll) this.scrollX = maxScroll;
|
|
110
|
+
if (this.scrollX < minScroll) this.scrollX = minScroll;
|
|
111
|
+
}
|
|
93
112
|
|
|
94
|
-
|
|
95
|
-
|
|
113
|
+
isPointInsideTouch(touch) {
|
|
114
|
+
const rect = this.framework.canvas.getBoundingClientRect();
|
|
115
|
+
const x = touch.clientX - rect.left;
|
|
116
|
+
const y = touch.clientY - rect.top;
|
|
117
|
+
return this.isPointInside(x, y);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
isPointInside(x, y) {
|
|
121
|
+
return x >= this.x && x <= this.x + this.width &&
|
|
122
|
+
y >= this.y && y <= this.y + this.height;
|
|
123
|
+
}
|
|
96
124
|
|
|
97
|
-
|
|
125
|
+
_requestRedraw() {
|
|
126
|
+
if (this.framework.markComponentDirty) this.framework.markComponentDirty(this);
|
|
98
127
|
}
|
|
99
128
|
|
|
129
|
+
// --------------------------
|
|
130
|
+
// Animation / Inertie
|
|
131
|
+
// --------------------------
|
|
100
132
|
animateScroll() {
|
|
101
133
|
const animate = () => {
|
|
102
134
|
if (!this.isDragging) {
|
|
103
|
-
// inertia
|
|
135
|
+
// inertia
|
|
104
136
|
if (Math.abs(this.velocity) > 0.1) {
|
|
105
137
|
this.scrollX += this.velocity;
|
|
106
|
-
this.velocity *= 0.95;
|
|
138
|
+
this.velocity *= 0.95;
|
|
107
139
|
|
|
108
|
-
|
|
109
|
-
if (this.scrollX > 0) this.scrollX = 0;
|
|
110
|
-
const maxScroll = -(this.images.length - 1) * (this.width + this.spacing);
|
|
111
|
-
if (this.scrollX < maxScroll) this.scrollX = maxScroll;
|
|
140
|
+
this._clampScroll();
|
|
112
141
|
} else {
|
|
113
|
-
// snap
|
|
142
|
+
// snap doux vers la page
|
|
114
143
|
const target = -this.currentIndex * (this.width + this.spacing);
|
|
115
144
|
this.scrollX += (target - this.scrollX) * 0.2;
|
|
116
145
|
}
|
|
117
146
|
}
|
|
118
|
-
|
|
119
147
|
requestAnimationFrame(animate);
|
|
120
148
|
};
|
|
121
149
|
animate();
|
|
122
150
|
}
|
|
123
151
|
|
|
152
|
+
// --------------------------
|
|
153
|
+
// Draw
|
|
154
|
+
// --------------------------
|
|
124
155
|
draw(ctx) {
|
|
125
156
|
ctx.save();
|
|
126
|
-
|
|
127
157
|
const startX = this.x + this.scrollX + this.spacing / 2;
|
|
128
158
|
|
|
129
159
|
for (let i = 0; i < this.images.length; i++) {
|
|
130
160
|
const imgX = startX + i * (this.width + this.spacing);
|
|
131
161
|
|
|
132
|
-
//
|
|
162
|
+
// lazy load
|
|
133
163
|
if (!this.loadedImages[i]) {
|
|
134
164
|
const img = new Image();
|
|
135
165
|
img.src = this.images[i];
|
|
136
|
-
img.onload = () => { this.loadedImages[i] = img; };
|
|
166
|
+
img.onload = () => { this.loadedImages[i] = img; this._requestRedraw(); };
|
|
137
167
|
}
|
|
138
168
|
|
|
139
169
|
ctx.save();
|
|
@@ -152,10 +182,11 @@ class ImageCarousel extends Component {
|
|
|
152
182
|
ctx.textBaseline = 'middle';
|
|
153
183
|
ctx.fillText('🖼', imgX + this.width / 2, this.y + this.height / 2);
|
|
154
184
|
}
|
|
185
|
+
|
|
155
186
|
ctx.restore();
|
|
156
187
|
}
|
|
157
188
|
|
|
158
|
-
//
|
|
189
|
+
// pagination Material
|
|
159
190
|
if (this.platform === 'material') {
|
|
160
191
|
const dotY = this.y + this.height + 12;
|
|
161
192
|
const totalWidth = this.images.length * this.pageIndicatorSize * 2;
|
|
@@ -183,11 +214,6 @@ class ImageCarousel extends Component {
|
|
|
183
214
|
ctx.lineTo(x, y + radius);
|
|
184
215
|
ctx.quadraticCurveTo(x, y, x + radius, y);
|
|
185
216
|
}
|
|
186
|
-
|
|
187
|
-
isPointInside(x, y) {
|
|
188
|
-
return x >= this.x && x <= this.x + this.width &&
|
|
189
|
-
y >= this.y && y <= this.y + this.height;
|
|
190
|
-
}
|
|
191
217
|
}
|
|
192
218
|
|
|
193
219
|
export default ImageCarousel;
|
package/components/InputTags.js
CHANGED
|
@@ -414,14 +414,44 @@ class InputTags extends Component {
|
|
|
414
414
|
|
|
415
415
|
return cursorX;
|
|
416
416
|
}
|
|
417
|
+
|
|
418
|
+
updateHeight() {
|
|
419
|
+
const maxX = this.x + this.width - 10; // marge droite
|
|
420
|
+
let currentX = this.x + 10;
|
|
421
|
+
let currentY = this.y + 10;
|
|
422
|
+
let lines = 1;
|
|
423
|
+
|
|
424
|
+
for (let i = 0; i < this.tags.length; i++) {
|
|
425
|
+
const tagWidth = this.measureTagWidth(this.tags[i]);
|
|
426
|
+
|
|
427
|
+
if (currentX + tagWidth > maxX) {
|
|
428
|
+
currentX = this.x + 10;
|
|
429
|
+
currentY += this.tagHeight + this.tagSpacing;
|
|
430
|
+
lines++;
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
currentX += tagWidth + this.tagSpacing;
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
// Ajouter la largeur du texte en cours
|
|
437
|
+
const textWidth = this.value.length * (this.fontSize * 0.6);
|
|
438
|
+
if (currentX + textWidth > maxX) {
|
|
439
|
+
lines++;
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
// Mettre à jour la hauteur dynamique
|
|
443
|
+
this.height = Math.max(lines * (this.tagHeight + this.tagSpacing) + 10, 40); // 40 = minHeight
|
|
444
|
+
}
|
|
417
445
|
|
|
418
446
|
/**
|
|
419
447
|
* Dessine l'input
|
|
420
448
|
* @param {CanvasRenderingContext2D} ctx - Contexte de dessin
|
|
421
449
|
*/
|
|
422
450
|
draw(ctx) {
|
|
451
|
+
this.updateHeight();
|
|
423
452
|
ctx.save();
|
|
424
|
-
|
|
453
|
+
|
|
454
|
+
// Dessin du contour
|
|
425
455
|
if (this.platform === 'material') {
|
|
426
456
|
ctx.strokeStyle = this.focused ? '#6200EE' : '#CCCCCC';
|
|
427
457
|
ctx.lineWidth = this.focused ? 2 : 1;
|
|
@@ -436,30 +466,38 @@ class InputTags extends Component {
|
|
|
436
466
|
this.roundRect(ctx, this.x, this.y, this.width, this.height, 8);
|
|
437
467
|
ctx.stroke();
|
|
438
468
|
}
|
|
439
|
-
|
|
469
|
+
|
|
440
470
|
// Position de départ pour dessiner les tags
|
|
441
471
|
let currentX = this.x + 10;
|
|
442
|
-
|
|
443
|
-
|
|
472
|
+
let currentY = this.y + 10;
|
|
473
|
+
const maxX = this.x + this.width - 10;
|
|
474
|
+
|
|
444
475
|
// Dessiner les tags
|
|
445
476
|
for (let i = 0; i < this.tags.length; i++) {
|
|
446
|
-
this.drawTag(ctx, this.tags[i], currentX, tagY);
|
|
447
477
|
const tagWidth = this.measureTagWidth(this.tags[i]);
|
|
478
|
+
|
|
479
|
+
// Retour à la ligne si dépassement
|
|
480
|
+
if (currentX + tagWidth > maxX) {
|
|
481
|
+
currentX = this.x + 10;
|
|
482
|
+
currentY += this.tagHeight + this.tagSpacing;
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
this.drawTag(ctx, this.tags[i], currentX, currentY);
|
|
448
486
|
currentX += tagWidth + this.tagSpacing;
|
|
449
487
|
}
|
|
450
|
-
|
|
451
|
-
// Dessiner le texte en cours
|
|
488
|
+
|
|
489
|
+
// Dessiner le texte en cours ou placeholder
|
|
452
490
|
if (this.value || (this.focused && this.tags.length === 0)) {
|
|
453
491
|
ctx.fillStyle = this.value ? '#000000' : '#999999';
|
|
454
492
|
ctx.font = `${this.fontSize}px -apple-system, BlinkMacSystemFont, 'Roboto', sans-serif`;
|
|
455
493
|
ctx.textAlign = 'left';
|
|
456
494
|
ctx.textBaseline = 'middle';
|
|
457
|
-
|
|
495
|
+
|
|
458
496
|
const displayText = this.value || this.placeholder;
|
|
459
|
-
const textY =
|
|
460
|
-
|
|
497
|
+
const textY = currentY + this.tagHeight / 2;
|
|
498
|
+
|
|
461
499
|
ctx.fillText(displayText, currentX, textY);
|
|
462
|
-
|
|
500
|
+
|
|
463
501
|
// Curseur
|
|
464
502
|
if (this.focused && this.cursorVisible) {
|
|
465
503
|
const textWidth = ctx.measureText(this.value).width;
|
|
@@ -467,7 +505,7 @@ class InputTags extends Component {
|
|
|
467
505
|
ctx.fillRect(currentX + textWidth, textY - this.fontSize / 2, 2, this.fontSize);
|
|
468
506
|
}
|
|
469
507
|
}
|
|
470
|
-
|
|
508
|
+
|
|
471
509
|
ctx.restore();
|
|
472
510
|
}
|
|
473
511
|
|
package/components/List.js
CHANGED
|
@@ -1,24 +1,12 @@
|
|
|
1
1
|
import Component from '../core/Component.js';
|
|
2
2
|
import ListItem from '../components/ListItem.js';
|
|
3
|
+
|
|
3
4
|
/**
|
|
4
5
|
* Conteneur pour les éléments de liste (ListItems) avec défilement automatique
|
|
5
6
|
* @class
|
|
6
7
|
* @extends Component
|
|
7
|
-
* @param {Framework} framework - Instance du framework
|
|
8
|
-
* @param {Object} [options={}] - Options de configuration
|
|
9
|
-
* @param {number} [options.itemHeight=56] - Hauteur de chaque item en pixels
|
|
10
|
-
* @param {Function} [options.onItemClick] - Callback appelé lors du clic sur un item
|
|
11
|
-
* @param {number} [options.y=0] - Position Y de départ
|
|
12
|
-
* @example
|
|
13
|
-
* const list = new List(framework, {
|
|
14
|
-
* itemHeight: 64,
|
|
15
|
-
* onItemClick: (index, itemOptions) => console.log('Item clicked:', index)
|
|
16
|
-
* });
|
|
17
8
|
*/
|
|
18
9
|
class List extends Component {
|
|
19
|
-
/**
|
|
20
|
-
* @constructs List
|
|
21
|
-
*/
|
|
22
10
|
constructor(framework, options = {}) {
|
|
23
11
|
super(framework, options);
|
|
24
12
|
/** @type {ListItem[]} */
|
|
@@ -28,37 +16,50 @@ class List extends Component {
|
|
|
28
16
|
/** @type {Function|undefined} */
|
|
29
17
|
this.onItemClick = options.onItemClick;
|
|
30
18
|
/** @type {number} */
|
|
31
|
-
this.y = options.y || 0;
|
|
19
|
+
this.y = options.y || 0;
|
|
32
20
|
}
|
|
33
21
|
|
|
34
22
|
/**
|
|
35
23
|
* Ajoute un item à la liste
|
|
36
|
-
* @param {Object} itemOptions - Options pour l'item
|
|
37
|
-
* @
|
|
38
|
-
* @param {Function} [itemOptions.onClick] - Callback spécifique à l'item
|
|
39
|
-
* @param {Object} [itemOptions.style] - Style optionnel pour l'item
|
|
40
|
-
* @returns {ListItem} L'item créé
|
|
24
|
+
* @param {Object|Component} itemOptions - Options pour l'item OU instance de Component (ex: SwipeableListItem)
|
|
25
|
+
* @returns {ListItem|Component} L'item créé ou passé
|
|
41
26
|
*/
|
|
42
27
|
addItem(itemOptions) {
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
28
|
+
let item;
|
|
29
|
+
|
|
30
|
+
// 🔹 Vérifier si c'est déjà un composant instancié (SwipeableListItem)
|
|
31
|
+
if (itemOptions instanceof Component) {
|
|
32
|
+
item = itemOptions;
|
|
33
|
+
|
|
34
|
+
// 🔹 CRITIQUE : Définir la position et taille de l'item
|
|
35
|
+
item.x = this.x;
|
|
36
|
+
item.y = this.y + (this.items.length * this.itemHeight);
|
|
37
|
+
item.width = this.width;
|
|
38
|
+
item.height = this.itemHeight;
|
|
39
|
+
|
|
40
|
+
console.log(`📦 SwipeableListItem positionné à y=${item.y}`);
|
|
41
|
+
} else {
|
|
42
|
+
// Créer un ListItem standard
|
|
43
|
+
item = new ListItem(this.framework, {
|
|
44
|
+
...itemOptions,
|
|
45
|
+
x: this.x,
|
|
46
|
+
y: this.y + (this.items.length * this.itemHeight),
|
|
47
|
+
width: this.width,
|
|
48
|
+
height: this.itemHeight,
|
|
49
|
+
onClick: () => {
|
|
50
|
+
if (this.onItemClick) {
|
|
51
|
+
this.onItemClick(this.items.length, itemOptions);
|
|
52
|
+
}
|
|
53
|
+
if (itemOptions.onClick) {
|
|
54
|
+
itemOptions.onClick();
|
|
55
|
+
}
|
|
55
56
|
}
|
|
56
|
-
}
|
|
57
|
-
}
|
|
57
|
+
});
|
|
58
|
+
}
|
|
58
59
|
|
|
59
60
|
this.items.push(item);
|
|
60
|
-
this.framework.add(item);
|
|
61
|
-
this.height = this.items.length * this.itemHeight;
|
|
61
|
+
this.framework.add(item);
|
|
62
|
+
this.height = this.items.length * this.itemHeight;
|
|
62
63
|
|
|
63
64
|
return item;
|
|
64
65
|
}
|