@devjuliovilla/jv-ui 1.8.0 → 1.10.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.
@@ -18717,9 +18717,135 @@ const JV_UI_DEFAULT_CONFIG = {
18717
18717
  density: 'normal',
18718
18718
  language: 'es-MX',
18719
18719
  locale: 'es',
18720
+ messagesLocale: 'es',
18721
+ formatLocale: 'es-MX',
18722
+ formats: {
18723
+ date: { day: '2-digit', month: '2-digit', year: 'numeric' },
18724
+ time: { hour: '2-digit', minute: '2-digit' },
18725
+ datetime: { day: '2-digit', month: '2-digit', year: 'numeric', hour: '2-digit', minute: '2-digit' },
18726
+ integer: { maximumFractionDigits: 0 },
18727
+ number: { minimumFractionDigits: 0, maximumFractionDigits: 2 },
18728
+ currency: { style: 'currency', currency: 'MXN', minimumFractionDigits: 2, maximumFractionDigits: 2 },
18729
+ percent: { style: 'percent', minimumFractionDigits: 0, maximumFractionDigits: 2 },
18730
+ },
18720
18731
  toastPosition: 'bottom-right',
18721
18732
  };
18722
18733
 
18734
+ class JvFormatService {
18735
+ config = inject(JvUiConfigService);
18736
+ formatLocale = signal(this.config.snapshot.formatLocale || this.config.snapshot.language || 'es-MX', ...(ngDevMode ? [{ debugName: "formatLocale" }] : /* istanbul ignore next */ []));
18737
+ formats = signal(this.resolveFormats(this.config.snapshot.formats), ...(ngDevMode ? [{ debugName: "formats" }] : /* istanbul ignore next */ []));
18738
+ firstDayOfWeek = computed(() => this.resolveFirstDayOfWeek(this.formatLocale()), ...(ngDevMode ? [{ debugName: "firstDayOfWeek" }] : /* istanbul ignore next */ []));
18739
+ initialize() {
18740
+ this.formatLocale.set(this.config.snapshot.formatLocale || this.config.snapshot.language || 'es-MX');
18741
+ this.formats.set(this.resolveFormats(this.config.snapshot.formats));
18742
+ }
18743
+ setFormatLocale(locale) {
18744
+ this.formatLocale.set(locale);
18745
+ }
18746
+ setFormats(formats) {
18747
+ this.formats.update((current) => this.resolveFormats({ ...current, ...formats }));
18748
+ }
18749
+ format(kind, value, overrides) {
18750
+ if (value == null || value === '')
18751
+ return '';
18752
+ if (kind === 'date' || kind === 'time' || kind === 'datetime') {
18753
+ const date = this.toDate(value);
18754
+ if (!date)
18755
+ return String(value);
18756
+ return new Intl.DateTimeFormat(this.formatLocale(), this.resolveDateOptions(kind, overrides)).format(date);
18757
+ }
18758
+ const number = this.toNumber(value);
18759
+ if (number === null)
18760
+ return String(value);
18761
+ return new Intl.NumberFormat(this.formatLocale(), this.resolveNumberOptions(kind, overrides)).format(number);
18762
+ }
18763
+ toComparable(kind, value) {
18764
+ if (value == null || value === '')
18765
+ return null;
18766
+ if (kind === 'date' || kind === 'time' || kind === 'datetime') {
18767
+ const date = this.toDate(value);
18768
+ return date ? date.getTime() : String(value);
18769
+ }
18770
+ if (kind === 'integer' || kind === 'number' || kind === 'currency' || kind === 'percent') {
18771
+ const number = this.toNumber(value);
18772
+ return number ?? String(value);
18773
+ }
18774
+ return String(value);
18775
+ }
18776
+ toDate(value) {
18777
+ if (value instanceof Date) {
18778
+ return Number.isNaN(value.getTime()) ? null : value;
18779
+ }
18780
+ if (typeof value === 'number' || typeof value === 'string') {
18781
+ const date = new Date(value);
18782
+ return Number.isNaN(date.getTime()) ? null : date;
18783
+ }
18784
+ return null;
18785
+ }
18786
+ toNumber(value) {
18787
+ if (typeof value === 'number') {
18788
+ return Number.isFinite(value) ? value : null;
18789
+ }
18790
+ if (typeof value === 'string') {
18791
+ const normalized = value.trim().replace(/[%,$\s]/g, '').replace(/,/g, '');
18792
+ if (!normalized)
18793
+ return null;
18794
+ const parsed = Number(normalized);
18795
+ return Number.isFinite(parsed) ? parsed : null;
18796
+ }
18797
+ return null;
18798
+ }
18799
+ toSearchValue(kind, value) {
18800
+ if (value == null)
18801
+ return '';
18802
+ if (kind === 'date' || kind === 'time' || kind === 'datetime') {
18803
+ const date = this.toDate(value);
18804
+ if (!date)
18805
+ return String(value).toLowerCase();
18806
+ return date.toISOString().toLowerCase();
18807
+ }
18808
+ return String(value).toLowerCase();
18809
+ }
18810
+ resolveFormats(formats) {
18811
+ const defaults = this.config.snapshot.formats;
18812
+ return {
18813
+ date: { ...defaults.date, ...formats?.date },
18814
+ time: { ...defaults.time, ...formats?.time },
18815
+ datetime: { ...defaults.datetime, ...formats?.datetime },
18816
+ integer: { ...defaults.integer, ...formats?.integer },
18817
+ number: { ...defaults.number, ...formats?.number },
18818
+ currency: { ...defaults.currency, ...formats?.currency },
18819
+ percent: { ...defaults.percent, ...formats?.percent },
18820
+ };
18821
+ }
18822
+ resolveDateOptions(kind, overrides) {
18823
+ return { ...this.formats()[kind], ...overrides };
18824
+ }
18825
+ resolveNumberOptions(kind, overrides) {
18826
+ return { ...this.formats()[kind], ...overrides };
18827
+ }
18828
+ resolveFirstDayOfWeek(locale) {
18829
+ try {
18830
+ const intlLocale = new Intl.Locale(locale);
18831
+ const firstDay = intlLocale.weekInfo?.firstDay;
18832
+ if (typeof firstDay === 'number') {
18833
+ return firstDay % 7;
18834
+ }
18835
+ }
18836
+ catch {
18837
+ // Ignore unsupported Intl.Locale implementations.
18838
+ }
18839
+ return 0;
18840
+ }
18841
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: JvFormatService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
18842
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: JvFormatService, providedIn: 'root' });
18843
+ }
18844
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: JvFormatService, decorators: [{
18845
+ type: Injectable,
18846
+ args: [{ providedIn: 'root' }]
18847
+ }] });
18848
+
18723
18849
  class JvThemeService {
18724
18850
  document = inject(DOCUMENT);
18725
18851
  config = inject(JvUiConfigService);
@@ -18810,6 +18936,7 @@ function provideJvUi(config = {}) {
18810
18936
  useValue: () => {
18811
18937
  inject(JvThemeService).initialize();
18812
18938
  inject(JvTranslationService).initialize();
18939
+ inject(JvFormatService).initialize();
18813
18940
  },
18814
18941
  },
18815
18942
  ]);
@@ -18817,9 +18944,19 @@ function provideJvUi(config = {}) {
18817
18944
 
18818
18945
  class JvUiConfigService {
18819
18946
  defaults = inject(JV_UI_CONFIG_DEFAULTS);
18820
- configLayers = inject(JV_UI_CONFIG, { optional: true }) ?? [];
18947
+ configLayers = (inject(JV_UI_CONFIG, { optional: true }) ?? []);
18821
18948
  get snapshot() {
18822
- return this.configLayers.reduce((merged, layer) => ({ ...merged, ...layer }), { ...this.defaults });
18949
+ return this.configLayers.reduce((merged, layer) => ({
18950
+ ...merged,
18951
+ ...layer,
18952
+ formats: {
18953
+ ...merged.formats,
18954
+ ...(layer.formats ?? {}),
18955
+ },
18956
+ }), {
18957
+ ...this.defaults,
18958
+ formats: { ...this.defaults.formats },
18959
+ });
18823
18960
  }
18824
18961
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: JvUiConfigService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
18825
18962
  static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: JvUiConfigService, providedIn: 'root' });
@@ -18878,6 +19015,15 @@ const ES = {
18878
19015
  'modalSelector.results': '{count} resultados',
18879
19016
  'modalSelector.selected': '{label} seleccionado',
18880
19017
  'modalSelector.selectionCleared': 'Seleccion limpiada',
19018
+ 'fileInput.dropLabel': 'Arrastre archivos aqui o haga clic para seleccionarlos',
19019
+ 'fileInput.helperText': 'Compatible con seleccion multiple y arrastrar y soltar',
19020
+ 'fileInput.selectionSummary': '{count} de {max} archivos seleccionados',
19021
+ 'fileInput.invalidFiles': '{count} archivos no coinciden con el tipo permitido',
19022
+ 'fileInput.tooManyFiles': 'Solo se permiten hasta {max} archivos',
19023
+ 'fileInput.removeFile': 'Quitar archivo {name}',
19024
+ 'dialog.confirmTitle': 'Confirmar',
19025
+ 'dialog.confirm': 'Aceptar',
19026
+ 'dialog.cancel': 'Cancelar',
18881
19027
  'common.expanded': 'Expandido',
18882
19028
  'common.collapsed': 'Colapsado',
18883
19029
  };
@@ -18931,6 +19077,15 @@ const EN = {
18931
19077
  'modalSelector.results': '{count} results',
18932
19078
  'modalSelector.selected': '{label} selected',
18933
19079
  'modalSelector.selectionCleared': 'Selection cleared',
19080
+ 'fileInput.dropLabel': 'Drag files here or click to choose them',
19081
+ 'fileInput.helperText': 'Supports multiple selection and drag and drop',
19082
+ 'fileInput.selectionSummary': '{count} of {max} files selected',
19083
+ 'fileInput.invalidFiles': '{count} files do not match the allowed type',
19084
+ 'fileInput.tooManyFiles': 'Only up to {max} files are allowed',
19085
+ 'fileInput.removeFile': 'Remove file {name}',
19086
+ 'dialog.confirmTitle': 'Confirm',
19087
+ 'dialog.confirm': 'Confirm',
19088
+ 'dialog.cancel': 'Cancel',
18934
19089
  'common.expanded': 'Expanded',
18935
19090
  'common.collapsed': 'Collapsed',
18936
19091
  };
@@ -18943,14 +19098,17 @@ const JV_DEFAULT_LOCALE = 'es';
18943
19098
 
18944
19099
  class JvTranslationService {
18945
19100
  config = inject(JvUiConfigService);
18946
- locale = signal(this.config.snapshot.locale, ...(ngDevMode ? [{ debugName: "locale" }] : /* istanbul ignore next */ []));
19101
+ locale = signal(this.resolveLocale(this.config.snapshot.messagesLocale ?? this.config.snapshot.locale), ...(ngDevMode ? [{ debugName: "locale" }] : /* istanbul ignore next */ []));
18947
19102
  initialize() {
18948
- const locale = this.resolveLocale(this.config.snapshot.locale);
19103
+ const locale = this.resolveLocale(this.config.snapshot.messagesLocale ?? this.config.snapshot.locale);
18949
19104
  this.locale.set(locale);
18950
19105
  }
18951
19106
  setLocale(locale) {
18952
19107
  this.locale.set(locale);
18953
19108
  }
19109
+ setMessagesLocale(locale) {
19110
+ this.setLocale(locale);
19111
+ }
18954
19112
  translate(key, params) {
18955
19113
  const dict = JV_LOCALE_DICTIONARIES[this.locale()] ?? JV_LOCALE_DICTIONARIES[JV_DEFAULT_LOCALE];
18956
19114
  let value = dict[key];
@@ -18965,6 +19123,8 @@ class JvTranslationService {
18965
19123
  return value;
18966
19124
  }
18967
19125
  resolveLocale(locale) {
19126
+ if (!locale)
19127
+ return JV_DEFAULT_LOCALE;
18968
19128
  if (locale === 'es' || locale === 'en')
18969
19129
  return locale;
18970
19130
  return locale.startsWith('es') ? 'es' : 'en';
@@ -19312,7 +19472,7 @@ class JvLoaderComponent {
19312
19472
  </div>
19313
19473
  </div>
19314
19474
  }
19315
- `, isInline: true, styles: [".jv-loader-backdrop{position:fixed;inset:0;z-index:1050;display:grid;place-items:center;background:#0f172a2e;-webkit-backdrop-filter:blur(2px);backdrop-filter:blur(2px)}.jv-loader-panel{display:inline-flex;align-items:center;gap:var(--jv-spacing-sm);padding:var(--jv-spacing-md) var(--jv-spacing-lg);border:1px solid var(--jv-color-border);border-radius:var(--jv-radius-lg);background:var(--jv-color-surface);box-shadow:var(--jv-shadow-lg);color:var(--jv-color-foreground)}.spinner{width:1rem;height:1rem;border:2px solid currentColor;border-right-color:transparent;border-radius:999px;animation:jv-loader-spin .8s linear infinite}@keyframes jv-loader-spin{to{transform:rotate(360deg)}}\n"] });
19475
+ `, isInline: true, styles: [".jv-loader-backdrop{position:fixed;inset:0;z-index:1050;display:grid;place-items:center;background:#0f172a2e;-webkit-backdrop-filter:blur(2px);backdrop-filter:blur(2px)}.jv-loader-panel{display:inline-flex;align-items:center;gap:var(--jv-spacing-sm);padding:var(--jv-spacing-md) var(--jv-spacing-lg);width:min(35vw,calc(100vw - 2rem));height:min(35vh,calc(100vh - 2rem));max-width:calc(100vw - 2rem);max-height:calc(100vh - 2rem);justify-content:center;border:1px solid #d6dce5;border-radius:var(--jv-radius-lg);background:#f8fafc;box-shadow:var(--jv-shadow-lg);color:#111827;text-align:center;forced-color-adjust:none}.spinner{width:1.25rem;height:1.25rem;border:2px solid currentColor;border-right-color:transparent;border-radius:999px;animation:jv-loader-spin .8s linear infinite}@media(forced-colors:active){.jv-loader-panel{border-color:#d6dce5;background:#f8fafc;color:#111827}}@keyframes jv-loader-spin{to{transform:rotate(360deg)}}\n"] });
19316
19476
  }
19317
19477
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: JvLoaderComponent, decorators: [{
19318
19478
  type: Component,
@@ -19325,7 +19485,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImpo
19325
19485
  </div>
19326
19486
  </div>
19327
19487
  }
19328
- `, styles: [".jv-loader-backdrop{position:fixed;inset:0;z-index:1050;display:grid;place-items:center;background:#0f172a2e;-webkit-backdrop-filter:blur(2px);backdrop-filter:blur(2px)}.jv-loader-panel{display:inline-flex;align-items:center;gap:var(--jv-spacing-sm);padding:var(--jv-spacing-md) var(--jv-spacing-lg);border:1px solid var(--jv-color-border);border-radius:var(--jv-radius-lg);background:var(--jv-color-surface);box-shadow:var(--jv-shadow-lg);color:var(--jv-color-foreground)}.spinner{width:1rem;height:1rem;border:2px solid currentColor;border-right-color:transparent;border-radius:999px;animation:jv-loader-spin .8s linear infinite}@keyframes jv-loader-spin{to{transform:rotate(360deg)}}\n"] }]
19488
+ `, styles: [".jv-loader-backdrop{position:fixed;inset:0;z-index:1050;display:grid;place-items:center;background:#0f172a2e;-webkit-backdrop-filter:blur(2px);backdrop-filter:blur(2px)}.jv-loader-panel{display:inline-flex;align-items:center;gap:var(--jv-spacing-sm);padding:var(--jv-spacing-md) var(--jv-spacing-lg);width:min(35vw,calc(100vw - 2rem));height:min(35vh,calc(100vh - 2rem));max-width:calc(100vw - 2rem);max-height:calc(100vh - 2rem);justify-content:center;border:1px solid #d6dce5;border-radius:var(--jv-radius-lg);background:#f8fafc;box-shadow:var(--jv-shadow-lg);color:#111827;text-align:center;forced-color-adjust:none}.spinner{width:1.25rem;height:1.25rem;border:2px solid currentColor;border-right-color:transparent;border-radius:999px;animation:jv-loader-spin .8s linear infinite}@media(forced-colors:active){.jv-loader-panel{border-color:#d6dce5;background:#f8fafc;color:#111827}}@keyframes jv-loader-spin{to{transform:rotate(360deg)}}\n"] }]
19329
19489
  }], ctorParameters: () => [], propDecorators: { handleWheel: [{
19330
19490
  type: HostListener,
19331
19491
  args: ['document:wheel', ['$event']]
@@ -19420,6 +19580,115 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImpo
19420
19580
  `, styles: [".jv-toast-viewport{position:fixed;z-index:1100;display:grid;gap:var(--jv-spacing-sm);width:min(360px,calc(100vw - 2rem))}.is-top-left{top:1rem;left:1rem}.is-top-center{top:1rem;left:50%;transform:translate(-50%)}.is-top-right{top:1rem;right:1rem}.is-bottom-left{bottom:1rem;left:1rem}.is-bottom-center{bottom:1rem;left:50%;transform:translate(-50%)}.is-bottom-right{bottom:1rem;right:1rem}.jv-toast-item{display:grid;grid-template-columns:1fr auto;gap:var(--jv-spacing-sm);padding:var(--jv-spacing-md);border:1px solid var(--jv-color-border);border-radius:var(--jv-radius-md);background:var(--jv-color-surface);box-shadow:var(--jv-shadow-lg)}.jv-toast-copy strong,.jv-toast-copy p{margin:0}.jv-toast-copy{display:grid;gap:.25rem}.jv-toast-copy p{color:var(--jv-color-foreground-muted)}.jv-toast-close{display:inline-grid;place-items:center;width:2rem;height:2rem;border:0;border-radius:999px;background:transparent;color:inherit}.jv-toast--success{border-color:color-mix(in srgb,var(--jv-color-success) 35%,var(--jv-color-border))}.jv-toast--warning{border-color:color-mix(in srgb,var(--jv-color-warning) 35%,var(--jv-color-border))}.jv-toast--danger{border-color:color-mix(in srgb,var(--jv-color-danger) 35%,var(--jv-color-border))}.jv-toast--info,.jv-toast--primary{border-color:color-mix(in srgb,var(--jv-color-primary) 35%,var(--jv-color-border))}\n"] }]
19421
19581
  }] });
19422
19582
 
19583
+ class JvCodeComponent {
19584
+ code = input.required(...(ngDevMode ? [{ debugName: "code" }] : /* istanbul ignore next */ []));
19585
+ language = input('', ...(ngDevMode ? [{ debugName: "language" }] : /* istanbul ignore next */ []));
19586
+ title = input('', ...(ngDevMode ? [{ debugName: "title" }] : /* istanbul ignore next */ []));
19587
+ font = input('jetbrains-mono', ...(ngDevMode ? [{ debugName: "font" }] : /* istanbul ignore next */ []));
19588
+ editable = input(false, ...(ngDevMode ? [{ debugName: "editable" }] : /* istanbul ignore next */ []));
19589
+ width = input('100%', ...(ngDevMode ? [{ debugName: "width" }] : /* istanbul ignore next */ []));
19590
+ height = input('auto', ...(ngDevMode ? [{ debugName: "height" }] : /* istanbul ignore next */ []));
19591
+ codeChange = output();
19592
+ fontFamily = computed(() => {
19593
+ switch (this.font()) {
19594
+ case 'fira-code':
19595
+ return `'Fira Code', 'JetBrains Mono', 'Cascadia Code', 'Consolas', monospace`;
19596
+ case 'cascadia':
19597
+ return `'Cascadia Code', 'JetBrains Mono', 'Fira Code', 'Consolas', monospace`;
19598
+ case 'consolas':
19599
+ return `'Consolas', 'Cascadia Code', 'JetBrains Mono', 'Fira Code', monospace`;
19600
+ case 'system':
19601
+ return `ui-monospace, 'SFMono-Regular', 'Cascadia Code', 'JetBrains Mono', 'Fira Code', 'Consolas', monospace`;
19602
+ default:
19603
+ return `'JetBrains Mono', 'Fira Code', 'Cascadia Code', 'Consolas', monospace`;
19604
+ }
19605
+ }, ...(ngDevMode ? [{ debugName: "fontFamily" }] : /* istanbul ignore next */ []));
19606
+ languageLabel() {
19607
+ return this.language().trim();
19608
+ }
19609
+ handleInput(event) {
19610
+ this.codeChange.emit(event.target.value);
19611
+ }
19612
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: JvCodeComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
19613
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.17", type: JvCodeComponent, isStandalone: true, selector: "jv-code", inputs: { code: { classPropertyName: "code", publicName: "code", isSignal: true, isRequired: true, transformFunction: null }, language: { classPropertyName: "language", publicName: "language", isSignal: true, isRequired: false, transformFunction: null }, title: { classPropertyName: "title", publicName: "title", isSignal: true, isRequired: false, transformFunction: null }, font: { classPropertyName: "font", publicName: "font", isSignal: true, isRequired: false, transformFunction: null }, editable: { classPropertyName: "editable", publicName: "editable", isSignal: true, isRequired: false, transformFunction: null }, width: { classPropertyName: "width", publicName: "width", isSignal: true, isRequired: false, transformFunction: null }, height: { classPropertyName: "height", publicName: "height", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { codeChange: "codeChange" }, ngImport: i0, template: `
19614
+ <section
19615
+ class="jv-code"
19616
+ [style.--jv-code-font-family]="fontFamily()"
19617
+ [style.width]="width()"
19618
+ role="region"
19619
+ [attr.aria-label]="title() || languageLabel() || 'Code block'"
19620
+ >
19621
+ @if (title() || languageLabel()) {
19622
+ <header class="jv-code-header">
19623
+ @if (title()) {
19624
+ <span class="jv-code-title">{{ title() }}</span>
19625
+ }
19626
+
19627
+ @if (languageLabel()) {
19628
+ <span class="jv-code-language">{{ languageLabel() }}</span>
19629
+ }
19630
+ </header>
19631
+ }
19632
+
19633
+ @if (editable()) {
19634
+ <div class="jv-code-body">
19635
+ <textarea
19636
+ class="jv-code-editor"
19637
+ [value]="code()"
19638
+ [style.height]="height()"
19639
+ spellcheck="false"
19640
+ (input)="handleInput($event)"
19641
+ ></textarea>
19642
+ </div>
19643
+ } @else {
19644
+ <div class="jv-code-scroll">
19645
+ <pre class="jv-code-pre" [style.min-height]="height() === 'auto' ? null : height()"><code>{{ code() }}</code></pre>
19646
+ </div>
19647
+ }
19648
+ </section>
19649
+ `, isInline: true, styles: [".jv-code{display:grid;gap:0;border:1px solid var(--jv-color-border);border-radius:var(--jv-radius-lg);background:color-mix(in srgb,var(--jv-color-surface-muted) 60%,#0f172a 40%);color:#e5eefb;overflow:hidden}.jv-code-body{min-width:0}.jv-code-header{display:flex;align-items:center;justify-content:space-between;gap:var(--jv-spacing-sm);padding:var(--jv-spacing-sm) var(--jv-spacing-md);border-bottom:1px solid rgba(148,163,184,.22);background:#0f172ae0}.jv-code-title{font-size:.875rem;font-weight:600;color:#f8fafc}.jv-code-language{display:inline-flex;align-items:center;min-height:1.75rem;padding:0 .65rem;border:1px solid rgba(125,211,252,.28);border-radius:999px;background:#0ea5e924;color:#c4f1ff;font-size:.75rem;text-transform:uppercase;letter-spacing:.06em}.jv-code-scroll{overflow-x:auto;overflow-y:hidden;scrollbar-width:thin;scrollbar-color:transparent transparent}.jv-code-scroll:hover,.jv-code-scroll:focus-within{scrollbar-color:rgba(148,163,184,.7) transparent}.jv-code-scroll::-webkit-scrollbar{height:.45rem}.jv-code-scroll::-webkit-scrollbar-track{background:transparent}.jv-code-scroll::-webkit-scrollbar-thumb{border-radius:999px;background:transparent}.jv-code-scroll:hover::-webkit-scrollbar-thumb,.jv-code-scroll:focus-within::-webkit-scrollbar-thumb{background:#94a3b8b8}.jv-code-pre{margin:0;min-width:max-content;padding:var(--jv-spacing-md);font-family:var(--jv-code-font-family, \"JetBrains Mono\", \"Fira Code\", \"Cascadia Code\", \"Consolas\", monospace);font-size:.875rem;line-height:1.6;white-space:pre}.jv-code-editor{display:block;width:100%;min-width:100%;min-height:12rem;padding:var(--jv-spacing-md);border:0;background:transparent;color:inherit;font-family:var(--jv-code-font-family, \"JetBrains Mono\", \"Fira Code\", \"Cascadia Code\", \"Consolas\", monospace);font-size:.875rem;line-height:1.6;white-space:pre;overflow:auto;resize:both;box-sizing:border-box}.jv-code-editor:focus{outline:2px solid color-mix(in srgb,#7dd3fc 70%,transparent);outline-offset:-2px}code{font:inherit}\n"] });
19650
+ }
19651
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: JvCodeComponent, decorators: [{
19652
+ type: Component,
19653
+ args: [{ selector: 'jv-code', standalone: true, template: `
19654
+ <section
19655
+ class="jv-code"
19656
+ [style.--jv-code-font-family]="fontFamily()"
19657
+ [style.width]="width()"
19658
+ role="region"
19659
+ [attr.aria-label]="title() || languageLabel() || 'Code block'"
19660
+ >
19661
+ @if (title() || languageLabel()) {
19662
+ <header class="jv-code-header">
19663
+ @if (title()) {
19664
+ <span class="jv-code-title">{{ title() }}</span>
19665
+ }
19666
+
19667
+ @if (languageLabel()) {
19668
+ <span class="jv-code-language">{{ languageLabel() }}</span>
19669
+ }
19670
+ </header>
19671
+ }
19672
+
19673
+ @if (editable()) {
19674
+ <div class="jv-code-body">
19675
+ <textarea
19676
+ class="jv-code-editor"
19677
+ [value]="code()"
19678
+ [style.height]="height()"
19679
+ spellcheck="false"
19680
+ (input)="handleInput($event)"
19681
+ ></textarea>
19682
+ </div>
19683
+ } @else {
19684
+ <div class="jv-code-scroll">
19685
+ <pre class="jv-code-pre" [style.min-height]="height() === 'auto' ? null : height()"><code>{{ code() }}</code></pre>
19686
+ </div>
19687
+ }
19688
+ </section>
19689
+ `, styles: [".jv-code{display:grid;gap:0;border:1px solid var(--jv-color-border);border-radius:var(--jv-radius-lg);background:color-mix(in srgb,var(--jv-color-surface-muted) 60%,#0f172a 40%);color:#e5eefb;overflow:hidden}.jv-code-body{min-width:0}.jv-code-header{display:flex;align-items:center;justify-content:space-between;gap:var(--jv-spacing-sm);padding:var(--jv-spacing-sm) var(--jv-spacing-md);border-bottom:1px solid rgba(148,163,184,.22);background:#0f172ae0}.jv-code-title{font-size:.875rem;font-weight:600;color:#f8fafc}.jv-code-language{display:inline-flex;align-items:center;min-height:1.75rem;padding:0 .65rem;border:1px solid rgba(125,211,252,.28);border-radius:999px;background:#0ea5e924;color:#c4f1ff;font-size:.75rem;text-transform:uppercase;letter-spacing:.06em}.jv-code-scroll{overflow-x:auto;overflow-y:hidden;scrollbar-width:thin;scrollbar-color:transparent transparent}.jv-code-scroll:hover,.jv-code-scroll:focus-within{scrollbar-color:rgba(148,163,184,.7) transparent}.jv-code-scroll::-webkit-scrollbar{height:.45rem}.jv-code-scroll::-webkit-scrollbar-track{background:transparent}.jv-code-scroll::-webkit-scrollbar-thumb{border-radius:999px;background:transparent}.jv-code-scroll:hover::-webkit-scrollbar-thumb,.jv-code-scroll:focus-within::-webkit-scrollbar-thumb{background:#94a3b8b8}.jv-code-pre{margin:0;min-width:max-content;padding:var(--jv-spacing-md);font-family:var(--jv-code-font-family, \"JetBrains Mono\", \"Fira Code\", \"Cascadia Code\", \"Consolas\", monospace);font-size:.875rem;line-height:1.6;white-space:pre}.jv-code-editor{display:block;width:100%;min-width:100%;min-height:12rem;padding:var(--jv-spacing-md);border:0;background:transparent;color:inherit;font-family:var(--jv-code-font-family, \"JetBrains Mono\", \"Fira Code\", \"Cascadia Code\", \"Consolas\", monospace);font-size:.875rem;line-height:1.6;white-space:pre;overflow:auto;resize:both;box-sizing:border-box}.jv-code-editor:focus{outline:2px solid color-mix(in srgb,#7dd3fc 70%,transparent);outline-offset:-2px}code{font:inherit}\n"] }]
19690
+ }], propDecorators: { code: [{ type: i0.Input, args: [{ isSignal: true, alias: "code", required: true }] }], language: [{ type: i0.Input, args: [{ isSignal: true, alias: "language", required: false }] }], title: [{ type: i0.Input, args: [{ isSignal: true, alias: "title", required: false }] }], font: [{ type: i0.Input, args: [{ isSignal: true, alias: "font", required: false }] }], editable: [{ type: i0.Input, args: [{ isSignal: true, alias: "editable", required: false }] }], width: [{ type: i0.Input, args: [{ isSignal: true, alias: "width", required: false }] }], height: [{ type: i0.Input, args: [{ isSignal: true, alias: "height", required: false }] }], codeChange: [{ type: i0.Output, args: ["codeChange"] }] } });
19691
+
19423
19692
  class JvDialogComponent {
19424
19693
  document = inject(DOCUMENT);
19425
19694
  panelRef = viewChild('panel', ...(ngDevMode ? [{ debugName: "panelRef" }] : /* istanbul ignore next */ []));
@@ -19634,6 +19903,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImpo
19634
19903
  let datePickerIdSequence = 0;
19635
19904
  class JvDatePickerComponent {
19636
19905
  translation = inject(JvTranslationService);
19906
+ formatService = inject(JvFormatService);
19637
19907
  announcementService = inject(JvAnnouncementService);
19638
19908
  instanceId = ++datePickerIdSequence;
19639
19909
  calendarRootRef = viewChild('calendarRoot', ...(ngDevMode ? [{ debugName: "calendarRootRef" }] : /* istanbul ignore next */ []));
@@ -19666,14 +19936,10 @@ class JvDatePickerComponent {
19666
19936
  if (!value) {
19667
19937
  return this.placeholder() || this.translation.translate('datePicker.placeholder');
19668
19938
  }
19669
- return new Intl.DateTimeFormat(this.translation.locale(), {
19670
- day: '2-digit',
19671
- month: '2-digit',
19672
- year: 'numeric',
19673
- }).format(value);
19939
+ return this.formatService.format('date', value);
19674
19940
  }, ...(ngDevMode ? [{ debugName: "displayValue" }] : /* istanbul ignore next */ []));
19675
19941
  monthOptions = computed(() => {
19676
- const formatter = new Intl.DateTimeFormat(this.translation.locale(), { month: 'long' });
19942
+ const formatter = new Intl.DateTimeFormat(this.formatService.formatLocale(), { month: 'long' });
19677
19943
  return Array.from({ length: 12 }, (_, month) => ({
19678
19944
  label: capitalize(formatter.format(new Date(2026, month, 1))),
19679
19945
  value: String(month),
@@ -19687,7 +19953,7 @@ class JvDatePickerComponent {
19687
19953
  }, ...(ngDevMode ? [{ debugName: "yearOptions" }] : /* istanbul ignore next */ []));
19688
19954
  weekdayLabels = computed(() => {
19689
19955
  const startDay = this.firstDayOfWeek();
19690
- const formatter = new Intl.DateTimeFormat(this.translation.locale(), { weekday: 'short' });
19956
+ const formatter = new Intl.DateTimeFormat(this.formatService.formatLocale(), { weekday: 'short' });
19691
19957
  return Array.from({ length: 7 }, (_, index) => capitalize(formatter.format(addDays(new Date(2026, 5, 7), startDay + index))));
19692
19958
  }, ...(ngDevMode ? [{ debugName: "weekdayLabels" }] : /* istanbul ignore next */ []));
19693
19959
  calendarCells = computed(() => {
@@ -19829,10 +20095,10 @@ class JvDatePickerComponent {
19829
20095
  root?.querySelector(`[data-jv-date="${this.isoDate(date)}"]`)?.focus();
19830
20096
  }
19831
20097
  firstDayOfWeek() {
19832
- return this.translation.locale() === 'es' ? 1 : 0;
20098
+ return this.formatService.firstDayOfWeek();
19833
20099
  }
19834
20100
  displayDateForAnnouncement(date) {
19835
- return new Intl.DateTimeFormat(this.translation.locale(), {
20101
+ return new Intl.DateTimeFormat(this.formatService.formatLocale(), {
19836
20102
  weekday: 'long',
19837
20103
  day: 'numeric',
19838
20104
  month: 'long',
@@ -19846,83 +20112,83 @@ class JvDatePickerComponent {
19846
20112
  useExisting: forwardRef(() => JvDatePickerComponent),
19847
20113
  multi: true,
19848
20114
  },
19849
- ], viewQueries: [{ propertyName: "calendarRootRef", first: true, predicate: ["calendarRoot"], descendants: true, isSignal: true }, { propertyName: "triggerButtonRef", first: true, predicate: ["triggerButton"], descendants: true, isSignal: true }], ngImport: i0, template: `
19850
- <jv-form-container
19851
- [label]="label()"
19852
- [inputId]="inputId()"
19853
- [required]="required()"
19854
- [invalid]="invalid()"
19855
- [hint]="hint()"
19856
- [error]="error()"
19857
- >
19858
- <button
19859
- #triggerButton
19860
- type="button"
19861
- class="jv-date-picker-trigger"
19862
- [class.is-invalid]="invalid()"
19863
- [id]="inputId()"
19864
- [disabled]="disabled"
19865
- [attr.aria-haspopup]="'dialog'"
19866
- [attr.aria-expanded]="dialogOpen() ? 'true' : 'false'"
19867
- [attr.aria-controls]="dialogId"
19868
- [attr.aria-invalid]="invalid() ? 'true' : null"
19869
- [attr.aria-describedby]="describedBy() || null"
19870
- [attr.aria-label]="triggerAriaLabel()"
19871
- [attr.title]="displayValue()"
19872
- (click)="openDialog()"
19873
- (blur)="handleBlur()"
19874
- >
19875
- <span [class.jv-date-picker-placeholder]="!currentValue()">{{ displayValue() }}</span>
19876
- <jv-icon name="calendar-days" [size]="18" />
19877
- </button>
19878
- </jv-form-container>
19879
-
19880
- <jv-dialog [open]="dialogOpen()" [title]="resolvedDialogTitle()" (closed)="closeDialog()">
19881
- <div #calendarRoot [id]="dialogId" class="jv-date-picker-dialog">
19882
- <div class="jv-date-picker-toolbar">
19883
- <jv-icon-button icon="chevron-left" variant="ghost" [ariaLabel]="translation.translate('datePicker.prevMonth')" (click)="shiftMonth(-1)" />
19884
-
19885
- <div class="jv-date-picker-selects">
19886
- <jv-select [options]="monthOptions()" [modelValue]="visibleMonth().getMonth().toString()" (selectionChange)="handleMonthChange($event)" />
19887
- <jv-select [options]="yearOptions()" [modelValue]="visibleMonth().getFullYear().toString()" (selectionChange)="handleYearChange($event)" />
19888
- </div>
19889
-
19890
- <jv-icon-button icon="chevron-right" variant="ghost" [ariaLabel]="translation.translate('datePicker.nextMonth')" (click)="shiftMonth(1)" />
19891
- </div>
19892
-
19893
- <div class="jv-date-picker-weekdays" aria-hidden="true">
19894
- @for (weekday of weekdayLabels(); track weekday) {
19895
- <span>{{ weekday }}</span>
19896
- }
19897
- </div>
19898
-
19899
- <div class="jv-date-picker-grid" role="grid" [attr.aria-label]="resolvedDialogTitle()">
19900
- @for (cell of calendarCells(); track isoDate(cell.date)) {
19901
- <button
19902
- type="button"
19903
- class="jv-date-picker-day"
19904
- [class.jv-date-picker-day--outside]="!cell.currentMonth"
19905
- [class.jv-date-picker-day--today]="cell.isToday"
19906
- [class.jv-date-picker-day--selected]="isSelected(cell.date)"
19907
- [attr.data-jv-date]="isoDate(cell.date)"
19908
- [attr.tabindex]="isFocusedDate(cell.date) ? 0 : -1"
19909
- [attr.aria-selected]="isSelected(cell.date) ? 'true' : 'false'"
19910
- (click)="selectDate(cell.date)"
19911
- (keydown)="handleDayKeydown($event, cell.date)"
19912
- >
19913
- {{ cell.date.getDate() }}
19914
- </button>
19915
- }
19916
- </div>
19917
- </div>
19918
-
19919
- <div dialog-actions class="jv-date-picker-actions">
19920
- @if (currentValue()) {
19921
- <jv-button variant="ghost" (click)="clearSelection()">{{ translation.translate('datePicker.clear') }}</jv-button>
19922
- }
19923
- <jv-button variant="outline" (click)="closeDialog()">{{ translation.translate('datePicker.close') }}</jv-button>
19924
- </div>
19925
- </jv-dialog>
20115
+ ], viewQueries: [{ propertyName: "calendarRootRef", first: true, predicate: ["calendarRoot"], descendants: true, isSignal: true }, { propertyName: "triggerButtonRef", first: true, predicate: ["triggerButton"], descendants: true, isSignal: true }], ngImport: i0, template: `
20116
+ <jv-form-container
20117
+ [label]="label()"
20118
+ [inputId]="inputId()"
20119
+ [required]="required()"
20120
+ [invalid]="invalid()"
20121
+ [hint]="hint()"
20122
+ [error]="error()"
20123
+ >
20124
+ <button
20125
+ #triggerButton
20126
+ type="button"
20127
+ class="jv-date-picker-trigger"
20128
+ [class.is-invalid]="invalid()"
20129
+ [id]="inputId()"
20130
+ [disabled]="disabled"
20131
+ [attr.aria-haspopup]="'dialog'"
20132
+ [attr.aria-expanded]="dialogOpen() ? 'true' : 'false'"
20133
+ [attr.aria-controls]="dialogId"
20134
+ [attr.aria-invalid]="invalid() ? 'true' : null"
20135
+ [attr.aria-describedby]="describedBy() || null"
20136
+ [attr.aria-label]="triggerAriaLabel()"
20137
+ [attr.title]="displayValue()"
20138
+ (click)="openDialog()"
20139
+ (blur)="handleBlur()"
20140
+ >
20141
+ <span [class.jv-date-picker-placeholder]="!currentValue()">{{ displayValue() }}</span>
20142
+ <jv-icon name="calendar-days" [size]="18" />
20143
+ </button>
20144
+ </jv-form-container>
20145
+
20146
+ <jv-dialog [open]="dialogOpen()" [title]="resolvedDialogTitle()" (closed)="closeDialog()">
20147
+ <div #calendarRoot [id]="dialogId" class="jv-date-picker-dialog">
20148
+ <div class="jv-date-picker-toolbar">
20149
+ <jv-icon-button icon="chevron-left" variant="ghost" [ariaLabel]="translation.translate('datePicker.prevMonth')" (click)="shiftMonth(-1)" />
20150
+
20151
+ <div class="jv-date-picker-selects">
20152
+ <jv-select [options]="monthOptions()" [modelValue]="visibleMonth().getMonth().toString()" (selectionChange)="handleMonthChange($event)" />
20153
+ <jv-select [options]="yearOptions()" [modelValue]="visibleMonth().getFullYear().toString()" (selectionChange)="handleYearChange($event)" />
20154
+ </div>
20155
+
20156
+ <jv-icon-button icon="chevron-right" variant="ghost" [ariaLabel]="translation.translate('datePicker.nextMonth')" (click)="shiftMonth(1)" />
20157
+ </div>
20158
+
20159
+ <div class="jv-date-picker-weekdays" aria-hidden="true">
20160
+ @for (weekday of weekdayLabels(); track weekday) {
20161
+ <span>{{ weekday }}</span>
20162
+ }
20163
+ </div>
20164
+
20165
+ <div class="jv-date-picker-grid" role="grid" [attr.aria-label]="resolvedDialogTitle()">
20166
+ @for (cell of calendarCells(); track isoDate(cell.date)) {
20167
+ <button
20168
+ type="button"
20169
+ class="jv-date-picker-day"
20170
+ [class.jv-date-picker-day--outside]="!cell.currentMonth"
20171
+ [class.jv-date-picker-day--today]="cell.isToday"
20172
+ [class.jv-date-picker-day--selected]="isSelected(cell.date)"
20173
+ [attr.data-jv-date]="isoDate(cell.date)"
20174
+ [attr.tabindex]="isFocusedDate(cell.date) ? 0 : -1"
20175
+ [attr.aria-selected]="isSelected(cell.date) ? 'true' : 'false'"
20176
+ (click)="selectDate(cell.date)"
20177
+ (keydown)="handleDayKeydown($event, cell.date)"
20178
+ >
20179
+ {{ cell.date.getDate() }}
20180
+ </button>
20181
+ }
20182
+ </div>
20183
+ </div>
20184
+
20185
+ <div dialog-actions class="jv-date-picker-actions">
20186
+ @if (currentValue()) {
20187
+ <jv-button variant="ghost" (click)="clearSelection()">{{ translation.translate('datePicker.clear') }}</jv-button>
20188
+ }
20189
+ <jv-button variant="outline" (click)="closeDialog()">{{ translation.translate('datePicker.close') }}</jv-button>
20190
+ </div>
20191
+ </jv-dialog>
19926
20192
  `, isInline: true, styles: [".jv-date-picker-trigger{width:100%;min-height:var(--jv-density-control-height);display:flex;align-items:center;justify-content:space-between;gap:var(--jv-spacing-sm);padding:0 var(--jv-spacing-md);border:1px solid var(--jv-color-border);border-radius:var(--jv-radius-md);background:var(--jv-color-surface);color:var(--jv-color-foreground);text-align:left;font:inherit;cursor:pointer}.jv-date-picker-trigger.is-invalid{border-color:var(--jv-color-danger)}.jv-date-picker-trigger:disabled{opacity:.7;cursor:not-allowed}.jv-date-picker-trigger:focus-visible{outline:2px solid var(--jv-color-primary);outline-offset:1px}.jv-date-picker-placeholder{color:var(--jv-color-foreground-muted)}.jv-date-picker-dialog,.jv-date-picker-actions{display:grid;gap:var(--jv-spacing-md)}.jv-date-picker-toolbar,.jv-date-picker-selects,.jv-date-picker-weekdays,.jv-date-picker-grid{display:grid;gap:var(--jv-spacing-sm)}.jv-date-picker-toolbar{grid-template-columns:auto 1fr auto;align-items:center}.jv-date-picker-selects{grid-template-columns:1fr 1fr}.jv-date-picker-weekdays,.jv-date-picker-grid{grid-template-columns:repeat(7,minmax(0,1fr))}.jv-date-picker-weekdays span{text-align:center;font-size:.8125rem;font-weight:600;color:var(--jv-color-foreground-muted)}.jv-date-picker-day{min-height:2.5rem;border:1px solid transparent;border-radius:var(--jv-radius-md);background:transparent;color:var(--jv-color-foreground);font:inherit;cursor:pointer;transition:background-color .16s ease,border-color .16s ease,color .16s ease}.jv-date-picker-day:hover,.jv-date-picker-day:focus-visible{background:var(--jv-color-surface-muted)}.jv-date-picker-day:focus-visible{outline:2px solid var(--jv-color-primary);outline-offset:1px}.jv-date-picker-day--outside{color:var(--jv-color-foreground-muted)}.jv-date-picker-day--today{border-color:var(--jv-color-primary)}.jv-date-picker-day--selected{background:var(--jv-color-primary);color:#fff}\n"], dependencies: [{ kind: "component", type: JvButtonComponent, selector: "jv-button", inputs: ["type", "variant", "icon", "iconPosition", "loading", "disabled"] }, { kind: "component", type: JvDialogComponent, selector: "jv-dialog", inputs: ["open", "title"], outputs: ["closed"] }, { kind: "component", type: JvFormContainerComponent, selector: "jv-form-container", inputs: ["label", "hint", "error", "inputId", "hintId", "errorId", "required", "invalid"] }, { kind: "component", type: JvIconButtonComponent, selector: "jv-icon-button", inputs: ["icon", "ariaLabel", "variant", "disabled"] }, { kind: "component", type: JvIconComponent, selector: "jv-icon", inputs: ["name", "size", "strokeWidth", "decorative", "ariaLabel"] }, { kind: "component", type: JvSelectComponent, selector: "jv-select", inputs: ["options", "placeholder", "required", "invalid", "describedBy", "inputId", "modelValue"], outputs: ["selectionChange"] }] });
19927
20193
  }
19928
20194
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: JvDatePickerComponent, decorators: [{
@@ -19933,83 +20199,83 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImpo
19933
20199
  useExisting: forwardRef(() => JvDatePickerComponent),
19934
20200
  multi: true,
19935
20201
  },
19936
- ], template: `
19937
- <jv-form-container
19938
- [label]="label()"
19939
- [inputId]="inputId()"
19940
- [required]="required()"
19941
- [invalid]="invalid()"
19942
- [hint]="hint()"
19943
- [error]="error()"
19944
- >
19945
- <button
19946
- #triggerButton
19947
- type="button"
19948
- class="jv-date-picker-trigger"
19949
- [class.is-invalid]="invalid()"
19950
- [id]="inputId()"
19951
- [disabled]="disabled"
19952
- [attr.aria-haspopup]="'dialog'"
19953
- [attr.aria-expanded]="dialogOpen() ? 'true' : 'false'"
19954
- [attr.aria-controls]="dialogId"
19955
- [attr.aria-invalid]="invalid() ? 'true' : null"
19956
- [attr.aria-describedby]="describedBy() || null"
19957
- [attr.aria-label]="triggerAriaLabel()"
19958
- [attr.title]="displayValue()"
19959
- (click)="openDialog()"
19960
- (blur)="handleBlur()"
19961
- >
19962
- <span [class.jv-date-picker-placeholder]="!currentValue()">{{ displayValue() }}</span>
19963
- <jv-icon name="calendar-days" [size]="18" />
19964
- </button>
19965
- </jv-form-container>
19966
-
19967
- <jv-dialog [open]="dialogOpen()" [title]="resolvedDialogTitle()" (closed)="closeDialog()">
19968
- <div #calendarRoot [id]="dialogId" class="jv-date-picker-dialog">
19969
- <div class="jv-date-picker-toolbar">
19970
- <jv-icon-button icon="chevron-left" variant="ghost" [ariaLabel]="translation.translate('datePicker.prevMonth')" (click)="shiftMonth(-1)" />
19971
-
19972
- <div class="jv-date-picker-selects">
19973
- <jv-select [options]="monthOptions()" [modelValue]="visibleMonth().getMonth().toString()" (selectionChange)="handleMonthChange($event)" />
19974
- <jv-select [options]="yearOptions()" [modelValue]="visibleMonth().getFullYear().toString()" (selectionChange)="handleYearChange($event)" />
19975
- </div>
19976
-
19977
- <jv-icon-button icon="chevron-right" variant="ghost" [ariaLabel]="translation.translate('datePicker.nextMonth')" (click)="shiftMonth(1)" />
19978
- </div>
19979
-
19980
- <div class="jv-date-picker-weekdays" aria-hidden="true">
19981
- @for (weekday of weekdayLabels(); track weekday) {
19982
- <span>{{ weekday }}</span>
19983
- }
19984
- </div>
19985
-
19986
- <div class="jv-date-picker-grid" role="grid" [attr.aria-label]="resolvedDialogTitle()">
19987
- @for (cell of calendarCells(); track isoDate(cell.date)) {
19988
- <button
19989
- type="button"
19990
- class="jv-date-picker-day"
19991
- [class.jv-date-picker-day--outside]="!cell.currentMonth"
19992
- [class.jv-date-picker-day--today]="cell.isToday"
19993
- [class.jv-date-picker-day--selected]="isSelected(cell.date)"
19994
- [attr.data-jv-date]="isoDate(cell.date)"
19995
- [attr.tabindex]="isFocusedDate(cell.date) ? 0 : -1"
19996
- [attr.aria-selected]="isSelected(cell.date) ? 'true' : 'false'"
19997
- (click)="selectDate(cell.date)"
19998
- (keydown)="handleDayKeydown($event, cell.date)"
19999
- >
20000
- {{ cell.date.getDate() }}
20001
- </button>
20002
- }
20003
- </div>
20004
- </div>
20005
-
20006
- <div dialog-actions class="jv-date-picker-actions">
20007
- @if (currentValue()) {
20008
- <jv-button variant="ghost" (click)="clearSelection()">{{ translation.translate('datePicker.clear') }}</jv-button>
20009
- }
20010
- <jv-button variant="outline" (click)="closeDialog()">{{ translation.translate('datePicker.close') }}</jv-button>
20011
- </div>
20012
- </jv-dialog>
20202
+ ], template: `
20203
+ <jv-form-container
20204
+ [label]="label()"
20205
+ [inputId]="inputId()"
20206
+ [required]="required()"
20207
+ [invalid]="invalid()"
20208
+ [hint]="hint()"
20209
+ [error]="error()"
20210
+ >
20211
+ <button
20212
+ #triggerButton
20213
+ type="button"
20214
+ class="jv-date-picker-trigger"
20215
+ [class.is-invalid]="invalid()"
20216
+ [id]="inputId()"
20217
+ [disabled]="disabled"
20218
+ [attr.aria-haspopup]="'dialog'"
20219
+ [attr.aria-expanded]="dialogOpen() ? 'true' : 'false'"
20220
+ [attr.aria-controls]="dialogId"
20221
+ [attr.aria-invalid]="invalid() ? 'true' : null"
20222
+ [attr.aria-describedby]="describedBy() || null"
20223
+ [attr.aria-label]="triggerAriaLabel()"
20224
+ [attr.title]="displayValue()"
20225
+ (click)="openDialog()"
20226
+ (blur)="handleBlur()"
20227
+ >
20228
+ <span [class.jv-date-picker-placeholder]="!currentValue()">{{ displayValue() }}</span>
20229
+ <jv-icon name="calendar-days" [size]="18" />
20230
+ </button>
20231
+ </jv-form-container>
20232
+
20233
+ <jv-dialog [open]="dialogOpen()" [title]="resolvedDialogTitle()" (closed)="closeDialog()">
20234
+ <div #calendarRoot [id]="dialogId" class="jv-date-picker-dialog">
20235
+ <div class="jv-date-picker-toolbar">
20236
+ <jv-icon-button icon="chevron-left" variant="ghost" [ariaLabel]="translation.translate('datePicker.prevMonth')" (click)="shiftMonth(-1)" />
20237
+
20238
+ <div class="jv-date-picker-selects">
20239
+ <jv-select [options]="monthOptions()" [modelValue]="visibleMonth().getMonth().toString()" (selectionChange)="handleMonthChange($event)" />
20240
+ <jv-select [options]="yearOptions()" [modelValue]="visibleMonth().getFullYear().toString()" (selectionChange)="handleYearChange($event)" />
20241
+ </div>
20242
+
20243
+ <jv-icon-button icon="chevron-right" variant="ghost" [ariaLabel]="translation.translate('datePicker.nextMonth')" (click)="shiftMonth(1)" />
20244
+ </div>
20245
+
20246
+ <div class="jv-date-picker-weekdays" aria-hidden="true">
20247
+ @for (weekday of weekdayLabels(); track weekday) {
20248
+ <span>{{ weekday }}</span>
20249
+ }
20250
+ </div>
20251
+
20252
+ <div class="jv-date-picker-grid" role="grid" [attr.aria-label]="resolvedDialogTitle()">
20253
+ @for (cell of calendarCells(); track isoDate(cell.date)) {
20254
+ <button
20255
+ type="button"
20256
+ class="jv-date-picker-day"
20257
+ [class.jv-date-picker-day--outside]="!cell.currentMonth"
20258
+ [class.jv-date-picker-day--today]="cell.isToday"
20259
+ [class.jv-date-picker-day--selected]="isSelected(cell.date)"
20260
+ [attr.data-jv-date]="isoDate(cell.date)"
20261
+ [attr.tabindex]="isFocusedDate(cell.date) ? 0 : -1"
20262
+ [attr.aria-selected]="isSelected(cell.date) ? 'true' : 'false'"
20263
+ (click)="selectDate(cell.date)"
20264
+ (keydown)="handleDayKeydown($event, cell.date)"
20265
+ >
20266
+ {{ cell.date.getDate() }}
20267
+ </button>
20268
+ }
20269
+ </div>
20270
+ </div>
20271
+
20272
+ <div dialog-actions class="jv-date-picker-actions">
20273
+ @if (currentValue()) {
20274
+ <jv-button variant="ghost" (click)="clearSelection()">{{ translation.translate('datePicker.clear') }}</jv-button>
20275
+ }
20276
+ <jv-button variant="outline" (click)="closeDialog()">{{ translation.translate('datePicker.close') }}</jv-button>
20277
+ </div>
20278
+ </jv-dialog>
20013
20279
  `, styles: [".jv-date-picker-trigger{width:100%;min-height:var(--jv-density-control-height);display:flex;align-items:center;justify-content:space-between;gap:var(--jv-spacing-sm);padding:0 var(--jv-spacing-md);border:1px solid var(--jv-color-border);border-radius:var(--jv-radius-md);background:var(--jv-color-surface);color:var(--jv-color-foreground);text-align:left;font:inherit;cursor:pointer}.jv-date-picker-trigger.is-invalid{border-color:var(--jv-color-danger)}.jv-date-picker-trigger:disabled{opacity:.7;cursor:not-allowed}.jv-date-picker-trigger:focus-visible{outline:2px solid var(--jv-color-primary);outline-offset:1px}.jv-date-picker-placeholder{color:var(--jv-color-foreground-muted)}.jv-date-picker-dialog,.jv-date-picker-actions{display:grid;gap:var(--jv-spacing-md)}.jv-date-picker-toolbar,.jv-date-picker-selects,.jv-date-picker-weekdays,.jv-date-picker-grid{display:grid;gap:var(--jv-spacing-sm)}.jv-date-picker-toolbar{grid-template-columns:auto 1fr auto;align-items:center}.jv-date-picker-selects{grid-template-columns:1fr 1fr}.jv-date-picker-weekdays,.jv-date-picker-grid{grid-template-columns:repeat(7,minmax(0,1fr))}.jv-date-picker-weekdays span{text-align:center;font-size:.8125rem;font-weight:600;color:var(--jv-color-foreground-muted)}.jv-date-picker-day{min-height:2.5rem;border:1px solid transparent;border-radius:var(--jv-radius-md);background:transparent;color:var(--jv-color-foreground);font:inherit;cursor:pointer;transition:background-color .16s ease,border-color .16s ease,color .16s ease}.jv-date-picker-day:hover,.jv-date-picker-day:focus-visible{background:var(--jv-color-surface-muted)}.jv-date-picker-day:focus-visible{outline:2px solid var(--jv-color-primary);outline-offset:1px}.jv-date-picker-day--outside{color:var(--jv-color-foreground-muted)}.jv-date-picker-day--today{border-color:var(--jv-color-primary)}.jv-date-picker-day--selected{background:var(--jv-color-primary);color:#fff}\n"] }]
20014
20280
  }], propDecorators: { calendarRootRef: [{ type: i0.ViewChild, args: ['calendarRoot', { isSignal: true }] }], triggerButtonRef: [{ type: i0.ViewChild, args: ['triggerButton', { isSignal: true }] }], label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], dialogTitle: [{ type: i0.Input, args: [{ isSignal: true, alias: "dialogTitle", required: false }] }], required: [{ type: i0.Input, args: [{ isSignal: true, alias: "required", required: false }] }], invalid: [{ type: i0.Input, args: [{ isSignal: true, alias: "invalid", required: false }] }], hint: [{ type: i0.Input, args: [{ isSignal: true, alias: "hint", required: false }] }], error: [{ type: i0.Input, args: [{ isSignal: true, alias: "error", required: false }] }], describedBy: [{ type: i0.Input, args: [{ isSignal: true, alias: "describedBy", required: false }] }], inputId: [{ type: i0.Input, args: [{ isSignal: true, alias: "inputId", required: false }] }], valueInput: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }], selectionChange: [{ type: i0.Output, args: ["selectionChange"] }] } });
20015
20281
  function daysInMonth(year, month) {
@@ -20034,6 +20300,297 @@ function capitalize(value) {
20034
20300
  return value.charAt(0).toUpperCase() + value.slice(1);
20035
20301
  }
20036
20302
 
20303
+ let fileInputIdSequence = 0;
20304
+ class JvFileInputComponent {
20305
+ translation = inject(JvTranslationService);
20306
+ instanceId = ++fileInputIdSequence;
20307
+ nativeInputRef = viewChild('nativeInput', ...(ngDevMode ? [{ debugName: "nativeInputRef" }] : /* istanbul ignore next */ []));
20308
+ label = input('', ...(ngDevMode ? [{ debugName: "label" }] : /* istanbul ignore next */ []));
20309
+ hint = input('', ...(ngDevMode ? [{ debugName: "hint" }] : /* istanbul ignore next */ []));
20310
+ error = input('', ...(ngDevMode ? [{ debugName: "error" }] : /* istanbul ignore next */ []));
20311
+ describedBy = input('', ...(ngDevMode ? [{ debugName: "describedBy" }] : /* istanbul ignore next */ []));
20312
+ inputId = input(`jv-file-input-${this.instanceId}`, ...(ngDevMode ? [{ debugName: "inputId" }] : /* istanbul ignore next */ []));
20313
+ accept = input('', ...(ngDevMode ? [{ debugName: "accept" }] : /* istanbul ignore next */ []));
20314
+ multiple = input(true, ...(ngDevMode ? [{ debugName: "multiple" }] : /* istanbul ignore next */ []));
20315
+ maxFiles = input(10, ...(ngDevMode ? [{ debugName: "maxFiles" }] : /* istanbul ignore next */ []));
20316
+ disabled = input(false, ...(ngDevMode ? [{ debugName: "disabled" }] : /* istanbul ignore next */ []));
20317
+ required = input(false, ...(ngDevMode ? [{ debugName: "required" }] : /* istanbul ignore next */ []));
20318
+ invalid = input(false, ...(ngDevMode ? [{ debugName: "invalid" }] : /* istanbul ignore next */ []));
20319
+ filesInput = input(undefined, { ...(ngDevMode ? { debugName: "filesInput" } : /* istanbul ignore next */ {}), alias: 'files' });
20320
+ filesChange = output();
20321
+ filesRejected = output();
20322
+ dragActive = signal(false, ...(ngDevMode ? [{ debugName: "dragActive" }] : /* istanbul ignore next */ []));
20323
+ rejectionMessage = signal('', ...(ngDevMode ? [{ debugName: "rejectionMessage" }] : /* istanbul ignore next */ []));
20324
+ internalFiles = signal([], ...(ngDevMode ? [{ debugName: "internalFiles" }] : /* istanbul ignore next */ []));
20325
+ currentFiles = computed(() => {
20326
+ const externalFiles = this.filesInput();
20327
+ return externalFiles !== undefined ? [...(externalFiles ?? [])] : this.internalFiles();
20328
+ }, ...(ngDevMode ? [{ debugName: "currentFiles" }] : /* istanbul ignore next */ []));
20329
+ selectionSummary = computed(() => this.translation.translate('fileInput.selectionSummary', { count: this.currentFiles().length, max: this.maxFiles() }), ...(ngDevMode ? [{ debugName: "selectionSummary" }] : /* istanbul ignore next */ []));
20330
+ dropzoneAriaLabel = computed(() => `${this.label() || this.translation.translate('fileInput.dropLabel')}. ${this.selectionSummary()}`, ...(ngDevMode ? [{ debugName: "dropzoneAriaLabel" }] : /* istanbul ignore next */ []));
20331
+ openFileDialog() {
20332
+ if (this.disabled())
20333
+ return;
20334
+ this.nativeInputRef()?.nativeElement.click();
20335
+ }
20336
+ handleDropzoneKeydown(event) {
20337
+ if (event.key === 'Enter' || event.key === ' ') {
20338
+ event.preventDefault();
20339
+ this.openFileDialog();
20340
+ }
20341
+ }
20342
+ onNativeInputChange(fileList) {
20343
+ this.consumeFiles(fileList ? Array.from(fileList) : []);
20344
+ const input = this.nativeInputRef()?.nativeElement;
20345
+ if (input) {
20346
+ input.value = '';
20347
+ }
20348
+ }
20349
+ onDragEnter(event) {
20350
+ if (this.disabled())
20351
+ return;
20352
+ event.preventDefault();
20353
+ this.dragActive.set(true);
20354
+ }
20355
+ onDragOver(event) {
20356
+ if (this.disabled())
20357
+ return;
20358
+ event.preventDefault();
20359
+ this.dragActive.set(true);
20360
+ }
20361
+ onDragLeave(event) {
20362
+ if (event.currentTarget?.contains(event.relatedTarget)) {
20363
+ return;
20364
+ }
20365
+ this.dragActive.set(false);
20366
+ }
20367
+ onDrop(event) {
20368
+ if (this.disabled())
20369
+ return;
20370
+ event.preventDefault();
20371
+ this.dragActive.set(false);
20372
+ this.consumeFiles(Array.from(event.dataTransfer?.files ?? []));
20373
+ }
20374
+ removeFile(index) {
20375
+ if (this.disabled())
20376
+ return;
20377
+ const nextFiles = [...this.currentFiles()];
20378
+ nextFiles.splice(index, 1);
20379
+ this.updateFiles(nextFiles);
20380
+ }
20381
+ trackFile(index, file) {
20382
+ return `${file.name}-${file.size}-${file.lastModified}-${index}`;
20383
+ }
20384
+ formatBytes(size) {
20385
+ if (size < 1024)
20386
+ return `${size} B`;
20387
+ if (size < 1024 * 1024)
20388
+ return `${(size / 1024).toFixed(1)} KB`;
20389
+ return `${(size / (1024 * 1024)).toFixed(1)} MB`;
20390
+ }
20391
+ consumeFiles(files) {
20392
+ if (files.length === 0)
20393
+ return;
20394
+ const accepted = [];
20395
+ const rejectedByAccept = [];
20396
+ const currentFiles = this.currentFiles();
20397
+ const remainingSlots = this.multiple() ? Math.max(this.maxFiles() - currentFiles.length, 0) : 1;
20398
+ for (const file of files) {
20399
+ if (!this.matchesAccept(file)) {
20400
+ rejectedByAccept.push(file);
20401
+ continue;
20402
+ }
20403
+ accepted.push(file);
20404
+ }
20405
+ const nextAccepted = this.multiple() ? accepted.slice(0, remainingSlots) : accepted.slice(0, 1);
20406
+ const rejectedByCount = this.multiple() ? accepted.slice(remainingSlots) : accepted.slice(1);
20407
+ const nextFiles = this.multiple() ? [...currentFiles, ...nextAccepted] : nextAccepted;
20408
+ this.rejectionMessage.set('');
20409
+ if (rejectedByAccept.length > 0) {
20410
+ this.rejectionMessage.set(this.translation.translate('fileInput.invalidFiles', { count: rejectedByAccept.length }));
20411
+ this.filesRejected.emit({ files: rejectedByAccept, reason: 'accept' });
20412
+ }
20413
+ if (rejectedByCount.length > 0) {
20414
+ this.rejectionMessage.set(this.translation.translate('fileInput.tooManyFiles', { max: this.maxFiles() }));
20415
+ this.filesRejected.emit({ files: rejectedByCount, reason: 'maxFiles' });
20416
+ }
20417
+ this.updateFiles(nextFiles);
20418
+ }
20419
+ updateFiles(files) {
20420
+ if (this.filesInput() === undefined) {
20421
+ this.internalFiles.set(files);
20422
+ }
20423
+ this.filesChange.emit(files);
20424
+ }
20425
+ matchesAccept(file) {
20426
+ const accept = this.accept().trim();
20427
+ if (!accept)
20428
+ return true;
20429
+ const type = file.type.toLowerCase();
20430
+ const name = file.name.toLowerCase();
20431
+ return accept
20432
+ .split(',')
20433
+ .map((entry) => entry.trim().toLowerCase())
20434
+ .some((rule) => {
20435
+ if (!rule)
20436
+ return false;
20437
+ if (rule.startsWith('.'))
20438
+ return name.endsWith(rule);
20439
+ if (rule.endsWith('/*'))
20440
+ return type.startsWith(`${rule.slice(0, -1)}`);
20441
+ return type === rule;
20442
+ });
20443
+ }
20444
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: JvFileInputComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
20445
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.17", type: JvFileInputComponent, isStandalone: true, selector: "jv-file-input", inputs: { label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, hint: { classPropertyName: "hint", publicName: "hint", isSignal: true, isRequired: false, transformFunction: null }, error: { classPropertyName: "error", publicName: "error", isSignal: true, isRequired: false, transformFunction: null }, describedBy: { classPropertyName: "describedBy", publicName: "describedBy", isSignal: true, isRequired: false, transformFunction: null }, inputId: { classPropertyName: "inputId", publicName: "inputId", isSignal: true, isRequired: false, transformFunction: null }, accept: { classPropertyName: "accept", publicName: "accept", isSignal: true, isRequired: false, transformFunction: null }, multiple: { classPropertyName: "multiple", publicName: "multiple", isSignal: true, isRequired: false, transformFunction: null }, maxFiles: { classPropertyName: "maxFiles", publicName: "maxFiles", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, required: { classPropertyName: "required", publicName: "required", isSignal: true, isRequired: false, transformFunction: null }, invalid: { classPropertyName: "invalid", publicName: "invalid", isSignal: true, isRequired: false, transformFunction: null }, filesInput: { classPropertyName: "filesInput", publicName: "files", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { filesChange: "filesChange", filesRejected: "filesRejected" }, viewQueries: [{ propertyName: "nativeInputRef", first: true, predicate: ["nativeInput"], descendants: true, isSignal: true }], ngImport: i0, template: `
20446
+ <jv-form-container
20447
+ [label]="label()"
20448
+ [inputId]="inputId()"
20449
+ [required]="required()"
20450
+ [invalid]="invalid()"
20451
+ [hint]="hint()"
20452
+ [error]="error()"
20453
+ >
20454
+ <input
20455
+ #nativeInput
20456
+ class="jv-file-input-native"
20457
+ type="file"
20458
+ [id]="inputId()"
20459
+ [attr.accept]="accept() || null"
20460
+ [attr.multiple]="multiple() ? '' : null"
20461
+ [disabled]="disabled()"
20462
+ [attr.aria-describedby]="describedBy() || null"
20463
+ (change)="onNativeInputChange($any($event.target).files)"
20464
+ />
20465
+
20466
+ <div
20467
+ class="jv-file-input-dropzone"
20468
+ [class.jv-file-input-dropzone--disabled]="disabled()"
20469
+ [class.jv-file-input-dropzone--dragover]="dragActive()"
20470
+ [class.jv-file-input-dropzone--invalid]="invalid()"
20471
+ role="button"
20472
+ tabindex="0"
20473
+ [attr.aria-disabled]="disabled() ? 'true' : 'false'"
20474
+ [attr.aria-label]="dropzoneAriaLabel()"
20475
+ (click)="openFileDialog()"
20476
+ (keydown)="handleDropzoneKeydown($event)"
20477
+ (dragenter)="onDragEnter($event)"
20478
+ (dragover)="onDragOver($event)"
20479
+ (dragleave)="onDragLeave($event)"
20480
+ (drop)="onDrop($event)"
20481
+ >
20482
+ <jv-icon name="upload-cloud" [size]="24" />
20483
+ <div class="jv-file-input-copy">
20484
+ <strong>{{ translation.translate('fileInput.dropLabel') }}</strong>
20485
+ <span>{{ translation.translate('fileInput.helperText') }}</span>
20486
+ <span class="jv-file-input-meta">{{ selectionSummary() }}</span>
20487
+ </div>
20488
+ </div>
20489
+
20490
+ @if (rejectionMessage()) {
20491
+ <p class="jv-file-input-rejection" aria-live="polite">{{ rejectionMessage() }}</p>
20492
+ }
20493
+
20494
+ @if (currentFiles().length > 0) {
20495
+ <ul class="jv-file-input-list">
20496
+ @for (file of currentFiles(); track trackFile($index, file); let index = $index) {
20497
+ <li class="jv-file-input-item">
20498
+ <div class="jv-file-input-item-copy">
20499
+ <span class="jv-file-input-item-name">{{ file.name }}</span>
20500
+ <span class="jv-file-input-item-meta">{{ formatBytes(file.size) }}</span>
20501
+ </div>
20502
+ <button
20503
+ type="button"
20504
+ class="jv-file-input-remove"
20505
+ [disabled]="disabled()"
20506
+ [attr.aria-label]="translation.translate('fileInput.removeFile', { name: file.name })"
20507
+ (click)="removeFile(index)"
20508
+ >
20509
+ <jv-icon name="x" [size]="16" />
20510
+ </button>
20511
+ </li>
20512
+ }
20513
+ </ul>
20514
+ }
20515
+ </jv-form-container>
20516
+ `, isInline: true, styles: [".jv-file-input-native{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);border:0}.jv-file-input-dropzone{display:grid;grid-template-columns:auto 1fr;gap:var(--jv-spacing-md);align-items:center;padding:var(--jv-spacing-lg);border:1px dashed var(--jv-color-border);border-radius:var(--jv-radius-md);background:var(--jv-color-surface);color:var(--jv-color-foreground);cursor:pointer;transition:border-color .16s ease,background-color .16s ease,box-shadow .16s ease;outline:none}.jv-file-input-dropzone:hover,.jv-file-input-dropzone:focus-visible,.jv-file-input-dropzone--dragover{border-color:var(--jv-color-primary);background:var(--jv-color-surface-muted);box-shadow:inset 0 0 0 1px var(--jv-color-primary)}.jv-file-input-dropzone--invalid{border-color:var(--jv-color-danger)}.jv-file-input-dropzone--disabled{opacity:.65;cursor:not-allowed}.jv-file-input-copy{display:grid;gap:.25rem;min-width:0}.jv-file-input-copy span,.jv-file-input-meta,.jv-file-input-item-meta,.jv-file-input-rejection{color:var(--jv-color-foreground-muted)}.jv-file-input-rejection{margin:var(--jv-spacing-sm) 0 0;font-size:.875rem}.jv-file-input-list{list-style:none;padding:0;margin:var(--jv-spacing-md) 0 0;display:grid;gap:var(--jv-spacing-sm)}.jv-file-input-item{display:flex;align-items:center;justify-content:space-between;gap:var(--jv-spacing-md);padding:var(--jv-spacing-sm) var(--jv-spacing-md);border:1px solid var(--jv-color-border);border-radius:var(--jv-radius-md);background:var(--jv-color-surface)}.jv-file-input-item-copy{min-width:0;display:grid;gap:.125rem}.jv-file-input-item-name{font-weight:600;color:var(--jv-color-foreground);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.jv-file-input-remove{display:inline-flex;align-items:center;justify-content:center;width:2rem;height:2rem;border:none;border-radius:999px;background:transparent;color:var(--jv-color-foreground-muted);cursor:pointer}.jv-file-input-remove:hover,.jv-file-input-remove:focus-visible{background:var(--jv-color-surface-muted);color:var(--jv-color-foreground);outline:none}.jv-file-input-remove:disabled{opacity:.5;cursor:not-allowed}\n"], dependencies: [{ kind: "component", type: JvFormContainerComponent, selector: "jv-form-container", inputs: ["label", "hint", "error", "inputId", "hintId", "errorId", "required", "invalid"] }, { kind: "component", type: JvIconComponent, selector: "jv-icon", inputs: ["name", "size", "strokeWidth", "decorative", "ariaLabel"] }] });
20517
+ }
20518
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: JvFileInputComponent, decorators: [{
20519
+ type: Component,
20520
+ args: [{ selector: 'jv-file-input', standalone: true, imports: [JvFormContainerComponent, JvIconComponent], template: `
20521
+ <jv-form-container
20522
+ [label]="label()"
20523
+ [inputId]="inputId()"
20524
+ [required]="required()"
20525
+ [invalid]="invalid()"
20526
+ [hint]="hint()"
20527
+ [error]="error()"
20528
+ >
20529
+ <input
20530
+ #nativeInput
20531
+ class="jv-file-input-native"
20532
+ type="file"
20533
+ [id]="inputId()"
20534
+ [attr.accept]="accept() || null"
20535
+ [attr.multiple]="multiple() ? '' : null"
20536
+ [disabled]="disabled()"
20537
+ [attr.aria-describedby]="describedBy() || null"
20538
+ (change)="onNativeInputChange($any($event.target).files)"
20539
+ />
20540
+
20541
+ <div
20542
+ class="jv-file-input-dropzone"
20543
+ [class.jv-file-input-dropzone--disabled]="disabled()"
20544
+ [class.jv-file-input-dropzone--dragover]="dragActive()"
20545
+ [class.jv-file-input-dropzone--invalid]="invalid()"
20546
+ role="button"
20547
+ tabindex="0"
20548
+ [attr.aria-disabled]="disabled() ? 'true' : 'false'"
20549
+ [attr.aria-label]="dropzoneAriaLabel()"
20550
+ (click)="openFileDialog()"
20551
+ (keydown)="handleDropzoneKeydown($event)"
20552
+ (dragenter)="onDragEnter($event)"
20553
+ (dragover)="onDragOver($event)"
20554
+ (dragleave)="onDragLeave($event)"
20555
+ (drop)="onDrop($event)"
20556
+ >
20557
+ <jv-icon name="upload-cloud" [size]="24" />
20558
+ <div class="jv-file-input-copy">
20559
+ <strong>{{ translation.translate('fileInput.dropLabel') }}</strong>
20560
+ <span>{{ translation.translate('fileInput.helperText') }}</span>
20561
+ <span class="jv-file-input-meta">{{ selectionSummary() }}</span>
20562
+ </div>
20563
+ </div>
20564
+
20565
+ @if (rejectionMessage()) {
20566
+ <p class="jv-file-input-rejection" aria-live="polite">{{ rejectionMessage() }}</p>
20567
+ }
20568
+
20569
+ @if (currentFiles().length > 0) {
20570
+ <ul class="jv-file-input-list">
20571
+ @for (file of currentFiles(); track trackFile($index, file); let index = $index) {
20572
+ <li class="jv-file-input-item">
20573
+ <div class="jv-file-input-item-copy">
20574
+ <span class="jv-file-input-item-name">{{ file.name }}</span>
20575
+ <span class="jv-file-input-item-meta">{{ formatBytes(file.size) }}</span>
20576
+ </div>
20577
+ <button
20578
+ type="button"
20579
+ class="jv-file-input-remove"
20580
+ [disabled]="disabled()"
20581
+ [attr.aria-label]="translation.translate('fileInput.removeFile', { name: file.name })"
20582
+ (click)="removeFile(index)"
20583
+ >
20584
+ <jv-icon name="x" [size]="16" />
20585
+ </button>
20586
+ </li>
20587
+ }
20588
+ </ul>
20589
+ }
20590
+ </jv-form-container>
20591
+ `, styles: [".jv-file-input-native{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);border:0}.jv-file-input-dropzone{display:grid;grid-template-columns:auto 1fr;gap:var(--jv-spacing-md);align-items:center;padding:var(--jv-spacing-lg);border:1px dashed var(--jv-color-border);border-radius:var(--jv-radius-md);background:var(--jv-color-surface);color:var(--jv-color-foreground);cursor:pointer;transition:border-color .16s ease,background-color .16s ease,box-shadow .16s ease;outline:none}.jv-file-input-dropzone:hover,.jv-file-input-dropzone:focus-visible,.jv-file-input-dropzone--dragover{border-color:var(--jv-color-primary);background:var(--jv-color-surface-muted);box-shadow:inset 0 0 0 1px var(--jv-color-primary)}.jv-file-input-dropzone--invalid{border-color:var(--jv-color-danger)}.jv-file-input-dropzone--disabled{opacity:.65;cursor:not-allowed}.jv-file-input-copy{display:grid;gap:.25rem;min-width:0}.jv-file-input-copy span,.jv-file-input-meta,.jv-file-input-item-meta,.jv-file-input-rejection{color:var(--jv-color-foreground-muted)}.jv-file-input-rejection{margin:var(--jv-spacing-sm) 0 0;font-size:.875rem}.jv-file-input-list{list-style:none;padding:0;margin:var(--jv-spacing-md) 0 0;display:grid;gap:var(--jv-spacing-sm)}.jv-file-input-item{display:flex;align-items:center;justify-content:space-between;gap:var(--jv-spacing-md);padding:var(--jv-spacing-sm) var(--jv-spacing-md);border:1px solid var(--jv-color-border);border-radius:var(--jv-radius-md);background:var(--jv-color-surface)}.jv-file-input-item-copy{min-width:0;display:grid;gap:.125rem}.jv-file-input-item-name{font-weight:600;color:var(--jv-color-foreground);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.jv-file-input-remove{display:inline-flex;align-items:center;justify-content:center;width:2rem;height:2rem;border:none;border-radius:999px;background:transparent;color:var(--jv-color-foreground-muted);cursor:pointer}.jv-file-input-remove:hover,.jv-file-input-remove:focus-visible{background:var(--jv-color-surface-muted);color:var(--jv-color-foreground);outline:none}.jv-file-input-remove:disabled{opacity:.5;cursor:not-allowed}\n"] }]
20592
+ }], propDecorators: { nativeInputRef: [{ type: i0.ViewChild, args: ['nativeInput', { isSignal: true }] }], label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], hint: [{ type: i0.Input, args: [{ isSignal: true, alias: "hint", required: false }] }], error: [{ type: i0.Input, args: [{ isSignal: true, alias: "error", required: false }] }], describedBy: [{ type: i0.Input, args: [{ isSignal: true, alias: "describedBy", required: false }] }], inputId: [{ type: i0.Input, args: [{ isSignal: true, alias: "inputId", required: false }] }], accept: [{ type: i0.Input, args: [{ isSignal: true, alias: "accept", required: false }] }], multiple: [{ type: i0.Input, args: [{ isSignal: true, alias: "multiple", required: false }] }], maxFiles: [{ type: i0.Input, args: [{ isSignal: true, alias: "maxFiles", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], required: [{ type: i0.Input, args: [{ isSignal: true, alias: "required", required: false }] }], invalid: [{ type: i0.Input, args: [{ isSignal: true, alias: "invalid", required: false }] }], filesInput: [{ type: i0.Input, args: [{ isSignal: true, alias: "files", required: false }] }], filesChange: [{ type: i0.Output, args: ["filesChange"] }], filesRejected: [{ type: i0.Output, args: ["filesRejected"] }] } });
20593
+
20037
20594
  class JvListComponent {
20038
20595
  role = input('list', ...(ngDevMode ? [{ debugName: "role" }] : /* istanbul ignore next */ []));
20039
20596
  ariaRole = input(null, ...(ngDevMode ? [{ debugName: "ariaRole" }] : /* istanbul ignore next */ []));
@@ -20687,6 +21244,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImpo
20687
21244
 
20688
21245
  class JvDialogService {
20689
21246
  announcementService = inject(JvAnnouncementService);
21247
+ translation = inject(JvTranslationService);
20690
21248
  document = inject(DOCUMENT);
20691
21249
  activeDialogSignal = signal(null, ...(ngDevMode ? [{ debugName: "activeDialogSignal" }] : /* istanbul ignore next */ []));
20692
21250
  nextId = 0;
@@ -20696,15 +21254,16 @@ class JvDialogService {
20696
21254
  confirm(options) {
20697
21255
  const id = ++this.nextId;
20698
21256
  this.restoreTarget = this.document.activeElement instanceof HTMLElement ? this.document.activeElement : null;
21257
+ const title = options.title ?? this.translation.translate('dialog.confirmTitle');
20699
21258
  this.activeDialogSignal.set({
20700
21259
  id,
20701
- title: options.title,
21260
+ title,
20702
21261
  message: options.message,
20703
- confirmLabel: options.confirmLabel ?? 'Confirm',
20704
- cancelLabel: options.cancelLabel ?? 'Cancel',
21262
+ confirmLabel: options.confirmLabel ?? this.translation.translate('dialog.confirm'),
21263
+ cancelLabel: options.cancelLabel ?? this.translation.translate('dialog.cancel'),
20705
21264
  tone: options.tone ?? 'primary',
20706
21265
  });
20707
- this.announcementService.announce(`${options.title}. ${options.message}`, 'assertive');
21266
+ this.announcementService.announce(`${title}. ${options.message}`, 'assertive');
20708
21267
  return new Promise((resolve) => {
20709
21268
  this.resolver = resolve;
20710
21269
  });
@@ -20726,12 +21285,13 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImpo
20726
21285
 
20727
21286
  class JvConfirmDialogComponent {
20728
21287
  dialogService = inject(JvDialogService);
21288
+ translation = inject(JvTranslationService);
20729
21289
  activeDialog = this.dialogService.activeDialog;
20730
21290
  isOpen = computed(() => this.activeDialog() !== null, ...(ngDevMode ? [{ debugName: "isOpen" }] : /* istanbul ignore next */ []));
20731
- dialogTitle = computed(() => this.activeDialog()?.title ?? 'Confirm', ...(ngDevMode ? [{ debugName: "dialogTitle" }] : /* istanbul ignore next */ []));
21291
+ dialogTitle = computed(() => this.activeDialog()?.title ?? this.translation.translate('dialog.confirmTitle'), ...(ngDevMode ? [{ debugName: "dialogTitle" }] : /* istanbul ignore next */ []));
20732
21292
  dialogMessage = computed(() => this.activeDialog()?.message ?? '', ...(ngDevMode ? [{ debugName: "dialogMessage" }] : /* istanbul ignore next */ []));
20733
- cancelLabel = computed(() => this.activeDialog()?.cancelLabel ?? 'Cancel', ...(ngDevMode ? [{ debugName: "cancelLabel" }] : /* istanbul ignore next */ []));
20734
- confirmLabel = computed(() => this.activeDialog()?.confirmLabel ?? 'Confirm', ...(ngDevMode ? [{ debugName: "confirmLabel" }] : /* istanbul ignore next */ []));
21293
+ cancelLabel = computed(() => this.activeDialog()?.cancelLabel ?? this.translation.translate('dialog.cancel'), ...(ngDevMode ? [{ debugName: "cancelLabel" }] : /* istanbul ignore next */ []));
21294
+ confirmLabel = computed(() => this.activeDialog()?.confirmLabel ?? this.translation.translate('dialog.confirm'), ...(ngDevMode ? [{ debugName: "confirmLabel" }] : /* istanbul ignore next */ []));
20735
21295
  confirmVariant = computed(() => {
20736
21296
  const tone = this.activeDialog()?.tone ?? 'primary';
20737
21297
  return tone === 'info' ? 'primary' : tone;
@@ -20746,8 +21306,8 @@ class JvConfirmDialogComponent {
20746
21306
  <p>{{ dialogMessage() }}</p>
20747
21307
 
20748
21308
  <div dialog-actions class="actions">
20749
- <jv-button variant="ghost" (click)="dialogService.resolve(false)">{{ cancelLabel() }}</jv-button>
20750
21309
  <jv-button [variant]="confirmVariant()" (click)="dialogService.resolve(true)">{{ confirmLabel() }}</jv-button>
21310
+ <jv-button variant="ghost" (click)="dialogService.resolve(false)">{{ cancelLabel() }}</jv-button>
20751
21311
  </div>
20752
21312
  </jv-dialog>
20753
21313
  `, isInline: true, styles: [".actions{display:flex;justify-content:flex-end;gap:var(--jv-spacing-sm);flex-wrap:wrap}p{margin:0;color:var(--jv-color-foreground-muted)}\n"], dependencies: [{ kind: "component", type: JvButtonComponent, selector: "jv-button", inputs: ["type", "variant", "icon", "iconPosition", "loading", "disabled"] }, { kind: "component", type: JvDialogComponent, selector: "jv-dialog", inputs: ["open", "title"], outputs: ["closed"] }] });
@@ -20763,8 +21323,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImpo
20763
21323
  <p>{{ dialogMessage() }}</p>
20764
21324
 
20765
21325
  <div dialog-actions class="actions">
20766
- <jv-button variant="ghost" (click)="dialogService.resolve(false)">{{ cancelLabel() }}</jv-button>
20767
21326
  <jv-button [variant]="confirmVariant()" (click)="dialogService.resolve(true)">{{ confirmLabel() }}</jv-button>
21327
+ <jv-button variant="ghost" (click)="dialogService.resolve(false)">{{ cancelLabel() }}</jv-button>
20768
21328
  </div>
20769
21329
  </jv-dialog>
20770
21330
  `, styles: [".actions{display:flex;justify-content:flex-end;gap:var(--jv-spacing-sm);flex-wrap:wrap}p{margin:0;color:var(--jv-color-foreground-muted)}\n"] }]
@@ -21035,7 +21595,7 @@ class JvSidebarComponent {
21035
21595
  </div>
21036
21596
  </div>
21037
21597
  </aside>
21038
- `, isInline: true, styles: [".jv-sidebar-backdrop{display:none}.jv-sidebar{display:grid;grid-template-rows:auto 1fr auto;gap:var(--jv-spacing-lg);width:280px;min-width:280px;height:100vh;padding:var(--jv-spacing-lg);overflow-y:auto;border-right:1px solid var(--jv-color-border);background:var(--jv-color-surface);scrollbar-width:none;-ms-overflow-style:none}.jv-sidebar::-webkit-scrollbar{width:0;height:0;display:none}.jv-sidebar.is-collapsed{width:96px;min-width:96px;padding-inline:var(--jv-spacing-md)}.jv-sidebar-footer{display:grid;gap:var(--jv-spacing-md)}.jv-sidebar-mobile-extras{display:none}.jv-sidebar-brand,.jv-sidebar-link,.jv-sidebar-toggle{display:flex;align-items:center;gap:var(--jv-spacing-sm)}.brand-mark{display:inline-grid;place-items:center;width:2.5rem;height:2.5rem;border-radius:var(--jv-radius-md);background:color-mix(in srgb,var(--jv-color-primary) 16%,var(--jv-color-surface));color:var(--jv-color-primary)}.brand-copy{display:grid;gap:.1rem}.brand-copy span{color:var(--jv-color-foreground-muted);font-size:.875rem}.jv-sidebar-item{display:grid;gap:var(--jv-spacing-xs);list-style:none}.jv-sidebar-list{display:grid;gap:var(--jv-spacing-xs);margin:0;padding:0}.jv-sidebar-nav{display:grid;gap:var(--jv-spacing-xs);align-content:start}.jv-sidebar-link,.jv-sidebar-child-link,.jv-sidebar-toggle{width:100%;display:flex;align-items:center;justify-content:space-between;min-height:var(--jv-density-control-height);padding:0 var(--jv-spacing-md);border:1px solid transparent;border-radius:var(--jv-radius-md);background:transparent;color:var(--jv-color-foreground-muted);text-decoration:none;cursor:pointer}.jv-sidebar-link-main{display:inline-flex;align-items:center;gap:var(--jv-spacing-sm)}.jv-sidebar-link:hover,.jv-sidebar-child-link:hover,.jv-sidebar-toggle:hover{background:var(--jv-color-surface-muted);color:var(--jv-color-foreground)}.jv-sidebar-link.is-active,.jv-sidebar-child-link.is-active{border-color:color-mix(in srgb,var(--jv-color-primary) 25%,var(--jv-color-border));background:color-mix(in srgb,var(--jv-color-primary) 12%,var(--jv-color-surface));color:var(--jv-color-foreground)}.jv-sidebar-children{display:grid;gap:var(--jv-spacing-xs);margin-left:var(--jv-spacing-xl);padding-left:var(--jv-spacing-sm);border-left:1px solid var(--jv-color-border)}.jv-sidebar-child-link{display:flex;align-items:center;min-height:2.25rem;font-size:.9rem}.jv-sidebar-toggle{width:100%;background:var(--jv-color-surface-muted);cursor:pointer}@media(max-width:900px){.jv-sidebar-backdrop{display:block;position:fixed;inset:0;z-index:99;background:#0006}.jv-sidebar{position:fixed;top:0;left:0;z-index:100;width:280px;min-width:280px;height:100vh;border-right:1px solid var(--jv-color-border);border-bottom:none;box-shadow:var(--jv-shadow-lg);transform:translate(-100%);visibility:hidden;transition:transform .24s cubic-bezier(.4,0,.2,1),visibility .24s}.jv-sidebar.is-mobile-open{transform:translate(0);visibility:visible}.jv-sidebar.is-collapsed{width:280px;min-width:280px}.jv-sidebar-mobile-extras{display:flex;flex-direction:column;gap:var(--jv-spacing-md);padding-top:var(--jv-spacing-md);border-top:1px solid var(--jv-color-border)}.jv-sidebar-mobile-extras .theme-group{display:flex;flex-wrap:wrap;gap:var(--jv-spacing-xs)}.jv-sidebar-mobile-extras .theme-pill{display:inline-flex;align-items:center;gap:var(--jv-spacing-xs);min-height:var(--jv-density-control-height);padding:0 var(--jv-spacing-md);border:1px solid var(--jv-color-border);border-radius:999px;background:var(--jv-color-surface-muted);color:var(--jv-color-foreground);cursor:pointer;font:inherit}.jv-sidebar-mobile-extras .theme-pill.is-active{border-color:var(--jv-color-primary);background:color-mix(in srgb,var(--jv-color-primary) 14%,var(--jv-color-surface))}.jv-sidebar-mobile-extras .mobile-actions{display:flex;gap:var(--jv-spacing-sm)}.jv-sidebar-mobile-extras .icon-button{display:inline-grid;place-items:center;width:var(--jv-density-control-height);height:var(--jv-density-control-height);border:1px solid var(--jv-color-border);border-radius:999px;background:var(--jv-color-surface-muted);color:var(--jv-color-foreground);cursor:pointer}}\n"], dependencies: [{ kind: "component", type: JvIconComponent, selector: "jv-icon", inputs: ["name", "size", "strokeWidth", "decorative", "ariaLabel"] }, { kind: "directive", type: RouterLink, selector: "[routerLink]", inputs: ["target", "queryParams", "fragment", "queryParamsHandling", "state", "info", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "routerLink"] }] });
21598
+ `, isInline: true, styles: [".jv-sidebar-backdrop{display:none}.jv-sidebar{display:grid;grid-template-rows:auto 1fr auto;gap:var(--jv-spacing-lg);width:280px;min-width:280px;height:100vh;padding:var(--jv-spacing-lg);overflow-y:auto;border-right:1px solid var(--jv-color-border);background:var(--jv-color-surface);scrollbar-width:none;-ms-overflow-style:none}.jv-sidebar::-webkit-scrollbar{width:0;height:0;display:none}.jv-sidebar.is-collapsed{width:96px;min-width:96px;padding-inline:var(--jv-spacing-md)}.jv-sidebar-footer{display:grid;gap:var(--jv-spacing-md)}.jv-sidebar-mobile-extras{display:none}.jv-sidebar-brand,.jv-sidebar-link,.jv-sidebar-toggle{display:flex;align-items:center;gap:var(--jv-spacing-sm)}.brand-mark{display:inline-grid;place-items:center;width:var(--jv-density-control-height);height:var(--jv-density-control-height);border-radius:var(--jv-radius-md);background:color-mix(in srgb,var(--jv-color-primary) 16%,var(--jv-color-surface));color:var(--jv-color-primary);flex:0 0 auto}.brand-copy{display:grid;gap:.1rem}.brand-copy span{color:var(--jv-color-foreground-muted);font-size:.875rem}.jv-sidebar-item{display:grid;gap:var(--jv-spacing-xs);list-style:none}.jv-sidebar-list{display:grid;gap:var(--jv-spacing-xs);margin:0;padding:0}.jv-sidebar-nav{display:grid;gap:var(--jv-spacing-xs);align-content:start}.jv-sidebar-link,.jv-sidebar-child-link,.jv-sidebar-toggle{width:100%;display:flex;align-items:center;justify-content:space-between;min-height:var(--jv-density-control-height);padding:0 var(--jv-spacing-md);border:1px solid transparent;border-radius:var(--jv-radius-md);background:transparent;color:var(--jv-color-foreground-muted);text-decoration:none;cursor:pointer}.jv-sidebar-link-main{display:inline-flex;align-items:center;gap:var(--jv-spacing-sm)}.jv-sidebar-link:hover,.jv-sidebar-child-link:hover,.jv-sidebar-toggle:hover{background:var(--jv-color-surface-muted);color:var(--jv-color-foreground)}.jv-sidebar-link.is-active,.jv-sidebar-child-link.is-active{border-color:color-mix(in srgb,var(--jv-color-primary) 25%,var(--jv-color-border));background:color-mix(in srgb,var(--jv-color-primary) 12%,var(--jv-color-surface));color:var(--jv-color-foreground)}.jv-sidebar-children{display:grid;gap:var(--jv-spacing-xs);margin-left:var(--jv-spacing-xl);padding-left:var(--jv-spacing-sm);border-left:1px solid var(--jv-color-border)}.jv-sidebar-child-link{display:flex;align-items:center;min-height:calc(var(--jv-density-control-height) - .25rem);font-size:.9rem}.jv-sidebar-toggle{width:100%;background:var(--jv-color-surface-muted);cursor:pointer}@media(max-width:900px){.jv-sidebar-backdrop{display:block;position:fixed;inset:0;z-index:99;background:#0006}.jv-sidebar{position:fixed;top:0;left:0;z-index:100;width:280px;min-width:280px;height:100vh;border-right:1px solid var(--jv-color-border);border-bottom:none;box-shadow:var(--jv-shadow-lg);transform:translate(-100%);visibility:hidden;transition:transform .24s cubic-bezier(.4,0,.2,1),visibility .24s}.jv-sidebar.is-mobile-open{transform:translate(0);visibility:visible}.jv-sidebar.is-collapsed{width:280px;min-width:280px}.jv-sidebar-mobile-extras{display:flex;flex-direction:column;gap:var(--jv-spacing-md);padding-top:var(--jv-spacing-md);border-top:1px solid var(--jv-color-border)}.jv-sidebar-mobile-extras .theme-group{display:flex;flex-wrap:wrap;gap:var(--jv-spacing-xs)}.jv-sidebar-mobile-extras .theme-pill{display:inline-flex;align-items:center;gap:var(--jv-spacing-xs);min-height:var(--jv-density-control-height);padding:0 var(--jv-spacing-md);border:1px solid var(--jv-color-border);border-radius:999px;background:var(--jv-color-surface-muted);color:var(--jv-color-foreground);cursor:pointer;font:inherit}.jv-sidebar-mobile-extras .theme-pill.is-active{border-color:var(--jv-color-primary);background:color-mix(in srgb,var(--jv-color-primary) 14%,var(--jv-color-surface))}.jv-sidebar-mobile-extras .mobile-actions{display:flex;gap:var(--jv-spacing-sm)}.jv-sidebar-mobile-extras .icon-button{display:inline-grid;place-items:center;width:var(--jv-density-control-height);height:var(--jv-density-control-height);border:1px solid var(--jv-color-border);border-radius:999px;background:var(--jv-color-surface-muted);color:var(--jv-color-foreground);cursor:pointer}}\n"], dependencies: [{ kind: "component", type: JvIconComponent, selector: "jv-icon", inputs: ["name", "size", "strokeWidth", "decorative", "ariaLabel"] }, { kind: "directive", type: RouterLink, selector: "[routerLink]", inputs: ["target", "queryParams", "fragment", "queryParamsHandling", "state", "info", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "routerLink"] }] });
21039
21599
  }
21040
21600
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: JvSidebarComponent, decorators: [{
21041
21601
  type: Component,
@@ -21194,7 +21754,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImpo
21194
21754
  </div>
21195
21755
  </div>
21196
21756
  </aside>
21197
- `, styles: [".jv-sidebar-backdrop{display:none}.jv-sidebar{display:grid;grid-template-rows:auto 1fr auto;gap:var(--jv-spacing-lg);width:280px;min-width:280px;height:100vh;padding:var(--jv-spacing-lg);overflow-y:auto;border-right:1px solid var(--jv-color-border);background:var(--jv-color-surface);scrollbar-width:none;-ms-overflow-style:none}.jv-sidebar::-webkit-scrollbar{width:0;height:0;display:none}.jv-sidebar.is-collapsed{width:96px;min-width:96px;padding-inline:var(--jv-spacing-md)}.jv-sidebar-footer{display:grid;gap:var(--jv-spacing-md)}.jv-sidebar-mobile-extras{display:none}.jv-sidebar-brand,.jv-sidebar-link,.jv-sidebar-toggle{display:flex;align-items:center;gap:var(--jv-spacing-sm)}.brand-mark{display:inline-grid;place-items:center;width:2.5rem;height:2.5rem;border-radius:var(--jv-radius-md);background:color-mix(in srgb,var(--jv-color-primary) 16%,var(--jv-color-surface));color:var(--jv-color-primary)}.brand-copy{display:grid;gap:.1rem}.brand-copy span{color:var(--jv-color-foreground-muted);font-size:.875rem}.jv-sidebar-item{display:grid;gap:var(--jv-spacing-xs);list-style:none}.jv-sidebar-list{display:grid;gap:var(--jv-spacing-xs);margin:0;padding:0}.jv-sidebar-nav{display:grid;gap:var(--jv-spacing-xs);align-content:start}.jv-sidebar-link,.jv-sidebar-child-link,.jv-sidebar-toggle{width:100%;display:flex;align-items:center;justify-content:space-between;min-height:var(--jv-density-control-height);padding:0 var(--jv-spacing-md);border:1px solid transparent;border-radius:var(--jv-radius-md);background:transparent;color:var(--jv-color-foreground-muted);text-decoration:none;cursor:pointer}.jv-sidebar-link-main{display:inline-flex;align-items:center;gap:var(--jv-spacing-sm)}.jv-sidebar-link:hover,.jv-sidebar-child-link:hover,.jv-sidebar-toggle:hover{background:var(--jv-color-surface-muted);color:var(--jv-color-foreground)}.jv-sidebar-link.is-active,.jv-sidebar-child-link.is-active{border-color:color-mix(in srgb,var(--jv-color-primary) 25%,var(--jv-color-border));background:color-mix(in srgb,var(--jv-color-primary) 12%,var(--jv-color-surface));color:var(--jv-color-foreground)}.jv-sidebar-children{display:grid;gap:var(--jv-spacing-xs);margin-left:var(--jv-spacing-xl);padding-left:var(--jv-spacing-sm);border-left:1px solid var(--jv-color-border)}.jv-sidebar-child-link{display:flex;align-items:center;min-height:2.25rem;font-size:.9rem}.jv-sidebar-toggle{width:100%;background:var(--jv-color-surface-muted);cursor:pointer}@media(max-width:900px){.jv-sidebar-backdrop{display:block;position:fixed;inset:0;z-index:99;background:#0006}.jv-sidebar{position:fixed;top:0;left:0;z-index:100;width:280px;min-width:280px;height:100vh;border-right:1px solid var(--jv-color-border);border-bottom:none;box-shadow:var(--jv-shadow-lg);transform:translate(-100%);visibility:hidden;transition:transform .24s cubic-bezier(.4,0,.2,1),visibility .24s}.jv-sidebar.is-mobile-open{transform:translate(0);visibility:visible}.jv-sidebar.is-collapsed{width:280px;min-width:280px}.jv-sidebar-mobile-extras{display:flex;flex-direction:column;gap:var(--jv-spacing-md);padding-top:var(--jv-spacing-md);border-top:1px solid var(--jv-color-border)}.jv-sidebar-mobile-extras .theme-group{display:flex;flex-wrap:wrap;gap:var(--jv-spacing-xs)}.jv-sidebar-mobile-extras .theme-pill{display:inline-flex;align-items:center;gap:var(--jv-spacing-xs);min-height:var(--jv-density-control-height);padding:0 var(--jv-spacing-md);border:1px solid var(--jv-color-border);border-radius:999px;background:var(--jv-color-surface-muted);color:var(--jv-color-foreground);cursor:pointer;font:inherit}.jv-sidebar-mobile-extras .theme-pill.is-active{border-color:var(--jv-color-primary);background:color-mix(in srgb,var(--jv-color-primary) 14%,var(--jv-color-surface))}.jv-sidebar-mobile-extras .mobile-actions{display:flex;gap:var(--jv-spacing-sm)}.jv-sidebar-mobile-extras .icon-button{display:inline-grid;place-items:center;width:var(--jv-density-control-height);height:var(--jv-density-control-height);border:1px solid var(--jv-color-border);border-radius:999px;background:var(--jv-color-surface-muted);color:var(--jv-color-foreground);cursor:pointer}}\n"] }]
21757
+ `, styles: [".jv-sidebar-backdrop{display:none}.jv-sidebar{display:grid;grid-template-rows:auto 1fr auto;gap:var(--jv-spacing-lg);width:280px;min-width:280px;height:100vh;padding:var(--jv-spacing-lg);overflow-y:auto;border-right:1px solid var(--jv-color-border);background:var(--jv-color-surface);scrollbar-width:none;-ms-overflow-style:none}.jv-sidebar::-webkit-scrollbar{width:0;height:0;display:none}.jv-sidebar.is-collapsed{width:96px;min-width:96px;padding-inline:var(--jv-spacing-md)}.jv-sidebar-footer{display:grid;gap:var(--jv-spacing-md)}.jv-sidebar-mobile-extras{display:none}.jv-sidebar-brand,.jv-sidebar-link,.jv-sidebar-toggle{display:flex;align-items:center;gap:var(--jv-spacing-sm)}.brand-mark{display:inline-grid;place-items:center;width:var(--jv-density-control-height);height:var(--jv-density-control-height);border-radius:var(--jv-radius-md);background:color-mix(in srgb,var(--jv-color-primary) 16%,var(--jv-color-surface));color:var(--jv-color-primary);flex:0 0 auto}.brand-copy{display:grid;gap:.1rem}.brand-copy span{color:var(--jv-color-foreground-muted);font-size:.875rem}.jv-sidebar-item{display:grid;gap:var(--jv-spacing-xs);list-style:none}.jv-sidebar-list{display:grid;gap:var(--jv-spacing-xs);margin:0;padding:0}.jv-sidebar-nav{display:grid;gap:var(--jv-spacing-xs);align-content:start}.jv-sidebar-link,.jv-sidebar-child-link,.jv-sidebar-toggle{width:100%;display:flex;align-items:center;justify-content:space-between;min-height:var(--jv-density-control-height);padding:0 var(--jv-spacing-md);border:1px solid transparent;border-radius:var(--jv-radius-md);background:transparent;color:var(--jv-color-foreground-muted);text-decoration:none;cursor:pointer}.jv-sidebar-link-main{display:inline-flex;align-items:center;gap:var(--jv-spacing-sm)}.jv-sidebar-link:hover,.jv-sidebar-child-link:hover,.jv-sidebar-toggle:hover{background:var(--jv-color-surface-muted);color:var(--jv-color-foreground)}.jv-sidebar-link.is-active,.jv-sidebar-child-link.is-active{border-color:color-mix(in srgb,var(--jv-color-primary) 25%,var(--jv-color-border));background:color-mix(in srgb,var(--jv-color-primary) 12%,var(--jv-color-surface));color:var(--jv-color-foreground)}.jv-sidebar-children{display:grid;gap:var(--jv-spacing-xs);margin-left:var(--jv-spacing-xl);padding-left:var(--jv-spacing-sm);border-left:1px solid var(--jv-color-border)}.jv-sidebar-child-link{display:flex;align-items:center;min-height:calc(var(--jv-density-control-height) - .25rem);font-size:.9rem}.jv-sidebar-toggle{width:100%;background:var(--jv-color-surface-muted);cursor:pointer}@media(max-width:900px){.jv-sidebar-backdrop{display:block;position:fixed;inset:0;z-index:99;background:#0006}.jv-sidebar{position:fixed;top:0;left:0;z-index:100;width:280px;min-width:280px;height:100vh;border-right:1px solid var(--jv-color-border);border-bottom:none;box-shadow:var(--jv-shadow-lg);transform:translate(-100%);visibility:hidden;transition:transform .24s cubic-bezier(.4,0,.2,1),visibility .24s}.jv-sidebar.is-mobile-open{transform:translate(0);visibility:visible}.jv-sidebar.is-collapsed{width:280px;min-width:280px}.jv-sidebar-mobile-extras{display:flex;flex-direction:column;gap:var(--jv-spacing-md);padding-top:var(--jv-spacing-md);border-top:1px solid var(--jv-color-border)}.jv-sidebar-mobile-extras .theme-group{display:flex;flex-wrap:wrap;gap:var(--jv-spacing-xs)}.jv-sidebar-mobile-extras .theme-pill{display:inline-flex;align-items:center;gap:var(--jv-spacing-xs);min-height:var(--jv-density-control-height);padding:0 var(--jv-spacing-md);border:1px solid var(--jv-color-border);border-radius:999px;background:var(--jv-color-surface-muted);color:var(--jv-color-foreground);cursor:pointer;font:inherit}.jv-sidebar-mobile-extras .theme-pill.is-active{border-color:var(--jv-color-primary);background:color-mix(in srgb,var(--jv-color-primary) 14%,var(--jv-color-surface))}.jv-sidebar-mobile-extras .mobile-actions{display:flex;gap:var(--jv-spacing-sm)}.jv-sidebar-mobile-extras .icon-button{display:inline-grid;place-items:center;width:var(--jv-density-control-height);height:var(--jv-density-control-height);border:1px solid var(--jv-color-border);border-radius:999px;background:var(--jv-color-surface-muted);color:var(--jv-color-foreground);cursor:pointer}}\n"] }]
21198
21758
  }], propDecorators: { title: [{ type: i0.Input, args: [{ isSignal: true, alias: "title", required: true }] }], subtitle: [{ type: i0.Input, args: [{ isSignal: true, alias: "subtitle", required: false }] }], brandIcon: [{ type: i0.Input, args: [{ isSignal: true, alias: "brandIcon", required: false }] }], items: [{ type: i0.Input, args: [{ isSignal: true, alias: "items", required: false }] }], collapsed: [{ type: i0.Input, args: [{ isSignal: true, alias: "collapsed", required: false }] }], mobileOpen: [{ type: i0.Input, args: [{ isSignal: true, alias: "mobileOpen", required: false }] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabel", required: false }] }], currentTheme: [{ type: i0.Input, args: [{ isSignal: true, alias: "currentTheme", required: false }] }], themeOptions: [{ type: i0.Input, args: [{ isSignal: true, alias: "themeOptions", required: false }] }], topbarActions: [{ type: i0.Input, args: [{ isSignal: true, alias: "topbarActions", required: false }] }], showThemeSelector: [{ type: i0.Input, args: [{ isSignal: true, alias: "showThemeSelector", required: false }] }], collapseToggle: [{ type: i0.Output, args: ["collapseToggle"] }], mobileClose: [{ type: i0.Output, args: ["mobileClose"] }], themeChange: [{ type: i0.Output, args: ["themeChange"] }], actionClick: [{ type: i0.Output, args: ["actionClick"] }] } });
21199
21759
 
21200
21760
  class JvTopbarComponent {
@@ -22036,6 +22596,7 @@ const JV_GRID_DEFAULT_OPTIONS = {
22036
22596
  let gridIdSequence = 0;
22037
22597
  class JvGridComponent {
22038
22598
  gridId = `jv-grid-${++gridIdSequence}`;
22599
+ formatService = inject(JvFormatService);
22039
22600
  translationService = inject(JvTranslationService);
22040
22601
  data = input([], ...(ngDevMode ? [{ debugName: "data" }] : /* istanbul ignore next */ []));
22041
22602
  columns = input([], ...(ngDevMode ? [{ debugName: "columns" }] : /* istanbul ignore next */ []));
@@ -22104,7 +22665,7 @@ class JvGridComponent {
22104
22665
  const value = this.getRawCellValue(row, col);
22105
22666
  if (value == null)
22106
22667
  return false;
22107
- return String(value).toLowerCase().includes(term);
22668
+ return this.getSearchValue(value, col).includes(term);
22108
22669
  });
22109
22670
  });
22110
22671
  }
@@ -22118,7 +22679,7 @@ class JvGridComponent {
22118
22679
  const col = this.columns().find(c => this.colKey(c) === key);
22119
22680
  if (!col || col.filterable === false)
22120
22681
  return true;
22121
- const value = String(this.getRawCellValue(row, col) ?? '').toLowerCase();
22682
+ const value = this.getSearchValue(this.getRawCellValue(row, col), col);
22122
22683
  const fv = filter.value.toLowerCase();
22123
22684
  switch (filter.operator) {
22124
22685
  case 'contains': return value.includes(fv);
@@ -22140,8 +22701,9 @@ class JvGridComponent {
22140
22701
  if (!columnKey || !direction)
22141
22702
  return items;
22142
22703
  return [...items].sort((a, b) => {
22143
- const va = this.getRawCellValue(a, this.findColumn(columnKey));
22144
- const vb = this.getRawCellValue(b, this.findColumn(columnKey));
22704
+ const col = this.findColumn(columnKey);
22705
+ const va = this.getComparableValue(this.getRawCellValue(a, col), col);
22706
+ const vb = this.getComparableValue(this.getRawCellValue(b, col), col);
22145
22707
  if (va == null)
22146
22708
  return 1;
22147
22709
  if (vb == null)
@@ -22245,14 +22807,56 @@ class JvGridComponent {
22245
22807
  const val = this.getCellValue(row, col);
22246
22808
  return val == null ? '' : String(val);
22247
22809
  }
22248
- formatValue(value, col) {
22810
+ formatValue(value, col, _row) {
22811
+ if (value == null)
22812
+ return '';
22813
+ const type = this.resolveColumnType(col);
22814
+ if (type === 'text' || type === 'boolean') {
22815
+ return String(value);
22816
+ }
22817
+ return this.formatService.format(type, value, this.getFormatOverrides(col));
22818
+ }
22819
+ getComparableValue(value, col) {
22820
+ const type = this.resolveColumnType(col);
22821
+ if (value == null || value === '')
22822
+ return null;
22823
+ if (type === 'text') {
22824
+ return String(value).toLowerCase();
22825
+ }
22826
+ if (type === 'boolean') {
22827
+ return Boolean(value);
22828
+ }
22829
+ return this.formatService.toComparable(type, value);
22830
+ }
22831
+ getSearchValue(value, col) {
22832
+ const type = this.resolveColumnType(col);
22833
+ if (type === 'text' || type === 'boolean') {
22834
+ return String(value ?? '').toLowerCase();
22835
+ }
22836
+ return this.formatService.toSearchValue(type, value);
22837
+ }
22838
+ getExportValue(value, col) {
22249
22839
  if (value == null)
22250
22840
  return '';
22251
- if (col.type === 'date' && value instanceof Date) {
22252
- return value.toLocaleDateString();
22841
+ const type = this.resolveColumnType(col);
22842
+ if (type === 'date' || type === 'time' || type === 'datetime') {
22843
+ const date = this.formatService.toDate(value);
22844
+ if (!date)
22845
+ return String(value);
22846
+ if (type === 'date')
22847
+ return date.toISOString().slice(0, 10);
22848
+ if (type === 'time')
22849
+ return date.toISOString().slice(11, 19);
22850
+ return date.toISOString();
22253
22851
  }
22254
22852
  return String(value);
22255
22853
  }
22854
+ resolveColumnType(col) {
22855
+ return col.type ?? 'text';
22856
+ }
22857
+ getFormatOverrides(col) {
22858
+ return col.formatOptions;
22859
+ }
22256
22860
  getAriaSort(col) {
22257
22861
  if (col.sortable === false || !this.resolvedOptions().sortable)
22258
22862
  return null;
@@ -22500,14 +23104,14 @@ class JvGridComponent {
22500
23104
  }
22501
23105
  // Export
22502
23106
  exportCsv() {
22503
- const cols = this.visibleColumns().map(c => ({ key: this.colKey(c), header: c.header }));
23107
+ const cols = this.visibleColumns().map(c => ({ key: this.colKey(c), header: c.header, column: c }));
22504
23108
  const allData = this.effectiveOptions().serverSide ? this.data() : this.sortedData();
22505
23109
  const headerRow = cols.map(c => `"${c.header}"`).join(',');
22506
23110
  const dataRows = allData.map(row => cols.map(col => {
22507
23111
  const val = row[col.key];
22508
23112
  if (val == null)
22509
23113
  return '';
22510
- return `"${String(val).replace(/"/g, '""')}"`;
23114
+ return `"${this.getExportValue(val, col.column).replace(/"/g, '""')}"`;
22511
23115
  }).join(','));
22512
23116
  const csv = [headerRow, ...dataRows].join('\r\n');
22513
23117
  const blob = new Blob([csv], { type: 'text/csv;charset=utf-8;' });
@@ -22519,13 +23123,13 @@ class JvGridComponent {
22519
23123
  URL.revokeObjectURL(url);
22520
23124
  }
22521
23125
  exportExcel() {
22522
- const cols = this.visibleColumns().map(c => ({ key: this.colKey(c), header: c.header }));
23126
+ const cols = this.visibleColumns().map(c => ({ key: this.colKey(c), header: c.header, column: c }));
22523
23127
  const allData = this.effectiveOptions().serverSide ? this.data() : this.sortedData();
22524
23128
  let html = '<table>';
22525
23129
  html += '<thead><tr>' + cols.map(c => `<th>${c.header}</th>`).join('') + '</tr></thead>';
22526
23130
  html += '<tbody>';
22527
23131
  for (const row of allData) {
22528
- html += '<tr>' + cols.map(col => `<td>${row[col.key] ?? ''}</td>`).join('') + '</tr>';
23132
+ html += '<tr>' + cols.map(col => `<td>${this.getExportValue(row[col.key], col.column)}</td>`).join('') + '</tr>';
22529
23133
  }
22530
23134
  html += '</tbody></table>';
22531
23135
  const blob = new Blob([html], { type: 'application/vnd.ms-excel' });
@@ -22806,7 +23410,7 @@ class JvGridComponent {
22806
23410
  } @else if (col.format) {
22807
23411
  {{ col.format(value, row) }}
22808
23412
  } @else {
22809
- {{ formatValue(value, col) }}
23413
+ {{ formatValue(value, col, row) }}
22810
23414
  }
22811
23415
  }
22812
23416
  </td>
@@ -23157,7 +23761,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImpo
23157
23761
  } @else if (col.format) {
23158
23762
  {{ col.format(value, row) }}
23159
23763
  } @else {
23160
- {{ formatValue(value, col) }}
23764
+ {{ formatValue(value, col, row) }}
23161
23765
  }
23162
23766
  }
23163
23767
  </td>
@@ -23406,5 +24010,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImpo
23406
24010
  * Generated bundle index. Do not edit.
23407
24011
  */
23408
24012
 
23409
- export { EN, ES, JV_DEFAULT_LOCALE, JV_FALLBACK_ICON_NAME, JV_GRID_DEFAULT_OPTIONS, JV_LOCALE_DICTIONARIES, JV_LUCIDE_ICON_REGISTRY, JV_UI_CONFIG, JV_UI_CONFIG_DEFAULTS, JV_UI_DEFAULT_CONFIG, JvAccordionComponent, JvAccordionPanelDirective, JvAlertComponent, JvAnnouncementService, JvBadgeComponent, JvBreadcrumbComponent, JvButtonComponent, JvButtonGroupComponent, JvCardComponent, JvChangePasswordPageComponent, JvCheckboxComponent, JvCollapsiblePanelComponent, JvConfirmDialogComponent, JvDashboardShellComponent, JvDatePickerComponent, JvDialogComponent, JvDialogService, JvDividerComponent, JvForgotPasswordPageComponent, JvFormContainerComponent, JvGridComponent, JvIconButtonComponent, JvIconComponent, JvInputComponent, JvListComponent, JvListItemComponent, JvLoaderComponent, JvLoaderService, JvLoginPageComponent, JvModalSelectorComponent, JvPageComponent, JvPaginationComponent, JvRadioComponent, JvSectionComponent, JvSelectComponent, JvSidebarComponent, JvSwitchComponent, JvTabPanelDirective, JvTabsComponent, JvTextareaComponent, JvThemeService, JvToastComponent, JvToastService, JvTooltipDirective, JvTopbarComponent, JvTranslationService, provideJvUi };
24013
+ export { EN, ES, JV_DEFAULT_LOCALE, JV_FALLBACK_ICON_NAME, JV_GRID_DEFAULT_OPTIONS, JV_LOCALE_DICTIONARIES, JV_LUCIDE_ICON_REGISTRY, JV_UI_CONFIG, JV_UI_CONFIG_DEFAULTS, JV_UI_DEFAULT_CONFIG, JvAccordionComponent, JvAccordionPanelDirective, JvAlertComponent, JvAnnouncementService, JvBadgeComponent, JvBreadcrumbComponent, JvButtonComponent, JvButtonGroupComponent, JvCardComponent, JvChangePasswordPageComponent, JvCheckboxComponent, JvCodeComponent, JvCollapsiblePanelComponent, JvConfirmDialogComponent, JvDashboardShellComponent, JvDatePickerComponent, JvDialogComponent, JvDialogService, JvDividerComponent, JvFileInputComponent, JvForgotPasswordPageComponent, JvFormContainerComponent, JvFormatService, JvGridComponent, JvIconButtonComponent, JvIconComponent, JvInputComponent, JvListComponent, JvListItemComponent, JvLoaderComponent, JvLoaderService, JvLoginPageComponent, JvModalSelectorComponent, JvPageComponent, JvPaginationComponent, JvRadioComponent, JvSectionComponent, JvSelectComponent, JvSidebarComponent, JvSwitchComponent, JvTabPanelDirective, JvTabsComponent, JvTextareaComponent, JvThemeService, JvToastComponent, JvToastService, JvTooltipDirective, JvTopbarComponent, JvTranslationService, provideJvUi };
23410
24014
  //# sourceMappingURL=devjuliovilla-jv-ui.mjs.map