mtrl 0.2.5 → 0.2.7
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/index.ts +18 -0
- package/package.json +1 -1
- package/src/components/badge/_styles.scss +123 -115
- package/src/components/badge/api.ts +57 -59
- package/src/components/badge/badge.ts +16 -2
- package/src/components/badge/config.ts +65 -11
- package/src/components/badge/constants.ts +22 -12
- package/src/components/badge/features.ts +44 -40
- package/src/components/badge/types.ts +42 -30
- package/src/components/bottom-app-bar/_styles.scss +103 -0
- package/src/components/bottom-app-bar/bottom-app-bar.ts +196 -0
- package/src/components/bottom-app-bar/config.ts +73 -0
- package/src/components/bottom-app-bar/index.ts +11 -0
- package/src/components/bottom-app-bar/types.ts +108 -0
- package/src/components/button/_styles.scss +0 -66
- package/src/components/button/api.ts +5 -0
- package/src/components/button/button.ts +0 -2
- package/src/components/button/config.ts +5 -0
- package/src/components/button/constants.ts +0 -6
- package/src/components/button/index.ts +2 -2
- package/src/components/button/types.ts +7 -7
- package/src/components/card/_styles.scss +67 -25
- package/src/components/card/api.ts +54 -3
- package/src/components/card/card.ts +25 -6
- package/src/components/card/config.ts +189 -22
- package/src/components/card/constants.ts +20 -19
- package/src/components/card/content.ts +299 -2
- package/src/components/card/features.ts +158 -4
- package/src/components/card/index.ts +31 -9
- package/src/components/card/types.ts +166 -15
- package/src/components/checkbox/_styles.scss +0 -2
- package/src/components/chip/chip.ts +1 -9
- package/src/components/chip/constants.ts +0 -10
- package/src/components/chip/index.ts +1 -1
- package/src/components/chip/types.ts +1 -4
- package/src/components/datepicker/_styles.scss +358 -0
- package/src/components/datepicker/api.ts +272 -0
- package/src/components/datepicker/config.ts +144 -0
- package/src/components/datepicker/constants.ts +98 -0
- package/src/components/datepicker/datepicker.ts +346 -0
- package/src/components/datepicker/index.ts +9 -0
- package/src/components/datepicker/render.ts +452 -0
- package/src/components/datepicker/types.ts +268 -0
- package/src/components/datepicker/utils.ts +290 -0
- package/src/components/dialog/_styles.scss +174 -128
- package/src/components/dialog/api.ts +48 -13
- package/src/components/dialog/config.ts +9 -5
- package/src/components/dialog/dialog.ts +6 -3
- package/src/components/dialog/features.ts +290 -130
- package/src/components/dialog/types.ts +7 -4
- package/src/components/divider/_styles.scss +57 -0
- package/src/components/divider/config.ts +81 -0
- package/src/components/divider/divider.ts +37 -0
- package/src/components/divider/features.ts +207 -0
- package/src/components/divider/index.ts +5 -0
- package/src/components/divider/types.ts +55 -0
- package/src/components/extended-fab/_styles.scss +267 -0
- package/src/components/extended-fab/api.ts +141 -0
- package/src/components/extended-fab/config.ts +108 -0
- package/src/components/extended-fab/constants.ts +36 -0
- package/src/components/extended-fab/extended-fab.ts +125 -0
- package/src/components/extended-fab/index.ts +4 -0
- package/src/components/extended-fab/types.ts +287 -0
- package/src/components/fab/_styles.scss +225 -0
- package/src/components/fab/api.ts +97 -0
- package/src/components/fab/config.ts +94 -0
- package/src/components/fab/constants.ts +41 -0
- package/src/components/fab/fab.ts +67 -0
- package/src/components/fab/index.ts +4 -0
- package/src/components/fab/types.ts +234 -0
- package/src/components/navigation/_styles.scss +1 -0
- package/src/components/navigation/api.ts +78 -50
- package/src/components/navigation/features/items.ts +280 -0
- package/src/components/navigation/nav-item.ts +72 -23
- package/src/components/navigation/navigation.ts +54 -2
- package/src/components/navigation/types.ts +210 -188
- package/src/components/progress/_styles.scss +0 -65
- package/src/components/progress/config.ts +1 -2
- package/src/components/progress/constants.ts +0 -14
- package/src/components/progress/index.ts +1 -1
- package/src/components/progress/progress.ts +1 -4
- package/src/components/progress/types.ts +1 -4
- package/src/components/radios/_styles.scss +0 -45
- package/src/components/radios/api.ts +85 -60
- package/src/components/radios/config.ts +1 -2
- package/src/components/radios/constants.ts +0 -9
- package/src/components/radios/index.ts +1 -1
- package/src/components/radios/radio.ts +34 -11
- package/src/components/radios/radios.ts +2 -1
- package/src/components/radios/types.ts +1 -7
- package/src/components/search/_styles.scss +306 -0
- package/src/components/search/api.ts +203 -0
- package/src/components/search/config.ts +87 -0
- package/src/components/search/constants.ts +21 -0
- package/src/components/search/features/index.ts +4 -0
- package/src/components/search/features/search.ts +718 -0
- package/src/components/search/features/states.ts +165 -0
- package/src/components/search/features/structure.ts +198 -0
- package/src/components/search/index.ts +10 -0
- package/src/components/search/search.ts +52 -0
- package/src/components/search/types.ts +163 -0
- package/src/components/segmented-button/_styles.scss +117 -0
- package/src/components/segmented-button/config.ts +67 -0
- package/src/components/segmented-button/constants.ts +42 -0
- package/src/components/segmented-button/index.ts +4 -0
- package/src/components/segmented-button/segment.ts +155 -0
- package/src/components/segmented-button/segmented-button.ts +250 -0
- package/src/components/segmented-button/types.ts +219 -0
- package/src/components/slider/_styles.scss +221 -168
- package/src/components/slider/accessibility.md +59 -0
- package/src/components/slider/api.ts +41 -120
- package/src/components/slider/config.ts +51 -49
- package/src/components/slider/features/handlers.ts +495 -0
- package/src/components/slider/features/index.ts +1 -2
- package/src/components/slider/features/slider.ts +66 -84
- package/src/components/slider/features/states.ts +195 -0
- package/src/components/slider/features/structure.ts +141 -184
- package/src/components/slider/features/ui.ts +150 -201
- package/src/components/slider/index.ts +2 -11
- package/src/components/slider/slider.ts +9 -12
- package/src/components/slider/types.ts +39 -24
- package/src/components/switch/_styles.scss +0 -2
- package/src/components/tabs/_styles.scss +346 -154
- package/src/components/tabs/api.ts +178 -400
- package/src/components/tabs/config.ts +46 -52
- package/src/components/tabs/constants.ts +85 -8
- package/src/components/tabs/features.ts +403 -0
- package/src/components/tabs/index.ts +60 -3
- package/src/components/tabs/indicator.ts +285 -0
- package/src/components/tabs/responsive.ts +144 -0
- package/src/components/tabs/scroll-indicators.ts +149 -0
- package/src/components/tabs/state.ts +186 -0
- package/src/components/tabs/tab-api.ts +258 -0
- package/src/components/tabs/tab.ts +255 -0
- package/src/components/tabs/tabs.ts +50 -31
- package/src/components/tabs/types.ts +332 -128
- package/src/components/tabs/utils.ts +107 -0
- package/src/components/textfield/_styles.scss +0 -98
- package/src/components/textfield/config.ts +2 -3
- package/src/components/textfield/constants.ts +0 -14
- package/src/components/textfield/index.ts +2 -2
- package/src/components/textfield/textfield.ts +0 -2
- package/src/components/textfield/types.ts +1 -4
- package/src/components/timepicker/README.md +277 -0
- package/src/components/timepicker/_styles.scss +451 -0
- package/src/components/timepicker/api.ts +632 -0
- package/src/components/timepicker/clockdial.ts +482 -0
- package/src/components/timepicker/config.ts +130 -0
- package/src/components/timepicker/constants.ts +138 -0
- package/src/components/timepicker/index.ts +8 -0
- package/src/components/timepicker/render.ts +613 -0
- package/src/components/timepicker/timepicker.ts +117 -0
- package/src/components/timepicker/types.ts +336 -0
- package/src/components/timepicker/utils.ts +241 -0
- package/src/components/top-app-bar/_styles.scss +225 -0
- package/src/components/top-app-bar/config.ts +83 -0
- package/src/components/top-app-bar/index.ts +11 -0
- package/src/components/top-app-bar/top-app-bar.ts +316 -0
- package/src/components/top-app-bar/types.ts +140 -0
- package/src/core/build/_ripple.scss +6 -6
- package/src/core/build/ripple.ts +72 -95
- package/src/core/compose/component.ts +1 -1
- package/src/core/compose/features/badge.ts +79 -0
- package/src/core/compose/features/icon.ts +3 -1
- package/src/core/compose/features/index.ts +3 -1
- package/src/core/compose/features/ripple.ts +4 -1
- package/src/core/compose/features/textlabel.ts +26 -2
- package/src/core/dom/create.ts +5 -0
- package/src/index.ts +9 -0
- package/src/styles/abstract/_theme.scss +115 -3
- package/src/styles/themes/_autumn.scss +21 -0
- package/src/styles/themes/_base-theme.scss +61 -0
- package/src/styles/themes/_baseline.scss +58 -0
- package/src/styles/themes/_bluekhaki.scss +125 -0
- package/src/styles/themes/_brownbeige.scss +125 -0
- package/src/styles/themes/_browngreen.scss +125 -0
- package/src/styles/themes/_forest.scss +6 -0
- package/src/styles/themes/_greenbeige.scss +125 -0
- package/src/styles/themes/_material.scss +125 -0
- package/src/styles/themes/_ocean.scss +6 -0
- package/src/styles/themes/_sageivory.scss +125 -0
- package/src/styles/themes/_spring.scss +6 -0
- package/src/styles/themes/_summer.scss +5 -0
- package/src/styles/themes/_sunset.scss +5 -0
- package/src/styles/themes/_tealcaramel.scss +125 -0
- package/src/styles/themes/_winter.scss +6 -0
- package/src/components/card/actions.ts +0 -48
- package/src/components/card/header.ts +0 -88
- package/src/components/card/media.ts +0 -52
- package/src/components/navigation/features/items.js +0 -192
- package/src/components/slider/features/appearance.ts +0 -94
- package/src/components/slider/features/disabled.ts +0 -43
- package/src/components/slider/features/events.ts +0 -164
- package/src/components/slider/features/interactions.ts +0 -261
- package/src/components/slider/features/keyboard.ts +0 -112
- package/src/core/collection/adapters/mongodb.js +0 -232
|
@@ -0,0 +1,482 @@
|
|
|
1
|
+
// src/components/timepicker/clockdial.ts
|
|
2
|
+
|
|
3
|
+
import { TimeValue, TIME_FORMAT, TIME_PICKER_TYPE, TIME_PERIOD } from './types';
|
|
4
|
+
import { DIAL_CONSTANTS, TIME_CONSTANTS } from './constants';
|
|
5
|
+
import { padZero } from './utils';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Interface for Clock Dial rendering options
|
|
9
|
+
*/
|
|
10
|
+
export interface ClockDialOptions {
|
|
11
|
+
type: TIME_PICKER_TYPE;
|
|
12
|
+
format: TIME_FORMAT;
|
|
13
|
+
showSeconds: boolean;
|
|
14
|
+
prefix: string;
|
|
15
|
+
activeSelector: 'hour' | 'minute' | 'second';
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Theme colors interface
|
|
20
|
+
*/
|
|
21
|
+
interface ThemeColors {
|
|
22
|
+
primaryColor: string;
|
|
23
|
+
onPrimaryColor: string;
|
|
24
|
+
onSurfaceColor: string;
|
|
25
|
+
selectedBgColor: string;
|
|
26
|
+
bgColor: string;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Updated constants for clock dial rendering
|
|
31
|
+
*/
|
|
32
|
+
const CLOCK_CONSTANTS = {
|
|
33
|
+
...DIAL_CONSTANTS,
|
|
34
|
+
KNOB_SIZE: 45, // Large hand knob size
|
|
35
|
+
CENTER_SIZE: 8,
|
|
36
|
+
NUMBER_SIZE: 24, // Same size for all numbers
|
|
37
|
+
INNER_RADIUS: 75, // for 24h inner ring
|
|
38
|
+
OUTER_RADIUS: 100, // for main ring
|
|
39
|
+
TRACK_WIDTH: 1.5,
|
|
40
|
+
HAND_WIDTH: 2
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Get theme colors from CSS variables
|
|
45
|
+
* @param {string} prefix - Component prefix (e.g., 'mtrl')
|
|
46
|
+
* @returns {ThemeColors} Object with theme colors
|
|
47
|
+
*/
|
|
48
|
+
function getThemeColors(prefix: string): ThemeColors {
|
|
49
|
+
const root = document.documentElement;
|
|
50
|
+
const styles = getComputedStyle(root);
|
|
51
|
+
console.log('styles', styles)
|
|
52
|
+
// Extract primary color
|
|
53
|
+
const primaryColor = styles.getPropertyValue(`--${prefix}-sys-color-primary`).trim() || '#6750A4';
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
console.log('primaryColor', primaryColor)
|
|
57
|
+
|
|
58
|
+
// Extract on-primary color
|
|
59
|
+
const onPrimaryColor = styles.getPropertyValue(`--${prefix}-sys-color-on-primary`).trim() || '#FFFFFF';
|
|
60
|
+
|
|
61
|
+
// Extract on-surface color
|
|
62
|
+
const onSurfaceColor = styles.getPropertyValue(`--${prefix}-sys-color-on-surface`).trim() || '#1C1B1F';
|
|
63
|
+
|
|
64
|
+
// Get RGB values for primary (for alpha calculations)
|
|
65
|
+
const primaryRgb = styles.getPropertyValue(`--${prefix}-sys-color-primary-rgb`).trim() || '103, 80, 164';
|
|
66
|
+
|
|
67
|
+
// Get RGB values for on-surface (for alpha calculations)
|
|
68
|
+
const onSurfaceRgb = styles.getPropertyValue(`--${prefix}-sys-color-on-surface-rgb`).trim() || '28, 27, 31';
|
|
69
|
+
|
|
70
|
+
// Create background with alpha
|
|
71
|
+
const bgColor = `rgba(${onSurfaceRgb}, 0.05)`;
|
|
72
|
+
|
|
73
|
+
// Create selected background with alpha
|
|
74
|
+
const selectedBgColor = `rgba(${primaryRgb}, 0.1)`;
|
|
75
|
+
|
|
76
|
+
return {
|
|
77
|
+
primaryColor,
|
|
78
|
+
onPrimaryColor,
|
|
79
|
+
onSurfaceColor,
|
|
80
|
+
bgColor,
|
|
81
|
+
selectedBgColor
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Renders a clock dial on canvas with improved visual design
|
|
87
|
+
* @param {HTMLCanvasElement} canvas - Canvas element to render on
|
|
88
|
+
* @param {TimeValue} timeValue - Current time value
|
|
89
|
+
* @param {ClockDialOptions} options - Rendering options
|
|
90
|
+
*/
|
|
91
|
+
export const renderClockDial = (
|
|
92
|
+
canvas: HTMLCanvasElement,
|
|
93
|
+
timeValue: TimeValue,
|
|
94
|
+
options: ClockDialOptions
|
|
95
|
+
): void => {
|
|
96
|
+
// Get the drawing context
|
|
97
|
+
const ctx = canvas.getContext('2d');
|
|
98
|
+
if (!ctx) {
|
|
99
|
+
console.error('Could not get canvas context');
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// Get theme colors on each render (to support theme changes)
|
|
104
|
+
const colors = getThemeColors(options.prefix);
|
|
105
|
+
|
|
106
|
+
// Ensure canvas dimensions are set correctly
|
|
107
|
+
const devicePixelRatio = window.devicePixelRatio || 1;
|
|
108
|
+
canvas.width = CLOCK_CONSTANTS.DIAMETER * devicePixelRatio;
|
|
109
|
+
canvas.height = CLOCK_CONSTANTS.DIAMETER * devicePixelRatio;
|
|
110
|
+
|
|
111
|
+
// Scale all drawing operations by the device pixel ratio
|
|
112
|
+
ctx.scale(devicePixelRatio, devicePixelRatio);
|
|
113
|
+
|
|
114
|
+
const centerX = CLOCK_CONSTANTS.DIAMETER / 2;
|
|
115
|
+
const centerY = CLOCK_CONSTANTS.DIAMETER / 2;
|
|
116
|
+
const radius = CLOCK_CONSTANTS.DIAMETER / 2;
|
|
117
|
+
|
|
118
|
+
// Clear canvas
|
|
119
|
+
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
|
120
|
+
|
|
121
|
+
// Draw clock face (outer circle)
|
|
122
|
+
ctx.beginPath();
|
|
123
|
+
ctx.arc(centerX, centerY, radius - 2, 0, 2 * Math.PI);
|
|
124
|
+
ctx.fillStyle = colors.bgColor;
|
|
125
|
+
ctx.fill();
|
|
126
|
+
|
|
127
|
+
// Draw center dot
|
|
128
|
+
ctx.beginPath();
|
|
129
|
+
ctx.arc(centerX, centerY, CLOCK_CONSTANTS.CENTER_SIZE / 2, 0, 2 * Math.PI);
|
|
130
|
+
ctx.fillStyle = colors.primaryColor;
|
|
131
|
+
ctx.fill();
|
|
132
|
+
|
|
133
|
+
// Draw numbers based on the active selector
|
|
134
|
+
if (options.activeSelector === 'hour') {
|
|
135
|
+
// For hour selector
|
|
136
|
+
if (options.format === TIME_FORMAT.MILITARY) {
|
|
137
|
+
// 24-hour clock face
|
|
138
|
+
drawHourNumbers24(ctx, centerX, centerY, radius, timeValue.hours, colors);
|
|
139
|
+
} else {
|
|
140
|
+
// 12-hour clock face
|
|
141
|
+
drawHourNumbers12(ctx, centerX, centerY, radius, timeValue.hours % 12 || 12, colors);
|
|
142
|
+
}
|
|
143
|
+
} else if (options.activeSelector === 'minute') {
|
|
144
|
+
// For minute selector
|
|
145
|
+
drawMinuteNumbers(ctx, centerX, centerY, radius, timeValue.minutes, colors);
|
|
146
|
+
} else if (options.activeSelector === 'second' && options.showSeconds) {
|
|
147
|
+
// For second selector
|
|
148
|
+
drawMinuteNumbers(ctx, centerX, centerY, radius, timeValue.seconds || 0, colors);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// Draw clock hand
|
|
152
|
+
drawClockHand(ctx, centerX, centerY, timeValue, options, colors);
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Draws 12-hour clock numbers
|
|
157
|
+
*/
|
|
158
|
+
const drawHourNumbers12 = (
|
|
159
|
+
ctx: CanvasRenderingContext2D,
|
|
160
|
+
centerX: number,
|
|
161
|
+
centerY: number,
|
|
162
|
+
radius: number,
|
|
163
|
+
selectedHour: number,
|
|
164
|
+
colors: ThemeColors
|
|
165
|
+
): void => {
|
|
166
|
+
const numbersRadius = CLOCK_CONSTANTS.OUTER_RADIUS;
|
|
167
|
+
|
|
168
|
+
for (let hour = 1; hour <= 12; hour++) {
|
|
169
|
+
const angle = (hour / 6) * Math.PI - Math.PI / 2; // Convert to radians, 0 at 12 o'clock
|
|
170
|
+
const x = centerX + numbersRadius * Math.cos(angle);
|
|
171
|
+
const y = centerY + numbersRadius * Math.sin(angle);
|
|
172
|
+
|
|
173
|
+
const isSelected = hour === selectedHour;
|
|
174
|
+
|
|
175
|
+
// Draw number background circle if it's the selected hour
|
|
176
|
+
if (isSelected) {
|
|
177
|
+
ctx.beginPath();
|
|
178
|
+
ctx.arc(x, y, CLOCK_CONSTANTS.NUMBER_SIZE / 2, 0, 2 * Math.PI);
|
|
179
|
+
ctx.fillStyle = colors.selectedBgColor;
|
|
180
|
+
ctx.fill();
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// Draw number text - same size for all, only color changes for selected
|
|
184
|
+
ctx.font = `16px Roboto, Arial, sans-serif`;
|
|
185
|
+
ctx.fillStyle = isSelected ? colors.primaryColor : colors.onSurfaceColor;
|
|
186
|
+
ctx.textAlign = 'center';
|
|
187
|
+
ctx.textBaseline = 'middle';
|
|
188
|
+
ctx.fillText(hour.toString(), x, y + 2);
|
|
189
|
+
}
|
|
190
|
+
};
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Draws 24-hour clock numbers (two rings)
|
|
194
|
+
*/
|
|
195
|
+
const drawHourNumbers24 = (
|
|
196
|
+
ctx: CanvasRenderingContext2D,
|
|
197
|
+
centerX: number,
|
|
198
|
+
centerY: number,
|
|
199
|
+
radius: number,
|
|
200
|
+
selectedHour: number,
|
|
201
|
+
colors: ThemeColors
|
|
202
|
+
): void => {
|
|
203
|
+
// Outer ring (1-12)
|
|
204
|
+
const outerRadius = CLOCK_CONSTANTS.OUTER_RADIUS;
|
|
205
|
+
|
|
206
|
+
for (let hour = 1; hour <= 12; hour++) {
|
|
207
|
+
const angle = (hour / 6) * Math.PI - Math.PI / 2; // Convert to radians, 0 at 12 o'clock
|
|
208
|
+
const x = centerX + outerRadius * Math.cos(angle);
|
|
209
|
+
const y = centerY + outerRadius * Math.sin(angle);
|
|
210
|
+
|
|
211
|
+
const isSelected = hour === selectedHour;
|
|
212
|
+
|
|
213
|
+
// Draw number background circle if it's the selected hour
|
|
214
|
+
if (isSelected) {
|
|
215
|
+
ctx.beginPath();
|
|
216
|
+
ctx.arc(x, y, CLOCK_CONSTANTS.NUMBER_SIZE / 2, 0, 2 * Math.PI);
|
|
217
|
+
ctx.fillStyle = colors.selectedBgColor;
|
|
218
|
+
ctx.fill();
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// Draw number text - same size for all
|
|
222
|
+
ctx.font = `16px Roboto, Arial, sans-serif`;
|
|
223
|
+
ctx.fillStyle = isSelected ? colors.primaryColor : colors.onSurfaceColor;
|
|
224
|
+
ctx.textAlign = 'center';
|
|
225
|
+
ctx.textBaseline = 'middle';
|
|
226
|
+
ctx.fillText(hour.toString(), x, y + 2);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// Inner ring (13-24/0)
|
|
230
|
+
const innerRadius = CLOCK_CONSTANTS.INNER_RADIUS;
|
|
231
|
+
|
|
232
|
+
for (let hour = 13; hour <= 24; hour++) {
|
|
233
|
+
const displayHour = hour === 24 ? 0 : hour; // Show 0 instead of 24
|
|
234
|
+
const angle = ((hour - 12) / 6) * Math.PI - Math.PI / 2; // Convert to radians, 0 at 12 o'clock
|
|
235
|
+
const x = centerX + innerRadius * Math.cos(angle);
|
|
236
|
+
const y = centerY + innerRadius * Math.sin(angle);
|
|
237
|
+
|
|
238
|
+
const isSelected = displayHour === selectedHour;
|
|
239
|
+
|
|
240
|
+
// Draw number background circle if it's the selected hour
|
|
241
|
+
if (isSelected) {
|
|
242
|
+
ctx.beginPath();
|
|
243
|
+
ctx.arc(x, y, CLOCK_CONSTANTS.NUMBER_SIZE / 2, 0, 2 * Math.PI);
|
|
244
|
+
ctx.fillStyle = colors.selectedBgColor;
|
|
245
|
+
ctx.fill();
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// Draw number text - same size but slightly smaller for inner ring
|
|
249
|
+
ctx.font = `14px Roboto, Arial, sans-serif`;
|
|
250
|
+
ctx.fillStyle = isSelected ? colors.primaryColor : colors.onSurfaceColor;
|
|
251
|
+
ctx.textAlign = 'center';
|
|
252
|
+
ctx.textBaseline = 'middle';
|
|
253
|
+
ctx.fillText(padZero(displayHour), x, y + 2);
|
|
254
|
+
}
|
|
255
|
+
};
|
|
256
|
+
|
|
257
|
+
/**
|
|
258
|
+
* Draws minute numbers (0, 5, 10, ..., 55)
|
|
259
|
+
*/
|
|
260
|
+
const drawMinuteNumbers = (
|
|
261
|
+
ctx: CanvasRenderingContext2D,
|
|
262
|
+
centerX: number,
|
|
263
|
+
centerY: number,
|
|
264
|
+
radius: number,
|
|
265
|
+
selectedMinute: number,
|
|
266
|
+
colors: ThemeColors
|
|
267
|
+
): void => {
|
|
268
|
+
const numbersRadius = CLOCK_CONSTANTS.OUTER_RADIUS;
|
|
269
|
+
|
|
270
|
+
// Draw minute markers in 5-minute increments
|
|
271
|
+
for (let i = 0; i < 60; i += 5) {
|
|
272
|
+
const angle = (i / 30) * Math.PI - Math.PI / 2; // Convert to radians, 0 at 12 o'clock
|
|
273
|
+
const x = centerX + numbersRadius * Math.cos(angle);
|
|
274
|
+
const y = centerY + numbersRadius * Math.sin(angle);
|
|
275
|
+
|
|
276
|
+
const isSelected = i === selectedMinute;
|
|
277
|
+
|
|
278
|
+
// Draw number background circle if it's the selected minute
|
|
279
|
+
if (isSelected) {
|
|
280
|
+
ctx.beginPath();
|
|
281
|
+
ctx.arc(x, y, CLOCK_CONSTANTS.NUMBER_SIZE / 2, 0, 2 * Math.PI);
|
|
282
|
+
ctx.fillStyle = colors.selectedBgColor;
|
|
283
|
+
ctx.fill();
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
// Draw number text - same size for all
|
|
287
|
+
ctx.font = `16px Roboto, Arial, sans-serif`;
|
|
288
|
+
ctx.fillStyle = isSelected ? colors.primaryColor : colors.onSurfaceColor;
|
|
289
|
+
ctx.textAlign = 'center';
|
|
290
|
+
ctx.textBaseline = 'middle';
|
|
291
|
+
ctx.fillText(padZero(i), x, y);
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
// If selected minute is not a multiple of 5, draw a special marker for it
|
|
295
|
+
if (selectedMinute % 5 !== 0) {
|
|
296
|
+
const angle = (selectedMinute / 30) * Math.PI - Math.PI / 2;
|
|
297
|
+
const x = centerX + numbersRadius * Math.cos(angle);
|
|
298
|
+
const y = centerY + numbersRadius * Math.sin(angle);
|
|
299
|
+
|
|
300
|
+
// Draw selected minute background
|
|
301
|
+
ctx.beginPath();
|
|
302
|
+
ctx.arc(x, y, CLOCK_CONSTANTS.NUMBER_SIZE / 2, 0, 2 * Math.PI);
|
|
303
|
+
ctx.fillStyle = colors.selectedBgColor;
|
|
304
|
+
ctx.fill();
|
|
305
|
+
|
|
306
|
+
// Draw selected minute text
|
|
307
|
+
ctx.font = `16px Roboto, Arial, sans-serif`;
|
|
308
|
+
ctx.fillStyle = colors.primaryColor;
|
|
309
|
+
ctx.textAlign = 'center';
|
|
310
|
+
ctx.textBaseline = 'middle';
|
|
311
|
+
ctx.fillText(padZero(selectedMinute), x, y);
|
|
312
|
+
}
|
|
313
|
+
};
|
|
314
|
+
|
|
315
|
+
/**
|
|
316
|
+
* Draws the clock hand
|
|
317
|
+
*/
|
|
318
|
+
const drawClockHand = (
|
|
319
|
+
ctx: CanvasRenderingContext2D,
|
|
320
|
+
centerX: number,
|
|
321
|
+
centerY: number,
|
|
322
|
+
timeValue: TimeValue,
|
|
323
|
+
options: ClockDialOptions,
|
|
324
|
+
colors: ThemeColors
|
|
325
|
+
): void => {
|
|
326
|
+
let angle: number, handLength: number;
|
|
327
|
+
|
|
328
|
+
// Calculate angle based on active selector
|
|
329
|
+
if (options.activeSelector === 'hour') {
|
|
330
|
+
if (options.format === TIME_FORMAT.MILITARY) {
|
|
331
|
+
// 24-hour format
|
|
332
|
+
if (timeValue.hours >= 12) {
|
|
333
|
+
// Inner ring (12-23)
|
|
334
|
+
const hour12 = timeValue.hours % 12 || 12;
|
|
335
|
+
angle = (hour12 / 6) * Math.PI - Math.PI / 2;
|
|
336
|
+
handLength = CLOCK_CONSTANTS.INNER_RADIUS;
|
|
337
|
+
} else {
|
|
338
|
+
// Outer ring (0-11)
|
|
339
|
+
let hour12 = timeValue.hours;
|
|
340
|
+
if (hour12 === 0) hour12 = 12;
|
|
341
|
+
angle = (hour12 / 6) * Math.PI - Math.PI / 2;
|
|
342
|
+
handLength = CLOCK_CONSTANTS.OUTER_RADIUS;
|
|
343
|
+
}
|
|
344
|
+
} else {
|
|
345
|
+
// 12-hour format
|
|
346
|
+
const hour12 = timeValue.hours % 12 || 12;
|
|
347
|
+
angle = (hour12 / 6) * Math.PI - Math.PI / 2;
|
|
348
|
+
handLength = CLOCK_CONSTANTS.OUTER_RADIUS;
|
|
349
|
+
}
|
|
350
|
+
} else if (options.activeSelector === 'minute') {
|
|
351
|
+
// Minutes (0-59)
|
|
352
|
+
angle = (timeValue.minutes / 30) * Math.PI - Math.PI / 2;
|
|
353
|
+
handLength = CLOCK_CONSTANTS.OUTER_RADIUS;
|
|
354
|
+
} else if (options.activeSelector === 'second') {
|
|
355
|
+
// Seconds (0-59)
|
|
356
|
+
angle = ((timeValue.seconds || 0) / 30) * Math.PI - Math.PI / 2;
|
|
357
|
+
handLength = CLOCK_CONSTANTS.OUTER_RADIUS;
|
|
358
|
+
} else {
|
|
359
|
+
return; // No valid selector
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
// Draw hand line
|
|
363
|
+
ctx.beginPath();
|
|
364
|
+
ctx.moveTo(centerX, centerY);
|
|
365
|
+
ctx.lineTo(
|
|
366
|
+
centerX + handLength * Math.cos(angle),
|
|
367
|
+
centerY + handLength * Math.sin(angle)
|
|
368
|
+
);
|
|
369
|
+
ctx.lineWidth = CLOCK_CONSTANTS.HAND_WIDTH;
|
|
370
|
+
ctx.strokeStyle = colors.primaryColor;
|
|
371
|
+
ctx.stroke();
|
|
372
|
+
|
|
373
|
+
// Draw hand knob at the end (large as in second image)
|
|
374
|
+
const knobX = centerX + handLength * Math.cos(angle);
|
|
375
|
+
const knobY = centerY + handLength * Math.sin(angle);
|
|
376
|
+
|
|
377
|
+
// Draw the background for the knob
|
|
378
|
+
ctx.beginPath();
|
|
379
|
+
ctx.arc(knobX, knobY, CLOCK_CONSTANTS.KNOB_SIZE / 2, 0, 2 * Math.PI);
|
|
380
|
+
ctx.fillStyle = colors.primaryColor;
|
|
381
|
+
ctx.fill();
|
|
382
|
+
|
|
383
|
+
// Draw the selected value in the knob
|
|
384
|
+
ctx.font = `16px Roboto, Arial, sans-serif`;
|
|
385
|
+
ctx.fillStyle = colors.onPrimaryColor;
|
|
386
|
+
ctx.textAlign = 'center';
|
|
387
|
+
ctx.textBaseline = 'middle';
|
|
388
|
+
|
|
389
|
+
// Display the appropriate value based on active selector
|
|
390
|
+
let displayValue = '';
|
|
391
|
+
if (options.activeSelector === 'hour') {
|
|
392
|
+
if (options.format === TIME_FORMAT.MILITARY) {
|
|
393
|
+
displayValue = padZero(timeValue.hours);
|
|
394
|
+
} else {
|
|
395
|
+
displayValue = String(timeValue.hours % 12 || 12);
|
|
396
|
+
}
|
|
397
|
+
} else if (options.activeSelector === 'minute') {
|
|
398
|
+
displayValue = padZero(timeValue.minutes);
|
|
399
|
+
} else if (options.activeSelector === 'second') {
|
|
400
|
+
displayValue = padZero(timeValue.seconds || 0);
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
ctx.fillText(displayValue, knobX, knobY);
|
|
404
|
+
};
|
|
405
|
+
|
|
406
|
+
/**
|
|
407
|
+
* Get time value from click coordinates on the canvas
|
|
408
|
+
* @param {HTMLCanvasElement} canvas - Canvas element
|
|
409
|
+
* @param {number} x - Click X coordinate relative to canvas
|
|
410
|
+
* @param {number} y - Click Y coordinate relative to canvas
|
|
411
|
+
* @param {ClockDialOptions} options - Clock dial options
|
|
412
|
+
* @returns {number | null} Selected value or null if not valid
|
|
413
|
+
*/
|
|
414
|
+
export const getTimeValueFromClick = (
|
|
415
|
+
canvas: HTMLCanvasElement,
|
|
416
|
+
x: number,
|
|
417
|
+
y: number,
|
|
418
|
+
options: ClockDialOptions
|
|
419
|
+
): number | null => {
|
|
420
|
+
const centerX = CLOCK_CONSTANTS.DIAMETER / 2;
|
|
421
|
+
const centerY = CLOCK_CONSTANTS.DIAMETER / 2;
|
|
422
|
+
|
|
423
|
+
// Calculate click position relative to center
|
|
424
|
+
const relX = x - centerX;
|
|
425
|
+
const relY = y - centerY;
|
|
426
|
+
|
|
427
|
+
// Calculate distance from center
|
|
428
|
+
const distance = Math.sqrt(relX * relX + relY * relY);
|
|
429
|
+
|
|
430
|
+
// Calculate angle in radians (0 at 12 o'clock, clockwise)
|
|
431
|
+
let angle = Math.atan2(relY, relX) + Math.PI / 2;
|
|
432
|
+
if (angle < 0) angle += 2 * Math.PI;
|
|
433
|
+
|
|
434
|
+
// Convert angle to value based on active selector
|
|
435
|
+
if (options.activeSelector === 'hour') {
|
|
436
|
+
if (options.format === TIME_FORMAT.MILITARY) {
|
|
437
|
+
// 24-hour format
|
|
438
|
+
const innerThreshold = (CLOCK_CONSTANTS.INNER_RADIUS + CLOCK_CONSTANTS.OUTER_RADIUS) / 2;
|
|
439
|
+
const isInnerRing = distance < innerThreshold;
|
|
440
|
+
|
|
441
|
+
// Get base hour (0-11)
|
|
442
|
+
let hour = Math.round(angle / (Math.PI / 6)) % 12;
|
|
443
|
+
|
|
444
|
+
// Add 12 for inner ring (except for 12/0)
|
|
445
|
+
if (isInnerRing && hour !== 0) {
|
|
446
|
+
hour += 12;
|
|
447
|
+
} else if (!isInnerRing && hour === 0) {
|
|
448
|
+
hour = 12;
|
|
449
|
+
} else if (isInnerRing && hour === 0) {
|
|
450
|
+
hour = 0; // 12 AM is 0 in 24h format
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
return hour;
|
|
454
|
+
} else {
|
|
455
|
+
// 12-hour format
|
|
456
|
+
let hour = Math.round(angle / (Math.PI / 6)) % 12;
|
|
457
|
+
if (hour === 0) hour = 12;
|
|
458
|
+
return hour;
|
|
459
|
+
}
|
|
460
|
+
} else if (options.activeSelector === 'minute' || options.activeSelector === 'second') {
|
|
461
|
+
// Minutes or seconds (0-59)
|
|
462
|
+
const value = Math.round(angle / (Math.PI / 30)) % 60;
|
|
463
|
+
return value;
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
return null;
|
|
467
|
+
};
|
|
468
|
+
|
|
469
|
+
/**
|
|
470
|
+
* Update the clock dial when theme changes (to be called by external code)
|
|
471
|
+
* @param {HTMLCanvasElement} canvas - Canvas element
|
|
472
|
+
* @param {TimeValue} timeValue - Current time value
|
|
473
|
+
* @param {ClockDialOptions} options - Rendering options
|
|
474
|
+
*/
|
|
475
|
+
export const updateClockDialTheme = (
|
|
476
|
+
canvas: HTMLCanvasElement,
|
|
477
|
+
timeValue: TimeValue,
|
|
478
|
+
options: ClockDialOptions
|
|
479
|
+
): void => {
|
|
480
|
+
// Simply re-render with current state
|
|
481
|
+
renderClockDial(canvas, timeValue, options);
|
|
482
|
+
};
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
// src/components/timepicker/config.ts
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
createComponentConfig,
|
|
5
|
+
createElementConfig,
|
|
6
|
+
BaseComponentConfig
|
|
7
|
+
} from '../../core/config/component-config';
|
|
8
|
+
import { TimePickerConfig } from './types';
|
|
9
|
+
import {
|
|
10
|
+
TIME_PICKER_TYPE,
|
|
11
|
+
TIME_PICKER_ORIENTATION,
|
|
12
|
+
TIME_FORMAT,
|
|
13
|
+
DEFAULT_CLOCK_ICON,
|
|
14
|
+
DEFAULT_KEYBOARD_ICON
|
|
15
|
+
} from './constants';
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Default configuration for the TimePicker component
|
|
19
|
+
*/
|
|
20
|
+
export const defaultConfig: TimePickerConfig = {
|
|
21
|
+
type: TIME_PICKER_TYPE.DIAL,
|
|
22
|
+
format: TIME_FORMAT.AMPM,
|
|
23
|
+
orientation: TIME_PICKER_ORIENTATION.VERTICAL,
|
|
24
|
+
showSeconds: false,
|
|
25
|
+
closeOnSelect: true,
|
|
26
|
+
minuteStep: 1,
|
|
27
|
+
secondStep: 1,
|
|
28
|
+
cancelText: 'Cancel',
|
|
29
|
+
confirmText: 'OK',
|
|
30
|
+
isOpen: false,
|
|
31
|
+
clockIcon: DEFAULT_CLOCK_ICON,
|
|
32
|
+
keyboardIcon: DEFAULT_KEYBOARD_ICON
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Creates the base configuration for TimePicker component
|
|
37
|
+
* @param {TimePickerConfig} config - User provided configuration
|
|
38
|
+
* @returns {TimePickerConfig} Complete configuration with defaults applied
|
|
39
|
+
*/
|
|
40
|
+
export const createBaseConfig = (config: TimePickerConfig = {}): TimePickerConfig =>
|
|
41
|
+
createComponentConfig(defaultConfig, config, 'time-picker') as TimePickerConfig;
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Generates element configuration for the TimePicker container
|
|
45
|
+
* @param {TimePickerConfig} config - TimePicker configuration
|
|
46
|
+
* @returns {Object} Element configuration object for withElement
|
|
47
|
+
*/
|
|
48
|
+
export const getContainerConfig = (config: TimePickerConfig) => {
|
|
49
|
+
return createElementConfig(config, {
|
|
50
|
+
tag: 'div',
|
|
51
|
+
attrs: {
|
|
52
|
+
role: 'dialog',
|
|
53
|
+
'aria-modal': 'true',
|
|
54
|
+
'aria-labelledby': `${config.prefix}-time-picker-title`
|
|
55
|
+
},
|
|
56
|
+
className: [
|
|
57
|
+
config.class,
|
|
58
|
+
config.isOpen ? `${config.prefix}-time-picker--open` : ''
|
|
59
|
+
],
|
|
60
|
+
forwardEvents: {
|
|
61
|
+
click: true,
|
|
62
|
+
keydown: true
|
|
63
|
+
},
|
|
64
|
+
interactive: true
|
|
65
|
+
});
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Generates element configuration for the TimePicker modal
|
|
70
|
+
* @param {TimePickerConfig} config - TimePicker configuration
|
|
71
|
+
* @returns {Object} Element configuration object for withElement
|
|
72
|
+
*/
|
|
73
|
+
export const getModalConfig = (config: TimePickerConfig) => {
|
|
74
|
+
return createElementConfig(config, {
|
|
75
|
+
tag: 'div',
|
|
76
|
+
attrs: {
|
|
77
|
+
role: 'presentation'
|
|
78
|
+
},
|
|
79
|
+
className: `${config.prefix}-time-picker-modal`,
|
|
80
|
+
forwardEvents: {
|
|
81
|
+
click: (component, event) => {
|
|
82
|
+
// Only close if clicking directly on the modal backdrop
|
|
83
|
+
if (event.target === component.element) {
|
|
84
|
+
return true;
|
|
85
|
+
}
|
|
86
|
+
return false;
|
|
87
|
+
}
|
|
88
|
+
},
|
|
89
|
+
interactive: true
|
|
90
|
+
});
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Generates element configuration for the TimePicker dialog
|
|
95
|
+
* @param {TimePickerConfig} config - TimePicker configuration
|
|
96
|
+
* @returns {Object} Element configuration object for withElement
|
|
97
|
+
*/
|
|
98
|
+
export const getDialogConfig = (config: TimePickerConfig) => {
|
|
99
|
+
return createElementConfig(config, {
|
|
100
|
+
tag: 'div',
|
|
101
|
+
className: [
|
|
102
|
+
`${config.prefix}-time-picker-dialog`,
|
|
103
|
+
`${config.prefix}-time-picker-dialog--${config.type}`,
|
|
104
|
+
`${config.prefix}-time-picker-dialog--${config.orientation}`,
|
|
105
|
+
`${config.prefix}-time-picker-dialog--${config.format}`
|
|
106
|
+
],
|
|
107
|
+
forwardEvents: {
|
|
108
|
+
click: true
|
|
109
|
+
},
|
|
110
|
+
interactive: true
|
|
111
|
+
});
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Creates API configuration for the TimePicker component
|
|
116
|
+
* @param {Object} comp - Component with events and lifecycle features
|
|
117
|
+
* @returns {Object} API configuration object
|
|
118
|
+
*/
|
|
119
|
+
export const getApiConfig = (comp: any) => ({
|
|
120
|
+
events: {
|
|
121
|
+
on: (event: string, handler: Function) => comp.on(event, handler),
|
|
122
|
+
off: (event: string, handler: Function) => comp.off(event, handler),
|
|
123
|
+
emit: (event: string, data?: any) => comp.emit(event, data),
|
|
124
|
+
},
|
|
125
|
+
lifecycle: {
|
|
126
|
+
destroy: () => comp.lifecycle.destroy()
|
|
127
|
+
}
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
export default defaultConfig;
|