@devjuliovilla/jv-ui 1.9.0 → 1.11.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.
@@ -1,5 +1,5 @@
1
1
  import * as i0 from '@angular/core';
2
- import { input, computed, Component, forwardRef, inject, output, Directive, Injectable, signal, InjectionToken, makeEnvironmentProviders, ENVIRONMENT_INITIALIZER, effect, contentChildren, HostListener, viewChild, viewChildren, afterNextRender, Renderer2, ElementRef } from '@angular/core';
2
+ import { input, computed, Component, forwardRef, inject, output, Directive, Injectable, signal, InjectionToken, makeEnvironmentProviders, ENVIRONMENT_INITIALIZER, effect, contentChildren, HostListener, viewChild, viewChildren, numberAttribute, afterNextRender, Renderer2, ElementRef } from '@angular/core';
3
3
  import * as i1 from '@angular/forms';
4
4
  import { NG_VALUE_ACCESSOR, FormBuilder, Validators, ReactiveFormsModule, FormsModule } from '@angular/forms';
5
5
  import { CommonModule, DOCUMENT, NgTemplateOutlet } from '@angular/common';
@@ -18717,9 +18717,136 @@ 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',
18732
+ tabsTransition: 'none',
18721
18733
  };
18722
18734
 
18735
+ class JvFormatService {
18736
+ config = inject(JvUiConfigService);
18737
+ formatLocale = signal(this.config.snapshot.formatLocale || this.config.snapshot.language || 'es-MX', ...(ngDevMode ? [{ debugName: "formatLocale" }] : /* istanbul ignore next */ []));
18738
+ formats = signal(this.resolveFormats(this.config.snapshot.formats), ...(ngDevMode ? [{ debugName: "formats" }] : /* istanbul ignore next */ []));
18739
+ firstDayOfWeek = computed(() => this.resolveFirstDayOfWeek(this.formatLocale()), ...(ngDevMode ? [{ debugName: "firstDayOfWeek" }] : /* istanbul ignore next */ []));
18740
+ initialize() {
18741
+ this.formatLocale.set(this.config.snapshot.formatLocale || this.config.snapshot.language || 'es-MX');
18742
+ this.formats.set(this.resolveFormats(this.config.snapshot.formats));
18743
+ }
18744
+ setFormatLocale(locale) {
18745
+ this.formatLocale.set(locale);
18746
+ }
18747
+ setFormats(formats) {
18748
+ this.formats.update((current) => this.resolveFormats({ ...current, ...formats }));
18749
+ }
18750
+ format(kind, value, overrides) {
18751
+ if (value == null || value === '')
18752
+ return '';
18753
+ if (kind === 'date' || kind === 'time' || kind === 'datetime') {
18754
+ const date = this.toDate(value);
18755
+ if (!date)
18756
+ return String(value);
18757
+ return new Intl.DateTimeFormat(this.formatLocale(), this.resolveDateOptions(kind, overrides)).format(date);
18758
+ }
18759
+ const number = this.toNumber(value);
18760
+ if (number === null)
18761
+ return String(value);
18762
+ return new Intl.NumberFormat(this.formatLocale(), this.resolveNumberOptions(kind, overrides)).format(number);
18763
+ }
18764
+ toComparable(kind, value) {
18765
+ if (value == null || value === '')
18766
+ return null;
18767
+ if (kind === 'date' || kind === 'time' || kind === 'datetime') {
18768
+ const date = this.toDate(value);
18769
+ return date ? date.getTime() : String(value);
18770
+ }
18771
+ if (kind === 'integer' || kind === 'number' || kind === 'currency' || kind === 'percent') {
18772
+ const number = this.toNumber(value);
18773
+ return number ?? String(value);
18774
+ }
18775
+ return String(value);
18776
+ }
18777
+ toDate(value) {
18778
+ if (value instanceof Date) {
18779
+ return Number.isNaN(value.getTime()) ? null : value;
18780
+ }
18781
+ if (typeof value === 'number' || typeof value === 'string') {
18782
+ const date = new Date(value);
18783
+ return Number.isNaN(date.getTime()) ? null : date;
18784
+ }
18785
+ return null;
18786
+ }
18787
+ toNumber(value) {
18788
+ if (typeof value === 'number') {
18789
+ return Number.isFinite(value) ? value : null;
18790
+ }
18791
+ if (typeof value === 'string') {
18792
+ const normalized = value.trim().replace(/[%,$\s]/g, '').replace(/,/g, '');
18793
+ if (!normalized)
18794
+ return null;
18795
+ const parsed = Number(normalized);
18796
+ return Number.isFinite(parsed) ? parsed : null;
18797
+ }
18798
+ return null;
18799
+ }
18800
+ toSearchValue(kind, value) {
18801
+ if (value == null)
18802
+ return '';
18803
+ if (kind === 'date' || kind === 'time' || kind === 'datetime') {
18804
+ const date = this.toDate(value);
18805
+ if (!date)
18806
+ return String(value).toLowerCase();
18807
+ return date.toISOString().toLowerCase();
18808
+ }
18809
+ return String(value).toLowerCase();
18810
+ }
18811
+ resolveFormats(formats) {
18812
+ const defaults = this.config.snapshot.formats;
18813
+ return {
18814
+ date: { ...defaults.date, ...formats?.date },
18815
+ time: { ...defaults.time, ...formats?.time },
18816
+ datetime: { ...defaults.datetime, ...formats?.datetime },
18817
+ integer: { ...defaults.integer, ...formats?.integer },
18818
+ number: { ...defaults.number, ...formats?.number },
18819
+ currency: { ...defaults.currency, ...formats?.currency },
18820
+ percent: { ...defaults.percent, ...formats?.percent },
18821
+ };
18822
+ }
18823
+ resolveDateOptions(kind, overrides) {
18824
+ return { ...this.formats()[kind], ...overrides };
18825
+ }
18826
+ resolveNumberOptions(kind, overrides) {
18827
+ return { ...this.formats()[kind], ...overrides };
18828
+ }
18829
+ resolveFirstDayOfWeek(locale) {
18830
+ try {
18831
+ const intlLocale = new Intl.Locale(locale);
18832
+ const firstDay = intlLocale.weekInfo?.firstDay;
18833
+ if (typeof firstDay === 'number') {
18834
+ return firstDay % 7;
18835
+ }
18836
+ }
18837
+ catch {
18838
+ // Ignore unsupported Intl.Locale implementations.
18839
+ }
18840
+ return 0;
18841
+ }
18842
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: JvFormatService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
18843
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: JvFormatService, providedIn: 'root' });
18844
+ }
18845
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: JvFormatService, decorators: [{
18846
+ type: Injectable,
18847
+ args: [{ providedIn: 'root' }]
18848
+ }] });
18849
+
18723
18850
  class JvThemeService {
18724
18851
  document = inject(DOCUMENT);
18725
18852
  config = inject(JvUiConfigService);
@@ -18810,6 +18937,7 @@ function provideJvUi(config = {}) {
18810
18937
  useValue: () => {
18811
18938
  inject(JvThemeService).initialize();
18812
18939
  inject(JvTranslationService).initialize();
18940
+ inject(JvFormatService).initialize();
18813
18941
  },
18814
18942
  },
18815
18943
  ]);
@@ -18817,9 +18945,19 @@ function provideJvUi(config = {}) {
18817
18945
 
18818
18946
  class JvUiConfigService {
18819
18947
  defaults = inject(JV_UI_CONFIG_DEFAULTS);
18820
- configLayers = inject(JV_UI_CONFIG, { optional: true }) ?? [];
18948
+ configLayers = (inject(JV_UI_CONFIG, { optional: true }) ?? []);
18821
18949
  get snapshot() {
18822
- return this.configLayers.reduce((merged, layer) => ({ ...merged, ...layer }), { ...this.defaults });
18950
+ return this.configLayers.reduce((merged, layer) => ({
18951
+ ...merged,
18952
+ ...layer,
18953
+ formats: {
18954
+ ...merged.formats,
18955
+ ...(layer.formats ?? {}),
18956
+ },
18957
+ }), {
18958
+ ...this.defaults,
18959
+ formats: { ...this.defaults.formats },
18960
+ });
18823
18961
  }
18824
18962
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: JvUiConfigService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
18825
18963
  static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: JvUiConfigService, providedIn: 'root' });
@@ -18878,11 +19016,23 @@ const ES = {
18878
19016
  'modalSelector.results': '{count} resultados',
18879
19017
  'modalSelector.selected': '{label} seleccionado',
18880
19018
  'modalSelector.selectionCleared': 'Seleccion limpiada',
19019
+ 'fileInput.dropLabel': 'Arrastre archivos aqui o haga clic para seleccionarlos',
19020
+ 'fileInput.helperText': 'Compatible con seleccion multiple y arrastrar y soltar',
19021
+ 'fileInput.selectionSummary': '{count} de {max} archivos seleccionados',
19022
+ 'fileInput.invalidFiles': '{count} archivos no coinciden con el tipo permitido',
19023
+ 'fileInput.tooManyFiles': 'Solo se permiten hasta {max} archivos',
19024
+ 'fileInput.removeFile': 'Quitar archivo {name}',
18881
19025
  'dialog.confirmTitle': 'Confirmar',
18882
19026
  'dialog.confirm': 'Aceptar',
18883
19027
  'dialog.cancel': 'Cancelar',
18884
19028
  'common.expanded': 'Expandido',
18885
19029
  'common.collapsed': 'Colapsado',
19030
+ 'slider.prevImage': 'Imagen anterior',
19031
+ 'slider.nextImage': 'Imagen siguiente',
19032
+ 'slider.thumbnailList': 'Miniaturas de imagenes',
19033
+ 'gallery.close': 'Cerrar',
19034
+ 'gallery.prevImage': 'Imagen anterior',
19035
+ 'gallery.nextImage': 'Imagen siguiente',
18886
19036
  };
18887
19037
 
18888
19038
  const EN = {
@@ -18934,11 +19084,23 @@ const EN = {
18934
19084
  'modalSelector.results': '{count} results',
18935
19085
  'modalSelector.selected': '{label} selected',
18936
19086
  'modalSelector.selectionCleared': 'Selection cleared',
19087
+ 'fileInput.dropLabel': 'Drag files here or click to choose them',
19088
+ 'fileInput.helperText': 'Supports multiple selection and drag and drop',
19089
+ 'fileInput.selectionSummary': '{count} of {max} files selected',
19090
+ 'fileInput.invalidFiles': '{count} files do not match the allowed type',
19091
+ 'fileInput.tooManyFiles': 'Only up to {max} files are allowed',
19092
+ 'fileInput.removeFile': 'Remove file {name}',
18937
19093
  'dialog.confirmTitle': 'Confirm',
18938
19094
  'dialog.confirm': 'Confirm',
18939
19095
  'dialog.cancel': 'Cancel',
18940
19096
  'common.expanded': 'Expanded',
18941
19097
  'common.collapsed': 'Collapsed',
19098
+ 'slider.prevImage': 'Previous image',
19099
+ 'slider.nextImage': 'Next image',
19100
+ 'slider.thumbnailList': 'Image thumbnails',
19101
+ 'gallery.close': 'Close',
19102
+ 'gallery.prevImage': 'Previous image',
19103
+ 'gallery.nextImage': 'Next image',
18942
19104
  };
18943
19105
 
18944
19106
  const JV_LOCALE_DICTIONARIES = {
@@ -18949,14 +19111,17 @@ const JV_DEFAULT_LOCALE = 'es';
18949
19111
 
18950
19112
  class JvTranslationService {
18951
19113
  config = inject(JvUiConfigService);
18952
- locale = signal(this.config.snapshot.locale, ...(ngDevMode ? [{ debugName: "locale" }] : /* istanbul ignore next */ []));
19114
+ locale = signal(this.resolveLocale(this.config.snapshot.messagesLocale ?? this.config.snapshot.locale), ...(ngDevMode ? [{ debugName: "locale" }] : /* istanbul ignore next */ []));
18953
19115
  initialize() {
18954
- const locale = this.resolveLocale(this.config.snapshot.locale);
19116
+ const locale = this.resolveLocale(this.config.snapshot.messagesLocale ?? this.config.snapshot.locale);
18955
19117
  this.locale.set(locale);
18956
19118
  }
18957
19119
  setLocale(locale) {
18958
19120
  this.locale.set(locale);
18959
19121
  }
19122
+ setMessagesLocale(locale) {
19123
+ this.setLocale(locale);
19124
+ }
18960
19125
  translate(key, params) {
18961
19126
  const dict = JV_LOCALE_DICTIONARIES[this.locale()] ?? JV_LOCALE_DICTIONARIES[JV_DEFAULT_LOCALE];
18962
19127
  let value = dict[key];
@@ -18971,6 +19136,8 @@ class JvTranslationService {
18971
19136
  return value;
18972
19137
  }
18973
19138
  resolveLocale(locale) {
19139
+ if (!locale)
19140
+ return JV_DEFAULT_LOCALE;
18974
19141
  if (locale === 'es' || locale === 'en')
18975
19142
  return locale;
18976
19143
  return locale.startsWith('es') ? 'es' : 'en';
@@ -19749,6 +19916,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImpo
19749
19916
  let datePickerIdSequence = 0;
19750
19917
  class JvDatePickerComponent {
19751
19918
  translation = inject(JvTranslationService);
19919
+ formatService = inject(JvFormatService);
19752
19920
  announcementService = inject(JvAnnouncementService);
19753
19921
  instanceId = ++datePickerIdSequence;
19754
19922
  calendarRootRef = viewChild('calendarRoot', ...(ngDevMode ? [{ debugName: "calendarRootRef" }] : /* istanbul ignore next */ []));
@@ -19781,14 +19949,10 @@ class JvDatePickerComponent {
19781
19949
  if (!value) {
19782
19950
  return this.placeholder() || this.translation.translate('datePicker.placeholder');
19783
19951
  }
19784
- return new Intl.DateTimeFormat(this.translation.locale(), {
19785
- day: '2-digit',
19786
- month: '2-digit',
19787
- year: 'numeric',
19788
- }).format(value);
19952
+ return this.formatService.format('date', value);
19789
19953
  }, ...(ngDevMode ? [{ debugName: "displayValue" }] : /* istanbul ignore next */ []));
19790
19954
  monthOptions = computed(() => {
19791
- const formatter = new Intl.DateTimeFormat(this.translation.locale(), { month: 'long' });
19955
+ const formatter = new Intl.DateTimeFormat(this.formatService.formatLocale(), { month: 'long' });
19792
19956
  return Array.from({ length: 12 }, (_, month) => ({
19793
19957
  label: capitalize(formatter.format(new Date(2026, month, 1))),
19794
19958
  value: String(month),
@@ -19802,7 +19966,7 @@ class JvDatePickerComponent {
19802
19966
  }, ...(ngDevMode ? [{ debugName: "yearOptions" }] : /* istanbul ignore next */ []));
19803
19967
  weekdayLabels = computed(() => {
19804
19968
  const startDay = this.firstDayOfWeek();
19805
- const formatter = new Intl.DateTimeFormat(this.translation.locale(), { weekday: 'short' });
19969
+ const formatter = new Intl.DateTimeFormat(this.formatService.formatLocale(), { weekday: 'short' });
19806
19970
  return Array.from({ length: 7 }, (_, index) => capitalize(formatter.format(addDays(new Date(2026, 5, 7), startDay + index))));
19807
19971
  }, ...(ngDevMode ? [{ debugName: "weekdayLabels" }] : /* istanbul ignore next */ []));
19808
19972
  calendarCells = computed(() => {
@@ -19944,10 +20108,10 @@ class JvDatePickerComponent {
19944
20108
  root?.querySelector(`[data-jv-date="${this.isoDate(date)}"]`)?.focus();
19945
20109
  }
19946
20110
  firstDayOfWeek() {
19947
- return this.translation.locale() === 'es' ? 1 : 0;
20111
+ return this.formatService.firstDayOfWeek();
19948
20112
  }
19949
20113
  displayDateForAnnouncement(date) {
19950
- return new Intl.DateTimeFormat(this.translation.locale(), {
20114
+ return new Intl.DateTimeFormat(this.formatService.formatLocale(), {
19951
20115
  weekday: 'long',
19952
20116
  day: 'numeric',
19953
20117
  month: 'long',
@@ -19961,83 +20125,83 @@ class JvDatePickerComponent {
19961
20125
  useExisting: forwardRef(() => JvDatePickerComponent),
19962
20126
  multi: true,
19963
20127
  },
19964
- ], viewQueries: [{ propertyName: "calendarRootRef", first: true, predicate: ["calendarRoot"], descendants: true, isSignal: true }, { propertyName: "triggerButtonRef", first: true, predicate: ["triggerButton"], descendants: true, isSignal: true }], ngImport: i0, template: `
19965
- <jv-form-container
19966
- [label]="label()"
19967
- [inputId]="inputId()"
19968
- [required]="required()"
19969
- [invalid]="invalid()"
19970
- [hint]="hint()"
19971
- [error]="error()"
19972
- >
19973
- <button
19974
- #triggerButton
19975
- type="button"
19976
- class="jv-date-picker-trigger"
19977
- [class.is-invalid]="invalid()"
19978
- [id]="inputId()"
19979
- [disabled]="disabled"
19980
- [attr.aria-haspopup]="'dialog'"
19981
- [attr.aria-expanded]="dialogOpen() ? 'true' : 'false'"
19982
- [attr.aria-controls]="dialogId"
19983
- [attr.aria-invalid]="invalid() ? 'true' : null"
19984
- [attr.aria-describedby]="describedBy() || null"
19985
- [attr.aria-label]="triggerAriaLabel()"
19986
- [attr.title]="displayValue()"
19987
- (click)="openDialog()"
19988
- (blur)="handleBlur()"
19989
- >
19990
- <span [class.jv-date-picker-placeholder]="!currentValue()">{{ displayValue() }}</span>
19991
- <jv-icon name="calendar-days" [size]="18" />
19992
- </button>
19993
- </jv-form-container>
19994
-
19995
- <jv-dialog [open]="dialogOpen()" [title]="resolvedDialogTitle()" (closed)="closeDialog()">
19996
- <div #calendarRoot [id]="dialogId" class="jv-date-picker-dialog">
19997
- <div class="jv-date-picker-toolbar">
19998
- <jv-icon-button icon="chevron-left" variant="ghost" [ariaLabel]="translation.translate('datePicker.prevMonth')" (click)="shiftMonth(-1)" />
19999
-
20000
- <div class="jv-date-picker-selects">
20001
- <jv-select [options]="monthOptions()" [modelValue]="visibleMonth().getMonth().toString()" (selectionChange)="handleMonthChange($event)" />
20002
- <jv-select [options]="yearOptions()" [modelValue]="visibleMonth().getFullYear().toString()" (selectionChange)="handleYearChange($event)" />
20003
- </div>
20004
-
20005
- <jv-icon-button icon="chevron-right" variant="ghost" [ariaLabel]="translation.translate('datePicker.nextMonth')" (click)="shiftMonth(1)" />
20006
- </div>
20007
-
20008
- <div class="jv-date-picker-weekdays" aria-hidden="true">
20009
- @for (weekday of weekdayLabels(); track weekday) {
20010
- <span>{{ weekday }}</span>
20011
- }
20012
- </div>
20013
-
20014
- <div class="jv-date-picker-grid" role="grid" [attr.aria-label]="resolvedDialogTitle()">
20015
- @for (cell of calendarCells(); track isoDate(cell.date)) {
20016
- <button
20017
- type="button"
20018
- class="jv-date-picker-day"
20019
- [class.jv-date-picker-day--outside]="!cell.currentMonth"
20020
- [class.jv-date-picker-day--today]="cell.isToday"
20021
- [class.jv-date-picker-day--selected]="isSelected(cell.date)"
20022
- [attr.data-jv-date]="isoDate(cell.date)"
20023
- [attr.tabindex]="isFocusedDate(cell.date) ? 0 : -1"
20024
- [attr.aria-selected]="isSelected(cell.date) ? 'true' : 'false'"
20025
- (click)="selectDate(cell.date)"
20026
- (keydown)="handleDayKeydown($event, cell.date)"
20027
- >
20028
- {{ cell.date.getDate() }}
20029
- </button>
20030
- }
20031
- </div>
20032
- </div>
20033
-
20034
- <div dialog-actions class="jv-date-picker-actions">
20035
- @if (currentValue()) {
20036
- <jv-button variant="ghost" (click)="clearSelection()">{{ translation.translate('datePicker.clear') }}</jv-button>
20037
- }
20038
- <jv-button variant="outline" (click)="closeDialog()">{{ translation.translate('datePicker.close') }}</jv-button>
20039
- </div>
20040
- </jv-dialog>
20128
+ ], viewQueries: [{ propertyName: "calendarRootRef", first: true, predicate: ["calendarRoot"], descendants: true, isSignal: true }, { propertyName: "triggerButtonRef", first: true, predicate: ["triggerButton"], descendants: true, isSignal: true }], ngImport: i0, template: `
20129
+ <jv-form-container
20130
+ [label]="label()"
20131
+ [inputId]="inputId()"
20132
+ [required]="required()"
20133
+ [invalid]="invalid()"
20134
+ [hint]="hint()"
20135
+ [error]="error()"
20136
+ >
20137
+ <button
20138
+ #triggerButton
20139
+ type="button"
20140
+ class="jv-date-picker-trigger"
20141
+ [class.is-invalid]="invalid()"
20142
+ [id]="inputId()"
20143
+ [disabled]="disabled"
20144
+ [attr.aria-haspopup]="'dialog'"
20145
+ [attr.aria-expanded]="dialogOpen() ? 'true' : 'false'"
20146
+ [attr.aria-controls]="dialogId"
20147
+ [attr.aria-invalid]="invalid() ? 'true' : null"
20148
+ [attr.aria-describedby]="describedBy() || null"
20149
+ [attr.aria-label]="triggerAriaLabel()"
20150
+ [attr.title]="displayValue()"
20151
+ (click)="openDialog()"
20152
+ (blur)="handleBlur()"
20153
+ >
20154
+ <span [class.jv-date-picker-placeholder]="!currentValue()">{{ displayValue() }}</span>
20155
+ <jv-icon name="calendar-days" [size]="18" />
20156
+ </button>
20157
+ </jv-form-container>
20158
+
20159
+ <jv-dialog [open]="dialogOpen()" [title]="resolvedDialogTitle()" (closed)="closeDialog()">
20160
+ <div #calendarRoot [id]="dialogId" class="jv-date-picker-dialog">
20161
+ <div class="jv-date-picker-toolbar">
20162
+ <jv-icon-button icon="chevron-left" variant="ghost" [ariaLabel]="translation.translate('datePicker.prevMonth')" (click)="shiftMonth(-1)" />
20163
+
20164
+ <div class="jv-date-picker-selects">
20165
+ <jv-select [options]="monthOptions()" [modelValue]="visibleMonth().getMonth().toString()" (selectionChange)="handleMonthChange($event)" />
20166
+ <jv-select [options]="yearOptions()" [modelValue]="visibleMonth().getFullYear().toString()" (selectionChange)="handleYearChange($event)" />
20167
+ </div>
20168
+
20169
+ <jv-icon-button icon="chevron-right" variant="ghost" [ariaLabel]="translation.translate('datePicker.nextMonth')" (click)="shiftMonth(1)" />
20170
+ </div>
20171
+
20172
+ <div class="jv-date-picker-weekdays" aria-hidden="true">
20173
+ @for (weekday of weekdayLabels(); track weekday) {
20174
+ <span>{{ weekday }}</span>
20175
+ }
20176
+ </div>
20177
+
20178
+ <div class="jv-date-picker-grid" role="grid" [attr.aria-label]="resolvedDialogTitle()">
20179
+ @for (cell of calendarCells(); track isoDate(cell.date)) {
20180
+ <button
20181
+ type="button"
20182
+ class="jv-date-picker-day"
20183
+ [class.jv-date-picker-day--outside]="!cell.currentMonth"
20184
+ [class.jv-date-picker-day--today]="cell.isToday"
20185
+ [class.jv-date-picker-day--selected]="isSelected(cell.date)"
20186
+ [attr.data-jv-date]="isoDate(cell.date)"
20187
+ [attr.tabindex]="isFocusedDate(cell.date) ? 0 : -1"
20188
+ [attr.aria-selected]="isSelected(cell.date) ? 'true' : 'false'"
20189
+ (click)="selectDate(cell.date)"
20190
+ (keydown)="handleDayKeydown($event, cell.date)"
20191
+ >
20192
+ {{ cell.date.getDate() }}
20193
+ </button>
20194
+ }
20195
+ </div>
20196
+ </div>
20197
+
20198
+ <div dialog-actions class="jv-date-picker-actions">
20199
+ @if (currentValue()) {
20200
+ <jv-button variant="ghost" (click)="clearSelection()">{{ translation.translate('datePicker.clear') }}</jv-button>
20201
+ }
20202
+ <jv-button variant="outline" (click)="closeDialog()">{{ translation.translate('datePicker.close') }}</jv-button>
20203
+ </div>
20204
+ </jv-dialog>
20041
20205
  `, 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"] }] });
20042
20206
  }
20043
20207
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: JvDatePickerComponent, decorators: [{
@@ -20048,83 +20212,83 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImpo
20048
20212
  useExisting: forwardRef(() => JvDatePickerComponent),
20049
20213
  multi: true,
20050
20214
  },
20051
- ], template: `
20052
- <jv-form-container
20053
- [label]="label()"
20054
- [inputId]="inputId()"
20055
- [required]="required()"
20056
- [invalid]="invalid()"
20057
- [hint]="hint()"
20058
- [error]="error()"
20059
- >
20060
- <button
20061
- #triggerButton
20062
- type="button"
20063
- class="jv-date-picker-trigger"
20064
- [class.is-invalid]="invalid()"
20065
- [id]="inputId()"
20066
- [disabled]="disabled"
20067
- [attr.aria-haspopup]="'dialog'"
20068
- [attr.aria-expanded]="dialogOpen() ? 'true' : 'false'"
20069
- [attr.aria-controls]="dialogId"
20070
- [attr.aria-invalid]="invalid() ? 'true' : null"
20071
- [attr.aria-describedby]="describedBy() || null"
20072
- [attr.aria-label]="triggerAriaLabel()"
20073
- [attr.title]="displayValue()"
20074
- (click)="openDialog()"
20075
- (blur)="handleBlur()"
20076
- >
20077
- <span [class.jv-date-picker-placeholder]="!currentValue()">{{ displayValue() }}</span>
20078
- <jv-icon name="calendar-days" [size]="18" />
20079
- </button>
20080
- </jv-form-container>
20081
-
20082
- <jv-dialog [open]="dialogOpen()" [title]="resolvedDialogTitle()" (closed)="closeDialog()">
20083
- <div #calendarRoot [id]="dialogId" class="jv-date-picker-dialog">
20084
- <div class="jv-date-picker-toolbar">
20085
- <jv-icon-button icon="chevron-left" variant="ghost" [ariaLabel]="translation.translate('datePicker.prevMonth')" (click)="shiftMonth(-1)" />
20086
-
20087
- <div class="jv-date-picker-selects">
20088
- <jv-select [options]="monthOptions()" [modelValue]="visibleMonth().getMonth().toString()" (selectionChange)="handleMonthChange($event)" />
20089
- <jv-select [options]="yearOptions()" [modelValue]="visibleMonth().getFullYear().toString()" (selectionChange)="handleYearChange($event)" />
20090
- </div>
20091
-
20092
- <jv-icon-button icon="chevron-right" variant="ghost" [ariaLabel]="translation.translate('datePicker.nextMonth')" (click)="shiftMonth(1)" />
20093
- </div>
20094
-
20095
- <div class="jv-date-picker-weekdays" aria-hidden="true">
20096
- @for (weekday of weekdayLabels(); track weekday) {
20097
- <span>{{ weekday }}</span>
20098
- }
20099
- </div>
20100
-
20101
- <div class="jv-date-picker-grid" role="grid" [attr.aria-label]="resolvedDialogTitle()">
20102
- @for (cell of calendarCells(); track isoDate(cell.date)) {
20103
- <button
20104
- type="button"
20105
- class="jv-date-picker-day"
20106
- [class.jv-date-picker-day--outside]="!cell.currentMonth"
20107
- [class.jv-date-picker-day--today]="cell.isToday"
20108
- [class.jv-date-picker-day--selected]="isSelected(cell.date)"
20109
- [attr.data-jv-date]="isoDate(cell.date)"
20110
- [attr.tabindex]="isFocusedDate(cell.date) ? 0 : -1"
20111
- [attr.aria-selected]="isSelected(cell.date) ? 'true' : 'false'"
20112
- (click)="selectDate(cell.date)"
20113
- (keydown)="handleDayKeydown($event, cell.date)"
20114
- >
20115
- {{ cell.date.getDate() }}
20116
- </button>
20117
- }
20118
- </div>
20119
- </div>
20120
-
20121
- <div dialog-actions class="jv-date-picker-actions">
20122
- @if (currentValue()) {
20123
- <jv-button variant="ghost" (click)="clearSelection()">{{ translation.translate('datePicker.clear') }}</jv-button>
20124
- }
20125
- <jv-button variant="outline" (click)="closeDialog()">{{ translation.translate('datePicker.close') }}</jv-button>
20126
- </div>
20127
- </jv-dialog>
20215
+ ], template: `
20216
+ <jv-form-container
20217
+ [label]="label()"
20218
+ [inputId]="inputId()"
20219
+ [required]="required()"
20220
+ [invalid]="invalid()"
20221
+ [hint]="hint()"
20222
+ [error]="error()"
20223
+ >
20224
+ <button
20225
+ #triggerButton
20226
+ type="button"
20227
+ class="jv-date-picker-trigger"
20228
+ [class.is-invalid]="invalid()"
20229
+ [id]="inputId()"
20230
+ [disabled]="disabled"
20231
+ [attr.aria-haspopup]="'dialog'"
20232
+ [attr.aria-expanded]="dialogOpen() ? 'true' : 'false'"
20233
+ [attr.aria-controls]="dialogId"
20234
+ [attr.aria-invalid]="invalid() ? 'true' : null"
20235
+ [attr.aria-describedby]="describedBy() || null"
20236
+ [attr.aria-label]="triggerAriaLabel()"
20237
+ [attr.title]="displayValue()"
20238
+ (click)="openDialog()"
20239
+ (blur)="handleBlur()"
20240
+ >
20241
+ <span [class.jv-date-picker-placeholder]="!currentValue()">{{ displayValue() }}</span>
20242
+ <jv-icon name="calendar-days" [size]="18" />
20243
+ </button>
20244
+ </jv-form-container>
20245
+
20246
+ <jv-dialog [open]="dialogOpen()" [title]="resolvedDialogTitle()" (closed)="closeDialog()">
20247
+ <div #calendarRoot [id]="dialogId" class="jv-date-picker-dialog">
20248
+ <div class="jv-date-picker-toolbar">
20249
+ <jv-icon-button icon="chevron-left" variant="ghost" [ariaLabel]="translation.translate('datePicker.prevMonth')" (click)="shiftMonth(-1)" />
20250
+
20251
+ <div class="jv-date-picker-selects">
20252
+ <jv-select [options]="monthOptions()" [modelValue]="visibleMonth().getMonth().toString()" (selectionChange)="handleMonthChange($event)" />
20253
+ <jv-select [options]="yearOptions()" [modelValue]="visibleMonth().getFullYear().toString()" (selectionChange)="handleYearChange($event)" />
20254
+ </div>
20255
+
20256
+ <jv-icon-button icon="chevron-right" variant="ghost" [ariaLabel]="translation.translate('datePicker.nextMonth')" (click)="shiftMonth(1)" />
20257
+ </div>
20258
+
20259
+ <div class="jv-date-picker-weekdays" aria-hidden="true">
20260
+ @for (weekday of weekdayLabels(); track weekday) {
20261
+ <span>{{ weekday }}</span>
20262
+ }
20263
+ </div>
20264
+
20265
+ <div class="jv-date-picker-grid" role="grid" [attr.aria-label]="resolvedDialogTitle()">
20266
+ @for (cell of calendarCells(); track isoDate(cell.date)) {
20267
+ <button
20268
+ type="button"
20269
+ class="jv-date-picker-day"
20270
+ [class.jv-date-picker-day--outside]="!cell.currentMonth"
20271
+ [class.jv-date-picker-day--today]="cell.isToday"
20272
+ [class.jv-date-picker-day--selected]="isSelected(cell.date)"
20273
+ [attr.data-jv-date]="isoDate(cell.date)"
20274
+ [attr.tabindex]="isFocusedDate(cell.date) ? 0 : -1"
20275
+ [attr.aria-selected]="isSelected(cell.date) ? 'true' : 'false'"
20276
+ (click)="selectDate(cell.date)"
20277
+ (keydown)="handleDayKeydown($event, cell.date)"
20278
+ >
20279
+ {{ cell.date.getDate() }}
20280
+ </button>
20281
+ }
20282
+ </div>
20283
+ </div>
20284
+
20285
+ <div dialog-actions class="jv-date-picker-actions">
20286
+ @if (currentValue()) {
20287
+ <jv-button variant="ghost" (click)="clearSelection()">{{ translation.translate('datePicker.clear') }}</jv-button>
20288
+ }
20289
+ <jv-button variant="outline" (click)="closeDialog()">{{ translation.translate('datePicker.close') }}</jv-button>
20290
+ </div>
20291
+ </jv-dialog>
20128
20292
  `, 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"] }]
20129
20293
  }], 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"] }] } });
20130
20294
  function daysInMonth(year, month) {
@@ -20149,6 +20313,297 @@ function capitalize(value) {
20149
20313
  return value.charAt(0).toUpperCase() + value.slice(1);
20150
20314
  }
20151
20315
 
20316
+ let fileInputIdSequence = 0;
20317
+ class JvFileInputComponent {
20318
+ translation = inject(JvTranslationService);
20319
+ instanceId = ++fileInputIdSequence;
20320
+ nativeInputRef = viewChild('nativeInput', ...(ngDevMode ? [{ debugName: "nativeInputRef" }] : /* istanbul ignore next */ []));
20321
+ label = input('', ...(ngDevMode ? [{ debugName: "label" }] : /* istanbul ignore next */ []));
20322
+ hint = input('', ...(ngDevMode ? [{ debugName: "hint" }] : /* istanbul ignore next */ []));
20323
+ error = input('', ...(ngDevMode ? [{ debugName: "error" }] : /* istanbul ignore next */ []));
20324
+ describedBy = input('', ...(ngDevMode ? [{ debugName: "describedBy" }] : /* istanbul ignore next */ []));
20325
+ inputId = input(`jv-file-input-${this.instanceId}`, ...(ngDevMode ? [{ debugName: "inputId" }] : /* istanbul ignore next */ []));
20326
+ accept = input('', ...(ngDevMode ? [{ debugName: "accept" }] : /* istanbul ignore next */ []));
20327
+ multiple = input(true, ...(ngDevMode ? [{ debugName: "multiple" }] : /* istanbul ignore next */ []));
20328
+ maxFiles = input(10, ...(ngDevMode ? [{ debugName: "maxFiles" }] : /* istanbul ignore next */ []));
20329
+ disabled = input(false, ...(ngDevMode ? [{ debugName: "disabled" }] : /* istanbul ignore next */ []));
20330
+ required = input(false, ...(ngDevMode ? [{ debugName: "required" }] : /* istanbul ignore next */ []));
20331
+ invalid = input(false, ...(ngDevMode ? [{ debugName: "invalid" }] : /* istanbul ignore next */ []));
20332
+ filesInput = input(undefined, { ...(ngDevMode ? { debugName: "filesInput" } : /* istanbul ignore next */ {}), alias: 'files' });
20333
+ filesChange = output();
20334
+ filesRejected = output();
20335
+ dragActive = signal(false, ...(ngDevMode ? [{ debugName: "dragActive" }] : /* istanbul ignore next */ []));
20336
+ rejectionMessage = signal('', ...(ngDevMode ? [{ debugName: "rejectionMessage" }] : /* istanbul ignore next */ []));
20337
+ internalFiles = signal([], ...(ngDevMode ? [{ debugName: "internalFiles" }] : /* istanbul ignore next */ []));
20338
+ currentFiles = computed(() => {
20339
+ const externalFiles = this.filesInput();
20340
+ return externalFiles !== undefined ? [...(externalFiles ?? [])] : this.internalFiles();
20341
+ }, ...(ngDevMode ? [{ debugName: "currentFiles" }] : /* istanbul ignore next */ []));
20342
+ selectionSummary = computed(() => this.translation.translate('fileInput.selectionSummary', { count: this.currentFiles().length, max: this.maxFiles() }), ...(ngDevMode ? [{ debugName: "selectionSummary" }] : /* istanbul ignore next */ []));
20343
+ dropzoneAriaLabel = computed(() => `${this.label() || this.translation.translate('fileInput.dropLabel')}. ${this.selectionSummary()}`, ...(ngDevMode ? [{ debugName: "dropzoneAriaLabel" }] : /* istanbul ignore next */ []));
20344
+ openFileDialog() {
20345
+ if (this.disabled())
20346
+ return;
20347
+ this.nativeInputRef()?.nativeElement.click();
20348
+ }
20349
+ handleDropzoneKeydown(event) {
20350
+ if (event.key === 'Enter' || event.key === ' ') {
20351
+ event.preventDefault();
20352
+ this.openFileDialog();
20353
+ }
20354
+ }
20355
+ onNativeInputChange(fileList) {
20356
+ this.consumeFiles(fileList ? Array.from(fileList) : []);
20357
+ const input = this.nativeInputRef()?.nativeElement;
20358
+ if (input) {
20359
+ input.value = '';
20360
+ }
20361
+ }
20362
+ onDragEnter(event) {
20363
+ if (this.disabled())
20364
+ return;
20365
+ event.preventDefault();
20366
+ this.dragActive.set(true);
20367
+ }
20368
+ onDragOver(event) {
20369
+ if (this.disabled())
20370
+ return;
20371
+ event.preventDefault();
20372
+ this.dragActive.set(true);
20373
+ }
20374
+ onDragLeave(event) {
20375
+ if (event.currentTarget?.contains(event.relatedTarget)) {
20376
+ return;
20377
+ }
20378
+ this.dragActive.set(false);
20379
+ }
20380
+ onDrop(event) {
20381
+ if (this.disabled())
20382
+ return;
20383
+ event.preventDefault();
20384
+ this.dragActive.set(false);
20385
+ this.consumeFiles(Array.from(event.dataTransfer?.files ?? []));
20386
+ }
20387
+ removeFile(index) {
20388
+ if (this.disabled())
20389
+ return;
20390
+ const nextFiles = [...this.currentFiles()];
20391
+ nextFiles.splice(index, 1);
20392
+ this.updateFiles(nextFiles);
20393
+ }
20394
+ trackFile(index, file) {
20395
+ return `${file.name}-${file.size}-${file.lastModified}-${index}`;
20396
+ }
20397
+ formatBytes(size) {
20398
+ if (size < 1024)
20399
+ return `${size} B`;
20400
+ if (size < 1024 * 1024)
20401
+ return `${(size / 1024).toFixed(1)} KB`;
20402
+ return `${(size / (1024 * 1024)).toFixed(1)} MB`;
20403
+ }
20404
+ consumeFiles(files) {
20405
+ if (files.length === 0)
20406
+ return;
20407
+ const accepted = [];
20408
+ const rejectedByAccept = [];
20409
+ const currentFiles = this.currentFiles();
20410
+ const remainingSlots = this.multiple() ? Math.max(this.maxFiles() - currentFiles.length, 0) : 1;
20411
+ for (const file of files) {
20412
+ if (!this.matchesAccept(file)) {
20413
+ rejectedByAccept.push(file);
20414
+ continue;
20415
+ }
20416
+ accepted.push(file);
20417
+ }
20418
+ const nextAccepted = this.multiple() ? accepted.slice(0, remainingSlots) : accepted.slice(0, 1);
20419
+ const rejectedByCount = this.multiple() ? accepted.slice(remainingSlots) : accepted.slice(1);
20420
+ const nextFiles = this.multiple() ? [...currentFiles, ...nextAccepted] : nextAccepted;
20421
+ this.rejectionMessage.set('');
20422
+ if (rejectedByAccept.length > 0) {
20423
+ this.rejectionMessage.set(this.translation.translate('fileInput.invalidFiles', { count: rejectedByAccept.length }));
20424
+ this.filesRejected.emit({ files: rejectedByAccept, reason: 'accept' });
20425
+ }
20426
+ if (rejectedByCount.length > 0) {
20427
+ this.rejectionMessage.set(this.translation.translate('fileInput.tooManyFiles', { max: this.maxFiles() }));
20428
+ this.filesRejected.emit({ files: rejectedByCount, reason: 'maxFiles' });
20429
+ }
20430
+ this.updateFiles(nextFiles);
20431
+ }
20432
+ updateFiles(files) {
20433
+ if (this.filesInput() === undefined) {
20434
+ this.internalFiles.set(files);
20435
+ }
20436
+ this.filesChange.emit(files);
20437
+ }
20438
+ matchesAccept(file) {
20439
+ const accept = this.accept().trim();
20440
+ if (!accept)
20441
+ return true;
20442
+ const type = file.type.toLowerCase();
20443
+ const name = file.name.toLowerCase();
20444
+ return accept
20445
+ .split(',')
20446
+ .map((entry) => entry.trim().toLowerCase())
20447
+ .some((rule) => {
20448
+ if (!rule)
20449
+ return false;
20450
+ if (rule.startsWith('.'))
20451
+ return name.endsWith(rule);
20452
+ if (rule.endsWith('/*'))
20453
+ return type.startsWith(`${rule.slice(0, -1)}`);
20454
+ return type === rule;
20455
+ });
20456
+ }
20457
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: JvFileInputComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
20458
+ 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: `
20459
+ <jv-form-container
20460
+ [label]="label()"
20461
+ [inputId]="inputId()"
20462
+ [required]="required()"
20463
+ [invalid]="invalid()"
20464
+ [hint]="hint()"
20465
+ [error]="error()"
20466
+ >
20467
+ <input
20468
+ #nativeInput
20469
+ class="jv-file-input-native"
20470
+ type="file"
20471
+ [id]="inputId()"
20472
+ [attr.accept]="accept() || null"
20473
+ [attr.multiple]="multiple() ? '' : null"
20474
+ [disabled]="disabled()"
20475
+ [attr.aria-describedby]="describedBy() || null"
20476
+ (change)="onNativeInputChange($any($event.target).files)"
20477
+ />
20478
+
20479
+ <div
20480
+ class="jv-file-input-dropzone"
20481
+ [class.jv-file-input-dropzone--disabled]="disabled()"
20482
+ [class.jv-file-input-dropzone--dragover]="dragActive()"
20483
+ [class.jv-file-input-dropzone--invalid]="invalid()"
20484
+ role="button"
20485
+ tabindex="0"
20486
+ [attr.aria-disabled]="disabled() ? 'true' : 'false'"
20487
+ [attr.aria-label]="dropzoneAriaLabel()"
20488
+ (click)="openFileDialog()"
20489
+ (keydown)="handleDropzoneKeydown($event)"
20490
+ (dragenter)="onDragEnter($event)"
20491
+ (dragover)="onDragOver($event)"
20492
+ (dragleave)="onDragLeave($event)"
20493
+ (drop)="onDrop($event)"
20494
+ >
20495
+ <jv-icon name="upload-cloud" [size]="24" />
20496
+ <div class="jv-file-input-copy">
20497
+ <strong>{{ translation.translate('fileInput.dropLabel') }}</strong>
20498
+ <span>{{ translation.translate('fileInput.helperText') }}</span>
20499
+ <span class="jv-file-input-meta">{{ selectionSummary() }}</span>
20500
+ </div>
20501
+ </div>
20502
+
20503
+ @if (rejectionMessage()) {
20504
+ <p class="jv-file-input-rejection" aria-live="polite">{{ rejectionMessage() }}</p>
20505
+ }
20506
+
20507
+ @if (currentFiles().length > 0) {
20508
+ <ul class="jv-file-input-list">
20509
+ @for (file of currentFiles(); track trackFile($index, file); let index = $index) {
20510
+ <li class="jv-file-input-item">
20511
+ <div class="jv-file-input-item-copy">
20512
+ <span class="jv-file-input-item-name">{{ file.name }}</span>
20513
+ <span class="jv-file-input-item-meta">{{ formatBytes(file.size) }}</span>
20514
+ </div>
20515
+ <button
20516
+ type="button"
20517
+ class="jv-file-input-remove"
20518
+ [disabled]="disabled()"
20519
+ [attr.aria-label]="translation.translate('fileInput.removeFile', { name: file.name })"
20520
+ (click)="removeFile(index)"
20521
+ >
20522
+ <jv-icon name="x" [size]="16" />
20523
+ </button>
20524
+ </li>
20525
+ }
20526
+ </ul>
20527
+ }
20528
+ </jv-form-container>
20529
+ `, 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"] }] });
20530
+ }
20531
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: JvFileInputComponent, decorators: [{
20532
+ type: Component,
20533
+ args: [{ selector: 'jv-file-input', standalone: true, imports: [JvFormContainerComponent, JvIconComponent], template: `
20534
+ <jv-form-container
20535
+ [label]="label()"
20536
+ [inputId]="inputId()"
20537
+ [required]="required()"
20538
+ [invalid]="invalid()"
20539
+ [hint]="hint()"
20540
+ [error]="error()"
20541
+ >
20542
+ <input
20543
+ #nativeInput
20544
+ class="jv-file-input-native"
20545
+ type="file"
20546
+ [id]="inputId()"
20547
+ [attr.accept]="accept() || null"
20548
+ [attr.multiple]="multiple() ? '' : null"
20549
+ [disabled]="disabled()"
20550
+ [attr.aria-describedby]="describedBy() || null"
20551
+ (change)="onNativeInputChange($any($event.target).files)"
20552
+ />
20553
+
20554
+ <div
20555
+ class="jv-file-input-dropzone"
20556
+ [class.jv-file-input-dropzone--disabled]="disabled()"
20557
+ [class.jv-file-input-dropzone--dragover]="dragActive()"
20558
+ [class.jv-file-input-dropzone--invalid]="invalid()"
20559
+ role="button"
20560
+ tabindex="0"
20561
+ [attr.aria-disabled]="disabled() ? 'true' : 'false'"
20562
+ [attr.aria-label]="dropzoneAriaLabel()"
20563
+ (click)="openFileDialog()"
20564
+ (keydown)="handleDropzoneKeydown($event)"
20565
+ (dragenter)="onDragEnter($event)"
20566
+ (dragover)="onDragOver($event)"
20567
+ (dragleave)="onDragLeave($event)"
20568
+ (drop)="onDrop($event)"
20569
+ >
20570
+ <jv-icon name="upload-cloud" [size]="24" />
20571
+ <div class="jv-file-input-copy">
20572
+ <strong>{{ translation.translate('fileInput.dropLabel') }}</strong>
20573
+ <span>{{ translation.translate('fileInput.helperText') }}</span>
20574
+ <span class="jv-file-input-meta">{{ selectionSummary() }}</span>
20575
+ </div>
20576
+ </div>
20577
+
20578
+ @if (rejectionMessage()) {
20579
+ <p class="jv-file-input-rejection" aria-live="polite">{{ rejectionMessage() }}</p>
20580
+ }
20581
+
20582
+ @if (currentFiles().length > 0) {
20583
+ <ul class="jv-file-input-list">
20584
+ @for (file of currentFiles(); track trackFile($index, file); let index = $index) {
20585
+ <li class="jv-file-input-item">
20586
+ <div class="jv-file-input-item-copy">
20587
+ <span class="jv-file-input-item-name">{{ file.name }}</span>
20588
+ <span class="jv-file-input-item-meta">{{ formatBytes(file.size) }}</span>
20589
+ </div>
20590
+ <button
20591
+ type="button"
20592
+ class="jv-file-input-remove"
20593
+ [disabled]="disabled()"
20594
+ [attr.aria-label]="translation.translate('fileInput.removeFile', { name: file.name })"
20595
+ (click)="removeFile(index)"
20596
+ >
20597
+ <jv-icon name="x" [size]="16" />
20598
+ </button>
20599
+ </li>
20600
+ }
20601
+ </ul>
20602
+ }
20603
+ </jv-form-container>
20604
+ `, 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"] }]
20605
+ }], 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"] }] } });
20606
+
20152
20607
  class JvListComponent {
20153
20608
  role = input('list', ...(ngDevMode ? [{ debugName: "role" }] : /* istanbul ignore next */ []));
20154
20609
  ariaRole = input(null, ...(ngDevMode ? [{ debugName: "ariaRole" }] : /* istanbul ignore next */ []));
@@ -20800,6 +21255,535 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImpo
20800
21255
  `, styles: [".jv-textarea{width:100%;min-height:var(--jv-density-control-height);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);color:var(--jv-color-foreground);resize:vertical;font-family:inherit;font-size:inherit;line-height:inherit;box-sizing:border-box}.jv-textarea::placeholder{color:var(--jv-color-foreground-muted)}.jv-textarea.is-invalid{border-color:var(--jv-color-danger)}.jv-textarea:disabled{opacity:.7;cursor:not-allowed}.jv-textarea:read-only{background:var(--jv-color-surface-muted);cursor:default}\n"] }]
20801
21256
  }], propDecorators: { placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], required: [{ type: i0.Input, args: [{ isSignal: true, alias: "required", required: false }] }], invalid: [{ type: i0.Input, args: [{ isSignal: true, alias: "invalid", required: false }] }], describedBy: [{ type: i0.Input, args: [{ isSignal: true, alias: "describedBy", required: false }] }], readonly: [{ type: i0.Input, args: [{ isSignal: true, alias: "readonly", required: false }] }], rows: [{ type: i0.Input, args: [{ isSignal: true, alias: "rows", required: false }] }], cols: [{ type: i0.Input, args: [{ isSignal: true, alias: "cols", required: false }] }], maxlength: [{ type: i0.Input, args: [{ isSignal: true, alias: "maxlength", required: false }] }], inputId: [{ type: i0.Input, args: [{ isSignal: true, alias: "inputId", required: false }] }] } });
20802
21257
 
21258
+ class JvImageGalleryComponent {
21259
+ translation = inject(JvTranslationService);
21260
+ images = input([], ...(ngDevMode ? [{ debugName: "images" }] : /* istanbul ignore next */ []));
21261
+ stripHeightVh = input(25, { ...(ngDevMode ? { debugName: "stripHeightVh" } : /* istanbul ignore next */ {}), transform: numberAttribute });
21262
+ actions = input([], ...(ngDevMode ? [{ debugName: "actions" }] : /* istanbul ignore next */ []));
21263
+ imageClick = output();
21264
+ actionClick = output();
21265
+ strip = viewChild('strip', ...(ngDevMode ? [{ debugName: "strip" }] : /* istanbul ignore next */ []));
21266
+ dialogOpen = signal(false, ...(ngDevMode ? [{ debugName: "dialogOpen" }] : /* istanbul ignore next */ []));
21267
+ currentIndex = signal(0, ...(ngDevMode ? [{ debugName: "currentIndex" }] : /* istanbul ignore next */ []));
21268
+ slideDirection = signal(null, ...(ngDevMode ? [{ debugName: "slideDirection" }] : /* istanbul ignore next */ []));
21269
+ hasMultiple = computed(() => this.images().length > 1, ...(ngDevMode ? [{ debugName: "hasMultiple" }] : /* istanbul ignore next */ []));
21270
+ stripHeightStyle = computed(() => `${this.clampStripHeight(this.stripHeightVh())}vh`, ...(ngDevMode ? [{ debugName: "stripHeightStyle" }] : /* istanbul ignore next */ []));
21271
+ currentImage = computed(() => this.images()[this.currentIndex()] ?? null, ...(ngDevMode ? [{ debugName: "currentImage" }] : /* istanbul ignore next */ []));
21272
+ dialogTitle = computed(() => {
21273
+ const image = this.currentImage();
21274
+ if (!image)
21275
+ return '';
21276
+ const counter = this.hasMultiple() ? ` (${this.currentIndex() + 1}/${this.images().length})` : '';
21277
+ return `${image.alt}${counter}`;
21278
+ }, ...(ngDevMode ? [{ debugName: "dialogTitle" }] : /* istanbul ignore next */ []));
21279
+ translatedLabels = computed(() => ({
21280
+ close: this.translation.translate('gallery.close'),
21281
+ prev: this.translation.translate('gallery.prevImage'),
21282
+ next: this.translation.translate('gallery.nextImage'),
21283
+ }), ...(ngDevMode ? [{ debugName: "translatedLabels" }] : /* istanbul ignore next */ []));
21284
+ handleKeydown(event) {
21285
+ if (!this.dialogOpen())
21286
+ return;
21287
+ switch (event.key) {
21288
+ case 'ArrowLeft':
21289
+ event.preventDefault();
21290
+ this.previous();
21291
+ break;
21292
+ case 'ArrowRight':
21293
+ event.preventDefault();
21294
+ this.next();
21295
+ break;
21296
+ }
21297
+ }
21298
+ onWheel(event) {
21299
+ const el = this.strip()?.nativeElement;
21300
+ if (!el)
21301
+ return;
21302
+ if (Math.abs(event.deltaY) > Math.abs(event.deltaX)) {
21303
+ const delta = event.deltaMode === WheelEvent.DOM_DELTA_LINE
21304
+ ? event.deltaY * 32
21305
+ : event.deltaMode === WheelEvent.DOM_DELTA_PAGE
21306
+ ? event.deltaY * el.clientWidth
21307
+ : event.deltaY;
21308
+ const firstThumb = el.querySelector('.jv-gallery-thumb');
21309
+ const gap = Number.parseFloat(getComputedStyle(el).columnGap || getComputedStyle(el).gap || '0') || 0;
21310
+ const step = firstThumb ? firstThumb.getBoundingClientRect().width + gap : 0;
21311
+ const offset = Math.sign(delta) * Math.max(Math.abs(delta), step);
21312
+ event.preventDefault();
21313
+ el.scrollLeft += offset;
21314
+ }
21315
+ }
21316
+ openDialog(index) {
21317
+ this.currentIndex.set(index);
21318
+ this.slideDirection.set(null);
21319
+ this.dialogOpen.set(true);
21320
+ this.imageClick.emit(this.images()[index]);
21321
+ }
21322
+ closeDialog() {
21323
+ this.dialogOpen.set(false);
21324
+ this.slideDirection.set(null);
21325
+ }
21326
+ previous() {
21327
+ if (!this.hasMultiple())
21328
+ return;
21329
+ const newIndex = this.currentIndex() === 0
21330
+ ? this.images().length - 1
21331
+ : this.currentIndex() - 1;
21332
+ this.showImage(newIndex, 'right');
21333
+ }
21334
+ next() {
21335
+ if (!this.hasMultiple())
21336
+ return;
21337
+ const newIndex = this.currentIndex() === this.images().length - 1
21338
+ ? 0
21339
+ : this.currentIndex() + 1;
21340
+ this.showImage(newIndex, 'left');
21341
+ }
21342
+ onAction(action) {
21343
+ const image = this.currentImage();
21344
+ if (image) {
21345
+ this.actionClick.emit({ actionId: action.id, image });
21346
+ }
21347
+ }
21348
+ clampStripHeight(value) {
21349
+ if (!Number.isFinite(value)) {
21350
+ return 25;
21351
+ }
21352
+ return Math.min(100, Math.max(0, value));
21353
+ }
21354
+ showImage(index, direction) {
21355
+ this.slideDirection.set(null);
21356
+ this.currentIndex.set(index);
21357
+ if (typeof requestAnimationFrame === 'function') {
21358
+ requestAnimationFrame(() => this.slideDirection.set(direction));
21359
+ return;
21360
+ }
21361
+ this.slideDirection.set(direction);
21362
+ }
21363
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: JvImageGalleryComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
21364
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.17", type: JvImageGalleryComponent, isStandalone: true, selector: "jv-image-gallery", inputs: { images: { classPropertyName: "images", publicName: "images", isSignal: true, isRequired: false, transformFunction: null }, stripHeightVh: { classPropertyName: "stripHeightVh", publicName: "stripHeightVh", isSignal: true, isRequired: false, transformFunction: null }, actions: { classPropertyName: "actions", publicName: "actions", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { imageClick: "imageClick", actionClick: "actionClick" }, host: { listeners: { "document:keydown": "handleKeydown($event)" } }, viewQueries: [{ propertyName: "strip", first: true, predicate: ["strip"], descendants: true, isSignal: true }], ngImport: i0, template: `
21365
+ <div class="jv-gallery">
21366
+ <div class="jv-gallery-strip" #strip [style.height]="stripHeightStyle()" (wheel)="onWheel($event)">
21367
+ @for (image of images(); track image.src; let i = $index) {
21368
+ <button
21369
+ type="button"
21370
+ class="jv-gallery-thumb"
21371
+ [class.jv-gallery-thumb--active]="dialogOpen() && currentIndex() === i"
21372
+ [attr.aria-label]="image.alt"
21373
+ (wheel)="onWheel($event)"
21374
+ (click)="openDialog(i)"
21375
+ >
21376
+ <img
21377
+ class="jv-gallery-thumb-image"
21378
+ [src]="image.thumbnail || image.src"
21379
+ [alt]="image.alt"
21380
+ loading="lazy"
21381
+ />
21382
+ </button>
21383
+ }
21384
+ </div>
21385
+
21386
+ <jv-dialog
21387
+ [open]="dialogOpen()"
21388
+ [title]="dialogTitle()"
21389
+ (closed)="closeDialog()"
21390
+ >
21391
+ <div class="jv-gallery-viewer" tabindex="0">
21392
+ @if (currentImage(); as image) {
21393
+ <img
21394
+ class="jv-gallery-viewer-image"
21395
+ [class.jv-gallery-viewer-image--slide-left]="slideDirection() === 'left'"
21396
+ [class.jv-gallery-viewer-image--slide-right]="slideDirection() === 'right'"
21397
+ [src]="image.src"
21398
+ [alt]="image.alt"
21399
+ />
21400
+ }
21401
+
21402
+ @if (hasMultiple()) {
21403
+ <button
21404
+ type="button"
21405
+ class="jv-gallery-viewer-nav jv-gallery-viewer-nav--prev"
21406
+ [attr.aria-label]="translatedLabels().prev"
21407
+ (click)="previous(); $event.stopPropagation()"
21408
+ >
21409
+ <jv-icon name="chevron-left" [size]="24" />
21410
+ </button>
21411
+
21412
+ <button
21413
+ type="button"
21414
+ class="jv-gallery-viewer-nav jv-gallery-viewer-nav--next"
21415
+ [attr.aria-label]="translatedLabels().next"
21416
+ (click)="next(); $event.stopPropagation()"
21417
+ >
21418
+ <jv-icon name="chevron-right" [size]="24" />
21419
+ </button>
21420
+
21421
+ <span class="jv-gallery-viewer-counter">
21422
+ {{ currentIndex() + 1 }} / {{ images().length }}
21423
+ </span>
21424
+ }
21425
+ </div>
21426
+
21427
+ <div dialog-actions>
21428
+ @for (action of actions(); track action.id) {
21429
+ <jv-button
21430
+ [variant]="action.tone ?? 'outline'"
21431
+ (click)="onAction(action)"
21432
+ >
21433
+ @if (action.icon) {
21434
+ <jv-icon [name]="action.icon" [size]="16" />
21435
+ }
21436
+ {{ action.label }}
21437
+ </jv-button>
21438
+ }
21439
+ <jv-button variant="ghost" (click)="closeDialog()">
21440
+ {{ translatedLabels().close }}
21441
+ </jv-button>
21442
+ </div>
21443
+ </jv-dialog>
21444
+ </div>
21445
+ `, isInline: true, styles: [".jv-gallery{display:block;width:100%;max-width:100vw}.jv-gallery-strip{display:flex;gap:5px;width:100%;overflow-x:auto;overflow-y:hidden;overscroll-behavior-x:contain;scrollbar-width:none;-ms-overflow-style:none;scroll-snap-type:x proximity;scroll-behavior:smooth;-webkit-overflow-scrolling:touch}.jv-gallery-strip::-webkit-scrollbar{display:none}.jv-gallery-thumb{flex:0 0 auto;width:calc((100vw - 20px)/4);min-width:120px;height:100%;padding:0;border:2px solid transparent;border-radius:var(--jv-radius-sm);background:transparent;cursor:pointer;overflow:hidden;scroll-snap-align:start;transition:border-color .16s ease,opacity .16s ease}.jv-gallery-thumb:hover{opacity:.85}.jv-gallery-thumb--active{border-color:var(--jv-color-primary)}.jv-gallery-thumb:focus-visible{outline:2px solid var(--jv-color-primary);outline-offset:1px}.jv-gallery-thumb-image{display:block;width:100%;height:100%;object-fit:cover;pointer-events:none}.jv-gallery-viewer{position:relative;display:flex;align-items:center;justify-content:center;width:100%;height:70vh;min-height:300px;background:#000;border-radius:var(--jv-radius-md);overflow:hidden}.jv-gallery-viewer:focus{outline:none}.jv-gallery-viewer-image{display:block;max-width:100%;max-height:100%;object-fit:contain;-webkit-user-select:none;user-select:none}@keyframes gallery-slide-left{0%{transform:translate(100%);opacity:0}to{transform:translate(0);opacity:1}}@keyframes gallery-slide-right{0%{transform:translate(-100%);opacity:0}to{transform:translate(0);opacity:1}}.jv-gallery-viewer-image--slide-left{animation:gallery-slide-left .28s ease-out}.jv-gallery-viewer-image--slide-right{animation:gallery-slide-right .28s ease-out}.jv-gallery-viewer-nav{position:absolute;top:50%;transform:translateY(-50%);display:inline-flex;align-items:center;justify-content:center;width:3rem;height:3rem;border:none;border-radius:var(--jv-radius-md);background:#00000080;color:#fff;cursor:pointer;transition:background-color .16s ease;z-index:1}.jv-gallery-viewer-nav:hover{background:#000000bf}.jv-gallery-viewer-nav:focus-visible{outline:2px solid var(--jv-color-primary);outline-offset:1px}.jv-gallery-viewer-nav--prev{left:var(--jv-spacing-md)}.jv-gallery-viewer-nav--next{right:var(--jv-spacing-md)}.jv-gallery-viewer-counter{position:absolute;bottom:var(--jv-spacing-md);left:50%;transform:translate(-50%);padding:var(--jv-spacing-xs) var(--jv-spacing-sm);border-radius:var(--jv-radius-sm);background:#0009;color:#fff;font-size:.8125rem;font-variant-numeric:tabular-nums;pointer-events:none}@media(max-width:768px){.jv-gallery-thumb{width:calc(100vw - 20px);min-width:200px}.jv-gallery-viewer-nav{width:2.5rem;height:2.5rem}.jv-gallery-viewer-nav--prev{left:var(--jv-spacing-sm)}.jv-gallery-viewer-nav--next{right:var(--jv-spacing-sm)}}@media(prefers-reduced-motion:reduce){.jv-gallery-strip{scroll-behavior:auto}.jv-gallery-viewer-image--slide-left,.jv-gallery-viewer-image--slide-right{animation:none}}\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: JvIconComponent, selector: "jv-icon", inputs: ["name", "size", "strokeWidth", "decorative", "ariaLabel"] }] });
21446
+ }
21447
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: JvImageGalleryComponent, decorators: [{
21448
+ type: Component,
21449
+ args: [{ selector: 'jv-image-gallery', standalone: true, imports: [JvButtonComponent, JvDialogComponent, JvIconComponent], template: `
21450
+ <div class="jv-gallery">
21451
+ <div class="jv-gallery-strip" #strip [style.height]="stripHeightStyle()" (wheel)="onWheel($event)">
21452
+ @for (image of images(); track image.src; let i = $index) {
21453
+ <button
21454
+ type="button"
21455
+ class="jv-gallery-thumb"
21456
+ [class.jv-gallery-thumb--active]="dialogOpen() && currentIndex() === i"
21457
+ [attr.aria-label]="image.alt"
21458
+ (wheel)="onWheel($event)"
21459
+ (click)="openDialog(i)"
21460
+ >
21461
+ <img
21462
+ class="jv-gallery-thumb-image"
21463
+ [src]="image.thumbnail || image.src"
21464
+ [alt]="image.alt"
21465
+ loading="lazy"
21466
+ />
21467
+ </button>
21468
+ }
21469
+ </div>
21470
+
21471
+ <jv-dialog
21472
+ [open]="dialogOpen()"
21473
+ [title]="dialogTitle()"
21474
+ (closed)="closeDialog()"
21475
+ >
21476
+ <div class="jv-gallery-viewer" tabindex="0">
21477
+ @if (currentImage(); as image) {
21478
+ <img
21479
+ class="jv-gallery-viewer-image"
21480
+ [class.jv-gallery-viewer-image--slide-left]="slideDirection() === 'left'"
21481
+ [class.jv-gallery-viewer-image--slide-right]="slideDirection() === 'right'"
21482
+ [src]="image.src"
21483
+ [alt]="image.alt"
21484
+ />
21485
+ }
21486
+
21487
+ @if (hasMultiple()) {
21488
+ <button
21489
+ type="button"
21490
+ class="jv-gallery-viewer-nav jv-gallery-viewer-nav--prev"
21491
+ [attr.aria-label]="translatedLabels().prev"
21492
+ (click)="previous(); $event.stopPropagation()"
21493
+ >
21494
+ <jv-icon name="chevron-left" [size]="24" />
21495
+ </button>
21496
+
21497
+ <button
21498
+ type="button"
21499
+ class="jv-gallery-viewer-nav jv-gallery-viewer-nav--next"
21500
+ [attr.aria-label]="translatedLabels().next"
21501
+ (click)="next(); $event.stopPropagation()"
21502
+ >
21503
+ <jv-icon name="chevron-right" [size]="24" />
21504
+ </button>
21505
+
21506
+ <span class="jv-gallery-viewer-counter">
21507
+ {{ currentIndex() + 1 }} / {{ images().length }}
21508
+ </span>
21509
+ }
21510
+ </div>
21511
+
21512
+ <div dialog-actions>
21513
+ @for (action of actions(); track action.id) {
21514
+ <jv-button
21515
+ [variant]="action.tone ?? 'outline'"
21516
+ (click)="onAction(action)"
21517
+ >
21518
+ @if (action.icon) {
21519
+ <jv-icon [name]="action.icon" [size]="16" />
21520
+ }
21521
+ {{ action.label }}
21522
+ </jv-button>
21523
+ }
21524
+ <jv-button variant="ghost" (click)="closeDialog()">
21525
+ {{ translatedLabels().close }}
21526
+ </jv-button>
21527
+ </div>
21528
+ </jv-dialog>
21529
+ </div>
21530
+ `, styles: [".jv-gallery{display:block;width:100%;max-width:100vw}.jv-gallery-strip{display:flex;gap:5px;width:100%;overflow-x:auto;overflow-y:hidden;overscroll-behavior-x:contain;scrollbar-width:none;-ms-overflow-style:none;scroll-snap-type:x proximity;scroll-behavior:smooth;-webkit-overflow-scrolling:touch}.jv-gallery-strip::-webkit-scrollbar{display:none}.jv-gallery-thumb{flex:0 0 auto;width:calc((100vw - 20px)/4);min-width:120px;height:100%;padding:0;border:2px solid transparent;border-radius:var(--jv-radius-sm);background:transparent;cursor:pointer;overflow:hidden;scroll-snap-align:start;transition:border-color .16s ease,opacity .16s ease}.jv-gallery-thumb:hover{opacity:.85}.jv-gallery-thumb--active{border-color:var(--jv-color-primary)}.jv-gallery-thumb:focus-visible{outline:2px solid var(--jv-color-primary);outline-offset:1px}.jv-gallery-thumb-image{display:block;width:100%;height:100%;object-fit:cover;pointer-events:none}.jv-gallery-viewer{position:relative;display:flex;align-items:center;justify-content:center;width:100%;height:70vh;min-height:300px;background:#000;border-radius:var(--jv-radius-md);overflow:hidden}.jv-gallery-viewer:focus{outline:none}.jv-gallery-viewer-image{display:block;max-width:100%;max-height:100%;object-fit:contain;-webkit-user-select:none;user-select:none}@keyframes gallery-slide-left{0%{transform:translate(100%);opacity:0}to{transform:translate(0);opacity:1}}@keyframes gallery-slide-right{0%{transform:translate(-100%);opacity:0}to{transform:translate(0);opacity:1}}.jv-gallery-viewer-image--slide-left{animation:gallery-slide-left .28s ease-out}.jv-gallery-viewer-image--slide-right{animation:gallery-slide-right .28s ease-out}.jv-gallery-viewer-nav{position:absolute;top:50%;transform:translateY(-50%);display:inline-flex;align-items:center;justify-content:center;width:3rem;height:3rem;border:none;border-radius:var(--jv-radius-md);background:#00000080;color:#fff;cursor:pointer;transition:background-color .16s ease;z-index:1}.jv-gallery-viewer-nav:hover{background:#000000bf}.jv-gallery-viewer-nav:focus-visible{outline:2px solid var(--jv-color-primary);outline-offset:1px}.jv-gallery-viewer-nav--prev{left:var(--jv-spacing-md)}.jv-gallery-viewer-nav--next{right:var(--jv-spacing-md)}.jv-gallery-viewer-counter{position:absolute;bottom:var(--jv-spacing-md);left:50%;transform:translate(-50%);padding:var(--jv-spacing-xs) var(--jv-spacing-sm);border-radius:var(--jv-radius-sm);background:#0009;color:#fff;font-size:.8125rem;font-variant-numeric:tabular-nums;pointer-events:none}@media(max-width:768px){.jv-gallery-thumb{width:calc(100vw - 20px);min-width:200px}.jv-gallery-viewer-nav{width:2.5rem;height:2.5rem}.jv-gallery-viewer-nav--prev{left:var(--jv-spacing-sm)}.jv-gallery-viewer-nav--next{right:var(--jv-spacing-sm)}}@media(prefers-reduced-motion:reduce){.jv-gallery-strip{scroll-behavior:auto}.jv-gallery-viewer-image--slide-left,.jv-gallery-viewer-image--slide-right{animation:none}}\n"] }]
21531
+ }], propDecorators: { images: [{ type: i0.Input, args: [{ isSignal: true, alias: "images", required: false }] }], stripHeightVh: [{ type: i0.Input, args: [{ isSignal: true, alias: "stripHeightVh", required: false }] }], actions: [{ type: i0.Input, args: [{ isSignal: true, alias: "actions", required: false }] }], imageClick: [{ type: i0.Output, args: ["imageClick"] }], actionClick: [{ type: i0.Output, args: ["actionClick"] }], strip: [{ type: i0.ViewChild, args: ['strip', { isSignal: true }] }], handleKeydown: [{
21532
+ type: HostListener,
21533
+ args: ['document:keydown', ['$event']]
21534
+ }] } });
21535
+
21536
+ class JvImageSliderComponent {
21537
+ thumbnailStripRef = viewChild('thumbnailStrip', ...(ngDevMode ? [{ debugName: "thumbnailStripRef" }] : /* istanbul ignore next */ []));
21538
+ translation = inject(JvTranslationService);
21539
+ images = input([], ...(ngDevMode ? [{ debugName: "images" }] : /* istanbul ignore next */ []));
21540
+ showThumbnails = input(true, ...(ngDevMode ? [{ debugName: "showThumbnails" }] : /* istanbul ignore next */ []));
21541
+ showDescription = input(true, ...(ngDevMode ? [{ debugName: "showDescription" }] : /* istanbul ignore next */ []));
21542
+ imageChange = output();
21543
+ currentIndex = signal(0, ...(ngDevMode ? [{ debugName: "currentIndex" }] : /* istanbul ignore next */ []));
21544
+ hasMultiple = computed(() => this.images().length > 1, ...(ngDevMode ? [{ debugName: "hasMultiple" }] : /* istanbul ignore next */ []));
21545
+ currentImage = computed(() => this.images()[this.currentIndex()] ?? null, ...(ngDevMode ? [{ debugName: "currentImage" }] : /* istanbul ignore next */ []));
21546
+ translatedAriaLabels = computed(() => ({
21547
+ prev: this.translation.translate('slider.prevImage'),
21548
+ next: this.translation.translate('slider.nextImage'),
21549
+ thumbnailList: this.translation.translate('slider.thumbnailList'),
21550
+ }), ...(ngDevMode ? [{ debugName: "translatedAriaLabels" }] : /* istanbul ignore next */ []));
21551
+ constructor() {
21552
+ effect(() => {
21553
+ const images = this.images();
21554
+ if (images.length > 0 && this.currentIndex() >= images.length) {
21555
+ this.currentIndex.set(0);
21556
+ }
21557
+ });
21558
+ }
21559
+ handleKeydown(event) {
21560
+ if (!this.hasMultiple()) {
21561
+ return;
21562
+ }
21563
+ switch (event.key) {
21564
+ case 'ArrowLeft':
21565
+ event.preventDefault();
21566
+ this.previous();
21567
+ break;
21568
+ case 'ArrowRight':
21569
+ event.preventDefault();
21570
+ this.next();
21571
+ break;
21572
+ case 'Home':
21573
+ event.preventDefault();
21574
+ this.goTo(0);
21575
+ break;
21576
+ case 'End':
21577
+ event.preventDefault();
21578
+ this.goTo(this.images().length - 1);
21579
+ break;
21580
+ }
21581
+ }
21582
+ previous() {
21583
+ if (!this.hasMultiple()) {
21584
+ return;
21585
+ }
21586
+ const newIndex = this.currentIndex() === 0
21587
+ ? this.images().length - 1
21588
+ : this.currentIndex() - 1;
21589
+ this.goTo(newIndex);
21590
+ }
21591
+ next() {
21592
+ if (!this.hasMultiple()) {
21593
+ return;
21594
+ }
21595
+ const newIndex = this.currentIndex() === this.images().length - 1
21596
+ ? 0
21597
+ : this.currentIndex() + 1;
21598
+ this.goTo(newIndex);
21599
+ }
21600
+ goTo(index) {
21601
+ if (index === this.currentIndex() || index < 0 || index >= this.images().length) {
21602
+ return;
21603
+ }
21604
+ this.currentIndex.set(index);
21605
+ this.imageChange.emit(index);
21606
+ this.scrollThumbnailIntoView(index);
21607
+ }
21608
+ scrollThumbnailIntoView(index) {
21609
+ const strip = this.thumbnailStripRef()?.nativeElement;
21610
+ if (!strip) {
21611
+ return;
21612
+ }
21613
+ const thumbnails = strip.children;
21614
+ if (thumbnails[index]) {
21615
+ thumbnails[index].scrollIntoView({
21616
+ behavior: 'smooth',
21617
+ block: 'nearest',
21618
+ inline: 'center',
21619
+ });
21620
+ }
21621
+ }
21622
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: JvImageSliderComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
21623
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.17", type: JvImageSliderComponent, isStandalone: true, selector: "jv-image-slider", inputs: { images: { classPropertyName: "images", publicName: "images", isSignal: true, isRequired: false, transformFunction: null }, showThumbnails: { classPropertyName: "showThumbnails", publicName: "showThumbnails", isSignal: true, isRequired: false, transformFunction: null }, showDescription: { classPropertyName: "showDescription", publicName: "showDescription", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { imageChange: "imageChange" }, host: { listeners: { "keydown": "handleKeydown($event)" } }, viewQueries: [{ propertyName: "thumbnailStripRef", first: true, predicate: ["thumbnailStrip"], descendants: true, isSignal: true }], ngImport: i0, template: `
21624
+ <div class="jv-slider">
21625
+ <div class="jv-slider-main">
21626
+ @if (hasMultiple()) {
21627
+ <button
21628
+ type="button"
21629
+ class="jv-slider-nav jv-slider-nav--prev"
21630
+ [attr.aria-label]="translatedAriaLabels().prev"
21631
+ (click)="previous()"
21632
+ >
21633
+ <jv-icon name="chevron-left" [size]="20" />
21634
+ </button>
21635
+ }
21636
+
21637
+ <div class="jv-slider-stage">
21638
+ @for (image of images(); track image.src; let i = $index) {
21639
+ <div
21640
+ class="jv-slider-slide"
21641
+ [class.jv-slider-slide--active]="i === currentIndex()"
21642
+ >
21643
+ <img
21644
+ class="jv-slider-image"
21645
+ [src]="image.src"
21646
+ [alt]="image.alt"
21647
+ loading="lazy"
21648
+ />
21649
+ </div>
21650
+ }
21651
+ </div>
21652
+
21653
+ @if (hasMultiple()) {
21654
+ <button
21655
+ type="button"
21656
+ class="jv-slider-nav jv-slider-nav--next"
21657
+ [attr.aria-label]="translatedAriaLabels().next"
21658
+ (click)="next()"
21659
+ >
21660
+ <jv-icon name="chevron-right" [size]="20" />
21661
+ </button>
21662
+ }
21663
+ </div>
21664
+
21665
+ @if (currentImage(); as image) {
21666
+ @if (showDescription() && image.description) {
21667
+ <div class="jv-slider-description">
21668
+ <p>{{ image.description }}</p>
21669
+ </div>
21670
+ }
21671
+ }
21672
+
21673
+ @if (hasMultiple() && showThumbnails()) {
21674
+ <div class="jv-slider-thumbnails" #thumbnailStrip role="tablist" [attr.aria-label]="translatedAriaLabels().thumbnailList">
21675
+ @for (image of images(); track image.src; let i = $index) {
21676
+ <button
21677
+ type="button"
21678
+ class="jv-slider-thumbnail"
21679
+ [class.jv-slider-thumbnail--active]="i === currentIndex()"
21680
+ role="tab"
21681
+ [attr.aria-selected]="i === currentIndex() ? 'true' : 'false'"
21682
+ [attr.aria-label]="image.alt"
21683
+ (click)="goTo(i)"
21684
+ >
21685
+ <img
21686
+ class="jv-slider-thumbnail-image"
21687
+ [src]="image.thumbnail || image.src"
21688
+ [alt]="image.alt"
21689
+ loading="lazy"
21690
+ />
21691
+ </button>
21692
+ }
21693
+ </div>
21694
+
21695
+ <div class="jv-slider-counter">
21696
+ {{ currentIndex() + 1 }} / {{ images().length }}
21697
+ </div>
21698
+ }
21699
+ </div>
21700
+ `, isInline: true, styles: [".jv-slider{display:grid;gap:var(--jv-spacing-sm);width:100%;max-width:100%}.jv-slider-main{position:relative;display:grid;grid-template-columns:auto 1fr auto;align-items:center;gap:var(--jv-spacing-xs)}.jv-slider-stage{position:relative;aspect-ratio:16 / 9;overflow:hidden;border-radius:var(--jv-radius-md);background:var(--jv-color-surface-muted)}.jv-slider-slide{position:absolute;inset:0;opacity:0;transition:opacity .2s ease;pointer-events:none}.jv-slider-slide--active{opacity:1;pointer-events:auto}.jv-slider-image{display:block;width:100%;height:100%;object-fit:contain;-webkit-user-select:none;user-select:none}.jv-slider-nav{display:inline-flex;align-items:center;justify-content:center;width:2.5rem;height:2.5rem;border:1px solid var(--jv-color-border);border-radius:var(--jv-radius-md);background:var(--jv-color-surface);color:var(--jv-color-foreground);cursor:pointer;transition:background-color .16s ease,border-color .16s ease,color .16s ease;flex-shrink:0;z-index:1}.jv-slider-nav:hover{background:var(--jv-color-surface-muted);border-color:var(--jv-color-foreground-muted)}.jv-slider-nav:focus-visible{outline:2px solid var(--jv-color-primary);outline-offset:1px}.jv-slider-description{padding:var(--jv-spacing-sm) 0}.jv-slider-description p{margin:0;font-size:var(--jv-density-font-size);color:var(--jv-color-foreground-muted);line-height:1.5}.jv-slider-thumbnails{display:flex;gap:var(--jv-spacing-xs);overflow-x:auto;scrollbar-width:none;-ms-overflow-style:none;padding:var(--jv-spacing-xs) 0}.jv-slider-thumbnails::-webkit-scrollbar{display:none}.jv-slider-thumbnail{flex:0 0 auto;width:3.5rem;height:3.5rem;padding:0;border:2px solid transparent;border-radius:var(--jv-radius-sm);background:transparent;cursor:pointer;overflow:hidden;transition:border-color .16s ease,opacity .16s ease;opacity:.6}.jv-slider-thumbnail:hover{opacity:.85}.jv-slider-thumbnail--active{border-color:var(--jv-color-primary);opacity:1}.jv-slider-thumbnail:focus-visible{outline:2px solid var(--jv-color-primary);outline-offset:1px}.jv-slider-thumbnail-image{display:block;width:100%;height:100%;object-fit:cover}.jv-slider-counter{text-align:center;font-size:.8125rem;color:var(--jv-color-foreground-muted);font-variant-numeric:tabular-nums}@media(max-width:640px){.jv-slider-nav{width:2rem;height:2rem}.jv-slider-thumbnail{width:2.75rem;height:2.75rem}}@media(prefers-reduced-motion:reduce){.jv-slider-slide{transition:none}}\n"], dependencies: [{ kind: "component", type: JvIconComponent, selector: "jv-icon", inputs: ["name", "size", "strokeWidth", "decorative", "ariaLabel"] }] });
21701
+ }
21702
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: JvImageSliderComponent, decorators: [{
21703
+ type: Component,
21704
+ args: [{ selector: 'jv-image-slider', standalone: true, imports: [JvIconComponent], template: `
21705
+ <div class="jv-slider">
21706
+ <div class="jv-slider-main">
21707
+ @if (hasMultiple()) {
21708
+ <button
21709
+ type="button"
21710
+ class="jv-slider-nav jv-slider-nav--prev"
21711
+ [attr.aria-label]="translatedAriaLabels().prev"
21712
+ (click)="previous()"
21713
+ >
21714
+ <jv-icon name="chevron-left" [size]="20" />
21715
+ </button>
21716
+ }
21717
+
21718
+ <div class="jv-slider-stage">
21719
+ @for (image of images(); track image.src; let i = $index) {
21720
+ <div
21721
+ class="jv-slider-slide"
21722
+ [class.jv-slider-slide--active]="i === currentIndex()"
21723
+ >
21724
+ <img
21725
+ class="jv-slider-image"
21726
+ [src]="image.src"
21727
+ [alt]="image.alt"
21728
+ loading="lazy"
21729
+ />
21730
+ </div>
21731
+ }
21732
+ </div>
21733
+
21734
+ @if (hasMultiple()) {
21735
+ <button
21736
+ type="button"
21737
+ class="jv-slider-nav jv-slider-nav--next"
21738
+ [attr.aria-label]="translatedAriaLabels().next"
21739
+ (click)="next()"
21740
+ >
21741
+ <jv-icon name="chevron-right" [size]="20" />
21742
+ </button>
21743
+ }
21744
+ </div>
21745
+
21746
+ @if (currentImage(); as image) {
21747
+ @if (showDescription() && image.description) {
21748
+ <div class="jv-slider-description">
21749
+ <p>{{ image.description }}</p>
21750
+ </div>
21751
+ }
21752
+ }
21753
+
21754
+ @if (hasMultiple() && showThumbnails()) {
21755
+ <div class="jv-slider-thumbnails" #thumbnailStrip role="tablist" [attr.aria-label]="translatedAriaLabels().thumbnailList">
21756
+ @for (image of images(); track image.src; let i = $index) {
21757
+ <button
21758
+ type="button"
21759
+ class="jv-slider-thumbnail"
21760
+ [class.jv-slider-thumbnail--active]="i === currentIndex()"
21761
+ role="tab"
21762
+ [attr.aria-selected]="i === currentIndex() ? 'true' : 'false'"
21763
+ [attr.aria-label]="image.alt"
21764
+ (click)="goTo(i)"
21765
+ >
21766
+ <img
21767
+ class="jv-slider-thumbnail-image"
21768
+ [src]="image.thumbnail || image.src"
21769
+ [alt]="image.alt"
21770
+ loading="lazy"
21771
+ />
21772
+ </button>
21773
+ }
21774
+ </div>
21775
+
21776
+ <div class="jv-slider-counter">
21777
+ {{ currentIndex() + 1 }} / {{ images().length }}
21778
+ </div>
21779
+ }
21780
+ </div>
21781
+ `, styles: [".jv-slider{display:grid;gap:var(--jv-spacing-sm);width:100%;max-width:100%}.jv-slider-main{position:relative;display:grid;grid-template-columns:auto 1fr auto;align-items:center;gap:var(--jv-spacing-xs)}.jv-slider-stage{position:relative;aspect-ratio:16 / 9;overflow:hidden;border-radius:var(--jv-radius-md);background:var(--jv-color-surface-muted)}.jv-slider-slide{position:absolute;inset:0;opacity:0;transition:opacity .2s ease;pointer-events:none}.jv-slider-slide--active{opacity:1;pointer-events:auto}.jv-slider-image{display:block;width:100%;height:100%;object-fit:contain;-webkit-user-select:none;user-select:none}.jv-slider-nav{display:inline-flex;align-items:center;justify-content:center;width:2.5rem;height:2.5rem;border:1px solid var(--jv-color-border);border-radius:var(--jv-radius-md);background:var(--jv-color-surface);color:var(--jv-color-foreground);cursor:pointer;transition:background-color .16s ease,border-color .16s ease,color .16s ease;flex-shrink:0;z-index:1}.jv-slider-nav:hover{background:var(--jv-color-surface-muted);border-color:var(--jv-color-foreground-muted)}.jv-slider-nav:focus-visible{outline:2px solid var(--jv-color-primary);outline-offset:1px}.jv-slider-description{padding:var(--jv-spacing-sm) 0}.jv-slider-description p{margin:0;font-size:var(--jv-density-font-size);color:var(--jv-color-foreground-muted);line-height:1.5}.jv-slider-thumbnails{display:flex;gap:var(--jv-spacing-xs);overflow-x:auto;scrollbar-width:none;-ms-overflow-style:none;padding:var(--jv-spacing-xs) 0}.jv-slider-thumbnails::-webkit-scrollbar{display:none}.jv-slider-thumbnail{flex:0 0 auto;width:3.5rem;height:3.5rem;padding:0;border:2px solid transparent;border-radius:var(--jv-radius-sm);background:transparent;cursor:pointer;overflow:hidden;transition:border-color .16s ease,opacity .16s ease;opacity:.6}.jv-slider-thumbnail:hover{opacity:.85}.jv-slider-thumbnail--active{border-color:var(--jv-color-primary);opacity:1}.jv-slider-thumbnail:focus-visible{outline:2px solid var(--jv-color-primary);outline-offset:1px}.jv-slider-thumbnail-image{display:block;width:100%;height:100%;object-fit:cover}.jv-slider-counter{text-align:center;font-size:.8125rem;color:var(--jv-color-foreground-muted);font-variant-numeric:tabular-nums}@media(max-width:640px){.jv-slider-nav{width:2rem;height:2rem}.jv-slider-thumbnail{width:2.75rem;height:2.75rem}}@media(prefers-reduced-motion:reduce){.jv-slider-slide{transition:none}}\n"] }]
21782
+ }], ctorParameters: () => [], propDecorators: { thumbnailStripRef: [{ type: i0.ViewChild, args: ['thumbnailStrip', { isSignal: true }] }], images: [{ type: i0.Input, args: [{ isSignal: true, alias: "images", required: false }] }], showThumbnails: [{ type: i0.Input, args: [{ isSignal: true, alias: "showThumbnails", required: false }] }], showDescription: [{ type: i0.Input, args: [{ isSignal: true, alias: "showDescription", required: false }] }], imageChange: [{ type: i0.Output, args: ["imageChange"] }], handleKeydown: [{
21783
+ type: HostListener,
21784
+ args: ['keydown', ['$event']]
21785
+ }] } });
21786
+
20803
21787
  class JvDialogService {
20804
21788
  announcementService = inject(JvAnnouncementService);
20805
21789
  translation = inject(JvTranslationService);
@@ -21683,12 +22667,25 @@ class JvTabsComponent {
21683
22667
  instanceId = ++tabsSequence;
21684
22668
  panelTemplates = contentChildren(JvTabPanelDirective, ...(ngDevMode ? [{ debugName: "panelTemplates" }] : /* istanbul ignore next */ []));
21685
22669
  tabListRef = viewChild('tabList', ...(ngDevMode ? [{ debugName: "tabListRef" }] : /* istanbul ignore next */ []));
22670
+ config = inject(JvUiConfigService);
21686
22671
  tabs = input([], ...(ngDevMode ? [{ debugName: "tabs" }] : /* istanbul ignore next */ []));
21687
22672
  activeTabId = input(null, ...(ngDevMode ? [{ debugName: "activeTabId" }] : /* istanbul ignore next */ []));
21688
22673
  keepMounted = input(true, ...(ngDevMode ? [{ debugName: "keepMounted" }] : /* istanbul ignore next */ []));
22674
+ transition = input(null, ...(ngDevMode ? [{ debugName: "transition" }] : /* istanbul ignore next */ []));
21689
22675
  activeTabChange = output();
21690
22676
  showScrollLeft = signal(false, ...(ngDevMode ? [{ debugName: "showScrollLeft" }] : /* istanbul ignore next */ []));
21691
22677
  showScrollRight = signal(false, ...(ngDevMode ? [{ debugName: "showScrollRight" }] : /* istanbul ignore next */ []));
22678
+ slideDirection = signal('right', ...(ngDevMode ? [{ debugName: "slideDirection" }] : /* istanbul ignore next */ []));
22679
+ outgoingPanelId = signal(null, ...(ngDevMode ? [{ debugName: "outgoingPanelId" }] : /* istanbul ignore next */ []));
22680
+ previousActiveTabId = signal(null, ...(ngDevMode ? [{ debugName: "previousActiveTabId" }] : /* istanbul ignore next */ []));
22681
+ effectiveTransition = computed(() => {
22682
+ const local = this.transition();
22683
+ if (local !== null) {
22684
+ return local === 'slide' && this.keepMounted() ? 'fade' : local;
22685
+ }
22686
+ const global = this.config.snapshot.tabsTransition;
22687
+ return global === 'slide' && this.keepMounted() ? 'fade' : global;
22688
+ }, ...(ngDevMode ? [{ debugName: "effectiveTransition" }] : /* istanbul ignore next */ []));
21692
22689
  resolvedActiveTabId = computed(() => {
21693
22690
  const enabledTabs = this.tabs().filter((tab) => !tab.disabled);
21694
22691
  const requestedId = this.activeTabId();
@@ -21708,6 +22705,20 @@ class JvTabsComponent {
21708
22705
  this.updateScrollControls();
21709
22706
  });
21710
22707
  });
22708
+ effect(() => {
22709
+ const activeId = this.resolvedActiveTabId();
22710
+ const prevId = this.previousActiveTabId();
22711
+ if (activeId !== prevId && prevId !== null && !this.keepMounted()) {
22712
+ const enabledTabs = this.tabs().filter((t) => !t.disabled);
22713
+ const newIndex = enabledTabs.findIndex((t) => t.id === activeId);
22714
+ const oldIndex = enabledTabs.findIndex((t) => t.id === prevId);
22715
+ if (newIndex !== oldIndex) {
22716
+ this.slideDirection.set(newIndex > oldIndex ? 'left' : 'right');
22717
+ }
22718
+ this.outgoingPanelId.set(prevId);
22719
+ }
22720
+ this.previousActiveTabId.set(activeId);
22721
+ });
21711
22722
  }
21712
22723
  ngAfterViewInit() {
21713
22724
  this.updateScrollControls();
@@ -21732,6 +22743,14 @@ class JvTabsComponent {
21732
22743
  this.activeTabChange.emit(tabId);
21733
22744
  queueMicrotask(() => this.focusTabButton(tabId));
21734
22745
  }
22746
+ handleTransitionEnd(event) {
22747
+ if (event.propertyName !== 'opacity' && event.propertyName !== 'transform') {
22748
+ return;
22749
+ }
22750
+ if (!this.keepMounted() && this.outgoingPanelId()) {
22751
+ this.outgoingPanelId.set(null);
22752
+ }
22753
+ }
21735
22754
  handleKeydown(event, currentTabId) {
21736
22755
  const enabledTabs = this.tabs().filter((tab) => !tab.disabled);
21737
22756
  if (enabledTabs.length === 0) {
@@ -21812,7 +22831,7 @@ class JvTabsComponent {
21812
22831
  return (Array.from(tabList.querySelectorAll('[data-jv-tab-id]')).find((button) => button.dataset['jvTabId'] === tabId) ?? null);
21813
22832
  }
21814
22833
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: JvTabsComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
21815
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.17", type: JvTabsComponent, isStandalone: true, selector: "jv-tabs", inputs: { tabs: { classPropertyName: "tabs", publicName: "tabs", isSignal: true, isRequired: false, transformFunction: null }, activeTabId: { classPropertyName: "activeTabId", publicName: "activeTabId", isSignal: true, isRequired: false, transformFunction: null }, keepMounted: { classPropertyName: "keepMounted", publicName: "keepMounted", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { activeTabChange: "activeTabChange" }, host: { listeners: { "window:resize": "handleWindowResize()" } }, queries: [{ propertyName: "panelTemplates", predicate: JvTabPanelDirective, isSignal: true }], viewQueries: [{ propertyName: "tabListRef", first: true, predicate: ["tabList"], descendants: true, isSignal: true }], ngImport: i0, template: `
22834
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.17", type: JvTabsComponent, isStandalone: true, selector: "jv-tabs", inputs: { tabs: { classPropertyName: "tabs", publicName: "tabs", isSignal: true, isRequired: false, transformFunction: null }, activeTabId: { classPropertyName: "activeTabId", publicName: "activeTabId", isSignal: true, isRequired: false, transformFunction: null }, keepMounted: { classPropertyName: "keepMounted", publicName: "keepMounted", isSignal: true, isRequired: false, transformFunction: null }, transition: { classPropertyName: "transition", publicName: "transition", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { activeTabChange: "activeTabChange" }, host: { listeners: { "window:resize": "handleWindowResize()" } }, queries: [{ propertyName: "panelTemplates", predicate: JvTabPanelDirective, isSignal: true }], viewQueries: [{ propertyName: "tabListRef", first: true, predicate: ["tabList"], descendants: true, isSignal: true }], ngImport: i0, template: `
21816
22835
  <div class="jv-tabs">
21817
22836
  <div class="jv-tabs-header">
21818
22837
  <div class="jv-tabs-scroll" #tabList role="tablist" aria-orientation="horizontal" (scroll)="updateScrollControls()">
@@ -21858,29 +22877,52 @@ class JvTabsComponent {
21858
22877
  <div
21859
22878
  class="jv-tab-panel"
21860
22879
  [class.jv-tab-panel--hidden]="!isActive(tab.id)"
22880
+ [class.jv-tab-panel--fade]="effectiveTransition() === 'fade'"
22881
+ [class.jv-tab-panel--slide-left]="effectiveTransition() === 'slide' && slideDirection() === 'left'"
22882
+ [class.jv-tab-panel--slide-right]="effectiveTransition() === 'slide' && slideDirection() === 'right'"
21861
22883
  [id]="panelId(tab.id)"
21862
22884
  role="tabpanel"
21863
22885
  [attr.aria-labelledby]="tabButtonId(tab.id)"
21864
22886
  [attr.tabindex]="isActive(tab.id) ? 0 : null"
21865
- [hidden]="!isActive(tab.id)"
21866
22887
  >
21867
22888
  <ng-container [ngTemplateOutlet]="panelTemplate(tab.id) ?? null" />
21868
22889
  </div>
21869
22890
  }
21870
- } @else if (resolvedActiveTabId(); as activeId) {
21871
- <div
21872
- class="jv-tab-panel"
21873
- [id]="panelId(activeId)"
21874
- role="tabpanel"
21875
- [attr.aria-labelledby]="tabButtonId(activeId)"
21876
- tabindex="0"
21877
- >
21878
- <ng-container [ngTemplateOutlet]="panelTemplate(activeId) ?? null" />
21879
- </div>
22891
+ } @else {
22892
+ @if (outgoingPanelId(); as outgoingId) {
22893
+ <div
22894
+ class="jv-tab-panel jv-tab-panel--exit"
22895
+ [class.jv-tab-panel--fade]="effectiveTransition() === 'fade'"
22896
+ [class.jv-tab-panel--slide-left]="effectiveTransition() === 'slide' && slideDirection() === 'left'"
22897
+ [class.jv-tab-panel--slide-right]="effectiveTransition() === 'slide' && slideDirection() === 'right'"
22898
+ [id]="panelId(outgoingId)"
22899
+ role="tabpanel"
22900
+ [attr.aria-labelledby]="tabButtonId(outgoingId)"
22901
+ tabindex="-1"
22902
+ (transitionend)="handleTransitionEnd($event)"
22903
+ >
22904
+ <ng-container [ngTemplateOutlet]="panelTemplate(outgoingId) ?? null" />
22905
+ </div>
22906
+ }
22907
+
22908
+ @if (resolvedActiveTabId(); as activeId) {
22909
+ <div
22910
+ class="jv-tab-panel jv-tab-panel--entering"
22911
+ [class.jv-tab-panel--fade]="effectiveTransition() === 'fade'"
22912
+ [class.jv-tab-panel--slide-left]="effectiveTransition() === 'slide' && slideDirection() === 'left'"
22913
+ [class.jv-tab-panel--slide-right]="effectiveTransition() === 'slide' && slideDirection() === 'right'"
22914
+ [id]="panelId(activeId)"
22915
+ role="tabpanel"
22916
+ [attr.aria-labelledby]="tabButtonId(activeId)"
22917
+ tabindex="0"
22918
+ >
22919
+ <ng-container [ngTemplateOutlet]="panelTemplate(activeId) ?? null" />
22920
+ </div>
22921
+ }
21880
22922
  }
21881
22923
  </div>
21882
22924
  </div>
21883
- `, isInline: true, styles: [".jv-tabs{display:grid;gap:var(--jv-spacing-md)}.jv-tabs-header{display:flex;align-items:center;gap:var(--jv-spacing-sm);min-width:0}.jv-tabs-scroll{flex:1;min-width:0;display:flex;align-items:stretch;gap:var(--jv-spacing-xs);overflow-x:auto;overflow-y:hidden;scrollbar-width:none;-ms-overflow-style:none;border-bottom:1px solid var(--jv-color-border);scroll-behavior:smooth}.jv-tabs-scroll::-webkit-scrollbar{display:none}.jv-tab{position:relative;display:inline-flex;align-items:center;gap:var(--jv-spacing-xs);flex:0 0 auto;min-height:var(--jv-density-control-height);padding:0 var(--jv-spacing-md);border:0;border-bottom:2px solid transparent;background:transparent;color:var(--jv-color-foreground-muted);font:inherit;font-weight:600;white-space:nowrap;cursor:pointer;transition:color .16s ease,border-color .16s ease,background-color .16s ease}.jv-tab:hover:not(:disabled){color:var(--jv-color-foreground);background:color-mix(in srgb,var(--jv-color-primary) 6%,transparent)}.jv-tab:focus-visible{outline:2px solid var(--jv-color-primary);outline-offset:-2px;border-radius:var(--jv-radius-sm) var(--jv-radius-sm) 0 0}.jv-tab--active{color:var(--jv-color-primary);border-bottom-color:var(--jv-color-primary)}.jv-tab--disabled{opacity:.5;cursor:not-allowed}.jv-tabs-controls{display:inline-flex;align-items:center;gap:var(--jv-spacing-xs)}.jv-tabs-scroll-button{display:inline-flex;align-items:center;justify-content:center;width:2rem;height:2rem;border:1px solid var(--jv-color-border);border-radius:var(--jv-radius-md);background:var(--jv-color-surface);color:var(--jv-color-foreground);cursor:pointer}.jv-tabs-scroll-button:disabled{opacity:.4;cursor:default}.jv-tab-panel{outline:none}\n"], dependencies: [{ kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: JvIconComponent, selector: "jv-icon", inputs: ["name", "size", "strokeWidth", "decorative", "ariaLabel"] }] });
22925
+ `, isInline: true, styles: [".jv-tabs{display:grid;gap:var(--jv-spacing-md)}.jv-tabs-header{display:flex;align-items:center;gap:var(--jv-spacing-sm);min-width:0}.jv-tabs-scroll{flex:1;min-width:0;display:flex;align-items:stretch;gap:var(--jv-spacing-xs);overflow-x:auto;overflow-y:hidden;scrollbar-width:none;-ms-overflow-style:none;border-bottom:1px solid var(--jv-color-border);scroll-behavior:smooth}.jv-tabs-scroll::-webkit-scrollbar{display:none}.jv-tab{position:relative;display:inline-flex;align-items:center;gap:var(--jv-spacing-xs);flex:0 0 auto;min-height:var(--jv-density-control-height);padding:0 var(--jv-spacing-md);border:0;border-bottom:2px solid transparent;background:transparent;color:var(--jv-color-foreground-muted);font:inherit;font-weight:600;white-space:nowrap;cursor:pointer;transition:color .16s ease,border-color .16s ease,background-color .16s ease}.jv-tab:hover:not(:disabled){color:var(--jv-color-foreground);background:color-mix(in srgb,var(--jv-color-primary) 6%,transparent)}.jv-tab:focus-visible{outline:2px solid var(--jv-color-primary);outline-offset:-2px;border-radius:var(--jv-radius-sm) var(--jv-radius-sm) 0 0}.jv-tab--active{color:var(--jv-color-primary);border-bottom-color:var(--jv-color-primary)}.jv-tab--disabled{opacity:.5;cursor:not-allowed}.jv-tabs-controls{display:inline-flex;align-items:center;gap:var(--jv-spacing-xs)}.jv-tabs-scroll-button{display:inline-flex;align-items:center;justify-content:center;width:2rem;height:2rem;border:1px solid var(--jv-color-border);border-radius:var(--jv-radius-md);background:var(--jv-color-surface);color:var(--jv-color-foreground);cursor:pointer}.jv-tabs-scroll-button:disabled{opacity:.4;cursor:default}.jv-tab-panel{outline:none}.jv-tab-panel--hidden{display:none}.jv-tab-panel--fade{transition:opacity .18s ease}.jv-tab-panel--hidden.jv-tab-panel--fade{display:block;opacity:0;pointer-events:none}.jv-tab-panel--slide-left,.jv-tab-panel--slide-right{transition:opacity .18s ease,transform .18s ease}.jv-tab-panel--hidden.jv-tab-panel--slide-left{display:block;opacity:0;transform:translate(20px);pointer-events:none}.jv-tab-panel--hidden.jv-tab-panel--slide-right{display:block;opacity:0;transform:translate(-20px);pointer-events:none}.jv-tab-panel--exit.jv-tab-panel--fade{position:absolute;opacity:0;pointer-events:none}.jv-tab-panel--exit.jv-tab-panel--slide-left{position:absolute;opacity:0;transform:translate(-20px);pointer-events:none}.jv-tab-panel--exit.jv-tab-panel--slide-right{position:absolute;opacity:0;transform:translate(20px);pointer-events:none}.jv-tab-panel--entering{animation:jv-tab-enter .18s ease forwards}.jv-tab-panel--entering.jv-tab-panel--slide-left{animation-name:jv-tab-slide-from-right}.jv-tab-panel--entering.jv-tab-panel--slide-right{animation-name:jv-tab-slide-from-left}@keyframes jv-tab-enter{0%{opacity:0}to{opacity:1}}@keyframes jv-tab-slide-from-right{0%{opacity:0;transform:translate(20px)}to{opacity:1;transform:translate(0)}}@keyframes jv-tab-slide-from-left{0%{opacity:0;transform:translate(-20px)}to{opacity:1;transform:translate(0)}}@media(prefers-reduced-motion:reduce){.jv-tab-panel--fade,.jv-tab-panel--slide-left,.jv-tab-panel--slide-right,.jv-tab-panel--entering{transition:none;animation:none}.jv-tab-panel--hidden.jv-tab-panel--fade,.jv-tab-panel--hidden.jv-tab-panel--slide-left,.jv-tab-panel--hidden.jv-tab-panel--slide-right{opacity:0}}\n"], dependencies: [{ kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: JvIconComponent, selector: "jv-icon", inputs: ["name", "size", "strokeWidth", "decorative", "ariaLabel"] }] });
21884
22926
  }
21885
22927
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: JvTabsComponent, decorators: [{
21886
22928
  type: Component,
@@ -21930,30 +22972,53 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImpo
21930
22972
  <div
21931
22973
  class="jv-tab-panel"
21932
22974
  [class.jv-tab-panel--hidden]="!isActive(tab.id)"
22975
+ [class.jv-tab-panel--fade]="effectiveTransition() === 'fade'"
22976
+ [class.jv-tab-panel--slide-left]="effectiveTransition() === 'slide' && slideDirection() === 'left'"
22977
+ [class.jv-tab-panel--slide-right]="effectiveTransition() === 'slide' && slideDirection() === 'right'"
21933
22978
  [id]="panelId(tab.id)"
21934
22979
  role="tabpanel"
21935
22980
  [attr.aria-labelledby]="tabButtonId(tab.id)"
21936
22981
  [attr.tabindex]="isActive(tab.id) ? 0 : null"
21937
- [hidden]="!isActive(tab.id)"
21938
22982
  >
21939
22983
  <ng-container [ngTemplateOutlet]="panelTemplate(tab.id) ?? null" />
21940
22984
  </div>
21941
22985
  }
21942
- } @else if (resolvedActiveTabId(); as activeId) {
21943
- <div
21944
- class="jv-tab-panel"
21945
- [id]="panelId(activeId)"
21946
- role="tabpanel"
21947
- [attr.aria-labelledby]="tabButtonId(activeId)"
21948
- tabindex="0"
21949
- >
21950
- <ng-container [ngTemplateOutlet]="panelTemplate(activeId) ?? null" />
21951
- </div>
22986
+ } @else {
22987
+ @if (outgoingPanelId(); as outgoingId) {
22988
+ <div
22989
+ class="jv-tab-panel jv-tab-panel--exit"
22990
+ [class.jv-tab-panel--fade]="effectiveTransition() === 'fade'"
22991
+ [class.jv-tab-panel--slide-left]="effectiveTransition() === 'slide' && slideDirection() === 'left'"
22992
+ [class.jv-tab-panel--slide-right]="effectiveTransition() === 'slide' && slideDirection() === 'right'"
22993
+ [id]="panelId(outgoingId)"
22994
+ role="tabpanel"
22995
+ [attr.aria-labelledby]="tabButtonId(outgoingId)"
22996
+ tabindex="-1"
22997
+ (transitionend)="handleTransitionEnd($event)"
22998
+ >
22999
+ <ng-container [ngTemplateOutlet]="panelTemplate(outgoingId) ?? null" />
23000
+ </div>
23001
+ }
23002
+
23003
+ @if (resolvedActiveTabId(); as activeId) {
23004
+ <div
23005
+ class="jv-tab-panel jv-tab-panel--entering"
23006
+ [class.jv-tab-panel--fade]="effectiveTransition() === 'fade'"
23007
+ [class.jv-tab-panel--slide-left]="effectiveTransition() === 'slide' && slideDirection() === 'left'"
23008
+ [class.jv-tab-panel--slide-right]="effectiveTransition() === 'slide' && slideDirection() === 'right'"
23009
+ [id]="panelId(activeId)"
23010
+ role="tabpanel"
23011
+ [attr.aria-labelledby]="tabButtonId(activeId)"
23012
+ tabindex="0"
23013
+ >
23014
+ <ng-container [ngTemplateOutlet]="panelTemplate(activeId) ?? null" />
23015
+ </div>
23016
+ }
21952
23017
  }
21953
23018
  </div>
21954
23019
  </div>
21955
- `, styles: [".jv-tabs{display:grid;gap:var(--jv-spacing-md)}.jv-tabs-header{display:flex;align-items:center;gap:var(--jv-spacing-sm);min-width:0}.jv-tabs-scroll{flex:1;min-width:0;display:flex;align-items:stretch;gap:var(--jv-spacing-xs);overflow-x:auto;overflow-y:hidden;scrollbar-width:none;-ms-overflow-style:none;border-bottom:1px solid var(--jv-color-border);scroll-behavior:smooth}.jv-tabs-scroll::-webkit-scrollbar{display:none}.jv-tab{position:relative;display:inline-flex;align-items:center;gap:var(--jv-spacing-xs);flex:0 0 auto;min-height:var(--jv-density-control-height);padding:0 var(--jv-spacing-md);border:0;border-bottom:2px solid transparent;background:transparent;color:var(--jv-color-foreground-muted);font:inherit;font-weight:600;white-space:nowrap;cursor:pointer;transition:color .16s ease,border-color .16s ease,background-color .16s ease}.jv-tab:hover:not(:disabled){color:var(--jv-color-foreground);background:color-mix(in srgb,var(--jv-color-primary) 6%,transparent)}.jv-tab:focus-visible{outline:2px solid var(--jv-color-primary);outline-offset:-2px;border-radius:var(--jv-radius-sm) var(--jv-radius-sm) 0 0}.jv-tab--active{color:var(--jv-color-primary);border-bottom-color:var(--jv-color-primary)}.jv-tab--disabled{opacity:.5;cursor:not-allowed}.jv-tabs-controls{display:inline-flex;align-items:center;gap:var(--jv-spacing-xs)}.jv-tabs-scroll-button{display:inline-flex;align-items:center;justify-content:center;width:2rem;height:2rem;border:1px solid var(--jv-color-border);border-radius:var(--jv-radius-md);background:var(--jv-color-surface);color:var(--jv-color-foreground);cursor:pointer}.jv-tabs-scroll-button:disabled{opacity:.4;cursor:default}.jv-tab-panel{outline:none}\n"] }]
21956
- }], ctorParameters: () => [], propDecorators: { panelTemplates: [{ type: i0.ContentChildren, args: [i0.forwardRef(() => JvTabPanelDirective), { isSignal: true }] }], tabListRef: [{ type: i0.ViewChild, args: ['tabList', { isSignal: true }] }], tabs: [{ type: i0.Input, args: [{ isSignal: true, alias: "tabs", required: false }] }], activeTabId: [{ type: i0.Input, args: [{ isSignal: true, alias: "activeTabId", required: false }] }], keepMounted: [{ type: i0.Input, args: [{ isSignal: true, alias: "keepMounted", required: false }] }], activeTabChange: [{ type: i0.Output, args: ["activeTabChange"] }], handleWindowResize: [{
23020
+ `, styles: [".jv-tabs{display:grid;gap:var(--jv-spacing-md)}.jv-tabs-header{display:flex;align-items:center;gap:var(--jv-spacing-sm);min-width:0}.jv-tabs-scroll{flex:1;min-width:0;display:flex;align-items:stretch;gap:var(--jv-spacing-xs);overflow-x:auto;overflow-y:hidden;scrollbar-width:none;-ms-overflow-style:none;border-bottom:1px solid var(--jv-color-border);scroll-behavior:smooth}.jv-tabs-scroll::-webkit-scrollbar{display:none}.jv-tab{position:relative;display:inline-flex;align-items:center;gap:var(--jv-spacing-xs);flex:0 0 auto;min-height:var(--jv-density-control-height);padding:0 var(--jv-spacing-md);border:0;border-bottom:2px solid transparent;background:transparent;color:var(--jv-color-foreground-muted);font:inherit;font-weight:600;white-space:nowrap;cursor:pointer;transition:color .16s ease,border-color .16s ease,background-color .16s ease}.jv-tab:hover:not(:disabled){color:var(--jv-color-foreground);background:color-mix(in srgb,var(--jv-color-primary) 6%,transparent)}.jv-tab:focus-visible{outline:2px solid var(--jv-color-primary);outline-offset:-2px;border-radius:var(--jv-radius-sm) var(--jv-radius-sm) 0 0}.jv-tab--active{color:var(--jv-color-primary);border-bottom-color:var(--jv-color-primary)}.jv-tab--disabled{opacity:.5;cursor:not-allowed}.jv-tabs-controls{display:inline-flex;align-items:center;gap:var(--jv-spacing-xs)}.jv-tabs-scroll-button{display:inline-flex;align-items:center;justify-content:center;width:2rem;height:2rem;border:1px solid var(--jv-color-border);border-radius:var(--jv-radius-md);background:var(--jv-color-surface);color:var(--jv-color-foreground);cursor:pointer}.jv-tabs-scroll-button:disabled{opacity:.4;cursor:default}.jv-tab-panel{outline:none}.jv-tab-panel--hidden{display:none}.jv-tab-panel--fade{transition:opacity .18s ease}.jv-tab-panel--hidden.jv-tab-panel--fade{display:block;opacity:0;pointer-events:none}.jv-tab-panel--slide-left,.jv-tab-panel--slide-right{transition:opacity .18s ease,transform .18s ease}.jv-tab-panel--hidden.jv-tab-panel--slide-left{display:block;opacity:0;transform:translate(20px);pointer-events:none}.jv-tab-panel--hidden.jv-tab-panel--slide-right{display:block;opacity:0;transform:translate(-20px);pointer-events:none}.jv-tab-panel--exit.jv-tab-panel--fade{position:absolute;opacity:0;pointer-events:none}.jv-tab-panel--exit.jv-tab-panel--slide-left{position:absolute;opacity:0;transform:translate(-20px);pointer-events:none}.jv-tab-panel--exit.jv-tab-panel--slide-right{position:absolute;opacity:0;transform:translate(20px);pointer-events:none}.jv-tab-panel--entering{animation:jv-tab-enter .18s ease forwards}.jv-tab-panel--entering.jv-tab-panel--slide-left{animation-name:jv-tab-slide-from-right}.jv-tab-panel--entering.jv-tab-panel--slide-right{animation-name:jv-tab-slide-from-left}@keyframes jv-tab-enter{0%{opacity:0}to{opacity:1}}@keyframes jv-tab-slide-from-right{0%{opacity:0;transform:translate(20px)}to{opacity:1;transform:translate(0)}}@keyframes jv-tab-slide-from-left{0%{opacity:0;transform:translate(-20px)}to{opacity:1;transform:translate(0)}}@media(prefers-reduced-motion:reduce){.jv-tab-panel--fade,.jv-tab-panel--slide-left,.jv-tab-panel--slide-right,.jv-tab-panel--entering{transition:none;animation:none}.jv-tab-panel--hidden.jv-tab-panel--fade,.jv-tab-panel--hidden.jv-tab-panel--slide-left,.jv-tab-panel--hidden.jv-tab-panel--slide-right{opacity:0}}\n"] }]
23021
+ }], ctorParameters: () => [], propDecorators: { panelTemplates: [{ type: i0.ContentChildren, args: [i0.forwardRef(() => JvTabPanelDirective), { isSignal: true }] }], tabListRef: [{ type: i0.ViewChild, args: ['tabList', { isSignal: true }] }], tabs: [{ type: i0.Input, args: [{ isSignal: true, alias: "tabs", required: false }] }], activeTabId: [{ type: i0.Input, args: [{ isSignal: true, alias: "activeTabId", required: false }] }], keepMounted: [{ type: i0.Input, args: [{ isSignal: true, alias: "keepMounted", required: false }] }], transition: [{ type: i0.Input, args: [{ isSignal: true, alias: "transition", required: false }] }], activeTabChange: [{ type: i0.Output, args: ["activeTabChange"] }], handleWindowResize: [{
21957
23022
  type: HostListener,
21958
23023
  args: ['window:resize']
21959
23024
  }] } });
@@ -22154,6 +23219,7 @@ const JV_GRID_DEFAULT_OPTIONS = {
22154
23219
  let gridIdSequence = 0;
22155
23220
  class JvGridComponent {
22156
23221
  gridId = `jv-grid-${++gridIdSequence}`;
23222
+ formatService = inject(JvFormatService);
22157
23223
  translationService = inject(JvTranslationService);
22158
23224
  data = input([], ...(ngDevMode ? [{ debugName: "data" }] : /* istanbul ignore next */ []));
22159
23225
  columns = input([], ...(ngDevMode ? [{ debugName: "columns" }] : /* istanbul ignore next */ []));
@@ -22222,7 +23288,7 @@ class JvGridComponent {
22222
23288
  const value = this.getRawCellValue(row, col);
22223
23289
  if (value == null)
22224
23290
  return false;
22225
- return String(value).toLowerCase().includes(term);
23291
+ return this.getSearchValue(value, col).includes(term);
22226
23292
  });
22227
23293
  });
22228
23294
  }
@@ -22236,7 +23302,7 @@ class JvGridComponent {
22236
23302
  const col = this.columns().find(c => this.colKey(c) === key);
22237
23303
  if (!col || col.filterable === false)
22238
23304
  return true;
22239
- const value = String(this.getRawCellValue(row, col) ?? '').toLowerCase();
23305
+ const value = this.getSearchValue(this.getRawCellValue(row, col), col);
22240
23306
  const fv = filter.value.toLowerCase();
22241
23307
  switch (filter.operator) {
22242
23308
  case 'contains': return value.includes(fv);
@@ -22258,8 +23324,9 @@ class JvGridComponent {
22258
23324
  if (!columnKey || !direction)
22259
23325
  return items;
22260
23326
  return [...items].sort((a, b) => {
22261
- const va = this.getRawCellValue(a, this.findColumn(columnKey));
22262
- const vb = this.getRawCellValue(b, this.findColumn(columnKey));
23327
+ const col = this.findColumn(columnKey);
23328
+ const va = this.getComparableValue(this.getRawCellValue(a, col), col);
23329
+ const vb = this.getComparableValue(this.getRawCellValue(b, col), col);
22263
23330
  if (va == null)
22264
23331
  return 1;
22265
23332
  if (vb == null)
@@ -22363,14 +23430,56 @@ class JvGridComponent {
22363
23430
  const val = this.getCellValue(row, col);
22364
23431
  return val == null ? '' : String(val);
22365
23432
  }
22366
- formatValue(value, col) {
23433
+ formatValue(value, col, _row) {
22367
23434
  if (value == null)
22368
23435
  return '';
22369
- if (col.type === 'date' && value instanceof Date) {
22370
- return value.toLocaleDateString();
23436
+ const type = this.resolveColumnType(col);
23437
+ if (type === 'text' || type === 'boolean') {
23438
+ return String(value);
23439
+ }
23440
+ return this.formatService.format(type, value, this.getFormatOverrides(col));
23441
+ }
23442
+ getComparableValue(value, col) {
23443
+ const type = this.resolveColumnType(col);
23444
+ if (value == null || value === '')
23445
+ return null;
23446
+ if (type === 'text') {
23447
+ return String(value).toLowerCase();
23448
+ }
23449
+ if (type === 'boolean') {
23450
+ return Boolean(value);
23451
+ }
23452
+ return this.formatService.toComparable(type, value);
23453
+ }
23454
+ getSearchValue(value, col) {
23455
+ const type = this.resolveColumnType(col);
23456
+ if (type === 'text' || type === 'boolean') {
23457
+ return String(value ?? '').toLowerCase();
23458
+ }
23459
+ return this.formatService.toSearchValue(type, value);
23460
+ }
23461
+ getExportValue(value, col) {
23462
+ if (value == null)
23463
+ return '';
23464
+ const type = this.resolveColumnType(col);
23465
+ if (type === 'date' || type === 'time' || type === 'datetime') {
23466
+ const date = this.formatService.toDate(value);
23467
+ if (!date)
23468
+ return String(value);
23469
+ if (type === 'date')
23470
+ return date.toISOString().slice(0, 10);
23471
+ if (type === 'time')
23472
+ return date.toISOString().slice(11, 19);
23473
+ return date.toISOString();
22371
23474
  }
22372
23475
  return String(value);
22373
23476
  }
23477
+ resolveColumnType(col) {
23478
+ return col.type ?? 'text';
23479
+ }
23480
+ getFormatOverrides(col) {
23481
+ return col.formatOptions;
23482
+ }
22374
23483
  getAriaSort(col) {
22375
23484
  if (col.sortable === false || !this.resolvedOptions().sortable)
22376
23485
  return null;
@@ -22618,14 +23727,14 @@ class JvGridComponent {
22618
23727
  }
22619
23728
  // Export
22620
23729
  exportCsv() {
22621
- const cols = this.visibleColumns().map(c => ({ key: this.colKey(c), header: c.header }));
23730
+ const cols = this.visibleColumns().map(c => ({ key: this.colKey(c), header: c.header, column: c }));
22622
23731
  const allData = this.effectiveOptions().serverSide ? this.data() : this.sortedData();
22623
23732
  const headerRow = cols.map(c => `"${c.header}"`).join(',');
22624
23733
  const dataRows = allData.map(row => cols.map(col => {
22625
23734
  const val = row[col.key];
22626
23735
  if (val == null)
22627
23736
  return '';
22628
- return `"${String(val).replace(/"/g, '""')}"`;
23737
+ return `"${this.getExportValue(val, col.column).replace(/"/g, '""')}"`;
22629
23738
  }).join(','));
22630
23739
  const csv = [headerRow, ...dataRows].join('\r\n');
22631
23740
  const blob = new Blob([csv], { type: 'text/csv;charset=utf-8;' });
@@ -22637,13 +23746,13 @@ class JvGridComponent {
22637
23746
  URL.revokeObjectURL(url);
22638
23747
  }
22639
23748
  exportExcel() {
22640
- const cols = this.visibleColumns().map(c => ({ key: this.colKey(c), header: c.header }));
23749
+ const cols = this.visibleColumns().map(c => ({ key: this.colKey(c), header: c.header, column: c }));
22641
23750
  const allData = this.effectiveOptions().serverSide ? this.data() : this.sortedData();
22642
23751
  let html = '<table>';
22643
23752
  html += '<thead><tr>' + cols.map(c => `<th>${c.header}</th>`).join('') + '</tr></thead>';
22644
23753
  html += '<tbody>';
22645
23754
  for (const row of allData) {
22646
- html += '<tr>' + cols.map(col => `<td>${row[col.key] ?? ''}</td>`).join('') + '</tr>';
23755
+ html += '<tr>' + cols.map(col => `<td>${this.getExportValue(row[col.key], col.column)}</td>`).join('') + '</tr>';
22647
23756
  }
22648
23757
  html += '</tbody></table>';
22649
23758
  const blob = new Blob([html], { type: 'application/vnd.ms-excel' });
@@ -22924,7 +24033,7 @@ class JvGridComponent {
22924
24033
  } @else if (col.format) {
22925
24034
  {{ col.format(value, row) }}
22926
24035
  } @else {
22927
- {{ formatValue(value, col) }}
24036
+ {{ formatValue(value, col, row) }}
22928
24037
  }
22929
24038
  }
22930
24039
  </td>
@@ -23275,7 +24384,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImpo
23275
24384
  } @else if (col.format) {
23276
24385
  {{ col.format(value, row) }}
23277
24386
  } @else {
23278
- {{ formatValue(value, col) }}
24387
+ {{ formatValue(value, col, row) }}
23279
24388
  }
23280
24389
  }
23281
24390
  </td>
@@ -23524,5 +24633,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImpo
23524
24633
  * Generated bundle index. Do not edit.
23525
24634
  */
23526
24635
 
23527
- 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, 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 };
24636
+ 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, JvImageGalleryComponent, JvImageSliderComponent, JvInputComponent, JvListComponent, JvListItemComponent, JvLoaderComponent, JvLoaderService, JvLoginPageComponent, JvModalSelectorComponent, JvPageComponent, JvPaginationComponent, JvRadioComponent, JvSectionComponent, JvSelectComponent, JvSidebarComponent, JvSwitchComponent, JvTabPanelDirective, JvTabsComponent, JvTextareaComponent, JvThemeService, JvToastComponent, JvToastService, JvTooltipDirective, JvTopbarComponent, JvTranslationService, provideJvUi };
23528
24637
  //# sourceMappingURL=devjuliovilla-jv-ui.mjs.map