canvasframework 0.5.18 → 0.5.19
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 +265 -0
- package/components/AndroidDatePickerDialog.js +406 -0
- package/components/AppBar.js +398 -0
- package/components/AudioPlayer.js +611 -0
- package/components/Avatar.js +202 -0
- package/components/Banner.js +342 -0
- package/components/BottomNavigationBar.js +433 -0
- package/components/BottomSheet.js +234 -0
- package/components/Button.js +358 -0
- package/components/Camera.js +644 -0
- package/components/Card.js +193 -0
- package/components/Chart.js +700 -0
- package/components/Checkbox.js +166 -0
- package/components/Chip.js +212 -0
- package/components/CircularProgress.js +327 -0
- package/components/ContextMenu.js +116 -0
- package/components/DatePicker.js +298 -0
- package/components/Dialog.js +337 -0
- package/components/Divider.js +125 -0
- package/components/Drawer.js +276 -0
- package/components/FAB.js +270 -0
- package/components/FileUpload.js +315 -0
- package/components/FloatedCamera.js +644 -0
- package/components/IOSDatePickerWheel.js +430 -0
- package/components/ImageCarousel.js +219 -0
- package/components/ImageComponent.js +223 -0
- package/components/Input.js +831 -0
- package/components/InputDatalist.js +723 -0
- package/components/InputTags.js +624 -0
- package/components/List.js +95 -0
- package/components/ListItem.js +269 -0
- package/components/Modal.js +364 -0
- package/components/MorphingFAB.js +428 -0
- package/components/MultiSelectDialog.js +206 -0
- package/components/NumberInput.js +271 -0
- package/components/PasswordInput.js +462 -0
- package/components/ProgressBar.js +88 -0
- package/components/QRCodeReader.js +539 -0
- package/components/RadioButton.js +151 -0
- package/components/SearchInput.js +315 -0
- package/components/SegmentedControl.js +357 -0
- package/components/Select.js +199 -0
- package/components/SelectDialog.js +255 -0
- package/components/Slider.js +113 -0
- package/components/SliverAppBar.js +139 -0
- package/components/Snackbar.js +243 -0
- package/components/SpeedDialFAB.js +397 -0
- package/components/Stepper.js +281 -0
- package/components/SwipeableListItem.js +327 -0
- package/components/Switch.js +147 -0
- package/components/Table.js +492 -0
- package/components/Tabs.js +423 -0
- package/components/Text.js +141 -0
- package/components/TextField.js +151 -0
- package/components/TimePicker.js +934 -0
- package/components/Toast.js +236 -0
- package/components/TreeView.js +420 -0
- package/components/Video.js +397 -0
- package/components/View.js +140 -0
- package/components/VirtualList.js +120 -0
- package/core/CanvasFramework.js +3045 -0
- package/core/Component.js +243 -0
- package/core/ThemeManager.js +358 -0
- package/core/UIBuilder.js +267 -0
- package/core/WebGLCanvasAdapter.js +782 -0
- package/features/Column.js +43 -0
- package/features/Grid.js +47 -0
- package/features/LayoutComponent.js +43 -0
- package/features/OpenStreetMap.js +310 -0
- package/features/Positioned.js +33 -0
- package/features/PullToRefresh.js +328 -0
- package/features/Row.js +40 -0
- package/features/SignaturePad.js +257 -0
- package/features/Skeleton.js +193 -0
- package/features/Stack.js +21 -0
- package/index.js +119 -0
- package/manager/AccessibilityManager.js +107 -0
- package/manager/ErrorHandler.js +59 -0
- package/manager/FeatureFlags.js +60 -0
- package/manager/MemoryManager.js +107 -0
- package/manager/PerformanceMonitor.js +84 -0
- package/manager/SecurityManager.js +54 -0
- package/package.json +22 -16
- package/utils/AnimationEngine.js +734 -0
- package/utils/CryptoManager.js +303 -0
- package/utils/DataStore.js +403 -0
- package/utils/DevTools.js +1618 -0
- package/utils/DevToolsConsole.js +201 -0
- package/utils/EventBus.js +407 -0
- package/utils/FetchClient.js +74 -0
- package/utils/FirebaseAuth.js +653 -0
- package/utils/FirebaseCore.js +246 -0
- package/utils/FirebaseFirestore.js +581 -0
- package/utils/FirebaseFunctions.js +97 -0
- package/utils/FirebaseRealtimeDB.js +498 -0
- package/utils/FirebaseStorage.js +612 -0
- package/utils/FormValidator.js +355 -0
- package/utils/GeoLocationService.js +62 -0
- package/utils/I18n.js +207 -0
- package/utils/IndexedDBManager.js +273 -0
- package/utils/InspectionOverlay.js +308 -0
- package/utils/NotificationManager.js +60 -0
- package/utils/OfflineSyncManager.js +342 -0
- package/utils/PayPalPayment.js +678 -0
- package/utils/QueryBuilder.js +478 -0
- package/utils/SafeArea.js +64 -0
- package/utils/SecureStorage.js +289 -0
- package/utils/StateManager.js +207 -0
- package/utils/StripePayment.js +552 -0
- package/utils/WebSocketClient.js +66 -0
- package/dist/canvasframework.js +0 -2
- package/dist/canvasframework.js.LICENSE.txt +0 -1
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
import Component from '../core/Component.js';
|
|
2
|
+
/**
|
|
3
|
+
* Avatar (photo de profil)
|
|
4
|
+
* @class
|
|
5
|
+
* @extends Component
|
|
6
|
+
* @property {string|null} imageUrl - URL de l'image
|
|
7
|
+
* @property {string} initials - Initiales
|
|
8
|
+
* @property {number} size - Taille
|
|
9
|
+
* @property {string} bgColor - Couleur de fond
|
|
10
|
+
* @property {string} textColor - Couleur du texte
|
|
11
|
+
* @property {number} borderWidth - Épaisseur de la bordure
|
|
12
|
+
* @property {string} borderColor - Couleur de la bordure
|
|
13
|
+
* @property {string|null} status - Statut ('online', 'offline', 'away', 'busy')
|
|
14
|
+
* @property {ImageComponent|null} imageComponent - Composant image interne
|
|
15
|
+
*/
|
|
16
|
+
class Avatar extends Component {
|
|
17
|
+
/**
|
|
18
|
+
* Crée une instance de Avatar
|
|
19
|
+
* @param {CanvasFramework} framework - Framework parent
|
|
20
|
+
* @param {Object} [options={}] - Options de configuration
|
|
21
|
+
* @param {string} [options.imageUrl] - URL de l'image
|
|
22
|
+
* @param {string} [options.initials='??'] - Initiales
|
|
23
|
+
* @param {number} [options.size=48] - Taille
|
|
24
|
+
* @param {string} [options.bgColor] - Couleur de fond (auto générée)
|
|
25
|
+
* @param {string} [options.textColor='#FFFFFF'] - Couleur du texte
|
|
26
|
+
* @param {number} [options.borderWidth=0] - Épaisseur de la bordure
|
|
27
|
+
* @param {string} [options.borderColor='#FFFFFF'] - Couleur de la bordure
|
|
28
|
+
* @param {string} [options.status] - Statut
|
|
29
|
+
*/
|
|
30
|
+
constructor(framework, options = {}) {
|
|
31
|
+
super(framework, options);
|
|
32
|
+
this.imageUrl = options.imageUrl || null;
|
|
33
|
+
this.initials = options.initials || '??';
|
|
34
|
+
this.size = options.size || 48;
|
|
35
|
+
this.bgColor = options.bgColor || this.generateColor(this.initials);
|
|
36
|
+
this.textColor = options.textColor || '#FFFFFF';
|
|
37
|
+
this.borderWidth = options.borderWidth || 0;
|
|
38
|
+
this.borderColor = options.borderColor || '#FFFFFF';
|
|
39
|
+
this.status = options.status || null; // 'online', 'offline', 'away', 'busy'
|
|
40
|
+
|
|
41
|
+
this.width = this.size;
|
|
42
|
+
this.height = this.size;
|
|
43
|
+
|
|
44
|
+
// Image component interne
|
|
45
|
+
this.imageComponent = null;
|
|
46
|
+
if (this.imageUrl) {
|
|
47
|
+
this.imageComponent = new ImageComponent(framework, {
|
|
48
|
+
x: this.x,
|
|
49
|
+
y: this.y,
|
|
50
|
+
width: this.size,
|
|
51
|
+
height: this.size,
|
|
52
|
+
src: this.imageUrl,
|
|
53
|
+
fit: 'cover',
|
|
54
|
+
borderRadius: this.size / 2
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Génère une couleur basée sur le texte
|
|
61
|
+
* @param {string} text - Texte pour générer la couleur
|
|
62
|
+
* @returns {string} Couleur hexadécimale
|
|
63
|
+
* @private
|
|
64
|
+
*/
|
|
65
|
+
generateColor(text) {
|
|
66
|
+
// Générer une couleur basée sur le texte (pour cohérence)
|
|
67
|
+
let hash = 0;
|
|
68
|
+
for (let i = 0; i < text.length; i++) {
|
|
69
|
+
hash = text.charCodeAt(i) + ((hash << 5) - hash);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const colors = [
|
|
73
|
+
'#E91E63', '#9C27B0', '#673AB7', '#3F51B5',
|
|
74
|
+
'#2196F3', '#00BCD4', '#009688', '#4CAF50',
|
|
75
|
+
'#FF9800', '#FF5722', '#795548', '#607D8B'
|
|
76
|
+
];
|
|
77
|
+
|
|
78
|
+
return colors[Math.abs(hash) % colors.length];
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Dessine l'avatar
|
|
83
|
+
* @param {CanvasRenderingContext2D} ctx - Contexte de dessin
|
|
84
|
+
*/
|
|
85
|
+
draw(ctx) {
|
|
86
|
+
ctx.save();
|
|
87
|
+
|
|
88
|
+
const centerX = this.x + this.size / 2;
|
|
89
|
+
const centerY = this.y + this.size / 2;
|
|
90
|
+
const radius = this.size / 2;
|
|
91
|
+
|
|
92
|
+
// Bordure
|
|
93
|
+
if (this.borderWidth > 0) {
|
|
94
|
+
ctx.fillStyle = this.borderColor;
|
|
95
|
+
ctx.beginPath();
|
|
96
|
+
ctx.arc(centerX, centerY, radius, 0, Math.PI * 2);
|
|
97
|
+
ctx.fill();
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Clipping circulaire
|
|
101
|
+
ctx.save();
|
|
102
|
+
ctx.beginPath();
|
|
103
|
+
ctx.arc(centerX, centerY, radius - this.borderWidth, 0, Math.PI * 2);
|
|
104
|
+
ctx.clip();
|
|
105
|
+
|
|
106
|
+
if (this.imageComponent && this.imageComponent.loaded) {
|
|
107
|
+
// Dessiner l'image
|
|
108
|
+
this.imageComponent.x = this.x + this.borderWidth;
|
|
109
|
+
this.imageComponent.y = this.y + this.borderWidth;
|
|
110
|
+
this.imageComponent.width = this.size - this.borderWidth * 2;
|
|
111
|
+
this.imageComponent.height = this.size - this.borderWidth * 2;
|
|
112
|
+
this.imageComponent.draw(ctx);
|
|
113
|
+
} else {
|
|
114
|
+
// Background coloré
|
|
115
|
+
ctx.fillStyle = this.bgColor;
|
|
116
|
+
ctx.fillRect(this.x, this.y, this.size, this.size);
|
|
117
|
+
|
|
118
|
+
// Initiales
|
|
119
|
+
ctx.fillStyle = this.textColor;
|
|
120
|
+
ctx.font = `bold ${this.size / 2.5}px -apple-system, sans-serif`;
|
|
121
|
+
ctx.textAlign = 'center';
|
|
122
|
+
ctx.textBaseline = 'middle';
|
|
123
|
+
ctx.fillText(this.initials.toUpperCase(), centerX, centerY);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
ctx.restore();
|
|
127
|
+
|
|
128
|
+
// Indicateur de statut
|
|
129
|
+
if (this.status) {
|
|
130
|
+
const statusSize = this.size / 4;
|
|
131
|
+
const statusX = this.x + this.size - statusSize;
|
|
132
|
+
const statusY = this.y + this.size - statusSize;
|
|
133
|
+
|
|
134
|
+
let statusColor;
|
|
135
|
+
switch (this.status) {
|
|
136
|
+
case 'online': statusColor = '#4CAF50'; break;
|
|
137
|
+
case 'offline': statusColor = '#9E9E9E'; break;
|
|
138
|
+
case 'away': statusColor = '#FF9800'; break;
|
|
139
|
+
case 'busy': statusColor = '#F44336'; break;
|
|
140
|
+
default: statusColor = '#9E9E9E';
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// Bordure blanche autour du statut
|
|
144
|
+
ctx.fillStyle = '#FFFFFF';
|
|
145
|
+
ctx.beginPath();
|
|
146
|
+
ctx.arc(statusX, statusY, statusSize / 2 + 1, 0, Math.PI * 2);
|
|
147
|
+
ctx.fill();
|
|
148
|
+
|
|
149
|
+
// Cercle de statut
|
|
150
|
+
ctx.fillStyle = statusColor;
|
|
151
|
+
ctx.beginPath();
|
|
152
|
+
ctx.arc(statusX, statusY, statusSize / 2, 0, Math.PI * 2);
|
|
153
|
+
ctx.fill();
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
ctx.restore();
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Change l'image de l'avatar
|
|
161
|
+
* @param {string} url - Nouvelle URL d'image
|
|
162
|
+
*/
|
|
163
|
+
setImage(url) {
|
|
164
|
+
this.imageUrl = url;
|
|
165
|
+
if (url) {
|
|
166
|
+
this.imageComponent = new ImageComponent(this.framework, {
|
|
167
|
+
x: this.x,
|
|
168
|
+
y: this.y,
|
|
169
|
+
width: this.size,
|
|
170
|
+
height: this.size,
|
|
171
|
+
src: url,
|
|
172
|
+
fit: 'cover',
|
|
173
|
+
borderRadius: this.size / 2
|
|
174
|
+
});
|
|
175
|
+
} else {
|
|
176
|
+
this.imageComponent = null;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Change le statut
|
|
182
|
+
* @param {string} status - Nouveau statut
|
|
183
|
+
*/
|
|
184
|
+
setStatus(status) {
|
|
185
|
+
this.status = status;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Vérifie si un point est dans les limites
|
|
190
|
+
* @param {number} x - Coordonnée X
|
|
191
|
+
* @param {number} y - Coordonnée Y
|
|
192
|
+
* @returns {boolean} True si le point est dans l'avatar
|
|
193
|
+
*/
|
|
194
|
+
isPointInside(x, y) {
|
|
195
|
+
const adjustedY = y - this.framework.scrollOffset;
|
|
196
|
+
const dx = x - (this.x + this.size / 2);
|
|
197
|
+
const dy = adjustedY - (this.y + this.size / 2);
|
|
198
|
+
return Math.sqrt(dx * dx + dy * dy) <= this.size / 2;
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
export default Avatar;
|
|
@@ -0,0 +1,342 @@
|
|
|
1
|
+
// components/Banner.js
|
|
2
|
+
import Component from '../core/Component.js';
|
|
3
|
+
|
|
4
|
+
export default class Banner extends Component {
|
|
5
|
+
constructor(framework, options = {}) {
|
|
6
|
+
super(framework, options);
|
|
7
|
+
|
|
8
|
+
this.text = options.text || '';
|
|
9
|
+
this.type = options.type || 'info';
|
|
10
|
+
this.actions = options.actions || [];
|
|
11
|
+
this.dismissible = options.dismissible === true;
|
|
12
|
+
|
|
13
|
+
this.platform = framework.platform || 'material';
|
|
14
|
+
|
|
15
|
+
this.width = options.width || framework.width || window.innerWidth;
|
|
16
|
+
this.height = options.height || 64;
|
|
17
|
+
this.x = options.x || 0;
|
|
18
|
+
this.y = options.y || 0;
|
|
19
|
+
|
|
20
|
+
this.visible = options.visible !== false;
|
|
21
|
+
this.progress = this.visible ? 1 : 0;
|
|
22
|
+
this.animSpeed = 0.18;
|
|
23
|
+
|
|
24
|
+
this._lastUpdate = performance.now();
|
|
25
|
+
this._colors = this._resolveColors();
|
|
26
|
+
|
|
27
|
+
// Bounds calculées à chaque frame
|
|
28
|
+
this._actionBounds = [];
|
|
29
|
+
this._dismissBounds = null;
|
|
30
|
+
|
|
31
|
+
// Pour indiquer qu'on gère nos propres clics
|
|
32
|
+
this.selfManagedClicks = true;
|
|
33
|
+
|
|
34
|
+
// Écouter les événements directement sur le canvas
|
|
35
|
+
this._setupEventListeners();
|
|
36
|
+
|
|
37
|
+
// Ref si fourni
|
|
38
|
+
if (options.ref) options.ref.current = this;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/* ===================== Setup ===================== */
|
|
42
|
+
_setupEventListeners() {
|
|
43
|
+
// Stocker les références pour pouvoir les retirer plus tard
|
|
44
|
+
this._boundHandleClick = this._handleClick.bind(this);
|
|
45
|
+
|
|
46
|
+
// Écouter les événements sur le canvas parent
|
|
47
|
+
if (this.framework && this.framework.canvas) {
|
|
48
|
+
this.framework.canvas.addEventListener('click', this._boundHandleClick);
|
|
49
|
+
this.framework.canvas.addEventListener('touchend', this._boundHandleClick);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
_removeEventListeners() {
|
|
54
|
+
if (this.framework && this.framework.canvas && this._boundHandleClick) {
|
|
55
|
+
this.framework.canvas.removeEventListener('click', this._boundHandleClick);
|
|
56
|
+
this.framework.canvas.removeEventListener('touchend', this._boundHandleClick);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/* ===================== Lifecycle ===================== */
|
|
61
|
+
onMount() {
|
|
62
|
+
this._setupEventListeners();
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
onUnmount() {
|
|
66
|
+
this._removeEventListeners();
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/* ===================== Colors ===================== */
|
|
70
|
+
_resolveColors() {
|
|
71
|
+
if (this.platform === 'cupertino') {
|
|
72
|
+
return {
|
|
73
|
+
bg: 'rgba(250,250,250,0.95)',
|
|
74
|
+
fg: '#000',
|
|
75
|
+
accent: '#007AFF',
|
|
76
|
+
divider: 'rgba(60,60,67,0.15)'
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Material v3
|
|
81
|
+
const map = {
|
|
82
|
+
info: '#E8F0FE',
|
|
83
|
+
success: '#E6F4EA',
|
|
84
|
+
warning: '#FEF7E0',
|
|
85
|
+
error: '#FCE8E6'
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
return {
|
|
89
|
+
bg: map[this.type] || map.info,
|
|
90
|
+
fg: '#1F1F1F',
|
|
91
|
+
accent: '#1A73E8'
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/* ===================== Show/Hide ===================== */
|
|
96
|
+
show() {
|
|
97
|
+
this.visible = true;
|
|
98
|
+
this.markDirty();
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
hide() {
|
|
102
|
+
this.visible = false;
|
|
103
|
+
this.markDirty();
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/* ===================== Update ===================== */
|
|
107
|
+
update() {
|
|
108
|
+
const now = performance.now();
|
|
109
|
+
const dt = Math.min((now - this._lastUpdate) / 16.6, 3);
|
|
110
|
+
|
|
111
|
+
const target = this.visible ? 1 : 0;
|
|
112
|
+
this.progress += (target - this.progress) * this.animSpeed * dt;
|
|
113
|
+
this.progress = Math.max(0, Math.min(1, this.progress));
|
|
114
|
+
|
|
115
|
+
if (Math.abs(target - this.progress) > 0.01) this.markDirty();
|
|
116
|
+
|
|
117
|
+
this._lastUpdate = now;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/* ===================== Draw ===================== */
|
|
121
|
+
draw(ctx) {
|
|
122
|
+
this.update();
|
|
123
|
+
if (this.progress <= 0.01) return;
|
|
124
|
+
|
|
125
|
+
const h = this.height * this.progress;
|
|
126
|
+
const visibleHeight = h;
|
|
127
|
+
|
|
128
|
+
ctx.save();
|
|
129
|
+
|
|
130
|
+
// Background
|
|
131
|
+
if (this.platform === 'material') {
|
|
132
|
+
ctx.shadowColor = 'rgba(0,0,0,0.18)';
|
|
133
|
+
ctx.shadowBlur = 8;
|
|
134
|
+
ctx.shadowOffsetY = 2;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
ctx.fillStyle = this._colors.bg;
|
|
138
|
+
ctx.fillRect(this.x, this.y, this.width, visibleHeight);
|
|
139
|
+
ctx.shadowColor = 'transparent';
|
|
140
|
+
|
|
141
|
+
// Divider iOS
|
|
142
|
+
if (this.platform === 'cupertino') {
|
|
143
|
+
ctx.strokeStyle = this._colors.divider;
|
|
144
|
+
ctx.beginPath();
|
|
145
|
+
ctx.moveTo(this.x, this.y + visibleHeight);
|
|
146
|
+
ctx.lineTo(this.x + this.width, this.y + visibleHeight);
|
|
147
|
+
ctx.stroke();
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// Text
|
|
151
|
+
ctx.fillStyle = this._colors.fg;
|
|
152
|
+
ctx.font =
|
|
153
|
+
this.platform === 'cupertino'
|
|
154
|
+
? '400 15px -apple-system'
|
|
155
|
+
: '400 14px Roboto, sans-serif';
|
|
156
|
+
ctx.textBaseline = 'middle';
|
|
157
|
+
ctx.textAlign = 'left';
|
|
158
|
+
ctx.fillText(this.text, this.x + 16, this.y + visibleHeight / 2);
|
|
159
|
+
|
|
160
|
+
// Actions - calculer et stocker les bounds
|
|
161
|
+
this._actionBounds = [];
|
|
162
|
+
let x = this.width - 16;
|
|
163
|
+
|
|
164
|
+
for (let i = this.actions.length - 1; i >= 0; i--) {
|
|
165
|
+
const action = this.actions[i];
|
|
166
|
+
const textWidth = ctx.measureText(action.label).width + 20;
|
|
167
|
+
x -= textWidth;
|
|
168
|
+
|
|
169
|
+
ctx.fillStyle = this._colors.accent;
|
|
170
|
+
ctx.textAlign = 'center';
|
|
171
|
+
ctx.textBaseline = 'middle';
|
|
172
|
+
ctx.fillText(action.label, this.x + x + textWidth / 2, this.y + visibleHeight / 2);
|
|
173
|
+
|
|
174
|
+
// Stocker la hitbox (en coordonnées écran, pas canvas)
|
|
175
|
+
this._actionBounds.push({
|
|
176
|
+
action: action,
|
|
177
|
+
bounds: {
|
|
178
|
+
x: this.x + x,
|
|
179
|
+
y: this.y + (visibleHeight - 44) / 2,
|
|
180
|
+
w: textWidth,
|
|
181
|
+
h: 44
|
|
182
|
+
}
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
x -= 12;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// Dismiss button
|
|
189
|
+
if (this.dismissible) {
|
|
190
|
+
const hitSize = 44;
|
|
191
|
+
const cx = this.width - 28;
|
|
192
|
+
const cy = this.y + visibleHeight / 2;
|
|
193
|
+
|
|
194
|
+
ctx.fillStyle =
|
|
195
|
+
this.platform === 'cupertino'
|
|
196
|
+
? 'rgba(60,60,67,0.6)'
|
|
197
|
+
: this._colors.fg;
|
|
198
|
+
|
|
199
|
+
ctx.font =
|
|
200
|
+
this.platform === 'cupertino'
|
|
201
|
+
? '600 16px -apple-system'
|
|
202
|
+
: '500 16px Roboto';
|
|
203
|
+
ctx.textAlign = 'center';
|
|
204
|
+
ctx.textBaseline = 'middle';
|
|
205
|
+
ctx.fillText('×', cx, cy);
|
|
206
|
+
|
|
207
|
+
this._dismissBounds = {
|
|
208
|
+
x: cx - hitSize / 2,
|
|
209
|
+
y: cy - hitSize / 2,
|
|
210
|
+
w: hitSize,
|
|
211
|
+
h: hitSize
|
|
212
|
+
};
|
|
213
|
+
} else {
|
|
214
|
+
this._dismissBounds = null;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
ctx.restore();
|
|
218
|
+
|
|
219
|
+
// DEBUG: Dessiner les hitboxes
|
|
220
|
+
if (this.framework && this.framework.debbug) {
|
|
221
|
+
this._drawDebugHitboxes(ctx);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/* ===================== Debug ===================== */
|
|
226
|
+
_drawDebugHitboxes(ctx) {
|
|
227
|
+
ctx.save();
|
|
228
|
+
ctx.strokeStyle = 'red';
|
|
229
|
+
ctx.lineWidth = 1;
|
|
230
|
+
ctx.fillStyle = 'rgba(255, 0, 0, 0.1)';
|
|
231
|
+
|
|
232
|
+
// Dessiner la hitbox principale du banner
|
|
233
|
+
const h = this.height * this.progress;
|
|
234
|
+
ctx.strokeRect(this.x, this.y, this.width, h);
|
|
235
|
+
|
|
236
|
+
// Dessiner les hitboxes des actions
|
|
237
|
+
if (this._actionBounds && this._actionBounds.length > 0) {
|
|
238
|
+
for (const item of this._actionBounds) {
|
|
239
|
+
const b = item.bounds;
|
|
240
|
+
ctx.fillRect(b.x, b.y, b.w, b.h);
|
|
241
|
+
ctx.strokeRect(b.x, b.y, b.w, b.h);
|
|
242
|
+
|
|
243
|
+
// Texte de debug
|
|
244
|
+
ctx.fillStyle = 'red';
|
|
245
|
+
ctx.font = '10px monospace';
|
|
246
|
+
ctx.fillText(item.action.label, b.x + 5, b.y + 12);
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// Dessiner la hitbox du dismiss button
|
|
251
|
+
if (this._dismissBounds) {
|
|
252
|
+
const b = this._dismissBounds;
|
|
253
|
+
ctx.fillRect(b.x, b.y, b.w, b.h);
|
|
254
|
+
ctx.strokeRect(b.x, b.y, b.w, b.h);
|
|
255
|
+
ctx.fillText('X', b.x + 5, b.y + 12);
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
ctx.restore();
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
/* ===================== Click Handling ===================== */
|
|
262
|
+
_handleClick(event) {
|
|
263
|
+
if (this.progress < 0.95) return;
|
|
264
|
+
|
|
265
|
+
// Obtenir les coordonnées du clic/touch
|
|
266
|
+
let clientX, clientY;
|
|
267
|
+
|
|
268
|
+
if (event.type === 'touchend') {
|
|
269
|
+
const touch = event.changedTouches[0];
|
|
270
|
+
clientX = touch.clientX;
|
|
271
|
+
clientY = touch.clientY;
|
|
272
|
+
} else {
|
|
273
|
+
clientX = event.clientX;
|
|
274
|
+
clientY = event.clientY;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
// Convertir en coordonnées canvas SIMPLIFIÉ
|
|
278
|
+
const canvasRect = this.framework.canvas.getBoundingClientRect();
|
|
279
|
+
|
|
280
|
+
// Coordonnées relatives au canvas (en pixels CSS, pas en pixels canvas)
|
|
281
|
+
const x = clientX - canvasRect.left;
|
|
282
|
+
const y = clientY - canvasRect.top;
|
|
283
|
+
|
|
284
|
+
console.log('Click converted:', {
|
|
285
|
+
clientX, clientY,
|
|
286
|
+
canvasLeft: canvasRect.left,
|
|
287
|
+
canvasTop: canvasRect.top,
|
|
288
|
+
x, y,
|
|
289
|
+
bannerX: this.x,
|
|
290
|
+
bannerY: this.y,
|
|
291
|
+
bannerWidth: this.width,
|
|
292
|
+
bannerHeight: this.height * this.progress
|
|
293
|
+
});
|
|
294
|
+
|
|
295
|
+
// Vérifier si on clique sur le banner (en coordonnées CSS)
|
|
296
|
+
const bannerBottom = this.y + (this.height * this.progress);
|
|
297
|
+
if (x < this.x || x > this.x + this.width || y < this.y || y > bannerBottom) {
|
|
298
|
+
console.log('Click outside banner');
|
|
299
|
+
return;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
console.log('Click INSIDE banner!');
|
|
303
|
+
|
|
304
|
+
// Empêcher la propagation
|
|
305
|
+
event.stopPropagation();
|
|
306
|
+
|
|
307
|
+
// 1️⃣ Dismiss button
|
|
308
|
+
if (this.dismissible && this._dismissBounds) {
|
|
309
|
+
const b = this._dismissBounds;
|
|
310
|
+
console.log('Checking dismiss bounds:', b, 'click:', {x, y});
|
|
311
|
+
if (x >= b.x && x <= b.x + b.w &&
|
|
312
|
+
y >= b.y && y <= b.y + b.h) {
|
|
313
|
+
console.log('Dismiss clicked!');
|
|
314
|
+
this.hide();
|
|
315
|
+
return;
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
// 2️⃣ Actions
|
|
320
|
+
if (this._actionBounds && this._actionBounds.length > 0) {
|
|
321
|
+
console.log('Checking', this._actionBounds.length, 'action bounds');
|
|
322
|
+
for (const item of this._actionBounds) {
|
|
323
|
+
const b = item.bounds;
|
|
324
|
+
console.log('Checking action:', item.action.label, 'bounds:', b);
|
|
325
|
+
if (x >= b.x && x <= b.x + b.w &&
|
|
326
|
+
y >= b.y && y <= b.y + b.h) {
|
|
327
|
+
console.log('Action clicked:', item.action.label);
|
|
328
|
+
item.action.onClick?.();
|
|
329
|
+
return;
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
console.log('Click on banner but not on any button');
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
/* ===================== Resize ===================== */
|
|
338
|
+
_resize(width) {
|
|
339
|
+
this.width = width;
|
|
340
|
+
this.markDirty();
|
|
341
|
+
}
|
|
342
|
+
}
|