@praxisui/cron-builder 6.0.0-beta.0 → 8.0.0-beta.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.
package/README.md CHANGED
@@ -93,35 +93,10 @@ Inputs/Outputs:
93
93
  - `previewOccurrences?: number` – number of preview dates to show.
94
94
  - `validators?: { invalidCronMessage?: string }` – customize error messages.
95
95
 
96
- ## CommonJS/ESM Notes
97
-
98
- This component dynamically imports `cron-parser`, `cronstrue` and `cron-validator`. Some versions of these, or their transitive deps (e.g. `luxon`), may emit CommonJS warnings in Angular builds.
99
-
100
- If you want to suppress warnings in your app build, add them to `allowedCommonJsDependencies`:
101
-
102
- ```json
103
- // angular.json
104
- {
105
- "projects": {
106
- "your-app": {
107
- "architect": {
108
- "build": {
109
- "options": {
110
- "allowedCommonJsDependencies": [
111
- "cron-parser",
112
- "cronstrue",
113
- "cron-validator",
114
- "luxon"
115
- ]
116
- }
117
- }
118
- }
119
- }
120
- }
121
- }
122
- ```
96
+ ## Build Notes
123
97
 
124
- Note: Depending on your Angular CLI version, the warning behavior can vary. The above option is supported in current CLI releases when using the application builder.
98
+ The builder resolves validation, humanized descriptions and occurrence preview inside the library runtime.
99
+ Apps consuming `@praxisui/cron-builder` should not need `allowedCommonJsDependencies` entries just to use the component.
125
100
 
126
101
  ## Compatibility
127
102
 
@@ -30,10 +30,405 @@ import { PraxisIconDirective } from '@praxisui/core';
30
30
  import { Subject } from 'rxjs';
31
31
  import { takeUntil } from 'rxjs/operators';
32
32
 
33
- // Lazy module singletons
34
- let _cronValidator;
35
- let _cronstrue;
36
- let _cronParser;
33
+ const MONTH_NAMES = {
34
+ JAN: 1,
35
+ FEB: 2,
36
+ MAR: 3,
37
+ APR: 4,
38
+ MAY: 5,
39
+ JUN: 6,
40
+ JUL: 7,
41
+ AUG: 8,
42
+ SEP: 9,
43
+ OCT: 10,
44
+ NOV: 11,
45
+ DEC: 12,
46
+ };
47
+ const WEEKDAY_NAMES = {
48
+ SUN: 0,
49
+ MON: 1,
50
+ TUE: 2,
51
+ WED: 3,
52
+ THU: 4,
53
+ FRI: 5,
54
+ SAT: 6,
55
+ };
56
+ const WEEKDAY_LABELS_EN = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'];
57
+ const zonedFormatters = new Map();
58
+ const timeFormatters = new Map();
59
+ function getTimeFormatter(locale) {
60
+ const key = locale || 'en-US';
61
+ if (!timeFormatters.has(key)) {
62
+ timeFormatters.set(key, new Intl.DateTimeFormat(key, {
63
+ hour: 'numeric',
64
+ minute: '2-digit',
65
+ hour12: true,
66
+ timeZone: 'UTC',
67
+ }));
68
+ }
69
+ return timeFormatters.get(key);
70
+ }
71
+ function getZonedFormatter(timeZone) {
72
+ const key = timeZone || 'UTC';
73
+ if (!zonedFormatters.has(key)) {
74
+ zonedFormatters.set(key, new Intl.DateTimeFormat('en-US', {
75
+ timeZone: key,
76
+ year: 'numeric',
77
+ month: '2-digit',
78
+ day: '2-digit',
79
+ hour: '2-digit',
80
+ minute: '2-digit',
81
+ second: '2-digit',
82
+ weekday: 'short',
83
+ hour12: false,
84
+ }));
85
+ }
86
+ return zonedFormatters.get(key);
87
+ }
88
+ function normalizeToken(token) {
89
+ return token.trim().toUpperCase();
90
+ }
91
+ function normalizeValue(raw, aliases) {
92
+ const normalized = normalizeToken(raw);
93
+ return normalized.replace(/[A-Z]{3}/g, (match) => {
94
+ const resolved = aliases[match];
95
+ return resolved == null ? match : String(resolved);
96
+ });
97
+ }
98
+ function makeAnyField() {
99
+ return { any: true, values: new Set(), nthWeekday: null };
100
+ }
101
+ function parseSimpleNumber(value, min, max) {
102
+ if (!/^\d+$/.test(value)) {
103
+ return null;
104
+ }
105
+ const parsed = Number(value);
106
+ if (!Number.isInteger(parsed) || parsed < min || parsed > max) {
107
+ return null;
108
+ }
109
+ return parsed;
110
+ }
111
+ function addRange(values, start, end, step, min, max) {
112
+ if (step < 1 || start > end || start < min || end > max) {
113
+ return false;
114
+ }
115
+ for (let current = start; current <= end; current += step) {
116
+ values.add(current);
117
+ }
118
+ return true;
119
+ }
120
+ function parseCronField(raw, min, max, aliases = {}, options) {
121
+ const token = normalizeValue(raw, aliases);
122
+ if (token === '*' || (options?.allowQuestion && token === '?')) {
123
+ return makeAnyField();
124
+ }
125
+ const field = { any: false, values: new Set(), nthWeekday: null };
126
+ for (const piece of token.split(',')) {
127
+ const current = piece.trim();
128
+ if (!current) {
129
+ return null;
130
+ }
131
+ if (options?.allowNthWeekday && current.includes('#')) {
132
+ const [weekdayRaw, nthRaw] = current.split('#');
133
+ const weekday = parseSimpleNumber(weekdayRaw, min, max);
134
+ const nth = parseSimpleNumber(nthRaw, 1, 5);
135
+ if (weekday == null || nth == null) {
136
+ return null;
137
+ }
138
+ field.nthWeekday = {
139
+ weekday: options.weekdayField ? normalizeWeekday(weekday) : weekday,
140
+ nth,
141
+ };
142
+ continue;
143
+ }
144
+ const [base, stepRaw] = current.split('/');
145
+ const step = stepRaw == null ? 1 : parseSimpleNumber(stepRaw, 1, max - min + 1);
146
+ if (step == null) {
147
+ return null;
148
+ }
149
+ if (base === '*') {
150
+ if (!addRange(field.values, min, max, step, min, max)) {
151
+ return null;
152
+ }
153
+ continue;
154
+ }
155
+ if (base.includes('-')) {
156
+ const [startRaw, endRaw] = base.split('-');
157
+ const start = parseSimpleNumber(startRaw, min, max);
158
+ const end = parseSimpleNumber(endRaw, min, max);
159
+ if (start == null || end == null) {
160
+ return null;
161
+ }
162
+ if (!addRange(field.values, normalizeWeekday(start, options?.weekdayField), normalizeWeekday(end, options?.weekdayField), step, min, max)) {
163
+ return null;
164
+ }
165
+ continue;
166
+ }
167
+ const single = parseSimpleNumber(base, min, max);
168
+ if (single == null) {
169
+ return null;
170
+ }
171
+ const normalizedSingle = normalizeWeekday(single, options?.weekdayField);
172
+ if (stepRaw == null) {
173
+ field.values.add(normalizedSingle);
174
+ continue;
175
+ }
176
+ if (!addRange(field.values, normalizedSingle, max, step, min, max)) {
177
+ return null;
178
+ }
179
+ }
180
+ return field.values.size > 0 || field.nthWeekday != null ? field : null;
181
+ }
182
+ function normalizeWeekday(value, weekdayField = false) {
183
+ if (!weekdayField) {
184
+ return value;
185
+ }
186
+ return value === 7 ? 0 : value;
187
+ }
188
+ function getZonedDateParts(date, timeZone) {
189
+ const formatter = getZonedFormatter(timeZone || 'UTC');
190
+ const parts = formatter.formatToParts(date);
191
+ const record = Object.fromEntries(parts.map((part) => [part.type, part.value]));
192
+ return {
193
+ year: Number(record.year),
194
+ month: Number(record.month),
195
+ day: Number(record.day),
196
+ hour: Number(record.hour),
197
+ minute: Number(record.minute),
198
+ second: Number(record.second),
199
+ weekday: WEEKDAY_NAMES[record.weekday.toUpperCase()] ?? 0,
200
+ };
201
+ }
202
+ function isNthWeekdayInMonth(parts, nthWeekday) {
203
+ if (parts.weekday !== nthWeekday.weekday) {
204
+ return false;
205
+ }
206
+ const occurrence = Math.floor((parts.day - 1) / 7) + 1;
207
+ return occurrence === nthWeekday.nth;
208
+ }
209
+ function matchesField(field, value, parts) {
210
+ if (field.any) {
211
+ return true;
212
+ }
213
+ if (field.nthWeekday) {
214
+ return isNthWeekdayInMonth(parts, field.nthWeekday);
215
+ }
216
+ return field.values.has(value);
217
+ }
218
+ function matchesDay(parsed, parts) {
219
+ const domAny = parsed.dayOfMonth.any;
220
+ const dowAny = parsed.dayOfWeek.any && parsed.dayOfWeek.nthWeekday == null;
221
+ const domMatch = matchesField(parsed.dayOfMonth, parts.day, parts);
222
+ const dowMatch = matchesField(parsed.dayOfWeek, parts.weekday, parts);
223
+ if (domAny && dowAny) {
224
+ return true;
225
+ }
226
+ if (domAny) {
227
+ return dowMatch;
228
+ }
229
+ if (dowAny) {
230
+ return domMatch;
231
+ }
232
+ return domMatch || dowMatch;
233
+ }
234
+ function parseCronExpression(expression) {
235
+ const trimmed = expression.trim();
236
+ if (!trimmed) {
237
+ return null;
238
+ }
239
+ const parts = trimmed.split(/\s+/);
240
+ if (parts.length !== 5 && parts.length !== 6) {
241
+ return null;
242
+ }
243
+ const hasSeconds = parts.length === 6;
244
+ const [secondsRaw, minutesRaw, hoursRaw, domRaw, monthRaw, dowRaw] = hasSeconds
245
+ ? parts
246
+ : ['0', ...parts];
247
+ const seconds = parseCronField(secondsRaw, 0, 59);
248
+ const minutes = parseCronField(minutesRaw, 0, 59);
249
+ const hours = parseCronField(hoursRaw, 0, 23);
250
+ const dayOfMonth = parseCronField(domRaw, 1, 31, {}, { allowQuestion: true });
251
+ const month = parseCronField(monthRaw, 1, 12, MONTH_NAMES);
252
+ const dayOfWeek = parseCronField(dowRaw, 0, 7, WEEKDAY_NAMES, {
253
+ allowQuestion: true,
254
+ allowNthWeekday: true,
255
+ weekdayField: true,
256
+ });
257
+ if (!seconds || !minutes || !hours || !dayOfMonth || !month || !dayOfWeek) {
258
+ return null;
259
+ }
260
+ return {
261
+ raw: trimmed,
262
+ hasSeconds,
263
+ seconds,
264
+ minutes,
265
+ hours,
266
+ dayOfMonth,
267
+ month,
268
+ dayOfWeek,
269
+ };
270
+ }
271
+ function isValidCronExpression(expression) {
272
+ return parseCronExpression(expression) != null;
273
+ }
274
+ function formatTime(hours, minutes, locale) {
275
+ const seed = new Date(Date.UTC(2024, 0, 1, hours, minutes, 0));
276
+ return getTimeFormatter(locale || 'en-US').format(seed);
277
+ }
278
+ function formatWeekdayList(days) {
279
+ if (days.length === 0) {
280
+ return '';
281
+ }
282
+ if (days.length === 2 && days[0] + 1 === days[1]) {
283
+ return `${WEEKDAY_LABELS_EN[days[0]]} through ${WEEKDAY_LABELS_EN[days[1]]}`;
284
+ }
285
+ if (days.length === 5 && days.join(',') === '1,2,3,4,5') {
286
+ return 'Monday through Friday';
287
+ }
288
+ if (days.length === 7) {
289
+ return 'every day';
290
+ }
291
+ return days.map((day) => WEEKDAY_LABELS_EN[day]).join(', ');
292
+ }
293
+ function ordinal(value) {
294
+ if (value % 100 >= 11 && value % 100 <= 13) {
295
+ return `${value}th`;
296
+ }
297
+ switch (value % 10) {
298
+ case 1:
299
+ return `${value}st`;
300
+ case 2:
301
+ return `${value}nd`;
302
+ case 3:
303
+ return `${value}rd`;
304
+ default:
305
+ return `${value}th`;
306
+ }
307
+ }
308
+ function humanizeCronExpression(expression, locale = 'en-US') {
309
+ const parsed = parseCronExpression(expression);
310
+ if (!parsed) {
311
+ return '';
312
+ }
313
+ const minutes = parsed.minutes.values;
314
+ const hours = parsed.hours.values;
315
+ const dom = parsed.dayOfMonth.values;
316
+ const dow = parsed.dayOfWeek.values;
317
+ const nthWeekday = parsed.dayOfWeek.nthWeekday;
318
+ const secondsIsZero = parsed.seconds.any || parsed.seconds.values.size === 1 && parsed.seconds.values.has(0);
319
+ if (secondsIsZero &&
320
+ parsed.minutes.any &&
321
+ parsed.hours.any &&
322
+ parsed.dayOfMonth.any &&
323
+ parsed.month.any &&
324
+ parsed.dayOfWeek.any) {
325
+ return 'Every minute';
326
+ }
327
+ if (secondsIsZero &&
328
+ !parsed.minutes.any &&
329
+ minutes.size > 1 &&
330
+ hours.size === 24 &&
331
+ parsed.dayOfMonth.any &&
332
+ parsed.month.any &&
333
+ parsed.dayOfWeek.any) {
334
+ const orderedMinutes = Array.from(minutes).sort((a, b) => a - b);
335
+ const step = orderedMinutes[1] - orderedMinutes[0];
336
+ const isRegularStep = orderedMinutes.every((value, index) => index === 0 || value - orderedMinutes[index - 1] === step);
337
+ if (isRegularStep && orderedMinutes[0] === 0) {
338
+ return `Every ${step} minutes`;
339
+ }
340
+ }
341
+ if (parsed.seconds.any &&
342
+ !parsed.minutes.any &&
343
+ minutes.size === 1 &&
344
+ !parsed.hours.any &&
345
+ hours.size === 1 &&
346
+ parsed.month.any &&
347
+ parsed.dayOfMonth.any &&
348
+ parsed.dayOfWeek.any) {
349
+ return `At ${formatTime(Array.from(hours)[0], Array.from(minutes)[0], locale)}`;
350
+ }
351
+ if (minutes.size === 1 && hours.size === 1 && parsed.month.any && parsed.dayOfMonth.any && parsed.dayOfWeek.any) {
352
+ return `At ${formatTime(Array.from(hours)[0], Array.from(minutes)[0], locale)}`;
353
+ }
354
+ if (minutes.size === 1 && hours.size === 1 && parsed.month.any && parsed.dayOfMonth.any && !parsed.dayOfWeek.any && nthWeekday == null) {
355
+ const orderedDays = Array.from(dow).sort((a, b) => a - b);
356
+ return `At ${formatTime(Array.from(hours)[0], Array.from(minutes)[0], locale)}, ${formatWeekdayList(orderedDays)}`;
357
+ }
358
+ if (minutes.size === 1 && hours.size === 1 && parsed.month.any && !parsed.dayOfMonth.any && parsed.dayOfWeek.any) {
359
+ return `At ${formatTime(Array.from(hours)[0], Array.from(minutes)[0], locale)}, on day ${Array.from(dom)[0]} of the month`;
360
+ }
361
+ if (minutes.size === 1 && hours.size === 1 && parsed.month.any && nthWeekday) {
362
+ return `At ${formatTime(Array.from(hours)[0], Array.from(minutes)[0], locale)}, on the ${ordinal(nthWeekday.nth)} ${WEEKDAY_LABELS_EN[nthWeekday.weekday]} of the month`;
363
+ }
364
+ const fields = parsed.hasSeconds
365
+ ? [
366
+ fieldToString(parsed.seconds),
367
+ fieldToString(parsed.minutes),
368
+ fieldToString(parsed.hours),
369
+ fieldToString(parsed.dayOfMonth),
370
+ fieldToString(parsed.month),
371
+ fieldToString(parsed.dayOfWeek),
372
+ ]
373
+ : [
374
+ fieldToString(parsed.minutes),
375
+ fieldToString(parsed.hours),
376
+ fieldToString(parsed.dayOfMonth),
377
+ fieldToString(parsed.month),
378
+ fieldToString(parsed.dayOfWeek),
379
+ ];
380
+ return `Runs on schedule ${fields.join(' ')}`;
381
+ }
382
+ function fieldToString(field) {
383
+ if (field.any) {
384
+ return '*';
385
+ }
386
+ if (field.nthWeekday) {
387
+ return `${field.nthWeekday.weekday}#${field.nthWeekday.nth}`;
388
+ }
389
+ return Array.from(field.values).sort((a, b) => a - b).join(',');
390
+ }
391
+ function roundCandidate(date, hasSeconds) {
392
+ const rounded = new Date(date.getTime());
393
+ if (hasSeconds) {
394
+ rounded.setMilliseconds(0);
395
+ rounded.setSeconds(rounded.getSeconds() + 1);
396
+ }
397
+ else {
398
+ rounded.setSeconds(0, 0);
399
+ rounded.setMinutes(rounded.getMinutes() + 1);
400
+ }
401
+ return rounded;
402
+ }
403
+ function matchesCron(parsed, date, timeZone) {
404
+ const parts = getZonedDateParts(date, timeZone || 'UTC');
405
+ return (matchesField(parsed.month, parts.month, parts) &&
406
+ matchesDay(parsed, parts) &&
407
+ matchesField(parsed.hours, parts.hour, parts) &&
408
+ matchesField(parsed.minutes, parts.minute, parts) &&
409
+ matchesField(parsed.seconds, parts.second, parts));
410
+ }
411
+ function getNextCronOccurrences(expression, count, options) {
412
+ const parsed = parseCronExpression(expression);
413
+ if (!parsed || count <= 0) {
414
+ return [];
415
+ }
416
+ const timeZone = options?.timeZone || 'UTC';
417
+ const results = [];
418
+ const stepMs = parsed.hasSeconds ? 1000 : 60_000;
419
+ let cursor = roundCandidate(options?.currentDate ?? new Date(), parsed.hasSeconds);
420
+ let iterations = 0;
421
+ const maxIterations = parsed.hasSeconds ? 250_000 : 50_000;
422
+ while (results.length < count && iterations < maxIterations) {
423
+ if (matchesCron(parsed, cursor, timeZone)) {
424
+ results.push(new Date(cursor.getTime()));
425
+ }
426
+ cursor = new Date(cursor.getTime() + stepMs);
427
+ iterations += 1;
428
+ }
429
+ return results;
430
+ }
431
+
37
432
  class PdxCronBuilderComponent {
38
433
  // Tipos auxiliares para Typed Forms
39
434
  fb = inject(NonNullableFormBuilder);
@@ -201,58 +596,26 @@ class PdxCronBuilderComponent {
201
596
  }
202
597
  }
203
598
  validate(cron) {
204
- (async () => {
205
- try {
206
- _cronValidator = _cronValidator || (await import('cron-validator'));
207
- const ok = await _cronValidator.isValidCron?.(cron, { alias: true, allowBlankDay: true });
208
- this.error = ok
209
- ? null
210
- : this.metadata.validators?.invalidCronMessage || 'Invalid CRON expression';
211
- }
212
- catch {
213
- // On import/validation failure, do a minimal sanity check and do not block UX
214
- this.error = /\S+ \S+ \S+ \S+ \S+/.test(cron) ? null : 'Invalid CRON expression';
215
- }
216
- })();
599
+ this.error = isValidCronExpression(cron)
600
+ ? null
601
+ : this.metadata.validators?.invalidCronMessage || 'Invalid CRON expression';
217
602
  }
218
603
  humanize(cron) {
219
604
  if (this.error) {
220
605
  this.humanized = '';
221
606
  return;
222
607
  }
223
- (async () => {
224
- try {
225
- _cronstrue = _cronstrue || (await import('cronstrue'));
226
- this.humanized = _cronstrue.toString?.(cron, { locale: this.metadata.locale }) || '';
227
- }
228
- catch {
229
- this.humanized = '';
230
- }
231
- })();
608
+ this.humanized = humanizeCronExpression(cron, this.metadata.locale || 'en-US');
232
609
  }
233
610
  generatePreview(cron) {
234
611
  if (this.error || !this.metadata.previewOccurrences) {
235
612
  this.preview = [];
236
613
  return;
237
614
  }
238
- (async () => {
239
- try {
240
- _cronParser = _cronParser || (await import('cron-parser'));
241
- const parse = _cronParser.parse || _cronParser.default?.parse;
242
- const interval = parse?.(cron, {
243
- currentDate: this.metadata.previewFrom ?? new Date(),
244
- tz: this.metadata.timezone,
245
- });
246
- const next = [];
247
- for (let i = 0; i < (this.metadata.previewOccurrences ?? 5); i++) {
248
- next.push(interval.next().toDate());
249
- }
250
- this.preview = next;
251
- }
252
- catch {
253
- this.preview = [];
254
- }
255
- })();
615
+ this.preview = getNextCronOccurrences(cron, this.metadata.previewOccurrences ?? 5, {
616
+ currentDate: this.metadata.previewFrom ?? new Date(),
617
+ timeZone: this.metadata.timezone,
618
+ });
256
619
  }
257
620
  parseCronString(cron) {
258
621
  const parts = cron.trim().split(/\s+/);
@@ -384,7 +747,7 @@ class PdxCronBuilderComponent {
384
747
  useExisting: forwardRef(() => PdxCronBuilderComponent),
385
748
  multi: true,
386
749
  },
387
- ], ngImport: i0, template: "<div class=\"cron-builder-container\" (focusout)=\"onTouched()\">\n @if (metadata.mode === 'both') {\n <mat-tab-group\n [selectedIndex]=\"selectedTabIndex\"\n (selectedTabChange)=\"onTabChange($event)\"\n >\n <mat-tab label=\"Simple\"></mat-tab>\n <mat-tab label=\"Advanced\"></mat-tab>\n </mat-tab-group>\n }\n\n @if (value) {\n <div class=\"cron-expression\">\n <mat-form-field appearance=\"outline\" class=\"cron-expression-field\">\n <mat-label>CRON Expression</mat-label>\n <input matInput [value]=\"value\" readonly />\n <button\n mat-icon-button\n matSuffix\n (click)=\"copyCron()\"\n [matTooltip]=\"'Copy to clipboard'\"\n aria-label=\"Copy CRON expression\"\n >\n <mat-icon [praxisIcon]=\"'content_copy'\"></mat-icon>\n </button>\n </mat-form-field>\n <button mat-button (click)=\"importCron()\">Import CRON</button>\n </div>\n }\n\n @switch (activeTab) {\n @case ('simple') {\n <div [formGroup]=\"simpleForm\" class=\"simple-mode\">\n <mat-form-field appearance=\"outline\" class=\"preset-select\">\n <mat-label>Preset</mat-label>\n <mat-select formControlName=\"type\">\n <mat-option value=\"everyNMinutes\">A cada X min</mat-option>\n <mat-option value=\"dailyAt\">Diariamente \u00E0s</mat-option>\n <mat-option value=\"weekly\">Semanal (dias marcados) \u00E0s</mat-option>\n <mat-option value=\"monthlyDay\">Mensal (dia N) \u00E0s</mat-option>\n <mat-option value=\"monthlyNthWeekday\">\n Mensal (N-\u00E9sima 2\u00AA-feira) \u00E0s\n </mat-option>\n </mat-select>\n </mat-form-field>\n\n @switch (simpleControls.type.value) {\n @case ('everyNMinutes') {\n <div class=\"preset-body\">\n <mat-slider\n formControlName=\"everyN\"\n min=\"1\"\n max=\"60\"\n step=\"1\"\n thumbLabel\n ></mat-slider>\n <div class=\"cron-hint\">\n A cada {{ simpleControls.everyN.value }} minutos\n </div>\n </div>\n }\n\n @case ('dailyAt') {\n <div class=\"preset-body\">\n <mat-form-field appearance=\"outline\">\n <mat-label>Hora</mat-label>\n <input matInput type=\"time\" formControlName=\"dailyTime\" />\n </mat-form-field>\n <div class=\"cron-hint\">\n Diariamente \u00E0s {{ simpleControls.dailyTime.value }}\n </div>\n </div>\n }\n\n @case ('weekly') {\n <div class=\"preset-body\">\n <mat-chip-listbox formControlName=\"weeklyDays\" multiple>\n @for (day of weeklyDayOptions; track day) {\n <mat-chip-option [value]=\"day\">\n {{ weekdayLabels[day] }}\n </mat-chip-option>\n }\n </mat-chip-listbox>\n <mat-form-field appearance=\"outline\">\n <mat-label>Hora</mat-label>\n <input matInput type=\"time\" formControlName=\"weeklyTime\" />\n </mat-form-field>\n <div class=\"cron-hint\">\n Semanalmente \u00E0s {{ simpleControls.weeklyTime.value }}\n </div>\n </div>\n }\n\n @case ('monthlyDay') {\n <div class=\"preset-body\">\n <mat-form-field appearance=\"outline\">\n <mat-label>Dia</mat-label>\n <input\n matInput\n type=\"number\"\n formControlName=\"monthlyDay\"\n min=\"1\"\n max=\"31\"\n />\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>Hora</mat-label>\n <input matInput type=\"time\" formControlName=\"monthlyTime\" />\n </mat-form-field>\n <div class=\"cron-hint\">\n Dia {{ simpleControls.monthlyDay.value }} \u00E0s\n {{ simpleControls.monthlyTime.value }}\n </div>\n </div>\n }\n\n @case ('monthlyNthWeekday') {\n <div class=\"preset-body\">\n <mat-form-field appearance=\"outline\">\n <mat-label>N-\u00E9sima</mat-label>\n <mat-select formControlName=\"nth\">\n @for (nth of nthOrderOptions; track nth) {\n <mat-option [value]=\"nth\">{{ nth }}\u00AA</mat-option>\n }\n </mat-select>\n </mat-form-field>\n <mat-chip-listbox formControlName=\"nthDay\">\n @for (day of nthDayOptions; track day) {\n <mat-chip-option [value]=\"day\">\n {{ weekdayLabels[day] }}\n </mat-chip-option>\n }\n </mat-chip-listbox>\n <mat-form-field appearance=\"outline\">\n <mat-label>Hora</mat-label>\n <input matInput type=\"time\" formControlName=\"nthTime\" />\n </mat-form-field>\n <div class=\"cron-hint\">\n {{ simpleControls.nth.value }}\u00AA\n {{ weekdayLabels[simpleControls.nthDay.value] }}\n \u00E0s\n {{ simpleControls.nthTime.value }}\n </div>\n </div>\n }\n }\n </div>\n }\n\n @case ('advanced') {\n <div [formGroup]=\"form\">\n <div class=\"cron-fields\">\n @if (metadata.fields?.minutes) {\n <mat-form-field>\n <mat-label>Minutes</mat-label>\n <input matInput formControlName=\"minutes\" />\n </mat-form-field>\n }\n @if (metadata.fields?.hours) {\n <mat-form-field>\n <mat-label>Hours</mat-label>\n <input matInput formControlName=\"hours\" />\n </mat-form-field>\n }\n @if (metadata.fields?.dom) {\n <mat-form-field>\n <mat-label>Day of Month</mat-label>\n <input matInput formControlName=\"dayOfMonth\" />\n </mat-form-field>\n }\n @if (metadata.fields?.month) {\n <mat-form-field>\n <mat-label>Month</mat-label>\n <input matInput formControlName=\"month\" />\n </mat-form-field>\n }\n @if (metadata.fields?.dow) {\n <mat-form-field>\n <mat-label>Day of Week</mat-label>\n <input matInput formControlName=\"dayOfWeek\" />\n </mat-form-field>\n }\n @if (metadata.fields?.seconds) {\n <mat-form-field>\n <mat-label>Seconds</mat-label>\n <input matInput formControlName=\"seconds\" />\n </mat-form-field>\n }\n </div>\n </div>\n }\n }\n\n <div class=\"cron-feedback\">\n <mat-form-field appearance=\"outline\" class=\"timezone-field\">\n <mat-label>Timezone</mat-label>\n <mat-select [formControl]=\"timezoneControl\">\n @for (tz of timezoneOptions; track tz) {\n <mat-option [value]=\"tz\">\n {{ tz }}\n </mat-option>\n }\n </mat-select>\n </mat-form-field>\n\n @if (humanized) {\n <div class=\"humanized-description\" aria-live=\"polite\">\n {{ humanized }}\n <button\n mat-icon-button\n class=\"copy-humanized\"\n (click)=\"copyHumanized()\"\n [matTooltip]=\"'Copy description'\"\n aria-label=\"Copy description\"\n >\n <mat-icon [praxisIcon]=\"'content_copy'\"></mat-icon>\n </button>\n </div>\n }\n\n @if (error) {\n <div class=\"cron-error\">{{ error }}</div>\n }\n\n @if (preview.length > 0) {\n <div class=\"preview-section\">\n <h4>Next Occurrences:</h4>\n <mat-list>\n @for (date of preview; track $index) {\n <mat-list-item>\n {{\n date\n | date\n : \"full\"\n : timezoneControl.value\n : metadata.locale || \"pt-BR\"\n }}\n </mat-list-item>\n }\n </mat-list>\n </div>\n }\n </div>\n\n @if (metadata.hint) {\n <div class=\"cron-hint\">{{ metadata.hint }}</div>\n }\n</div>\n", styles: [":host{display:block}.cron-expression-field{width:100%}.cron-expression{margin-bottom:1rem}.simple-mode{display:flex;flex-direction:column;gap:1rem}.preset-body{display:flex;flex-direction:column;gap:.5rem}.cron-feedback{margin-top:1rem;display:flex;flex-direction:column;gap:.5rem}.humanized-description{display:flex;align-items:center;gap:.5rem}.timezone-field{width:250px}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1.NumberValueAccessor, selector: "input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]" }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1.MinValidator, selector: "input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]", inputs: ["min"] }, { kind: "directive", type: i1.MaxValidator, selector: "input[type=number][max][formControlName],input[type=number][max][formControl],input[type=number][max][ngModel]", inputs: ["max"] }, { kind: "directive", type: i1.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }, { kind: "directive", type: i1.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i1.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { kind: "ngmodule", type: MatTabsModule }, { kind: "component", type: i2.MatTab, selector: "mat-tab", inputs: ["disabled", "label", "aria-label", "aria-labelledby", "labelClass", "bodyClass", "id"], exportAs: ["matTab"] }, { kind: "component", type: i2.MatTabGroup, selector: "mat-tab-group", inputs: ["color", "fitInkBarToContent", "mat-stretch-tabs", "mat-align-tabs", "dynamicHeight", "selectedIndex", "headerPosition", "animationDuration", "contentTabIndex", "disablePagination", "disableRipple", "preserveContent", "backgroundColor", "aria-label", "aria-labelledby"], outputs: ["selectedIndexChange", "focusChange", "animationDone", "selectedTabChange"], exportAs: ["matTabGroup"] }, { kind: "ngmodule", type: MatRadioModule }, { kind: "ngmodule", type: MatSelectModule }, { kind: "component", type: i3.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: i3.MatLabel, selector: "mat-label" }, { kind: "directive", type: i3.MatSuffix, selector: "[matSuffix], [matIconSuffix], [matTextSuffix]", inputs: ["matTextSuffix"] }, { kind: "component", type: i3.MatSelect, selector: "mat-select", inputs: ["aria-describedby", "panelClass", "disabled", "disableRipple", "tabIndex", "hideSingleSelectionIndicator", "placeholder", "required", "multiple", "disableOptionCentering", "compareWith", "value", "aria-label", "aria-labelledby", "errorStateMatcher", "typeaheadDebounceInterval", "sortComparator", "id", "panelWidth", "canSelectNullableOptions"], outputs: ["openedChange", "opened", "closed", "selectionChange", "valueChange"], exportAs: ["matSelect"] }, { kind: "component", type: i3.MatOption, selector: "mat-option", inputs: ["value", "id", "disabled"], outputs: ["onSelectionChange"], exportAs: ["matOption"] }, { kind: "ngmodule", type: MatInputModule }, { kind: "directive", type: i4.MatInput, selector: "input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]", inputs: ["disabled", "id", "placeholder", "name", "required", "type", "errorStateMatcher", "aria-describedby", "value", "readonly", "disabledInteractive"], exportAs: ["matInput"] }, { kind: "ngmodule", type: MatFormFieldModule }, { kind: "ngmodule", type: MatListModule }, { kind: "component", type: i5.MatList, selector: "mat-list", exportAs: ["matList"] }, { kind: "component", type: i5.MatListItem, selector: "mat-list-item, a[mat-list-item], button[mat-list-item]", inputs: ["activated"], exportAs: ["matListItem"] }, { kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i6.MatButton, selector: " button[matButton], a[matButton], button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button], a[mat-button], a[mat-raised-button], a[mat-flat-button], a[mat-stroked-button] ", inputs: ["matButton"], exportAs: ["matButton", "matAnchor"] }, { kind: "component", type: i6.MatIconButton, selector: "button[mat-icon-button], a[mat-icon-button], button[matIconButton], a[matIconButton]", exportAs: ["matButton", "matAnchor"] }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i7.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "ngmodule", type: MatTooltipModule }, { kind: "directive", type: i8.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }, { kind: "ngmodule", type: MatSliderModule }, { kind: "component", type: i9.MatSlider, selector: "mat-slider", inputs: ["disabled", "discrete", "showTickMarks", "min", "color", "disableRipple", "max", "step", "displayWith"], exportAs: ["matSlider"] }, { kind: "ngmodule", type: MatChipsModule }, { kind: "component", type: i10.MatChipListbox, selector: "mat-chip-listbox", inputs: ["multiple", "aria-orientation", "selectable", "compareWith", "required", "hideSingleSelectionIndicator", "value"], outputs: ["change"] }, { kind: "component", type: i10.MatChipOption, selector: "mat-basic-chip-option, [mat-basic-chip-option], mat-chip-option, [mat-chip-option]", inputs: ["selectable", "selected"], outputs: ["selectionChange"] }, { kind: "ngmodule", type: MatSnackBarModule }, { kind: "directive", type: PraxisIconDirective, selector: "mat-icon[praxisIcon]", inputs: ["praxisIcon"] }, { kind: "pipe", type: i11.DatePipe, name: "date" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
750
+ ], ngImport: i0, template: "<div class=\"cron-builder-container\" (focusout)=\"onTouched()\">\n @if (metadata.mode === 'both') {\n <mat-tab-group\n [selectedIndex]=\"selectedTabIndex\"\n (selectedTabChange)=\"onTabChange($event)\"\n >\n <mat-tab label=\"Simple\"></mat-tab>\n <mat-tab label=\"Advanced\"></mat-tab>\n </mat-tab-group>\n }\n\n @if (value) {\n <div class=\"cron-expression\">\n <mat-form-field appearance=\"outline\" class=\"cron-expression-field\">\n <mat-label>CRON Expression</mat-label>\n <input matInput [value]=\"value\" readonly />\n <button\n mat-icon-button\n matSuffix\n (click)=\"copyCron()\"\n [matTooltip]=\"'Copy to clipboard'\"\n aria-label=\"Copy CRON expression\"\n >\n <mat-icon [praxisIcon]=\"'content_copy'\"></mat-icon>\n </button>\n </mat-form-field>\n <button mat-button (click)=\"importCron()\">Import CRON</button>\n </div>\n }\n\n @switch (activeTab) {\n @case ('simple') {\n <div [formGroup]=\"simpleForm\" class=\"simple-mode\">\n <mat-form-field appearance=\"outline\" class=\"preset-select\">\n <mat-label>Preset</mat-label>\n <mat-select formControlName=\"type\">\n <mat-option value=\"everyNMinutes\">A cada X min</mat-option>\n <mat-option value=\"dailyAt\">Diariamente \u00E0s</mat-option>\n <mat-option value=\"weekly\">Semanal (dias marcados) \u00E0s</mat-option>\n <mat-option value=\"monthlyDay\">Mensal (dia N) \u00E0s</mat-option>\n <mat-option value=\"monthlyNthWeekday\">\n Mensal (N-\u00E9sima 2\u00AA-feira) \u00E0s\n </mat-option>\n </mat-select>\n </mat-form-field>\n\n @switch (simpleControls.type.value) {\n @case ('everyNMinutes') {\n <div class=\"preset-body\">\n <mat-slider\n min=\"1\"\n max=\"60\"\n step=\"1\"\n thumbLabel\n >\n <input matSliderThumb formControlName=\"everyN\" />\n </mat-slider>\n <div class=\"cron-hint\">\n A cada {{ simpleControls.everyN.value }} minutos\n </div>\n </div>\n }\n\n @case ('dailyAt') {\n <div class=\"preset-body\">\n <mat-form-field appearance=\"outline\">\n <mat-label>Hora</mat-label>\n <input matInput type=\"time\" formControlName=\"dailyTime\" />\n </mat-form-field>\n <div class=\"cron-hint\">\n Diariamente \u00E0s {{ simpleControls.dailyTime.value }}\n </div>\n </div>\n }\n\n @case ('weekly') {\n <div class=\"preset-body\">\n <mat-chip-listbox formControlName=\"weeklyDays\" multiple>\n @for (day of weeklyDayOptions; track day) {\n <mat-chip-option [value]=\"day\">\n {{ weekdayLabels[day] }}\n </mat-chip-option>\n }\n </mat-chip-listbox>\n <mat-form-field appearance=\"outline\">\n <mat-label>Hora</mat-label>\n <input matInput type=\"time\" formControlName=\"weeklyTime\" />\n </mat-form-field>\n <div class=\"cron-hint\">\n Semanalmente \u00E0s {{ simpleControls.weeklyTime.value }}\n </div>\n </div>\n }\n\n @case ('monthlyDay') {\n <div class=\"preset-body\">\n <mat-form-field appearance=\"outline\">\n <mat-label>Dia</mat-label>\n <input\n matInput\n type=\"number\"\n formControlName=\"monthlyDay\"\n min=\"1\"\n max=\"31\"\n />\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>Hora</mat-label>\n <input matInput type=\"time\" formControlName=\"monthlyTime\" />\n </mat-form-field>\n <div class=\"cron-hint\">\n Dia {{ simpleControls.monthlyDay.value }} \u00E0s\n {{ simpleControls.monthlyTime.value }}\n </div>\n </div>\n }\n\n @case ('monthlyNthWeekday') {\n <div class=\"preset-body\">\n <mat-form-field appearance=\"outline\">\n <mat-label>N-\u00E9sima</mat-label>\n <mat-select formControlName=\"nth\">\n @for (nth of nthOrderOptions; track nth) {\n <mat-option [value]=\"nth\">{{ nth }}\u00AA</mat-option>\n }\n </mat-select>\n </mat-form-field>\n <mat-chip-listbox formControlName=\"nthDay\">\n @for (day of nthDayOptions; track day) {\n <mat-chip-option [value]=\"day\">\n {{ weekdayLabels[day] }}\n </mat-chip-option>\n }\n </mat-chip-listbox>\n <mat-form-field appearance=\"outline\">\n <mat-label>Hora</mat-label>\n <input matInput type=\"time\" formControlName=\"nthTime\" />\n </mat-form-field>\n <div class=\"cron-hint\">\n {{ simpleControls.nth.value }}\u00AA\n {{ weekdayLabels[simpleControls.nthDay.value] }}\n \u00E0s\n {{ simpleControls.nthTime.value }}\n </div>\n </div>\n }\n }\n </div>\n }\n\n @case ('advanced') {\n <div [formGroup]=\"form\">\n <div class=\"cron-fields\">\n @if (metadata.fields?.minutes) {\n <mat-form-field>\n <mat-label>Minutes</mat-label>\n <input matInput formControlName=\"minutes\" />\n </mat-form-field>\n }\n @if (metadata.fields?.hours) {\n <mat-form-field>\n <mat-label>Hours</mat-label>\n <input matInput formControlName=\"hours\" />\n </mat-form-field>\n }\n @if (metadata.fields?.dom) {\n <mat-form-field>\n <mat-label>Day of Month</mat-label>\n <input matInput formControlName=\"dayOfMonth\" />\n </mat-form-field>\n }\n @if (metadata.fields?.month) {\n <mat-form-field>\n <mat-label>Month</mat-label>\n <input matInput formControlName=\"month\" />\n </mat-form-field>\n }\n @if (metadata.fields?.dow) {\n <mat-form-field>\n <mat-label>Day of Week</mat-label>\n <input matInput formControlName=\"dayOfWeek\" />\n </mat-form-field>\n }\n @if (metadata.fields?.seconds) {\n <mat-form-field>\n <mat-label>Seconds</mat-label>\n <input matInput formControlName=\"seconds\" />\n </mat-form-field>\n }\n </div>\n </div>\n }\n }\n\n <div class=\"cron-feedback\">\n <mat-form-field appearance=\"outline\" class=\"timezone-field\">\n <mat-label>Timezone</mat-label>\n <mat-select [formControl]=\"timezoneControl\">\n @for (tz of timezoneOptions; track tz) {\n <mat-option [value]=\"tz\">\n {{ tz }}\n </mat-option>\n }\n </mat-select>\n </mat-form-field>\n\n @if (humanized) {\n <div class=\"humanized-description\" aria-live=\"polite\">\n {{ humanized }}\n <button\n mat-icon-button\n class=\"copy-humanized\"\n (click)=\"copyHumanized()\"\n [matTooltip]=\"'Copy description'\"\n aria-label=\"Copy description\"\n >\n <mat-icon [praxisIcon]=\"'content_copy'\"></mat-icon>\n </button>\n </div>\n }\n\n @if (error) {\n <div class=\"cron-error\">{{ error }}</div>\n }\n\n @if (preview.length > 0) {\n <div class=\"preview-section\">\n <h4>Next Occurrences:</h4>\n <mat-list>\n @for (date of preview; track $index) {\n <mat-list-item>\n {{\n date\n | date\n : \"full\"\n : timezoneControl.value\n : metadata.locale || \"pt-BR\"\n }}\n </mat-list-item>\n }\n </mat-list>\n </div>\n }\n </div>\n\n @if (metadata.hint) {\n <div class=\"cron-hint\">{{ metadata.hint }}</div>\n }\n</div>\n", styles: [":host{display:block}.cron-expression-field{width:100%}.cron-expression{margin-bottom:1rem}.simple-mode{display:flex;flex-direction:column;gap:1rem}.preset-body{display:flex;flex-direction:column;gap:.5rem}.cron-feedback{margin-top:1rem;display:flex;flex-direction:column;gap:.5rem}.humanized-description{display:flex;align-items:center;gap:.5rem}.timezone-field{width:250px}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1.NumberValueAccessor, selector: "input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]" }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1.MinValidator, selector: "input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]", inputs: ["min"] }, { kind: "directive", type: i1.MaxValidator, selector: "input[type=number][max][formControlName],input[type=number][max][formControl],input[type=number][max][ngModel]", inputs: ["max"] }, { kind: "directive", type: i1.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }, { kind: "directive", type: i1.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i1.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { kind: "ngmodule", type: MatTabsModule }, { kind: "component", type: i2.MatTab, selector: "mat-tab", inputs: ["disabled", "label", "aria-label", "aria-labelledby", "labelClass", "bodyClass", "id"], exportAs: ["matTab"] }, { kind: "component", type: i2.MatTabGroup, selector: "mat-tab-group", inputs: ["color", "fitInkBarToContent", "mat-stretch-tabs", "mat-align-tabs", "dynamicHeight", "selectedIndex", "headerPosition", "animationDuration", "contentTabIndex", "disablePagination", "disableRipple", "preserveContent", "backgroundColor", "aria-label", "aria-labelledby"], outputs: ["selectedIndexChange", "focusChange", "animationDone", "selectedTabChange"], exportAs: ["matTabGroup"] }, { kind: "ngmodule", type: MatRadioModule }, { kind: "ngmodule", type: MatSelectModule }, { kind: "component", type: i3.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: i3.MatLabel, selector: "mat-label" }, { kind: "directive", type: i3.MatSuffix, selector: "[matSuffix], [matIconSuffix], [matTextSuffix]", inputs: ["matTextSuffix"] }, { kind: "component", type: i3.MatSelect, selector: "mat-select", inputs: ["aria-describedby", "panelClass", "disabled", "disableRipple", "tabIndex", "hideSingleSelectionIndicator", "placeholder", "required", "multiple", "disableOptionCentering", "compareWith", "value", "aria-label", "aria-labelledby", "errorStateMatcher", "typeaheadDebounceInterval", "sortComparator", "id", "panelWidth", "canSelectNullableOptions"], outputs: ["openedChange", "opened", "closed", "selectionChange", "valueChange"], exportAs: ["matSelect"] }, { kind: "component", type: i3.MatOption, selector: "mat-option", inputs: ["value", "id", "disabled"], outputs: ["onSelectionChange"], exportAs: ["matOption"] }, { kind: "ngmodule", type: MatInputModule }, { kind: "directive", type: i4.MatInput, selector: "input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]", inputs: ["disabled", "id", "placeholder", "name", "required", "type", "errorStateMatcher", "aria-describedby", "value", "readonly", "disabledInteractive"], exportAs: ["matInput"] }, { kind: "ngmodule", type: MatFormFieldModule }, { kind: "ngmodule", type: MatListModule }, { kind: "component", type: i5.MatList, selector: "mat-list", exportAs: ["matList"] }, { kind: "component", type: i5.MatListItem, selector: "mat-list-item, a[mat-list-item], button[mat-list-item]", inputs: ["activated"], exportAs: ["matListItem"] }, { kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i6.MatButton, selector: " button[matButton], a[matButton], button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button], a[mat-button], a[mat-raised-button], a[mat-flat-button], a[mat-stroked-button] ", inputs: ["matButton"], exportAs: ["matButton", "matAnchor"] }, { kind: "component", type: i6.MatIconButton, selector: "button[mat-icon-button], a[mat-icon-button], button[matIconButton], a[matIconButton]", exportAs: ["matButton", "matAnchor"] }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i7.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "ngmodule", type: MatTooltipModule }, { kind: "directive", type: i8.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }, { kind: "ngmodule", type: MatSliderModule }, { kind: "component", type: i9.MatSlider, selector: "mat-slider", inputs: ["disabled", "discrete", "showTickMarks", "min", "color", "disableRipple", "max", "step", "displayWith"], exportAs: ["matSlider"] }, { kind: "directive", type: i9.MatSliderThumb, selector: "input[matSliderThumb]", inputs: ["value"], outputs: ["valueChange", "dragStart", "dragEnd"], exportAs: ["matSliderThumb"] }, { kind: "ngmodule", type: MatChipsModule }, { kind: "component", type: i10.MatChipListbox, selector: "mat-chip-listbox", inputs: ["multiple", "aria-orientation", "selectable", "compareWith", "required", "hideSingleSelectionIndicator", "value"], outputs: ["change"] }, { kind: "component", type: i10.MatChipOption, selector: "mat-basic-chip-option, [mat-basic-chip-option], mat-chip-option, [mat-chip-option]", inputs: ["selectable", "selected"], outputs: ["selectionChange"] }, { kind: "ngmodule", type: MatSnackBarModule }, { kind: "directive", type: PraxisIconDirective, selector: "mat-icon[praxisIcon]", inputs: ["praxisIcon"] }, { kind: "pipe", type: i11.DatePipe, name: "date" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
388
751
  }
389
752
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: PdxCronBuilderComponent, decorators: [{
390
753
  type: Component,
@@ -410,7 +773,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
410
773
  useExisting: forwardRef(() => PdxCronBuilderComponent),
411
774
  multi: true,
412
775
  },
413
- ], template: "<div class=\"cron-builder-container\" (focusout)=\"onTouched()\">\n @if (metadata.mode === 'both') {\n <mat-tab-group\n [selectedIndex]=\"selectedTabIndex\"\n (selectedTabChange)=\"onTabChange($event)\"\n >\n <mat-tab label=\"Simple\"></mat-tab>\n <mat-tab label=\"Advanced\"></mat-tab>\n </mat-tab-group>\n }\n\n @if (value) {\n <div class=\"cron-expression\">\n <mat-form-field appearance=\"outline\" class=\"cron-expression-field\">\n <mat-label>CRON Expression</mat-label>\n <input matInput [value]=\"value\" readonly />\n <button\n mat-icon-button\n matSuffix\n (click)=\"copyCron()\"\n [matTooltip]=\"'Copy to clipboard'\"\n aria-label=\"Copy CRON expression\"\n >\n <mat-icon [praxisIcon]=\"'content_copy'\"></mat-icon>\n </button>\n </mat-form-field>\n <button mat-button (click)=\"importCron()\">Import CRON</button>\n </div>\n }\n\n @switch (activeTab) {\n @case ('simple') {\n <div [formGroup]=\"simpleForm\" class=\"simple-mode\">\n <mat-form-field appearance=\"outline\" class=\"preset-select\">\n <mat-label>Preset</mat-label>\n <mat-select formControlName=\"type\">\n <mat-option value=\"everyNMinutes\">A cada X min</mat-option>\n <mat-option value=\"dailyAt\">Diariamente \u00E0s</mat-option>\n <mat-option value=\"weekly\">Semanal (dias marcados) \u00E0s</mat-option>\n <mat-option value=\"monthlyDay\">Mensal (dia N) \u00E0s</mat-option>\n <mat-option value=\"monthlyNthWeekday\">\n Mensal (N-\u00E9sima 2\u00AA-feira) \u00E0s\n </mat-option>\n </mat-select>\n </mat-form-field>\n\n @switch (simpleControls.type.value) {\n @case ('everyNMinutes') {\n <div class=\"preset-body\">\n <mat-slider\n formControlName=\"everyN\"\n min=\"1\"\n max=\"60\"\n step=\"1\"\n thumbLabel\n ></mat-slider>\n <div class=\"cron-hint\">\n A cada {{ simpleControls.everyN.value }} minutos\n </div>\n </div>\n }\n\n @case ('dailyAt') {\n <div class=\"preset-body\">\n <mat-form-field appearance=\"outline\">\n <mat-label>Hora</mat-label>\n <input matInput type=\"time\" formControlName=\"dailyTime\" />\n </mat-form-field>\n <div class=\"cron-hint\">\n Diariamente \u00E0s {{ simpleControls.dailyTime.value }}\n </div>\n </div>\n }\n\n @case ('weekly') {\n <div class=\"preset-body\">\n <mat-chip-listbox formControlName=\"weeklyDays\" multiple>\n @for (day of weeklyDayOptions; track day) {\n <mat-chip-option [value]=\"day\">\n {{ weekdayLabels[day] }}\n </mat-chip-option>\n }\n </mat-chip-listbox>\n <mat-form-field appearance=\"outline\">\n <mat-label>Hora</mat-label>\n <input matInput type=\"time\" formControlName=\"weeklyTime\" />\n </mat-form-field>\n <div class=\"cron-hint\">\n Semanalmente \u00E0s {{ simpleControls.weeklyTime.value }}\n </div>\n </div>\n }\n\n @case ('monthlyDay') {\n <div class=\"preset-body\">\n <mat-form-field appearance=\"outline\">\n <mat-label>Dia</mat-label>\n <input\n matInput\n type=\"number\"\n formControlName=\"monthlyDay\"\n min=\"1\"\n max=\"31\"\n />\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>Hora</mat-label>\n <input matInput type=\"time\" formControlName=\"monthlyTime\" />\n </mat-form-field>\n <div class=\"cron-hint\">\n Dia {{ simpleControls.monthlyDay.value }} \u00E0s\n {{ simpleControls.monthlyTime.value }}\n </div>\n </div>\n }\n\n @case ('monthlyNthWeekday') {\n <div class=\"preset-body\">\n <mat-form-field appearance=\"outline\">\n <mat-label>N-\u00E9sima</mat-label>\n <mat-select formControlName=\"nth\">\n @for (nth of nthOrderOptions; track nth) {\n <mat-option [value]=\"nth\">{{ nth }}\u00AA</mat-option>\n }\n </mat-select>\n </mat-form-field>\n <mat-chip-listbox formControlName=\"nthDay\">\n @for (day of nthDayOptions; track day) {\n <mat-chip-option [value]=\"day\">\n {{ weekdayLabels[day] }}\n </mat-chip-option>\n }\n </mat-chip-listbox>\n <mat-form-field appearance=\"outline\">\n <mat-label>Hora</mat-label>\n <input matInput type=\"time\" formControlName=\"nthTime\" />\n </mat-form-field>\n <div class=\"cron-hint\">\n {{ simpleControls.nth.value }}\u00AA\n {{ weekdayLabels[simpleControls.nthDay.value] }}\n \u00E0s\n {{ simpleControls.nthTime.value }}\n </div>\n </div>\n }\n }\n </div>\n }\n\n @case ('advanced') {\n <div [formGroup]=\"form\">\n <div class=\"cron-fields\">\n @if (metadata.fields?.minutes) {\n <mat-form-field>\n <mat-label>Minutes</mat-label>\n <input matInput formControlName=\"minutes\" />\n </mat-form-field>\n }\n @if (metadata.fields?.hours) {\n <mat-form-field>\n <mat-label>Hours</mat-label>\n <input matInput formControlName=\"hours\" />\n </mat-form-field>\n }\n @if (metadata.fields?.dom) {\n <mat-form-field>\n <mat-label>Day of Month</mat-label>\n <input matInput formControlName=\"dayOfMonth\" />\n </mat-form-field>\n }\n @if (metadata.fields?.month) {\n <mat-form-field>\n <mat-label>Month</mat-label>\n <input matInput formControlName=\"month\" />\n </mat-form-field>\n }\n @if (metadata.fields?.dow) {\n <mat-form-field>\n <mat-label>Day of Week</mat-label>\n <input matInput formControlName=\"dayOfWeek\" />\n </mat-form-field>\n }\n @if (metadata.fields?.seconds) {\n <mat-form-field>\n <mat-label>Seconds</mat-label>\n <input matInput formControlName=\"seconds\" />\n </mat-form-field>\n }\n </div>\n </div>\n }\n }\n\n <div class=\"cron-feedback\">\n <mat-form-field appearance=\"outline\" class=\"timezone-field\">\n <mat-label>Timezone</mat-label>\n <mat-select [formControl]=\"timezoneControl\">\n @for (tz of timezoneOptions; track tz) {\n <mat-option [value]=\"tz\">\n {{ tz }}\n </mat-option>\n }\n </mat-select>\n </mat-form-field>\n\n @if (humanized) {\n <div class=\"humanized-description\" aria-live=\"polite\">\n {{ humanized }}\n <button\n mat-icon-button\n class=\"copy-humanized\"\n (click)=\"copyHumanized()\"\n [matTooltip]=\"'Copy description'\"\n aria-label=\"Copy description\"\n >\n <mat-icon [praxisIcon]=\"'content_copy'\"></mat-icon>\n </button>\n </div>\n }\n\n @if (error) {\n <div class=\"cron-error\">{{ error }}</div>\n }\n\n @if (preview.length > 0) {\n <div class=\"preview-section\">\n <h4>Next Occurrences:</h4>\n <mat-list>\n @for (date of preview; track $index) {\n <mat-list-item>\n {{\n date\n | date\n : \"full\"\n : timezoneControl.value\n : metadata.locale || \"pt-BR\"\n }}\n </mat-list-item>\n }\n </mat-list>\n </div>\n }\n </div>\n\n @if (metadata.hint) {\n <div class=\"cron-hint\">{{ metadata.hint }}</div>\n }\n</div>\n", styles: [":host{display:block}.cron-expression-field{width:100%}.cron-expression{margin-bottom:1rem}.simple-mode{display:flex;flex-direction:column;gap:1rem}.preset-body{display:flex;flex-direction:column;gap:.5rem}.cron-feedback{margin-top:1rem;display:flex;flex-direction:column;gap:.5rem}.humanized-description{display:flex;align-items:center;gap:.5rem}.timezone-field{width:250px}\n"] }]
776
+ ], template: "<div class=\"cron-builder-container\" (focusout)=\"onTouched()\">\n @if (metadata.mode === 'both') {\n <mat-tab-group\n [selectedIndex]=\"selectedTabIndex\"\n (selectedTabChange)=\"onTabChange($event)\"\n >\n <mat-tab label=\"Simple\"></mat-tab>\n <mat-tab label=\"Advanced\"></mat-tab>\n </mat-tab-group>\n }\n\n @if (value) {\n <div class=\"cron-expression\">\n <mat-form-field appearance=\"outline\" class=\"cron-expression-field\">\n <mat-label>CRON Expression</mat-label>\n <input matInput [value]=\"value\" readonly />\n <button\n mat-icon-button\n matSuffix\n (click)=\"copyCron()\"\n [matTooltip]=\"'Copy to clipboard'\"\n aria-label=\"Copy CRON expression\"\n >\n <mat-icon [praxisIcon]=\"'content_copy'\"></mat-icon>\n </button>\n </mat-form-field>\n <button mat-button (click)=\"importCron()\">Import CRON</button>\n </div>\n }\n\n @switch (activeTab) {\n @case ('simple') {\n <div [formGroup]=\"simpleForm\" class=\"simple-mode\">\n <mat-form-field appearance=\"outline\" class=\"preset-select\">\n <mat-label>Preset</mat-label>\n <mat-select formControlName=\"type\">\n <mat-option value=\"everyNMinutes\">A cada X min</mat-option>\n <mat-option value=\"dailyAt\">Diariamente \u00E0s</mat-option>\n <mat-option value=\"weekly\">Semanal (dias marcados) \u00E0s</mat-option>\n <mat-option value=\"monthlyDay\">Mensal (dia N) \u00E0s</mat-option>\n <mat-option value=\"monthlyNthWeekday\">\n Mensal (N-\u00E9sima 2\u00AA-feira) \u00E0s\n </mat-option>\n </mat-select>\n </mat-form-field>\n\n @switch (simpleControls.type.value) {\n @case ('everyNMinutes') {\n <div class=\"preset-body\">\n <mat-slider\n min=\"1\"\n max=\"60\"\n step=\"1\"\n thumbLabel\n >\n <input matSliderThumb formControlName=\"everyN\" />\n </mat-slider>\n <div class=\"cron-hint\">\n A cada {{ simpleControls.everyN.value }} minutos\n </div>\n </div>\n }\n\n @case ('dailyAt') {\n <div class=\"preset-body\">\n <mat-form-field appearance=\"outline\">\n <mat-label>Hora</mat-label>\n <input matInput type=\"time\" formControlName=\"dailyTime\" />\n </mat-form-field>\n <div class=\"cron-hint\">\n Diariamente \u00E0s {{ simpleControls.dailyTime.value }}\n </div>\n </div>\n }\n\n @case ('weekly') {\n <div class=\"preset-body\">\n <mat-chip-listbox formControlName=\"weeklyDays\" multiple>\n @for (day of weeklyDayOptions; track day) {\n <mat-chip-option [value]=\"day\">\n {{ weekdayLabels[day] }}\n </mat-chip-option>\n }\n </mat-chip-listbox>\n <mat-form-field appearance=\"outline\">\n <mat-label>Hora</mat-label>\n <input matInput type=\"time\" formControlName=\"weeklyTime\" />\n </mat-form-field>\n <div class=\"cron-hint\">\n Semanalmente \u00E0s {{ simpleControls.weeklyTime.value }}\n </div>\n </div>\n }\n\n @case ('monthlyDay') {\n <div class=\"preset-body\">\n <mat-form-field appearance=\"outline\">\n <mat-label>Dia</mat-label>\n <input\n matInput\n type=\"number\"\n formControlName=\"monthlyDay\"\n min=\"1\"\n max=\"31\"\n />\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>Hora</mat-label>\n <input matInput type=\"time\" formControlName=\"monthlyTime\" />\n </mat-form-field>\n <div class=\"cron-hint\">\n Dia {{ simpleControls.monthlyDay.value }} \u00E0s\n {{ simpleControls.monthlyTime.value }}\n </div>\n </div>\n }\n\n @case ('monthlyNthWeekday') {\n <div class=\"preset-body\">\n <mat-form-field appearance=\"outline\">\n <mat-label>N-\u00E9sima</mat-label>\n <mat-select formControlName=\"nth\">\n @for (nth of nthOrderOptions; track nth) {\n <mat-option [value]=\"nth\">{{ nth }}\u00AA</mat-option>\n }\n </mat-select>\n </mat-form-field>\n <mat-chip-listbox formControlName=\"nthDay\">\n @for (day of nthDayOptions; track day) {\n <mat-chip-option [value]=\"day\">\n {{ weekdayLabels[day] }}\n </mat-chip-option>\n }\n </mat-chip-listbox>\n <mat-form-field appearance=\"outline\">\n <mat-label>Hora</mat-label>\n <input matInput type=\"time\" formControlName=\"nthTime\" />\n </mat-form-field>\n <div class=\"cron-hint\">\n {{ simpleControls.nth.value }}\u00AA\n {{ weekdayLabels[simpleControls.nthDay.value] }}\n \u00E0s\n {{ simpleControls.nthTime.value }}\n </div>\n </div>\n }\n }\n </div>\n }\n\n @case ('advanced') {\n <div [formGroup]=\"form\">\n <div class=\"cron-fields\">\n @if (metadata.fields?.minutes) {\n <mat-form-field>\n <mat-label>Minutes</mat-label>\n <input matInput formControlName=\"minutes\" />\n </mat-form-field>\n }\n @if (metadata.fields?.hours) {\n <mat-form-field>\n <mat-label>Hours</mat-label>\n <input matInput formControlName=\"hours\" />\n </mat-form-field>\n }\n @if (metadata.fields?.dom) {\n <mat-form-field>\n <mat-label>Day of Month</mat-label>\n <input matInput formControlName=\"dayOfMonth\" />\n </mat-form-field>\n }\n @if (metadata.fields?.month) {\n <mat-form-field>\n <mat-label>Month</mat-label>\n <input matInput formControlName=\"month\" />\n </mat-form-field>\n }\n @if (metadata.fields?.dow) {\n <mat-form-field>\n <mat-label>Day of Week</mat-label>\n <input matInput formControlName=\"dayOfWeek\" />\n </mat-form-field>\n }\n @if (metadata.fields?.seconds) {\n <mat-form-field>\n <mat-label>Seconds</mat-label>\n <input matInput formControlName=\"seconds\" />\n </mat-form-field>\n }\n </div>\n </div>\n }\n }\n\n <div class=\"cron-feedback\">\n <mat-form-field appearance=\"outline\" class=\"timezone-field\">\n <mat-label>Timezone</mat-label>\n <mat-select [formControl]=\"timezoneControl\">\n @for (tz of timezoneOptions; track tz) {\n <mat-option [value]=\"tz\">\n {{ tz }}\n </mat-option>\n }\n </mat-select>\n </mat-form-field>\n\n @if (humanized) {\n <div class=\"humanized-description\" aria-live=\"polite\">\n {{ humanized }}\n <button\n mat-icon-button\n class=\"copy-humanized\"\n (click)=\"copyHumanized()\"\n [matTooltip]=\"'Copy description'\"\n aria-label=\"Copy description\"\n >\n <mat-icon [praxisIcon]=\"'content_copy'\"></mat-icon>\n </button>\n </div>\n }\n\n @if (error) {\n <div class=\"cron-error\">{{ error }}</div>\n }\n\n @if (preview.length > 0) {\n <div class=\"preview-section\">\n <h4>Next Occurrences:</h4>\n <mat-list>\n @for (date of preview; track $index) {\n <mat-list-item>\n {{\n date\n | date\n : \"full\"\n : timezoneControl.value\n : metadata.locale || \"pt-BR\"\n }}\n </mat-list-item>\n }\n </mat-list>\n </div>\n }\n </div>\n\n @if (metadata.hint) {\n <div class=\"cron-hint\">{{ metadata.hint }}</div>\n }\n</div>\n", styles: [":host{display:block}.cron-expression-field{width:100%}.cron-expression{margin-bottom:1rem}.simple-mode{display:flex;flex-direction:column;gap:1rem}.preset-body{display:flex;flex-direction:column;gap:.5rem}.cron-feedback{margin-top:1rem;display:flex;flex-direction:column;gap:.5rem}.humanized-description{display:flex;align-items:center;gap:.5rem}.timezone-field{width:250px}\n"] }]
414
777
  }], ctorParameters: () => [], propDecorators: { metadata: [{
415
778
  type: Input
416
779
  }] } });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@praxisui/cron-builder",
3
- "version": "6.0.0-beta.0",
3
+ "version": "8.0.0-beta.0",
4
4
  "description": "Cron expression builder utilities and components for Praxis UI.",
5
5
  "peerDependencies": {
6
6
  "@angular/common": "^20.1.0",
@@ -8,7 +8,7 @@
8
8
  "@angular/forms": "^20.1.0",
9
9
  "@angular/cdk": "^20.1.0",
10
10
  "@angular/material": "^20.1.0",
11
- "@praxisui/core": "^6.0.0-beta.0"
11
+ "@praxisui/core": "^8.0.0-beta.0"
12
12
  },
13
13
  "dependencies": {
14
14
  "tslib": "^2.3.0",