canvasframework 0.6.3 → 0.7.1
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/AppBar.js +159 -99
- package/components/Avatar.js +142 -143
- package/components/BottomSheet.js +124 -106
- package/components/Button.js +207 -254
- package/components/Checkbox.js +98 -91
- package/components/Chip.js +137 -106
- package/components/Dialog.js +161 -146
- package/components/FAB.js +122 -195
- package/components/Switch.js +146 -112
- package/components/Text.js +104 -103
- package/components/Toast.js +132 -156
- package/components/VirtualList.js +88 -59
- package/core/CanvasFramework.js +115 -44
- package/core/CanvasUtils.js +141 -0
- package/core/Component.js +124 -66
- package/core/ThemeManager.js +162 -176
- package/core/WebGLCanvasAdapter.js +88 -39
- package/package.json +1 -1
package/components/Toast.js
CHANGED
|
@@ -1,236 +1,212 @@
|
|
|
1
1
|
import Component from '../core/Component.js';
|
|
2
|
+
import { roundRect } from '../core/CanvasUtils.js';
|
|
3
|
+
|
|
2
4
|
/**
|
|
3
|
-
* Toast
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
* @property {number} opacity - Opacité
|
|
11
|
-
* @property {string} platform - Plateforme
|
|
12
|
-
* @property {boolean} isVisible - Visibilité
|
|
13
|
-
* @property {number} targetY - Position Y cible
|
|
14
|
-
* @property {number} minWidth - Largeur minimale
|
|
15
|
-
* @property {number} maxWidth - Largeur maximale
|
|
16
|
-
* @property {boolean} animating - En cours d'animation
|
|
5
|
+
* Toast — notification temporaire non bloquante.
|
|
6
|
+
*
|
|
7
|
+
* Corrections :
|
|
8
|
+
* - roundRect() vient de CanvasUtils (plus de copie locale)
|
|
9
|
+
* - Guard _destroyed dans les boucles RAF
|
|
10
|
+
* - targetY tient compte de SafeArea / bottomOffset
|
|
11
|
+
* - destroy() annule les RAF en cours
|
|
17
12
|
*/
|
|
18
13
|
class Toast extends Component {
|
|
19
14
|
/**
|
|
20
|
-
*
|
|
21
|
-
* @param {
|
|
22
|
-
* @param {
|
|
23
|
-
* @param {
|
|
24
|
-
* @param {number}
|
|
25
|
-
* @param {number} [options.x] - Position X (auto-centré)
|
|
26
|
-
* @param {number} [options.y] - Position Y (en bas)
|
|
15
|
+
* @param {CanvasFramework} framework
|
|
16
|
+
* @param {Object} [options={}]
|
|
17
|
+
* @param {string} [options.text='']
|
|
18
|
+
* @param {number} [options.duration=3000] - Durée d'affichage en ms
|
|
19
|
+
* @param {number} [options.bottomOffset=100] - Distance depuis le bas de l'écran
|
|
27
20
|
*/
|
|
28
21
|
constructor(framework, options = {}) {
|
|
29
22
|
super(framework, {
|
|
30
23
|
x: 0,
|
|
31
|
-
y: framework.height,
|
|
24
|
+
y: framework.height,
|
|
32
25
|
width: framework.width,
|
|
33
|
-
height: 60,
|
|
34
|
-
...options
|
|
26
|
+
height: 60,
|
|
27
|
+
...options,
|
|
35
28
|
});
|
|
36
|
-
|
|
37
|
-
this.text
|
|
38
|
-
this.duration
|
|
39
|
-
this.fontSize
|
|
40
|
-
this.padding
|
|
41
|
-
this.opacity
|
|
42
|
-
this.platform
|
|
43
|
-
this.isVisible
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
29
|
+
|
|
30
|
+
this.text = options.text || '';
|
|
31
|
+
this.duration = options.duration ?? 3000;
|
|
32
|
+
this.fontSize = 16;
|
|
33
|
+
this.padding = 20;
|
|
34
|
+
this.opacity = 0;
|
|
35
|
+
this.platform = framework.platform;
|
|
36
|
+
this.isVisible = false;
|
|
37
|
+
this._rafId = null;
|
|
38
|
+
this._hideTimer = null;
|
|
39
|
+
|
|
40
|
+
// Offset configurable — respecte SafeArea si disponible
|
|
41
|
+
const safeAreaBottom = framework.safeArea?.bottom ?? 0;
|
|
42
|
+
const bottomOffset = options.bottomOffset ?? 100;
|
|
43
|
+
this.targetY = framework.height - bottomOffset - safeAreaBottom;
|
|
44
|
+
|
|
49
45
|
this.minWidth = 200;
|
|
50
46
|
this.maxWidth = Math.min(600, framework.width - 40);
|
|
51
|
-
|
|
52
|
-
// Animation
|
|
53
|
-
this.animating = false;
|
|
54
|
-
|
|
55
|
-
// NE PAS appeler show() ici - laissé à l'appelant
|
|
56
47
|
}
|
|
57
48
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
49
|
+
// ─────────────────────────────────────────
|
|
50
|
+
// API PUBLIQUE
|
|
51
|
+
// ─────────────────────────────────────────
|
|
52
|
+
|
|
53
|
+
/** Affiche le toast et programme sa disparition. */
|
|
61
54
|
show() {
|
|
62
55
|
this.isVisible = true;
|
|
63
|
-
this.visible
|
|
64
|
-
this.
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
if (this.isVisible) {
|
|
69
|
-
this.hide();
|
|
70
|
-
}
|
|
56
|
+
this.visible = true;
|
|
57
|
+
this._animateIn();
|
|
58
|
+
|
|
59
|
+
this._hideTimer = setTimeout(() => {
|
|
60
|
+
if (this.isVisible) this.hide();
|
|
71
61
|
}, this.duration);
|
|
72
62
|
}
|
|
73
63
|
|
|
74
|
-
/**
|
|
75
|
-
* Cache le toast
|
|
76
|
-
*/
|
|
64
|
+
/** Masque le toast. */
|
|
77
65
|
hide() {
|
|
78
|
-
this.
|
|
66
|
+
if (this._hideTimer) {
|
|
67
|
+
clearTimeout(this._hideTimer);
|
|
68
|
+
this._hideTimer = null;
|
|
69
|
+
}
|
|
70
|
+
this._animateOut();
|
|
79
71
|
}
|
|
80
72
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
this.
|
|
88
|
-
|
|
73
|
+
// ─────────────────────────────────────────
|
|
74
|
+
// ANIMATIONS
|
|
75
|
+
// ─────────────────────────────────────────
|
|
76
|
+
|
|
77
|
+
/** @private */
|
|
78
|
+
_animateIn() {
|
|
79
|
+
if (this._rafId) return;
|
|
80
|
+
|
|
89
81
|
const animate = () => {
|
|
82
|
+
if (this._destroyed) { this._rafId = null; return; }
|
|
83
|
+
|
|
90
84
|
this.opacity += 0.1;
|
|
91
|
-
this.y
|
|
92
|
-
|
|
85
|
+
this.y -= (this.y - this.targetY) * 0.2;
|
|
86
|
+
|
|
93
87
|
if (this.opacity >= 1 && Math.abs(this.y - this.targetY) < 1) {
|
|
94
88
|
this.opacity = 1;
|
|
95
|
-
this.y
|
|
96
|
-
this.
|
|
89
|
+
this.y = this.targetY;
|
|
90
|
+
this._rafId = null;
|
|
97
91
|
return;
|
|
98
92
|
}
|
|
99
|
-
|
|
100
|
-
requestAnimationFrame(animate);
|
|
93
|
+
|
|
94
|
+
this._rafId = requestAnimationFrame(animate);
|
|
101
95
|
};
|
|
102
|
-
|
|
103
|
-
animate
|
|
96
|
+
|
|
97
|
+
this._rafId = requestAnimationFrame(animate);
|
|
104
98
|
}
|
|
105
99
|
|
|
106
|
-
/**
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
100
|
+
/** @private */
|
|
101
|
+
_animateOut() {
|
|
102
|
+
if (this._rafId) {
|
|
103
|
+
cancelAnimationFrame(this._rafId);
|
|
104
|
+
this._rafId = null;
|
|
105
|
+
}
|
|
106
|
+
|
|
114
107
|
const animate = () => {
|
|
108
|
+
if (this._destroyed) { this._rafId = null; return; }
|
|
109
|
+
|
|
115
110
|
this.opacity -= 0.1;
|
|
116
|
-
this.y
|
|
117
|
-
|
|
111
|
+
this.y += 5;
|
|
112
|
+
|
|
118
113
|
if (this.opacity <= 0) {
|
|
119
|
-
this.opacity
|
|
114
|
+
this.opacity = 0;
|
|
120
115
|
this.isVisible = false;
|
|
121
|
-
this.visible
|
|
122
|
-
this.
|
|
116
|
+
this.visible = false;
|
|
117
|
+
this._rafId = null;
|
|
123
118
|
this.framework.remove(this);
|
|
124
119
|
return;
|
|
125
120
|
}
|
|
126
|
-
|
|
127
|
-
requestAnimationFrame(animate);
|
|
121
|
+
|
|
122
|
+
this._rafId = requestAnimationFrame(animate);
|
|
128
123
|
};
|
|
129
|
-
|
|
130
|
-
animate
|
|
124
|
+
|
|
125
|
+
this._rafId = requestAnimationFrame(animate);
|
|
131
126
|
}
|
|
132
127
|
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
128
|
+
// ─────────────────────────────────────────
|
|
129
|
+
// DESSIN
|
|
130
|
+
// ─────────────────────────────────────────
|
|
131
|
+
|
|
137
132
|
draw(ctx) {
|
|
138
133
|
if (!this.isVisible || this.opacity <= 0) return;
|
|
139
|
-
|
|
134
|
+
|
|
140
135
|
ctx.save();
|
|
141
136
|
ctx.globalAlpha = this.opacity;
|
|
142
|
-
|
|
143
|
-
// Calculer la largeur en fonction du texte
|
|
137
|
+
|
|
144
138
|
ctx.font = `${this.fontSize}px -apple-system, BlinkMacSystemFont, 'Roboto', sans-serif`;
|
|
145
|
-
const textWidth
|
|
139
|
+
const textWidth = ctx.measureText(this.text).width;
|
|
146
140
|
const toastWidth = Math.min(
|
|
147
141
|
this.maxWidth,
|
|
148
142
|
Math.max(this.minWidth, textWidth + this.padding * 2)
|
|
149
143
|
);
|
|
150
|
-
|
|
151
|
-
// Position centrée horizontalement
|
|
144
|
+
|
|
152
145
|
const toastX = (this.framework.width - toastWidth) / 2;
|
|
153
146
|
const toastY = this.y;
|
|
154
|
-
|
|
147
|
+
|
|
155
148
|
if (this.platform === 'material') {
|
|
156
|
-
|
|
157
|
-
ctx.
|
|
158
|
-
ctx.
|
|
159
|
-
ctx.
|
|
160
|
-
ctx.shadowOffsetY = 4;
|
|
149
|
+
ctx.fillStyle = '#323232';
|
|
150
|
+
ctx.shadowColor = 'rgba(0,0,0,0.3)';
|
|
151
|
+
ctx.shadowBlur = 15;
|
|
152
|
+
ctx.shadowOffsetY = 4;
|
|
161
153
|
ctx.beginPath();
|
|
162
|
-
|
|
154
|
+
roundRect(ctx, toastX, toastY, toastWidth, this.height, 8);
|
|
163
155
|
ctx.fill();
|
|
164
156
|
} else {
|
|
165
|
-
|
|
166
|
-
ctx.
|
|
167
|
-
ctx.
|
|
168
|
-
ctx.
|
|
169
|
-
ctx.shadowOffsetY = 4;
|
|
157
|
+
ctx.fillStyle = 'rgba(0,0,0,0.85)';
|
|
158
|
+
ctx.shadowColor = 'rgba(0,0,0,0.2)';
|
|
159
|
+
ctx.shadowBlur = 20;
|
|
160
|
+
ctx.shadowOffsetY = 4;
|
|
170
161
|
ctx.beginPath();
|
|
171
|
-
|
|
162
|
+
roundRect(ctx, toastX, toastY, toastWidth, this.height, 14);
|
|
172
163
|
ctx.fill();
|
|
173
164
|
}
|
|
174
|
-
|
|
175
|
-
ctx.shadowColor
|
|
176
|
-
ctx.shadowBlur
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
ctx.
|
|
181
|
-
ctx.textAlign
|
|
165
|
+
|
|
166
|
+
ctx.shadowColor = 'transparent';
|
|
167
|
+
ctx.shadowBlur = 0;
|
|
168
|
+
ctx.shadowOffsetY = 0;
|
|
169
|
+
|
|
170
|
+
// Texte (tronqué si nécessaire)
|
|
171
|
+
ctx.fillStyle = '#FFFFFF';
|
|
172
|
+
ctx.textAlign = 'center';
|
|
182
173
|
ctx.textBaseline = 'middle';
|
|
183
|
-
|
|
184
|
-
// Tronquer le texte si nécessaire
|
|
174
|
+
|
|
185
175
|
let displayText = this.text;
|
|
186
176
|
if (textWidth > toastWidth - this.padding * 2) {
|
|
187
|
-
// Trouver où couper le texte
|
|
188
|
-
let truncated = this.text;
|
|
189
177
|
for (let i = this.text.length; i > 0; i--) {
|
|
190
|
-
truncated = this.text.substring(0, i) + '...';
|
|
178
|
+
const truncated = this.text.substring(0, i) + '...';
|
|
191
179
|
if (ctx.measureText(truncated).width <= toastWidth - this.padding * 2) {
|
|
192
180
|
displayText = truncated;
|
|
193
181
|
break;
|
|
194
182
|
}
|
|
195
183
|
}
|
|
196
184
|
}
|
|
197
|
-
|
|
185
|
+
|
|
198
186
|
ctx.fillText(displayText, toastX + toastWidth / 2, toastY + this.height / 2);
|
|
199
|
-
|
|
200
|
-
ctx.restore();
|
|
201
|
-
}
|
|
202
187
|
|
|
203
|
-
|
|
204
|
-
* Dessine un rectangle avec coins arrondis
|
|
205
|
-
* @param {CanvasRenderingContext2D} ctx - Contexte de dessin
|
|
206
|
-
* @param {number} x - Position X
|
|
207
|
-
* @param {number} y - Position Y
|
|
208
|
-
* @param {number} width - Largeur
|
|
209
|
-
* @param {number} height - Hauteur
|
|
210
|
-
* @param {number} radius - Rayon des coins
|
|
211
|
-
* @private
|
|
212
|
-
*/
|
|
213
|
-
roundRect(ctx, x, y, width, height, radius) {
|
|
214
|
-
ctx.beginPath();
|
|
215
|
-
ctx.moveTo(x + radius, y);
|
|
216
|
-
ctx.lineTo(x + width - radius, y);
|
|
217
|
-
ctx.quadraticCurveTo(x + width, y, x + width, y + radius);
|
|
218
|
-
ctx.lineTo(x + width, y + height - radius);
|
|
219
|
-
ctx.quadraticCurveTo(x + width, y + height, x + width - radius, y + height);
|
|
220
|
-
ctx.lineTo(x + radius, y + height);
|
|
221
|
-
ctx.quadraticCurveTo(x, y + height, x, y + height - radius);
|
|
222
|
-
ctx.lineTo(x, y + radius);
|
|
223
|
-
ctx.quadraticCurveTo(x, y, x + radius, y);
|
|
224
|
-
ctx.closePath();
|
|
188
|
+
ctx.restore();
|
|
225
189
|
}
|
|
226
190
|
|
|
227
|
-
/**
|
|
228
|
-
* Vérifie si un point est dans les limites
|
|
229
|
-
* @returns {boolean} False (non cliquable)
|
|
230
|
-
*/
|
|
231
191
|
isPointInside() {
|
|
232
192
|
return false; // Non cliquable
|
|
233
193
|
}
|
|
194
|
+
|
|
195
|
+
// ─────────────────────────────────────────
|
|
196
|
+
// DESTROY
|
|
197
|
+
// ─────────────────────────────────────────
|
|
198
|
+
|
|
199
|
+
destroy() {
|
|
200
|
+
if (this._rafId) {
|
|
201
|
+
cancelAnimationFrame(this._rafId);
|
|
202
|
+
this._rafId = null;
|
|
203
|
+
}
|
|
204
|
+
if (this._hideTimer) {
|
|
205
|
+
clearTimeout(this._hideTimer);
|
|
206
|
+
this._hideTimer = null;
|
|
207
|
+
}
|
|
208
|
+
super.destroy();
|
|
209
|
+
}
|
|
234
210
|
}
|
|
235
211
|
|
|
236
212
|
export default Toast;
|
|
@@ -2,119 +2,148 @@ import Component from '../core/Component.js';
|
|
|
2
2
|
import ListItem from '../components/ListItem.js';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
5
|
+
* VirtualList — rendu optimisé pour les longues listes.
|
|
6
|
+
*
|
|
7
|
+
* Corrections :
|
|
8
|
+
* - visibleItems passe de Array à Map<index, ListItem> → lookup O(1)
|
|
9
|
+
* - scroll() appelle markDirty() pour déclencher le re-render
|
|
10
|
+
* - destroy() supprime tous les items restants
|
|
8
11
|
*/
|
|
9
12
|
class VirtualList extends Component {
|
|
13
|
+
/**
|
|
14
|
+
* @param {CanvasFramework} framework
|
|
15
|
+
* @param {Object} [options={}]
|
|
16
|
+
* @param {number} [options.itemHeight=56]
|
|
17
|
+
* @param {number} [options.height] - Hauteur du viewport visible
|
|
18
|
+
* @param {Function} [options.onItemClick] - (index, data) => void
|
|
19
|
+
*/
|
|
10
20
|
constructor(framework, options = {}) {
|
|
11
21
|
super(framework, options);
|
|
12
22
|
|
|
13
|
-
this.allItemsData
|
|
14
|
-
this.visibleItems
|
|
15
|
-
this.itemHeight
|
|
16
|
-
this.onItemClick
|
|
17
|
-
this.y
|
|
18
|
-
|
|
19
|
-
this.
|
|
20
|
-
this.scrollOffset = 0; // Position de scroll
|
|
23
|
+
this.allItemsData = []; // Toutes les données (légères)
|
|
24
|
+
this.visibleItems = new Map(); // Map<index, ListItem> — O(1) lookup
|
|
25
|
+
this.itemHeight = options.itemHeight || 56;
|
|
26
|
+
this.onItemClick = options.onItemClick;
|
|
27
|
+
this.y = options.y || 0;
|
|
28
|
+
this.viewportHeight = options.height || framework.height;
|
|
29
|
+
this.scrollOffset = 0;
|
|
21
30
|
}
|
|
22
31
|
|
|
32
|
+
// ─────────────────────────────────────────
|
|
33
|
+
// API PUBLIQUE
|
|
34
|
+
// ─────────────────────────────────────────
|
|
35
|
+
|
|
23
36
|
/**
|
|
24
|
-
* Ajoute un item (seule la
|
|
25
|
-
* @param {Object} itemOptions
|
|
37
|
+
* Ajoute un item (seule la donnée est stockée, pas l'objet visuel).
|
|
38
|
+
* @param {Object} itemOptions - Options passées à ListItem lors de la création
|
|
26
39
|
*/
|
|
27
40
|
addItem(itemOptions) {
|
|
28
41
|
this.allItemsData.push(itemOptions);
|
|
29
|
-
this.
|
|
42
|
+
this._updateVisibleItems();
|
|
30
43
|
}
|
|
31
44
|
|
|
32
45
|
/**
|
|
33
|
-
* Supprime tous les items
|
|
46
|
+
* Supprime tous les items (données + objets visuels).
|
|
34
47
|
*/
|
|
35
48
|
clear() {
|
|
36
|
-
for (
|
|
49
|
+
for (const item of this.visibleItems.values()) {
|
|
37
50
|
this.framework.remove(item);
|
|
38
51
|
}
|
|
39
52
|
this.allItemsData = [];
|
|
40
|
-
this.visibleItems
|
|
53
|
+
this.visibleItems.clear();
|
|
41
54
|
this.height = 0;
|
|
42
55
|
}
|
|
43
56
|
|
|
44
57
|
/**
|
|
45
|
-
*
|
|
58
|
+
* Fait défiler la liste d'un delta.
|
|
59
|
+
* @param {number} deltaY - Valeur positive = vers le bas
|
|
46
60
|
*/
|
|
47
|
-
|
|
61
|
+
scroll(deltaY) {
|
|
62
|
+
this.scrollOffset += deltaY;
|
|
63
|
+
this.scrollOffset = Math.max(0, this.scrollOffset);
|
|
64
|
+
|
|
65
|
+
const maxScroll = Math.max(0, this.allItemsData.length * this.itemHeight - this.viewportHeight);
|
|
66
|
+
this.scrollOffset = Math.min(this.scrollOffset, maxScroll);
|
|
67
|
+
|
|
68
|
+
this._updateVisibleItems();
|
|
69
|
+
this.markDirty(); // Déclenche le re-render du framework
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// ─────────────────────────────────────────
|
|
73
|
+
// INTERNE
|
|
74
|
+
// ─────────────────────────────────────────
|
|
75
|
+
|
|
76
|
+
/** @private */
|
|
77
|
+
_updateVisibleItems() {
|
|
48
78
|
const firstIndex = Math.floor(this.scrollOffset / this.itemHeight);
|
|
49
|
-
const lastIndex
|
|
79
|
+
const lastIndex = Math.min(
|
|
80
|
+
this.allItemsData.length - 1,
|
|
81
|
+
Math.ceil((this.scrollOffset + this.viewportHeight) / this.itemHeight)
|
|
82
|
+
);
|
|
50
83
|
|
|
51
|
-
const
|
|
84
|
+
const nextVisible = new Set();
|
|
52
85
|
|
|
53
86
|
for (let i = firstIndex; i <= lastIndex; i++) {
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
87
|
+
nextVisible.add(i);
|
|
88
|
+
|
|
89
|
+
const targetY = this.y + i * this.itemHeight - this.scrollOffset;
|
|
90
|
+
|
|
91
|
+
if (this.visibleItems.has(i)) {
|
|
92
|
+
// Mettre à jour la position Y si l'item existe déjà
|
|
93
|
+
this.visibleItems.get(i).y = targetY;
|
|
94
|
+
} else {
|
|
95
|
+
// Créer un nouvel item
|
|
57
96
|
const data = this.allItemsData[i];
|
|
58
|
-
item = new ListItem(this.framework, {
|
|
97
|
+
const item = new ListItem(this.framework, {
|
|
59
98
|
...data,
|
|
60
99
|
x: this.x,
|
|
61
|
-
y:
|
|
100
|
+
y: targetY,
|
|
62
101
|
width: this.width,
|
|
63
102
|
height: this.itemHeight,
|
|
64
103
|
onClick: () => {
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
}
|
|
104
|
+
this.onItemClick?.(i, data);
|
|
105
|
+
data.onClick?.();
|
|
106
|
+
},
|
|
68
107
|
});
|
|
69
108
|
item.__virtualIndex = i;
|
|
70
109
|
this.framework.add(item);
|
|
71
|
-
|
|
72
|
-
// Met à jour la position Y si déjà existant
|
|
73
|
-
item.y = this.y + i * this.itemHeight - this.scrollOffset;
|
|
110
|
+
this.visibleItems.set(i, item);
|
|
74
111
|
}
|
|
75
|
-
newVisibleItems.push(item);
|
|
76
112
|
}
|
|
77
113
|
|
|
78
|
-
//
|
|
79
|
-
for (
|
|
80
|
-
if (!
|
|
114
|
+
// Supprimer les items qui sortent du viewport
|
|
115
|
+
for (const [index, item] of this.visibleItems) {
|
|
116
|
+
if (!nextVisible.has(index)) {
|
|
81
117
|
this.framework.remove(item);
|
|
118
|
+
this.visibleItems.delete(index);
|
|
82
119
|
}
|
|
83
120
|
}
|
|
84
121
|
|
|
85
|
-
this.visibleItems = newVisibleItems;
|
|
86
122
|
this.height = this.allItemsData.length * this.itemHeight;
|
|
87
123
|
}
|
|
88
124
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
*/
|
|
93
|
-
scroll(deltaY) {
|
|
94
|
-
this.scrollOffset += deltaY;
|
|
95
|
-
if (this.scrollOffset < 0) this.scrollOffset = 0;
|
|
96
|
-
const maxScroll = Math.max(0, this.height - this.viewportHeight);
|
|
97
|
-
if (this.scrollOffset > maxScroll) this.scrollOffset = maxScroll;
|
|
98
|
-
|
|
99
|
-
this.updateVisibleItems();
|
|
100
|
-
}
|
|
125
|
+
// ─────────────────────────────────────────
|
|
126
|
+
// DESSIN
|
|
127
|
+
// ─────────────────────────────────────────
|
|
101
128
|
|
|
102
|
-
/**
|
|
103
|
-
* Dessine les items visibles
|
|
104
|
-
* @param {CanvasRenderingContext2D} ctx
|
|
105
|
-
*/
|
|
106
129
|
draw(ctx) {
|
|
107
|
-
for (
|
|
130
|
+
for (const item of this.visibleItems.values()) {
|
|
108
131
|
item.draw(ctx);
|
|
109
132
|
}
|
|
110
133
|
}
|
|
111
134
|
|
|
112
|
-
/**
|
|
113
|
-
* Toujours false : les ListItems gèrent leurs clics
|
|
114
|
-
*/
|
|
115
135
|
isPointInside() {
|
|
116
|
-
return false;
|
|
136
|
+
return false; // Les ListItems gèrent leurs propres hit-tests
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// ─────────────────────────────────────────
|
|
140
|
+
// DESTROY
|
|
141
|
+
// ─────────────────────────────────────────
|
|
142
|
+
|
|
143
|
+
destroy() {
|
|
144
|
+
this.clear();
|
|
145
|
+
super.destroy();
|
|
117
146
|
}
|
|
118
147
|
}
|
|
119
148
|
|
|
120
|
-
export default VirtualList;
|
|
149
|
+
export default VirtualList;
|