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.
Files changed (196) hide show
  1. package/index.ts +18 -0
  2. package/package.json +1 -1
  3. package/src/components/badge/_styles.scss +123 -115
  4. package/src/components/badge/api.ts +57 -59
  5. package/src/components/badge/badge.ts +16 -2
  6. package/src/components/badge/config.ts +65 -11
  7. package/src/components/badge/constants.ts +22 -12
  8. package/src/components/badge/features.ts +44 -40
  9. package/src/components/badge/types.ts +42 -30
  10. package/src/components/bottom-app-bar/_styles.scss +103 -0
  11. package/src/components/bottom-app-bar/bottom-app-bar.ts +196 -0
  12. package/src/components/bottom-app-bar/config.ts +73 -0
  13. package/src/components/bottom-app-bar/index.ts +11 -0
  14. package/src/components/bottom-app-bar/types.ts +108 -0
  15. package/src/components/button/_styles.scss +0 -66
  16. package/src/components/button/api.ts +5 -0
  17. package/src/components/button/button.ts +0 -2
  18. package/src/components/button/config.ts +5 -0
  19. package/src/components/button/constants.ts +0 -6
  20. package/src/components/button/index.ts +2 -2
  21. package/src/components/button/types.ts +7 -7
  22. package/src/components/card/_styles.scss +67 -25
  23. package/src/components/card/api.ts +54 -3
  24. package/src/components/card/card.ts +25 -6
  25. package/src/components/card/config.ts +189 -22
  26. package/src/components/card/constants.ts +20 -19
  27. package/src/components/card/content.ts +299 -2
  28. package/src/components/card/features.ts +158 -4
  29. package/src/components/card/index.ts +31 -9
  30. package/src/components/card/types.ts +166 -15
  31. package/src/components/checkbox/_styles.scss +0 -2
  32. package/src/components/chip/chip.ts +1 -9
  33. package/src/components/chip/constants.ts +0 -10
  34. package/src/components/chip/index.ts +1 -1
  35. package/src/components/chip/types.ts +1 -4
  36. package/src/components/datepicker/_styles.scss +358 -0
  37. package/src/components/datepicker/api.ts +272 -0
  38. package/src/components/datepicker/config.ts +144 -0
  39. package/src/components/datepicker/constants.ts +98 -0
  40. package/src/components/datepicker/datepicker.ts +346 -0
  41. package/src/components/datepicker/index.ts +9 -0
  42. package/src/components/datepicker/render.ts +452 -0
  43. package/src/components/datepicker/types.ts +268 -0
  44. package/src/components/datepicker/utils.ts +290 -0
  45. package/src/components/dialog/_styles.scss +174 -128
  46. package/src/components/dialog/api.ts +48 -13
  47. package/src/components/dialog/config.ts +9 -5
  48. package/src/components/dialog/dialog.ts +6 -3
  49. package/src/components/dialog/features.ts +290 -130
  50. package/src/components/dialog/types.ts +7 -4
  51. package/src/components/divider/_styles.scss +57 -0
  52. package/src/components/divider/config.ts +81 -0
  53. package/src/components/divider/divider.ts +37 -0
  54. package/src/components/divider/features.ts +207 -0
  55. package/src/components/divider/index.ts +5 -0
  56. package/src/components/divider/types.ts +55 -0
  57. package/src/components/extended-fab/_styles.scss +267 -0
  58. package/src/components/extended-fab/api.ts +141 -0
  59. package/src/components/extended-fab/config.ts +108 -0
  60. package/src/components/extended-fab/constants.ts +36 -0
  61. package/src/components/extended-fab/extended-fab.ts +125 -0
  62. package/src/components/extended-fab/index.ts +4 -0
  63. package/src/components/extended-fab/types.ts +287 -0
  64. package/src/components/fab/_styles.scss +225 -0
  65. package/src/components/fab/api.ts +97 -0
  66. package/src/components/fab/config.ts +94 -0
  67. package/src/components/fab/constants.ts +41 -0
  68. package/src/components/fab/fab.ts +67 -0
  69. package/src/components/fab/index.ts +4 -0
  70. package/src/components/fab/types.ts +234 -0
  71. package/src/components/navigation/_styles.scss +1 -0
  72. package/src/components/navigation/api.ts +78 -50
  73. package/src/components/navigation/features/items.ts +280 -0
  74. package/src/components/navigation/nav-item.ts +72 -23
  75. package/src/components/navigation/navigation.ts +54 -2
  76. package/src/components/navigation/types.ts +210 -188
  77. package/src/components/progress/_styles.scss +0 -65
  78. package/src/components/progress/config.ts +1 -2
  79. package/src/components/progress/constants.ts +0 -14
  80. package/src/components/progress/index.ts +1 -1
  81. package/src/components/progress/progress.ts +1 -4
  82. package/src/components/progress/types.ts +1 -4
  83. package/src/components/radios/_styles.scss +0 -45
  84. package/src/components/radios/api.ts +85 -60
  85. package/src/components/radios/config.ts +1 -2
  86. package/src/components/radios/constants.ts +0 -9
  87. package/src/components/radios/index.ts +1 -1
  88. package/src/components/radios/radio.ts +34 -11
  89. package/src/components/radios/radios.ts +2 -1
  90. package/src/components/radios/types.ts +1 -7
  91. package/src/components/search/_styles.scss +306 -0
  92. package/src/components/search/api.ts +203 -0
  93. package/src/components/search/config.ts +87 -0
  94. package/src/components/search/constants.ts +21 -0
  95. package/src/components/search/features/index.ts +4 -0
  96. package/src/components/search/features/search.ts +718 -0
  97. package/src/components/search/features/states.ts +165 -0
  98. package/src/components/search/features/structure.ts +198 -0
  99. package/src/components/search/index.ts +10 -0
  100. package/src/components/search/search.ts +52 -0
  101. package/src/components/search/types.ts +163 -0
  102. package/src/components/segmented-button/_styles.scss +117 -0
  103. package/src/components/segmented-button/config.ts +67 -0
  104. package/src/components/segmented-button/constants.ts +42 -0
  105. package/src/components/segmented-button/index.ts +4 -0
  106. package/src/components/segmented-button/segment.ts +155 -0
  107. package/src/components/segmented-button/segmented-button.ts +250 -0
  108. package/src/components/segmented-button/types.ts +219 -0
  109. package/src/components/slider/_styles.scss +221 -168
  110. package/src/components/slider/accessibility.md +59 -0
  111. package/src/components/slider/api.ts +41 -120
  112. package/src/components/slider/config.ts +51 -49
  113. package/src/components/slider/features/handlers.ts +495 -0
  114. package/src/components/slider/features/index.ts +1 -2
  115. package/src/components/slider/features/slider.ts +66 -84
  116. package/src/components/slider/features/states.ts +195 -0
  117. package/src/components/slider/features/structure.ts +141 -184
  118. package/src/components/slider/features/ui.ts +150 -201
  119. package/src/components/slider/index.ts +2 -11
  120. package/src/components/slider/slider.ts +9 -12
  121. package/src/components/slider/types.ts +39 -24
  122. package/src/components/switch/_styles.scss +0 -2
  123. package/src/components/tabs/_styles.scss +346 -154
  124. package/src/components/tabs/api.ts +178 -400
  125. package/src/components/tabs/config.ts +46 -52
  126. package/src/components/tabs/constants.ts +85 -8
  127. package/src/components/tabs/features.ts +403 -0
  128. package/src/components/tabs/index.ts +60 -3
  129. package/src/components/tabs/indicator.ts +285 -0
  130. package/src/components/tabs/responsive.ts +144 -0
  131. package/src/components/tabs/scroll-indicators.ts +149 -0
  132. package/src/components/tabs/state.ts +186 -0
  133. package/src/components/tabs/tab-api.ts +258 -0
  134. package/src/components/tabs/tab.ts +255 -0
  135. package/src/components/tabs/tabs.ts +50 -31
  136. package/src/components/tabs/types.ts +332 -128
  137. package/src/components/tabs/utils.ts +107 -0
  138. package/src/components/textfield/_styles.scss +0 -98
  139. package/src/components/textfield/config.ts +2 -3
  140. package/src/components/textfield/constants.ts +0 -14
  141. package/src/components/textfield/index.ts +2 -2
  142. package/src/components/textfield/textfield.ts +0 -2
  143. package/src/components/textfield/types.ts +1 -4
  144. package/src/components/timepicker/README.md +277 -0
  145. package/src/components/timepicker/_styles.scss +451 -0
  146. package/src/components/timepicker/api.ts +632 -0
  147. package/src/components/timepicker/clockdial.ts +482 -0
  148. package/src/components/timepicker/config.ts +130 -0
  149. package/src/components/timepicker/constants.ts +138 -0
  150. package/src/components/timepicker/index.ts +8 -0
  151. package/src/components/timepicker/render.ts +613 -0
  152. package/src/components/timepicker/timepicker.ts +117 -0
  153. package/src/components/timepicker/types.ts +336 -0
  154. package/src/components/timepicker/utils.ts +241 -0
  155. package/src/components/top-app-bar/_styles.scss +225 -0
  156. package/src/components/top-app-bar/config.ts +83 -0
  157. package/src/components/top-app-bar/index.ts +11 -0
  158. package/src/components/top-app-bar/top-app-bar.ts +316 -0
  159. package/src/components/top-app-bar/types.ts +140 -0
  160. package/src/core/build/_ripple.scss +6 -6
  161. package/src/core/build/ripple.ts +72 -95
  162. package/src/core/compose/component.ts +1 -1
  163. package/src/core/compose/features/badge.ts +79 -0
  164. package/src/core/compose/features/icon.ts +3 -1
  165. package/src/core/compose/features/index.ts +3 -1
  166. package/src/core/compose/features/ripple.ts +4 -1
  167. package/src/core/compose/features/textlabel.ts +26 -2
  168. package/src/core/dom/create.ts +5 -0
  169. package/src/index.ts +9 -0
  170. package/src/styles/abstract/_theme.scss +115 -3
  171. package/src/styles/themes/_autumn.scss +21 -0
  172. package/src/styles/themes/_base-theme.scss +61 -0
  173. package/src/styles/themes/_baseline.scss +58 -0
  174. package/src/styles/themes/_bluekhaki.scss +125 -0
  175. package/src/styles/themes/_brownbeige.scss +125 -0
  176. package/src/styles/themes/_browngreen.scss +125 -0
  177. package/src/styles/themes/_forest.scss +6 -0
  178. package/src/styles/themes/_greenbeige.scss +125 -0
  179. package/src/styles/themes/_material.scss +125 -0
  180. package/src/styles/themes/_ocean.scss +6 -0
  181. package/src/styles/themes/_sageivory.scss +125 -0
  182. package/src/styles/themes/_spring.scss +6 -0
  183. package/src/styles/themes/_summer.scss +5 -0
  184. package/src/styles/themes/_sunset.scss +5 -0
  185. package/src/styles/themes/_tealcaramel.scss +125 -0
  186. package/src/styles/themes/_winter.scss +6 -0
  187. package/src/components/card/actions.ts +0 -48
  188. package/src/components/card/header.ts +0 -88
  189. package/src/components/card/media.ts +0 -52
  190. package/src/components/navigation/features/items.js +0 -192
  191. package/src/components/slider/features/appearance.ts +0 -94
  192. package/src/components/slider/features/disabled.ts +0 -43
  193. package/src/components/slider/features/events.ts +0 -164
  194. package/src/components/slider/features/interactions.ts +0 -261
  195. package/src/components/slider/features/keyboard.ts +0 -112
  196. 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;