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.
Files changed (112) hide show
  1. package/components/Accordion.js +265 -0
  2. package/components/AndroidDatePickerDialog.js +406 -0
  3. package/components/AppBar.js +398 -0
  4. package/components/AudioPlayer.js +611 -0
  5. package/components/Avatar.js +202 -0
  6. package/components/Banner.js +342 -0
  7. package/components/BottomNavigationBar.js +433 -0
  8. package/components/BottomSheet.js +234 -0
  9. package/components/Button.js +358 -0
  10. package/components/Camera.js +644 -0
  11. package/components/Card.js +193 -0
  12. package/components/Chart.js +700 -0
  13. package/components/Checkbox.js +166 -0
  14. package/components/Chip.js +212 -0
  15. package/components/CircularProgress.js +327 -0
  16. package/components/ContextMenu.js +116 -0
  17. package/components/DatePicker.js +298 -0
  18. package/components/Dialog.js +337 -0
  19. package/components/Divider.js +125 -0
  20. package/components/Drawer.js +276 -0
  21. package/components/FAB.js +270 -0
  22. package/components/FileUpload.js +315 -0
  23. package/components/FloatedCamera.js +644 -0
  24. package/components/IOSDatePickerWheel.js +430 -0
  25. package/components/ImageCarousel.js +219 -0
  26. package/components/ImageComponent.js +223 -0
  27. package/components/Input.js +831 -0
  28. package/components/InputDatalist.js +723 -0
  29. package/components/InputTags.js +624 -0
  30. package/components/List.js +95 -0
  31. package/components/ListItem.js +269 -0
  32. package/components/Modal.js +364 -0
  33. package/components/MorphingFAB.js +428 -0
  34. package/components/MultiSelectDialog.js +206 -0
  35. package/components/NumberInput.js +271 -0
  36. package/components/PasswordInput.js +462 -0
  37. package/components/ProgressBar.js +88 -0
  38. package/components/QRCodeReader.js +539 -0
  39. package/components/RadioButton.js +151 -0
  40. package/components/SearchInput.js +315 -0
  41. package/components/SegmentedControl.js +357 -0
  42. package/components/Select.js +199 -0
  43. package/components/SelectDialog.js +255 -0
  44. package/components/Slider.js +113 -0
  45. package/components/SliverAppBar.js +139 -0
  46. package/components/Snackbar.js +243 -0
  47. package/components/SpeedDialFAB.js +397 -0
  48. package/components/Stepper.js +281 -0
  49. package/components/SwipeableListItem.js +327 -0
  50. package/components/Switch.js +147 -0
  51. package/components/Table.js +492 -0
  52. package/components/Tabs.js +423 -0
  53. package/components/Text.js +141 -0
  54. package/components/TextField.js +151 -0
  55. package/components/TimePicker.js +934 -0
  56. package/components/Toast.js +236 -0
  57. package/components/TreeView.js +420 -0
  58. package/components/Video.js +397 -0
  59. package/components/View.js +140 -0
  60. package/components/VirtualList.js +120 -0
  61. package/core/CanvasFramework.js +3045 -0
  62. package/core/Component.js +243 -0
  63. package/core/ThemeManager.js +358 -0
  64. package/core/UIBuilder.js +267 -0
  65. package/core/WebGLCanvasAdapter.js +782 -0
  66. package/features/Column.js +43 -0
  67. package/features/Grid.js +47 -0
  68. package/features/LayoutComponent.js +43 -0
  69. package/features/OpenStreetMap.js +310 -0
  70. package/features/Positioned.js +33 -0
  71. package/features/PullToRefresh.js +328 -0
  72. package/features/Row.js +40 -0
  73. package/features/SignaturePad.js +257 -0
  74. package/features/Skeleton.js +193 -0
  75. package/features/Stack.js +21 -0
  76. package/index.js +119 -0
  77. package/manager/AccessibilityManager.js +107 -0
  78. package/manager/ErrorHandler.js +59 -0
  79. package/manager/FeatureFlags.js +60 -0
  80. package/manager/MemoryManager.js +107 -0
  81. package/manager/PerformanceMonitor.js +84 -0
  82. package/manager/SecurityManager.js +54 -0
  83. package/package.json +22 -16
  84. package/utils/AnimationEngine.js +734 -0
  85. package/utils/CryptoManager.js +303 -0
  86. package/utils/DataStore.js +403 -0
  87. package/utils/DevTools.js +1618 -0
  88. package/utils/DevToolsConsole.js +201 -0
  89. package/utils/EventBus.js +407 -0
  90. package/utils/FetchClient.js +74 -0
  91. package/utils/FirebaseAuth.js +653 -0
  92. package/utils/FirebaseCore.js +246 -0
  93. package/utils/FirebaseFirestore.js +581 -0
  94. package/utils/FirebaseFunctions.js +97 -0
  95. package/utils/FirebaseRealtimeDB.js +498 -0
  96. package/utils/FirebaseStorage.js +612 -0
  97. package/utils/FormValidator.js +355 -0
  98. package/utils/GeoLocationService.js +62 -0
  99. package/utils/I18n.js +207 -0
  100. package/utils/IndexedDBManager.js +273 -0
  101. package/utils/InspectionOverlay.js +308 -0
  102. package/utils/NotificationManager.js +60 -0
  103. package/utils/OfflineSyncManager.js +342 -0
  104. package/utils/PayPalPayment.js +678 -0
  105. package/utils/QueryBuilder.js +478 -0
  106. package/utils/SafeArea.js +64 -0
  107. package/utils/SecureStorage.js +289 -0
  108. package/utils/StateManager.js +207 -0
  109. package/utils/StripePayment.js +552 -0
  110. package/utils/WebSocketClient.js +66 -0
  111. package/dist/canvasframework.js +0 -2
  112. 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
+ }