@devjuliovilla/jv-ui 1.12.0 → 1.14.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, inject, signal, Injectable, InjectionToken, makeEnvironmentProviders, ENVIRONMENT_INITIALIZER, Directive, contentChild, output, effect, forwardRef, viewChild, contentChildren, HostListener, viewChildren, numberAttribute, afterNextRender, Renderer2, ElementRef } from '@angular/core';
2
+ import { input, computed, Component, inject, signal, Injectable, InjectionToken, makeEnvironmentProviders, ENVIRONMENT_INITIALIZER, Directive, contentChild, output, effect, forwardRef, viewChild, untracked, contentChildren, HostListener, viewChildren, numberAttribute, afterNextRender, Renderer2, ElementRef, DestroyRef } from '@angular/core';
3
3
  import { DOCUMENT, NgTemplateOutlet, CommonModule } from '@angular/common';
4
4
  import * as i1 from '@angular/forms';
5
5
  import { NG_VALUE_ACCESSOR, FormBuilder, Validators, ReactiveFormsModule, FormsModule } from '@angular/forms';
@@ -18159,6 +18159,8 @@ const ES = {
18159
18159
  'grid.filterPlaceholder': 'Filtrar...',
18160
18160
  'grid.filterForColumn': 'Filtrar por ${column}',
18161
18161
  'grid.actionsLabel': 'Acciones',
18162
+ 'grid.booleanYes': 'SI',
18163
+ 'grid.booleanNo': 'NO',
18162
18164
  'pagination.pageLabel': 'Página {page}',
18163
18165
  'pagination.itemsShowing': '{start}-{end} de {total}',
18164
18166
  'pagination.of': 'de',
@@ -18178,6 +18180,8 @@ const ES = {
18178
18180
  'monthCalendar.today': 'Hoy',
18179
18181
  'monthCalendar.prevMonth': 'Mes anterior',
18180
18182
  'monthCalendar.nextMonth': 'Mes siguiente',
18183
+ 'monthCalendar.prevYear': 'Ano anterior',
18184
+ 'monthCalendar.nextYear': 'Ano siguiente',
18181
18185
  'modalSelector.placeholder': 'Seleccione una opcion',
18182
18186
  'modalSelector.searchPlaceholder': 'Buscar',
18183
18187
  'modalSelector.emptyMessage': 'No se encontraron resultados',
@@ -18236,6 +18240,8 @@ const EN = {
18236
18240
  'grid.filterPlaceholder': 'Filter...',
18237
18241
  'grid.filterForColumn': 'Filter by ${column}',
18238
18242
  'grid.actionsLabel': 'Actions',
18243
+ 'grid.booleanYes': 'YES',
18244
+ 'grid.booleanNo': 'NO',
18239
18245
  'pagination.pageLabel': 'Page {page}',
18240
18246
  'pagination.itemsShowing': '{start}-{end} of {total}',
18241
18247
  'pagination.of': 'of',
@@ -18255,6 +18261,8 @@ const EN = {
18255
18261
  'monthCalendar.today': 'Today',
18256
18262
  'monthCalendar.prevMonth': 'Previous month',
18257
18263
  'monthCalendar.nextMonth': 'Next month',
18264
+ 'monthCalendar.prevYear': 'Previous year',
18265
+ 'monthCalendar.nextYear': 'Next year',
18258
18266
  'modalSelector.placeholder': 'Select an option',
18259
18267
  'modalSelector.searchPlaceholder': 'Search',
18260
18268
  'modalSelector.emptyMessage': 'No results found',
@@ -18362,18 +18370,19 @@ function provideJvUi(config = {}) {
18362
18370
  class JvUiConfigService {
18363
18371
  defaults = inject(JV_UI_CONFIG_DEFAULTS);
18364
18372
  configLayers = (inject(JV_UI_CONFIG, { optional: true }) ?? []);
18373
+ resolvedConfig = this.configLayers.reduce((merged, layer) => ({
18374
+ ...merged,
18375
+ ...layer,
18376
+ formats: {
18377
+ ...merged.formats,
18378
+ ...(layer.formats ?? {}),
18379
+ },
18380
+ }), {
18381
+ ...this.defaults,
18382
+ formats: { ...this.defaults.formats },
18383
+ });
18365
18384
  get snapshot() {
18366
- return this.configLayers.reduce((merged, layer) => ({
18367
- ...merged,
18368
- ...layer,
18369
- formats: {
18370
- ...merged.formats,
18371
- ...(layer.formats ?? {}),
18372
- },
18373
- }), {
18374
- ...this.defaults,
18375
- formats: { ...this.defaults.formats },
18376
- });
18385
+ return this.resolvedConfig;
18377
18386
  }
18378
18387
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: JvUiConfigService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
18379
18388
  static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: JvUiConfigService, providedIn: 'root' });
@@ -18843,6 +18852,7 @@ class JvSelectComponent {
18843
18852
  required = input(false, ...(ngDevMode ? [{ debugName: "required" }] : /* istanbul ignore next */ []));
18844
18853
  invalid = input(false, ...(ngDevMode ? [{ debugName: "invalid" }] : /* istanbul ignore next */ []));
18845
18854
  describedBy = input('', ...(ngDevMode ? [{ debugName: "describedBy" }] : /* istanbul ignore next */ []));
18855
+ ariaLabel = input('', ...(ngDevMode ? [{ debugName: "ariaLabel" }] : /* istanbul ignore next */ []));
18846
18856
  inputId = input(`jv-select-${++selectIdSequence}`, ...(ngDevMode ? [{ debugName: "inputId" }] : /* istanbul ignore next */ []));
18847
18857
  modelValue = input('', ...(ngDevMode ? [{ debugName: "modelValue" }] : /* istanbul ignore next */ []));
18848
18858
  selectionChange = output();
@@ -18873,7 +18883,7 @@ class JvSelectComponent {
18873
18883
  this.onTouched();
18874
18884
  }
18875
18885
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: JvSelectComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
18876
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.17", type: JvSelectComponent, isStandalone: true, selector: "jv-select", inputs: { options: { classPropertyName: "options", publicName: "options", isSignal: true, isRequired: false, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", 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 }, describedBy: { classPropertyName: "describedBy", publicName: "describedBy", isSignal: true, isRequired: false, transformFunction: null }, inputId: { classPropertyName: "inputId", publicName: "inputId", isSignal: true, isRequired: false, transformFunction: null }, modelValue: { classPropertyName: "modelValue", publicName: "modelValue", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { selectionChange: "selectionChange" }, providers: [
18886
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.17", type: JvSelectComponent, isStandalone: true, selector: "jv-select", inputs: { options: { classPropertyName: "options", publicName: "options", isSignal: true, isRequired: false, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", 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 }, describedBy: { classPropertyName: "describedBy", publicName: "describedBy", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: false, transformFunction: null }, inputId: { classPropertyName: "inputId", publicName: "inputId", isSignal: true, isRequired: false, transformFunction: null }, modelValue: { classPropertyName: "modelValue", publicName: "modelValue", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { selectionChange: "selectionChange" }, providers: [
18877
18887
  {
18878
18888
  provide: NG_VALUE_ACCESSOR,
18879
18889
  useExisting: forwardRef(() => JvSelectComponent),
@@ -18888,16 +18898,16 @@ class JvSelectComponent {
18888
18898
  [required]="required()"
18889
18899
  [attr.aria-invalid]="invalid() ? 'true' : null"
18890
18900
  [attr.aria-describedby]="describedBy() || null"
18891
- [value]="currentValue()"
18901
+ [attr.aria-label]="ariaLabel() || null"
18892
18902
  (change)="handleChange($event)"
18893
18903
  (blur)="handleBlur()"
18894
18904
  >
18895
18905
  @if (placeholder()) {
18896
- <option value="" [disabled]="required()">{{ placeholder() }}</option>
18906
+ <option value="" [disabled]="required()" [selected]="currentValue() === ''">{{ placeholder() }}</option>
18897
18907
  }
18898
18908
 
18899
18909
  @for (option of options(); track option.label + ':' + option.value) {
18900
- <option [value]="option.value" [disabled]="option.disabled ?? false">{{ option.label }}</option>
18910
+ <option [value]="option.value" [disabled]="option.disabled ?? false" [selected]="option.value === currentValue()">{{ option.label }}</option>
18901
18911
  }
18902
18912
  </select>
18903
18913
  `, isInline: true, styles: [".jv-select{width:100%;min-height:var(--jv-density-control-height);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)}.jv-select.is-invalid{border-color:var(--jv-color-danger)}\n"] });
@@ -18919,20 +18929,20 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImpo
18919
18929
  [required]="required()"
18920
18930
  [attr.aria-invalid]="invalid() ? 'true' : null"
18921
18931
  [attr.aria-describedby]="describedBy() || null"
18922
- [value]="currentValue()"
18932
+ [attr.aria-label]="ariaLabel() || null"
18923
18933
  (change)="handleChange($event)"
18924
18934
  (blur)="handleBlur()"
18925
18935
  >
18926
18936
  @if (placeholder()) {
18927
- <option value="" [disabled]="required()">{{ placeholder() }}</option>
18937
+ <option value="" [disabled]="required()" [selected]="currentValue() === ''">{{ placeholder() }}</option>
18928
18938
  }
18929
18939
 
18930
18940
  @for (option of options(); track option.label + ':' + option.value) {
18931
- <option [value]="option.value" [disabled]="option.disabled ?? false">{{ option.label }}</option>
18941
+ <option [value]="option.value" [disabled]="option.disabled ?? false" [selected]="option.value === currentValue()">{{ option.label }}</option>
18932
18942
  }
18933
18943
  </select>
18934
18944
  `, styles: [".jv-select{width:100%;min-height:var(--jv-density-control-height);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)}.jv-select.is-invalid{border-color:var(--jv-color-danger)}\n"] }]
18935
- }], propDecorators: { options: [{ type: i0.Input, args: [{ isSignal: true, alias: "options", required: false }] }], 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 }] }], inputId: [{ type: i0.Input, args: [{ isSignal: true, alias: "inputId", required: false }] }], modelValue: [{ type: i0.Input, args: [{ isSignal: true, alias: "modelValue", required: false }] }], selectionChange: [{ type: i0.Output, args: ["selectionChange"] }] } });
18945
+ }], propDecorators: { options: [{ type: i0.Input, args: [{ isSignal: true, alias: "options", required: false }] }], 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 }] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabel", required: false }] }], inputId: [{ type: i0.Input, args: [{ isSignal: true, alias: "inputId", required: false }] }], modelValue: [{ type: i0.Input, args: [{ isSignal: true, alias: "modelValue", required: false }] }], selectionChange: [{ type: i0.Output, args: ["selectionChange"] }] } });
18936
18946
 
18937
18947
  class JvMonthCalendarDayDirective {
18938
18948
  templateRef;
@@ -18956,13 +18966,27 @@ class JvMonthCalendarComponent {
18956
18966
  gridRootRef = viewChild('gridRoot', ...(ngDevMode ? [{ debugName: "gridRootRef" }] : /* istanbul ignore next */ []));
18957
18967
  customDayTemplate = contentChild(JvMonthCalendarDayDirective, ...(ngDevMode ? [{ debugName: "customDayTemplate" }] : /* istanbul ignore next */ []));
18958
18968
  valueInput = input(undefined, { ...(ngDevMode ? { debugName: "valueInput" } : /* istanbul ignore next */ {}), alias: 'value' });
18969
+ visibleMonthInput = input(undefined, { ...(ngDevMode ? { debugName: "visibleMonthInput" } : /* istanbul ignore next */ {}), alias: 'visibleMonth' });
18959
18970
  ariaLabel = input('', ...(ngDevMode ? [{ debugName: "ariaLabel" }] : /* istanbul ignore next */ []));
18971
+ monthLabel = input('', ...(ngDevMode ? [{ debugName: "monthLabel" }] : /* istanbul ignore next */ []));
18972
+ yearLabel = input('', ...(ngDevMode ? [{ debugName: "yearLabel" }] : /* istanbul ignore next */ []));
18973
+ minYear = input(undefined, ...(ngDevMode ? [{ debugName: "minYear" }] : /* istanbul ignore next */ []));
18974
+ maxYear = input(undefined, ...(ngDevMode ? [{ debugName: "maxYear" }] : /* istanbul ignore next */ []));
18975
+ navigation = input(null, ...(ngDevMode ? [{ debugName: "navigation" }] : /* istanbul ignore next */ []));
18960
18976
  dayMeta = input([], ...(ngDevMode ? [{ debugName: "dayMeta" }] : /* istanbul ignore next */ []));
18961
18977
  selectionChange = output();
18962
18978
  visibleMonthChange = output();
18963
18979
  visibleMonth = signal(firstOfMonth$1(new Date()), ...(ngDevMode ? [{ debugName: "visibleMonth" }] : /* istanbul ignore next */ []));
18964
18980
  focusedDate = signal(startOfDay$1(new Date()), ...(ngDevMode ? [{ debugName: "focusedDate" }] : /* istanbul ignore next */ []));
18965
18981
  resolvedAriaLabel = computed(() => this.ariaLabel() || this.translation.translate('monthCalendar.title'), ...(ngDevMode ? [{ debugName: "resolvedAriaLabel" }] : /* istanbul ignore next */ []));
18982
+ resolvedMonthLabel = computed(() => this.monthLabel() || 'Month', ...(ngDevMode ? [{ debugName: "resolvedMonthLabel" }] : /* istanbul ignore next */ []));
18983
+ resolvedYearLabel = computed(() => this.yearLabel() || 'Year', ...(ngDevMode ? [{ debugName: "resolvedYearLabel" }] : /* istanbul ignore next */ []));
18984
+ resolvedNavigation = computed(() => ({
18985
+ previousMonth: this.navigation()?.previousMonth ?? true,
18986
+ nextMonth: this.navigation()?.nextMonth ?? true,
18987
+ previousYear: this.navigation()?.previousYear ?? false,
18988
+ nextYear: this.navigation()?.nextYear ?? false,
18989
+ }), ...(ngDevMode ? [{ debugName: "resolvedNavigation" }] : /* istanbul ignore next */ []));
18966
18990
  monthOptions = computed(() => {
18967
18991
  const formatter = new Intl.DateTimeFormat(this.formatService.formatLocale(), { month: 'long' });
18968
18992
  return Array.from({ length: 12 }, (_, month) => ({
@@ -18971,9 +18995,9 @@ class JvMonthCalendarComponent {
18971
18995
  }));
18972
18996
  }, ...(ngDevMode ? [{ debugName: "monthOptions" }] : /* istanbul ignore next */ []));
18973
18997
  yearOptions = computed(() => {
18974
- const currentYear = new Date().getFullYear();
18975
- return Array.from({ length: 21 }, (_, index) => {
18976
- const year = currentYear - 10 + index;
18998
+ const [startYear, endYear] = this.yearBounds();
18999
+ return Array.from({ length: endYear - startYear + 1 }, (_, index) => {
19000
+ const year = startYear + index;
18977
19001
  return { label: String(year), value: String(year) };
18978
19002
  });
18979
19003
  }, ...(ngDevMode ? [{ debugName: "yearOptions" }] : /* istanbul ignore next */ []));
@@ -19015,9 +19039,22 @@ class JvMonthCalendarComponent {
19015
19039
  });
19016
19040
  }, ...(ngDevMode ? [{ debugName: "calendarCells" }] : /* istanbul ignore next */ []));
19017
19041
  selectedDate = computed(() => normalizeDate(this.valueInput()), ...(ngDevMode ? [{ debugName: "selectedDate" }] : /* istanbul ignore next */ []));
19042
+ visibleMonthValue = computed(() => normalizeMonth(this.visibleMonthInput()), ...(ngDevMode ? [{ debugName: "visibleMonthValue" }] : /* istanbul ignore next */ []));
19043
+ yearBounds = computed(() => {
19044
+ const fallbackYear = new Date().getFullYear();
19045
+ const minYear = this.minYear() ?? fallbackYear - 10;
19046
+ const maxYear = this.maxYear() ?? fallbackYear + 10;
19047
+ return minYear <= maxYear ? [minYear, maxYear] : [maxYear, minYear];
19048
+ }, ...(ngDevMode ? [{ debugName: "yearBounds" }] : /* istanbul ignore next */ []));
19018
19049
  constructor() {
19019
19050
  effect(() => {
19051
+ const explicitVisibleMonth = this.visibleMonthValue();
19020
19052
  const selectedDate = this.selectedDate();
19053
+ if (explicitVisibleMonth) {
19054
+ this.focusedDate.set(this.focusDateForMonth(explicitVisibleMonth, untracked(() => this.focusedDate())));
19055
+ this.visibleMonth.set(explicitVisibleMonth);
19056
+ return;
19057
+ }
19021
19058
  if (!selectedDate) {
19022
19059
  return;
19023
19060
  }
@@ -19035,6 +19072,10 @@ class JvMonthCalendarComponent {
19035
19072
  this.setVisibleMonth(new Date(this.visibleMonth().getFullYear(), this.visibleMonth().getMonth() + offset, 1));
19036
19073
  this.focusDateWithinVisibleMonth(this.focusedDate());
19037
19074
  }
19075
+ shiftYear(offset) {
19076
+ this.setVisibleMonth(new Date(this.visibleMonth().getFullYear() + offset, this.visibleMonth().getMonth(), 1));
19077
+ this.focusDateWithinVisibleMonth(this.focusedDate());
19078
+ }
19038
19079
  handleMonthChange(value) {
19039
19080
  const current = this.visibleMonth();
19040
19081
  this.setVisibleMonth(new Date(current.getFullYear(), Number(value), 1));
@@ -19121,46 +19162,76 @@ class JvMonthCalendarComponent {
19121
19162
  this.visibleMonthChange.emit(month);
19122
19163
  }
19123
19164
  focusDateWithinVisibleMonth(date) {
19124
- const month = this.visibleMonth();
19125
- const nextDate = startOfDay$1(new Date(month.getFullYear(), month.getMonth(), Math.min(date.getDate(), daysInMonth$1(month.getFullYear(), month.getMonth()))));
19165
+ const nextDate = this.focusDateForMonth(this.visibleMonth(), date);
19126
19166
  this.focusedDate.set(nextDate);
19127
19167
  queueMicrotask(() => this.focusDateButton(nextDate));
19128
19168
  }
19169
+ focusDateForMonth(month, date) {
19170
+ return startOfDay$1(new Date(month.getFullYear(), month.getMonth(), Math.min(date.getDate(), daysInMonth$1(month.getFullYear(), month.getMonth()))));
19171
+ }
19129
19172
  focusDateButton(date) {
19130
19173
  const button = this.gridRootRef()?.nativeElement
19131
19174
  .querySelector(`[data-jv-date="${toDateKey(date)}"]`);
19132
19175
  button?.focus();
19133
19176
  }
19134
19177
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: JvMonthCalendarComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
19135
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.17", type: JvMonthCalendarComponent, isStandalone: true, selector: "jv-month-calendar", inputs: { valueInput: { classPropertyName: "valueInput", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: false, transformFunction: null }, dayMeta: { classPropertyName: "dayMeta", publicName: "dayMeta", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { selectionChange: "selectionChange", visibleMonthChange: "visibleMonthChange" }, queries: [{ propertyName: "customDayTemplate", first: true, predicate: JvMonthCalendarDayDirective, descendants: true, isSignal: true }], viewQueries: [{ propertyName: "gridRootRef", first: true, predicate: ["gridRoot"], descendants: true, isSignal: true }], ngImport: i0, template: `
19178
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.17", type: JvMonthCalendarComponent, isStandalone: true, selector: "jv-month-calendar", inputs: { valueInput: { classPropertyName: "valueInput", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, visibleMonthInput: { classPropertyName: "visibleMonthInput", publicName: "visibleMonth", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: false, transformFunction: null }, monthLabel: { classPropertyName: "monthLabel", publicName: "monthLabel", isSignal: true, isRequired: false, transformFunction: null }, yearLabel: { classPropertyName: "yearLabel", publicName: "yearLabel", isSignal: true, isRequired: false, transformFunction: null }, minYear: { classPropertyName: "minYear", publicName: "minYear", isSignal: true, isRequired: false, transformFunction: null }, maxYear: { classPropertyName: "maxYear", publicName: "maxYear", isSignal: true, isRequired: false, transformFunction: null }, navigation: { classPropertyName: "navigation", publicName: "navigation", isSignal: true, isRequired: false, transformFunction: null }, dayMeta: { classPropertyName: "dayMeta", publicName: "dayMeta", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { selectionChange: "selectionChange", visibleMonthChange: "visibleMonthChange" }, queries: [{ propertyName: "customDayTemplate", first: true, predicate: JvMonthCalendarDayDirective, descendants: true, isSignal: true }], viewQueries: [{ propertyName: "gridRootRef", first: true, predicate: ["gridRoot"], descendants: true, isSignal: true }], ngImport: i0, template: `
19136
19179
  <section class="jv-month-calendar" [attr.aria-label]="resolvedAriaLabel()">
19137
19180
  <div class="jv-month-calendar-toolbar">
19138
- <jv-icon-button
19139
- icon="chevron-left"
19140
- variant="ghost"
19141
- [ariaLabel]="translation.translate('monthCalendar.prevMonth')"
19142
- (click)="shiftMonth(-1)"
19143
- />
19181
+ <div class="jv-month-calendar-nav-group jv-month-calendar-nav-group--start">
19182
+ @if (resolvedNavigation().previousYear) {
19183
+ <jv-icon-button
19184
+ icon="chevrons-left"
19185
+ variant="ghost"
19186
+ [ariaLabel]="translation.translate('monthCalendar.prevYear')"
19187
+ (click)="shiftYear(-1)"
19188
+ />
19189
+ }
19190
+
19191
+ @if (resolvedNavigation().previousMonth) {
19192
+ <jv-icon-button
19193
+ icon="chevron-left"
19194
+ variant="ghost"
19195
+ [ariaLabel]="translation.translate('monthCalendar.prevMonth')"
19196
+ (click)="shiftMonth(-1)"
19197
+ />
19198
+ }
19199
+ </div>
19144
19200
 
19145
19201
  <div class="jv-month-calendar-selects">
19146
19202
  <jv-select
19147
19203
  [options]="monthOptions()"
19204
+ [ariaLabel]="resolvedMonthLabel()"
19148
19205
  [modelValue]="visibleMonth().getMonth().toString()"
19149
19206
  (selectionChange)="handleMonthChange($event)"
19150
19207
  />
19151
19208
  <jv-select
19152
19209
  [options]="yearOptions()"
19210
+ [ariaLabel]="resolvedYearLabel()"
19153
19211
  [modelValue]="visibleMonth().getFullYear().toString()"
19154
19212
  (selectionChange)="handleYearChange($event)"
19155
19213
  />
19156
19214
  </div>
19157
19215
 
19158
- <jv-icon-button
19159
- icon="chevron-right"
19160
- variant="ghost"
19161
- [ariaLabel]="translation.translate('monthCalendar.nextMonth')"
19162
- (click)="shiftMonth(1)"
19163
- />
19216
+ <div class="jv-month-calendar-nav-group jv-month-calendar-nav-group--end">
19217
+ @if (resolvedNavigation().nextMonth) {
19218
+ <jv-icon-button
19219
+ icon="chevron-right"
19220
+ variant="ghost"
19221
+ [ariaLabel]="translation.translate('monthCalendar.nextMonth')"
19222
+ (click)="shiftMonth(1)"
19223
+ />
19224
+ }
19225
+
19226
+ @if (resolvedNavigation().nextYear) {
19227
+ <jv-icon-button
19228
+ icon="chevrons-right"
19229
+ variant="ghost"
19230
+ [ariaLabel]="translation.translate('monthCalendar.nextYear')"
19231
+ (click)="shiftYear(1)"
19232
+ />
19233
+ }
19234
+ </div>
19164
19235
  </div>
19165
19236
 
19166
19237
  <div class="jv-month-calendar-actions">
@@ -19204,39 +19275,67 @@ class JvMonthCalendarComponent {
19204
19275
  }
19205
19276
  </div>
19206
19277
  </section>
19207
- `, isInline: true, styles: [".jv-month-calendar{display:grid;gap:var(--jv-spacing-md);padding:var(--jv-spacing-md);border:1px solid var(--jv-color-border);border-radius:var(--jv-radius-lg);background:var(--jv-color-surface)}.jv-month-calendar-toolbar,.jv-month-calendar-selects,.jv-month-calendar-weekdays,.jv-month-calendar-grid,.jv-month-calendar-actions{display:grid;gap:var(--jv-spacing-sm)}.jv-month-calendar-toolbar{grid-template-columns:auto 1fr auto;align-items:center}.jv-month-calendar-selects{grid-template-columns:1fr 1fr}.jv-month-calendar-actions{justify-content:end}.jv-month-calendar-today{min-height:2.25rem;padding:0 var(--jv-spacing-md);border:1px solid var(--jv-color-border);border-radius:999px;background:var(--jv-color-surface-muted);color:var(--jv-color-foreground);font:inherit;cursor:pointer}.jv-month-calendar-today:hover,.jv-month-calendar-today:focus-visible{border-color:var(--jv-color-primary);outline:none}.jv-month-calendar-weekdays,.jv-month-calendar-grid{grid-template-columns:repeat(7,minmax(0,1fr))}.jv-month-calendar-weekdays span{text-align:center;font-size:.8125rem;font-weight:600;color:var(--jv-color-foreground-muted)}.jv-month-calendar-day{position:relative;min-height:5rem;display:grid;align-content:start;justify-items:start;gap:var(--jv-spacing-xs);padding:var(--jv-spacing-sm);border:1px solid transparent;border-radius:var(--jv-radius-md);background:transparent;color:var(--jv-color-foreground);text-align:left;font:inherit;cursor:pointer;transition:background-color .16s ease,border-color .16s ease,color .16s ease}.jv-month-calendar-day:hover,.jv-month-calendar-day:focus-visible{background:var(--jv-color-surface-muted);outline:none}.jv-month-calendar-day--outside{color:var(--jv-color-foreground-muted)}.jv-month-calendar-day--today{border-color:var(--jv-color-primary)}.jv-month-calendar-day--selected{background:color-mix(in srgb,var(--jv-color-primary) 14%,var(--jv-color-surface));border-color:var(--jv-color-primary)}.jv-month-calendar-day-number{font-weight:700}.jv-month-calendar-day-count,.jv-month-calendar-day-dot{display:inline-flex;align-items:center;justify-content:center;border-radius:999px}.jv-month-calendar-day-count{min-width:1.5rem;min-height:1.5rem;padding:0 .375rem;font-size:.75rem;font-weight:700}.jv-month-calendar-day-dot{width:.5rem;height:.5rem;margin-top:auto}.jv-month-calendar-marker--primary{background:color-mix(in srgb,var(--jv-color-primary) 18%,var(--jv-color-surface));color:var(--jv-color-primary)}.jv-month-calendar-marker--secondary,.jv-month-calendar-marker--neutral{background:var(--jv-color-surface-muted);color:var(--jv-color-foreground)}.jv-month-calendar-marker--success{background:color-mix(in srgb,var(--jv-color-success) 18%,var(--jv-color-surface));color:var(--jv-color-success)}.jv-month-calendar-marker--warning{background:color-mix(in srgb,var(--jv-color-warning) 20%,var(--jv-color-surface));color:#8a4b00}.jv-month-calendar-marker--danger{background:color-mix(in srgb,var(--jv-color-danger) 18%,var(--jv-color-surface));color:var(--jv-color-danger)}.jv-month-calendar-marker--info{background:color-mix(in srgb,var(--jv-color-info) 18%,var(--jv-color-surface));color:var(--jv-color-info)}\n"], dependencies: [{ kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: JvIconButtonComponent, selector: "jv-icon-button", inputs: ["icon", "ariaLabel", "variant", "disabled"] }, { kind: "component", type: JvSelectComponent, selector: "jv-select", inputs: ["options", "placeholder", "required", "invalid", "describedBy", "inputId", "modelValue"], outputs: ["selectionChange"] }] });
19278
+ `, isInline: true, styles: [".jv-month-calendar{display:grid;gap:var(--jv-spacing-md);padding:var(--jv-spacing-md);border:1px solid var(--jv-color-border);border-radius:var(--jv-radius-lg);background:var(--jv-color-surface)}.jv-month-calendar-toolbar,.jv-month-calendar-nav-group,.jv-month-calendar-selects,.jv-month-calendar-weekdays,.jv-month-calendar-grid,.jv-month-calendar-actions{display:grid;gap:var(--jv-spacing-sm)}.jv-month-calendar-toolbar{grid-template-columns:auto 1fr auto;align-items:center}.jv-month-calendar-nav-group{display:inline-flex;align-items:center;gap:var(--jv-spacing-xs)}.jv-month-calendar-nav-group--end{justify-content:end}.jv-month-calendar-selects{grid-template-columns:1fr 1fr}.jv-month-calendar-actions{justify-content:end}.jv-month-calendar-today{min-height:2.25rem;padding:0 var(--jv-spacing-md);border:1px solid var(--jv-color-border);border-radius:999px;background:var(--jv-color-surface-muted);color:var(--jv-color-foreground);font:inherit;cursor:pointer}.jv-month-calendar-today:hover,.jv-month-calendar-today:focus-visible{border-color:var(--jv-color-primary);outline:none}.jv-month-calendar-weekdays,.jv-month-calendar-grid{grid-template-columns:repeat(7,minmax(0,1fr))}.jv-month-calendar-weekdays span{text-align:center;font-size:.8125rem;font-weight:600;color:var(--jv-color-foreground-muted)}.jv-month-calendar-day{position:relative;min-height:5rem;display:grid;align-content:start;justify-items:start;gap:var(--jv-spacing-xs);padding:var(--jv-spacing-sm);border:1px solid transparent;border-radius:var(--jv-radius-md);background:transparent;color:var(--jv-color-foreground);text-align:left;font:inherit;cursor:pointer;transition:background-color .16s ease,border-color .16s ease,color .16s ease}.jv-month-calendar-day:hover,.jv-month-calendar-day:focus-visible{background:var(--jv-color-surface-muted);outline:none}.jv-month-calendar-day--outside{color:var(--jv-color-foreground-muted)}.jv-month-calendar-day--today{border-color:var(--jv-color-primary)}.jv-month-calendar-day--selected{background:color-mix(in srgb,var(--jv-color-primary) 14%,var(--jv-color-surface));border-color:var(--jv-color-primary)}.jv-month-calendar-day-number{font-weight:700}.jv-month-calendar-day-count,.jv-month-calendar-day-dot{display:inline-flex;align-items:center;justify-content:center;border-radius:999px}.jv-month-calendar-day-count{min-width:1.5rem;min-height:1.5rem;padding:0 .375rem;font-size:.75rem;font-weight:700}.jv-month-calendar-day-dot{width:.5rem;height:.5rem;margin-top:auto}.jv-month-calendar-marker--primary{background:color-mix(in srgb,var(--jv-color-primary) 18%,var(--jv-color-surface));color:var(--jv-color-primary)}.jv-month-calendar-marker--secondary,.jv-month-calendar-marker--neutral{background:var(--jv-color-surface-muted);color:var(--jv-color-foreground)}.jv-month-calendar-marker--success{background:color-mix(in srgb,var(--jv-color-success) 18%,var(--jv-color-surface));color:var(--jv-color-success)}.jv-month-calendar-marker--warning{background:color-mix(in srgb,var(--jv-color-warning) 20%,var(--jv-color-surface));color:#8a4b00}.jv-month-calendar-marker--danger{background:color-mix(in srgb,var(--jv-color-danger) 18%,var(--jv-color-surface));color:var(--jv-color-danger)}.jv-month-calendar-marker--info{background:color-mix(in srgb,var(--jv-color-info) 18%,var(--jv-color-surface));color:var(--jv-color-info)}\n"], dependencies: [{ kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: JvIconButtonComponent, selector: "jv-icon-button", inputs: ["icon", "ariaLabel", "variant", "disabled"] }, { kind: "component", type: JvSelectComponent, selector: "jv-select", inputs: ["options", "placeholder", "required", "invalid", "describedBy", "ariaLabel", "inputId", "modelValue"], outputs: ["selectionChange"] }] });
19208
19279
  }
19209
19280
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: JvMonthCalendarComponent, decorators: [{
19210
19281
  type: Component,
19211
19282
  args: [{ selector: 'jv-month-calendar', standalone: true, imports: [NgTemplateOutlet, JvIconButtonComponent, JvSelectComponent], template: `
19212
19283
  <section class="jv-month-calendar" [attr.aria-label]="resolvedAriaLabel()">
19213
19284
  <div class="jv-month-calendar-toolbar">
19214
- <jv-icon-button
19215
- icon="chevron-left"
19216
- variant="ghost"
19217
- [ariaLabel]="translation.translate('monthCalendar.prevMonth')"
19218
- (click)="shiftMonth(-1)"
19219
- />
19285
+ <div class="jv-month-calendar-nav-group jv-month-calendar-nav-group--start">
19286
+ @if (resolvedNavigation().previousYear) {
19287
+ <jv-icon-button
19288
+ icon="chevrons-left"
19289
+ variant="ghost"
19290
+ [ariaLabel]="translation.translate('monthCalendar.prevYear')"
19291
+ (click)="shiftYear(-1)"
19292
+ />
19293
+ }
19294
+
19295
+ @if (resolvedNavigation().previousMonth) {
19296
+ <jv-icon-button
19297
+ icon="chevron-left"
19298
+ variant="ghost"
19299
+ [ariaLabel]="translation.translate('monthCalendar.prevMonth')"
19300
+ (click)="shiftMonth(-1)"
19301
+ />
19302
+ }
19303
+ </div>
19220
19304
 
19221
19305
  <div class="jv-month-calendar-selects">
19222
19306
  <jv-select
19223
19307
  [options]="monthOptions()"
19308
+ [ariaLabel]="resolvedMonthLabel()"
19224
19309
  [modelValue]="visibleMonth().getMonth().toString()"
19225
19310
  (selectionChange)="handleMonthChange($event)"
19226
19311
  />
19227
19312
  <jv-select
19228
19313
  [options]="yearOptions()"
19314
+ [ariaLabel]="resolvedYearLabel()"
19229
19315
  [modelValue]="visibleMonth().getFullYear().toString()"
19230
19316
  (selectionChange)="handleYearChange($event)"
19231
19317
  />
19232
19318
  </div>
19233
19319
 
19234
- <jv-icon-button
19235
- icon="chevron-right"
19236
- variant="ghost"
19237
- [ariaLabel]="translation.translate('monthCalendar.nextMonth')"
19238
- (click)="shiftMonth(1)"
19239
- />
19320
+ <div class="jv-month-calendar-nav-group jv-month-calendar-nav-group--end">
19321
+ @if (resolvedNavigation().nextMonth) {
19322
+ <jv-icon-button
19323
+ icon="chevron-right"
19324
+ variant="ghost"
19325
+ [ariaLabel]="translation.translate('monthCalendar.nextMonth')"
19326
+ (click)="shiftMonth(1)"
19327
+ />
19328
+ }
19329
+
19330
+ @if (resolvedNavigation().nextYear) {
19331
+ <jv-icon-button
19332
+ icon="chevrons-right"
19333
+ variant="ghost"
19334
+ [ariaLabel]="translation.translate('monthCalendar.nextYear')"
19335
+ (click)="shiftYear(1)"
19336
+ />
19337
+ }
19338
+ </div>
19240
19339
  </div>
19241
19340
 
19242
19341
  <div class="jv-month-calendar-actions">
@@ -19280,8 +19379,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImpo
19280
19379
  }
19281
19380
  </div>
19282
19381
  </section>
19283
- `, styles: [".jv-month-calendar{display:grid;gap:var(--jv-spacing-md);padding:var(--jv-spacing-md);border:1px solid var(--jv-color-border);border-radius:var(--jv-radius-lg);background:var(--jv-color-surface)}.jv-month-calendar-toolbar,.jv-month-calendar-selects,.jv-month-calendar-weekdays,.jv-month-calendar-grid,.jv-month-calendar-actions{display:grid;gap:var(--jv-spacing-sm)}.jv-month-calendar-toolbar{grid-template-columns:auto 1fr auto;align-items:center}.jv-month-calendar-selects{grid-template-columns:1fr 1fr}.jv-month-calendar-actions{justify-content:end}.jv-month-calendar-today{min-height:2.25rem;padding:0 var(--jv-spacing-md);border:1px solid var(--jv-color-border);border-radius:999px;background:var(--jv-color-surface-muted);color:var(--jv-color-foreground);font:inherit;cursor:pointer}.jv-month-calendar-today:hover,.jv-month-calendar-today:focus-visible{border-color:var(--jv-color-primary);outline:none}.jv-month-calendar-weekdays,.jv-month-calendar-grid{grid-template-columns:repeat(7,minmax(0,1fr))}.jv-month-calendar-weekdays span{text-align:center;font-size:.8125rem;font-weight:600;color:var(--jv-color-foreground-muted)}.jv-month-calendar-day{position:relative;min-height:5rem;display:grid;align-content:start;justify-items:start;gap:var(--jv-spacing-xs);padding:var(--jv-spacing-sm);border:1px solid transparent;border-radius:var(--jv-radius-md);background:transparent;color:var(--jv-color-foreground);text-align:left;font:inherit;cursor:pointer;transition:background-color .16s ease,border-color .16s ease,color .16s ease}.jv-month-calendar-day:hover,.jv-month-calendar-day:focus-visible{background:var(--jv-color-surface-muted);outline:none}.jv-month-calendar-day--outside{color:var(--jv-color-foreground-muted)}.jv-month-calendar-day--today{border-color:var(--jv-color-primary)}.jv-month-calendar-day--selected{background:color-mix(in srgb,var(--jv-color-primary) 14%,var(--jv-color-surface));border-color:var(--jv-color-primary)}.jv-month-calendar-day-number{font-weight:700}.jv-month-calendar-day-count,.jv-month-calendar-day-dot{display:inline-flex;align-items:center;justify-content:center;border-radius:999px}.jv-month-calendar-day-count{min-width:1.5rem;min-height:1.5rem;padding:0 .375rem;font-size:.75rem;font-weight:700}.jv-month-calendar-day-dot{width:.5rem;height:.5rem;margin-top:auto}.jv-month-calendar-marker--primary{background:color-mix(in srgb,var(--jv-color-primary) 18%,var(--jv-color-surface));color:var(--jv-color-primary)}.jv-month-calendar-marker--secondary,.jv-month-calendar-marker--neutral{background:var(--jv-color-surface-muted);color:var(--jv-color-foreground)}.jv-month-calendar-marker--success{background:color-mix(in srgb,var(--jv-color-success) 18%,var(--jv-color-surface));color:var(--jv-color-success)}.jv-month-calendar-marker--warning{background:color-mix(in srgb,var(--jv-color-warning) 20%,var(--jv-color-surface));color:#8a4b00}.jv-month-calendar-marker--danger{background:color-mix(in srgb,var(--jv-color-danger) 18%,var(--jv-color-surface));color:var(--jv-color-danger)}.jv-month-calendar-marker--info{background:color-mix(in srgb,var(--jv-color-info) 18%,var(--jv-color-surface));color:var(--jv-color-info)}\n"] }]
19284
- }], ctorParameters: () => [], propDecorators: { gridRootRef: [{ type: i0.ViewChild, args: ['gridRoot', { isSignal: true }] }], customDayTemplate: [{ type: i0.ContentChild, args: [i0.forwardRef(() => JvMonthCalendarDayDirective), { isSignal: true }] }], valueInput: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabel", required: false }] }], dayMeta: [{ type: i0.Input, args: [{ isSignal: true, alias: "dayMeta", required: false }] }], selectionChange: [{ type: i0.Output, args: ["selectionChange"] }], visibleMonthChange: [{ type: i0.Output, args: ["visibleMonthChange"] }] } });
19382
+ `, styles: [".jv-month-calendar{display:grid;gap:var(--jv-spacing-md);padding:var(--jv-spacing-md);border:1px solid var(--jv-color-border);border-radius:var(--jv-radius-lg);background:var(--jv-color-surface)}.jv-month-calendar-toolbar,.jv-month-calendar-nav-group,.jv-month-calendar-selects,.jv-month-calendar-weekdays,.jv-month-calendar-grid,.jv-month-calendar-actions{display:grid;gap:var(--jv-spacing-sm)}.jv-month-calendar-toolbar{grid-template-columns:auto 1fr auto;align-items:center}.jv-month-calendar-nav-group{display:inline-flex;align-items:center;gap:var(--jv-spacing-xs)}.jv-month-calendar-nav-group--end{justify-content:end}.jv-month-calendar-selects{grid-template-columns:1fr 1fr}.jv-month-calendar-actions{justify-content:end}.jv-month-calendar-today{min-height:2.25rem;padding:0 var(--jv-spacing-md);border:1px solid var(--jv-color-border);border-radius:999px;background:var(--jv-color-surface-muted);color:var(--jv-color-foreground);font:inherit;cursor:pointer}.jv-month-calendar-today:hover,.jv-month-calendar-today:focus-visible{border-color:var(--jv-color-primary);outline:none}.jv-month-calendar-weekdays,.jv-month-calendar-grid{grid-template-columns:repeat(7,minmax(0,1fr))}.jv-month-calendar-weekdays span{text-align:center;font-size:.8125rem;font-weight:600;color:var(--jv-color-foreground-muted)}.jv-month-calendar-day{position:relative;min-height:5rem;display:grid;align-content:start;justify-items:start;gap:var(--jv-spacing-xs);padding:var(--jv-spacing-sm);border:1px solid transparent;border-radius:var(--jv-radius-md);background:transparent;color:var(--jv-color-foreground);text-align:left;font:inherit;cursor:pointer;transition:background-color .16s ease,border-color .16s ease,color .16s ease}.jv-month-calendar-day:hover,.jv-month-calendar-day:focus-visible{background:var(--jv-color-surface-muted);outline:none}.jv-month-calendar-day--outside{color:var(--jv-color-foreground-muted)}.jv-month-calendar-day--today{border-color:var(--jv-color-primary)}.jv-month-calendar-day--selected{background:color-mix(in srgb,var(--jv-color-primary) 14%,var(--jv-color-surface));border-color:var(--jv-color-primary)}.jv-month-calendar-day-number{font-weight:700}.jv-month-calendar-day-count,.jv-month-calendar-day-dot{display:inline-flex;align-items:center;justify-content:center;border-radius:999px}.jv-month-calendar-day-count{min-width:1.5rem;min-height:1.5rem;padding:0 .375rem;font-size:.75rem;font-weight:700}.jv-month-calendar-day-dot{width:.5rem;height:.5rem;margin-top:auto}.jv-month-calendar-marker--primary{background:color-mix(in srgb,var(--jv-color-primary) 18%,var(--jv-color-surface));color:var(--jv-color-primary)}.jv-month-calendar-marker--secondary,.jv-month-calendar-marker--neutral{background:var(--jv-color-surface-muted);color:var(--jv-color-foreground)}.jv-month-calendar-marker--success{background:color-mix(in srgb,var(--jv-color-success) 18%,var(--jv-color-surface));color:var(--jv-color-success)}.jv-month-calendar-marker--warning{background:color-mix(in srgb,var(--jv-color-warning) 20%,var(--jv-color-surface));color:#8a4b00}.jv-month-calendar-marker--danger{background:color-mix(in srgb,var(--jv-color-danger) 18%,var(--jv-color-surface));color:var(--jv-color-danger)}.jv-month-calendar-marker--info{background:color-mix(in srgb,var(--jv-color-info) 18%,var(--jv-color-surface));color:var(--jv-color-info)}\n"] }]
19383
+ }], ctorParameters: () => [], propDecorators: { gridRootRef: [{ type: i0.ViewChild, args: ['gridRoot', { isSignal: true }] }], customDayTemplate: [{ type: i0.ContentChild, args: [i0.forwardRef(() => JvMonthCalendarDayDirective), { isSignal: true }] }], valueInput: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }], visibleMonthInput: [{ type: i0.Input, args: [{ isSignal: true, alias: "visibleMonth", required: false }] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabel", required: false }] }], monthLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "monthLabel", required: false }] }], yearLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "yearLabel", required: false }] }], minYear: [{ type: i0.Input, args: [{ isSignal: true, alias: "minYear", required: false }] }], maxYear: [{ type: i0.Input, args: [{ isSignal: true, alias: "maxYear", required: false }] }], navigation: [{ type: i0.Input, args: [{ isSignal: true, alias: "navigation", required: false }] }], dayMeta: [{ type: i0.Input, args: [{ isSignal: true, alias: "dayMeta", required: false }] }], selectionChange: [{ type: i0.Output, args: ["selectionChange"] }], visibleMonthChange: [{ type: i0.Output, args: ["visibleMonthChange"] }] } });
19285
19384
  function normalizeDate(value) {
19286
19385
  if (!value) {
19287
19386
  return null;
@@ -19289,6 +19388,10 @@ function normalizeDate(value) {
19289
19388
  const date = value instanceof Date ? value : new Date(value);
19290
19389
  return Number.isNaN(date.getTime()) ? null : startOfDay$1(date);
19291
19390
  }
19391
+ function normalizeMonth(value) {
19392
+ const date = normalizeDate(value);
19393
+ return date ? firstOfMonth$1(date) : null;
19394
+ }
19292
19395
  function daysInMonth$1(year, month) {
19293
19396
  return new Date(year, month + 1, 0).getDate();
19294
19397
  }
@@ -20533,6 +20636,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImpo
20533
20636
  class JvDialogComponent {
20534
20637
  document = inject(DOCUMENT);
20535
20638
  panelRef = viewChild('panel', ...(ngDevMode ? [{ debugName: "panelRef" }] : /* istanbul ignore next */ []));
20639
+ previouslyFocusedElement = null;
20536
20640
  open = input(false, ...(ngDevMode ? [{ debugName: "open" }] : /* istanbul ignore next */ []));
20537
20641
  title = input('Dialog', ...(ngDevMode ? [{ debugName: "title" }] : /* istanbul ignore next */ []));
20538
20642
  closed = output();
@@ -20541,8 +20645,11 @@ class JvDialogComponent {
20541
20645
  constructor() {
20542
20646
  effect(() => {
20543
20647
  if (this.open()) {
20648
+ this.previouslyFocusedElement = this.document.activeElement instanceof HTMLElement ? this.document.activeElement : null;
20544
20649
  queueMicrotask(() => this.focusInitialElement());
20650
+ return;
20545
20651
  }
20652
+ this.restoreFocus();
20546
20653
  });
20547
20654
  }
20548
20655
  handleDocumentKeydown(event) {
@@ -20569,6 +20676,10 @@ class JvDialogComponent {
20569
20676
  backdropClose() {
20570
20677
  this.closed.emit();
20571
20678
  }
20679
+ restoreFocus() {
20680
+ this.previouslyFocusedElement?.focus();
20681
+ this.previouslyFocusedElement = null;
20682
+ }
20572
20683
  trapFocus(event) {
20573
20684
  const focusable = this.getFocusableElements();
20574
20685
  const panel = this.panelRef()?.nativeElement;
@@ -20600,7 +20711,7 @@ class JvDialogComponent {
20600
20711
  static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.17", type: JvDialogComponent, isStandalone: true, selector: "jv-dialog", inputs: { open: { classPropertyName: "open", publicName: "open", isSignal: true, isRequired: false, transformFunction: null }, title: { classPropertyName: "title", publicName: "title", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { closed: "closed" }, host: { listeners: { "document:keydown": "handleDocumentKeydown($event)" } }, viewQueries: [{ propertyName: "panelRef", first: true, predicate: ["panel"], descendants: true, isSignal: true }], ngImport: i0, template: `
20601
20712
  @if (open()) {
20602
20713
  <div class="jv-dialog-backdrop" (click)="backdropClose()">
20603
- <div class="jv-dialog-panel" role="dialog" aria-modal="true" tabindex="-1" [attr.aria-labelledby]="titleId" [attr.aria-describedby]="descriptionId" (click)="$event.stopPropagation()" #panel>
20714
+ <div class="jv-dialog-panel" role="dialog" aria-modal="true" tabindex="-1" [attr.aria-labelledby]="titleId" [attr.aria-describedby]="descriptionId" (click)="$event.stopPropagation()" #panel>
20604
20715
  <header class="jv-dialog-header">
20605
20716
  <h2 [id]="titleId">{{ title() }}</h2>
20606
20717
  </header>
@@ -20622,7 +20733,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImpo
20622
20733
  args: [{ selector: 'jv-dialog', standalone: true, template: `
20623
20734
  @if (open()) {
20624
20735
  <div class="jv-dialog-backdrop" (click)="backdropClose()">
20625
- <div class="jv-dialog-panel" role="dialog" aria-modal="true" tabindex="-1" [attr.aria-labelledby]="titleId" [attr.aria-describedby]="descriptionId" (click)="$event.stopPropagation()" #panel>
20736
+ <div class="jv-dialog-panel" role="dialog" aria-modal="true" tabindex="-1" [attr.aria-labelledby]="titleId" [attr.aria-describedby]="descriptionId" (click)="$event.stopPropagation()" #panel>
20626
20737
  <header class="jv-dialog-header">
20627
20738
  <h2 [id]="titleId">{{ title() }}</h2>
20628
20739
  </header>
@@ -20932,7 +21043,7 @@ class JvDatePickerComponent {
20932
21043
  <jv-button variant="outline" (click)="closeDialog()">{{ translation.translate('datePicker.close') }}</jv-button>
20933
21044
  </div>
20934
21045
  </jv-dialog>
20935
- `, 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"] }] });
21046
+ `, 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", "ariaLabel", "inputId", "modelValue"], outputs: ["selectionChange"] }] });
20936
21047
  }
20937
21048
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: JvDatePickerComponent, decorators: [{
20938
21049
  type: Component,
@@ -21212,7 +21323,7 @@ class JvFileInputComponent {
21212
21323
  [class.jv-file-input-dropzone--dragover]="dragActive()"
21213
21324
  [class.jv-file-input-dropzone--invalid]="invalid()"
21214
21325
  role="button"
21215
- tabindex="0"
21326
+ [attr.tabindex]="disabled() ? -1 : 0"
21216
21327
  [attr.aria-disabled]="disabled() ? 'true' : 'false'"
21217
21328
  [attr.aria-label]="dropzoneAriaLabel()"
21218
21329
  (click)="openFileDialog()"
@@ -21287,7 +21398,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImpo
21287
21398
  [class.jv-file-input-dropzone--dragover]="dragActive()"
21288
21399
  [class.jv-file-input-dropzone--invalid]="invalid()"
21289
21400
  role="button"
21290
- tabindex="0"
21401
+ [attr.tabindex]="disabled() ? -1 : 0"
21291
21402
  [attr.aria-disabled]="disabled() ? 'true' : 'false'"
21292
21403
  [attr.aria-label]="dropzoneAriaLabel()"
21293
21404
  (click)="openFileDialog()"
@@ -21335,6 +21446,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImpo
21335
21446
  }], 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"] }] } });
21336
21447
 
21337
21448
  class JvListComponent {
21449
+ listId = input('', ...(ngDevMode ? [{ debugName: "listId" }] : /* istanbul ignore next */ []));
21338
21450
  role = input('list', ...(ngDevMode ? [{ debugName: "role" }] : /* istanbul ignore next */ []));
21339
21451
  ariaRole = input(null, ...(ngDevMode ? [{ debugName: "ariaRole" }] : /* istanbul ignore next */ []));
21340
21452
  variant = input(null, ...(ngDevMode ? [{ debugName: "variant" }] : /* istanbul ignore next */ []));
@@ -21345,13 +21457,14 @@ class JvListComponent {
21345
21457
  }, ...(ngDevMode ? [{ debugName: "variantClass" }] : /* istanbul ignore next */ []));
21346
21458
  resolvedRole = computed(() => this.ariaRole() ?? (this.role() === 'navigation' ? 'list' : null), ...(ngDevMode ? [{ debugName: "resolvedRole" }] : /* istanbul ignore next */ []));
21347
21459
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: JvListComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
21348
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "21.2.17", type: JvListComponent, isStandalone: true, selector: "jv-list", inputs: { role: { classPropertyName: "role", publicName: "role", isSignal: true, isRequired: false, transformFunction: null }, ariaRole: { classPropertyName: "ariaRole", publicName: "ariaRole", isSignal: true, isRequired: false, transformFunction: null }, variant: { classPropertyName: "variant", publicName: "variant", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: `
21349
- <ul
21350
- class="jv-list"
21351
- [class]="variantClass()"
21352
- [attr.role]="resolvedRole()"
21353
- [attr.aria-label]="ariaLabel()"
21354
- >
21460
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "21.2.17", type: JvListComponent, isStandalone: true, selector: "jv-list", inputs: { listId: { classPropertyName: "listId", publicName: "listId", isSignal: true, isRequired: false, transformFunction: null }, role: { classPropertyName: "role", publicName: "role", isSignal: true, isRequired: false, transformFunction: null }, ariaRole: { classPropertyName: "ariaRole", publicName: "ariaRole", isSignal: true, isRequired: false, transformFunction: null }, variant: { classPropertyName: "variant", publicName: "variant", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: `
21461
+ <ul
21462
+ [id]="listId() || null"
21463
+ class="jv-list"
21464
+ [class]="variantClass()"
21465
+ [attr.role]="resolvedRole()"
21466
+ [attr.aria-label]="ariaLabel()"
21467
+ >
21355
21468
  <ng-content />
21356
21469
  </ul>
21357
21470
  `, isInline: true, styles: [".jv-list{display:block;list-style:none;margin:0;padding:0}.jv-list>:not(:last-child){border-bottom:1px solid var(--jv-color-border)}.jv-list--bordered{border:1px solid var(--jv-color-border);border-radius:var(--jv-radius-md)}\n"] });
@@ -21359,16 +21472,17 @@ class JvListComponent {
21359
21472
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: JvListComponent, decorators: [{
21360
21473
  type: Component,
21361
21474
  args: [{ selector: 'jv-list', standalone: true, template: `
21362
- <ul
21363
- class="jv-list"
21364
- [class]="variantClass()"
21365
- [attr.role]="resolvedRole()"
21366
- [attr.aria-label]="ariaLabel()"
21367
- >
21475
+ <ul
21476
+ [id]="listId() || null"
21477
+ class="jv-list"
21478
+ [class]="variantClass()"
21479
+ [attr.role]="resolvedRole()"
21480
+ [attr.aria-label]="ariaLabel()"
21481
+ >
21368
21482
  <ng-content />
21369
21483
  </ul>
21370
21484
  `, styles: [".jv-list{display:block;list-style:none;margin:0;padding:0}.jv-list>:not(:last-child){border-bottom:1px solid var(--jv-color-border)}.jv-list--bordered{border:1px solid var(--jv-color-border);border-radius:var(--jv-radius-md)}\n"] }]
21371
- }], propDecorators: { role: [{ type: i0.Input, args: [{ isSignal: true, alias: "role", required: false }] }], ariaRole: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaRole", required: false }] }], variant: [{ type: i0.Input, args: [{ isSignal: true, alias: "variant", required: false }] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabel", required: false }] }] } });
21485
+ }], propDecorators: { listId: [{ type: i0.Input, args: [{ isSignal: true, alias: "listId", required: false }] }], role: [{ type: i0.Input, args: [{ isSignal: true, alias: "role", required: false }] }], ariaRole: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaRole", required: false }] }], variant: [{ type: i0.Input, args: [{ isSignal: true, alias: "variant", required: false }] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabel", required: false }] }] } });
21372
21486
 
21373
21487
  let modalSelectorIdSequence = 0;
21374
21488
  class JvModalSelectorComponent {
@@ -21398,6 +21512,8 @@ class JvModalSelectorComponent {
21398
21512
  dialogOpen = signal(false, ...(ngDevMode ? [{ debugName: "dialogOpen" }] : /* istanbul ignore next */ []));
21399
21513
  searchQuery = signal('', ...(ngDevMode ? [{ debugName: "searchQuery" }] : /* istanbul ignore next */ []));
21400
21514
  dialogId = `jv-modal-selector-dialog-${this.instanceId}`;
21515
+ listboxId = `jv-modal-selector-listbox-${this.instanceId}`;
21516
+ resultsId = `jv-modal-selector-results-${this.instanceId}`;
21401
21517
  searchInputId = `jv-modal-selector-search-${this.instanceId}`;
21402
21518
  value = null;
21403
21519
  disabled = false;
@@ -21531,100 +21647,102 @@ class JvModalSelectorComponent {
21531
21647
  useExisting: forwardRef(() => JvModalSelectorComponent),
21532
21648
  multi: true,
21533
21649
  },
21534
- ], viewQueries: [{ propertyName: "triggerButtonRef", first: true, predicate: ["triggerButton"], descendants: true, isSignal: true }, { propertyName: "optionElements", predicate: ["optionElement"], descendants: true, isSignal: true }], ngImport: i0, template: `
21535
- <jv-form-container
21536
- [label]="label()"
21537
- [inputId]="inputId()"
21538
- [required]="required()"
21539
- [invalid]="invalid()"
21540
- [hint]="hint()"
21541
- [error]="error()"
21542
- >
21543
- <button
21544
- #triggerButton
21545
- type="button"
21546
- class="jv-modal-selector-trigger"
21547
- [class.is-invalid]="invalid()"
21548
- [id]="inputId()"
21549
- [disabled]="disabled"
21550
- [attr.aria-haspopup]="'dialog'"
21551
- [attr.aria-expanded]="dialogOpen() ? 'true' : 'false'"
21552
- [attr.aria-controls]="dialogId"
21553
- [attr.aria-invalid]="invalid() ? 'true' : null"
21554
- [attr.aria-describedby]="describedBy() || null"
21555
- [attr.aria-label]="triggerAriaLabel()"
21556
- (click)="openDialog()"
21557
- (blur)="handleBlur()"
21558
- >
21559
- <span class="jv-modal-selector-trigger-copy">
21560
- <span [class.jv-modal-selector-trigger-placeholder]="!selectedOption()">
21561
- {{ selectedOption()?.label ?? resolvedPlaceholder() }}
21562
- </span>
21563
- </span>
21564
- <jv-icon name="chevrons-up-down" [size]="18" />
21565
- </button>
21566
- </jv-form-container>
21567
-
21568
- <jv-dialog [open]="dialogOpen()" [title]="resolvedDialogTitle()" (closed)="closeDialog()">
21569
- <div [id]="dialogId" class="jv-modal-selector-dialog">
21570
- @if (dialogSubtitle()) {
21571
- <p class="jv-modal-selector-subtitle">{{ dialogSubtitle() }}</p>
21572
- }
21573
-
21574
- @if (showSearch()) {
21575
- <jv-input
21576
- type="search"
21577
- [inputId]="searchInputId"
21578
- [placeholder]="searchPlaceholder() || translation.translate('modalSelector.searchPlaceholder')"
21579
- [ngModel]="searchQuery()"
21580
- (ngModelChange)="searchQuery.set($event)"
21581
- />
21582
- }
21583
-
21584
- <p class="jv-modal-selector-results" aria-live="polite">{{ resultsLabel() }}</p>
21585
-
21586
- @if (filteredOptions().length > 0) {
21587
- <jv-list variant="bordered" [ariaRole]="'listbox'" [ariaLabel]="resolvedDialogTitle()">
21588
- @for (option of filteredOptions(); track option.value; let index = $index) {
21589
- <li role="presentation">
21590
- <div
21591
- #optionElement
21592
- class="jv-modal-selector-option"
21593
- [class.jv-modal-selector-option--selected]="isSelected(option)"
21594
- [class.jv-modal-selector-option--disabled]="option.disabled ?? false"
21595
- [id]="optionId(index)"
21596
- role="option"
21597
- [attr.aria-selected]="isSelected(option) ? 'true' : 'false'"
21598
- [attr.tabindex]="getOptionTabIndex(option, index)"
21599
- (click)="selectOption(option)"
21600
- (keydown)="handleOptionKeydown($event, index, option)"
21601
- >
21602
- <div class="jv-modal-selector-option-copy">
21603
- <span class="jv-modal-selector-option-label">{{ option.label }}</span>
21604
- @if (option.description) {
21605
- <span class="jv-modal-selector-option-description">{{ option.description }}</span>
21606
- }
21607
- </div>
21608
- @if (isSelected(option)) {
21609
- <jv-icon name="check" [size]="16" />
21610
- }
21611
- </div>
21612
- </li>
21613
- }
21614
- </jv-list>
21615
- } @else {
21616
- <div class="jv-modal-selector-empty" aria-live="polite">{{ emptyMessage() || translation.translate('modalSelector.emptyMessage') }}</div>
21617
- }
21618
- </div>
21619
-
21620
- <div dialog-actions class="jv-modal-selector-actions">
21621
- @if (selectedOption()) {
21622
- <jv-button variant="ghost" (click)="clearSelection()">{{ clearLabel() || translation.translate('modalSelector.clear') }}</jv-button>
21623
- }
21624
- <jv-button variant="outline" (click)="closeDialog()">{{ closeLabel() || translation.translate('modalSelector.close') }}</jv-button>
21625
- </div>
21626
- </jv-dialog>
21627
- `, isInline: true, styles: [".jv-modal-selector-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-modal-selector-trigger.is-invalid{border-color:var(--jv-color-danger)}.jv-modal-selector-trigger:disabled{opacity:.7;cursor:not-allowed}.jv-modal-selector-trigger:focus-visible{outline:2px solid var(--jv-color-primary);outline-offset:1px}.jv-modal-selector-trigger-copy{min-width:0;flex:1}.jv-modal-selector-trigger-placeholder{color:var(--jv-color-foreground-muted)}.jv-modal-selector-dialog,.jv-modal-selector-actions{display:grid;gap:var(--jv-spacing-md)}.jv-modal-selector-subtitle,.jv-modal-selector-results,.jv-modal-selector-option-description,.jv-modal-selector-empty{margin:0;color:var(--jv-color-foreground-muted)}.jv-modal-selector-empty{padding:var(--jv-spacing-md);border:1px dashed var(--jv-color-border);border-radius:var(--jv-radius-md);text-align:center}.jv-modal-selector-option{display:flex;align-items:center;justify-content:space-between;gap:var(--jv-spacing-md);padding:var(--jv-spacing-md);background:var(--jv-color-surface);cursor:pointer;outline:none;transition:background-color .16s ease}.jv-modal-selector-option:hover,.jv-modal-selector-option:focus-visible{background:var(--jv-color-surface-muted)}.jv-modal-selector-option:focus-visible{outline:2px solid var(--jv-color-primary);outline-offset:-2px}.jv-modal-selector-option--selected{background:color-mix(in srgb,var(--jv-color-primary) 8%,var(--jv-color-surface))}.jv-modal-selector-option--disabled{opacity:.55;cursor:not-allowed;pointer-events:none}.jv-modal-selector-option-copy{min-width:0;display:grid;gap:.25rem}.jv-modal-selector-option-label{font-weight:600;color:var(--jv-color-foreground)}\n"], dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { 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: JvIconComponent, selector: "jv-icon", inputs: ["name", "size", "strokeWidth", "decorative", "ariaLabel"] }, { kind: "component", type: JvInputComponent, selector: "jv-input", inputs: ["type", "placeholder", "required", "invalid", "describedBy", "inputId"] }, { kind: "component", type: JvListComponent, selector: "jv-list", inputs: ["role", "ariaRole", "variant", "ariaLabel"] }] });
21650
+ ], viewQueries: [{ propertyName: "triggerButtonRef", first: true, predicate: ["triggerButton"], descendants: true, isSignal: true }, { propertyName: "optionElements", predicate: ["optionElement"], descendants: true, isSignal: true }], ngImport: i0, template: `
21651
+ <jv-form-container
21652
+ [label]="label()"
21653
+ [inputId]="inputId()"
21654
+ [required]="required()"
21655
+ [invalid]="invalid()"
21656
+ [hint]="hint()"
21657
+ [error]="error()"
21658
+ >
21659
+ <button
21660
+ #triggerButton
21661
+ type="button"
21662
+ class="jv-modal-selector-trigger"
21663
+ [class.is-invalid]="invalid()"
21664
+ [id]="inputId()"
21665
+ [disabled]="disabled"
21666
+ [attr.aria-haspopup]="'dialog'"
21667
+ [attr.aria-expanded]="dialogOpen() ? 'true' : 'false'"
21668
+ [attr.aria-controls]="dialogId"
21669
+ [attr.aria-invalid]="invalid() ? 'true' : null"
21670
+ [attr.aria-describedby]="describedBy() || null"
21671
+ [attr.aria-label]="triggerAriaLabel()"
21672
+ (click)="openDialog()"
21673
+ (blur)="handleBlur()"
21674
+ >
21675
+ <span class="jv-modal-selector-trigger-copy">
21676
+ <span [class.jv-modal-selector-trigger-placeholder]="!selectedOption()">
21677
+ {{ selectedOption()?.label ?? resolvedPlaceholder() }}
21678
+ </span>
21679
+ </span>
21680
+ <jv-icon name="chevrons-up-down" [size]="18" />
21681
+ </button>
21682
+ </jv-form-container>
21683
+
21684
+ <jv-dialog [open]="dialogOpen()" [title]="resolvedDialogTitle()" (closed)="closeDialog()">
21685
+ <div [id]="dialogId" class="jv-modal-selector-dialog">
21686
+ @if (dialogSubtitle()) {
21687
+ <p class="jv-modal-selector-subtitle">{{ dialogSubtitle() }}</p>
21688
+ }
21689
+
21690
+ @if (showSearch()) {
21691
+ <jv-input
21692
+ type="search"
21693
+ [inputId]="searchInputId"
21694
+ [placeholder]="searchPlaceholder() || translation.translate('modalSelector.searchPlaceholder')"
21695
+ [describedBy]="resultsId"
21696
+ [ngModel]="searchQuery()"
21697
+ (ngModelChange)="searchQuery.set($event)"
21698
+ />
21699
+ }
21700
+
21701
+ <p class="jv-modal-selector-results" [id]="resultsId" aria-live="polite">{{ resultsLabel() }}</p>
21702
+
21703
+ @if (filteredOptions().length > 0) {
21704
+ <jv-list [listId]="listboxId" variant="bordered" [ariaRole]="'listbox'" [ariaLabel]="resolvedDialogTitle()">
21705
+ @for (option of filteredOptions(); track option.value; let index = $index) {
21706
+ <li role="presentation">
21707
+ <div
21708
+ #optionElement
21709
+ class="jv-modal-selector-option"
21710
+ [class.jv-modal-selector-option--selected]="isSelected(option)"
21711
+ [class.jv-modal-selector-option--disabled]="option.disabled ?? false"
21712
+ [id]="optionId(index)"
21713
+ role="option"
21714
+ [attr.aria-selected]="isSelected(option) ? 'true' : 'false'"
21715
+ [attr.aria-disabled]="option.disabled ? 'true' : null"
21716
+ [attr.tabindex]="getOptionTabIndex(option, index)"
21717
+ (click)="selectOption(option)"
21718
+ (keydown)="handleOptionKeydown($event, index, option)"
21719
+ >
21720
+ <div class="jv-modal-selector-option-copy">
21721
+ <span class="jv-modal-selector-option-label">{{ option.label }}</span>
21722
+ @if (option.description) {
21723
+ <span class="jv-modal-selector-option-description">{{ option.description }}</span>
21724
+ }
21725
+ </div>
21726
+ @if (isSelected(option)) {
21727
+ <jv-icon name="check" [size]="16" />
21728
+ }
21729
+ </div>
21730
+ </li>
21731
+ }
21732
+ </jv-list>
21733
+ } @else {
21734
+ <div class="jv-modal-selector-empty" aria-live="polite">{{ emptyMessage() || translation.translate('modalSelector.emptyMessage') }}</div>
21735
+ }
21736
+ </div>
21737
+
21738
+ <div dialog-actions class="jv-modal-selector-actions">
21739
+ @if (selectedOption()) {
21740
+ <jv-button variant="ghost" (click)="clearSelection()">{{ clearLabel() || translation.translate('modalSelector.clear') }}</jv-button>
21741
+ }
21742
+ <jv-button variant="outline" (click)="closeDialog()">{{ closeLabel() || translation.translate('modalSelector.close') }}</jv-button>
21743
+ </div>
21744
+ </jv-dialog>
21745
+ `, isInline: true, styles: [".jv-modal-selector-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-modal-selector-trigger.is-invalid{border-color:var(--jv-color-danger)}.jv-modal-selector-trigger:disabled{opacity:.7;cursor:not-allowed}.jv-modal-selector-trigger:focus-visible{outline:2px solid var(--jv-color-primary);outline-offset:1px}.jv-modal-selector-trigger-copy{min-width:0;flex:1}.jv-modal-selector-trigger-placeholder{color:var(--jv-color-foreground-muted)}.jv-modal-selector-dialog,.jv-modal-selector-actions{display:grid;gap:var(--jv-spacing-md)}.jv-modal-selector-subtitle,.jv-modal-selector-results,.jv-modal-selector-option-description,.jv-modal-selector-empty{margin:0;color:var(--jv-color-foreground-muted)}.jv-modal-selector-empty{padding:var(--jv-spacing-md);border:1px dashed var(--jv-color-border);border-radius:var(--jv-radius-md);text-align:center}.jv-modal-selector-option{display:flex;align-items:center;justify-content:space-between;gap:var(--jv-spacing-md);padding:var(--jv-spacing-md);background:var(--jv-color-surface);cursor:pointer;outline:none;transition:background-color .16s ease}.jv-modal-selector-option:hover,.jv-modal-selector-option:focus-visible{background:var(--jv-color-surface-muted)}.jv-modal-selector-option:focus-visible{outline:2px solid var(--jv-color-primary);outline-offset:-2px}.jv-modal-selector-option--selected{background:color-mix(in srgb,var(--jv-color-primary) 8%,var(--jv-color-surface))}.jv-modal-selector-option--disabled{opacity:.55;cursor:not-allowed;pointer-events:none}.jv-modal-selector-option-copy{min-width:0;display:grid;gap:.25rem}.jv-modal-selector-option-label{font-weight:600;color:var(--jv-color-foreground)}\n"], dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { 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: JvIconComponent, selector: "jv-icon", inputs: ["name", "size", "strokeWidth", "decorative", "ariaLabel"] }, { kind: "component", type: JvInputComponent, selector: "jv-input", inputs: ["type", "placeholder", "required", "invalid", "describedBy", "inputId"] }, { kind: "component", type: JvListComponent, selector: "jv-list", inputs: ["listId", "role", "ariaRole", "variant", "ariaLabel"] }] });
21628
21746
  }
21629
21747
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: JvModalSelectorComponent, decorators: [{
21630
21748
  type: Component,
@@ -21634,99 +21752,101 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImpo
21634
21752
  useExisting: forwardRef(() => JvModalSelectorComponent),
21635
21753
  multi: true,
21636
21754
  },
21637
- ], template: `
21638
- <jv-form-container
21639
- [label]="label()"
21640
- [inputId]="inputId()"
21641
- [required]="required()"
21642
- [invalid]="invalid()"
21643
- [hint]="hint()"
21644
- [error]="error()"
21645
- >
21646
- <button
21647
- #triggerButton
21648
- type="button"
21649
- class="jv-modal-selector-trigger"
21650
- [class.is-invalid]="invalid()"
21651
- [id]="inputId()"
21652
- [disabled]="disabled"
21653
- [attr.aria-haspopup]="'dialog'"
21654
- [attr.aria-expanded]="dialogOpen() ? 'true' : 'false'"
21655
- [attr.aria-controls]="dialogId"
21656
- [attr.aria-invalid]="invalid() ? 'true' : null"
21657
- [attr.aria-describedby]="describedBy() || null"
21658
- [attr.aria-label]="triggerAriaLabel()"
21659
- (click)="openDialog()"
21660
- (blur)="handleBlur()"
21661
- >
21662
- <span class="jv-modal-selector-trigger-copy">
21663
- <span [class.jv-modal-selector-trigger-placeholder]="!selectedOption()">
21664
- {{ selectedOption()?.label ?? resolvedPlaceholder() }}
21665
- </span>
21666
- </span>
21667
- <jv-icon name="chevrons-up-down" [size]="18" />
21668
- </button>
21669
- </jv-form-container>
21670
-
21671
- <jv-dialog [open]="dialogOpen()" [title]="resolvedDialogTitle()" (closed)="closeDialog()">
21672
- <div [id]="dialogId" class="jv-modal-selector-dialog">
21673
- @if (dialogSubtitle()) {
21674
- <p class="jv-modal-selector-subtitle">{{ dialogSubtitle() }}</p>
21675
- }
21676
-
21677
- @if (showSearch()) {
21678
- <jv-input
21679
- type="search"
21680
- [inputId]="searchInputId"
21681
- [placeholder]="searchPlaceholder() || translation.translate('modalSelector.searchPlaceholder')"
21682
- [ngModel]="searchQuery()"
21683
- (ngModelChange)="searchQuery.set($event)"
21684
- />
21685
- }
21686
-
21687
- <p class="jv-modal-selector-results" aria-live="polite">{{ resultsLabel() }}</p>
21688
-
21689
- @if (filteredOptions().length > 0) {
21690
- <jv-list variant="bordered" [ariaRole]="'listbox'" [ariaLabel]="resolvedDialogTitle()">
21691
- @for (option of filteredOptions(); track option.value; let index = $index) {
21692
- <li role="presentation">
21693
- <div
21694
- #optionElement
21695
- class="jv-modal-selector-option"
21696
- [class.jv-modal-selector-option--selected]="isSelected(option)"
21697
- [class.jv-modal-selector-option--disabled]="option.disabled ?? false"
21698
- [id]="optionId(index)"
21699
- role="option"
21700
- [attr.aria-selected]="isSelected(option) ? 'true' : 'false'"
21701
- [attr.tabindex]="getOptionTabIndex(option, index)"
21702
- (click)="selectOption(option)"
21703
- (keydown)="handleOptionKeydown($event, index, option)"
21704
- >
21705
- <div class="jv-modal-selector-option-copy">
21706
- <span class="jv-modal-selector-option-label">{{ option.label }}</span>
21707
- @if (option.description) {
21708
- <span class="jv-modal-selector-option-description">{{ option.description }}</span>
21709
- }
21710
- </div>
21711
- @if (isSelected(option)) {
21712
- <jv-icon name="check" [size]="16" />
21713
- }
21714
- </div>
21715
- </li>
21716
- }
21717
- </jv-list>
21718
- } @else {
21719
- <div class="jv-modal-selector-empty" aria-live="polite">{{ emptyMessage() || translation.translate('modalSelector.emptyMessage') }}</div>
21720
- }
21721
- </div>
21722
-
21723
- <div dialog-actions class="jv-modal-selector-actions">
21724
- @if (selectedOption()) {
21725
- <jv-button variant="ghost" (click)="clearSelection()">{{ clearLabel() || translation.translate('modalSelector.clear') }}</jv-button>
21726
- }
21727
- <jv-button variant="outline" (click)="closeDialog()">{{ closeLabel() || translation.translate('modalSelector.close') }}</jv-button>
21728
- </div>
21729
- </jv-dialog>
21755
+ ], template: `
21756
+ <jv-form-container
21757
+ [label]="label()"
21758
+ [inputId]="inputId()"
21759
+ [required]="required()"
21760
+ [invalid]="invalid()"
21761
+ [hint]="hint()"
21762
+ [error]="error()"
21763
+ >
21764
+ <button
21765
+ #triggerButton
21766
+ type="button"
21767
+ class="jv-modal-selector-trigger"
21768
+ [class.is-invalid]="invalid()"
21769
+ [id]="inputId()"
21770
+ [disabled]="disabled"
21771
+ [attr.aria-haspopup]="'dialog'"
21772
+ [attr.aria-expanded]="dialogOpen() ? 'true' : 'false'"
21773
+ [attr.aria-controls]="dialogId"
21774
+ [attr.aria-invalid]="invalid() ? 'true' : null"
21775
+ [attr.aria-describedby]="describedBy() || null"
21776
+ [attr.aria-label]="triggerAriaLabel()"
21777
+ (click)="openDialog()"
21778
+ (blur)="handleBlur()"
21779
+ >
21780
+ <span class="jv-modal-selector-trigger-copy">
21781
+ <span [class.jv-modal-selector-trigger-placeholder]="!selectedOption()">
21782
+ {{ selectedOption()?.label ?? resolvedPlaceholder() }}
21783
+ </span>
21784
+ </span>
21785
+ <jv-icon name="chevrons-up-down" [size]="18" />
21786
+ </button>
21787
+ </jv-form-container>
21788
+
21789
+ <jv-dialog [open]="dialogOpen()" [title]="resolvedDialogTitle()" (closed)="closeDialog()">
21790
+ <div [id]="dialogId" class="jv-modal-selector-dialog">
21791
+ @if (dialogSubtitle()) {
21792
+ <p class="jv-modal-selector-subtitle">{{ dialogSubtitle() }}</p>
21793
+ }
21794
+
21795
+ @if (showSearch()) {
21796
+ <jv-input
21797
+ type="search"
21798
+ [inputId]="searchInputId"
21799
+ [placeholder]="searchPlaceholder() || translation.translate('modalSelector.searchPlaceholder')"
21800
+ [describedBy]="resultsId"
21801
+ [ngModel]="searchQuery()"
21802
+ (ngModelChange)="searchQuery.set($event)"
21803
+ />
21804
+ }
21805
+
21806
+ <p class="jv-modal-selector-results" [id]="resultsId" aria-live="polite">{{ resultsLabel() }}</p>
21807
+
21808
+ @if (filteredOptions().length > 0) {
21809
+ <jv-list [listId]="listboxId" variant="bordered" [ariaRole]="'listbox'" [ariaLabel]="resolvedDialogTitle()">
21810
+ @for (option of filteredOptions(); track option.value; let index = $index) {
21811
+ <li role="presentation">
21812
+ <div
21813
+ #optionElement
21814
+ class="jv-modal-selector-option"
21815
+ [class.jv-modal-selector-option--selected]="isSelected(option)"
21816
+ [class.jv-modal-selector-option--disabled]="option.disabled ?? false"
21817
+ [id]="optionId(index)"
21818
+ role="option"
21819
+ [attr.aria-selected]="isSelected(option) ? 'true' : 'false'"
21820
+ [attr.aria-disabled]="option.disabled ? 'true' : null"
21821
+ [attr.tabindex]="getOptionTabIndex(option, index)"
21822
+ (click)="selectOption(option)"
21823
+ (keydown)="handleOptionKeydown($event, index, option)"
21824
+ >
21825
+ <div class="jv-modal-selector-option-copy">
21826
+ <span class="jv-modal-selector-option-label">{{ option.label }}</span>
21827
+ @if (option.description) {
21828
+ <span class="jv-modal-selector-option-description">{{ option.description }}</span>
21829
+ }
21830
+ </div>
21831
+ @if (isSelected(option)) {
21832
+ <jv-icon name="check" [size]="16" />
21833
+ }
21834
+ </div>
21835
+ </li>
21836
+ }
21837
+ </jv-list>
21838
+ } @else {
21839
+ <div class="jv-modal-selector-empty" aria-live="polite">{{ emptyMessage() || translation.translate('modalSelector.emptyMessage') }}</div>
21840
+ }
21841
+ </div>
21842
+
21843
+ <div dialog-actions class="jv-modal-selector-actions">
21844
+ @if (selectedOption()) {
21845
+ <jv-button variant="ghost" (click)="clearSelection()">{{ clearLabel() || translation.translate('modalSelector.clear') }}</jv-button>
21846
+ }
21847
+ <jv-button variant="outline" (click)="closeDialog()">{{ closeLabel() || translation.translate('modalSelector.close') }}</jv-button>
21848
+ </div>
21849
+ </jv-dialog>
21730
21850
  `, styles: [".jv-modal-selector-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-modal-selector-trigger.is-invalid{border-color:var(--jv-color-danger)}.jv-modal-selector-trigger:disabled{opacity:.7;cursor:not-allowed}.jv-modal-selector-trigger:focus-visible{outline:2px solid var(--jv-color-primary);outline-offset:1px}.jv-modal-selector-trigger-copy{min-width:0;flex:1}.jv-modal-selector-trigger-placeholder{color:var(--jv-color-foreground-muted)}.jv-modal-selector-dialog,.jv-modal-selector-actions{display:grid;gap:var(--jv-spacing-md)}.jv-modal-selector-subtitle,.jv-modal-selector-results,.jv-modal-selector-option-description,.jv-modal-selector-empty{margin:0;color:var(--jv-color-foreground-muted)}.jv-modal-selector-empty{padding:var(--jv-spacing-md);border:1px dashed var(--jv-color-border);border-radius:var(--jv-radius-md);text-align:center}.jv-modal-selector-option{display:flex;align-items:center;justify-content:space-between;gap:var(--jv-spacing-md);padding:var(--jv-spacing-md);background:var(--jv-color-surface);cursor:pointer;outline:none;transition:background-color .16s ease}.jv-modal-selector-option:hover,.jv-modal-selector-option:focus-visible{background:var(--jv-color-surface-muted)}.jv-modal-selector-option:focus-visible{outline:2px solid var(--jv-color-primary);outline-offset:-2px}.jv-modal-selector-option--selected{background:color-mix(in srgb,var(--jv-color-primary) 8%,var(--jv-color-surface))}.jv-modal-selector-option--disabled{opacity:.55;cursor:not-allowed;pointer-events:none}.jv-modal-selector-option-copy{min-width:0;display:grid;gap:.25rem}.jv-modal-selector-option-label{font-weight:600;color:var(--jv-color-foreground)}\n"] }]
21731
21851
  }], propDecorators: { triggerButtonRef: [{ type: i0.ViewChild, args: ['triggerButton', { isSignal: true }] }], optionElements: [{ type: i0.ViewChildren, args: ['optionElement', { isSignal: true }] }], options: [{ type: i0.Input, args: [{ isSignal: true, alias: "options", required: false }] }], 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 }] }], dialogSubtitle: [{ type: i0.Input, args: [{ isSignal: true, alias: "dialogSubtitle", required: false }] }], searchPlaceholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "searchPlaceholder", required: false }] }], emptyMessage: [{ type: i0.Input, args: [{ isSignal: true, alias: "emptyMessage", required: false }] }], clearLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "clearLabel", required: false }] }], closeLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "closeLabel", required: false }] }], showSearch: [{ type: i0.Input, args: [{ isSignal: true, alias: "showSearch", 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"] }] } });
21732
21852
 
@@ -23532,8 +23652,9 @@ class JvListItemComponent {
23532
23652
  class="jv-list-item"
23533
23653
  [class.jv-list-item--disabled]="disabled()"
23534
23654
  [class.jv-list-item--active]="active()"
23535
- tabindex="0"
23655
+ [attr.tabindex]="disabled() ? -1 : 0"
23536
23656
  role="listitem"
23657
+ [attr.aria-disabled]="disabled() ? 'true' : null"
23537
23658
  (click)="onActivate()"
23538
23659
  (keydown.enter)="onActivate()"
23539
23660
  (keydown.space)="onActivate(); $event.preventDefault()"
@@ -23560,8 +23681,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImpo
23560
23681
  class="jv-list-item"
23561
23682
  [class.jv-list-item--disabled]="disabled()"
23562
23683
  [class.jv-list-item--active]="active()"
23563
- tabindex="0"
23684
+ [attr.tabindex]="disabled() ? -1 : 0"
23564
23685
  role="listitem"
23686
+ [attr.aria-disabled]="disabled() ? 'true' : null"
23565
23687
  (click)="onActivate()"
23566
23688
  (keydown.enter)="onActivate()"
23567
23689
  (keydown.space)="onActivate(); $event.preventDefault()"
@@ -23797,11 +23919,23 @@ class JvTabsComponent {
23797
23919
  </div>
23798
23920
 
23799
23921
  @if (showScrollLeft() || showScrollRight()) {
23800
- <div class="jv-tabs-controls" aria-hidden="true">
23801
- <button type="button" class="jv-tabs-scroll-button" [disabled]="!showScrollLeft()" (click)="scrollTabs(-1)">
23922
+ <div class="jv-tabs-controls">
23923
+ <button
23924
+ type="button"
23925
+ class="jv-tabs-scroll-button"
23926
+ aria-label="Scroll tabs backward"
23927
+ [disabled]="!showScrollLeft()"
23928
+ (click)="scrollTabs(-1)"
23929
+ >
23802
23930
  <jv-icon name="chevron-left" [size]="16" />
23803
23931
  </button>
23804
- <button type="button" class="jv-tabs-scroll-button" [disabled]="!showScrollRight()" (click)="scrollTabs(1)">
23932
+ <button
23933
+ type="button"
23934
+ class="jv-tabs-scroll-button"
23935
+ aria-label="Scroll tabs forward"
23936
+ [disabled]="!showScrollRight()"
23937
+ (click)="scrollTabs(1)"
23938
+ >
23805
23939
  <jv-icon name="chevron-right" [size]="16" />
23806
23940
  </button>
23807
23941
  </div>
@@ -23892,11 +24026,23 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImpo
23892
24026
  </div>
23893
24027
 
23894
24028
  @if (showScrollLeft() || showScrollRight()) {
23895
- <div class="jv-tabs-controls" aria-hidden="true">
23896
- <button type="button" class="jv-tabs-scroll-button" [disabled]="!showScrollLeft()" (click)="scrollTabs(-1)">
24029
+ <div class="jv-tabs-controls">
24030
+ <button
24031
+ type="button"
24032
+ class="jv-tabs-scroll-button"
24033
+ aria-label="Scroll tabs backward"
24034
+ [disabled]="!showScrollLeft()"
24035
+ (click)="scrollTabs(-1)"
24036
+ >
23897
24037
  <jv-icon name="chevron-left" [size]="16" />
23898
24038
  </button>
23899
- <button type="button" class="jv-tabs-scroll-button" [disabled]="!showScrollRight()" (click)="scrollTabs(1)">
24039
+ <button
24040
+ type="button"
24041
+ class="jv-tabs-scroll-button"
24042
+ aria-label="Scroll tabs forward"
24043
+ [disabled]="!showScrollRight()"
24044
+ (click)="scrollTabs(1)"
24045
+ >
23900
24046
  <jv-icon name="chevron-right" [size]="16" />
23901
24047
  </button>
23902
24048
  </div>
@@ -23969,6 +24115,7 @@ class JvTooltipDirective {
23969
24115
  showTimeout = null;
23970
24116
  hideTimeout = null;
23971
24117
  tooltipId = `jv-tooltip-${++tooltipSequence}`;
24118
+ originalDescribedBy = null;
23972
24119
  text = input.required({ ...(ngDevMode ? { debugName: "text" } : /* istanbul ignore next */ {}), alias: 'jvTooltip' });
23973
24120
  position = input('top', { ...(ngDevMode ? { debugName: "position" } : /* istanbul ignore next */ {}), alias: 'jvTooltipPosition' });
23974
24121
  show() {
@@ -23982,7 +24129,7 @@ class JvTooltipDirective {
23982
24129
  this.showTimeout = setTimeout(() => {
23983
24130
  this.renderTooltip();
23984
24131
  this.positionTooltip();
23985
- this.renderer.setAttribute(this.anchorElement(), 'aria-describedby', this.tooltipId);
24132
+ this.attachTooltipDescription();
23986
24133
  }, 120);
23987
24134
  }
23988
24135
  hide() {
@@ -24069,10 +24216,29 @@ class JvTooltipDirective {
24069
24216
  if (!this.tooltipElement) {
24070
24217
  return;
24071
24218
  }
24072
- this.renderer.removeAttribute(this.anchorElement(), 'aria-describedby');
24219
+ this.restoreTooltipDescription();
24073
24220
  this.renderer.removeChild(this.document.body, this.tooltipElement);
24074
24221
  this.tooltipElement = null;
24075
24222
  }
24223
+ attachTooltipDescription() {
24224
+ const anchor = this.anchorElement();
24225
+ this.originalDescribedBy ??= anchor.getAttribute('aria-describedby');
24226
+ const describedBy = [this.originalDescribedBy, this.tooltipId]
24227
+ .filter((value) => !!value)
24228
+ .join(' ')
24229
+ .trim();
24230
+ this.renderer.setAttribute(anchor, 'aria-describedby', describedBy);
24231
+ }
24232
+ restoreTooltipDescription() {
24233
+ const anchor = this.anchorElement();
24234
+ if (this.originalDescribedBy) {
24235
+ this.renderer.setAttribute(anchor, 'aria-describedby', this.originalDescribedBy);
24236
+ }
24237
+ else {
24238
+ this.renderer.removeAttribute(anchor, 'aria-describedby');
24239
+ }
24240
+ this.originalDescribedBy = null;
24241
+ }
24076
24242
  anchorElement() {
24077
24243
  const host = this.hostRef.nativeElement;
24078
24244
  if (isInteractiveElement(host)) {
@@ -24152,6 +24318,7 @@ const JV_GRID_DEFAULT_OPTIONS = {
24152
24318
  exportFileName: '',
24153
24319
  columnFilters: false,
24154
24320
  rowDoubleClick: false,
24321
+ keyboardNavigation: false,
24155
24322
  };
24156
24323
 
24157
24324
  const MAIN_NS = 'http://schemas.openxmlformats.org/spreadsheetml/2006/main';
@@ -24432,6 +24599,9 @@ function exportGridToExcel(header, rows, filename = 'export.xlsx') {
24432
24599
  let gridIdSequence = 0;
24433
24600
  class JvGridComponent {
24434
24601
  gridId = `jv-grid-${++gridIdSequence}`;
24602
+ gridSearchInputId = `${this.gridId}-search`;
24603
+ destroyRef = inject(DestroyRef);
24604
+ document = inject(DOCUMENT);
24435
24605
  formatService = inject(JvFormatService);
24436
24606
  translationService = inject(JvTranslationService);
24437
24607
  data = input([], ...(ngDevMode ? [{ debugName: "data" }] : /* istanbul ignore next */ []));
@@ -24439,7 +24609,7 @@ class JvGridComponent {
24439
24609
  actions = input([], ...(ngDevMode ? [{ debugName: "actions" }] : /* istanbul ignore next */ []));
24440
24610
  options = input({}, ...(ngDevMode ? [{ debugName: "options" }] : /* istanbul ignore next */ []));
24441
24611
  trackBy = input(null, ...(ngDevMode ? [{ debugName: "trackBy" }] : /* istanbul ignore next */ []));
24442
- selectedIds = input([], ...(ngDevMode ? [{ debugName: "selectedIds" }] : /* istanbul ignore next */ []));
24612
+ selectedIds = input(null, ...(ngDevMode ? [{ debugName: "selectedIds" }] : /* istanbul ignore next */ []));
24443
24613
  rowClick = output();
24444
24614
  rowDoubleClick = output();
24445
24615
  actionClick = output();
@@ -24463,8 +24633,9 @@ class JvGridComponent {
24463
24633
  columnOrder = signal([], ...(ngDevMode ? [{ debugName: "columnOrder" }] : /* istanbul ignore next */ []));
24464
24634
  colFilters = signal({}, ...(ngDevMode ? [{ debugName: "colFilters" }] : /* istanbul ignore next */ []));
24465
24635
  columnWidths = signal({}, ...(ngDevMode ? [{ debugName: "columnWidths" }] : /* istanbul ignore next */ []));
24636
+ activeCell = signal({ row: 0, column: 0 }, ...(ngDevMode ? [{ debugName: "activeCell" }] : /* istanbul ignore next */ []));
24466
24637
  visibleColumns = computed(() => {
24467
- const cols = this.columns().filter(col => !col.hidden);
24638
+ const cols = this.flattenColumns(this.columns()).filter(col => !col.hidden);
24468
24639
  const order = this.columnOrder();
24469
24640
  if (order.length > 0) {
24470
24641
  return order.map(key => cols.find(c => this.colKey(c) === key)).filter(Boolean);
@@ -24476,15 +24647,29 @@ class JvGridComponent {
24476
24647
  searchTerm = signal('', ...(ngDevMode ? [{ debugName: "searchTerm" }] : /* istanbul ignore next */ []));
24477
24648
  sortState = signal({ columnKey: '', direction: null }, ...(ngDevMode ? [{ debugName: "sortState" }] : /* istanbul ignore next */ []));
24478
24649
  pageIndex = signal(0, ...(ngDevMode ? [{ debugName: "pageIndex" }] : /* istanbul ignore next */ []));
24479
- pageSize = computed(() => this.effectiveOptions().pageSize ?? 20, ...(ngDevMode ? [{ debugName: "pageSize" }] : /* istanbul ignore next */ []));
24650
+ internalPageSize = signal(null, ...(ngDevMode ? [{ debugName: "internalPageSize" }] : /* istanbul ignore next */ []));
24651
+ pageSize = computed(() => this.internalPageSize() ?? this.effectiveOptions().pageSize ?? 20, ...(ngDevMode ? [{ debugName: "pageSize" }] : /* istanbul ignore next */ []));
24652
+ pageSizeStr = computed(() => String(this.pageSize()), ...(ngDevMode ? [{ debugName: "pageSizeStr" }] : /* istanbul ignore next */ []));
24653
+ pageSizeOptions = computed(() => (this.effectiveOptions().pageSizeOptions ?? [])
24654
+ .filter(size => Number.isInteger(size) && size > 0)
24655
+ .map(size => ({ label: String(size), value: String(size) })), ...(ngDevMode ? [{ debugName: "pageSizeOptions" }] : /* istanbul ignore next */ []));
24656
+ pageSizeInputSync = effect(() => {
24657
+ this.options().pageSize;
24658
+ this.internalPageSize.set(null);
24659
+ }, ...(ngDevMode ? [{ debugName: "pageSizeInputSync" }] : /* istanbul ignore next */ []));
24480
24660
  searchPageResetter = effect(() => {
24481
24661
  this.searchTerm();
24482
24662
  this.pageIndex.set(0);
24483
24663
  }, ...(ngDevMode ? [{ debugName: "searchPageResetter" }] : /* istanbul ignore next */ []));
24664
+ pageBoundsGuard = effect(() => {
24665
+ const lastPage = this.effectiveTotalPages() - 1;
24666
+ if (this.pageIndex() > lastPage)
24667
+ this.pageIndex.set(Math.max(0, lastPage));
24668
+ }, ...(ngDevMode ? [{ debugName: "pageBoundsGuard" }] : /* istanbul ignore next */ []));
24484
24669
  selectedIdSet = computed(() => {
24485
24670
  const fromInput = this.selectedIds();
24486
24671
  const fromInternal = this.internalSelectedIds();
24487
- const ids = fromInput.length > 0 ? fromInput : fromInternal;
24672
+ const ids = fromInput !== null ? fromInput : fromInternal;
24488
24673
  return new Set(ids);
24489
24674
  }, ...(ngDevMode ? [{ debugName: "selectedIdSet" }] : /* istanbul ignore next */ []));
24490
24675
  hasActiveFilters = computed(() => Object.values(this.colFilters()).some(f => f.value.trim().length > 0), ...(ngDevMode ? [{ debugName: "hasActiveFilters" }] : /* istanbul ignore next */ []));
@@ -24556,9 +24741,8 @@ class JvGridComponent {
24556
24741
  }, ...(ngDevMode ? [{ debugName: "effectiveTotalItems" }] : /* istanbul ignore next */ []));
24557
24742
  effectiveTotalPages = computed(() => Math.max(1, Math.ceil(this.effectiveTotalItems() / this.pageSize())), ...(ngDevMode ? [{ debugName: "effectiveTotalPages" }] : /* istanbul ignore next */ []));
24558
24743
  displayData = computed(() => {
24559
- if (this.effectiveOptions().virtualScroll) {
24560
- return this.sortedData();
24561
- }
24744
+ if (this.virtualScrollEnabled())
24745
+ return this.virtualDisplayData();
24562
24746
  if (!this.effectiveOptions().pageable)
24563
24747
  return this.sortedData();
24564
24748
  const items = this.sortedData();
@@ -24566,12 +24750,17 @@ class JvGridComponent {
24566
24750
  const page = Math.min(this.pageIndex(), this.effectiveTotalPages() - 1);
24567
24751
  return items.slice(page * ps, page * ps + ps);
24568
24752
  }, ...(ngDevMode ? [{ debugName: "displayData" }] : /* istanbul ignore next */ []));
24569
- virtualScroll = computed(() => this.effectiveOptions().virtualScroll && this.effectiveOptions().virtualScrollRowHeight, ...(ngDevMode ? [{ debugName: "virtualScroll" }] : /* istanbul ignore next */ []));
24753
+ virtualScrollEnabled = computed(() => this.effectiveOptions().virtualScroll && !this.effectiveOptions().serverSide, ...(ngDevMode ? [{ debugName: "virtualScrollEnabled" }] : /* istanbul ignore next */ []));
24570
24754
  virtualStartIndex = signal(0, ...(ngDevMode ? [{ debugName: "virtualStartIndex" }] : /* istanbul ignore next */ []));
24571
- virtualEndIndex = signal(0, ...(ngDevMode ? [{ debugName: "virtualEndIndex" }] : /* istanbul ignore next */ []));
24755
+ virtualEndIndex = signal(20, ...(ngDevMode ? [{ debugName: "virtualEndIndex" }] : /* istanbul ignore next */ []));
24756
+ virtualRangeResetter = effect(() => {
24757
+ const length = this.sortedData().length;
24758
+ this.virtualStartIndex.set(0);
24759
+ this.virtualEndIndex.set(Math.min(20, length));
24760
+ }, ...(ngDevMode ? [{ debugName: "virtualRangeResetter" }] : /* istanbul ignore next */ []));
24572
24761
  virtualDisplayData = computed(() => {
24573
- if (!this.effectiveOptions().virtualScroll)
24574
- return this.displayData();
24762
+ if (!this.virtualScrollEnabled())
24763
+ return this.sortedData();
24575
24764
  const items = this.sortedData();
24576
24765
  const start = this.virtualStartIndex();
24577
24766
  const end = this.virtualEndIndex();
@@ -24617,6 +24806,95 @@ class JvGridComponent {
24617
24806
  return count;
24618
24807
  }, ...(ngDevMode ? [{ debugName: "colspan" }] : /* istanbul ignore next */ []));
24619
24808
  densityClass = computed(() => `jv-grid-density--${this.resolvedOptions().density ?? 'normal'}`, ...(ngDevMode ? [{ debugName: "densityClass" }] : /* istanbul ignore next */ []));
24809
+ cellTabIndex(rowIndex, columnIndex, col) {
24810
+ if (!this.resolvedOptions().keyboardNavigation) {
24811
+ return this.isCellEditable(col) ? -1 : null;
24812
+ }
24813
+ const active = this.activeCell();
24814
+ return active.row === rowIndex && active.column === columnIndex ? 0 : -1;
24815
+ }
24816
+ flattenColumns(columns) {
24817
+ return columns.flatMap(col => col.children?.length ? this.flattenColumns(col.children) : [col]);
24818
+ }
24819
+ onCellFocus(rowIndex, columnIndex) {
24820
+ if (this.resolvedOptions().keyboardNavigation) {
24821
+ this.activeCell.set({ row: rowIndex, column: columnIndex });
24822
+ }
24823
+ }
24824
+ onCellKeydown(event, rowIndex, columnIndex) {
24825
+ if (!this.resolvedOptions().keyboardNavigation)
24826
+ return;
24827
+ const target = event.target;
24828
+ if (target !== event.currentTarget && target.closest('button, input, select, textarea, [contenteditable="true"]')) {
24829
+ return;
24830
+ }
24831
+ const movement = {
24832
+ ArrowDown: [1, 0],
24833
+ ArrowLeft: [0, -1],
24834
+ ArrowRight: [0, 1],
24835
+ };
24836
+ if (event.key === 'ArrowUp' && rowIndex === 0) {
24837
+ event.preventDefault();
24838
+ this.activeCell.set({ row: 0, column: columnIndex });
24839
+ const table = event.currentTarget.closest('table');
24840
+ table?.querySelector(`[data-column-index="${columnIndex}"][role="columnheader"]`)?.focus();
24841
+ return;
24842
+ }
24843
+ const delta = event.key === 'ArrowUp' ? [-1, 0] : movement[event.key];
24844
+ if (!delta)
24845
+ return;
24846
+ const nextRow = rowIndex + delta[0];
24847
+ const nextColumn = columnIndex + delta[1];
24848
+ const rows = this.displayData();
24849
+ const columns = this.visibleColumns();
24850
+ if (nextRow < 0 || nextRow >= rows.length || nextColumn < 0 || nextColumn >= columns.length)
24851
+ return;
24852
+ event.preventDefault();
24853
+ this.activeCell.set({ row: nextRow, column: nextColumn });
24854
+ const table = event.currentTarget.closest('table');
24855
+ const nextCell = table?.querySelector(`.jv-grid-keyboard-cell[data-row-index="${nextRow}"][data-column-index="${nextColumn}"]`);
24856
+ nextCell?.focus();
24857
+ }
24858
+ onCellF2(event, rowIndex, columnIndex) {
24859
+ if (!this.resolvedOptions().keyboardNavigation)
24860
+ return;
24861
+ const col = this.visibleColumns()[columnIndex];
24862
+ const row = this.displayData()[rowIndex];
24863
+ if (!col || !row || !this.isCellEditable(col))
24864
+ return;
24865
+ event.preventDefault();
24866
+ event.stopImmediatePropagation();
24867
+ this.startEdit(row, col, event);
24868
+ }
24869
+ onHeaderFocus(columnIndex) {
24870
+ if (this.resolvedOptions().keyboardNavigation) {
24871
+ this.activeCell.set({ row: 0, column: columnIndex });
24872
+ }
24873
+ }
24874
+ onHeaderKeydown(event, columnIndex) {
24875
+ if (!this.resolvedOptions().keyboardNavigation)
24876
+ return;
24877
+ if (event.key === 'ArrowDown') {
24878
+ const table = event.currentTarget.closest('table');
24879
+ const firstCell = table?.querySelector(`.jv-grid-keyboard-cell[data-row-index="0"][data-column-index="${columnIndex}"]`);
24880
+ if (firstCell) {
24881
+ event.preventDefault();
24882
+ this.activeCell.set({ row: 0, column: columnIndex });
24883
+ firstCell.focus();
24884
+ }
24885
+ return;
24886
+ }
24887
+ const delta = event.key === 'ArrowLeft' ? -1 : event.key === 'ArrowRight' ? 1 : 0;
24888
+ if (!delta)
24889
+ return;
24890
+ const nextColumn = columnIndex + delta;
24891
+ if (nextColumn < 0 || nextColumn >= this.visibleColumns().length)
24892
+ return;
24893
+ event.preventDefault();
24894
+ this.activeCell.set({ row: 0, column: nextColumn });
24895
+ const table = event.currentTarget.closest('table');
24896
+ table?.querySelector(`[data-column-index="${nextColumn}"][role="columnheader"]`)?.focus();
24897
+ }
24620
24898
  editState = signal(null, ...(ngDevMode ? [{ debugName: "editState" }] : /* istanbul ignore next */ []));
24621
24899
  t = (key, params) => this.translationService.translate(key, params);
24622
24900
  getRowId(row, index) {
@@ -24626,6 +24904,11 @@ class JvGridComponent {
24626
24904
  const tb = this.trackBy();
24627
24905
  if (typeof tb === 'function')
24628
24906
  return tb(row, index ?? 0);
24907
+ if (row && typeof row === 'object' && ('id' in row)) {
24908
+ const id = row.id;
24909
+ if (typeof id === 'string' || typeof id === 'number')
24910
+ return id;
24911
+ }
24629
24912
  if (index !== undefined)
24630
24913
  return index;
24631
24914
  return this.data().indexOf(row);
@@ -24641,8 +24924,106 @@ class JvGridComponent {
24641
24924
  }
24642
24925
  getEditValue(row, col) {
24643
24926
  const val = this.getCellValue(row, col);
24927
+ const editType = this.editType(col);
24928
+ if (editType === 'date')
24929
+ return this.toDateInputValue(val);
24930
+ if (editType === 'datetime')
24931
+ return this.toDateTimeInputValue(val);
24932
+ if (editType === 'time')
24933
+ return this.toTimeInputValue(val);
24644
24934
  return val == null ? '' : String(val);
24645
24935
  }
24936
+ getBooleanEditValue(row, col) {
24937
+ const value = this.getCellValue(row, col);
24938
+ return value === true || value === 'true' || value === 1 || value === '1';
24939
+ }
24940
+ isBooleanTrue(value) {
24941
+ return value === true || value === 'true' || value === 1 || value === '1';
24942
+ }
24943
+ editType(col) {
24944
+ if (col.editType)
24945
+ return col.editType;
24946
+ switch (col.type) {
24947
+ case 'integer':
24948
+ case 'number':
24949
+ case 'currency':
24950
+ case 'percent':
24951
+ return 'number';
24952
+ case 'date':
24953
+ return 'date';
24954
+ case 'time':
24955
+ return 'time';
24956
+ case 'datetime':
24957
+ return 'datetime';
24958
+ case 'boolean':
24959
+ return 'boolean';
24960
+ default:
24961
+ return 'text';
24962
+ }
24963
+ }
24964
+ inputType(col) {
24965
+ switch (this.editType(col)) {
24966
+ case 'number': return 'number';
24967
+ case 'date': return 'date';
24968
+ case 'time': return 'time';
24969
+ case 'datetime': return 'datetime-local';
24970
+ default: return 'text';
24971
+ }
24972
+ }
24973
+ normalizeEditValue(col, value) {
24974
+ switch (this.editType(col)) {
24975
+ case 'number': {
24976
+ if (value === '' || value == null)
24977
+ return null;
24978
+ const number = Number(value);
24979
+ return Number.isFinite(number) ? number : null;
24980
+ }
24981
+ case 'date':
24982
+ return this.toDateInputValue(value) || null;
24983
+ case 'datetime': {
24984
+ if (value === '' || value == null)
24985
+ return null;
24986
+ const date = new Date(String(value));
24987
+ return Number.isNaN(date.getTime()) ? null : date.toISOString();
24988
+ }
24989
+ case 'time': {
24990
+ if (value === '' || value == null)
24991
+ return null;
24992
+ const time = String(value);
24993
+ return /^\d{2}:\d{2}$/.test(time) ? `${time}:00` : time;
24994
+ }
24995
+ case 'boolean':
24996
+ return Boolean(value);
24997
+ default:
24998
+ return value;
24999
+ }
25000
+ }
25001
+ toDateInputValue(value) {
25002
+ if (typeof value === 'string') {
25003
+ const match = value.match(/^\d{4}-\d{2}-\d{2}/);
25004
+ if (match)
25005
+ return match[0];
25006
+ }
25007
+ const date = this.formatService.toDate(value);
25008
+ if (!date)
25009
+ return '';
25010
+ return `${date.getUTCFullYear()}-${String(date.getUTCMonth() + 1).padStart(2, '0')}-${String(date.getUTCDate()).padStart(2, '0')}`;
25011
+ }
25012
+ toDateTimeInputValue(value) {
25013
+ const date = this.formatService.toDate(value);
25014
+ if (!date)
25015
+ return '';
25016
+ const pad = (part) => String(part).padStart(2, '0');
25017
+ return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())}T${pad(date.getHours())}:${pad(date.getMinutes())}`;
25018
+ }
25019
+ toTimeInputValue(value) {
25020
+ if (typeof value === 'string' && /^\d{2}:\d{2}(:\d{2})?$/.test(value))
25021
+ return value.slice(0, 5);
25022
+ const date = this.formatService.toDate(value);
25023
+ if (!date)
25024
+ return '';
25025
+ return `${String(date.getHours()).padStart(2, '0')}:${String(date.getMinutes()).padStart(2, '0')}`;
25026
+ }
24646
25027
  formatValue(value, col, _row) {
24647
25028
  if (value == null)
24648
25029
  return '';
@@ -24728,7 +25109,7 @@ class JvGridComponent {
24728
25109
  return (this.resolvedOptions().editable || col.editable === true) && col.editable !== false;
24729
25110
  }
24730
25111
  groupColspan(group) {
24731
- return group.children?.length ?? 1;
25112
+ return this.flattenColumns(group.children ?? []).filter(col => !col.hidden).length || 1;
24732
25113
  }
24733
25114
  isEditingCell(row, col) {
24734
25115
  const es = this.editState();
@@ -24745,30 +25126,28 @@ class JvGridComponent {
24745
25126
  }));
24746
25127
  }
24747
25128
  findColumn(key) {
24748
- return this.columns().find(c => this.colKey(c) === key) ?? { key, header: key };
25129
+ return this.visibleColumns().find(c => this.colKey(c) === key) ?? { key, header: key };
24749
25130
  }
24750
25131
  toggleSort(col, event) {
24751
- if (!event || !(event instanceof KeyboardEvent && event.key === ' ')) {
24752
- if (col.sortable === false || !this.resolvedOptions().sortable)
24753
- return;
24754
- if (event instanceof KeyboardEvent)
24755
- event.preventDefault();
24756
- const key = this.colKey(col);
24757
- const current = this.sortState();
24758
- let direction;
24759
- if (current.columnKey !== key) {
24760
- direction = 'asc';
24761
- }
24762
- else if (current.direction === 'asc') {
24763
- direction = 'desc';
24764
- }
24765
- else {
24766
- direction = null;
24767
- }
24768
- this.sortState.set({ columnKey: direction ? key : '', direction });
24769
- this.pageIndex.set(0);
24770
- this.sortChange.emit({ columnKey: direction ? key : '', direction });
25132
+ if (col.sortable === false || !this.resolvedOptions().sortable)
25133
+ return;
25134
+ if (event instanceof KeyboardEvent)
25135
+ event.preventDefault();
25136
+ const key = this.colKey(col);
25137
+ const current = this.sortState();
25138
+ let direction;
25139
+ if (current.columnKey !== key) {
25140
+ direction = 'asc';
24771
25141
  }
25142
+ else if (current.direction === 'asc') {
25143
+ direction = 'desc';
25144
+ }
25145
+ else {
25146
+ direction = null;
25147
+ }
25148
+ this.sortState.set({ columnKey: direction ? key : '', direction });
25149
+ this.pageIndex.set(0);
25150
+ this.sortChange.emit({ columnKey: direction ? key : '', direction });
24772
25151
  }
24773
25152
  actionVariant = (v) => v === 'primary' ? 'primary' : v === 'danger' ? 'danger' : 'ghost';
24774
25153
  onSearchInput(value) {
@@ -24786,29 +25165,37 @@ class JvGridComponent {
24786
25165
  }
24787
25166
  toggleRow(row) {
24788
25167
  const id = this.getRowId(row);
24789
- this.internalSelectedIds.update(ids => {
24790
- const set = new Set(ids);
24791
- if (set.has(id))
24792
- set.delete(id);
24793
- else
24794
- set.add(id);
24795
- return Array.from(set);
24796
- });
24797
- this.selectionChange.emit(this.internalSelectedIds());
25168
+ const set = new Set(this.selectedIdSet());
25169
+ if (set.has(id))
25170
+ set.delete(id);
25171
+ else
25172
+ set.add(id);
25173
+ const nextIds = Array.from(set);
25174
+ if (this.selectedIds() === null)
25175
+ this.internalSelectedIds.set(nextIds);
25176
+ this.selectionChange.emit(nextIds);
25177
+ }
25178
+ onPageSizeChange(value) {
25179
+ const pageSize = Number(value);
25180
+ if (!Number.isInteger(pageSize) || pageSize <= 0)
25181
+ return;
25182
+ this.internalPageSize.set(pageSize);
25183
+ this.pageIndex.set(0);
25184
+ this.pageChange.emit({ pageIndex: 0, pageSize });
24798
25185
  }
24799
25186
  toggleSelectAll(checked) {
24800
- this.internalSelectedIds.update(ids => {
24801
- const set = new Set(ids);
24802
- const rows = this.displayData();
24803
- rows.forEach(r => {
24804
- if (checked)
24805
- set.add(this.getRowId(r));
24806
- else
24807
- set.delete(this.getRowId(r));
24808
- });
24809
- return Array.from(set);
25187
+ const set = new Set(this.selectedIdSet());
25188
+ const rows = this.displayData();
25189
+ rows.forEach(r => {
25190
+ if (checked)
25191
+ set.add(this.getRowId(r));
25192
+ else
25193
+ set.delete(this.getRowId(r));
24810
25194
  });
24811
- this.selectionChange.emit(this.internalSelectedIds());
25195
+ const nextIds = Array.from(set);
25196
+ if (this.selectedIds() === null)
25197
+ this.internalSelectedIds.set(nextIds);
25198
+ this.selectionChange.emit(nextIds);
24812
25199
  }
24813
25200
  onRowClick(event, row) {
24814
25201
  if (event instanceof Event) {
@@ -24847,33 +25234,52 @@ class JvGridComponent {
24847
25234
  startEdit(row, col, event) {
24848
25235
  if (!this.isCellEditable(col))
24849
25236
  return;
24850
- if (col.type === 'boolean')
24851
- return;
25237
+ const cell = event.currentTarget;
24852
25238
  event.preventDefault();
24853
25239
  this.editState.set({ rowId: this.getRowId(row), colKey: this.colKey(col) });
25240
+ setTimeout(() => {
25241
+ const editor = cell?.querySelector('input:not(.jv-grid-checkbox), .jv-grid-checkbox, select');
25242
+ editor?.focus();
25243
+ if (editor instanceof HTMLInputElement && editor.type !== 'checkbox')
25244
+ editor.select();
25245
+ });
24854
25246
  }
24855
25247
  commitEdit(row, col, value) {
24856
25248
  const colKey = this.colKey(col);
24857
- if (this.editState()?.colKey === colKey) {
25249
+ if (this.editState()?.rowId === this.getRowId(row) && this.editState()?.colKey === colKey) {
24858
25250
  this.rowEdit.emit({ row, column: col, value });
24859
25251
  this.editState.set(null);
24860
25252
  }
24861
25253
  }
25254
+ commitEditFromKeyboard(event, row, col, value) {
25255
+ event.stopPropagation();
25256
+ const cell = event.currentTarget.closest('.jv-grid-keyboard-cell');
25257
+ this.commitEdit(row, col, value);
25258
+ setTimeout(() => cell?.focus());
25259
+ }
24862
25260
  cancelEdit() {
24863
25261
  this.editState.set(null);
24864
25262
  }
24865
25263
  onEditValueChange(row, col, value) {
24866
- this.commitEdit(row, col, value);
25264
+ const option = col.editOptions?.find(item => String(item.value) === value);
25265
+ this.commitEdit(row, col, option?.value ?? value);
24867
25266
  }
24868
25267
  // Column resizing
24869
25268
  resizeData = null;
25269
+ constructor() {
25270
+ this.destroyRef.onDestroy(() => {
25271
+ this.document.removeEventListener('mousemove', this.onResizeMove);
25272
+ this.document.removeEventListener('mouseup', this.onResizeEnd);
25273
+ this.resizeData = null;
25274
+ });
25275
+ }
24870
25276
  onResizeStart(event, col, _colIdx) {
24871
25277
  event.preventDefault();
24872
25278
  const th = event.target.closest('th');
24873
25279
  const startWidth = th.offsetWidth;
24874
25280
  this.resizeData = { col, startX: event.clientX, startWidth };
24875
- document.addEventListener('mousemove', this.onResizeMove);
24876
- document.addEventListener('mouseup', this.onResizeEnd);
25281
+ this.document.addEventListener('mousemove', this.onResizeMove);
25282
+ this.document.addEventListener('mouseup', this.onResizeEnd);
24877
25283
  }
24878
25284
  onResizeMove = (event) => {
24879
25285
  if (!this.resizeData)
@@ -24892,8 +25298,8 @@ class JvGridComponent {
24892
25298
  this.columnResize.emit({ columnKey: key, width: w });
24893
25299
  }
24894
25300
  this.resizeData = null;
24895
- document.removeEventListener('mousemove', this.onResizeMove);
24896
- document.removeEventListener('mouseup', this.onResizeEnd);
25301
+ this.document.removeEventListener('mousemove', this.onResizeMove);
25302
+ this.document.removeEventListener('mouseup', this.onResizeEnd);
24897
25303
  };
24898
25304
  // Column reordering
24899
25305
  dragIndex = null;
@@ -24927,7 +25333,7 @@ class JvGridComponent {
24927
25333
  }
24928
25334
  // Virtual scrolling
24929
25335
  onVirtualScroll(target) {
24930
- if (!this.effectiveOptions().virtualScroll)
25336
+ if (!this.virtualScrollEnabled())
24931
25337
  return;
24932
25338
  const rowHeight = this.effectiveOptions().virtualScrollRowHeight ?? 48;
24933
25339
  const buffer = 5;
@@ -24975,7 +25381,7 @@ class JvGridComponent {
24975
25381
  <div class="jv-grid-search">
24976
25382
  <jv-icon name="search" [size]="16" />
24977
25383
  <jv-input
24978
- inputId="grid-search"
25384
+ [inputId]="gridSearchInputId"
24979
25385
  [placeholder]="t('grid.searchPlaceholder')"
24980
25386
  [ngModel]="searchTerm()"
24981
25387
  (ngModelChange)="onSearchInput($event)"
@@ -24999,11 +25405,11 @@ class JvGridComponent {
24999
25405
  } @else {
25000
25406
  <div
25001
25407
  class="jv-grid-table-wrap"
25002
- [class.jv-grid-virtual]="resolvedOptions().virtualScroll"
25408
+ [class.jv-grid-virtual]="virtualScrollEnabled()"
25003
25409
  #tableWrap
25004
25410
  (scroll)="onVirtualScroll($any($event.target))"
25005
25411
  >
25006
- @if (resolvedOptions().virtualScroll) {
25412
+ @if (virtualScrollEnabled()) {
25007
25413
  <div class="jv-grid-virtual-spacer" [style.height.px]="virtualTotalHeight()"></div>
25008
25414
  }
25009
25415
  <table
@@ -25011,9 +25417,9 @@ class JvGridComponent {
25011
25417
  role="grid"
25012
25418
  [attr.aria-label]="resolvedOptions().ariaLabel || gridLabel()"
25013
25419
  [attr.aria-rowcount]="effectiveTotalItems()"
25014
- [attr.aria-colcount]="visibleColumns().length"
25420
+ [attr.aria-colcount]="colspan()"
25015
25421
  [class]="densityClass()"
25016
- [style.position]="resolvedOptions().virtualScroll ? 'relative' : null"
25422
+ [style.position]="virtualScrollEnabled() ? 'relative' : null"
25017
25423
  [style.top.px]="virtualOffsetY()"
25018
25424
  >
25019
25425
  <caption class="sr-only">{{ resolvedOptions().ariaLabel || gridLabel() }}</caption>
@@ -25081,12 +25487,16 @@ class JvGridComponent {
25081
25487
  [style.width]="getColumnWidth(col)"
25082
25488
  [style.min-width]="col.minWidth || undefined"
25083
25489
  [style.max-width]="col.maxWidth || undefined"
25084
- scope="col"
25085
- [attr.aria-sort]="getAriaSort(col)"
25086
- [attr.aria-label]="getSortLabel(col)"
25087
- [tabindex]="col.sortable !== false && resolvedOptions().sortable ? 0 : -1"
25088
- (click)="toggleSort(col)"
25089
- (keydown.enter)="toggleSort(col)"
25490
+ scope="col"
25491
+ role="columnheader"
25492
+ [attr.data-column-index]="colIdx"
25493
+ [attr.aria-sort]="getAriaSort(col)"
25494
+ [attr.aria-label]="getSortLabel(col)"
25495
+ [tabindex]="resolvedOptions().keyboardNavigation ? (activeCell().column === colIdx ? 0 : -1) : (col.sortable !== false && resolvedOptions().sortable ? 0 : -1)"
25496
+ (click)="toggleSort(col)"
25497
+ (focus)="onHeaderFocus(colIdx)"
25498
+ (keydown)="onHeaderKeydown($event, colIdx)"
25499
+ (keydown.enter)="toggleSort(col)"
25090
25500
  (keydown.space)="toggleSort(col, $event)"
25091
25501
  draggable="{{ resolvedOptions().reorderableColumns ? 'true' : 'false' }}"
25092
25502
  (dragstart)="onDragStart($event, colIdx)"
@@ -25187,9 +25597,10 @@ class JvGridComponent {
25187
25597
  />
25188
25598
  </td>
25189
25599
  }
25190
- @for (col of visibleColumns(); track colKey(col)) {
25600
+ @for (col of visibleColumns(); track colKey(col); let colIdx = $index) {
25191
25601
  <td
25192
25602
  class="jv-grid-td"
25603
+ [attr.role]="resolvedOptions().keyboardNavigation ? 'gridcell' : null"
25193
25604
  [class]="col.cellClass || ''"
25194
25605
  [class.jv-grid-td-sticky-left]="isStickyLeft(col)"
25195
25606
  [class.jv-grid-td-sticky-right]="isStickyRight(col)"
@@ -25197,30 +25608,38 @@ class JvGridComponent {
25197
25608
  [style.text-align]="col.align ?? 'start'"
25198
25609
  (dblclick)="startEdit(row, col, $event)"
25199
25610
  (keydown.enter)="startEdit(row, col, $event)"
25200
- [tabindex]="isCellEditable(col) ? -1 : null"
25611
+ [attr.data-row-index]="rowIdx"
25612
+ [attr.data-column-index]="colIdx"
25613
+ [class.jv-grid-keyboard-cell]="resolvedOptions().keyboardNavigation"
25614
+ [attr.tabindex]="cellTabIndex(rowIdx, colIdx, col)"
25615
+ (focus)="onCellFocus(rowIdx, colIdx)"
25616
+ (keydown.f2)="onCellF2($event, rowIdx, colIdx)"
25617
+ (keydown)="onCellKeydown($event, rowIdx, colIdx)"
25201
25618
  >
25202
25619
  @if (isEditingCell(row, col)) {
25203
- @if (col.editType === 'select' && col.editOptions) {
25620
+ @if (editType(col) === 'select' && col.editOptions) {
25204
25621
  <jv-select
25205
25622
  [options]="getEditOptions(col)"
25206
25623
  [modelValue]="getEditValue(row, col)"
25207
25624
  (selectionChange)="onEditValueChange(row, col, $event)"
25208
25625
  />
25209
- } @else if (col.editType === 'boolean') {
25626
+ } @else if (editType(col) === 'boolean') {
25210
25627
  <input
25211
25628
  type="checkbox"
25212
25629
  class="jv-grid-checkbox"
25213
- [checked]="getCellValue(row, col) === true"
25214
- (change)="commitEdit(row, col, $any($event.target).checked)"
25630
+ [checked]="getBooleanEditValue(row, col)"
25631
+ (change)="commitEdit(row, col, normalizeEditValue(col, $any($event.target).checked))"
25632
+ (keydown.enter)="commitEditFromKeyboard($event, row, col, normalizeEditValue(col, $any($event.target).checked))"
25633
+ (keydown.escape)="cancelEdit()"
25215
25634
  [attr.aria-label]="col.header"
25216
25635
  />
25217
25636
  } @else {
25218
25637
  <input
25219
25638
  class="jv-grid-edit-input"
25220
- [type]="col.editType === 'number' ? 'number' : 'text'"
25639
+ [type]="inputType(col)"
25221
25640
  [value]="getEditValue(row, col)"
25222
- (blur)="commitEdit(row, col, $any($event.target).value)"
25223
- (keydown.enter)="commitEdit(row, col, $any($event.target).value)"
25641
+ (blur)="commitEdit(row, col, normalizeEditValue(col, $any($event.target).value))"
25642
+ (keydown.enter)="commitEditFromKeyboard($event, row, col, normalizeEditValue(col, $any($event.target).value))"
25224
25643
  (keydown.escape)="cancelEdit()"
25225
25644
  autofocus
25226
25645
  />
@@ -25228,10 +25647,10 @@ class JvGridComponent {
25228
25647
  } @else {
25229
25648
  @let value = getCellValue(row, col);
25230
25649
  @if (col.type === 'boolean') {
25231
- @if (value) {
25232
- <span class="jv-grid-boolean jv-grid-boolean-true" aria-label="true">✓</span>
25650
+ @if (isBooleanTrue(value)) {
25651
+ <span class="jv-grid-boolean jv-grid-boolean-true" [attr.aria-label]="t('grid.booleanYes')">{{ t('grid.booleanYes') }}</span>
25233
25652
  } @else {
25234
- <span class="jv-grid-boolean jv-grid-boolean-false" aria-label="false">✗</span>
25653
+ <span class="jv-grid-boolean jv-grid-boolean-false" [attr.aria-label]="t('grid.booleanNo')">{{ t('grid.booleanNo') }}</span>
25235
25654
  }
25236
25655
  } @else if (col.format) {
25237
25656
  {{ col.format(value, row) }}
@@ -25268,13 +25687,21 @@ class JvGridComponent {
25268
25687
  </div>
25269
25688
  }
25270
25689
 
25271
- @if (resolvedOptions().pageable && effectiveTotalPages() > 1 && !resolvedOptions().loading) {
25690
+ @if (resolvedOptions().pageable && !virtualScrollEnabled() && effectiveTotalPages() > 1 && !resolvedOptions().loading) {
25272
25691
  <nav class="jv-grid-pagination" role="navigation" [attr.aria-label]="t('grid.paginationLabel', { label: '' })">
25273
25692
  <div class="jv-grid-pagination-info" aria-live="polite" aria-atomic="true">
25274
25693
  {{ t('grid.itemsShowing', { start: pageStart(), end: pageEnd(), total: effectiveTotalItems() }) }}
25275
25694
  </div>
25276
- <div class="jv-grid-pagination-controls">
25277
- <jv-button
25695
+ <div class="jv-grid-pagination-controls">
25696
+ @if (pageSizeOptions().length > 0) {
25697
+ <jv-select
25698
+ [options]="pageSizeOptions()"
25699
+ [modelValue]="pageSizeStr()"
25700
+ [ariaLabel]="t('grid.itemsPerPage')"
25701
+ (selectionChange)="onPageSizeChange($event)"
25702
+ />
25703
+ }
25704
+ <jv-button
25278
25705
  variant="outline"
25279
25706
  icon="chevrons-left"
25280
25707
  [disabled]="pageIndex() === 0"
@@ -25314,7 +25741,7 @@ class JvGridComponent {
25314
25741
  </nav>
25315
25742
  }
25316
25743
  </div>
25317
- `, isInline: true, styles: [".jv-grid{display:flex;flex-direction:column;gap:var(--jv-spacing-md)}.jv-grid-toolbar{display:flex;align-items:center;justify-content:space-between;gap:var(--jv-spacing-md);flex-wrap:wrap}.jv-grid-search{display:flex;align-items:center;gap:var(--jv-spacing-sm)}.jv-grid-export-actions{display:flex;align-items:center;gap:var(--jv-spacing-xs)}.jv-grid-table-wrap{overflow-x:auto;overflow-y:auto;-webkit-overflow-scrolling:touch;border:1px solid var(--jv-color-border);border-radius:var(--jv-radius-md);position:relative}.jv-grid-virtual{max-height:600px}.jv-grid-virtual-spacer{pointer-events:none}.jv-grid-table{width:100%;border-collapse:collapse;background:var(--jv-color-surface)}.jv-grid-thead{background:var(--jv-color-surface-muted)}.jv-grid-th{padding:var(--jv-spacing-sm) var(--jv-spacing-md);font-weight:600;font-size:.875rem;color:var(--jv-color-foreground-muted);text-align:start;white-space:nowrap;border-bottom:1px solid var(--jv-color-border);-webkit-user-select:none;user-select:none;position:relative}.jv-grid-th-sortable{cursor:pointer}.jv-grid-th-sortable:hover{background:var(--jv-color-surface)}.jv-grid-th-reorderable{cursor:grab}.jv-grid-th-reorderable:active{cursor:grabbing}.jv-grid-th-sticky-left{position:sticky;left:0;z-index:2;background:var(--jv-color-surface-muted);border-right:1px solid var(--jv-color-border)}.jv-grid-th-sticky-right{position:sticky;right:0;z-index:2;background:var(--jv-color-surface-muted);border-left:1px solid var(--jv-color-border)}.jv-grid-th-content{display:flex;align-items:center;gap:var(--jv-spacing-xs)}.jv-grid-sort-icon{font-size:.75rem;line-height:1}.jv-grid-th-select,.jv-grid-td-select{width:3rem;text-align:center}.jv-grid-th-actions,.jv-grid-td-actions{width:1%;white-space:nowrap}.jv-grid-td{padding:var(--jv-spacing-sm) var(--jv-spacing-md);color:var(--jv-color-foreground);font-size:var(--jv-density-font-size);border-bottom:1px solid var(--jv-color-border)}.jv-grid-td-sticky-left{position:sticky;left:0;z-index:1;background:var(--jv-color-surface);border-right:1px solid var(--jv-color-border)}.jv-grid-td-sticky-right{position:sticky;right:0;z-index:1;background:var(--jv-color-surface);border-left:1px solid var(--jv-color-border)}.jv-grid-t:focus-visible{outline:2px solid var(--jv-color-primary);outline-offset:-2px}.jv-grid-tr{transition:background-color .12s ease}.jv-grid-tr:hover{background:var(--jv-color-surface-muted)}.jv-grid-tr:focus-visible{outline:2px solid var(--jv-color-primary);outline-offset:-2px}.jv-grid-tr-editing{background:var(--jv-color-surface-highlight, rgba(59, 130, 246, .05))}.jv-grid-tr-selected{background:var(--jv-color-primary-light, rgba(59, 130, 246, .08))}.jv-grid-checkbox{width:1rem;height:1rem;accent-color:var(--jv-color-primary);cursor:pointer}.jv-grid-boolean{display:inline-flex;align-items:center;justify-content:center;width:1.5rem;height:1.5rem;border-radius:999px;font-size:.75rem;font-weight:700}.jv-grid-boolean-true{background:var(--jv-color-success);color:#fff}.jv-grid-boolean-false{background:var(--jv-color-danger);color:#fff}.jv-grid-empty{padding:var(--jv-spacing-xl) var(--jv-spacing-md);text-align:center;color:var(--jv-color-foreground-muted);font-style:italic}.jv-grid-actions-group{display:flex;align-items:center;gap:var(--jv-spacing-xs)}.jv-grid-loading{display:flex;align-items:center;justify-content:center;gap:var(--jv-spacing-md);padding:var(--jv-spacing-xl);color:var(--jv-color-foreground-muted)}.jv-grid-spinner{width:1.25rem;height:1.25rem;border:2px solid var(--jv-color-border);border-right-color:var(--jv-color-primary);border-radius:999px;animation:jv-grid-spin .8s linear infinite}@keyframes jv-grid-spin{to{transform:rotate(360deg)}}.jv-grid-pagination{display:flex;align-items:center;justify-content:space-between;gap:var(--jv-spacing-md);flex-wrap:wrap}.jv-grid-pagination-info{font-size:.875rem;color:var(--jv-color-foreground-muted)}.jv-grid-pagination-controls{display:flex;align-items:center;gap:var(--jv-spacing-xs);flex-wrap:wrap}@media(max-width:640px){.jv-grid-pagination{flex-direction:column;align-items:stretch;gap:var(--jv-spacing-sm)}.jv-grid-pagination-info{text-align:center}.jv-grid-pagination-controls{justify-content:center}.jv-grid-toolbar{flex-direction:column;align-items:stretch}.jv-grid-search{width:100%}.jv-grid-export-actions{justify-content:stretch}.jv-grid-export-actions jv-button{flex:1}}.jv-grid-resize-handle{position:absolute;top:0;right:0;width:4px;height:100%;cursor:col-resize;background:transparent;z-index:3}.jv-grid-resize-handle:hover,.jv-grid-resize-handle:active{background:var(--jv-color-primary)}.jv-grid-filter-row .jv-grid-th{padding:var(--jv-spacing-xs) var(--jv-spacing-sm)}.jv-grid-filter-input{width:100%;min-height:var(--jv-density-control-height);padding:0 var(--jv-spacing-sm);border:1px solid var(--jv-color-border);border-radius:var(--jv-radius-sm);background:var(--jv-color-surface);color:var(--jv-color-foreground);font-size:.8rem;box-sizing:border-box}.jv-grid-td-editable{cursor:pointer}.jv-grid-td-editable:hover{box-shadow:inset 0 0 0 1px var(--jv-color-primary)}.jv-grid-edit-input{width:100%;min-height:var(--jv-density-control-height);padding:0 var(--jv-spacing-sm);border:1px solid var(--jv-color-primary);border-radius:var(--jv-radius-sm);background:var(--jv-color-surface);color:var(--jv-color-foreground);font-size:var(--jv-density-font-size);box-sizing:border-box}.jv-grid-edit-input:focus{outline:2px solid var(--jv-color-primary);outline-offset:-1px}\n"], dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "component", type: JvButtonComponent, selector: "jv-button", inputs: ["type", "variant", "icon", "iconPosition", "loading", "disabled"] }, { kind: "component", type: JvIconComponent, selector: "jv-icon", inputs: ["name", "size", "strokeWidth", "decorative", "ariaLabel"] }, { kind: "component", type: JvInputComponent, selector: "jv-input", inputs: ["type", "placeholder", "required", "invalid", "describedBy", "inputId"] }, { kind: "component", type: JvSelectComponent, selector: "jv-select", inputs: ["options", "placeholder", "required", "invalid", "describedBy", "inputId", "modelValue"], outputs: ["selectionChange"] }] });
25744
+ `, isInline: true, styles: [".jv-grid{display:flex;flex-direction:column;gap:var(--jv-spacing-md)}.jv-grid-toolbar{display:flex;align-items:center;justify-content:space-between;gap:var(--jv-spacing-md);flex-wrap:wrap}.jv-grid-search{display:flex;align-items:center;gap:var(--jv-spacing-sm)}.jv-grid-export-actions{display:flex;align-items:center;gap:var(--jv-spacing-xs)}.jv-grid-table-wrap{overflow-x:auto;overflow-y:auto;-webkit-overflow-scrolling:touch;border:1px solid var(--jv-color-border);border-radius:var(--jv-radius-md);position:relative}.jv-grid-virtual{max-height:600px}.jv-grid-virtual-spacer{pointer-events:none}.jv-grid-table{width:100%;border-collapse:collapse;background:var(--jv-color-surface)}.jv-grid-thead{background:var(--jv-color-surface-muted)}.jv-grid-th{padding:var(--jv-spacing-sm) var(--jv-spacing-md);font-weight:600;font-size:.875rem;color:var(--jv-color-foreground-muted);text-align:start;white-space:nowrap;border-bottom:1px solid var(--jv-color-border);-webkit-user-select:none;user-select:none;position:relative}.jv-grid-th-sortable{cursor:pointer}.jv-grid-th-sortable:hover{background:var(--jv-color-surface)}.jv-grid-th-reorderable{cursor:grab}.jv-grid-th-reorderable:active{cursor:grabbing}.jv-grid-th-sticky-left{position:sticky;left:0;z-index:2;background:var(--jv-color-surface-muted);border-right:1px solid var(--jv-color-border)}.jv-grid-th-sticky-right{position:sticky;right:0;z-index:2;background:var(--jv-color-surface-muted);border-left:1px solid var(--jv-color-border)}.jv-grid-th-content{display:flex;align-items:center;gap:var(--jv-spacing-xs)}.jv-grid-sort-icon{font-size:.75rem;line-height:1}.jv-grid-th-select,.jv-grid-td-select{width:3rem;text-align:center}.jv-grid-th-actions,.jv-grid-td-actions{width:1%;white-space:nowrap}.jv-grid-td{padding:var(--jv-spacing-sm) var(--jv-spacing-md);color:var(--jv-color-foreground);font-size:var(--jv-density-font-size);border-bottom:1px solid var(--jv-color-border)}.jv-grid-keyboard-cell:focus-visible{outline:2px solid var(--jv-color-primary);outline-offset:-2px}.jv-grid-td-sticky-left{position:sticky;left:0;z-index:1;background:var(--jv-color-surface);border-right:1px solid var(--jv-color-border)}.jv-grid-td-sticky-right{position:sticky;right:0;z-index:1;background:var(--jv-color-surface);border-left:1px solid var(--jv-color-border)}.jv-grid-t:focus-visible{outline:2px solid var(--jv-color-primary);outline-offset:-2px}.jv-grid-tr{transition:background-color .12s ease}.jv-grid-tr:hover{background:var(--jv-color-surface-muted)}.jv-grid-tr:focus-visible{outline:2px solid var(--jv-color-primary);outline-offset:-2px}.jv-grid-tr-editing{background:var(--jv-color-surface-highlight, rgba(59, 130, 246, .05))}.jv-grid-tr-selected{background:var(--jv-color-primary-light, rgba(59, 130, 246, .08))}.jv-grid-checkbox{width:1rem;height:1rem;accent-color:var(--jv-color-primary);cursor:pointer}.jv-grid-boolean{display:inline-flex;align-items:center;justify-content:center;width:1.5rem;height:1.5rem;border-radius:999px;font-size:.75rem;font-weight:700}.jv-grid-boolean-true{background:var(--jv-color-success);color:#fff}.jv-grid-boolean-false{background:var(--jv-color-danger);color:#fff}.jv-grid-empty{padding:var(--jv-spacing-xl) var(--jv-spacing-md);text-align:center;color:var(--jv-color-foreground-muted);font-style:italic}.jv-grid-actions-group{display:flex;align-items:center;gap:var(--jv-spacing-xs)}.jv-grid-loading{display:flex;align-items:center;justify-content:center;gap:var(--jv-spacing-md);padding:var(--jv-spacing-xl);color:var(--jv-color-foreground-muted)}.jv-grid-spinner{width:1.25rem;height:1.25rem;border:2px solid var(--jv-color-border);border-right-color:var(--jv-color-primary);border-radius:999px;animation:jv-grid-spin .8s linear infinite}@keyframes jv-grid-spin{to{transform:rotate(360deg)}}.jv-grid-pagination{display:flex;align-items:center;justify-content:space-between;gap:var(--jv-spacing-md);flex-wrap:wrap}.jv-grid-pagination-info{font-size:.875rem;color:var(--jv-color-foreground-muted)}.jv-grid-pagination-controls{display:flex;align-items:center;gap:var(--jv-spacing-xs);flex-wrap:wrap}@media(max-width:640px){.jv-grid-pagination{flex-direction:column;align-items:stretch;gap:var(--jv-spacing-sm)}.jv-grid-pagination-info{text-align:center}.jv-grid-pagination-controls{justify-content:center}.jv-grid-toolbar{flex-direction:column;align-items:stretch}.jv-grid-search{width:100%}.jv-grid-export-actions{justify-content:stretch}.jv-grid-export-actions jv-button{flex:1}}.jv-grid-resize-handle{position:absolute;top:0;right:0;width:4px;height:100%;cursor:col-resize;background:transparent;z-index:3}.jv-grid-resize-handle:hover,.jv-grid-resize-handle:active{background:var(--jv-color-primary)}.jv-grid-filter-row .jv-grid-th{padding:var(--jv-spacing-xs) var(--jv-spacing-sm)}.jv-grid-filter-input{width:100%;min-height:var(--jv-density-control-height);padding:0 var(--jv-spacing-sm);border:1px solid var(--jv-color-border);border-radius:var(--jv-radius-sm);background:var(--jv-color-surface);color:var(--jv-color-foreground);font-size:.8rem;box-sizing:border-box}.jv-grid-td-editable{cursor:pointer}.jv-grid-td-editable:hover{box-shadow:inset 0 0 0 1px var(--jv-color-primary)}.jv-grid-edit-input{width:100%;min-height:var(--jv-density-control-height);padding:0 var(--jv-spacing-sm);border:1px solid var(--jv-color-primary);border-radius:var(--jv-radius-sm);background:var(--jv-color-surface);color:var(--jv-color-foreground);font-size:var(--jv-density-font-size);box-sizing:border-box}.jv-grid-edit-input:focus{outline:2px solid var(--jv-color-primary);outline-offset:-1px}\n"], dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "component", type: JvButtonComponent, selector: "jv-button", inputs: ["type", "variant", "icon", "iconPosition", "loading", "disabled"] }, { kind: "component", type: JvIconComponent, selector: "jv-icon", inputs: ["name", "size", "strokeWidth", "decorative", "ariaLabel"] }, { kind: "component", type: JvInputComponent, selector: "jv-input", inputs: ["type", "placeholder", "required", "invalid", "describedBy", "inputId"] }, { kind: "component", type: JvSelectComponent, selector: "jv-select", inputs: ["options", "placeholder", "required", "invalid", "describedBy", "ariaLabel", "inputId", "modelValue"], outputs: ["selectionChange"] }] });
25318
25745
  }
25319
25746
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: JvGridComponent, decorators: [{
25320
25747
  type: Component,
@@ -25326,7 +25753,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImpo
25326
25753
  <div class="jv-grid-search">
25327
25754
  <jv-icon name="search" [size]="16" />
25328
25755
  <jv-input
25329
- inputId="grid-search"
25756
+ [inputId]="gridSearchInputId"
25330
25757
  [placeholder]="t('grid.searchPlaceholder')"
25331
25758
  [ngModel]="searchTerm()"
25332
25759
  (ngModelChange)="onSearchInput($event)"
@@ -25350,11 +25777,11 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImpo
25350
25777
  } @else {
25351
25778
  <div
25352
25779
  class="jv-grid-table-wrap"
25353
- [class.jv-grid-virtual]="resolvedOptions().virtualScroll"
25780
+ [class.jv-grid-virtual]="virtualScrollEnabled()"
25354
25781
  #tableWrap
25355
25782
  (scroll)="onVirtualScroll($any($event.target))"
25356
25783
  >
25357
- @if (resolvedOptions().virtualScroll) {
25784
+ @if (virtualScrollEnabled()) {
25358
25785
  <div class="jv-grid-virtual-spacer" [style.height.px]="virtualTotalHeight()"></div>
25359
25786
  }
25360
25787
  <table
@@ -25362,9 +25789,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImpo
25362
25789
  role="grid"
25363
25790
  [attr.aria-label]="resolvedOptions().ariaLabel || gridLabel()"
25364
25791
  [attr.aria-rowcount]="effectiveTotalItems()"
25365
- [attr.aria-colcount]="visibleColumns().length"
25792
+ [attr.aria-colcount]="colspan()"
25366
25793
  [class]="densityClass()"
25367
- [style.position]="resolvedOptions().virtualScroll ? 'relative' : null"
25794
+ [style.position]="virtualScrollEnabled() ? 'relative' : null"
25368
25795
  [style.top.px]="virtualOffsetY()"
25369
25796
  >
25370
25797
  <caption class="sr-only">{{ resolvedOptions().ariaLabel || gridLabel() }}</caption>
@@ -25432,12 +25859,16 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImpo
25432
25859
  [style.width]="getColumnWidth(col)"
25433
25860
  [style.min-width]="col.minWidth || undefined"
25434
25861
  [style.max-width]="col.maxWidth || undefined"
25435
- scope="col"
25436
- [attr.aria-sort]="getAriaSort(col)"
25437
- [attr.aria-label]="getSortLabel(col)"
25438
- [tabindex]="col.sortable !== false && resolvedOptions().sortable ? 0 : -1"
25439
- (click)="toggleSort(col)"
25440
- (keydown.enter)="toggleSort(col)"
25862
+ scope="col"
25863
+ role="columnheader"
25864
+ [attr.data-column-index]="colIdx"
25865
+ [attr.aria-sort]="getAriaSort(col)"
25866
+ [attr.aria-label]="getSortLabel(col)"
25867
+ [tabindex]="resolvedOptions().keyboardNavigation ? (activeCell().column === colIdx ? 0 : -1) : (col.sortable !== false && resolvedOptions().sortable ? 0 : -1)"
25868
+ (click)="toggleSort(col)"
25869
+ (focus)="onHeaderFocus(colIdx)"
25870
+ (keydown)="onHeaderKeydown($event, colIdx)"
25871
+ (keydown.enter)="toggleSort(col)"
25441
25872
  (keydown.space)="toggleSort(col, $event)"
25442
25873
  draggable="{{ resolvedOptions().reorderableColumns ? 'true' : 'false' }}"
25443
25874
  (dragstart)="onDragStart($event, colIdx)"
@@ -25538,9 +25969,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImpo
25538
25969
  />
25539
25970
  </td>
25540
25971
  }
25541
- @for (col of visibleColumns(); track colKey(col)) {
25972
+ @for (col of visibleColumns(); track colKey(col); let colIdx = $index) {
25542
25973
  <td
25543
25974
  class="jv-grid-td"
25975
+ [attr.role]="resolvedOptions().keyboardNavigation ? 'gridcell' : null"
25544
25976
  [class]="col.cellClass || ''"
25545
25977
  [class.jv-grid-td-sticky-left]="isStickyLeft(col)"
25546
25978
  [class.jv-grid-td-sticky-right]="isStickyRight(col)"
@@ -25548,30 +25980,38 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImpo
25548
25980
  [style.text-align]="col.align ?? 'start'"
25549
25981
  (dblclick)="startEdit(row, col, $event)"
25550
25982
  (keydown.enter)="startEdit(row, col, $event)"
25551
- [tabindex]="isCellEditable(col) ? -1 : null"
25983
+ [attr.data-row-index]="rowIdx"
25984
+ [attr.data-column-index]="colIdx"
25985
+ [class.jv-grid-keyboard-cell]="resolvedOptions().keyboardNavigation"
25986
+ [attr.tabindex]="cellTabIndex(rowIdx, colIdx, col)"
25987
+ (focus)="onCellFocus(rowIdx, colIdx)"
25988
+ (keydown.f2)="onCellF2($event, rowIdx, colIdx)"
25989
+ (keydown)="onCellKeydown($event, rowIdx, colIdx)"
25552
25990
  >
25553
25991
  @if (isEditingCell(row, col)) {
25554
- @if (col.editType === 'select' && col.editOptions) {
25992
+ @if (editType(col) === 'select' && col.editOptions) {
25555
25993
  <jv-select
25556
25994
  [options]="getEditOptions(col)"
25557
25995
  [modelValue]="getEditValue(row, col)"
25558
25996
  (selectionChange)="onEditValueChange(row, col, $event)"
25559
25997
  />
25560
- } @else if (col.editType === 'boolean') {
25998
+ } @else if (editType(col) === 'boolean') {
25561
25999
  <input
25562
26000
  type="checkbox"
25563
26001
  class="jv-grid-checkbox"
25564
- [checked]="getCellValue(row, col) === true"
25565
- (change)="commitEdit(row, col, $any($event.target).checked)"
26002
+ [checked]="getBooleanEditValue(row, col)"
26003
+ (change)="commitEdit(row, col, normalizeEditValue(col, $any($event.target).checked))"
26004
+ (keydown.enter)="commitEditFromKeyboard($event, row, col, normalizeEditValue(col, $any($event.target).checked))"
26005
+ (keydown.escape)="cancelEdit()"
25566
26006
  [attr.aria-label]="col.header"
25567
26007
  />
25568
26008
  } @else {
25569
26009
  <input
25570
26010
  class="jv-grid-edit-input"
25571
- [type]="col.editType === 'number' ? 'number' : 'text'"
26011
+ [type]="inputType(col)"
25572
26012
  [value]="getEditValue(row, col)"
25573
- (blur)="commitEdit(row, col, $any($event.target).value)"
25574
- (keydown.enter)="commitEdit(row, col, $any($event.target).value)"
26013
+ (blur)="commitEdit(row, col, normalizeEditValue(col, $any($event.target).value))"
26014
+ (keydown.enter)="commitEditFromKeyboard($event, row, col, normalizeEditValue(col, $any($event.target).value))"
25575
26015
  (keydown.escape)="cancelEdit()"
25576
26016
  autofocus
25577
26017
  />
@@ -25579,10 +26019,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImpo
25579
26019
  } @else {
25580
26020
  @let value = getCellValue(row, col);
25581
26021
  @if (col.type === 'boolean') {
25582
- @if (value) {
25583
- <span class="jv-grid-boolean jv-grid-boolean-true" aria-label="true">✓</span>
26022
+ @if (isBooleanTrue(value)) {
26023
+ <span class="jv-grid-boolean jv-grid-boolean-true" [attr.aria-label]="t('grid.booleanYes')">{{ t('grid.booleanYes') }}</span>
25584
26024
  } @else {
25585
- <span class="jv-grid-boolean jv-grid-boolean-false" aria-label="false">✗</span>
26025
+ <span class="jv-grid-boolean jv-grid-boolean-false" [attr.aria-label]="t('grid.booleanNo')">{{ t('grid.booleanNo') }}</span>
25586
26026
  }
25587
26027
  } @else if (col.format) {
25588
26028
  {{ col.format(value, row) }}
@@ -25619,13 +26059,21 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImpo
25619
26059
  </div>
25620
26060
  }
25621
26061
 
25622
- @if (resolvedOptions().pageable && effectiveTotalPages() > 1 && !resolvedOptions().loading) {
26062
+ @if (resolvedOptions().pageable && !virtualScrollEnabled() && effectiveTotalPages() > 1 && !resolvedOptions().loading) {
25623
26063
  <nav class="jv-grid-pagination" role="navigation" [attr.aria-label]="t('grid.paginationLabel', { label: '' })">
25624
26064
  <div class="jv-grid-pagination-info" aria-live="polite" aria-atomic="true">
25625
26065
  {{ t('grid.itemsShowing', { start: pageStart(), end: pageEnd(), total: effectiveTotalItems() }) }}
25626
26066
  </div>
25627
- <div class="jv-grid-pagination-controls">
25628
- <jv-button
26067
+ <div class="jv-grid-pagination-controls">
26068
+ @if (pageSizeOptions().length > 0) {
26069
+ <jv-select
26070
+ [options]="pageSizeOptions()"
26071
+ [modelValue]="pageSizeStr()"
26072
+ [ariaLabel]="t('grid.itemsPerPage')"
26073
+ (selectionChange)="onPageSizeChange($event)"
26074
+ />
26075
+ }
26076
+ <jv-button
25629
26077
  variant="outline"
25630
26078
  icon="chevrons-left"
25631
26079
  [disabled]="pageIndex() === 0"
@@ -25665,8 +26113,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImpo
25665
26113
  </nav>
25666
26114
  }
25667
26115
  </div>
25668
- `, styles: [".jv-grid{display:flex;flex-direction:column;gap:var(--jv-spacing-md)}.jv-grid-toolbar{display:flex;align-items:center;justify-content:space-between;gap:var(--jv-spacing-md);flex-wrap:wrap}.jv-grid-search{display:flex;align-items:center;gap:var(--jv-spacing-sm)}.jv-grid-export-actions{display:flex;align-items:center;gap:var(--jv-spacing-xs)}.jv-grid-table-wrap{overflow-x:auto;overflow-y:auto;-webkit-overflow-scrolling:touch;border:1px solid var(--jv-color-border);border-radius:var(--jv-radius-md);position:relative}.jv-grid-virtual{max-height:600px}.jv-grid-virtual-spacer{pointer-events:none}.jv-grid-table{width:100%;border-collapse:collapse;background:var(--jv-color-surface)}.jv-grid-thead{background:var(--jv-color-surface-muted)}.jv-grid-th{padding:var(--jv-spacing-sm) var(--jv-spacing-md);font-weight:600;font-size:.875rem;color:var(--jv-color-foreground-muted);text-align:start;white-space:nowrap;border-bottom:1px solid var(--jv-color-border);-webkit-user-select:none;user-select:none;position:relative}.jv-grid-th-sortable{cursor:pointer}.jv-grid-th-sortable:hover{background:var(--jv-color-surface)}.jv-grid-th-reorderable{cursor:grab}.jv-grid-th-reorderable:active{cursor:grabbing}.jv-grid-th-sticky-left{position:sticky;left:0;z-index:2;background:var(--jv-color-surface-muted);border-right:1px solid var(--jv-color-border)}.jv-grid-th-sticky-right{position:sticky;right:0;z-index:2;background:var(--jv-color-surface-muted);border-left:1px solid var(--jv-color-border)}.jv-grid-th-content{display:flex;align-items:center;gap:var(--jv-spacing-xs)}.jv-grid-sort-icon{font-size:.75rem;line-height:1}.jv-grid-th-select,.jv-grid-td-select{width:3rem;text-align:center}.jv-grid-th-actions,.jv-grid-td-actions{width:1%;white-space:nowrap}.jv-grid-td{padding:var(--jv-spacing-sm) var(--jv-spacing-md);color:var(--jv-color-foreground);font-size:var(--jv-density-font-size);border-bottom:1px solid var(--jv-color-border)}.jv-grid-td-sticky-left{position:sticky;left:0;z-index:1;background:var(--jv-color-surface);border-right:1px solid var(--jv-color-border)}.jv-grid-td-sticky-right{position:sticky;right:0;z-index:1;background:var(--jv-color-surface);border-left:1px solid var(--jv-color-border)}.jv-grid-t:focus-visible{outline:2px solid var(--jv-color-primary);outline-offset:-2px}.jv-grid-tr{transition:background-color .12s ease}.jv-grid-tr:hover{background:var(--jv-color-surface-muted)}.jv-grid-tr:focus-visible{outline:2px solid var(--jv-color-primary);outline-offset:-2px}.jv-grid-tr-editing{background:var(--jv-color-surface-highlight, rgba(59, 130, 246, .05))}.jv-grid-tr-selected{background:var(--jv-color-primary-light, rgba(59, 130, 246, .08))}.jv-grid-checkbox{width:1rem;height:1rem;accent-color:var(--jv-color-primary);cursor:pointer}.jv-grid-boolean{display:inline-flex;align-items:center;justify-content:center;width:1.5rem;height:1.5rem;border-radius:999px;font-size:.75rem;font-weight:700}.jv-grid-boolean-true{background:var(--jv-color-success);color:#fff}.jv-grid-boolean-false{background:var(--jv-color-danger);color:#fff}.jv-grid-empty{padding:var(--jv-spacing-xl) var(--jv-spacing-md);text-align:center;color:var(--jv-color-foreground-muted);font-style:italic}.jv-grid-actions-group{display:flex;align-items:center;gap:var(--jv-spacing-xs)}.jv-grid-loading{display:flex;align-items:center;justify-content:center;gap:var(--jv-spacing-md);padding:var(--jv-spacing-xl);color:var(--jv-color-foreground-muted)}.jv-grid-spinner{width:1.25rem;height:1.25rem;border:2px solid var(--jv-color-border);border-right-color:var(--jv-color-primary);border-radius:999px;animation:jv-grid-spin .8s linear infinite}@keyframes jv-grid-spin{to{transform:rotate(360deg)}}.jv-grid-pagination{display:flex;align-items:center;justify-content:space-between;gap:var(--jv-spacing-md);flex-wrap:wrap}.jv-grid-pagination-info{font-size:.875rem;color:var(--jv-color-foreground-muted)}.jv-grid-pagination-controls{display:flex;align-items:center;gap:var(--jv-spacing-xs);flex-wrap:wrap}@media(max-width:640px){.jv-grid-pagination{flex-direction:column;align-items:stretch;gap:var(--jv-spacing-sm)}.jv-grid-pagination-info{text-align:center}.jv-grid-pagination-controls{justify-content:center}.jv-grid-toolbar{flex-direction:column;align-items:stretch}.jv-grid-search{width:100%}.jv-grid-export-actions{justify-content:stretch}.jv-grid-export-actions jv-button{flex:1}}.jv-grid-resize-handle{position:absolute;top:0;right:0;width:4px;height:100%;cursor:col-resize;background:transparent;z-index:3}.jv-grid-resize-handle:hover,.jv-grid-resize-handle:active{background:var(--jv-color-primary)}.jv-grid-filter-row .jv-grid-th{padding:var(--jv-spacing-xs) var(--jv-spacing-sm)}.jv-grid-filter-input{width:100%;min-height:var(--jv-density-control-height);padding:0 var(--jv-spacing-sm);border:1px solid var(--jv-color-border);border-radius:var(--jv-radius-sm);background:var(--jv-color-surface);color:var(--jv-color-foreground);font-size:.8rem;box-sizing:border-box}.jv-grid-td-editable{cursor:pointer}.jv-grid-td-editable:hover{box-shadow:inset 0 0 0 1px var(--jv-color-primary)}.jv-grid-edit-input{width:100%;min-height:var(--jv-density-control-height);padding:0 var(--jv-spacing-sm);border:1px solid var(--jv-color-primary);border-radius:var(--jv-radius-sm);background:var(--jv-color-surface);color:var(--jv-color-foreground);font-size:var(--jv-density-font-size);box-sizing:border-box}.jv-grid-edit-input:focus{outline:2px solid var(--jv-color-primary);outline-offset:-1px}\n"] }]
25669
- }], propDecorators: { data: [{ type: i0.Input, args: [{ isSignal: true, alias: "data", required: false }] }], columns: [{ type: i0.Input, args: [{ isSignal: true, alias: "columns", required: false }] }], actions: [{ type: i0.Input, args: [{ isSignal: true, alias: "actions", required: false }] }], options: [{ type: i0.Input, args: [{ isSignal: true, alias: "options", required: false }] }], trackBy: [{ type: i0.Input, args: [{ isSignal: true, alias: "trackBy", required: false }] }], selectedIds: [{ type: i0.Input, args: [{ isSignal: true, alias: "selectedIds", required: false }] }], rowClick: [{ type: i0.Output, args: ["rowClick"] }], rowDoubleClick: [{ type: i0.Output, args: ["rowDoubleClick"] }], actionClick: [{ type: i0.Output, args: ["actionClick"] }], selectionChange: [{ type: i0.Output, args: ["selectionChange"] }], pageChange: [{ type: i0.Output, args: ["pageChange"] }], searchChange: [{ type: i0.Output, args: ["searchChange"] }], sortChange: [{ type: i0.Output, args: ["sortChange"] }], columnFilter: [{ type: i0.Output, args: ["columnFilter"] }], columnResize: [{ type: i0.Output, args: ["columnResize"] }], columnReorder: [{ type: i0.Output, args: ["columnReorder"] }], rowEdit: [{ type: i0.Output, args: ["rowEdit"] }] } });
26116
+ `, styles: [".jv-grid{display:flex;flex-direction:column;gap:var(--jv-spacing-md)}.jv-grid-toolbar{display:flex;align-items:center;justify-content:space-between;gap:var(--jv-spacing-md);flex-wrap:wrap}.jv-grid-search{display:flex;align-items:center;gap:var(--jv-spacing-sm)}.jv-grid-export-actions{display:flex;align-items:center;gap:var(--jv-spacing-xs)}.jv-grid-table-wrap{overflow-x:auto;overflow-y:auto;-webkit-overflow-scrolling:touch;border:1px solid var(--jv-color-border);border-radius:var(--jv-radius-md);position:relative}.jv-grid-virtual{max-height:600px}.jv-grid-virtual-spacer{pointer-events:none}.jv-grid-table{width:100%;border-collapse:collapse;background:var(--jv-color-surface)}.jv-grid-thead{background:var(--jv-color-surface-muted)}.jv-grid-th{padding:var(--jv-spacing-sm) var(--jv-spacing-md);font-weight:600;font-size:.875rem;color:var(--jv-color-foreground-muted);text-align:start;white-space:nowrap;border-bottom:1px solid var(--jv-color-border);-webkit-user-select:none;user-select:none;position:relative}.jv-grid-th-sortable{cursor:pointer}.jv-grid-th-sortable:hover{background:var(--jv-color-surface)}.jv-grid-th-reorderable{cursor:grab}.jv-grid-th-reorderable:active{cursor:grabbing}.jv-grid-th-sticky-left{position:sticky;left:0;z-index:2;background:var(--jv-color-surface-muted);border-right:1px solid var(--jv-color-border)}.jv-grid-th-sticky-right{position:sticky;right:0;z-index:2;background:var(--jv-color-surface-muted);border-left:1px solid var(--jv-color-border)}.jv-grid-th-content{display:flex;align-items:center;gap:var(--jv-spacing-xs)}.jv-grid-sort-icon{font-size:.75rem;line-height:1}.jv-grid-th-select,.jv-grid-td-select{width:3rem;text-align:center}.jv-grid-th-actions,.jv-grid-td-actions{width:1%;white-space:nowrap}.jv-grid-td{padding:var(--jv-spacing-sm) var(--jv-spacing-md);color:var(--jv-color-foreground);font-size:var(--jv-density-font-size);border-bottom:1px solid var(--jv-color-border)}.jv-grid-keyboard-cell:focus-visible{outline:2px solid var(--jv-color-primary);outline-offset:-2px}.jv-grid-td-sticky-left{position:sticky;left:0;z-index:1;background:var(--jv-color-surface);border-right:1px solid var(--jv-color-border)}.jv-grid-td-sticky-right{position:sticky;right:0;z-index:1;background:var(--jv-color-surface);border-left:1px solid var(--jv-color-border)}.jv-grid-t:focus-visible{outline:2px solid var(--jv-color-primary);outline-offset:-2px}.jv-grid-tr{transition:background-color .12s ease}.jv-grid-tr:hover{background:var(--jv-color-surface-muted)}.jv-grid-tr:focus-visible{outline:2px solid var(--jv-color-primary);outline-offset:-2px}.jv-grid-tr-editing{background:var(--jv-color-surface-highlight, rgba(59, 130, 246, .05))}.jv-grid-tr-selected{background:var(--jv-color-primary-light, rgba(59, 130, 246, .08))}.jv-grid-checkbox{width:1rem;height:1rem;accent-color:var(--jv-color-primary);cursor:pointer}.jv-grid-boolean{display:inline-flex;align-items:center;justify-content:center;width:1.5rem;height:1.5rem;border-radius:999px;font-size:.75rem;font-weight:700}.jv-grid-boolean-true{background:var(--jv-color-success);color:#fff}.jv-grid-boolean-false{background:var(--jv-color-danger);color:#fff}.jv-grid-empty{padding:var(--jv-spacing-xl) var(--jv-spacing-md);text-align:center;color:var(--jv-color-foreground-muted);font-style:italic}.jv-grid-actions-group{display:flex;align-items:center;gap:var(--jv-spacing-xs)}.jv-grid-loading{display:flex;align-items:center;justify-content:center;gap:var(--jv-spacing-md);padding:var(--jv-spacing-xl);color:var(--jv-color-foreground-muted)}.jv-grid-spinner{width:1.25rem;height:1.25rem;border:2px solid var(--jv-color-border);border-right-color:var(--jv-color-primary);border-radius:999px;animation:jv-grid-spin .8s linear infinite}@keyframes jv-grid-spin{to{transform:rotate(360deg)}}.jv-grid-pagination{display:flex;align-items:center;justify-content:space-between;gap:var(--jv-spacing-md);flex-wrap:wrap}.jv-grid-pagination-info{font-size:.875rem;color:var(--jv-color-foreground-muted)}.jv-grid-pagination-controls{display:flex;align-items:center;gap:var(--jv-spacing-xs);flex-wrap:wrap}@media(max-width:640px){.jv-grid-pagination{flex-direction:column;align-items:stretch;gap:var(--jv-spacing-sm)}.jv-grid-pagination-info{text-align:center}.jv-grid-pagination-controls{justify-content:center}.jv-grid-toolbar{flex-direction:column;align-items:stretch}.jv-grid-search{width:100%}.jv-grid-export-actions{justify-content:stretch}.jv-grid-export-actions jv-button{flex:1}}.jv-grid-resize-handle{position:absolute;top:0;right:0;width:4px;height:100%;cursor:col-resize;background:transparent;z-index:3}.jv-grid-resize-handle:hover,.jv-grid-resize-handle:active{background:var(--jv-color-primary)}.jv-grid-filter-row .jv-grid-th{padding:var(--jv-spacing-xs) var(--jv-spacing-sm)}.jv-grid-filter-input{width:100%;min-height:var(--jv-density-control-height);padding:0 var(--jv-spacing-sm);border:1px solid var(--jv-color-border);border-radius:var(--jv-radius-sm);background:var(--jv-color-surface);color:var(--jv-color-foreground);font-size:.8rem;box-sizing:border-box}.jv-grid-td-editable{cursor:pointer}.jv-grid-td-editable:hover{box-shadow:inset 0 0 0 1px var(--jv-color-primary)}.jv-grid-edit-input{width:100%;min-height:var(--jv-density-control-height);padding:0 var(--jv-spacing-sm);border:1px solid var(--jv-color-primary);border-radius:var(--jv-radius-sm);background:var(--jv-color-surface);color:var(--jv-color-foreground);font-size:var(--jv-density-font-size);box-sizing:border-box}.jv-grid-edit-input:focus{outline:2px solid var(--jv-color-primary);outline-offset:-1px}\n"] }]
26117
+ }], ctorParameters: () => [], propDecorators: { data: [{ type: i0.Input, args: [{ isSignal: true, alias: "data", required: false }] }], columns: [{ type: i0.Input, args: [{ isSignal: true, alias: "columns", required: false }] }], actions: [{ type: i0.Input, args: [{ isSignal: true, alias: "actions", required: false }] }], options: [{ type: i0.Input, args: [{ isSignal: true, alias: "options", required: false }] }], trackBy: [{ type: i0.Input, args: [{ isSignal: true, alias: "trackBy", required: false }] }], selectedIds: [{ type: i0.Input, args: [{ isSignal: true, alias: "selectedIds", required: false }] }], rowClick: [{ type: i0.Output, args: ["rowClick"] }], rowDoubleClick: [{ type: i0.Output, args: ["rowDoubleClick"] }], actionClick: [{ type: i0.Output, args: ["actionClick"] }], selectionChange: [{ type: i0.Output, args: ["selectionChange"] }], pageChange: [{ type: i0.Output, args: ["pageChange"] }], searchChange: [{ type: i0.Output, args: ["searchChange"] }], sortChange: [{ type: i0.Output, args: ["sortChange"] }], columnFilter: [{ type: i0.Output, args: ["columnFilter"] }], columnResize: [{ type: i0.Output, args: ["columnResize"] }], columnReorder: [{ type: i0.Output, args: ["columnReorder"] }], rowEdit: [{ type: i0.Output, args: ["rowEdit"] }] } });
25670
26118
 
25671
26119
  class JvPaginationComponent {
25672
26120
  translationService = inject(JvTranslationService);
@@ -25765,7 +26213,7 @@ class JvPaginationComponent {
25765
26213
  </div>
25766
26214
  </div>
25767
26215
  </nav>
25768
- `, isInline: true, styles: [".jv-pagination{display:flex;align-items:center;justify-content:space-between;gap:var(--jv-spacing-md);flex-wrap:wrap}.jv-pagination-info{font-size:.875rem;color:var(--jv-color-foreground-muted)}.jv-pagination-controls{display:flex;align-items:center;gap:var(--jv-spacing-md);flex-wrap:wrap}.jv-pagination-size{display:flex;align-items:center;gap:var(--jv-spacing-sm)}.jv-pagination-size-label{font-size:.875rem;color:var(--jv-color-foreground-muted);white-space:nowrap}.jv-pagination-size jv-select{width:auto;min-width:4.5rem}.jv-pagination-buttons{display:flex;align-items:center;gap:var(--jv-spacing-xs)}\n"], dependencies: [{ kind: "component", type: JvButtonComponent, selector: "jv-button", inputs: ["type", "variant", "icon", "iconPosition", "loading", "disabled"] }, { kind: "component", type: JvSelectComponent, selector: "jv-select", inputs: ["options", "placeholder", "required", "invalid", "describedBy", "inputId", "modelValue"], outputs: ["selectionChange"] }] });
26216
+ `, isInline: true, styles: [".jv-pagination{display:flex;align-items:center;justify-content:space-between;gap:var(--jv-spacing-md);flex-wrap:wrap}.jv-pagination-info{font-size:.875rem;color:var(--jv-color-foreground-muted)}.jv-pagination-controls{display:flex;align-items:center;gap:var(--jv-spacing-md);flex-wrap:wrap}.jv-pagination-size{display:flex;align-items:center;gap:var(--jv-spacing-sm)}.jv-pagination-size-label{font-size:.875rem;color:var(--jv-color-foreground-muted);white-space:nowrap}.jv-pagination-size jv-select{width:auto;min-width:4.5rem}.jv-pagination-buttons{display:flex;align-items:center;gap:var(--jv-spacing-xs)}\n"], dependencies: [{ kind: "component", type: JvButtonComponent, selector: "jv-button", inputs: ["type", "variant", "icon", "iconPosition", "loading", "disabled"] }, { kind: "component", type: JvSelectComponent, selector: "jv-select", inputs: ["options", "placeholder", "required", "invalid", "describedBy", "ariaLabel", "inputId", "modelValue"], outputs: ["selectionChange"] }] });
25769
26217
  }
25770
26218
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: JvPaginationComponent, decorators: [{
25771
26219
  type: Component,