canvasframework 0.3.6
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/README.md +554 -0
- package/components/Accordion.js +252 -0
- package/components/AndroidDatePickerDialog.js +398 -0
- package/components/AppBar.js +225 -0
- package/components/Avatar.js +202 -0
- package/components/BottomNavigationBar.js +205 -0
- package/components/BottomSheet.js +374 -0
- package/components/Button.js +225 -0
- package/components/Card.js +193 -0
- package/components/Checkbox.js +180 -0
- package/components/Chip.js +212 -0
- package/components/CircularProgress.js +143 -0
- package/components/ContextMenu.js +116 -0
- package/components/DatePicker.js +257 -0
- package/components/Dialog.js +367 -0
- package/components/Divider.js +125 -0
- package/components/Drawer.js +261 -0
- package/components/FAB.js +270 -0
- package/components/FileUpload.js +315 -0
- package/components/IOSDatePickerWheel.js +268 -0
- package/components/ImageCarousel.js +193 -0
- package/components/ImageComponent.js +223 -0
- package/components/Input.js +309 -0
- package/components/List.js +94 -0
- package/components/ListItem.js +223 -0
- package/components/Modal.js +364 -0
- package/components/MultiSelectDialog.js +206 -0
- package/components/NumberInput.js +271 -0
- package/components/ProgressBar.js +88 -0
- package/components/RadioButton.js +142 -0
- package/components/SearchInput.js +315 -0
- package/components/SegmentedControl.js +202 -0
- package/components/Select.js +199 -0
- package/components/SelectDialog.js +255 -0
- package/components/Slider.js +113 -0
- package/components/Snackbar.js +243 -0
- package/components/Stepper.js +281 -0
- package/components/SwipeableListItem.js +179 -0
- package/components/Switch.js +147 -0
- package/components/Table.js +492 -0
- package/components/Tabs.js +125 -0
- package/components/Text.js +141 -0
- package/components/TextField.js +331 -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 +1271 -0
- package/core/CanvasWork.js +32 -0
- package/core/Component.js +153 -0
- package/core/LogicWorker.js +25 -0
- package/core/WebGLCanvasAdapter.js +1369 -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 +84 -0
- package/features/Stack.js +21 -0
- package/index.js +101 -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 +28 -0
- package/utils/AnimationEngine.js +428 -0
- package/utils/DataStore.js +403 -0
- package/utils/EventBus.js +407 -0
- package/utils/FetchClient.js +74 -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/OfflineSyncManager.js +342 -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/WebSocketClient.js +66 -0
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import Component from '../core/Component.js';
|
|
2
|
+
/**
|
|
3
|
+
* Spinner de chargement circulaire
|
|
4
|
+
* @class
|
|
5
|
+
* @extends Component
|
|
6
|
+
* @property {number} size - Taille du spinner
|
|
7
|
+
* @property {boolean} indeterminate - Mode indéterminé
|
|
8
|
+
* @property {number} progress - Progression (0-100)
|
|
9
|
+
* @property {string} platform - Plateforme
|
|
10
|
+
* @property {string} color - Couleur
|
|
11
|
+
* @property {number} lineWidth - Épaisseur de la ligne
|
|
12
|
+
* @property {number} rotation - Rotation actuelle
|
|
13
|
+
* @property {number} animationSpeed - Vitesse d'animation
|
|
14
|
+
*/
|
|
15
|
+
class CircularProgress extends Component {
|
|
16
|
+
/**
|
|
17
|
+
* Crée une instance de CircularProgress
|
|
18
|
+
* @param {CanvasFramework} framework - Framework parent
|
|
19
|
+
* @param {Object} [options={}] - Options de configuration
|
|
20
|
+
* @param {number} [options.size=40] - Taille
|
|
21
|
+
* @param {boolean} [options.indeterminate=true] - Mode indéterminé
|
|
22
|
+
* @param {number} [options.progress=0] - Progression (0-100)
|
|
23
|
+
* @param {string} [options.color] - Couleur (auto selon platform)
|
|
24
|
+
* @param {number} [options.lineWidth=4] - Épaisseur
|
|
25
|
+
* @param {number} [options.animationSpeed=0.05] - Vitesse d'animation
|
|
26
|
+
*/
|
|
27
|
+
constructor(framework, options = {}) {
|
|
28
|
+
super(framework, options);
|
|
29
|
+
this.size = options.size || 40;
|
|
30
|
+
this.indeterminate = options.indeterminate !== false;
|
|
31
|
+
this.progress = options.progress || 0; // 0-100
|
|
32
|
+
this.platform = framework.platform;
|
|
33
|
+
this.color = options.color || (framework.platform === 'material' ? '#6200EE' : '#007AFF');
|
|
34
|
+
this.lineWidth = options.lineWidth || 4;
|
|
35
|
+
this.rotation = 0;
|
|
36
|
+
this.animationSpeed = options.animationSpeed || 0.05;
|
|
37
|
+
|
|
38
|
+
this.width = this.size;
|
|
39
|
+
this.height = this.size;
|
|
40
|
+
|
|
41
|
+
// Démarrer l'animation pour indeterminate
|
|
42
|
+
if (this.indeterminate) {
|
|
43
|
+
this.startAnimation();
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Démarre l'animation du spinner
|
|
49
|
+
* @private
|
|
50
|
+
*/
|
|
51
|
+
startAnimation() {
|
|
52
|
+
const animate = () => {
|
|
53
|
+
if (!this.visible || !this.indeterminate) return;
|
|
54
|
+
|
|
55
|
+
this.rotation += this.animationSpeed;
|
|
56
|
+
if (this.rotation > Math.PI * 2) {
|
|
57
|
+
this.rotation = 0;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
requestAnimationFrame(animate);
|
|
61
|
+
};
|
|
62
|
+
animate();
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Dessine le spinner
|
|
67
|
+
* @param {CanvasRenderingContext2D} ctx - Contexte de dessin
|
|
68
|
+
*/
|
|
69
|
+
draw(ctx) {
|
|
70
|
+
ctx.save();
|
|
71
|
+
|
|
72
|
+
const centerX = this.x + this.size / 2;
|
|
73
|
+
const centerY = this.y + this.size / 2;
|
|
74
|
+
const radius = (this.size - this.lineWidth) / 2;
|
|
75
|
+
|
|
76
|
+
if (this.indeterminate) {
|
|
77
|
+
// Spinner qui tourne
|
|
78
|
+
ctx.translate(centerX, centerY);
|
|
79
|
+
ctx.rotate(this.rotation);
|
|
80
|
+
ctx.translate(-centerX, -centerY);
|
|
81
|
+
|
|
82
|
+
// Cercle de base (track)
|
|
83
|
+
ctx.strokeStyle = this.platform === 'material' ? '#E0E0E0' : '#E5E5EA';
|
|
84
|
+
ctx.lineWidth = this.lineWidth;
|
|
85
|
+
ctx.lineCap = 'round';
|
|
86
|
+
ctx.beginPath();
|
|
87
|
+
ctx.arc(centerX, centerY, radius, 0, Math.PI * 2);
|
|
88
|
+
ctx.stroke();
|
|
89
|
+
|
|
90
|
+
// Arc animé
|
|
91
|
+
ctx.strokeStyle = this.color;
|
|
92
|
+
ctx.beginPath();
|
|
93
|
+
ctx.arc(centerX, centerY, radius, 0, Math.PI * 1.5);
|
|
94
|
+
ctx.stroke();
|
|
95
|
+
|
|
96
|
+
} else {
|
|
97
|
+
// Progress circulaire déterminé
|
|
98
|
+
// Track
|
|
99
|
+
ctx.strokeStyle = this.platform === 'material' ? '#E0E0E0' : '#E5E5EA';
|
|
100
|
+
ctx.lineWidth = this.lineWidth;
|
|
101
|
+
ctx.beginPath();
|
|
102
|
+
ctx.arc(centerX, centerY, radius, 0, Math.PI * 2);
|
|
103
|
+
ctx.stroke();
|
|
104
|
+
|
|
105
|
+
// Progress
|
|
106
|
+
const angle = (this.progress / 100) * Math.PI * 2;
|
|
107
|
+
ctx.strokeStyle = this.color;
|
|
108
|
+
ctx.lineCap = 'round';
|
|
109
|
+
ctx.beginPath();
|
|
110
|
+
ctx.arc(centerX, centerY, radius, -Math.PI / 2, -Math.PI / 2 + angle);
|
|
111
|
+
ctx.stroke();
|
|
112
|
+
|
|
113
|
+
// Pourcentage au centre (optionnel)
|
|
114
|
+
if (this.progress > 0) {
|
|
115
|
+
ctx.fillStyle = this.color;
|
|
116
|
+
ctx.font = `bold ${this.size / 3}px -apple-system, sans-serif`;
|
|
117
|
+
ctx.textAlign = 'center';
|
|
118
|
+
ctx.textBaseline = 'middle';
|
|
119
|
+
ctx.fillText(`${Math.round(this.progress)}%`, centerX, centerY);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
ctx.restore();
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Définit la progression
|
|
128
|
+
* @param {number} value - Valeur de progression (0-100)
|
|
129
|
+
*/
|
|
130
|
+
setProgress(value) {
|
|
131
|
+
this.progress = Math.max(0, Math.min(100, value));
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Vérifie si un point est dans les limites
|
|
136
|
+
* @returns {boolean} False (non cliquable)
|
|
137
|
+
*/
|
|
138
|
+
isPointInside() {
|
|
139
|
+
return false; // Non cliquable
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
export default CircularProgress;
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import Component from '../core/Component.js';
|
|
2
|
+
/**
|
|
3
|
+
* Menu contextuel
|
|
4
|
+
* @class
|
|
5
|
+
* @extends Component
|
|
6
|
+
* @property {string[]} options - Options du menu
|
|
7
|
+
* @property {Function} onSelect - Callback à la sélection
|
|
8
|
+
* @property {number} itemHeight - Hauteur d'un item
|
|
9
|
+
* @property {boolean} isOpen - État ouvert
|
|
10
|
+
* @property {number} hoveredIndex - Index survolé
|
|
11
|
+
*/
|
|
12
|
+
class ContextMenu extends Component {
|
|
13
|
+
/**
|
|
14
|
+
* Crée une instance de ContextMenu
|
|
15
|
+
* @param {CanvasFramework} framework - Framework parent
|
|
16
|
+
* @param {Object} [options={}] - Options de configuration
|
|
17
|
+
* @param {string[]} [options.options=[]] - Options du menu
|
|
18
|
+
* @param {Function} [options.onSelect] - Callback à la sélection
|
|
19
|
+
*/
|
|
20
|
+
constructor(framework, options = {}) {
|
|
21
|
+
super(framework, options);
|
|
22
|
+
this.options = options.options || [];
|
|
23
|
+
this.onSelect = options.onSelect;
|
|
24
|
+
this.itemHeight = 48;
|
|
25
|
+
this.height = this.options.length * this.itemHeight;
|
|
26
|
+
this.hoveredIndex = -1;
|
|
27
|
+
this.isOpen = true;
|
|
28
|
+
|
|
29
|
+
// Définir onClick pour le menu
|
|
30
|
+
this.onClick = this.handleClick.bind(this);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Dessine le menu contextuel
|
|
35
|
+
* @param {CanvasRenderingContext2D} ctx - Contexte de dessin
|
|
36
|
+
*/
|
|
37
|
+
draw(ctx) {
|
|
38
|
+
if (!this.isOpen) return;
|
|
39
|
+
|
|
40
|
+
ctx.save();
|
|
41
|
+
|
|
42
|
+
// Background
|
|
43
|
+
ctx.fillStyle = '#FFFFFF';
|
|
44
|
+
ctx.shadowColor = 'rgba(0, 0, 0, 0.2)';
|
|
45
|
+
ctx.shadowBlur = 10;
|
|
46
|
+
ctx.fillRect(this.x, this.y, this.width, this.height);
|
|
47
|
+
|
|
48
|
+
ctx.shadowColor = 'transparent';
|
|
49
|
+
|
|
50
|
+
// Options
|
|
51
|
+
for (let i = 0; i < this.options.length; i++) {
|
|
52
|
+
const itemY = this.y + i * this.itemHeight;
|
|
53
|
+
|
|
54
|
+
if (this.hoveredIndex === i) {
|
|
55
|
+
ctx.fillStyle = '#F5F5F5';
|
|
56
|
+
ctx.fillRect(this.x, itemY, this.width, this.itemHeight);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
ctx.fillStyle = '#000000';
|
|
60
|
+
ctx.font = '16px -apple-system, sans-serif';
|
|
61
|
+
ctx.textAlign = 'left';
|
|
62
|
+
ctx.textBaseline = 'middle';
|
|
63
|
+
ctx.fillText(this.options[i], this.x + 16, itemY + this.itemHeight / 2);
|
|
64
|
+
|
|
65
|
+
// Divider
|
|
66
|
+
if (i < this.options.length - 1) {
|
|
67
|
+
ctx.strokeStyle = '#E0E0E0';
|
|
68
|
+
ctx.lineWidth = 1;
|
|
69
|
+
ctx.beginPath();
|
|
70
|
+
ctx.moveTo(this.x, itemY + this.itemHeight);
|
|
71
|
+
ctx.lineTo(this.x + this.width, itemY + this.itemHeight);
|
|
72
|
+
ctx.stroke();
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
ctx.restore();
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Vérifie si un point est dans les limites
|
|
81
|
+
* @param {number} x - Coordonnée X
|
|
82
|
+
* @param {number} y - Coordonnée Y
|
|
83
|
+
* @returns {boolean} True si le point est dans le menu
|
|
84
|
+
*/
|
|
85
|
+
isPointInside(x, y) {
|
|
86
|
+
const adjustedY = y - this.framework.scrollOffset;
|
|
87
|
+
const inBounds = super.isPointInside(x, adjustedY);
|
|
88
|
+
if (inBounds) {
|
|
89
|
+
this.hoveredIndex = Math.floor((adjustedY - this.y) / this.itemHeight);
|
|
90
|
+
} else {
|
|
91
|
+
this.hoveredIndex = -1;
|
|
92
|
+
}
|
|
93
|
+
return inBounds;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Gère le clic sur le menu
|
|
98
|
+
* @private
|
|
99
|
+
*/
|
|
100
|
+
handleClick() {
|
|
101
|
+
if (this.hoveredIndex >= 0 && this.onSelect) {
|
|
102
|
+
this.onSelect(this.hoveredIndex);
|
|
103
|
+
this.close();
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Ferme le menu contextuel
|
|
109
|
+
*/
|
|
110
|
+
close() {
|
|
111
|
+
this.isOpen = false;
|
|
112
|
+
this.framework.remove(this);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
export default ContextMenu;
|
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
import Component from '../core/Component.js';
|
|
2
|
+
/**
|
|
3
|
+
* Sélecteur de date (wrapper)
|
|
4
|
+
* @class
|
|
5
|
+
* @extends Component
|
|
6
|
+
* @property {Date} selectedDate - Date sélectionnée
|
|
7
|
+
* @property {Date|null} minDate - Date minimum
|
|
8
|
+
* @property {Date|null} maxDate - Date maximum
|
|
9
|
+
* @property {Function} onChange - Callback au changement
|
|
10
|
+
* @property {string} platform - Plateforme
|
|
11
|
+
* @property {string} label - Label
|
|
12
|
+
* @property {Modal|null} pickerModal - Modal iOS
|
|
13
|
+
*/
|
|
14
|
+
class DatePicker extends Component {
|
|
15
|
+
/**
|
|
16
|
+
* Crée une instance de DatePicker
|
|
17
|
+
* @param {CanvasFramework} framework - Framework parent
|
|
18
|
+
* @param {Object} [options={}] - Options de configuration
|
|
19
|
+
* @param {Date} [options.selectedDate=new Date()] - Date initiale
|
|
20
|
+
* @param {Date} [options.minDate] - Date minimum
|
|
21
|
+
* @param {Date} [options.maxDate] - Date maximum
|
|
22
|
+
* @param {Function} [options.onChange] - Callback au changement
|
|
23
|
+
* @param {string} [options.label='Sélectionner une date'] - Label
|
|
24
|
+
*/
|
|
25
|
+
constructor(framework, options = {}) {
|
|
26
|
+
super(framework, options);
|
|
27
|
+
this.selectedDate = options.selectedDate || new Date();
|
|
28
|
+
this.minDate = options.minDate || null;
|
|
29
|
+
this.maxDate = options.maxDate || null;
|
|
30
|
+
this.onChange = options.onChange;
|
|
31
|
+
this.platform = framework.platform;
|
|
32
|
+
this.label = options.label || 'Sélectionner une date';
|
|
33
|
+
|
|
34
|
+
// Pour iOS: créer un bouton qui ouvre le picker
|
|
35
|
+
if (this.platform === 'cupertino') {
|
|
36
|
+
this.width = options.width || framework.width - 40;
|
|
37
|
+
this.height = 50;
|
|
38
|
+
this.pickerModal = null;
|
|
39
|
+
this.onClick = this.openPicker.bind(this);
|
|
40
|
+
} else {
|
|
41
|
+
// Pour Android: afficher directement le modal
|
|
42
|
+
this.width = options.width || Math.min(320, framework.width - 40);
|
|
43
|
+
this.height = 50;
|
|
44
|
+
this.onClick = this.openPicker.bind(this);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Ouvre le sélecteur de date
|
|
50
|
+
* @private
|
|
51
|
+
*/
|
|
52
|
+
openPicker() {
|
|
53
|
+
if (this.platform === 'cupertino') {
|
|
54
|
+
this.openIOSPicker();
|
|
55
|
+
} else {
|
|
56
|
+
this.openAndroidDialog();
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Ouvre le sélecteur iOS
|
|
62
|
+
* @private
|
|
63
|
+
*/
|
|
64
|
+
openIOSPicker() {
|
|
65
|
+
// Créer un modal avec le picker iOS
|
|
66
|
+
const modal = new Modal(this.framework, {
|
|
67
|
+
title: '',
|
|
68
|
+
width: this.framework.width,
|
|
69
|
+
height: 320,
|
|
70
|
+
showCloseButton: false,
|
|
71
|
+
closeOnOverlayClick: true,
|
|
72
|
+
bgColor: '#F9F9F9'
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
// Créer le picker de date iOS style
|
|
76
|
+
const picker = new IOSDatePickerWheel(this.framework, {
|
|
77
|
+
x: 0,
|
|
78
|
+
y: 20,
|
|
79
|
+
width: this.framework.width - 40,
|
|
80
|
+
selectedDate: this.selectedDate,
|
|
81
|
+
onChange: (date) => {
|
|
82
|
+
this.selectedDate = date;
|
|
83
|
+
if (this.onChange) this.onChange(date);
|
|
84
|
+
}
|
|
85
|
+
});
|
|
86
|
+
modal.add(picker);
|
|
87
|
+
|
|
88
|
+
// Bouton Valider
|
|
89
|
+
const btnOK = new Button(this.framework, {
|
|
90
|
+
x: (this.framework.width - 200) / 2,
|
|
91
|
+
y: 230,
|
|
92
|
+
width: 200,
|
|
93
|
+
height: 44,
|
|
94
|
+
text: 'Valider',
|
|
95
|
+
onClick: () => {
|
|
96
|
+
modal.hide();
|
|
97
|
+
}
|
|
98
|
+
});
|
|
99
|
+
modal.add(btnOK);
|
|
100
|
+
|
|
101
|
+
this.framework.add(modal);
|
|
102
|
+
modal.show();
|
|
103
|
+
this.pickerModal = modal;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Ouvre le dialog Android
|
|
108
|
+
* @private
|
|
109
|
+
*/
|
|
110
|
+
openAndroidDialog() {
|
|
111
|
+
// Créer un dialog Material Design avec calendrier
|
|
112
|
+
const dialog = new AndroidDatePickerDialog(this.framework, {
|
|
113
|
+
selectedDate: this.selectedDate,
|
|
114
|
+
onChange: (date) => {
|
|
115
|
+
this.selectedDate = date;
|
|
116
|
+
if (this.onChange) this.onChange(date);
|
|
117
|
+
}
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
this.framework.add(dialog);
|
|
121
|
+
dialog.show();
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Dessine le sélecteur de date
|
|
126
|
+
* @param {CanvasRenderingContext2D} ctx - Contexte de dessin
|
|
127
|
+
*/
|
|
128
|
+
draw(ctx) {
|
|
129
|
+
if (this.platform === 'cupertino') {
|
|
130
|
+
// Dessiner un bouton iOS
|
|
131
|
+
ctx.save();
|
|
132
|
+
|
|
133
|
+
// Background
|
|
134
|
+
ctx.fillStyle = '#FFFFFF';
|
|
135
|
+
ctx.beginPath();
|
|
136
|
+
this.roundRect(ctx, this.x, this.y, this.width, this.height, 10);
|
|
137
|
+
ctx.fill();
|
|
138
|
+
|
|
139
|
+
// Bordure
|
|
140
|
+
ctx.strokeStyle = '#C7C7CC';
|
|
141
|
+
ctx.lineWidth = 1;
|
|
142
|
+
ctx.stroke();
|
|
143
|
+
|
|
144
|
+
// Label
|
|
145
|
+
ctx.fillStyle = '#8E8E93';
|
|
146
|
+
ctx.font = '14px -apple-system, sans-serif';
|
|
147
|
+
ctx.textAlign = 'left';
|
|
148
|
+
ctx.textBaseline = 'middle';
|
|
149
|
+
ctx.fillText(this.label, this.x + 15, this.y + this.height / 2 - 10);
|
|
150
|
+
|
|
151
|
+
// Date sélectionnée
|
|
152
|
+
ctx.fillStyle = '#000000';
|
|
153
|
+
ctx.font = '16px -apple-system, sans-serif';
|
|
154
|
+
ctx.fillText(this.formatDate(this.selectedDate), this.x + 15, this.y + this.height / 2 + 10);
|
|
155
|
+
|
|
156
|
+
// Chevron
|
|
157
|
+
ctx.strokeStyle = '#C7C7CC';
|
|
158
|
+
ctx.lineWidth = 2;
|
|
159
|
+
ctx.lineCap = 'round';
|
|
160
|
+
ctx.beginPath();
|
|
161
|
+
ctx.moveTo(this.x + this.width - 25, this.y + this.height / 2 - 5);
|
|
162
|
+
ctx.lineTo(this.x + this.width - 20, this.y + this.height / 2);
|
|
163
|
+
ctx.lineTo(this.x + this.width - 15, this.y + this.height / 2 - 5);
|
|
164
|
+
ctx.stroke();
|
|
165
|
+
|
|
166
|
+
ctx.restore();
|
|
167
|
+
} else {
|
|
168
|
+
// Dessiner un bouton Material
|
|
169
|
+
ctx.save();
|
|
170
|
+
|
|
171
|
+
// Background
|
|
172
|
+
ctx.fillStyle = this.pressed ? '#F5F5F5' : '#FFFFFF';
|
|
173
|
+
ctx.fillRect(this.x, this.y, this.width, this.height);
|
|
174
|
+
|
|
175
|
+
// Bordure
|
|
176
|
+
ctx.strokeStyle = '#E0E0E0';
|
|
177
|
+
ctx.lineWidth = 1;
|
|
178
|
+
ctx.strokeRect(this.x, this.y, this.width, this.height);
|
|
179
|
+
|
|
180
|
+
// Icône calendrier
|
|
181
|
+
ctx.strokeStyle = '#666666';
|
|
182
|
+
ctx.lineWidth = 2;
|
|
183
|
+
ctx.strokeRect(this.x + 15, this.y + 15, 20, 20);
|
|
184
|
+
ctx.beginPath();
|
|
185
|
+
ctx.moveTo(this.x + 18, this.y + 12);
|
|
186
|
+
ctx.lineTo(this.x + 18, this.y + 18);
|
|
187
|
+
ctx.moveTo(this.x + 32, this.y + 12);
|
|
188
|
+
ctx.lineTo(this.x + 32, this.y + 18);
|
|
189
|
+
ctx.stroke();
|
|
190
|
+
|
|
191
|
+
// Label
|
|
192
|
+
ctx.fillStyle = '#666666';
|
|
193
|
+
ctx.font = '12px Roboto, sans-serif';
|
|
194
|
+
ctx.textAlign = 'left';
|
|
195
|
+
ctx.textBaseline = 'top';
|
|
196
|
+
ctx.fillText(this.label, this.x + 45, this.y + 8);
|
|
197
|
+
|
|
198
|
+
// Date
|
|
199
|
+
ctx.fillStyle = '#000000';
|
|
200
|
+
ctx.font = '16px Roboto, sans-serif';
|
|
201
|
+
ctx.textBaseline = 'bottom';
|
|
202
|
+
ctx.fillText(this.formatDate(this.selectedDate), this.x + 45, this.y + this.height - 8);
|
|
203
|
+
|
|
204
|
+
ctx.restore();
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Formate une date
|
|
210
|
+
* @param {Date} date - Date à formater
|
|
211
|
+
* @returns {string} Date formatée
|
|
212
|
+
* @private
|
|
213
|
+
*/
|
|
214
|
+
formatDate(date) {
|
|
215
|
+
const day = date.getDate().toString().padStart(2, '0');
|
|
216
|
+
const month = (date.getMonth() + 1).toString().padStart(2, '0');
|
|
217
|
+
const year = date.getFullYear();
|
|
218
|
+
return `${day}/${month}/${year}`;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* Dessine un rectangle avec coins arrondis
|
|
223
|
+
* @param {CanvasRenderingContext2D} ctx - Contexte de dessin
|
|
224
|
+
* @param {number} x - Position X
|
|
225
|
+
* @param {number} y - Position Y
|
|
226
|
+
* @param {number} width - Largeur
|
|
227
|
+
* @param {number} height - Hauteur
|
|
228
|
+
* @param {number} radius - Rayon des coins
|
|
229
|
+
* @private
|
|
230
|
+
*/
|
|
231
|
+
roundRect(ctx, x, y, width, height, radius) {
|
|
232
|
+
ctx.beginPath();
|
|
233
|
+
ctx.moveTo(x + radius, y);
|
|
234
|
+
ctx.lineTo(x + width - radius, y);
|
|
235
|
+
ctx.quadraticCurveTo(x + width, y, x + width, y + radius);
|
|
236
|
+
ctx.lineTo(x + width, y + height - radius);
|
|
237
|
+
ctx.quadraticCurveTo(x + width, y + height, x + width - radius, y + height);
|
|
238
|
+
ctx.lineTo(x + radius, y + height);
|
|
239
|
+
ctx.quadraticCurveTo(x, y + height, x, y + height - radius);
|
|
240
|
+
ctx.lineTo(x, y + radius);
|
|
241
|
+
ctx.quadraticCurveTo(x, y, x + radius, y);
|
|
242
|
+
ctx.closePath();
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* Vérifie si un point est dans les limites
|
|
247
|
+
* @param {number} x - Coordonnée X
|
|
248
|
+
* @param {number} y - Coordonnée Y
|
|
249
|
+
* @returns {boolean} True si le point est dans le sélecteur
|
|
250
|
+
*/
|
|
251
|
+
isPointInside(x, y) {
|
|
252
|
+
return x >= this.x && x <= this.x + this.width &&
|
|
253
|
+
y >= this.y && y <= this.y + this.height;
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
export default DatePicker;
|