@praxisui/cron-builder
Version:
Cron expression builder utilities and components for Praxis UI.
452 lines (446 loc) • 41 kB
JavaScript
import * as i0 from '@angular/core';
import { inject, forwardRef, Input, ChangeDetectionStrategy, Component } from '@angular/core';
import * as i1 from '@angular/forms';
import { NonNullableFormBuilder, FormControl, ReactiveFormsModule, NG_VALUE_ACCESSOR } from '@angular/forms';
import * as i11 from '@angular/common';
import { CommonModule } from '@angular/common';
import * as i2 from '@angular/material/tabs';
import { MatTabsModule } from '@angular/material/tabs';
import { MatRadioModule } from '@angular/material/radio';
import * as i3 from '@angular/material/select';
import { MatSelectModule } from '@angular/material/select';
import * as i4 from '@angular/material/input';
import { MatInputModule } from '@angular/material/input';
import { MatFormFieldModule } from '@angular/material/form-field';
import * as i5 from '@angular/material/list';
import { MatListModule } from '@angular/material/list';
import * as i6 from '@angular/material/button';
import { MatButtonModule } from '@angular/material/button';
import * as i7 from '@angular/material/icon';
import { MatIconModule } from '@angular/material/icon';
import * as i8 from '@angular/material/tooltip';
import { MatTooltipModule } from '@angular/material/tooltip';
import * as i9 from '@angular/material/slider';
import { MatSliderModule } from '@angular/material/slider';
import * as i10 from '@angular/material/chips';
import { MatChipsModule } from '@angular/material/chips';
import { MatSnackBar, MatSnackBarModule } from '@angular/material/snack-bar';
import { Clipboard } from '@angular/cdk/clipboard';
import { PraxisIconDirective } from '@praxisui/core';
import { Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators';
// Lazy module singletons
let _cronValidator;
let _cronstrue;
let _cronParser;
class PdxCronBuilderComponent {
// Tipos auxiliares para Typed Forms
fb = inject(NonNullableFormBuilder);
clipboard = inject(Clipboard);
snackBar = inject(MatSnackBar);
metadata = {
mode: 'both',
fields: {
seconds: false,
minutes: true,
hours: true,
dom: true,
month: true,
dow: true,
},
presets: [
{ label: 'Every minute (* * * * *)', cron: '* * * * *' },
{ label: 'Every 5 minutes (*/5 * * * *)', cron: '*/5 * * * *' },
{ label: 'Daily at midnight (0 0 * * *)', cron: '0 0 * * *' },
{ label: 'Weekly on Sunday at midnight (0 0 * * 0)', cron: '0 0 * * 0' },
],
previewOccurrences: 5,
};
value = null;
isDisabled = false;
activeTab = 'simple';
selectedTabIndex = 0;
form;
simpleForm;
timezoneControl = new FormControl('UTC', { nonNullable: true });
destroy$ = new Subject();
humanized = '';
preview = [];
error = null;
// Opções expostas ao template para controle de fluxo moderno
weekdayLabels = ['Dom', 'Seg', 'Ter', 'Qua', 'Qui', 'Sex', 'Sáb'];
weeklyDayOptions = [0, 1, 2, 3, 4, 5, 6];
nthOrderOptions = [1, 2, 3, 4, 5];
nthDayOptions = [1, 2, 3, 4, 5, 6, 0];
timezoneOptions = ['UTC', 'America/Sao_Paulo', 'Europe/London', 'America/New_York'];
constructor() {
this.form = this.fb.group({
seconds: this.fb.control('*'),
minutes: this.fb.control('*'),
hours: this.fb.control('*'),
dayOfMonth: this.fb.control('*'),
month: this.fb.control('*'),
dayOfWeek: this.fb.control('*'),
});
this.simpleForm = this.fb.group({
type: this.fb.control('everyNMinutes'),
everyN: this.fb.control(5),
dailyTime: this.fb.control('00:00'),
weeklyDays: this.fb.control([]),
weeklyTime: this.fb.control('00:00'),
monthlyDay: this.fb.control(1),
monthlyTime: this.fb.control('00:00'),
nth: this.fb.control(1),
nthDay: this.fb.control(1),
nthTime: this.fb.control('00:00'),
});
}
// Acesso facilitado aos controles no template
get simpleControls() {
return this.simpleForm.controls;
}
ngOnInit() {
if (this.metadata.mode === 'simple') {
this.activeTab = 'simple';
this.selectedTabIndex = 0;
}
else if (this.metadata.mode === 'advanced') {
this.activeTab = 'advanced';
this.selectedTabIndex = 1;
}
this.form.valueChanges.pipe(takeUntil(this.destroy$)).subscribe((value) => {
const cron = this.constructCronString(value);
this.updateValue(cron);
});
this.simpleForm.valueChanges
.pipe(takeUntil(this.destroy$))
.subscribe((value) => {
const cron = this.buildCronFromSimple(value);
this.updateValue(cron);
});
this.timezoneControl.setValue(this.metadata.timezone || 'UTC', {
emitEvent: false,
});
this.timezoneControl.valueChanges
.pipe(takeUntil(this.destroy$))
.subscribe((tz) => {
this.metadata.timezone = tz || 'UTC';
if (typeof this.value === 'string') {
this.generatePreview(this.value);
}
});
}
onChange = () => { };
onTouched = () => { };
writeValue(value) {
if (typeof value === 'string') {
this.updateValue(value, false);
this.parseCronString(value);
this.syncSimpleForm(value);
}
}
registerOnChange(fn) {
this.onChange = fn;
}
registerOnTouched(fn) {
this.onTouched = fn;
}
setDisabledState(isDisabled) {
this.isDisabled = isDisabled;
if (isDisabled) {
this.form.disable();
this.simpleForm.disable();
this.timezoneControl.disable();
}
else {
this.form.enable();
this.simpleForm.enable();
this.timezoneControl.enable();
}
}
onTabChange(event) {
this.selectedTabIndex = event.index;
this.activeTab = event.index === 0 ? 'simple' : 'advanced';
this.onTouched();
}
setPreset(cron) {
this.updateValue(cron);
this.parseCronString(cron);
this.syncSimpleForm(cron);
}
updateValue(cron, emitEvent = true) {
this.value = cron;
if (emitEvent) {
this.onChange(cron);
}
this.validate(cron);
this.humanize(cron);
this.generatePreview(cron);
}
copyCron() {
if (typeof this.value === 'string') {
this.clipboard.copy(this.value);
this.snackBar.open('CRON expression copied', undefined, {
duration: 2000,
});
}
}
copyHumanized() {
if (this.humanized) {
this.clipboard.copy(this.humanized);
this.snackBar.open('Description copied', undefined, { duration: 2000 });
}
}
importCron() {
const cron = prompt('Enter CRON expression');
if (cron) {
this.updateValue(cron);
this.parseCronString(cron);
this.syncSimpleForm(cron);
}
}
validate(cron) {
(async () => {
try {
_cronValidator = _cronValidator || (await import('cron-validator'));
const ok = await _cronValidator.isValidCron?.(cron, { alias: true, allowBlankDay: true });
this.error = ok
? null
: this.metadata.validators?.invalidCronMessage || 'Invalid CRON expression';
}
catch {
// On import/validation failure, do a minimal sanity check and do not block UX
this.error = /\S+ \S+ \S+ \S+ \S+/.test(cron) ? null : 'Invalid CRON expression';
}
})();
}
humanize(cron) {
if (this.error) {
this.humanized = '';
return;
}
(async () => {
try {
_cronstrue = _cronstrue || (await import('cronstrue'));
this.humanized = _cronstrue.toString?.(cron, { locale: this.metadata.locale }) || '';
}
catch {
this.humanized = '';
}
})();
}
generatePreview(cron) {
if (this.error || !this.metadata.previewOccurrences) {
this.preview = [];
return;
}
(async () => {
try {
_cronParser = _cronParser || (await import('cron-parser'));
const parse = _cronParser.parse || _cronParser.default?.parse;
const interval = parse?.(cron, {
currentDate: this.metadata.previewFrom ?? new Date(),
tz: this.metadata.timezone,
});
const next = [];
for (let i = 0; i < (this.metadata.previewOccurrences ?? 5); i++) {
next.push(interval.next().toDate());
}
this.preview = next;
}
catch {
this.preview = [];
}
})();
}
parseCronString(cron) {
const parts = cron.trim().split(/\s+/);
if (parts.length < 5)
return;
let seconds = '*';
let minutes;
let hours;
let dayOfMonth;
let month;
let dayOfWeek;
if (parts.length === 6) {
[seconds, minutes, hours, dayOfMonth, month, dayOfWeek] = parts;
}
else {
[minutes, hours, dayOfMonth, month, dayOfWeek] = parts;
}
this.form.patchValue({
seconds: this.metadata.fields?.seconds ? seconds : '*',
minutes: minutes || '*',
hours: hours || '*',
dayOfMonth: dayOfMonth || '*',
month: month || '*',
dayOfWeek: dayOfWeek || '*',
}, { emitEvent: false });
}
syncSimpleForm(cron) {
const parts = cron.trim().split(/\s+/);
if (parts.length < 5)
return;
if (parts.length === 6) {
parts.shift();
}
const [minutes, hours, dom, month, dow] = parts;
if (minutes.startsWith('*/') &&
hours === '*' &&
dom === '*' &&
month === '*' &&
dow === '*') {
this.simpleForm.patchValue({ type: 'everyNMinutes', everyN: parseInt(minutes.slice(2), 10) }, { emitEvent: false });
}
else if (dom === '*' && month === '*' && dow === '*') {
this.simpleForm.patchValue({ type: 'dailyAt', dailyTime: this.toTime(hours, minutes) }, { emitEvent: false });
}
else if (dom === '*' && month === '*') {
this.simpleForm.patchValue({
type: 'weekly',
weeklyTime: this.toTime(hours, minutes),
weeklyDays: dow.split(',').map((d) => +d),
}, { emitEvent: false });
}
else if (month === '*' && dow === '*') {
this.simpleForm.patchValue({
type: 'monthlyDay',
monthlyTime: this.toTime(hours, minutes),
monthlyDay: +dom,
}, { emitEvent: false });
}
else if (dom === '?' && dow.includes('#')) {
const [day, nth] = dow.split('#');
this.simpleForm.patchValue({
type: 'monthlyNthWeekday',
nth: +nth,
nthDay: +day,
nthTime: this.toTime(hours, minutes),
}, { emitEvent: false });
}
}
buildCronFromSimple(v) {
let cron;
switch (v.type) {
case 'everyNMinutes':
cron = `*/${v.everyN || 1} * * * *`;
break;
case 'dailyAt': {
const [h, m] = this.parseTime(v.dailyTime);
cron = `${m} ${h} * * *`;
break;
}
case 'weekly': {
const [h, m] = this.parseTime(v.weeklyTime);
const days = (v.weeklyDays || []).sort().join(',') || '*';
cron = `${m} ${h} * * ${days}`;
break;
}
case 'monthlyDay': {
const [h, m] = this.parseTime(v.monthlyTime);
cron = `${m} ${h} ${v.monthlyDay || 1} * *`;
break;
}
case 'monthlyNthWeekday': {
const [h, m] = this.parseTime(v.nthTime);
cron = `${m} ${h} ? * ${v.nthDay || 1}#${v.nth || 1}`;
break;
}
default:
cron = '* * * * *';
}
return this.metadata.fields?.seconds ? `0 ${cron}` : cron;
}
parseTime(time) {
if (!time) {
return [0, 0];
}
const [h, m] = time.split(':').map((v) => parseInt(v, 10));
return [h || 0, m || 0];
}
toTime(h, m) {
return `${this.pad(parseInt(h, 10))}:${this.pad(parseInt(m, 10))}`;
}
pad(v) {
return v.toString().padStart(2, '0');
}
constructCronString(value) {
const { seconds, minutes, hours, dayOfMonth, month, dayOfWeek } = value;
if (this.metadata.fields?.seconds) {
return `${seconds || '*'} ${minutes || '*'} ${hours || '*'} ${dayOfMonth || '*'} ${month || '*'} ${dayOfWeek || '*'}`;
}
return `${minutes || '*'} ${hours || '*'} ${dayOfMonth || '*'} ${month || '*'} ${dayOfWeek || '*'}`;
}
ngOnDestroy() {
this.destroy$.next();
this.destroy$.complete();
}
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.4", ngImport: i0, type: PdxCronBuilderComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.1.4", type: PdxCronBuilderComponent, isStandalone: true, selector: "pdx-cron-builder", inputs: { metadata: "metadata" }, providers: [
{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => PdxCronBuilderComponent),
multi: true,
},
], 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 });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.4", ngImport: i0, type: PdxCronBuilderComponent, decorators: [{
type: Component,
args: [{ selector: 'pdx-cron-builder', standalone: true, imports: [
CommonModule,
ReactiveFormsModule,
MatTabsModule,
MatRadioModule,
MatSelectModule,
MatInputModule,
MatFormFieldModule,
MatListModule,
MatButtonModule,
MatIconModule,
MatTooltipModule,
MatSliderModule,
MatChipsModule,
MatSnackBarModule,
PraxisIconDirective,
], changeDetection: ChangeDetectionStrategy.OnPush, providers: [
{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => PdxCronBuilderComponent),
multi: true,
},
], 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"] }]
}], ctorParameters: () => [], propDecorators: { metadata: [{
type: Input
}] } });
// Keep this metadata object self-contained to avoid build-time dependency on @praxisui/core types.
const PDX_CRON_BUILDER_COMPONENT_METADATA = {
id: 'pdx-cron-builder',
selector: 'pdx-cron-builder',
component: PdxCronBuilderComponent,
friendlyName: 'Cron (builder)',
description: 'Editor visual para expressões CRON com validação e presets.',
icon: 'schedule',
inputs: [
{ name: 'label', type: 'string', description: 'Rótulo do campo' },
{ name: 'hint', type: 'string', description: 'Texto auxiliar' },
{ name: 'presets', type: 'Array<{ label: string; cron: string }>', description: 'Lista de presets disponíveis' },
{ name: 'enableSeconds', type: 'boolean', description: 'Habilita segundo campo (6 partes)', default: false },
{ name: 'metadata', type: 'any', description: 'Metadados compatíveis com formulários Praxis' },
],
outputs: [
{ name: 'valueChange', type: 'string', description: 'Emite quando a expressão cron muda' },
],
tags: ['widget', 'field', 'cron', 'schedule'],
lib: '/cron-builder',
};
/*
* Public API Surface of praxis-cron-builder
*/
// Note: runtime cron libraries are loaded dynamically inside the component to
// avoid build-time type resolution issues with ng-packagr.
/**
* Generated bundle index. Do not edit.
*/
export { PDX_CRON_BUILDER_COMPONENT_METADATA, PdxCronBuilderComponent };
//# sourceMappingURL=praxisui-cron-builder.mjs.map