UNPKG

@praxisui/cron-builder

Version:

Cron expression builder utilities and components for Praxis UI.

452 lines (446 loc) 41 kB
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: '@praxisui/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