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,199 @@
1
+ import Component from '../core/Component.js';
2
+ import SelectDialog from '../components/SelectDialog.js';
3
+
4
+ /**
5
+ * Composant de sélection déroulante (dropdown)
6
+ * @class
7
+ * @extends Component
8
+ * @param {Framework} framework - Instance du framework
9
+ * @param {Object} [options={}] - Options de configuration
10
+ * @param {string[]} [options.options=[]] - Liste des options
11
+ * @param {number} [options.selectedIndex=0] - Index de l'option sélectionnée
12
+ * @param {string} [options.placeholder='Select...'] - Texte par défaut
13
+ * @param {number} [options.fontSize=16] - Taille de police
14
+ * @param {Function} [options.onChange] - Callback lors du changement de sélection
15
+ * @example
16
+ * const select = new Select(framework, {
17
+ * options: ['Option 1', 'Option 2', 'Option 3'],
18
+ * placeholder: 'Choisissez une option',
19
+ * onChange: (value, index) => console.log('Selected:', value)
20
+ * });
21
+ */
22
+ class Select extends Component {
23
+ /**
24
+ * @constructs Select
25
+ */
26
+ constructor(framework, options = {}) {
27
+ super(framework, options);
28
+ /** @type {string[]} */
29
+ this.options = options.options || [];
30
+ /** @type {number} */
31
+ this.selectedIndex = options.selectedIndex || 0;
32
+ /** @type {string} */
33
+ this.placeholder = options.placeholder || 'Select...';
34
+ /** @type {string} */
35
+ this.platform = framework.platform;
36
+ /** @type {number} */
37
+ this.fontSize = options.fontSize || 16;
38
+ /** @type {Function|undefined} */
39
+ this.onChange = options.onChange;
40
+ /** @type {boolean} */
41
+ this.isOpen = false;
42
+ /** @type {SelectDialog|null} */
43
+ this.dialog = null;
44
+
45
+ // Définir onClick pour le Select
46
+ this.onClick = this.toggleMenu.bind(this);
47
+ }
48
+
49
+ /**
50
+ * Ouvre ou ferme le menu de sélection
51
+ */
52
+ toggleMenu() {
53
+ if (this.isOpen && this.dialog) {
54
+ this.closeMenu();
55
+ return;
56
+ }
57
+
58
+ this.openMenu();
59
+ }
60
+
61
+ /**
62
+ * Ouvre le menu de sélection (affiche le modal)
63
+ */
64
+ openMenu() {
65
+ if (this.isOpen) return;
66
+
67
+ this.dialog = new SelectDialog(this.framework, {
68
+ title: this.placeholder,
69
+ options: this.options,
70
+ selectedIndex: this.selectedIndex,
71
+ onSelect: (index, value) => {
72
+ this.selectedIndex = index;
73
+ if (this.onChange) {
74
+ this.onChange(value, index);
75
+ }
76
+ this.closeMenu();
77
+ }
78
+ });
79
+
80
+ this.framework.add(this.dialog);
81
+ this.isOpen = true;
82
+ }
83
+
84
+ /**
85
+ * Ferme le menu de sélection
86
+ */
87
+ closeMenu() {
88
+ if (this.dialog) {
89
+ this.dialog.hide();
90
+ this.dialog = null;
91
+ }
92
+ this.isOpen = false;
93
+ }
94
+
95
+ /**
96
+ * Dessine le composant Select
97
+ * @param {CanvasRenderingContext2D} ctx - Contexte de dessin
98
+ */
99
+ draw(ctx) {
100
+ ctx.save();
101
+
102
+ const selectedValue = this.options[this.selectedIndex] || this.placeholder;
103
+
104
+ if (this.platform === 'material') {
105
+ // Material Design Select
106
+ ctx.fillStyle = this.pressed ? '#F5F5F5' : '#FFFFFF';
107
+ ctx.fillRect(this.x, this.y, this.width, this.height);
108
+
109
+ ctx.strokeStyle = this.isOpen ? '#6200EE' : '#CCCCCC';
110
+ ctx.lineWidth = this.isOpen ? 2 : 1;
111
+ ctx.strokeRect(this.x, this.y, this.width, this.height);
112
+
113
+ // Texte
114
+ ctx.fillStyle = selectedValue === this.placeholder ? '#999999' : '#000000';
115
+ ctx.font = `${this.fontSize}px Roboto, sans-serif`;
116
+ ctx.textAlign = 'left';
117
+ ctx.textBaseline = 'middle';
118
+ ctx.fillText(selectedValue, this.x + 15, this.y + this.height / 2);
119
+
120
+ // Flèche
121
+ ctx.fillStyle = '#666666';
122
+ const arrowX = this.x + this.width - 20;
123
+ const arrowY = this.y + this.height / 2;
124
+ ctx.beginPath();
125
+ ctx.moveTo(arrowX - 5, arrowY - 3);
126
+ ctx.lineTo(arrowX + 5, arrowY - 3);
127
+ ctx.lineTo(arrowX, arrowY + 3);
128
+ ctx.closePath();
129
+ ctx.fill();
130
+ } else {
131
+ // Cupertino Select
132
+ ctx.fillStyle = '#FFFFFF';
133
+ ctx.beginPath();
134
+ this.roundRect(ctx, this.x, this.y, this.width, this.height, 8);
135
+ ctx.fill();
136
+
137
+ ctx.strokeStyle = this.isOpen ? '#007AFF' : '#C7C7CC';
138
+ ctx.lineWidth = 1;
139
+ ctx.stroke();
140
+
141
+ // Texte
142
+ ctx.fillStyle = selectedValue === this.placeholder ? '#999999' : '#000000';
143
+ ctx.font = `${this.fontSize}px -apple-system, sans-serif`;
144
+ ctx.textAlign = 'left';
145
+ ctx.textBaseline = 'middle';
146
+ ctx.fillText(selectedValue, this.x + 15, this.y + this.height / 2);
147
+
148
+ // Chevron
149
+ ctx.strokeStyle = '#007AFF';
150
+ ctx.lineWidth = 2;
151
+ const chevronX = this.x + this.width - 20;
152
+ const chevronY = this.y + this.height / 2;
153
+ ctx.beginPath();
154
+ ctx.moveTo(chevronX - 5, chevronY - 3);
155
+ ctx.lineTo(chevronX, chevronY + 2);
156
+ ctx.lineTo(chevronX + 5, chevronY - 3);
157
+ ctx.stroke();
158
+ }
159
+
160
+ ctx.restore();
161
+ }
162
+
163
+ /**
164
+ * Dessine un rectangle avec des coins arrondis
165
+ * @param {CanvasRenderingContext2D} ctx - Contexte de dessin
166
+ * @param {number} x - Position X
167
+ * @param {number} y - Position Y
168
+ * @param {number} width - Largeur
169
+ * @param {number} height - Hauteur
170
+ * @param {number} radius - Rayon des coins
171
+ * @private
172
+ */
173
+ roundRect(ctx, x, y, width, height, radius) {
174
+ ctx.moveTo(x + radius, y);
175
+ ctx.lineTo(x + width - radius, y);
176
+ ctx.quadraticCurveTo(x + width, y, x + width, y + radius);
177
+ ctx.lineTo(x + width, y + height - radius);
178
+ ctx.quadraticCurveTo(x + width, y + height, x + width - radius, y + height);
179
+ ctx.lineTo(x + radius, y + height);
180
+ ctx.quadraticCurveTo(x, y + height, x, y + height - radius);
181
+ ctx.lineTo(x, y + radius);
182
+ ctx.quadraticCurveTo(x, y, x + radius, y);
183
+ }
184
+
185
+ /**
186
+ * Vérifie si un point est à l'intérieur du composant
187
+ * @param {number} x - Position X
188
+ * @param {number} y - Position Y
189
+ * @returns {boolean} True si le point est à l'intérieur
190
+ */
191
+ isPointInside(x, y) {
192
+ return x >= this.x &&
193
+ x <= this.x + this.width &&
194
+ y >= this.y &&
195
+ y <= this.y + this.height;
196
+ }
197
+ }
198
+
199
+ export default Select;
@@ -0,0 +1,255 @@
1
+ import Modal from '../components/Modal.js';
2
+ /**
3
+ * Modal pour la sélection d'une option parmi une liste
4
+ * @class
5
+ * @extends Modal
6
+ * @param {Framework} framework - Instance du framework
7
+ * @param {Object} [options={}] - Options de configuration
8
+ * @param {string} [options.title='Sélectionner une option'] - Titre du modal
9
+ * @param {string[]} [options.options=[]] - Liste des options
10
+ * @param {number} [options.selectedIndex=0] - Index de l'option sélectionnée par défaut
11
+ * @param {Function} [options.onSelect] - Callback lors de la sélection
12
+ * @example
13
+ * const dialog = new SelectDialog(framework, {
14
+ * title: 'Choisir une couleur',
15
+ * options: ['Rouge', 'Vert', 'Bleu'],
16
+ * selectedIndex: 1,
17
+ * onSelect: (index, value) => console.log('Selected:', value)
18
+ * });
19
+ */
20
+ class SelectDialog extends Modal {
21
+ /**
22
+ * @constructs SelectDialog
23
+ */
24
+ constructor(framework, options = {}) {
25
+ // Calculer la hauteur en fonction du nombre d'options
26
+ const optionsCount = options.options?.length || 0;
27
+ const itemHeight = 50;
28
+ const dialogHeight = Math.min(
29
+ 400, // Hauteur max
30
+ Math.max(200, optionsCount * itemHeight + 100) // Hauteur min + espace pour titre
31
+ );
32
+
33
+ // Appeler le constructeur parent
34
+ super(framework, {
35
+ title: options.title || 'Sélectionner une option',
36
+ width: Math.min(350, framework.width - 40),
37
+ height: dialogHeight,
38
+ showCloseButton: true,
39
+ closeOnOverlayClick: true,
40
+ padding: 0, // Pas de padding, on gère nous-même
41
+ ...options
42
+ });
43
+
44
+ /** @type {string[]} */
45
+ this.options = options.options || [];
46
+ /** @type {number} */
47
+ this.selectedIndex = options.selectedIndex || 0;
48
+ /** @type {Function|undefined} */
49
+ this.onSelect = options.onSelect;
50
+ /** @type {number} */
51
+ this.itemHeight = itemHeight;
52
+ /** @type {number} */
53
+ this.hoveredIndex = -1;
54
+
55
+ // Désactiver les animations
56
+ this.opacity = 1;
57
+ this.scale = 1;
58
+ this.isVisible = true;
59
+ this.visible = true;
60
+
61
+ // AJOUTER: Définir onPress et onMove pour que le framework les appelle
62
+ this.onPress = this.handlePress.bind(this);
63
+ this.onMove = this.handleMove.bind(this);
64
+ }
65
+
66
+ /**
67
+ * Dessine le modal de sélection
68
+ * @param {CanvasRenderingContext2D} ctx - Contexte de dessin
69
+ */
70
+ draw(ctx) {
71
+ if (!this.isVisible) return;
72
+
73
+ ctx.save();
74
+ ctx.globalAlpha = this.opacity;
75
+
76
+ // Overlay sombre
77
+ ctx.fillStyle = 'rgba(0, 0, 0, 0.5)';
78
+ ctx.fillRect(0, 0, this.framework.width, this.framework.height);
79
+
80
+ // Calculer la position du modal (centré)
81
+ const modalX = (this.framework.width - this.modalWidth) / 2;
82
+ const modalY = (this.framework.height - this.modalHeight) / 2;
83
+
84
+ // Fond du modal
85
+ ctx.fillStyle = '#FFFFFF';
86
+ ctx.shadowColor = 'rgba(0, 0, 0, 0.3)';
87
+ ctx.shadowBlur = 20;
88
+ ctx.shadowOffsetY = 10;
89
+
90
+ ctx.beginPath();
91
+ this.roundRect(ctx, modalX, modalY, this.modalWidth, this.modalHeight, 12);
92
+ ctx.fill();
93
+
94
+ ctx.shadowColor = 'transparent';
95
+
96
+ // Titre
97
+ if (this.title) {
98
+ ctx.fillStyle = '#000000';
99
+ ctx.font = 'bold 18px -apple-system, sans-serif';
100
+ ctx.textAlign = 'center';
101
+ ctx.textBaseline = 'middle';
102
+ ctx.fillText(this.title, modalX + this.modalWidth / 2, modalY + 30);
103
+
104
+ // Ligne de séparation sous le titre
105
+ ctx.strokeStyle = '#E0E0E0';
106
+ ctx.lineWidth = 1;
107
+ ctx.beginPath();
108
+ ctx.moveTo(modalX, modalY + 50);
109
+ ctx.lineTo(modalX + this.modalWidth, modalY + 50);
110
+ ctx.stroke();
111
+ }
112
+
113
+ // Zone de contenu (avec scroll si nécessaire)
114
+ const contentX = modalX;
115
+ const contentY = modalY + 55; // Après le titre et la ligne
116
+ const contentHeight = this.modalHeight - 55;
117
+
118
+ ctx.save();
119
+ ctx.beginPath();
120
+ ctx.rect(contentX, contentY, this.modalWidth, contentHeight);
121
+ ctx.clip();
122
+
123
+ // Options
124
+ for (let i = 0; i < this.options.length; i++) {
125
+ const optionY = contentY + i * this.itemHeight;
126
+
127
+ // Si l'option est en dehors de la zone visible, passer à la suivante
128
+ if (optionY + this.itemHeight < contentY || optionY > contentY + contentHeight) {
129
+ continue;
130
+ }
131
+
132
+ // Option sélectionnée
133
+ if (i === this.selectedIndex) {
134
+ ctx.fillStyle = this.framework.platform === 'material' ? 'rgba(98, 0, 238, 0.1)' : 'rgba(0, 122, 255, 0.1)';
135
+ ctx.fillRect(contentX, optionY, this.modalWidth, this.itemHeight);
136
+ }
137
+
138
+ // Effet hover
139
+ if (this.hoveredIndex === i) {
140
+ ctx.fillStyle = '#F5F5F5';
141
+ ctx.fillRect(contentX, optionY, this.modalWidth, this.itemHeight);
142
+ }
143
+
144
+ // Texte de l'option
145
+ ctx.fillStyle = i === this.selectedIndex ?
146
+ (this.framework.platform === 'material' ? '#6200EE' : '#007AFF') :
147
+ '#000000';
148
+ ctx.font = i === this.selectedIndex ? 'bold 16px -apple-system, sans-serif' : '16px -apple-system, sans-serif';
149
+ ctx.textAlign = 'left';
150
+ ctx.textBaseline = 'middle';
151
+ ctx.fillText(this.options[i], contentX + 20, optionY + this.itemHeight / 2);
152
+
153
+ // Divider entre les options
154
+ if (i < this.options.length - 1) {
155
+ ctx.strokeStyle = '#E0E0E0';
156
+ ctx.lineWidth = 1;
157
+ ctx.beginPath();
158
+ ctx.moveTo(contentX + 20, optionY + this.itemHeight);
159
+ ctx.lineTo(contentX + this.modalWidth - 20, optionY + this.itemHeight);
160
+ ctx.stroke();
161
+ }
162
+ }
163
+
164
+ ctx.restore();
165
+ ctx.restore();
166
+ }
167
+
168
+ /**
169
+ * Gère le clic dans le modal
170
+ * @param {number} x - Position X du clic
171
+ * @param {number} y - Position Y du clic
172
+ */
173
+ handlePress(x, y) {
174
+ // POUR LES MODALS: utiliser les coordonnées brutes car le modal est un composant FIXE
175
+ // Les modals ne sont pas affectés par le scroll
176
+
177
+ const modalX = (this.framework.width - this.modalWidth) / 2;
178
+ const modalY = (this.framework.height - this.modalHeight) / 2;
179
+ const contentY = modalY + 55;
180
+
181
+ // Vérifier si une option a été cliquée
182
+ for (let i = 0; i < this.options.length; i++) {
183
+ const optionY = contentY + i * this.itemHeight;
184
+
185
+ // IMPORTANT: Pas d'ajustement de scroll pour les modals
186
+ if (y >= optionY && y <= optionY + this.itemHeight &&
187
+ x >= modalX && x <= modalX + this.modalWidth) {
188
+
189
+ this.selectedIndex = i;
190
+ if (this.onSelect) {
191
+ this.onSelect(i, this.options[i]);
192
+ }
193
+ this.hide();
194
+ return;
195
+ }
196
+ }
197
+
198
+ // Sinon, laisser le parent gérer (bouton de fermeture, overlay)
199
+ super.handlePress(x, y);
200
+ }
201
+
202
+ /**
203
+ * Gère le survol dans le modal
204
+ * @param {number} x - Position X actuelle
205
+ * @param {number} y - Position Y actuelle
206
+ */
207
+ handleMove(x, y) {
208
+ const modalX = (this.framework.width - this.modalWidth) / 2;
209
+ const modalY = (this.framework.height - this.modalHeight) / 2;
210
+ const contentY = modalY + 55;
211
+
212
+ this.hoveredIndex = -1;
213
+
214
+ // Vérifier si on survole une option
215
+ for (let i = 0; i < this.options.length; i++) {
216
+ const optionY = contentY + i * this.itemHeight;
217
+
218
+ // IMPORTANT: Pas d'ajustement de scroll pour les modals
219
+ if (y >= optionY && y <= optionY + this.itemHeight &&
220
+ x >= modalX && x <= modalX + this.modalWidth) {
221
+ this.hoveredIndex = i;
222
+ break;
223
+ }
224
+ }
225
+ }
226
+
227
+ /**
228
+ * Vérifie si un point est à l'intérieur du modal
229
+ * @param {number} x - Position X
230
+ * @param {number} y - Position Y
231
+ * @returns {boolean} True si le point est à l'intérieur du modal
232
+ */
233
+ isPointInside(x, y) {
234
+ const modalX = (this.framework.width - this.modalWidth) / 2;
235
+ const modalY = (this.framework.height - this.modalHeight) / 2;
236
+
237
+ // Les modals sont des composants fixes, donc pas d'ajustement de scroll
238
+ return x >= modalX &&
239
+ x <= modalX + this.modalWidth &&
240
+ y >= modalY &&
241
+ y <= modalY + this.modalHeight;
242
+ }
243
+
244
+ /**
245
+ * Affiche le modal
246
+ */
247
+ show() {
248
+ this.isVisible = true;
249
+ this.visible = true;
250
+ this.opacity = 1;
251
+ this.scale = 1;
252
+ }
253
+ }
254
+
255
+ export default SelectDialog;
@@ -0,0 +1,113 @@
1
+ import Component from '../core/Component.js';
2
+
3
+ class Slider extends Component {
4
+ constructor(framework, options = {}) {
5
+ super(framework, options);
6
+ this.min = options.min || 0;
7
+ this.max = options.max || 100;
8
+ this.value = options.value || 50;
9
+ this.platform = framework.platform;
10
+ this.onChange = options.onChange;
11
+ this.dragging = false;
12
+
13
+ // Animation state
14
+ this.thumbScale = 1.0;
15
+ this.targetScale = 1.0;
16
+
17
+ this.onPress = this.handlePress.bind(this);
18
+ this.onMove = this.handleMove.bind(this);
19
+ this.onClick = this.handleClick.bind(this);
20
+ }
21
+
22
+ handlePress(x, y) {
23
+ this.dragging = true;
24
+ this.targetScale = 1.5; // Agrandissement de 50%
25
+ this.updateValue(x);
26
+ return true;
27
+ }
28
+
29
+ handleMove(x, y) {
30
+ if (this.dragging) {
31
+ this.updateValue(x);
32
+ }
33
+ }
34
+
35
+ handleClick() {
36
+ this.dragging = false;
37
+ this.targetScale = 1.0; // Retour à la taille normale
38
+ }
39
+
40
+ updateValue(x) {
41
+ const relativeX = Math.max(0, Math.min(this.width, x - this.x));
42
+ const newValue = this.min + (relativeX / this.width) * (this.max - this.min);
43
+
44
+ if (newValue !== this.value) {
45
+ this.value = newValue;
46
+ if (this.onChange) this.onChange(this.value);
47
+ }
48
+ }
49
+
50
+ draw(ctx) {
51
+ ctx.save();
52
+
53
+ // Animation du scale
54
+ this.thumbScale += (this.targetScale - this.thumbScale) * 0.2;
55
+
56
+ const progress = (this.value - this.min) / (this.max - this.min);
57
+ const thumbX = this.x + progress * this.width;
58
+
59
+ // Track
60
+ ctx.strokeStyle = this.platform === 'material' ? '#E0E0E0' : '#C7C7CC';
61
+ ctx.lineWidth = 2;
62
+ ctx.beginPath();
63
+ ctx.moveTo(this.x, this.y + this.height / 2);
64
+ ctx.lineTo(this.x + this.width, this.y + this.height / 2);
65
+ ctx.stroke();
66
+
67
+ // Track rempli
68
+ const trackColor = this.platform === 'material' ? '#6200EE' : '#007AFF';
69
+ ctx.strokeStyle = trackColor;
70
+ ctx.lineWidth = 2;
71
+ ctx.beginPath();
72
+ ctx.moveTo(this.x, this.y + this.height / 2);
73
+ ctx.lineTo(thumbX, this.y + this.height / 2);
74
+ ctx.stroke();
75
+
76
+ // Curseur avec animation
77
+ const baseRadius = 8;
78
+ const currentRadius = baseRadius * this.thumbScale;
79
+
80
+ // Effet d'ombre pendant le drag
81
+ if (this.dragging) {
82
+ ctx.shadowColor = trackColor;
83
+ ctx.shadowBlur = 10;
84
+ }
85
+
86
+ ctx.fillStyle = trackColor;
87
+ ctx.beginPath();
88
+ ctx.arc(thumbX, this.y + this.height / 2, currentRadius, 0, Math.PI * 2);
89
+ ctx.fill();
90
+
91
+ if (this.dragging) {
92
+ ctx.shadowColor = 'transparent';
93
+ ctx.shadowBlur = 0;
94
+ }
95
+
96
+ ctx.restore();
97
+ }
98
+
99
+ isPointInside(x, y) {
100
+ const progress = (this.value - this.min) / (this.max - this.min);
101
+ const thumbX = this.x + progress * this.width;
102
+ const thumbY = this.y + this.height / 2;
103
+ const maxRadius = 8 * 1.5 + 5; // Rayon max + marge
104
+
105
+ // Zone de clic plus large pour faciliter l'utilisation
106
+ const distance = Math.sqrt((x - thumbX) ** 2 + (y - thumbY) ** 2);
107
+ return distance <= maxRadius ||
108
+ (x >= this.x && x <= this.x + this.width &&
109
+ y >= this.y && y <= this.y + this.height);
110
+ }
111
+ }
112
+
113
+ export default Slider;
@@ -0,0 +1,139 @@
1
+ import AppBar from './AppBar.js';
2
+ /**
3
+ * SliverAppBar - CanvasFramework
4
+ * Collapse / Stretch / Parallax
5
+ * Compatible Material & Cupertino
6
+ * ⚠️ Ne modifie PAS AppBar
7
+ */
8
+ class SliverAppBar extends AppBar {
9
+ constructor(framework, options = {}) {
10
+ super(framework, {
11
+ ...options,
12
+ y: 0
13
+ });
14
+ // Heights
15
+ this.expandedHeight = options.expandedHeight || 240;
16
+ this.collapsedHeight = options.collapsedHeight ?? 56;
17
+ // Effects
18
+ this.stretch = options.stretch ?? true;
19
+ this.parallax = options.parallax ?? true;
20
+ this.parallaxFactor = options.parallaxFactor ?? 0.4;
21
+ // Background
22
+ this.backgroundImage = options.backgroundImage || null;
23
+ this.backgroundColor = options.backgroundColor || this.bgColor || '#FFFFFF';
24
+ this.backgroundOpacity = this._extractOpacity(this.backgroundColor);
25
+ // Overlay (au-dessus de l'image)
26
+ this.overlayColor = options.overlayColor || '#000000';
27
+ this.overlayOpacity = options.overlayOpacity ?? 0;
28
+ // Foreground (titre + icônes)
29
+ this.foregroundColor = options.foregroundColor || options.textColor || this.textColor || '#FFFFFF';
30
+ // SliverAppBar participe au scroll
31
+ this.fixed = false;
32
+ }
33
+
34
+ /**
35
+ * Extrait l'opacité d'une couleur rgba() ou retourne 1
36
+ */
37
+ _extractOpacity(color) {
38
+ if (typeof color === 'string' && color.startsWith('rgba')) {
39
+ const match = color.match(/rgba?\([^,]+,[^,]+,[^,]+,\s*([\d.]+)\)/);
40
+ return match ? parseFloat(match[1]) : 1;
41
+ }
42
+ return 1;
43
+ }
44
+
45
+ /**
46
+ * Mise à jour dynamique selon le scroll
47
+ */
48
+ updateWithScroll(scrollOffset) {
49
+ const scrollY = -scrollOffset;
50
+ const collapseRange = this.expandedHeight - this.collapsedHeight;
51
+ const collapse = Math.min(scrollY, collapseRange);
52
+ let newHeight = this.expandedHeight - collapse;
53
+ // Stretch (overscroll)
54
+ if (scrollY < 0 && this.stretch) {
55
+ newHeight = this.expandedHeight - scrollY;
56
+ }
57
+ this.height = Math.max(newHeight, this.collapsedHeight);
58
+ }
59
+
60
+ draw(ctx) {
61
+ const scrollOffset = this.framework.scrollOffset || 0;
62
+ this.updateWithScroll(scrollOffset);
63
+ ctx.save();
64
+ // ===== Collapse progress (0 → 1)
65
+ const collapseProgress = Math.min(
66
+ 1,
67
+ Math.max(
68
+ 0,
69
+ (this.expandedHeight - this.height) /
70
+ (this.expandedHeight - this.collapsedHeight)
71
+ )
72
+ );
73
+ // ===== BACKGROUND IMAGE (en premier) =====
74
+ if (this.backgroundImage && this.backgroundImage.complete) {
75
+ let bgOffset = 0;
76
+ if (this.parallax) {
77
+ bgOffset = Math.max(0, -scrollOffset * this.parallaxFactor);
78
+ }
79
+ ctx.drawImage(
80
+ this.backgroundImage,
81
+ this.x,
82
+ this.y - bgOffset,
83
+ this.width,
84
+ this.height + bgOffset
85
+ );
86
+ }
87
+
88
+ // ===== BACKGROUND COLOR (overlay coloré au-dessus de l'image) =====
89
+ ctx.globalAlpha = this.backgroundOpacity;
90
+ ctx.fillStyle = this.backgroundColor;
91
+ ctx.fillRect(this.x, this.y, this.width, this.height);
92
+ ctx.globalAlpha = 1;
93
+
94
+ // ===== OVERLAY SUPPLÉMENTAIRE (si besoin) =====
95
+ if (this.overlayOpacity > 0) {
96
+ ctx.globalAlpha = this.overlayOpacity;
97
+ ctx.fillStyle = this.overlayColor;
98
+ ctx.fillRect(this.x, this.y, this.width, this.height);
99
+ ctx.globalAlpha = 1;
100
+ }
101
+ // ===== TITLE (fade with collapse) =====
102
+ ctx.globalAlpha = 1 - collapseProgress;
103
+ ctx.fillStyle = this.foregroundColor;
104
+ ctx.font = `bold 20px -apple-system, Roboto, sans-serif`;
105
+ ctx.textAlign = 'left';
106
+ ctx.textBaseline = 'bottom';
107
+ ctx.fillText(
108
+ this.title,
109
+ this.x + 16,
110
+ this.y + this.height - 16
111
+ );
112
+ ctx.globalAlpha = 1;
113
+
114
+ // ===== PREVENT APPBAR BACKGROUND OVERWRITE =====
115
+ const originalBg = this.bgColor;
116
+ const originalTextColor = this.textColor;
117
+ const originalIconColor = this.iconColor;
118
+ const originalPlatform = this.platform;
119
+
120
+ // Force transparent pour éviter que super.draw() dessine un fond
121
+ this.bgColor = 'rgba(0,0,0,0)';
122
+ // Force la couleur du texte et des icônes
123
+ this.textColor = this.foregroundColor;
124
+ this.iconColor = this.foregroundColor;
125
+ // Force Material pour uniformiser le comportement des couleurs
126
+ this.platform = 'material';
127
+
128
+ // Icons, ripple, elevation, etc.
129
+ super.draw(ctx);
130
+
131
+ // Restore
132
+ this.bgColor = originalBg;
133
+ this.textColor = originalTextColor;
134
+ this.iconColor = originalIconColor;
135
+ this.platform = originalPlatform;
136
+ ctx.restore();
137
+ }
138
+ }
139
+ export default SliverAppBar;