@redkhat/timepicker 0.0.1

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.
@@ -0,0 +1,1415 @@
1
+ import * as i0 from '@angular/core';
2
+ import { model, computed, viewChild, inject, ElementRef, Renderer2, DestroyRef, Injector, signal, afterNextRender, effect, untracked, Component, ChangeDetectionStrategy, ViewEncapsulation, input, output, afterRender, booleanAttribute, Directive } from '@angular/core';
3
+ import { DOCUMENT, NgClass } from '@angular/common';
4
+ import { MatButton, MatIconButton } from '@angular/material/button';
5
+ import { MatIcon } from '@angular/material/icon';
6
+ import { Dialog, DialogModule } from '@angular/cdk/dialog';
7
+ import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
8
+ import { fromEvent, merge, tap, switchMap, takeUntil } from 'rxjs';
9
+ import * as i1 from '@angular/material/core';
10
+ import { MatRippleModule } from '@angular/material/core';
11
+ import { FormsModule, NG_VALUE_ACCESSOR, NG_VALIDATORS } from '@angular/forms';
12
+ import { MAT_INPUT_VALUE_ACCESSOR } from '@angular/material/input';
13
+
14
+ /**
15
+ * A class that implements the `Iterable` interface to generate coordinates for clock labels.
16
+ *
17
+ * @template T - The type of the labels.
18
+ */
19
+ class ClockIterable {
20
+ /**
21
+ * The labels to be positioned around the clock.
22
+ */
23
+ labels;
24
+ /**
25
+ * The radius of the clock.
26
+ */
27
+ radio;
28
+ /**
29
+ * The size of each label.
30
+ */
31
+ labelSize;
32
+ /**
33
+ * The inset distance from the edge of the clock.
34
+ */
35
+ inset;
36
+ /**
37
+ * The degrees between each label.
38
+ */
39
+ degreesByNumber = 30;
40
+ /**
41
+ * Creates an instance of ClockIterable.
42
+ *
43
+ * @param labels - An array of labels to be positioned around the clock [12, 1, 2, 3...].
44
+ * @param labelSize - The size of each label.
45
+ * @param diameter - The diameter of the clock.
46
+ * @param inset - The inset distance from the edge of the clock (default is 0).
47
+ */
48
+ constructor(labels, labelSize, diameter, inset = 0) {
49
+ this.labels = labels;
50
+ this.labelSize = labelSize / 2;
51
+ this.radio = (diameter / 2) - inset;
52
+ this.inset = inset;
53
+ }
54
+ /**
55
+ * Returns an iterator that yields the label and its x, y coordinates.
56
+ *
57
+ * @returns An iterator that yields a tuple containing the label and its x, y coordinates.
58
+ */
59
+ *[Symbol.iterator]() {
60
+ for (let i = 0; i < this.labels.length; i++) {
61
+ const x = this.radio + (this.radio - this.labelSize) * Math.sin(i * this.degreesByNumber * Math.PI / 180);
62
+ const y = this.radio - (this.radio - this.labelSize) * Math.cos(i * this.degreesByNumber * Math.PI / 180);
63
+ yield [this.labels[i], this.inset + x - this.labelSize, this.inset + y - this.labelSize];
64
+ }
65
+ }
66
+ }
67
+
68
+ /**
69
+ * Converts a time string or number to the number of seconds since midnight.
70
+ * Supports various formats including:
71
+ * - Integers (e.g., "10", "1030", 10, 1030) representing hours in 24-hour format (up to 2359).
72
+ * - Space-separated numbers (e.g., "10 30") representing HH MM.
73
+ * - Colon-separated numbers (e.g., "10:30") representing HH:MM.
74
+ * - Colon-separated numbers with AM/PM (e.g., "10:30am", "10:30 am").
75
+ *
76
+ * @param input The input to convert (can be a string or a number).
77
+ * @returns The number of seconds since midnight, or null if the input is invalid.
78
+ */
79
+ function timeToSeconds(input) {
80
+ const inputStr = typeof input === 'number' ? String(input) : input;
81
+ // Handle numeric cases (number or string numeric)
82
+ if (/^\d+$/.test(inputStr)) {
83
+ const num = parseInt(inputStr, 10);
84
+ if (num < 0 || num > 2359)
85
+ return null;
86
+ const len = inputStr.length;
87
+ let hour, minute;
88
+ if (len <= 2) {
89
+ hour = num;
90
+ minute = 0;
91
+ }
92
+ else if (len === 3) {
93
+ hour = parseInt(inputStr[0], 10);
94
+ minute = parseInt(inputStr.substring(1), 10);
95
+ }
96
+ else {
97
+ hour = parseInt(inputStr.substring(0, 2), 10);
98
+ minute = parseInt(inputStr.substring(2), 10);
99
+ }
100
+ return (hour <= 23 && minute <= 59) ? hour * 3600 + minute * 60 : null;
101
+ }
102
+ // Handle formats like "1pm", "1 pm", "10am", "10 AM"
103
+ const ampmMatch = inputStr.match(/^(\d{1,2})\s?([APap][Mm])$/i);
104
+ if (ampmMatch) {
105
+ let hour = parseInt(ampmMatch[1], 10);
106
+ const period = ampmMatch[2].toUpperCase();
107
+ if (hour < 1 || hour > 12)
108
+ return null;
109
+ if (period === 'PM') {
110
+ hour = hour === 12 ? 12 : hour + 12;
111
+ }
112
+ else {
113
+ hour = hour === 12 ? 0 : hour;
114
+ }
115
+ return hour * 3600; // No minutes, so default to 0 minutes
116
+ }
117
+ // Handle format with colon and AM/PM (e.g., "1:00pm", "1:00 PM")
118
+ const colonAmpmMatch = inputStr.match(/^(\d{1,2}):(\d{1,2})\s?([APap][Mm])$/i);
119
+ if (colonAmpmMatch) {
120
+ let hour = parseInt(colonAmpmMatch[1], 10);
121
+ const minute = parseInt(colonAmpmMatch[2], 10);
122
+ const period = colonAmpmMatch[3].toUpperCase();
123
+ if (hour < 1 || hour > 12 || minute > 59)
124
+ return null;
125
+ if (period === 'PM')
126
+ hour = hour === 12 ? 12 : hour + 12;
127
+ else
128
+ hour = hour === 12 ? 0 : hour;
129
+ return hour * 3600 + minute * 60;
130
+ }
131
+ // Handle cases with separators
132
+ const [hPart, mPart] = inputStr.split(/[\s:]/);
133
+ if (hPart && mPart && /^\d+$/.test(hPart) && /^\d+$/.test(mPart)) {
134
+ const hour = parseInt(hPart, 10);
135
+ const minute = parseInt(mPart, 10);
136
+ if (hour >= 0 && hour <= 23 && minute >= 0 && minute <= 59) {
137
+ return hour * 3600 + minute * 60;
138
+ }
139
+ }
140
+ return null;
141
+ }
142
+ function secondsToDate(seconds) {
143
+ const initialDate = new Date(0, 0, 0, 0);
144
+ const date = new Date(initialDate.getTime() + seconds * 1000);
145
+ return date;
146
+ }
147
+ function detectTimeFormat(date) {
148
+ const localTime = date.toLocaleTimeString();
149
+ if (localTime.includes('AM') || localTime.includes('PM')) {
150
+ return '12h';
151
+ }
152
+ else {
153
+ return '24h';
154
+ }
155
+ }
156
+ function splitDate(date) {
157
+ const hours = date.getHours();
158
+ const minutes = date.getMinutes();
159
+ return { hours, minutes };
160
+ }
161
+ function amPmTransform(isAm, date) {
162
+ const newDate = new Date(date);
163
+ const tempHours = newDate.getHours();
164
+ if (isAm === 'AM' && tempHours >= 12) {
165
+ newDate.setHours(tempHours - 12);
166
+ }
167
+ else if (isAm === 'PM' && tempHours < 12) {
168
+ newDate.setHours(tempHours + 12);
169
+ }
170
+ return newDate;
171
+ }
172
+ function validateTimeValue(value, max) {
173
+ let numericValue = value.replace(/[^0-9]/g, '');
174
+ if (numericValue.length > 2) {
175
+ numericValue = numericValue.slice(0, 2);
176
+ }
177
+ if (numericValue === '') {
178
+ return '';
179
+ }
180
+ const num = parseInt(numericValue, 10);
181
+ return num > max ? max.toString() : numericValue;
182
+ }
183
+ function formatTimeValue(value, defaultValue, padLength = 2) {
184
+ if (value === '') {
185
+ return defaultValue;
186
+ }
187
+ const num = parseInt(value, 10);
188
+ if (num === 0) {
189
+ return defaultValue;
190
+ }
191
+ return value.padStart(padLength, '0');
192
+ }
193
+ /**
194
+ * Converts a time string or number to the number of seconds since midnight.
195
+ *
196
+ * Supported formats include:
197
+ * - Integers (e.g., "10", "1030", 10, 1030) in 24-hour format (up to 2359).
198
+ * - Space-separated numbers (e.g., "10 30") as HH MM.
199
+ * - Colon-separated numbers (e.g., "10:30" or "10:30:00") as HH:MM or HH:MM:SS.
200
+ * - Formats with regional AM/PM markers either at the beginning or at the end
201
+ * (e.g., "10:30am", "am 10:30", "午後3:25:00", "10:30 du matin").
202
+ *
203
+ * Additionally, this function normalizes Arabic digits (Unicode range \u0660-\u0669)
204
+ * to their Western counterparts before processing.
205
+ *
206
+ * @param input The input to convert (can be a string or number).
207
+ * @returns The number of seconds since midnight, or null if the input is invalid.
208
+ */
209
+ function timeToSecondsi18n(input) {
210
+ // Convert input to string and trim whitespace.
211
+ let inputStr = typeof input === 'number' ? String(input) : input;
212
+ inputStr = inputStr.trim();
213
+ // Normalize Arabic digits to Western digits if found.
214
+ if (/[\u0660-\u0669]/.test(inputStr)) {
215
+ inputStr = inputStr.replace(/[\u0660-\u0669]/g, d => (d.charCodeAt(0) - 0x0660).toString());
216
+ }
217
+ // Extended list of AM and PM markers across various regions/languages.
218
+ const AM_MARKERS = [
219
+ 'am', 'a.m.', 'a.m', // English
220
+ '午前', // Japanese
221
+ '上午', // Chinese
222
+ '오전', // Korean
223
+ 'ص', 'صباح', 'صباحاً', // Arabic
224
+ 'du matin', 'matin' // French
225
+ ];
226
+ const PM_MARKERS = [
227
+ 'pm', 'p.m.', 'p.m', // English
228
+ '午後', // Japanese
229
+ '下午', // Chinese
230
+ '오후', // Korean
231
+ 'م', 'مساء', 'مساءً', // Arabic
232
+ "de l’après-midi", "de l'aprés-midi", "de l'après-midi",
233
+ 'après-midi', 'du soir', 'soir' // French
234
+ ];
235
+ let period = null;
236
+ // Helper functions to compare prefix/suffix ignoring case.
237
+ const startsWithIgnoreCase = (str, prefix) => str.substring(0, prefix.length).toLowerCase() === prefix.toLowerCase();
238
+ const endsWithIgnoreCase = (str, suffix) => str.substring(str.length - suffix.length).toLowerCase() === suffix.toLowerCase();
239
+ // Check if the period marker is at the beginning.
240
+ for (const marker of AM_MARKERS) {
241
+ if (startsWithIgnoreCase(inputStr, marker)) {
242
+ period = 'AM';
243
+ inputStr = inputStr.substring(marker.length).trim();
244
+ break;
245
+ }
246
+ }
247
+ if (!period) {
248
+ for (const marker of PM_MARKERS) {
249
+ if (startsWithIgnoreCase(inputStr, marker)) {
250
+ period = 'PM';
251
+ inputStr = inputStr.substring(marker.length).trim();
252
+ break;
253
+ }
254
+ }
255
+ }
256
+ // If not found at the beginning, check at the end.
257
+ if (!period) {
258
+ for (const marker of AM_MARKERS) {
259
+ if (endsWithIgnoreCase(inputStr, marker)) {
260
+ period = 'AM';
261
+ inputStr = inputStr.substring(0, inputStr.length - marker.length).trim();
262
+ break;
263
+ }
264
+ }
265
+ }
266
+ if (!period) {
267
+ for (const marker of PM_MARKERS) {
268
+ if (endsWithIgnoreCase(inputStr, marker)) {
269
+ period = 'PM';
270
+ inputStr = inputStr.substring(0, inputStr.length - marker.length).trim();
271
+ break;
272
+ }
273
+ }
274
+ }
275
+ // Split the numeric part of the time.
276
+ let timeParts = [];
277
+ if (inputStr.includes(':')) {
278
+ // Colon-separated format. Accepts HH:MM or HH:MM:SS.
279
+ const parts = inputStr.split(':');
280
+ if (parts.length < 2 || parts.length > 3)
281
+ return null;
282
+ for (const part of parts) {
283
+ const num = parseInt(part, 10);
284
+ if (isNaN(num))
285
+ return null;
286
+ timeParts.push(num);
287
+ }
288
+ }
289
+ else if (inputStr.includes(' ')) {
290
+ // Space-separated format (e.g., "10 30").
291
+ const parts = inputStr.split(/\s+/);
292
+ if (parts.length > 3)
293
+ return null;
294
+ for (const part of parts) {
295
+ const num = parseInt(part, 10);
296
+ if (isNaN(num))
297
+ return null;
298
+ timeParts.push(num);
299
+ }
300
+ }
301
+ else if (/^\d+$/.test(inputStr)) {
302
+ // Numeric case without separators: "10", "1030".
303
+ if (inputStr.length <= 2) {
304
+ timeParts = [parseInt(inputStr, 10)];
305
+ }
306
+ else if (inputStr.length === 3) {
307
+ timeParts = [
308
+ parseInt(inputStr.substring(0, 1), 10),
309
+ parseInt(inputStr.substring(1), 10),
310
+ ];
311
+ }
312
+ else if (inputStr.length === 4) {
313
+ timeParts = [
314
+ parseInt(inputStr.substring(0, 2), 10),
315
+ parseInt(inputStr.substring(2), 10),
316
+ ];
317
+ }
318
+ else {
319
+ // Unsupported numeric format with more than 4 digits.
320
+ return null;
321
+ }
322
+ }
323
+ else {
324
+ // Unrecognized format.
325
+ return null;
326
+ }
327
+ // Extract hour, minute, and second (defaulting to 0 if not provided).
328
+ let hour = timeParts[0];
329
+ let minute = timeParts.length >= 2 ? timeParts[1] : 0;
330
+ let second = timeParts.length === 3 ? timeParts[2] : 0;
331
+ // Validate minute and second ranges.
332
+ if (minute < 0 || minute > 59)
333
+ return null;
334
+ if (second < 0 || second > 59)
335
+ return null;
336
+ // Adjust hour based on the AM/PM marker.
337
+ if (period) {
338
+ if (hour < 1 || hour > 12)
339
+ return null;
340
+ if (period === 'PM') {
341
+ hour = hour === 12 ? 12 : hour + 12;
342
+ }
343
+ else {
344
+ hour = hour === 12 ? 0 : hour;
345
+ }
346
+ }
347
+ else {
348
+ // Without a period, assume 24-hour format.
349
+ if (hour < 0 || hour > 23)
350
+ return null;
351
+ }
352
+ return hour * 3600 + minute * 60 + second;
353
+ }
354
+
355
+ function cubicBezier(p1x, p1y, p2x, p2y, t) {
356
+ const cx = 3.0 * p1x;
357
+ const bx = 3.0 * (p2x - p1x) - cx;
358
+ const ax = 1.0 - cx - bx;
359
+ const cy = 3.0 * p1y;
360
+ const by = 3.0 * (p2y - p1y) - cy;
361
+ const ay = 1.0 - cy - by;
362
+ let x = t;
363
+ for (let i = 0; i < 4; i++) {
364
+ const t_0 = x * x * x * ax + x * x * bx + x * cx;
365
+ const t_1 = 3 * x * x * ax + 2 * x * bx + cx;
366
+ x = x - (t_0 - t) / t_1;
367
+ }
368
+ return x * x * x * ay + x * x * by + x * cy;
369
+ }
370
+
371
+ function normalizeEvent(event) {
372
+ if (event instanceof MouseEvent) {
373
+ return event;
374
+ }
375
+ else if (event instanceof TouchEvent) {
376
+ return event.touches[0] || event.changedTouches[0];
377
+ }
378
+ else if (event instanceof Touch) {
379
+ return {
380
+ clientX: event.clientX,
381
+ clientY: event.clientY,
382
+ pageX: event.pageX,
383
+ pageY: event.pageY,
384
+ screenX: event.screenX,
385
+ screenY: event.screenY,
386
+ target: event.target
387
+ };
388
+ }
389
+ return event;
390
+ }
391
+ function createPointerEvents(element) {
392
+ const pointerDown$ = fromEvent(element, 'pointerdown');
393
+ const pointerMove$ = fromEvent(document, 'pointermove');
394
+ const pointerUp$ = fromEvent(document, 'pointerup');
395
+ const pointerLeave$ = fromEvent(document, 'pointerleave');
396
+ const start$ = pointerDown$;
397
+ const move$ = pointerMove$;
398
+ const end$ = merge(pointerUp$, pointerLeave$);
399
+ return { start$, move$, end$ };
400
+ }
401
+
402
+ function snapAngle(angle, steps) {
403
+ // Calc the size of each step in the scale. 12 for hours, 60 for minutes
404
+ const stepSize = 360 / steps;
405
+ // Find the closest multiple of the step size. 12 for hours, 60 for minutes
406
+ const closestStep = Math.round(angle / stepSize);
407
+ // Calc the snapped angle
408
+ const snappedAngle = closestStep * stepSize;
409
+ return snappedAngle;
410
+ }
411
+ function hoursToAngle(hour) {
412
+ const angle = (hour - 3) * 30;
413
+ return (angle + 360) % 360;
414
+ }
415
+ function minutesToAngle(minute) {
416
+ let angle = (minute - 15) * 6;
417
+ angle = (angle + 360) % 360;
418
+ return angle;
419
+ }
420
+ function hours24ToAngle(hour) {
421
+ let angle;
422
+ if (hour >= 0 && hour <= 11) {
423
+ angle = (hour - 3) * 30; // Dial 1: 0 to 11
424
+ }
425
+ else {
426
+ angle = (hour - 15) * 30; // Dial 2: 12 to 23
427
+ }
428
+ angle = (angle + 360) % 360;
429
+ return angle;
430
+ }
431
+ function angleToHours(angle) {
432
+ // Normalize the angle to be within 0-360 range
433
+ angle = (angle + 360) % 360;
434
+ // Correct mapping: 0 degrees -> 3, 270 degrees -> 12
435
+ let hour = Math.floor((angle + 90) / 30) % 12;
436
+ // Handle the 0 hour case (midnight)
437
+ if (hour === 0) {
438
+ hour = 12;
439
+ }
440
+ return hour;
441
+ }
442
+ function angleToMinutes(angle) {
443
+ // Normalize the angle
444
+ angle = (angle + 360) % 360;
445
+ let minute = (angle / 6) + 15;
446
+ minute = (minute + 60) % 60; // Ensure positive value and wrap around 60
447
+ return Math.floor(minute);
448
+ }
449
+ function angleToHours24(angle, dial) {
450
+ angle = (angle + 360) % 360;
451
+ let hour = 0;
452
+ const baseValue = angle / 30;
453
+ if (dial === 1) {
454
+ hour = (Math.floor(baseValue + 3) % 12 + 12) % 12;
455
+ }
456
+ else if (dial === 2) {
457
+ hour = (Math.floor(baseValue + 3) % 12) + 12;
458
+ }
459
+ return hour;
460
+ }
461
+
462
+ const HOURS_LABEL = [12, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11];
463
+ const HOURS_LABEL_24_P1 = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11];
464
+ const HOURS_LABEL_24_P2 = [12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23];
465
+ const MINUTES_LABEL = [0, 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55];
466
+
467
+ class RkTimepickerDial {
468
+ currentTime = model.required({ alias: 'time' });
469
+ timeFormat = model('12h', { alias: 'format' });
470
+ selectedTime = model('hours', { alias: 'selected' });
471
+ period = model('AM');
472
+ value = computed(() => {
473
+ const date = this.currentTime();
474
+ return date ? splitDate(date) : { hours: 0, minutes: 0 };
475
+ });
476
+ format = computed(() => {
477
+ const fm = this.timeFormat();
478
+ return (fm && (fm === '24h' || fm === '12h')) ? fm : detectTimeFormat(new Date());
479
+ });
480
+ selected = computed(() => {
481
+ const selected = this.selectedTime();
482
+ return selected === 'hours' || selected === 'minutes' ? selected : 'hours';
483
+ });
484
+ _dial = viewChild.required('dial');
485
+ _clipedLabel = viewChild.required('dial2');
486
+ _dialSelector = viewChild.required('dialSelector');
487
+ _elRef = inject(ElementRef);
488
+ _renderer = inject(Renderer2);
489
+ _destroyRef = inject(DestroyRef);
490
+ _injector = inject(Injector);
491
+ _document = inject(DOCUMENT);
492
+ _currentDial = signal(1);
493
+ _currentDegree = signal(0);
494
+ _loading = signal(false);
495
+ _motion = 400;
496
+ _radius = 128;
497
+ _labelSize = 48;
498
+ constructor() {
499
+ afterNextRender(() => {
500
+ this._fillBySelected(this.selected(), this.format());
501
+ effect(() => {
502
+ const selected = this.selected();
503
+ const format = this.format();
504
+ this._fillBySelected(selected, format);
505
+ untracked(() => {
506
+ const value = this.value();
507
+ this.period.set(this.value().hours < 12 ? 'AM' : 'PM');
508
+ this._currentDial.set(format === '24h' && selected === 'hours' && value.hours >= 12 ? 2 : 1);
509
+ this._handleInputsChange(value, format, selected);
510
+ });
511
+ }, { injector: this._injector });
512
+ effect(() => {
513
+ const value = this.value();
514
+ untracked(() => {
515
+ if (this._loading())
516
+ return;
517
+ const format = this.format();
518
+ const selected = this.selected();
519
+ this.period.set(this.value().hours < 12 ? 'AM' : 'PM');
520
+ this._currentDial.set(format === '24h' && selected === 'hours' && value.hours >= 12 ? 2 : 1);
521
+ this._handleInputsChange(value, format, selected, true);
522
+ });
523
+ }, { injector: this._injector });
524
+ effect(() => {
525
+ const period = this.period();
526
+ untracked(() => {
527
+ const currentTime = this.currentTime();
528
+ if (period === null || currentTime === null)
529
+ return;
530
+ this.currentTime.set(amPmTransform(period, currentTime));
531
+ });
532
+ }, { injector: this._injector });
533
+ const { start$, move$, end$ } = createPointerEvents(this._elRef.nativeElement);
534
+ start$.pipe(tap((event) => this._onPointerEventInit(normalizeEvent(event))), switchMap(() => {
535
+ return move$.pipe(takeUntil(end$.pipe(tap((event) => this._onPointerEventStop(normalizeEvent(event))))));
536
+ }), takeUntilDestroyed(this._destroyRef)).subscribe(event => {
537
+ const normalized = normalizeEvent(event);
538
+ this._onPointerEventInit(normalized);
539
+ });
540
+ });
541
+ }
542
+ _handleInputsChange(value, format, selected, animate = true) {
543
+ if (selected === 'hours') {
544
+ const angle = format === '24h' ? hours24ToAngle(value.hours) : hoursToAngle(value.hours);
545
+ if (animate) {
546
+ this._rotateAnimation(this._currentDegree(), angle, this._motion, 2);
547
+ }
548
+ else {
549
+ this._moveByAngle(angle, 2);
550
+ }
551
+ this._currentDegree.set(angle);
552
+ }
553
+ else {
554
+ const angle = minutesToAngle(value.minutes);
555
+ if (animate) {
556
+ this._rotateAnimation(this._currentDegree(), angle, this._motion, 2);
557
+ }
558
+ else {
559
+ this._moveByAngle(angle, 2);
560
+ }
561
+ this._currentDegree.set(angle);
562
+ }
563
+ }
564
+ _onPointerEventInit(event) {
565
+ this._loading.set(true);
566
+ this._moveByTouchClick(event, this._dial().nativeElement);
567
+ }
568
+ async _onPointerEventStop(event) {
569
+ const currentDegree = this._currentDegree();
570
+ const snapDegrees = this.selected() === 'hours' ? snapAngle(currentDegree, 12) : snapAngle(currentDegree, 60);
571
+ await this._rotateAnimation(this._currentDegree(), snapDegrees, this._motion / 2, 2);
572
+ this._currentDegree.set(snapDegrees);
573
+ this._loading.set(false);
574
+ }
575
+ _fillDial(dial, labels, withClean, inset = 2) {
576
+ if (withClean) {
577
+ dial.innerHTML = '';
578
+ }
579
+ const clockIterable = new ClockIterable(labels, this._labelSize, this._radius * 2, inset);
580
+ for (const [label, x, y] of clockIterable) {
581
+ const numero = this._document.createElement('div');
582
+ numero.classList.add('rk-clock-dial-label');
583
+ numero.textContent = this.selected() === 'hours' ? label.toString() : label.toString().padStart(2, '0');
584
+ numero.style.position = 'absolute';
585
+ numero.style.left = x + 'px';
586
+ numero.style.top = y + 'px';
587
+ this._renderer.appendChild(dial, numero);
588
+ }
589
+ }
590
+ _fillBySelected(selected, format) {
591
+ switch (selected) {
592
+ case 'hours':
593
+ if (format === '12h') {
594
+ this._fillDial(this._dial().nativeElement, HOURS_LABEL, true);
595
+ this._fillDial(this._clipedLabel().nativeElement, HOURS_LABEL, true);
596
+ }
597
+ else if (format === '24h') {
598
+ this._fillDial(this._dial().nativeElement, HOURS_LABEL_24_P1, true);
599
+ this._fillDial(this._dial().nativeElement, HOURS_LABEL_24_P2, false, 38);
600
+ this._fillDial(this._clipedLabel().nativeElement, HOURS_LABEL_24_P1, true);
601
+ this._fillDial(this._clipedLabel().nativeElement, HOURS_LABEL_24_P2, false, 38);
602
+ }
603
+ break;
604
+ case 'minutes':
605
+ this._fillDial(this._dial().nativeElement, MINUTES_LABEL, true);
606
+ this._fillDial(this._clipedLabel().nativeElement, MINUTES_LABEL, true);
607
+ break;
608
+ default:
609
+ this._fillDial(this._dial().nativeElement, HOURS_LABEL, true);
610
+ this._fillDial(this._clipedLabel().nativeElement, HOURS_LABEL, true);
611
+ }
612
+ }
613
+ _moveByTouchClick(event, dial, inset = 2) {
614
+ const rect = dial.getBoundingClientRect();
615
+ // relative to the center of the clock
616
+ const x = event.clientX - rect.left - this._radius;
617
+ const y = event.clientY - rect.top - this._radius;
618
+ // Calc angle in degrees
619
+ let angle = Math.atan2(y, x) * (180 / Math.PI);
620
+ if (angle < 0) {
621
+ angle += 360; //make sure the angle is in the range [0, 360]
622
+ }
623
+ // Calc new position for the object (using radius as reference)
624
+ let radius = this._radius - (this._labelSize / 2) - inset;
625
+ //--------------------------------------------------------------------------------------------------------------//
626
+ // logic when format is 24h and two dial exist
627
+ const distanceFromCenter = Math.sqrt(x * x + y * y);
628
+ const MAX_RAD = radius - 20;
629
+ let selectorInset = 0;
630
+ if (distanceFromCenter < MAX_RAD && this.selected() === 'hours' && this.format() === '24h') {
631
+ radius -= 36;
632
+ this._currentDial.set(2);
633
+ selectorInset = 36;
634
+ }
635
+ else {
636
+ this._currentDial.set(1);
637
+ selectorInset = 0;
638
+ }
639
+ //----------------------------------------------------------------------------------------------------------------//
640
+ const moveX = this._radius + radius * Math.cos(angle * (Math.PI / 180));
641
+ const moveY = this._radius + radius * Math.sin(angle * (Math.PI / 180));
642
+ // Call the provided functions with the calculated values
643
+ this._moveTrack(moveX, moveY);
644
+ this._moveSelector(angle, selectorInset);
645
+ this._currentDegree.set(angle);
646
+ //update time
647
+ const snapDegrees = this.selected() === 'hours' ? snapAngle(angle, 12) : snapAngle(angle, 60);
648
+ const currentDate = this.currentTime();
649
+ const date = currentDate ? new Date(currentDate) : new Date();
650
+ if (this.selected() === 'hours') {
651
+ let hours = this.format() === '24h' ? angleToHours24(snapDegrees, this._currentDial()) : angleToHours(snapDegrees);
652
+ if (this.format() === '24h') {
653
+ this.period.set(hours < 12 ? 'AM' : 'PM');
654
+ }
655
+ if (this.format() !== '24h' && this.period() === 'PM') {
656
+ hours = hours < 12 ? hours + 12 : hours;
657
+ }
658
+ if (this.period() === 'AM' && hours >= 12 && this.format() === '12h') {
659
+ hours = hours - 12;
660
+ }
661
+ date.setHours(hours);
662
+ }
663
+ else {
664
+ date.setMinutes(angleToMinutes(snapDegrees));
665
+ }
666
+ this.currentTime.set(date);
667
+ }
668
+ _moveByAngle(angle, inset = 2) {
669
+ // Ensure angle is within 0-360 range
670
+ angle = angle % 360;
671
+ if (angle < 0) {
672
+ angle += 360;
673
+ }
674
+ // Calc new position for the object (using radius as reference)
675
+ let radius = this._radius - (this._labelSize / 2) - inset;
676
+ // logic when format is 24h and two dial exist
677
+ let selectorInset = 0;
678
+ if (this.selected() === 'hours' && this.format() === '24h' && this._currentDial() === 2) {
679
+ radius -= 36;
680
+ selectorInset = 36;
681
+ }
682
+ const moveX = this._radius + radius * Math.cos(angle * (Math.PI / 180));
683
+ const moveY = this._radius + radius * Math.sin(angle * (Math.PI / 180));
684
+ // Call the provided functions with the calculated values
685
+ this._moveTrack(moveX, moveY);
686
+ this._moveSelector(angle, selectorInset);
687
+ }
688
+ _moveTrack(x, y) {
689
+ this._renderer.setStyle(this._clipedLabel().nativeElement, 'clip-path', `circle(24px at ${x}px ${y}px)`);
690
+ }
691
+ _moveSelector(degrees, inset = 0) {
692
+ this._renderer.setStyle(this._dialSelector().nativeElement, 'width', 104 - inset + 'px');
693
+ this._renderer.setStyle(this._dialSelector().nativeElement, 'transform', `rotate(${degrees}deg)`);
694
+ }
695
+ async _rotateAnimation(currentDegree, degree, animationTime, inset = 2) {
696
+ return new Promise((resolve) => {
697
+ // Calculate the shortest angle difference between the current and target degrees.
698
+ let angleDiff = degree - currentDegree;
699
+ // Adjust the angle difference to be within the range of -180 to 180 degrees.
700
+ if (angleDiff > 180) {
701
+ angleDiff -= 360;
702
+ }
703
+ else if (angleDiff < -180) {
704
+ angleDiff += 360;
705
+ }
706
+ // Store the start time of the animation.
707
+ let startTime = null;
708
+ const animate = (timestamp) => {
709
+ // Initialize the start time if it hasn't been set yet.
710
+ if (!startTime)
711
+ startTime = timestamp;
712
+ // Calculate the progress of the animation (0 to 1).
713
+ let progress = Math.min((timestamp - startTime) / animationTime, 1);
714
+ // Apply a cubic Bezier easing function to the progress. This makes the animation smoother.
715
+ const easedProgress = cubicBezier(0.05, 0.7, 0.1, 1.0, progress);
716
+ // Calculate the current angle based on the eased progress.
717
+ const currentAngle = currentDegree + angleDiff * easedProgress;
718
+ // Update the position of the dial and selector based on the current angle.
719
+ this._moveByAngle(currentAngle, inset);
720
+ // Continue the animation if it's not finished.
721
+ if (progress < 1) {
722
+ requestAnimationFrame(animate);
723
+ }
724
+ else {
725
+ resolve(); // Resolve the promise when the animation is complete.
726
+ }
727
+ };
728
+ requestAnimationFrame(animate);
729
+ });
730
+ }
731
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.1.6", ngImport: i0, type: RkTimepickerDial, deps: [], target: i0.ɵɵFactoryTarget.Component });
732
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.2.0", version: "19.1.6", type: RkTimepickerDial, isStandalone: true, selector: "rk-timepicker-dial", inputs: { currentTime: { classPropertyName: "currentTime", publicName: "time", isSignal: true, isRequired: true, transformFunction: null }, timeFormat: { classPropertyName: "timeFormat", publicName: "format", isSignal: true, isRequired: false, transformFunction: null }, selectedTime: { classPropertyName: "selectedTime", publicName: "selected", isSignal: true, isRequired: false, transformFunction: null }, period: { classPropertyName: "period", publicName: "period", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { currentTime: "timeChange", timeFormat: "formatChange", selectedTime: "selectedChange", period: "periodChange" }, host: { classAttribute: "rk-timepicker-dial" }, viewQueries: [{ propertyName: "_dial", first: true, predicate: ["dial"], descendants: true, isSignal: true }, { propertyName: "_clipedLabel", first: true, predicate: ["dial2"], descendants: true, isSignal: true }, { propertyName: "_dialSelector", first: true, predicate: ["dialSelector"], descendants: true, isSignal: true }], ngImport: i0, template: `
733
+ <div class="rk-timepicker-dial-container">
734
+ <div #dial class="rk-label-container rk-dial-size">
735
+ </div>
736
+ <div #dial2 class="rk-cliped-label rk-dial-size">
737
+ </div>
738
+ <div #dialSelector class="rk-dial-selector"></div>
739
+ <div class="rk-pivot-point"></div>
740
+ </div>
741
+ `, isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
742
+ }
743
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.1.6", ngImport: i0, type: RkTimepickerDial, decorators: [{
744
+ type: Component,
745
+ args: [{ selector: 'rk-timepicker-dial', host: {
746
+ 'class': 'rk-timepicker-dial',
747
+ }, imports: [], template: `
748
+ <div class="rk-timepicker-dial-container">
749
+ <div #dial class="rk-label-container rk-dial-size">
750
+ </div>
751
+ <div #dial2 class="rk-cliped-label rk-dial-size">
752
+ </div>
753
+ <div #dialSelector class="rk-dial-selector"></div>
754
+ <div class="rk-pivot-point"></div>
755
+ </div>
756
+ `, standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None }]
757
+ }], ctorParameters: () => [] });
758
+
759
+ class RkTimepickerInputLabel {
760
+ currentTime = model.required({ alias: 'time' });
761
+ selected = model('hours');
762
+ period = model('AM');
763
+ format = model('12h');
764
+ editable = model(false);
765
+ hours = signal('');
766
+ minutes = signal('');
767
+ inputSupportLabels = input(['Hour', 'Minute'], { alias: 'inputLabels' });
768
+ inputHours = viewChild('inputH');
769
+ inputMinutes = viewChild('inputM');
770
+ value = computed(() => {
771
+ const date = this.currentTime();
772
+ const format = this.format();
773
+ if (date) {
774
+ const { hours, minutes } = splitDate(date);
775
+ if (format === '12h') {
776
+ return { hours: hours > 12 ? hours - 12 : hours === 0 ? 12 : hours, minutes };
777
+ }
778
+ return { hours, minutes };
779
+ }
780
+ return { hours: 0, minutes: 0 };
781
+ });
782
+ constructor() {
783
+ effect(() => {
784
+ const editable = this.editable();
785
+ const selected = this.selected();
786
+ if (editable) {
787
+ if (selected === 'hours') {
788
+ this.inputHours()?.nativeElement.focus();
789
+ }
790
+ else if (selected === 'minutes') {
791
+ this.inputMinutes()?.nativeElement.focus();
792
+ }
793
+ }
794
+ });
795
+ effect(() => {
796
+ const value = this.value();
797
+ untracked(() => {
798
+ this.onHoursInput(value.hours.toString().padStart(2, '0'));
799
+ this.onMinutesInput(value.minutes.toString().padStart(2, '0'));
800
+ });
801
+ });
802
+ }
803
+ onHoursInput(event) {
804
+ const input = this.inputHours()?.nativeElement;
805
+ const value = typeof event === 'string' ? event : event.target?.value;
806
+ const max = this.format() === '24h' ? 23 : 12;
807
+ const validatedValue = validateTimeValue(value, max);
808
+ if (input && input.value !== validatedValue) {
809
+ input.value = validatedValue;
810
+ }
811
+ this.hours.set(validatedValue);
812
+ }
813
+ onMinutesInput(event) {
814
+ const input = this.inputMinutes()?.nativeElement;
815
+ const value = typeof event === 'string' ? event : event.target?.value;
816
+ const validatedValue = validateTimeValue(value, 59);
817
+ if (input && input.value !== validatedValue) {
818
+ input.value = validatedValue;
819
+ }
820
+ this.minutes.set(validatedValue);
821
+ }
822
+ onHoursBlur() {
823
+ const defaultHour = this.format() === '24h' ? '00' : '01';
824
+ const formattedValue = formatTimeValue(this.hours(), defaultHour);
825
+ this.hours.set(formattedValue);
826
+ this.updateModel();
827
+ }
828
+ onMinutesBlur() {
829
+ const formattedValue = formatTimeValue(this.minutes(), '00');
830
+ this.minutes.set(formattedValue);
831
+ this.updateModel();
832
+ }
833
+ updateModel() {
834
+ const tempDate = this.currentTime();
835
+ if (tempDate) {
836
+ const newDate = new Date(tempDate);
837
+ let hoursN = Number(this.hours());
838
+ const minutesN = Number(this.minutes());
839
+ if (this.format() === '12h' && this.period() === 'PM') {
840
+ hoursN = hoursN < 12 ? hoursN + 12 : hoursN;
841
+ }
842
+ newDate.setHours(hoursN);
843
+ newDate.setMinutes(minutesN);
844
+ this.currentTime.set(newDate);
845
+ }
846
+ }
847
+ selectedChange(selected) {
848
+ if (this.selected() === selected) {
849
+ return;
850
+ }
851
+ this.selected.set(selected);
852
+ }
853
+ periodSelector(period) {
854
+ if (this.period() === period) {
855
+ return;
856
+ }
857
+ this.period.set(period);
858
+ }
859
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.1.6", ngImport: i0, type: RkTimepickerInputLabel, deps: [], target: i0.ɵɵFactoryTarget.Component });
860
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.1.6", type: RkTimepickerInputLabel, isStandalone: true, selector: "rk-timepicker-input-label", inputs: { currentTime: { classPropertyName: "currentTime", publicName: "time", isSignal: true, isRequired: true, transformFunction: null }, selected: { classPropertyName: "selected", publicName: "selected", isSignal: true, isRequired: false, transformFunction: null }, period: { classPropertyName: "period", publicName: "period", isSignal: true, isRequired: false, transformFunction: null }, format: { classPropertyName: "format", publicName: "format", isSignal: true, isRequired: false, transformFunction: null }, editable: { classPropertyName: "editable", publicName: "editable", isSignal: true, isRequired: false, transformFunction: null }, inputSupportLabels: { classPropertyName: "inputSupportLabels", publicName: "inputLabels", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { currentTime: "timeChange", selected: "selectedChange", period: "periodChange", format: "formatChange", editable: "editableChange" }, host: { classAttribute: "rk-time-input-field" }, viewQueries: [{ propertyName: "inputHours", first: true, predicate: ["inputH"], descendants: true, isSignal: true }, { propertyName: "inputMinutes", first: true, predicate: ["inputM"], descendants: true, isSignal: true }], ngImport: i0, template: `
861
+ <div class="rk-time-input-label-container">
862
+ <div class="rk-time-selector-container" matRipple [ngClass]="{'rk-time-selector-container-selected': selected() === 'hours'}" (click)="selectedChange('hours')">
863
+ @if (!editable()) {
864
+ {{value().hours.toString().padStart(2, '0')}}
865
+ } @else {
866
+ <input type="text" maxlength="2" #inputH [value]="hours()" (focus)="selectedChange('hours')" (input)="onHoursInput($event)" (blur)="onHoursBlur()" name="hours">
867
+ }
868
+ </div>
869
+ @if (editable()) {
870
+ <span class="rk-time-input-support-label">{{inputSupportLabels()[0]}}</span>
871
+ }
872
+ </div>
873
+ <div class="rk-time-divider">:</div>
874
+ <div class="rk-time-input-label-container">
875
+ <div class="rk-time-selector-container" matRipple [ngClass]="{'rk-time-selector-container-selected': selected() === 'minutes'}" (click)="selectedChange('minutes')">
876
+ @if (!editable()) {
877
+ {{value().minutes.toString().padStart(2, '0')}}
878
+ } @else {
879
+ <input type="text" maxlength="2" #inputM [value]="minutes()" (focus)="selectedChange('minutes')" (input)="onMinutesInput($event)" (blur)="onMinutesBlur()" name="minutes">
880
+ }
881
+ </div>
882
+ @if (editable()) {
883
+ <span class="rk-time-input-support-label">{{inputSupportLabels()[1]}}</span>
884
+ }
885
+ </div>
886
+ @if (format() === '12h') {
887
+ <div class="rk-period-selector-container">
888
+ <div class="rk-period-selector" matRipple [ngClass]="{'rk-period-selector-selected': period() === 'AM'}" (click)="periodSelector('AM')">AM</div>
889
+ <div class="rk-period-selector" matRipple [ngClass]="{'rk-period-selector-selected': period() === 'PM'}" (click)="periodSelector('PM')">PM</div>
890
+ </div>
891
+ }
892
+ `, isInline: true, styles: [""], dependencies: [{ kind: "directive", type: NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "ngmodule", type: MatRippleModule }, { kind: "directive", type: i1.MatRipple, selector: "[mat-ripple], [matRipple]", inputs: ["matRippleColor", "matRippleUnbounded", "matRippleCentered", "matRippleRadius", "matRippleAnimation", "matRippleDisabled", "matRippleTrigger"], exportAs: ["matRipple"] }, { kind: "ngmodule", type: FormsModule }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
893
+ }
894
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.1.6", ngImport: i0, type: RkTimepickerInputLabel, decorators: [{
895
+ type: Component,
896
+ args: [{ selector: 'rk-timepicker-input-label', imports: [NgClass, MatRippleModule, FormsModule], host: {
897
+ 'class': 'rk-time-input-field',
898
+ }, template: `
899
+ <div class="rk-time-input-label-container">
900
+ <div class="rk-time-selector-container" matRipple [ngClass]="{'rk-time-selector-container-selected': selected() === 'hours'}" (click)="selectedChange('hours')">
901
+ @if (!editable()) {
902
+ {{value().hours.toString().padStart(2, '0')}}
903
+ } @else {
904
+ <input type="text" maxlength="2" #inputH [value]="hours()" (focus)="selectedChange('hours')" (input)="onHoursInput($event)" (blur)="onHoursBlur()" name="hours">
905
+ }
906
+ </div>
907
+ @if (editable()) {
908
+ <span class="rk-time-input-support-label">{{inputSupportLabels()[0]}}</span>
909
+ }
910
+ </div>
911
+ <div class="rk-time-divider">:</div>
912
+ <div class="rk-time-input-label-container">
913
+ <div class="rk-time-selector-container" matRipple [ngClass]="{'rk-time-selector-container-selected': selected() === 'minutes'}" (click)="selectedChange('minutes')">
914
+ @if (!editable()) {
915
+ {{value().minutes.toString().padStart(2, '0')}}
916
+ } @else {
917
+ <input type="text" maxlength="2" #inputM [value]="minutes()" (focus)="selectedChange('minutes')" (input)="onMinutesInput($event)" (blur)="onMinutesBlur()" name="minutes">
918
+ }
919
+ </div>
920
+ @if (editable()) {
921
+ <span class="rk-time-input-support-label">{{inputSupportLabels()[1]}}</span>
922
+ }
923
+ </div>
924
+ @if (format() === '12h') {
925
+ <div class="rk-period-selector-container">
926
+ <div class="rk-period-selector" matRipple [ngClass]="{'rk-period-selector-selected': period() === 'AM'}" (click)="periodSelector('AM')">AM</div>
927
+ <div class="rk-period-selector" matRipple [ngClass]="{'rk-period-selector-selected': period() === 'PM'}" (click)="periodSelector('PM')">PM</div>
928
+ </div>
929
+ }
930
+ `, standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None }]
931
+ }], ctorParameters: () => [] });
932
+
933
+ class RkTimepicker {
934
+ selected = model('hours');
935
+ format = model('12h');
936
+ period = model('AM');
937
+ editable = model(false);
938
+ orientation = model('portrait');
939
+ _inputSupportLabels = input(['Hour', 'Minute'], { alias: 'inputLabels' });
940
+ _headline = input(['Enter time', 'Select time'], { alias: 'headline' });
941
+ _actions = input(['Cancel', 'OK'], { alias: 'actions' });
942
+ /** Whether the timepicker is currently disabled. */
943
+ disabled = computed(() => !!this._input()?.disabled());
944
+ defaultTime = input(null, { alias: 'time' });
945
+ load = signal(false);
946
+ isLandscape = computed(() => {
947
+ return this.orientation() === 'landscape';
948
+ });
949
+ headline = computed(() => {
950
+ const editable = this.editable();
951
+ const labels = this._headline();
952
+ return editable ? labels[0] : labels[1];
953
+ });
954
+ _panelTemplate = viewChild.required('panelTemplate');
955
+ _dialogRef = signal(null);
956
+ _input = signal(null);
957
+ _isOpen = signal(false);
958
+ currentTime = signal(null);
959
+ animated = signal(false);
960
+ /** Emits when the timepicker is opened. */
961
+ opened = output();
962
+ /** Emits when the timepicker is closed. */
963
+ closed = output();
964
+ selectedTime = output();
965
+ _dialog = inject(Dialog);
966
+ constructor() {
967
+ effect(() => {
968
+ const value = this.defaultTime();
969
+ if (!value) {
970
+ this.currentTime.set(new Date());
971
+ }
972
+ else {
973
+ this.currentTime.set(value);
974
+ }
975
+ });
976
+ effect(() => {
977
+ const editable = this.editable();
978
+ untracked(() => {
979
+ this.animated.set(true);
980
+ });
981
+ });
982
+ afterRender(() => {
983
+ this.load.set(true);
984
+ });
985
+ }
986
+ /** Opens the timepicker. */
987
+ open() {
988
+ const input = this._input();
989
+ if (!input) {
990
+ return;
991
+ }
992
+ if (this._isOpen()) {
993
+ return;
994
+ }
995
+ this.currentTime.set(input.value());
996
+ this.animated.set(false);
997
+ this._isOpen.set(true);
998
+ this.opened.emit();
999
+ const dialogRef = this._dialog.open(this._panelTemplate(), {
1000
+ width: '328px',
1001
+ });
1002
+ this._dialogRef.set(dialogRef);
1003
+ dialogRef.closed.subscribe(result => {
1004
+ if (result) {
1005
+ this._input()?.value.set(result);
1006
+ this.selectedTime.emit(result);
1007
+ input.simulateEnter();
1008
+ }
1009
+ this._isOpen.set(false);
1010
+ this.closed.emit();
1011
+ input.focus();
1012
+ });
1013
+ }
1014
+ /** Registers an input with the timepicker. */
1015
+ registerInput(input) {
1016
+ const currentInput = this._input();
1017
+ if (currentInput && input !== currentInput) {
1018
+ console.warn('RkTimepicker can only be registered with one input at a time');
1019
+ }
1020
+ this._input.set(input);
1021
+ }
1022
+ _handleKeydown(event) {
1023
+ const keyCode = event.key;
1024
+ if (keyCode === 'Enter') {
1025
+ event.preventDefault();
1026
+ this.onConfirm();
1027
+ }
1028
+ else if (keyCode === 'Escape') {
1029
+ event.preventDefault();
1030
+ this.onCancel();
1031
+ }
1032
+ }
1033
+ changeMode() {
1034
+ this.editable.set(!this.editable());
1035
+ }
1036
+ onConfirm() {
1037
+ this._dialogRef()?.close(this.currentTime());
1038
+ }
1039
+ onCancel() {
1040
+ this._dialogRef()?.close();
1041
+ }
1042
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.1.6", ngImport: i0, type: RkTimepicker, deps: [], target: i0.ɵɵFactoryTarget.Component });
1043
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.1.6", type: RkTimepicker, isStandalone: true, selector: "rk-timepicker", inputs: { selected: { classPropertyName: "selected", publicName: "selected", isSignal: true, isRequired: false, transformFunction: null }, format: { classPropertyName: "format", publicName: "format", isSignal: true, isRequired: false, transformFunction: null }, period: { classPropertyName: "period", publicName: "period", isSignal: true, isRequired: false, transformFunction: null }, editable: { classPropertyName: "editable", publicName: "editable", isSignal: true, isRequired: false, transformFunction: null }, orientation: { classPropertyName: "orientation", publicName: "orientation", isSignal: true, isRequired: false, transformFunction: null }, _inputSupportLabels: { classPropertyName: "_inputSupportLabels", publicName: "inputLabels", isSignal: true, isRequired: false, transformFunction: null }, _headline: { classPropertyName: "_headline", publicName: "headline", isSignal: true, isRequired: false, transformFunction: null }, _actions: { classPropertyName: "_actions", publicName: "actions", isSignal: true, isRequired: false, transformFunction: null }, defaultTime: { classPropertyName: "defaultTime", publicName: "time", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { selected: "selectedChange", format: "formatChange", period: "periodChange", editable: "editableChange", orientation: "orientationChange", opened: "opened", closed: "closed", selectedTime: "selectedTime" }, host: { listeners: { "keydown": "_handleKeydown($event)" }, properties: { "class.rk-timepicker-landscape": "isLandscape()", "class.rk-timepicker-editable": "editable()" } }, viewQueries: [{ propertyName: "_panelTemplate", first: true, predicate: ["panelTemplate"], descendants: true, isSignal: true }], exportAs: ["rkTimepicker"], ngImport: i0, template: `
1044
+ <ng-template #panelTemplate>
1045
+ <div class="rk-timepicker rk-timepicker-panel">
1046
+ <div class="rk-timepicker-container" [ngClass]="{ 'rk-timepicker-container-hide': !load() }">
1047
+ <span class="rk-timepicker-headline">{{ headline() }}</span>
1048
+ <rk-timepicker-input-label [(time)]="currentTime" [inputLabels]="_inputSupportLabels()" [(editable)]="editable" [(format)]="format" [(period)]="period" [(selected)]="selected"></rk-timepicker-input-label>
1049
+ <rk-timepicker-dial [ngClass]="{ 'rk-dial-closed': editable(), 'rk-dial-animated': animated()}" [(time)]="currentTime" [(format)]="format" [(period)]="period" [(selected)]="selected"></rk-timepicker-dial>
1050
+ <div class="rk-timepicker-footer">
1051
+ <button class="rk-timepicker-mode-button" (click)="changeMode()" mat-icon-button>
1052
+ @if (editable()) {
1053
+ <mat-icon>schedule</mat-icon>
1054
+ } @else {
1055
+ <mat-icon>keyboard</mat-icon>
1056
+ }
1057
+ </button>
1058
+ <div class="rk-timepicker-actions">
1059
+ <button mat-button (click)="onCancel()">{{_actions()[0]}}</button>
1060
+ <button mat-button (click)="onConfirm()">{{_actions()[1]}}</button>
1061
+ </div>
1062
+ </div>
1063
+ </div>
1064
+ </div>
1065
+ </ng-template>
1066
+ `, isInline: true, styles: [""], dependencies: [{ kind: "component", type: RkTimepickerInputLabel, selector: "rk-timepicker-input-label", inputs: ["time", "selected", "period", "format", "editable", "inputLabels"], outputs: ["timeChange", "selectedChange", "periodChange", "formatChange", "editableChange"] }, { kind: "directive", type: NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "ngmodule", type: DialogModule }, { kind: "component", type: RkTimepickerDial, selector: "rk-timepicker-dial", inputs: ["time", "format", "selected", "period"], outputs: ["timeChange", "formatChange", "selectedChange", "periodChange"] }, { kind: "component", type: MatButton, selector: " button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button] ", exportAs: ["matButton"] }, { kind: "component", type: MatIconButton, selector: "button[mat-icon-button]", exportAs: ["matButton"] }, { kind: "component", type: MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
1067
+ }
1068
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.1.6", ngImport: i0, type: RkTimepicker, decorators: [{
1069
+ type: Component,
1070
+ args: [{ selector: 'rk-timepicker', exportAs: 'rkTimepicker', imports: [RkTimepickerInputLabel, NgClass, DialogModule, RkTimepickerDial, MatButton, MatIconButton, MatIcon], host: {
1071
+ '[class.rk-timepicker-landscape]': 'isLandscape()',
1072
+ '[class.rk-timepicker-editable]': 'editable()',
1073
+ '(keydown)': '_handleKeydown($event)',
1074
+ }, template: `
1075
+ <ng-template #panelTemplate>
1076
+ <div class="rk-timepicker rk-timepicker-panel">
1077
+ <div class="rk-timepicker-container" [ngClass]="{ 'rk-timepicker-container-hide': !load() }">
1078
+ <span class="rk-timepicker-headline">{{ headline() }}</span>
1079
+ <rk-timepicker-input-label [(time)]="currentTime" [inputLabels]="_inputSupportLabels()" [(editable)]="editable" [(format)]="format" [(period)]="period" [(selected)]="selected"></rk-timepicker-input-label>
1080
+ <rk-timepicker-dial [ngClass]="{ 'rk-dial-closed': editable(), 'rk-dial-animated': animated()}" [(time)]="currentTime" [(format)]="format" [(period)]="period" [(selected)]="selected"></rk-timepicker-dial>
1081
+ <div class="rk-timepicker-footer">
1082
+ <button class="rk-timepicker-mode-button" (click)="changeMode()" mat-icon-button>
1083
+ @if (editable()) {
1084
+ <mat-icon>schedule</mat-icon>
1085
+ } @else {
1086
+ <mat-icon>keyboard</mat-icon>
1087
+ }
1088
+ </button>
1089
+ <div class="rk-timepicker-actions">
1090
+ <button mat-button (click)="onCancel()">{{_actions()[0]}}</button>
1091
+ <button mat-button (click)="onConfirm()">{{_actions()[1]}}</button>
1092
+ </div>
1093
+ </div>
1094
+ </div>
1095
+ </div>
1096
+ </ng-template>
1097
+ `, standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None }]
1098
+ }], ctorParameters: () => [] });
1099
+
1100
+ class RkTimepickerInput {
1101
+ _elementRef = inject(ElementRef);
1102
+ _renderer = inject(Renderer2);
1103
+ /** Necessary for ControlValueAccessor implementation */
1104
+ _onChange;
1105
+ _onTouched;
1106
+ _accessorDisabled = signal(false);
1107
+ _validatorOnChange;
1108
+ /** Current value of the input. */
1109
+ value = model(null);
1110
+ _tempDateSafe = signal(null);
1111
+ /** Timepicker that the input is associated with. */
1112
+ timepicker = input.required({
1113
+ alias: 'rkTimepicker',
1114
+ });
1115
+ /** Whether the input is disabled. */
1116
+ disabled = computed(() => this.disabledInput() || this._accessorDisabled());
1117
+ /**
1118
+ * Whether the input should be disabled through the template.
1119
+ */
1120
+ disabledInput = input(false, {
1121
+ transform: booleanAttribute,
1122
+ alias: 'disabled',
1123
+ });
1124
+ constructor() {
1125
+ this._registerTimepicker();
1126
+ }
1127
+ _updateModelValue() {
1128
+ const value = this.value();
1129
+ if (!value) {
1130
+ this._validatorOnChange?.();
1131
+ return;
1132
+ }
1133
+ this._onChange?.(value);
1134
+ }
1135
+ _validateTimeOrNull(value) {
1136
+ if (value === null || value instanceof Date) {
1137
+ this.value.set(value);
1138
+ return;
1139
+ }
1140
+ const validTime = timeToSecondsi18n(value);
1141
+ const time = validTime ? secondsToDate(validTime) : null;
1142
+ if (!time) {
1143
+ this.value.set(null);
1144
+ }
1145
+ else {
1146
+ const newDate = new Date(this._tempDateSafe() ?? new Date());
1147
+ newDate.setHours(time.getHours(), time.getMinutes());
1148
+ this.value.set(newDate);
1149
+ }
1150
+ }
1151
+ _updateInputValue() {
1152
+ if (this.value() !== null) {
1153
+ this._renderer.setProperty(this._elementRef.nativeElement, 'value', this.value()?.toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" }));
1154
+ }
1155
+ }
1156
+ /** Handles the `input` event. */
1157
+ _handleInput(value) {
1158
+ this._validateTimeOrNull(value);
1159
+ this._validatorOnChange?.();
1160
+ }
1161
+ /** Handles the `keydown` event. */
1162
+ _handleKeydown(event) {
1163
+ if (['Enter', 'Escape', 'Tab'].includes(event.code)) {
1164
+ this._updateModelValue();
1165
+ this._updateInputValue();
1166
+ }
1167
+ }
1168
+ simulateEnter() {
1169
+ this._handleKeydown({ code: 'Enter' });
1170
+ }
1171
+ /** Handles the `blur` event. */
1172
+ _handleBlur() {
1173
+ this._onTouched?.();
1174
+ this._updateModelValue();
1175
+ this._updateInputValue();
1176
+ }
1177
+ /** Focuses the input. */
1178
+ focus() {
1179
+ this._elementRef.nativeElement.focus();
1180
+ }
1181
+ /** Implemented as a part of ControlValueAccessor. */
1182
+ writeValue(value) {
1183
+ if (value instanceof Date) {
1184
+ this._tempDateSafe.set(value);
1185
+ this.value.set(value);
1186
+ this._updateInputValue();
1187
+ }
1188
+ else {
1189
+ this._validateTimeOrNull(value);
1190
+ this._updateInputValue();
1191
+ }
1192
+ }
1193
+ /** Implemented as a part of ControlValueAccessor. */
1194
+ registerOnChange(fn) {
1195
+ this._onChange = fn;
1196
+ }
1197
+ /** Implemented as a part of ControlValueAccessor. */
1198
+ registerOnTouched(fn) {
1199
+ this._onTouched = fn;
1200
+ }
1201
+ /** Implemented as a part of ControlValueAccessor. */
1202
+ setDisabledState(isDisabled) {
1203
+ this._accessorDisabled.set(isDisabled);
1204
+ }
1205
+ // implemented as a part of Validator
1206
+ validate(control) {
1207
+ if (control.value == null || this.value() == null) {
1208
+ return { invalidTimeFormat: true };
1209
+ }
1210
+ return null;
1211
+ }
1212
+ registerOnValidatorChange(fn) {
1213
+ this._validatorOnChange = fn;
1214
+ }
1215
+ /** Sets up the logic that registers the input with the timepicker. */
1216
+ _registerTimepicker() {
1217
+ effect(() => {
1218
+ const timepicker = this.timepicker();
1219
+ timepicker.registerInput(this);
1220
+ timepicker.closed.subscribe(() => this._onTouched?.());
1221
+ });
1222
+ }
1223
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.1.6", ngImport: i0, type: RkTimepickerInput, deps: [], target: i0.ɵɵFactoryTarget.Directive });
1224
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "19.1.6", type: RkTimepickerInput, isStandalone: true, selector: "input[rkTimepicker]", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, timepicker: { classPropertyName: "timepicker", publicName: "rkTimepicker", isSignal: true, isRequired: true, transformFunction: null }, disabledInput: { classPropertyName: "disabledInput", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange" }, host: { attributes: { "type": "text" }, listeners: { "input": "_handleInput($event.target.value)", "blur": "_handleBlur()", "keydown": "_handleKeydown($event)" }, properties: { "disabled": "disabled()" }, classAttribute: "rk-timepicker-input" }, providers: [
1225
+ {
1226
+ provide: NG_VALUE_ACCESSOR,
1227
+ useExisting: RkTimepickerInput,
1228
+ multi: true,
1229
+ },
1230
+ {
1231
+ provide: NG_VALIDATORS,
1232
+ useExisting: RkTimepickerInput,
1233
+ multi: true,
1234
+ },
1235
+ {
1236
+ provide: MAT_INPUT_VALUE_ACCESSOR,
1237
+ useExisting: RkTimepickerInput,
1238
+ },
1239
+ ], exportAs: ["rkTimepickerInput"], ngImport: i0 });
1240
+ }
1241
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.1.6", ngImport: i0, type: RkTimepickerInput, decorators: [{
1242
+ type: Directive,
1243
+ args: [{
1244
+ selector: 'input[rkTimepicker]',
1245
+ exportAs: 'rkTimepickerInput',
1246
+ host: {
1247
+ 'class': 'rk-timepicker-input',
1248
+ 'type': 'text',
1249
+ '(input)': '_handleInput($event.target.value)',
1250
+ '(blur)': '_handleBlur()',
1251
+ '(keydown)': '_handleKeydown($event)',
1252
+ '[disabled]': 'disabled()',
1253
+ },
1254
+ providers: [
1255
+ {
1256
+ provide: NG_VALUE_ACCESSOR,
1257
+ useExisting: RkTimepickerInput,
1258
+ multi: true,
1259
+ },
1260
+ {
1261
+ provide: NG_VALIDATORS,
1262
+ useExisting: RkTimepickerInput,
1263
+ multi: true,
1264
+ },
1265
+ {
1266
+ provide: MAT_INPUT_VALUE_ACCESSOR,
1267
+ useExisting: RkTimepickerInput,
1268
+ },
1269
+ ]
1270
+ }]
1271
+ }], ctorParameters: () => [] });
1272
+
1273
+ class RkTimepickerToggle {
1274
+ timepicker = input.required({
1275
+ alias: 'for',
1276
+ });
1277
+ /** Whether the toggle button is disabled. */
1278
+ disabled = input(false, {
1279
+ transform: booleanAttribute,
1280
+ alias: 'disabled',
1281
+ });
1282
+ _isDisabled = computed(() => {
1283
+ const timepicker = this.timepicker();
1284
+ return this.disabled() || timepicker.disabled();
1285
+ });
1286
+ /** Opens the connected timepicker. */
1287
+ _open(event) {
1288
+ if (this.timepicker() && !this._isDisabled()) {
1289
+ this.timepicker().open();
1290
+ event.stopPropagation();
1291
+ }
1292
+ }
1293
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.1.6", ngImport: i0, type: RkTimepickerToggle, deps: [], target: i0.ɵɵFactoryTarget.Component });
1294
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "19.1.6", type: RkTimepickerToggle, isStandalone: true, selector: "rk-timepicker-toggle", inputs: { timepicker: { classPropertyName: "timepicker", publicName: "for", isSignal: true, isRequired: true, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: `
1295
+ <button mat-icon-button type="button" [disabled]="_isDisabled()" (click)="_open($event)">
1296
+ <ng-content select="[rkTimepickerToggleIcon]">
1297
+ <svg
1298
+ class="rk-timepicker-toggle-default-icon"
1299
+ height="24px"
1300
+ width="24px"
1301
+ viewBox="0 -960 960 960"
1302
+ fill="currentColor"
1303
+ focusable="false"
1304
+ aria-hidden="true">
1305
+ <path d="m612-292 56-56-148-148v-184h-80v216l172 172ZM480-80q-83 0-156-31.5T197-197q-54-54-85.5-127T80-480q0-83 31.5-156T197-763q54-54 127-85.5T480-880q83 0 156 31.5T763-763q54 54 85.5 127T880-480q0 83-31.5 156T763-197q-54 54-127 85.5T480-80Zm0-400Zm0 320q133 0 226.5-93.5T800-480q0-133-93.5-226.5T480-800q-133 0-226.5 93.5T160-480q0 133 93.5 226.5T480-160Z"/>
1306
+ </svg>
1307
+ </ng-content>
1308
+ </button>
1309
+ `, isInline: true, styles: [""], dependencies: [{ kind: "component", type: MatIconButton, selector: "button[mat-icon-button]", exportAs: ["matButton"] }] });
1310
+ }
1311
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.1.6", ngImport: i0, type: RkTimepickerToggle, decorators: [{
1312
+ type: Component,
1313
+ args: [{ selector: 'rk-timepicker-toggle', imports: [MatIconButton], template: `
1314
+ <button mat-icon-button type="button" [disabled]="_isDisabled()" (click)="_open($event)">
1315
+ <ng-content select="[rkTimepickerToggleIcon]">
1316
+ <svg
1317
+ class="rk-timepicker-toggle-default-icon"
1318
+ height="24px"
1319
+ width="24px"
1320
+ viewBox="0 -960 960 960"
1321
+ fill="currentColor"
1322
+ focusable="false"
1323
+ aria-hidden="true">
1324
+ <path d="m612-292 56-56-148-148v-184h-80v216l172 172ZM480-80q-83 0-156-31.5T197-197q-54-54-85.5-127T80-480q0-83 31.5-156T197-763q54-54 127-85.5T480-880q83 0 156 31.5T763-763q54 54 85.5 127T880-480q0 83-31.5 156T763-197q-54 54-127 85.5T480-80Zm0-400Zm0 320q133 0 226.5-93.5T800-480q0-133-93.5-226.5T480-800q-133 0-226.5 93.5T160-480q0 133 93.5 226.5T480-160Z"/>
1325
+ </svg>
1326
+ </ng-content>
1327
+ </button>
1328
+ ` }]
1329
+ }] });
1330
+
1331
+ class RkClock {
1332
+ value = model(null, { alias: 'time' });
1333
+ selected = model('hours');
1334
+ format = model('24h');
1335
+ period = model('AM');
1336
+ editable = model(false);
1337
+ orientation = model('portrait');
1338
+ inputSupportLabels = input(['Hour', 'Minute'], { alias: 'inputLabels' });
1339
+ _headline = input(['Enter time', 'Select time'], { alias: 'headline' });
1340
+ load = signal(false);
1341
+ isLandscape = computed(() => {
1342
+ return this.orientation() === 'landscape';
1343
+ });
1344
+ headline = computed(() => {
1345
+ const editable = this.editable();
1346
+ const labels = this._headline();
1347
+ return editable ? labels[0] : labels[1];
1348
+ });
1349
+ constructor() {
1350
+ effect(() => {
1351
+ const value = this.value();
1352
+ if (!value) {
1353
+ this.value.set(new Date());
1354
+ }
1355
+ ;
1356
+ });
1357
+ afterRender(() => {
1358
+ this.load.set(true);
1359
+ });
1360
+ }
1361
+ changeMode() {
1362
+ this.editable.set(!this.editable());
1363
+ }
1364
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.1.6", ngImport: i0, type: RkClock, deps: [], target: i0.ɵɵFactoryTarget.Component });
1365
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.1.6", type: RkClock, isStandalone: true, selector: "rk-clock", inputs: { value: { classPropertyName: "value", publicName: "time", isSignal: true, isRequired: false, transformFunction: null }, selected: { classPropertyName: "selected", publicName: "selected", isSignal: true, isRequired: false, transformFunction: null }, format: { classPropertyName: "format", publicName: "format", isSignal: true, isRequired: false, transformFunction: null }, period: { classPropertyName: "period", publicName: "period", isSignal: true, isRequired: false, transformFunction: null }, editable: { classPropertyName: "editable", publicName: "editable", isSignal: true, isRequired: false, transformFunction: null }, orientation: { classPropertyName: "orientation", publicName: "orientation", isSignal: true, isRequired: false, transformFunction: null }, inputSupportLabels: { classPropertyName: "inputSupportLabels", publicName: "inputLabels", isSignal: true, isRequired: false, transformFunction: null }, _headline: { classPropertyName: "_headline", publicName: "headline", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "timeChange", selected: "selectedChange", format: "formatChange", period: "periodChange", editable: "editableChange", orientation: "orientationChange" }, host: { properties: { "class.rk-timepicker-landscape": "isLandscape()", "class.rk-timepicker-editable": "editable()" }, classAttribute: "rk-timepicker" }, exportAs: ["rkClock"], ngImport: i0, template: `
1366
+ <div class="rk-timepicker-container" [ngClass]="{ 'rk-timepicker-container-hide': !load() }">
1367
+ <span class="rk-timepicker-headline">{{ headline() }}</span>
1368
+ <rk-timepicker-input-label [(time)]="value" [inputLabels]="inputSupportLabels()" [(editable)]="editable" [(format)]="format" [(period)]="period" [(selected)]="selected"></rk-timepicker-input-label>
1369
+ <rk-timepicker-dial [ngClass]="{ 'rk-dial-closed': editable()}" [(time)]="value" [(format)]="format" [(period)]="period" [(selected)]="selected"></rk-timepicker-dial>
1370
+ <div class="rk-timepicker-footer">
1371
+ <button class="rk-timepicker-mode-button" (click)="changeMode()" mat-icon-button>
1372
+ @if (editable()) {
1373
+ <mat-icon>schedule</mat-icon>
1374
+ } @else {
1375
+ <mat-icon>keyboard</mat-icon>
1376
+ }
1377
+ </button>
1378
+ </div>
1379
+ </div>
1380
+ `, isInline: true, styles: [""], dependencies: [{ kind: "component", type: RkTimepickerInputLabel, selector: "rk-timepicker-input-label", inputs: ["time", "selected", "period", "format", "editable", "inputLabels"], outputs: ["timeChange", "selectedChange", "periodChange", "formatChange", "editableChange"] }, { kind: "directive", type: NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "component", type: RkTimepickerDial, selector: "rk-timepicker-dial", inputs: ["time", "format", "selected", "period"], outputs: ["timeChange", "formatChange", "selectedChange", "periodChange"] }, { kind: "component", type: MatIconButton, selector: "button[mat-icon-button]", exportAs: ["matButton"] }, { kind: "component", type: MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
1381
+ }
1382
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.1.6", ngImport: i0, type: RkClock, decorators: [{
1383
+ type: Component,
1384
+ args: [{ selector: 'rk-clock', exportAs: 'rkClock', imports: [RkTimepickerInputLabel, NgClass, RkTimepickerDial, MatButton, MatIconButton, MatIcon], host: {
1385
+ 'class': 'rk-timepicker',
1386
+ '[class.rk-timepicker-landscape]': 'isLandscape()',
1387
+ '[class.rk-timepicker-editable]': 'editable()',
1388
+ }, template: `
1389
+ <div class="rk-timepicker-container" [ngClass]="{ 'rk-timepicker-container-hide': !load() }">
1390
+ <span class="rk-timepicker-headline">{{ headline() }}</span>
1391
+ <rk-timepicker-input-label [(time)]="value" [inputLabels]="inputSupportLabels()" [(editable)]="editable" [(format)]="format" [(period)]="period" [(selected)]="selected"></rk-timepicker-input-label>
1392
+ <rk-timepicker-dial [ngClass]="{ 'rk-dial-closed': editable()}" [(time)]="value" [(format)]="format" [(period)]="period" [(selected)]="selected"></rk-timepicker-dial>
1393
+ <div class="rk-timepicker-footer">
1394
+ <button class="rk-timepicker-mode-button" (click)="changeMode()" mat-icon-button>
1395
+ @if (editable()) {
1396
+ <mat-icon>schedule</mat-icon>
1397
+ } @else {
1398
+ <mat-icon>keyboard</mat-icon>
1399
+ }
1400
+ </button>
1401
+ </div>
1402
+ </div>
1403
+ `, standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None }]
1404
+ }], ctorParameters: () => [] });
1405
+
1406
+ /*
1407
+ * Public API Surface of timepicker
1408
+ */
1409
+
1410
+ /**
1411
+ * Generated bundle index. Do not edit.
1412
+ */
1413
+
1414
+ export { HOURS_LABEL, HOURS_LABEL_24_P1, HOURS_LABEL_24_P2, MINUTES_LABEL, RkClock, RkTimepicker, RkTimepickerInput, RkTimepickerToggle, amPmTransform, angleToHours, angleToHours24, angleToMinutes, createPointerEvents, detectTimeFormat, formatTimeValue, hours24ToAngle, hoursToAngle, minutesToAngle, normalizeEvent, secondsToDate, snapAngle, splitDate, timeToSeconds, timeToSecondsi18n, validateTimeValue };
1415
+ //# sourceMappingURL=redkhat-timepicker.mjs.map