@gfazioli/mantine-clock 2.1.17 → 3.1.0

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 (61) hide show
  1. package/README.md +9 -4
  2. package/dist/cjs/Clock.cjs +240 -563
  3. package/dist/cjs/Clock.cjs.map +1 -1
  4. package/dist/cjs/Clock.module.css.cjs +1 -1
  5. package/dist/cjs/ClockDigital.cjs +123 -0
  6. package/dist/cjs/ClockDigital.cjs.map +1 -0
  7. package/dist/cjs/ClockDigital.module.css.cjs +7 -0
  8. package/dist/cjs/ClockDigital.module.css.cjs.map +1 -0
  9. package/dist/cjs/ClockDigitalMediaVariables.cjs +76 -0
  10. package/dist/cjs/ClockDigitalMediaVariables.cjs.map +1 -0
  11. package/dist/cjs/ClockFaceStatic.cjs +96 -0
  12. package/dist/cjs/ClockFaceStatic.cjs.map +1 -0
  13. package/dist/cjs/RealClock.cjs +379 -0
  14. package/dist/cjs/RealClock.cjs.map +1 -0
  15. package/dist/cjs/clock-utils.cjs +63 -0
  16. package/dist/cjs/clock-utils.cjs.map +1 -0
  17. package/dist/cjs/geometry.cjs +217 -0
  18. package/dist/cjs/geometry.cjs.map +1 -0
  19. package/dist/cjs/hooks/use-clock-count-down.cjs +92 -124
  20. package/dist/cjs/hooks/use-clock-count-down.cjs.map +1 -1
  21. package/dist/cjs/hooks/use-clock.cjs +27 -38
  22. package/dist/cjs/hooks/use-clock.cjs.map +1 -1
  23. package/dist/cjs/index.cjs +6 -0
  24. package/dist/cjs/index.cjs.map +1 -1
  25. package/dist/esm/Clock.mjs +240 -563
  26. package/dist/esm/Clock.mjs.map +1 -1
  27. package/dist/esm/Clock.module.css.mjs +1 -1
  28. package/dist/esm/ClockDigital.mjs +121 -0
  29. package/dist/esm/ClockDigital.mjs.map +1 -0
  30. package/dist/esm/ClockDigital.module.css.mjs +5 -0
  31. package/dist/esm/ClockDigital.module.css.mjs.map +1 -0
  32. package/dist/esm/ClockDigitalMediaVariables.mjs +74 -0
  33. package/dist/esm/ClockDigitalMediaVariables.mjs.map +1 -0
  34. package/dist/esm/ClockFaceStatic.mjs +94 -0
  35. package/dist/esm/ClockFaceStatic.mjs.map +1 -0
  36. package/dist/esm/RealClock.mjs +377 -0
  37. package/dist/esm/RealClock.mjs.map +1 -0
  38. package/dist/esm/clock-utils.mjs +56 -0
  39. package/dist/esm/clock-utils.mjs.map +1 -0
  40. package/dist/esm/geometry.mjs +208 -0
  41. package/dist/esm/geometry.mjs.map +1 -0
  42. package/dist/esm/hooks/use-clock-count-down.mjs +92 -124
  43. package/dist/esm/hooks/use-clock-count-down.mjs.map +1 -1
  44. package/dist/esm/hooks/use-clock.mjs +27 -38
  45. package/dist/esm/hooks/use-clock.mjs.map +1 -1
  46. package/dist/esm/index.mjs +2 -0
  47. package/dist/esm/index.mjs.map +1 -1
  48. package/dist/styles.css +2 -2
  49. package/dist/styles.layer.css +2 -2
  50. package/dist/types/Clock.d.ts +73 -4
  51. package/dist/types/ClockDigital.d.ts +50 -0
  52. package/dist/types/ClockDigitalMediaVariables.d.ts +9 -0
  53. package/dist/types/ClockFaceStatic.d.ts +18 -0
  54. package/dist/types/RealClock.d.ts +12 -0
  55. package/dist/types/clock-utils.d.ts +23 -0
  56. package/dist/types/geometry.d.ts +92 -0
  57. package/dist/types/hooks/use-clock-count-down.d.ts +25 -28
  58. package/dist/types/hooks/use-clock.d.ts +14 -10
  59. package/dist/types/index.d.mts +5 -1
  60. package/dist/types/index.d.ts +5 -1
  61. package/package.json +15 -7
@@ -1,26 +1,20 @@
1
1
  'use client';
2
2
  import dayjs from 'dayjs';
3
- import timezonePlugin from 'dayjs/plugin/timezone';
3
+ import timezone from 'dayjs/plugin/timezone';
4
4
  import utc from 'dayjs/plugin/utc';
5
- import React, { useState, useRef, useEffect } from 'react';
6
- import { createVarsResolver, px, getSize, parseThemeColor, factory, useProps, useStyles, Box, Text } from '@mantine/core';
5
+ import React, { useState, useRef, useMemo, useEffect } from 'react';
6
+ import { createVarsResolver, factory, useProps, useStyles, useMatches, px, getSize, Box, parseThemeColor } from '@mantine/core';
7
+ import { useCallbackRef, useMergedRef } from '@mantine/hooks';
8
+ import { round2, defaultClockProps, parseTimeValue } from './clock-utils.mjs';
9
+ import { ClockDigital } from './ClockDigital.mjs';
10
+ import { ClockFaceStatic } from './ClockFaceStatic.mjs';
11
+ import { createGeometry } from './geometry.mjs';
12
+ import { RealClock } from './RealClock.mjs';
7
13
  import classes from './Clock.module.css.mjs';
8
14
 
9
15
  dayjs.extend(utc);
10
- dayjs.extend(timezonePlugin);
11
- const defaultProps = {
12
- size: 400,
13
- hourHandSize: 0.017,
14
- minuteHandSize: 0.011,
15
- secondHandSize: 6e-3,
16
- hourHandLength: 0.4,
17
- minuteHandLength: 0.57,
18
- secondHandLength: 0.68,
19
- secondHandOpacity: 1,
20
- minuteHandOpacity: 1,
21
- hourHandOpacity: 1,
22
- running: true
23
- };
16
+ dayjs.extend(timezone);
17
+ const defaultProps = defaultClockProps;
24
18
  const defaultClockSizes = {
25
19
  xs: 100,
26
20
  sm: 200,
@@ -28,464 +22,41 @@ const defaultClockSizes = {
28
22
  lg: 480,
29
23
  xl: 512
30
24
  };
31
- const varsResolver = createVarsResolver(
32
- (theme, {
33
- size,
34
- color,
35
- hourTicksColor,
36
- hourTicksOpacity,
37
- minuteTicksColor,
38
- minuteTicksOpacity,
39
- primaryNumbersColor,
40
- primaryNumbersOpacity,
41
- secondaryNumbersColor,
42
- secondaryNumbersOpacity,
43
- secondHandColor,
44
- minuteHandColor,
45
- hourHandColor,
46
- secondsArcColor,
47
- minutesArcColor,
48
- hoursArcColor
49
- }) => {
50
- const sizeValue = size || "md";
51
- const clockSize = typeof sizeValue === "string" && sizeValue in defaultClockSizes ? defaultClockSizes[sizeValue] : sizeValue;
52
- const effectiveSize = Math.round(px(getSize(clockSize, "clock-size")));
53
- return {
54
- root: {
55
- "--clock-size": `${effectiveSize}px`,
56
- "--clock-color": parseThemeColor({
57
- color: color || "",
58
- theme
59
- }).value,
60
- "--clock-hour-ticks-color": parseThemeColor({
61
- color: hourTicksColor || "",
62
- theme
63
- }).value,
64
- "--clock-hour-ticks-opacity": (Math.round((hourTicksOpacity || 1) * 100) / 100).toString(),
65
- "--clock-minute-ticks-color": parseThemeColor({
66
- color: minuteTicksColor || "",
67
- theme
68
- }).value,
69
- "--clock-minute-ticks-opacity": (Math.round((minuteTicksOpacity || 1) * 100) / 100).toString(),
70
- "--clock-primary-numbers-color": parseThemeColor({
71
- color: primaryNumbersColor || "",
72
- theme
73
- }).value,
74
- "--clock-primary-numbers-opacity": (Math.round((primaryNumbersOpacity || 1) * 100) / 100).toString(),
75
- "--clock-secondary-numbers-color": parseThemeColor({
76
- color: secondaryNumbersColor || "",
77
- theme
78
- }).value,
79
- "--clock-secondary-numbers-opacity": (Math.round((secondaryNumbersOpacity || 1) * 100) / 100).toString(),
80
- "--clock-second-hand-color": parseThemeColor({
81
- color: secondHandColor || "",
82
- theme
83
- }).value,
84
- "--clock-minute-hand-color": parseThemeColor({
85
- color: minuteHandColor || "",
86
- theme
87
- }).value,
88
- "--clock-hour-hand-color": parseThemeColor({
89
- color: hourHandColor || "",
90
- theme
91
- }).value,
92
- "--clock-seconds-arc-color": parseThemeColor({
93
- color: secondsArcColor || secondHandColor || "",
94
- theme
95
- }).value,
96
- "--clock-minutes-arc-color": parseThemeColor({
97
- color: minutesArcColor || minuteHandColor || "",
98
- theme
99
- }).value,
100
- "--clock-hours-arc-color": parseThemeColor({
101
- color: hoursArcColor || hourHandColor || "",
102
- theme
103
- }).value
104
- }
105
- };
106
- }
107
- );
108
- const parseTimeValue = (value) => {
109
- if (!value) {
110
- return null;
111
- }
112
- if (value instanceof Date) {
113
- return value;
114
- }
115
- if (dayjs.isDayjs(value)) {
116
- return value.toDate();
117
- }
118
- if (typeof value === "string") {
119
- const timeRegex = /^(\d{1,2}):(\d{1,2})(?::(\d{1,2}))?$/;
120
- const match = value.match(timeRegex);
121
- if (match) {
122
- const hours = parseInt(match[1], 10);
123
- const minutes = parseInt(match[2], 10);
124
- const seconds = parseInt(match[3] || "0", 10);
125
- const date = /* @__PURE__ */ new Date();
126
- date.setHours(hours, minutes, seconds, 0);
127
- return date;
128
- }
129
- const parsed = new Date(value);
130
- if (!isNaN(parsed.getTime())) {
131
- return parsed;
132
- }
133
- }
134
- return null;
135
- };
136
- const RealClock = (props) => {
137
- const {
138
- time,
139
- timezone: timezone2,
140
- getStyles,
141
- effectiveSize,
142
- hourHandSize,
143
- minuteHandSize,
144
- secondHandSize,
145
- hourHandLength,
146
- minuteHandLength,
147
- secondHandLength,
148
- secondHandBehavior,
149
- secondHandOpacity,
150
- minuteHandOpacity,
151
- hourHandOpacity,
152
- hourTicksOpacity,
153
- minuteTicksOpacity,
154
- primaryNumbersOpacity,
155
- secondaryNumbersOpacity,
156
- hourNumbersDistance = 0.75,
157
- // Default distance for hour numbers
158
- primaryNumbersProps,
159
- secondaryNumbersProps,
160
- withSecondsArc,
161
- secondsArcFrom,
162
- secondsArcDirection = "clockwise",
163
- withMinutesArc,
164
- minutesArcFrom,
165
- minutesArcDirection = "clockwise",
166
- withHoursArc,
167
- hoursArcFrom,
168
- hoursArcDirection = "clockwise",
169
- secondsArcOpacity,
170
- minutesArcOpacity,
171
- hoursArcOpacity
172
- } = props;
173
- const timezoneTime = timezone2 && timezone2 !== "" ? dayjs(time).tz(timezone2) : dayjs(time);
174
- const hours = timezoneTime.hour() % 12;
175
- const minutes = timezoneTime.minute();
176
- const seconds = timezoneTime.second();
177
- const milliseconds = timezoneTime.millisecond();
178
- const hourAngle = hours * 30 + minutes * 0.5;
179
- const minuteAngle = minutes * 6;
180
- let secondAngle = 0;
181
- switch (secondHandBehavior) {
182
- case "tick":
183
- secondAngle = seconds * 6;
184
- break;
185
- case "tick-half":
186
- secondAngle = (seconds + Math.floor(milliseconds / 500) * 0.5) * 6;
187
- break;
188
- case "tick-high-freq":
189
- secondAngle = (seconds + Math.floor(milliseconds / 125) * 0.125) * 6;
190
- break;
191
- case "smooth":
192
- default:
193
- secondAngle = (seconds + milliseconds / 1e3) * 6;
194
- break;
195
- }
196
- const size = effectiveSize;
197
- const clockRadius = Math.round(size / 2);
198
- const numberRadius = Math.round(clockRadius * hourNumbersDistance);
199
- const calculatedHourHandLength = Math.round(
200
- clockRadius * (hourHandLength ?? defaultProps.hourHandLength)
201
- );
202
- const calculatedMinuteHandLength = Math.round(
203
- clockRadius * (minuteHandLength ?? defaultProps.minuteHandLength)
204
- );
205
- const calculatedSecondHandLength = Math.round(
206
- clockRadius * (secondHandLength ?? defaultProps.secondHandLength)
207
- );
208
- const centerSize = Math.round(size * 0.034);
209
- const tickOffset = Math.round(size * 0.028);
210
- const toClockAngle = (deg) => (deg % 360 + 360) % 360;
211
- const secAngleFromDate = (d) => {
212
- if (!d) {
213
- return 0;
25
+ const varsResolver = createVarsResolver((theme, props) => {
26
+ const c = (color) => parseThemeColor({ color, theme }).value;
27
+ return {
28
+ root: {
29
+ "--clock-size": void 0,
30
+ "--clock-color": c(props.color || ""),
31
+ "--clock-hour-ticks-color": c(props.hourTicksColor || ""),
32
+ "--clock-hour-ticks-opacity": round2(props.hourTicksOpacity ?? 1).toString(),
33
+ "--clock-minute-ticks-color": c(props.minuteTicksColor || ""),
34
+ "--clock-minute-ticks-opacity": round2(props.minuteTicksOpacity ?? 1).toString(),
35
+ "--clock-primary-numbers-color": c(props.primaryNumbersColor || ""),
36
+ "--clock-primary-numbers-opacity": round2(props.primaryNumbersOpacity ?? 1).toString(),
37
+ "--clock-secondary-numbers-color": c(props.secondaryNumbersColor || ""),
38
+ "--clock-secondary-numbers-opacity": round2(props.secondaryNumbersOpacity ?? 1).toString(),
39
+ "--clock-second-hand-color": c(props.secondHandColor || ""),
40
+ "--clock-minute-hand-color": c(props.minuteHandColor || ""),
41
+ "--clock-hour-hand-color": c(props.hourHandColor || ""),
42
+ "--clock-seconds-arc-color": c(props.secondsArcColor || props.secondHandColor || ""),
43
+ "--clock-minutes-arc-color": c(props.minutesArcColor || props.minuteHandColor || ""),
44
+ "--clock-hours-arc-color": c(props.hoursArcColor || props.hourHandColor || "")
214
45
  }
215
- const dt = timezone2 && timezone2 !== "" ? dayjs(d).tz(timezone2) : dayjs(d);
216
- const s = dt.second();
217
- const ms = dt.millisecond();
218
- return toClockAngle((s + ms / 1e3) * 6);
219
46
  };
220
- const minAngleFromDate = (d) => {
221
- if (!d) {
222
- return 0;
223
- }
224
- const dt = timezone2 && timezone2 !== "" ? dayjs(d).tz(timezone2) : dayjs(d);
225
- const m = dt.minute();
226
- return toClockAngle(m * 6);
227
- };
228
- const hourAngleFromDate = (d) => {
229
- if (!d) {
230
- return 0;
231
- }
232
- const dt = timezone2 && timezone2 !== "" ? dayjs(d).tz(timezone2) : dayjs(d);
233
- const h = dt.hour() % 12;
234
- const m = dt.minute();
235
- return toClockAngle(h * 30 + m * 0.5);
236
- };
237
- const describeSector = (cx, cy, r, startDeg, endDeg, direction) => {
238
- const start = toClockAngle(startDeg);
239
- const end = toClockAngle(endDeg);
240
- let delta = 0;
241
- if (direction === "clockwise") {
242
- delta = end - start;
243
- if (delta < 0) {
244
- delta += 360;
245
- }
246
- } else {
247
- delta = start - end;
248
- if (delta < 0) {
249
- delta += 360;
250
- }
251
- }
252
- const largeArc = delta >= 180 ? 1 : 0;
253
- const sweep = direction === "clockwise" ? 1 : 0;
254
- const aStart = start * Math.PI / 180;
255
- const aEnd = end * Math.PI / 180;
256
- const x1 = cx + r * Math.sin(aStart);
257
- const y1 = cy - r * Math.cos(aStart);
258
- const x2 = cx + r * Math.sin(aEnd);
259
- const y2 = cy - r * Math.cos(aEnd);
260
- const fmt = (n) => Number.isFinite(n) ? n.toFixed(3) : "0";
261
- return `M ${fmt(cx)} ${fmt(cy)} L ${fmt(x1)} ${fmt(y1)} A ${fmt(r)} ${fmt(r)} 0 ${largeArc} ${sweep} ${fmt(x2)} ${fmt(y2)} Z`;
262
- };
263
- const showSecArc = withSecondsArc === true && (secondsArcOpacity ?? 1) !== 0;
264
- const showMinArc = withMinutesArc === true && (minutesArcOpacity ?? 1) !== 0;
265
- const showHrArc = withHoursArc === true && (hoursArcOpacity ?? 1) !== 0;
266
- return /* @__PURE__ */ React.createElement(Box, { ...getStyles("clockContainer") }, /* @__PURE__ */ React.createElement(Box, { ...getStyles("glassWrapper") }, /* @__PURE__ */ React.createElement(Box, { ...getStyles("clockFace") }, (showSecArc || showMinArc || showHrArc) && /* @__PURE__ */ React.createElement(
267
- "svg",
268
- {
269
- ...getStyles("arcsLayer", { style: { width: size, height: size } }),
270
- viewBox: `0 0 ${size} ${size}`
271
- },
272
- showHrArc && /* @__PURE__ */ React.createElement(
273
- "path",
274
- {
275
- d: describeSector(
276
- clockRadius,
277
- clockRadius,
278
- calculatedHourHandLength,
279
- hourAngleFromDate(parseTimeValue(hoursArcFrom) ?? null),
280
- hourAngle,
281
- hoursArcDirection
282
- ),
283
- fill: "var(--clock-hours-arc-color-resolved)",
284
- fillOpacity: Math.round((hoursArcOpacity ?? 1) * 100) / 100
285
- }
286
- ),
287
- showMinArc && /* @__PURE__ */ React.createElement(
288
- "path",
289
- {
290
- d: describeSector(
291
- clockRadius,
292
- clockRadius,
293
- calculatedMinuteHandLength,
294
- minAngleFromDate(parseTimeValue(minutesArcFrom) ?? null),
295
- minuteAngle,
296
- minutesArcDirection
297
- ),
298
- fill: "var(--clock-minutes-arc-color-resolved)",
299
- fillOpacity: Math.round((minutesArcOpacity ?? 1) * 100) / 100
300
- }
301
- ),
302
- showSecArc && /* @__PURE__ */ React.createElement(
303
- "path",
304
- {
305
- d: describeSector(
306
- clockRadius,
307
- clockRadius,
308
- calculatedSecondHandLength,
309
- secAngleFromDate(parseTimeValue(secondsArcFrom) ?? null),
310
- secondAngle,
311
- secondsArcDirection
312
- ),
313
- fill: "var(--clock-seconds-arc-color-resolved)",
314
- fillOpacity: Math.round((secondsArcOpacity ?? 1) * 100) / 100
315
- }
316
- )
317
- ), /* @__PURE__ */ React.createElement(Box, { ...getStyles("hourMarks") }, hourTicksOpacity !== 0 && Array.from({ length: 12 }, (_, i) => /* @__PURE__ */ React.createElement(
318
- Box,
319
- {
320
- key: `hour-tick-${i}`,
321
- ...getStyles("hourTick", {
322
- style: {
323
- top: tickOffset,
324
- left: "50%",
325
- transformOrigin: `50% ${clockRadius - tickOffset}px`,
326
- transform: `translateX(-50%) rotate(${i * 30}deg)`
327
- }
328
- })
329
- }
330
- )), minuteTicksOpacity !== 0 && Array.from({ length: 60 }, (_, i) => {
331
- if (i % 5 === 0) {
332
- return null;
333
- }
334
- return /* @__PURE__ */ React.createElement(
335
- Box,
336
- {
337
- key: `minute-tick-${i}`,
338
- ...getStyles("minuteTick", {
339
- style: {
340
- top: tickOffset,
341
- left: "50%",
342
- transformOrigin: `50% ${clockRadius - tickOffset}px`,
343
- transform: `translateX(-50%) rotate(${i * 6}deg)`
344
- }
345
- })
346
- }
347
- );
348
- }), primaryNumbersOpacity !== 0 && [12, 3, 6, 9].map((num) => {
349
- const i = num === 12 ? 0 : num;
350
- const angle = (i * 30 - 90) * (Math.PI / 180);
351
- const x = Math.round(clockRadius + Math.cos(angle) * numberRadius);
352
- const y = Math.round(clockRadius + Math.sin(angle) * numberRadius);
353
- return /* @__PURE__ */ React.createElement(
354
- Text,
355
- {
356
- key: `primary-number-${num}`,
357
- ...primaryNumbersProps,
358
- ...getStyles("primaryNumber", {
359
- className: getStyles("number").className,
360
- style: {
361
- left: x,
362
- top: y
363
- }
364
- })
365
- },
366
- num
367
- );
368
- }), secondaryNumbersOpacity !== 0 && [1, 2, 4, 5, 7, 8, 10, 11].map((num) => {
369
- const i = num;
370
- const angle = (i * 30 - 90) * (Math.PI / 180);
371
- const x = Math.round(clockRadius + Math.cos(angle) * numberRadius);
372
- const y = Math.round(clockRadius + Math.sin(angle) * numberRadius);
373
- return /* @__PURE__ */ React.createElement(
374
- Text,
375
- {
376
- key: `secondary-number-${num}`,
377
- ...secondaryNumbersProps,
378
- ...getStyles("secondaryNumber", {
379
- className: getStyles("number").className,
380
- style: {
381
- left: x,
382
- top: y
383
- }
384
- })
385
- },
386
- num
387
- );
388
- })), (hourHandOpacity ?? defaultProps.hourHandOpacity) !== 0 && /* @__PURE__ */ React.createElement(
389
- Box,
390
- {
391
- ...getStyles("hand", {
392
- className: getStyles("hourHand").className,
393
- style: {
394
- width: Math.round(size * (hourHandSize ?? defaultProps.hourHandSize) * 100) / 100,
395
- height: calculatedHourHandLength,
396
- opacity: Math.round((hourHandOpacity ?? defaultProps.hourHandOpacity) * 100) / 100,
397
- bottom: clockRadius,
398
- left: clockRadius,
399
- marginLeft: Math.round(-(size * (hourHandSize ?? defaultProps.hourHandSize)) / 2 * 100) / 100,
400
- borderRadius: `${Math.round(size * (hourHandSize ?? defaultProps.hourHandSize) * 100) / 100}px`,
401
- transform: `rotate(${Math.round(hourAngle * 100) / 100}deg)`
402
- }
403
- })
404
- }
405
- ), (minuteHandOpacity ?? defaultProps.minuteHandOpacity) !== 0 && /* @__PURE__ */ React.createElement(
406
- Box,
407
- {
408
- ...getStyles("hand", {
409
- className: getStyles("minuteHand").className,
410
- style: {
411
- width: Math.round(size * (minuteHandSize ?? defaultProps.minuteHandSize) * 100) / 100,
412
- height: calculatedMinuteHandLength,
413
- opacity: Math.round((minuteHandOpacity ?? defaultProps.minuteHandOpacity) * 100) / 100,
414
- bottom: clockRadius,
415
- left: clockRadius,
416
- marginLeft: Math.round(
417
- -(size * (minuteHandSize ?? defaultProps.minuteHandSize)) / 2 * 100
418
- ) / 100,
419
- borderRadius: `${Math.round(size * (minuteHandSize ?? defaultProps.minuteHandSize) * 100) / 100}px`,
420
- transform: `rotate(${Math.round(minuteAngle * 100) / 100}deg)`
421
- }
422
- })
423
- }
424
- ), (secondHandOpacity ?? defaultProps.secondHandOpacity) !== 0 && /* @__PURE__ */ React.createElement(
425
- Box,
426
- {
427
- ...getStyles("secondHandContainer", {
428
- style: {
429
- width: Math.round(size * (secondHandSize ?? defaultProps.secondHandSize) * 100) / 100,
430
- height: calculatedSecondHandLength,
431
- top: clockRadius - calculatedSecondHandLength,
432
- left: clockRadius,
433
- marginLeft: Math.round(
434
- -(size * (secondHandSize ?? defaultProps.secondHandSize)) / 2 * 100
435
- ) / 100,
436
- transformOrigin: `${Math.round(size * (secondHandSize ?? defaultProps.secondHandSize) / 2 * 100) / 100}px ${calculatedSecondHandLength}px`,
437
- transform: `rotate(${Math.round(secondAngle * 100) / 100}deg)`
438
- }
439
- })
440
- },
441
- /* @__PURE__ */ React.createElement(
442
- Box,
443
- {
444
- ...getStyles("secondHand", {
445
- style: {
446
- width: Math.round(size * (secondHandSize ?? defaultProps.secondHandSize) * 100) / 100,
447
- height: calculatedSecondHandLength,
448
- opacity: Math.round((secondHandOpacity ?? defaultProps.secondHandOpacity) * 100) / 100
449
- }
450
- })
451
- }
452
- ),
453
- /* @__PURE__ */ React.createElement(
454
- Box,
455
- {
456
- ...getStyles("secondHandCounterweight", {
457
- style: {
458
- width: Math.round(size * 6e-3 * 3 * 100) / 100,
459
- opacity: Math.round((secondHandOpacity ?? defaultProps.secondHandOpacity) * 100) / 100,
460
- left: Math.round(
461
- size * (secondHandSize ?? defaultProps.secondHandSize) / 2 - size * 6e-3 * 3 / 2
462
- )
463
- }
464
- })
465
- }
466
- )
467
- ), /* @__PURE__ */ React.createElement(Box, { ...getStyles("centerBlur") }), /* @__PURE__ */ React.createElement(
468
- Box,
469
- {
470
- ...getStyles("centerDot", {
471
- style: {
472
- width: centerSize,
473
- height: centerSize,
474
- opacity: Math.round((secondHandOpacity ?? defaultProps.secondHandOpacity) * 100) / 100,
475
- top: Math.round(clockRadius - centerSize / 2),
476
- left: Math.round(clockRadius - centerSize / 2)
477
- }
478
- })
479
- }
480
- ))));
481
- };
47
+ });
482
48
  const Clock = factory((_props, ref) => {
483
49
  const props = useProps("Clock", defaultProps, _props);
484
50
  const [time, setTime] = useState(/* @__PURE__ */ new Date());
485
51
  const [hasMounted, setHasMounted] = useState(false);
486
52
  const intervalRef = useRef(null);
53
+ const rafRef = useRef(null);
487
54
  const startTimeRef = useRef(null);
488
55
  const realStartTimeRef = useRef(null);
56
+ const onTimeChange = useCallbackRef(_props.onTimeChange);
57
+ const containerRef = useRef(null);
58
+ const [measuredSize, setMeasuredSize] = useState(null);
59
+ const mergedRef = useMergedRef(ref, containerRef);
489
60
  const {
490
61
  // Clock-specific props that should not be passed to DOM
491
62
  size,
@@ -511,14 +82,13 @@ const Clock = factory((_props, ref) => {
511
82
  hourHandOpacity,
512
83
  hourHandSize,
513
84
  hourHandLength,
514
- hourTicksOpacity: _hourTicksOpacity,
515
- minuteTicksOpacity: _minuteTicksOpacity,
516
85
  hourNumbersDistance,
517
86
  primaryNumbersProps,
518
87
  secondaryNumbersProps,
519
88
  timezone: timezone2,
520
89
  running,
521
90
  value,
91
+ ariaLabel,
522
92
  withSecondsArc,
523
93
  secondsArcFrom,
524
94
  secondsArcDirection,
@@ -534,6 +104,17 @@ const Clock = factory((_props, ref) => {
534
104
  hoursArcDirection,
535
105
  hoursArcColor,
536
106
  hoursArcOpacity,
107
+ sectors,
108
+ faceContent,
109
+ animateOnMount,
110
+ animateOnMountDuration,
111
+ shape,
112
+ aspectRatio,
113
+ borderRadius,
114
+ onTimeChange: _onTimeChange,
115
+ renderHourHand,
116
+ renderMinuteHand,
117
+ renderSecondHand,
537
118
  // Styles API props
538
119
  classNames,
539
120
  style,
@@ -555,17 +136,46 @@ const Clock = factory((_props, ref) => {
555
136
  vars,
556
137
  varsResolver
557
138
  });
558
- const effectiveSize = Math.round(
139
+ const resolvedSize = useMatches(
140
+ typeof size === "object" && size !== null && !Array.isArray(size) ? size : { base: size ?? defaultProps.size }
141
+ );
142
+ const isAutoSize = resolvedSize === "auto";
143
+ const effectiveSize = isAutoSize ? measuredSize ?? 400 : Math.round(
559
144
  px(
560
145
  getSize(
561
- typeof size === "string" && size in defaultClockSizes ? defaultClockSizes[size] : size || defaultProps.size,
146
+ typeof resolvedSize === "string" && resolvedSize in defaultClockSizes ? defaultClockSizes[resolvedSize] : resolvedSize || defaultProps.size,
562
147
  "clock-size"
563
148
  )
564
149
  )
565
150
  );
151
+ const geometry = useMemo(
152
+ () => createGeometry(effectiveSize, shape, { aspectRatio, borderRadius }),
153
+ [effectiveSize, shape, aspectRatio, borderRadius]
154
+ );
566
155
  useEffect(() => {
567
156
  setHasMounted(true);
568
157
  }, []);
158
+ useEffect(() => {
159
+ if (!isAutoSize || !hasMounted) {
160
+ return;
161
+ }
162
+ const element = containerRef.current;
163
+ if (!element || typeof ResizeObserver === "undefined") {
164
+ return;
165
+ }
166
+ const observer = new ResizeObserver((entries) => {
167
+ const entry = entries[0];
168
+ if (entry) {
169
+ const { width, height } = entry.contentRect;
170
+ const newSize = Math.round(Math.min(width, height));
171
+ if (newSize > 0) {
172
+ setMeasuredSize(newSize);
173
+ }
174
+ }
175
+ });
176
+ observer.observe(element);
177
+ return () => observer.disconnect();
178
+ }, [isAutoSize, hasMounted]);
569
179
  const getEffectiveTime = () => {
570
180
  const parsedValue = parseTimeValue(value);
571
181
  if (!running) {
@@ -586,6 +196,10 @@ const Clock = factory((_props, ref) => {
586
196
  clearInterval(intervalRef.current);
587
197
  intervalRef.current = null;
588
198
  }
199
+ if (rafRef.current !== null) {
200
+ cancelAnimationFrame(rafRef.current);
201
+ rafRef.current = null;
202
+ }
589
203
  if (!running) {
590
204
  return;
591
205
  }
@@ -597,122 +211,185 @@ const Clock = factory((_props, ref) => {
597
211
  startTimeRef.current = null;
598
212
  realStartTimeRef.current = null;
599
213
  }
600
- let interval = 1e3;
601
- switch (secondHandBehavior) {
602
- case "smooth":
603
- interval = 16;
604
- break;
605
- case "tick-half":
606
- interval = 500;
607
- break;
608
- case "tick-high-freq":
609
- interval = 125;
610
- break;
611
- case "tick":
612
- default:
613
- interval = 1e3;
614
- break;
615
- }
214
+ const fireOnTimeChange = (now) => {
215
+ const tz = timezone2 && timezone2 !== "" ? dayjs(now).tz(timezone2) : dayjs(now);
216
+ onTimeChange({
217
+ hours: tz.hour(),
218
+ minutes: tz.minute(),
219
+ seconds: tz.second(),
220
+ milliseconds: tz.millisecond()
221
+ });
222
+ };
616
223
  const updateTime = () => {
617
- setTime(/* @__PURE__ */ new Date());
224
+ const now = /* @__PURE__ */ new Date();
225
+ setTime(now);
226
+ fireOnTimeChange(now);
618
227
  };
619
228
  updateTime();
620
- intervalRef.current = setInterval(updateTime, interval);
229
+ if (secondHandBehavior === "smooth") {
230
+ const animate = () => {
231
+ const now = /* @__PURE__ */ new Date();
232
+ setTime(now);
233
+ fireOnTimeChange(now);
234
+ rafRef.current = requestAnimationFrame(animate);
235
+ };
236
+ animate();
237
+ } else {
238
+ let interval = 1e3;
239
+ switch (secondHandBehavior) {
240
+ case "tick-half":
241
+ interval = 500;
242
+ break;
243
+ case "tick-high-freq":
244
+ interval = 125;
245
+ break;
246
+ case "tick":
247
+ default:
248
+ interval = 1e3;
249
+ break;
250
+ }
251
+ intervalRef.current = setInterval(updateTime, interval);
252
+ }
621
253
  return () => {
622
254
  if (intervalRef.current) {
623
255
  clearInterval(intervalRef.current);
624
256
  }
257
+ if (rafRef.current !== null) {
258
+ cancelAnimationFrame(rafRef.current);
259
+ }
625
260
  };
626
- }, [running, value, secondHandBehavior, secondHandOpacity]);
261
+ }, [running, value, secondHandBehavior, timezone2]);
627
262
  if (!hasMounted) {
628
- return /* @__PURE__ */ React.createElement(Box, { ...getStyles("root"), ref, ...others }, /* @__PURE__ */ React.createElement(Box, { ...getStyles("clockContainer") }, /* @__PURE__ */ React.createElement(Box, { ...getStyles("glassWrapper") }, /* @__PURE__ */ React.createElement(Box, { ...getStyles("clockFace") }, /* @__PURE__ */ React.createElement(Box, { ...getStyles("hourMarks") }, (hourTicksOpacity || 1) !== 0 && Array.from({ length: 12 }, (_, i) => /* @__PURE__ */ React.createElement(
263
+ return /* @__PURE__ */ React.createElement(
629
264
  Box,
630
265
  {
631
- key: `hour-tick-${i}`,
632
- ...getStyles("hourTick", {
266
+ ...getStyles("root", {
633
267
  style: {
634
- top: Math.round(effectiveSize * 0.028),
635
- left: "50%",
636
- transformOrigin: `50% ${Math.round(effectiveSize / 2) - Math.round(effectiveSize * 0.028)}px`,
637
- transform: `translateX(-50%) rotate(${i * 30}deg)`
268
+ ...{ "--clock-size": `${effectiveSize}px` },
269
+ ...isAutoSize ? { width: "100%", height: "100%" } : { width: geometry.width, height: geometry.height }
638
270
  }
639
- })
640
- }
641
- )), (minuteTicksOpacity || 1) !== 0 && Array.from({ length: 60 }, (_, i) => {
642
- if (i % 5 === 0) {
643
- return null;
644
- }
645
- return /* @__PURE__ */ React.createElement(
271
+ }),
272
+ ref: mergedRef,
273
+ role: "img",
274
+ "aria-label": ariaLabel || "Clock",
275
+ ...others
276
+ },
277
+ /* @__PURE__ */ React.createElement(
646
278
  Box,
647
279
  {
648
- key: `minute-tick-${i}`,
649
- ...getStyles("minuteTick", {
280
+ ...getStyles("clockContainer", {
650
281
  style: {
651
- top: Math.round(effectiveSize * 0.028),
652
- left: "50%",
653
- transformOrigin: `50% ${Math.round(effectiveSize / 2) - Math.round(effectiveSize * 0.028)}px`,
654
- transform: `translateX(-50%) rotate(${i * 6}deg)`
655
- }
656
- })
657
- }
658
- );
659
- }), (primaryNumbersOpacity || 1) !== 0 && [12, 3, 6, 9].map((num) => {
660
- const i = num === 12 ? 0 : num;
661
- const angle = (i * 30 - 90) * (Math.PI / 180);
662
- const clockRadius = Math.round(effectiveSize / 2);
663
- const numberRadius = Math.round(clockRadius * (hourNumbersDistance || 0.75));
664
- const x = Math.round(clockRadius + Math.cos(angle) * numberRadius);
665
- const y = Math.round(clockRadius + Math.sin(angle) * numberRadius);
666
- return /* @__PURE__ */ React.createElement(
667
- Text,
668
- {
669
- key: `primary-number-${num}`,
670
- ...primaryNumbersProps,
671
- ...getStyles("primaryNumber", {
672
- className: getStyles("number").className,
673
- style: {
674
- left: x,
675
- top: y
282
+ width: geometry.width,
283
+ height: geometry.height
676
284
  }
677
285
  })
678
286
  },
679
- num
680
- );
681
- }), (secondaryNumbersOpacity || 1) !== 0 && [1, 2, 4, 5, 7, 8, 10, 11].map((num) => {
682
- const i = num;
683
- const angle = (i * 30 - 90) * (Math.PI / 180);
684
- const clockRadius = Math.round(effectiveSize / 2);
685
- const numberRadius = Math.round(clockRadius * (hourNumbersDistance || 0.75));
686
- const x = Math.round(clockRadius + Math.cos(angle) * numberRadius);
687
- const y = Math.round(clockRadius + Math.sin(angle) * numberRadius);
688
- return /* @__PURE__ */ React.createElement(
689
- Text,
690
- {
691
- key: `secondary-number-${num}`,
692
- ...secondaryNumbersProps,
693
- ...getStyles("secondaryNumber", {
694
- className: getStyles("number").className,
695
- style: {
696
- left: x,
697
- top: y
698
- }
699
- })
700
- },
701
- num
702
- );
703
- }))))));
287
+ /* @__PURE__ */ React.createElement(
288
+ Box,
289
+ {
290
+ ...getStyles("glassWrapper", {
291
+ style: { width: geometry.width, height: geometry.height }
292
+ })
293
+ },
294
+ /* @__PURE__ */ React.createElement(
295
+ Box,
296
+ {
297
+ ...getStyles("clockFace", {
298
+ style: {
299
+ width: geometry.width,
300
+ height: geometry.height,
301
+ borderRadius: geometry.borderRadius(),
302
+ clipPath: geometry.clipPath()
303
+ }
304
+ })
305
+ },
306
+ faceContent && /* @__PURE__ */ React.createElement(Box, { ...getStyles("faceContent") }, faceContent),
307
+ /* @__PURE__ */ React.createElement(
308
+ ClockFaceStatic,
309
+ {
310
+ getStyles,
311
+ effectiveSize,
312
+ geometry,
313
+ hourTicksOpacity,
314
+ minuteTicksOpacity,
315
+ primaryNumbersOpacity,
316
+ secondaryNumbersOpacity,
317
+ hourNumbersDistance,
318
+ primaryNumbersProps,
319
+ secondaryNumbersProps
320
+ }
321
+ )
322
+ )
323
+ )
324
+ )
325
+ );
704
326
  }
705
327
  const effectiveTime = getEffectiveTime();
706
- return /* @__PURE__ */ React.createElement(Box, { ...getStyles("root"), ref, ...others }, /* @__PURE__ */ React.createElement(
707
- RealClock,
328
+ const timezoneTime = timezone2 && timezone2 !== "" ? dayjs(effectiveTime).tz(timezone2) : dayjs(effectiveTime);
329
+ const computedAriaLabel = ariaLabel || `Clock showing ${String(timezoneTime.hour()).padStart(2, "0")}:${String(timezoneTime.minute()).padStart(2, "0")}:${String(timezoneTime.second()).padStart(2, "0")}`;
330
+ return /* @__PURE__ */ React.createElement(
331
+ Box,
708
332
  {
709
- time: effectiveTime,
710
- getStyles,
711
- effectiveSize,
712
- ...props
713
- }
714
- ));
333
+ ...getStyles("root", {
334
+ style: {
335
+ ...{ "--clock-size": `${effectiveSize}px` },
336
+ ...isAutoSize ? { width: "100%", height: "100%" } : { width: geometry.width, height: geometry.height }
337
+ }
338
+ }),
339
+ ref: mergedRef,
340
+ role: "img",
341
+ "aria-label": computedAriaLabel,
342
+ ...others
343
+ },
344
+ /* @__PURE__ */ React.createElement(
345
+ RealClock,
346
+ {
347
+ time: effectiveTime,
348
+ getStyles,
349
+ effectiveSize,
350
+ geometry,
351
+ timezone: timezone2,
352
+ hourHandSize,
353
+ minuteHandSize,
354
+ secondHandSize,
355
+ hourHandLength,
356
+ minuteHandLength,
357
+ secondHandLength,
358
+ secondHandBehavior,
359
+ secondHandOpacity,
360
+ minuteHandOpacity,
361
+ hourHandOpacity,
362
+ hourTicksOpacity,
363
+ minuteTicksOpacity,
364
+ primaryNumbersOpacity,
365
+ secondaryNumbersOpacity,
366
+ hourNumbersDistance,
367
+ primaryNumbersProps,
368
+ secondaryNumbersProps,
369
+ withSecondsArc,
370
+ secondsArcFrom,
371
+ secondsArcDirection,
372
+ secondsArcOpacity,
373
+ withMinutesArc,
374
+ minutesArcFrom,
375
+ minutesArcDirection,
376
+ minutesArcOpacity,
377
+ withHoursArc,
378
+ hoursArcFrom,
379
+ hoursArcDirection,
380
+ hoursArcOpacity,
381
+ sectors,
382
+ faceContent,
383
+ animateOnMount,
384
+ animateOnMountDuration,
385
+ renderHourHand,
386
+ renderMinuteHand,
387
+ renderSecondHand
388
+ }
389
+ )
390
+ );
715
391
  });
392
+ Clock.Digital = ClockDigital;
716
393
  Clock.classes = classes;
717
394
  Clock.displayName = "Clock";
718
395