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/core/Component.js
CHANGED
|
@@ -37,25 +37,53 @@ class Component {
|
|
|
37
37
|
this.hovered = false;
|
|
38
38
|
this.onClick = options.onClick;
|
|
39
39
|
this.onPress = options.onPress;
|
|
40
|
-
|
|
41
|
-
// Système dirty simple
|
|
40
|
+
|
|
41
|
+
// Système dirty simple
|
|
42
42
|
this._dirty = true;
|
|
43
|
-
|
|
43
|
+
|
|
44
|
+
// Flag de destruction — stoppe les RAF/timers des sous-classes
|
|
45
|
+
this._destroyed = false;
|
|
46
|
+
|
|
44
47
|
// Lifecycle
|
|
45
48
|
this._mounted = false;
|
|
46
49
|
|
|
47
|
-
//
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
50
|
+
// Deep copy pour éviter que _prevProps référence les mêmes objets
|
|
51
|
+
// qu'options (important si options contient des tableaux ou objets imbriqués).
|
|
52
|
+
this._prevProps = Component._deepClone(options);
|
|
53
|
+
|
|
54
|
+
// Système de listeners
|
|
51
55
|
this._listeners = new Map();
|
|
52
56
|
}
|
|
53
57
|
|
|
58
|
+
// ─────────────────────────────────────────
|
|
59
|
+
// UTILITAIRE INTERNE
|
|
60
|
+
// ─────────────────────────────────────────
|
|
61
|
+
|
|
54
62
|
/**
|
|
55
|
-
*
|
|
56
|
-
*
|
|
57
|
-
*
|
|
58
|
-
* @
|
|
63
|
+
* Clone profond léger (JSON-safe).
|
|
64
|
+
* Pour des options contenant des fonctions (onClick…), on préfère
|
|
65
|
+
* une copie structurée qui ignore les fonctions non-sérialisables.
|
|
66
|
+
* @private
|
|
67
|
+
*/
|
|
68
|
+
static _deepClone(obj) {
|
|
69
|
+
try {
|
|
70
|
+
return JSON.parse(JSON.stringify(obj, (key, val) =>
|
|
71
|
+
typeof val === 'function' ? undefined : val
|
|
72
|
+
));
|
|
73
|
+
} catch {
|
|
74
|
+
return { ...obj };
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// ─────────────────────────────────────────
|
|
79
|
+
// SYSTÈME DE LISTENERS
|
|
80
|
+
// ─────────────────────────────────────────
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Ajoute un listener pour un événement.
|
|
84
|
+
* @param {string} event
|
|
85
|
+
* @param {Function} handler
|
|
86
|
+
* @returns {Component}
|
|
59
87
|
*/
|
|
60
88
|
on(event, handler) {
|
|
61
89
|
if (!this._listeners.has(event)) {
|
|
@@ -66,26 +94,23 @@ class Component {
|
|
|
66
94
|
}
|
|
67
95
|
|
|
68
96
|
/**
|
|
69
|
-
* Retire un listener
|
|
70
|
-
* @param {string} event
|
|
71
|
-
* @param {Function} handler
|
|
97
|
+
* Retire un listener.
|
|
98
|
+
* @param {string} event
|
|
99
|
+
* @param {Function} handler
|
|
72
100
|
* @returns {Component}
|
|
73
101
|
*/
|
|
74
102
|
off(event, handler) {
|
|
75
103
|
if (!this._listeners.has(event)) return this;
|
|
76
|
-
|
|
77
104
|
const handlers = this._listeners.get(event);
|
|
78
105
|
const index = handlers.indexOf(handler);
|
|
79
|
-
if (index > -1)
|
|
80
|
-
handlers.splice(index, 1);
|
|
81
|
-
}
|
|
106
|
+
if (index > -1) handlers.splice(index, 1);
|
|
82
107
|
return this;
|
|
83
108
|
}
|
|
84
109
|
|
|
85
110
|
/**
|
|
86
|
-
* Ajoute un listener qui s'exécute une seule fois
|
|
87
|
-
* @param {string} event
|
|
88
|
-
* @param {Function} handler
|
|
111
|
+
* Ajoute un listener qui s'exécute une seule fois.
|
|
112
|
+
* @param {string} event
|
|
113
|
+
* @param {Function} handler
|
|
89
114
|
* @returns {Component}
|
|
90
115
|
*/
|
|
91
116
|
once(event, handler) {
|
|
@@ -97,28 +122,26 @@ class Component {
|
|
|
97
122
|
}
|
|
98
123
|
|
|
99
124
|
/**
|
|
100
|
-
* Émet un événement
|
|
101
|
-
* @param {string} event
|
|
102
|
-
* @param {...any} args
|
|
125
|
+
* Émet un événement.
|
|
126
|
+
* @param {string} event
|
|
127
|
+
* @param {...any} args
|
|
103
128
|
* @returns {Component}
|
|
104
129
|
*/
|
|
105
130
|
emit(event, ...args) {
|
|
106
131
|
if (!this._listeners.has(event)) return this;
|
|
107
|
-
|
|
108
|
-
const handlers = this._listeners.get(event);
|
|
109
|
-
for (let handler of handlers) {
|
|
132
|
+
for (const handler of this._listeners.get(event)) {
|
|
110
133
|
try {
|
|
111
134
|
handler(...args);
|
|
112
135
|
} catch (error) {
|
|
113
|
-
console.error(`Error in ${event} handler:`, error);
|
|
136
|
+
console.error(`Error in "${event}" handler:`, error);
|
|
114
137
|
}
|
|
115
138
|
}
|
|
116
139
|
return this;
|
|
117
140
|
}
|
|
118
141
|
|
|
119
142
|
/**
|
|
120
|
-
* Retire tous les listeners d'un événement
|
|
121
|
-
* @param {string} [event]
|
|
143
|
+
* Retire tous les listeners (ou ceux d'un événement précis).
|
|
144
|
+
* @param {string} [event]
|
|
122
145
|
* @returns {Component}
|
|
123
146
|
*/
|
|
124
147
|
removeAllListeners(event) {
|
|
@@ -131,24 +154,23 @@ class Component {
|
|
|
131
154
|
}
|
|
132
155
|
|
|
133
156
|
/**
|
|
134
|
-
* Retourne le nombre de listeners pour un événement
|
|
135
|
-
* @param {string} event
|
|
157
|
+
* Retourne le nombre de listeners pour un événement.
|
|
158
|
+
* @param {string} event
|
|
136
159
|
* @returns {number}
|
|
137
160
|
*/
|
|
138
161
|
listenerCount(event) {
|
|
139
162
|
return this._listeners.has(event) ? this._listeners.get(event).length : 0;
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// ─────────────────────────────────────────
|
|
166
|
+
// LIFECYCLE HOOKS (à surcharger)
|
|
167
|
+
// ─────────────────────────────────────────
|
|
144
168
|
|
|
145
169
|
onMount() {}
|
|
146
170
|
onUnmount() {}
|
|
147
171
|
onUpdate(prevProps) {}
|
|
148
172
|
onResize(width, height) {}
|
|
149
173
|
|
|
150
|
-
/* ======================= */
|
|
151
|
-
|
|
152
174
|
_mount() {
|
|
153
175
|
if (!this._mounted) {
|
|
154
176
|
this._mounted = true;
|
|
@@ -165,7 +187,7 @@ class Component {
|
|
|
165
187
|
|
|
166
188
|
_update(newProps) {
|
|
167
189
|
this.onUpdate(this._prevProps);
|
|
168
|
-
this._prevProps =
|
|
190
|
+
this._prevProps = Component._deepClone(newProps);
|
|
169
191
|
this.markDirty();
|
|
170
192
|
}
|
|
171
193
|
|
|
@@ -176,25 +198,23 @@ class Component {
|
|
|
176
198
|
|
|
177
199
|
setProps(newProps = {}) {
|
|
178
200
|
const changed = Object.keys(newProps).some(
|
|
179
|
-
key => this[key] !== newProps[key]
|
|
201
|
+
(key) => this[key] !== newProps[key]
|
|
180
202
|
);
|
|
181
|
-
|
|
182
203
|
if (!changed) return;
|
|
183
|
-
|
|
184
204
|
Object.assign(this, newProps);
|
|
185
205
|
this._update(newProps);
|
|
186
206
|
}
|
|
187
207
|
|
|
188
208
|
measure(constraints) {
|
|
189
|
-
return {
|
|
190
|
-
width: this.width,
|
|
191
|
-
height: this.height
|
|
192
|
-
};
|
|
209
|
+
return { width: this.width, height: this.height };
|
|
193
210
|
}
|
|
194
211
|
|
|
212
|
+
// ─────────────────────────────────────────
|
|
213
|
+
// DIRTY / CLEAN
|
|
214
|
+
// ─────────────────────────────────────────
|
|
215
|
+
|
|
195
216
|
/**
|
|
196
|
-
* Marque le composant pour redessin
|
|
197
|
-
* Appelez cette méthode après avoir modifié une propriété
|
|
217
|
+
* Marque le composant pour redessin.
|
|
198
218
|
*/
|
|
199
219
|
markDirty() {
|
|
200
220
|
this._dirty = true;
|
|
@@ -203,41 +223,79 @@ class Component {
|
|
|
203
223
|
}
|
|
204
224
|
}
|
|
205
225
|
|
|
206
|
-
/**
|
|
207
|
-
* Marque le composant comme propre (appelé automatiquement après draw)
|
|
208
|
-
*/
|
|
226
|
+
/** Marque le composant comme propre (appelé après draw). */
|
|
209
227
|
markClean() {
|
|
210
228
|
this._dirty = false;
|
|
211
229
|
}
|
|
212
230
|
|
|
213
|
-
/**
|
|
214
|
-
* Vérifie si le composant est dirty
|
|
215
|
-
*/
|
|
231
|
+
/** Indique si le composant doit être redessiné. */
|
|
216
232
|
isDirty() {
|
|
217
233
|
return this._dirty;
|
|
218
234
|
}
|
|
219
235
|
|
|
236
|
+
// ─────────────────────────────────────────
|
|
237
|
+
// INTERACTION
|
|
238
|
+
// ─────────────────────────────────────────
|
|
239
|
+
|
|
220
240
|
/**
|
|
221
|
-
* Vérifie si un point est
|
|
241
|
+
* Vérifie si un point (x, y) est à l'intérieur du composant.
|
|
242
|
+
* @param {number} x
|
|
243
|
+
* @param {number} y
|
|
244
|
+
* @returns {boolean}
|
|
222
245
|
*/
|
|
223
246
|
isPointInside(x, y) {
|
|
224
|
-
return
|
|
225
|
-
|
|
247
|
+
return (
|
|
248
|
+
x >= this.x &&
|
|
249
|
+
x <= this.x + this.width &&
|
|
250
|
+
y >= this.y &&
|
|
251
|
+
y <= this.y + this.height
|
|
252
|
+
);
|
|
226
253
|
}
|
|
227
254
|
|
|
255
|
+
// ─────────────────────────────────────────
|
|
256
|
+
// DESSIN — À SURCHARGER OBLIGATOIREMENT
|
|
257
|
+
// ─────────────────────────────────────────
|
|
258
|
+
|
|
228
259
|
/**
|
|
229
|
-
*
|
|
260
|
+
* Dessine le composant sur le canvas.
|
|
261
|
+
* **Méthode abstraite** — doit être implémentée par chaque sous-classe.
|
|
262
|
+
*
|
|
263
|
+
* @abstract
|
|
264
|
+
* @param {CanvasRenderingContext2D} ctx - Contexte 2D du canvas
|
|
230
265
|
*/
|
|
231
266
|
draw(ctx) {
|
|
232
|
-
//
|
|
267
|
+
// Méthode abstraite — implémenter dans la sous-classe.
|
|
268
|
+
// Ne jamais appeler super.draw(ctx) depuis une sous-classe.
|
|
233
269
|
}
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
export default Component;
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
270
|
|
|
271
|
+
// ─────────────────────────────────────────
|
|
272
|
+
// DESTROY
|
|
273
|
+
// ─────────────────────────────────────────
|
|
242
274
|
|
|
275
|
+
/**
|
|
276
|
+
* Libère les ressources du composant.
|
|
277
|
+
*
|
|
278
|
+
* Les sous-classes qui utilisent setInterval, requestAnimationFrame,
|
|
279
|
+
* des EventListeners DOM ou d'autres ressources externes **doivent**
|
|
280
|
+
* surcharger cette méthode et appeler super.destroy() à la fin.
|
|
281
|
+
*
|
|
282
|
+
* Le flag `this._destroyed` est positionné à true AVANT les appels
|
|
283
|
+
* internes : les boucles d'animation doivent tester ce flag pour s'arrêter.
|
|
284
|
+
*
|
|
285
|
+
* @example
|
|
286
|
+
* destroy() {
|
|
287
|
+
* // annuler propre RAF ou timer ici
|
|
288
|
+
* cancelAnimationFrame(this._rafId);
|
|
289
|
+
* super.destroy();
|
|
290
|
+
* }
|
|
291
|
+
*/
|
|
292
|
+
destroy() {
|
|
293
|
+
this._destroyed = true;
|
|
294
|
+
this._unmount();
|
|
295
|
+
this.removeAllListeners();
|
|
296
|
+
this.onClick = null;
|
|
297
|
+
this.onPress = null;
|
|
298
|
+
}
|
|
299
|
+
}
|
|
243
300
|
|
|
301
|
+
export default Component;
|