@devjuliovilla/jv-ui 1.13.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, untracked, 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',
@@ -18238,6 +18240,8 @@ const EN = {
18238
18240
  'grid.filterPlaceholder': 'Filter...',
18239
18241
  'grid.filterForColumn': 'Filter by ${column}',
18240
18242
  'grid.actionsLabel': 'Actions',
18243
+ 'grid.booleanYes': 'YES',
18244
+ 'grid.booleanNo': 'NO',
18241
18245
  'pagination.pageLabel': 'Page {page}',
18242
18246
  'pagination.itemsShowing': '{start}-{end} of {total}',
18243
18247
  'pagination.of': 'of',
@@ -18366,18 +18370,19 @@ function provideJvUi(config = {}) {
18366
18370
  class JvUiConfigService {
18367
18371
  defaults = inject(JV_UI_CONFIG_DEFAULTS);
18368
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
+ });
18369
18384
  get snapshot() {
18370
- return this.configLayers.reduce((merged, layer) => ({
18371
- ...merged,
18372
- ...layer,
18373
- formats: {
18374
- ...merged.formats,
18375
- ...(layer.formats ?? {}),
18376
- },
18377
- }), {
18378
- ...this.defaults,
18379
- formats: { ...this.defaults.formats },
18380
- });
18385
+ return this.resolvedConfig;
18381
18386
  }
18382
18387
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: JvUiConfigService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
18383
18388
  static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: JvUiConfigService, providedIn: 'root' });
@@ -24313,6 +24318,7 @@ const JV_GRID_DEFAULT_OPTIONS = {
24313
24318
  exportFileName: '',
24314
24319
  columnFilters: false,
24315
24320
  rowDoubleClick: false,
24321
+ keyboardNavigation: false,
24316
24322
  };
24317
24323
 
24318
24324
  const MAIN_NS = 'http://schemas.openxmlformats.org/spreadsheetml/2006/main';
@@ -24594,6 +24600,8 @@ let gridIdSequence = 0;
24594
24600
  class JvGridComponent {
24595
24601
  gridId = `jv-grid-${++gridIdSequence}`;
24596
24602
  gridSearchInputId = `${this.gridId}-search`;
24603
+ destroyRef = inject(DestroyRef);
24604
+ document = inject(DOCUMENT);
24597
24605
  formatService = inject(JvFormatService);
24598
24606
  translationService = inject(JvTranslationService);
24599
24607
  data = input([], ...(ngDevMode ? [{ debugName: "data" }] : /* istanbul ignore next */ []));
@@ -24601,7 +24609,7 @@ class JvGridComponent {
24601
24609
  actions = input([], ...(ngDevMode ? [{ debugName: "actions" }] : /* istanbul ignore next */ []));
24602
24610
  options = input({}, ...(ngDevMode ? [{ debugName: "options" }] : /* istanbul ignore next */ []));
24603
24611
  trackBy = input(null, ...(ngDevMode ? [{ debugName: "trackBy" }] : /* istanbul ignore next */ []));
24604
- selectedIds = input([], ...(ngDevMode ? [{ debugName: "selectedIds" }] : /* istanbul ignore next */ []));
24612
+ selectedIds = input(null, ...(ngDevMode ? [{ debugName: "selectedIds" }] : /* istanbul ignore next */ []));
24605
24613
  rowClick = output();
24606
24614
  rowDoubleClick = output();
24607
24615
  actionClick = output();
@@ -24625,8 +24633,9 @@ class JvGridComponent {
24625
24633
  columnOrder = signal([], ...(ngDevMode ? [{ debugName: "columnOrder" }] : /* istanbul ignore next */ []));
24626
24634
  colFilters = signal({}, ...(ngDevMode ? [{ debugName: "colFilters" }] : /* istanbul ignore next */ []));
24627
24635
  columnWidths = signal({}, ...(ngDevMode ? [{ debugName: "columnWidths" }] : /* istanbul ignore next */ []));
24636
+ activeCell = signal({ row: 0, column: 0 }, ...(ngDevMode ? [{ debugName: "activeCell" }] : /* istanbul ignore next */ []));
24628
24637
  visibleColumns = computed(() => {
24629
- const cols = this.columns().filter(col => !col.hidden);
24638
+ const cols = this.flattenColumns(this.columns()).filter(col => !col.hidden);
24630
24639
  const order = this.columnOrder();
24631
24640
  if (order.length > 0) {
24632
24641
  return order.map(key => cols.find(c => this.colKey(c) === key)).filter(Boolean);
@@ -24638,15 +24647,29 @@ class JvGridComponent {
24638
24647
  searchTerm = signal('', ...(ngDevMode ? [{ debugName: "searchTerm" }] : /* istanbul ignore next */ []));
24639
24648
  sortState = signal({ columnKey: '', direction: null }, ...(ngDevMode ? [{ debugName: "sortState" }] : /* istanbul ignore next */ []));
24640
24649
  pageIndex = signal(0, ...(ngDevMode ? [{ debugName: "pageIndex" }] : /* istanbul ignore next */ []));
24641
- 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 */ []));
24642
24660
  searchPageResetter = effect(() => {
24643
24661
  this.searchTerm();
24644
24662
  this.pageIndex.set(0);
24645
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 */ []));
24646
24669
  selectedIdSet = computed(() => {
24647
24670
  const fromInput = this.selectedIds();
24648
24671
  const fromInternal = this.internalSelectedIds();
24649
- const ids = fromInput.length > 0 ? fromInput : fromInternal;
24672
+ const ids = fromInput !== null ? fromInput : fromInternal;
24650
24673
  return new Set(ids);
24651
24674
  }, ...(ngDevMode ? [{ debugName: "selectedIdSet" }] : /* istanbul ignore next */ []));
24652
24675
  hasActiveFilters = computed(() => Object.values(this.colFilters()).some(f => f.value.trim().length > 0), ...(ngDevMode ? [{ debugName: "hasActiveFilters" }] : /* istanbul ignore next */ []));
@@ -24718,9 +24741,8 @@ class JvGridComponent {
24718
24741
  }, ...(ngDevMode ? [{ debugName: "effectiveTotalItems" }] : /* istanbul ignore next */ []));
24719
24742
  effectiveTotalPages = computed(() => Math.max(1, Math.ceil(this.effectiveTotalItems() / this.pageSize())), ...(ngDevMode ? [{ debugName: "effectiveTotalPages" }] : /* istanbul ignore next */ []));
24720
24743
  displayData = computed(() => {
24721
- if (this.effectiveOptions().virtualScroll) {
24722
- return this.sortedData();
24723
- }
24744
+ if (this.virtualScrollEnabled())
24745
+ return this.virtualDisplayData();
24724
24746
  if (!this.effectiveOptions().pageable)
24725
24747
  return this.sortedData();
24726
24748
  const items = this.sortedData();
@@ -24728,12 +24750,17 @@ class JvGridComponent {
24728
24750
  const page = Math.min(this.pageIndex(), this.effectiveTotalPages() - 1);
24729
24751
  return items.slice(page * ps, page * ps + ps);
24730
24752
  }, ...(ngDevMode ? [{ debugName: "displayData" }] : /* istanbul ignore next */ []));
24731
- 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 */ []));
24732
24754
  virtualStartIndex = signal(0, ...(ngDevMode ? [{ debugName: "virtualStartIndex" }] : /* istanbul ignore next */ []));
24733
- 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 */ []));
24734
24761
  virtualDisplayData = computed(() => {
24735
- if (!this.effectiveOptions().virtualScroll)
24736
- return this.displayData();
24762
+ if (!this.virtualScrollEnabled())
24763
+ return this.sortedData();
24737
24764
  const items = this.sortedData();
24738
24765
  const start = this.virtualStartIndex();
24739
24766
  const end = this.virtualEndIndex();
@@ -24779,6 +24806,95 @@ class JvGridComponent {
24779
24806
  return count;
24780
24807
  }, ...(ngDevMode ? [{ debugName: "colspan" }] : /* istanbul ignore next */ []));
24781
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
+ }
24782
24898
  editState = signal(null, ...(ngDevMode ? [{ debugName: "editState" }] : /* istanbul ignore next */ []));
24783
24899
  t = (key, params) => this.translationService.translate(key, params);
24784
24900
  getRowId(row, index) {
@@ -24788,6 +24904,11 @@ class JvGridComponent {
24788
24904
  const tb = this.trackBy();
24789
24905
  if (typeof tb === 'function')
24790
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
+ }
24791
24912
  if (index !== undefined)
24792
24913
  return index;
24793
24914
  return this.data().indexOf(row);
@@ -24803,8 +24924,106 @@ class JvGridComponent {
24803
24924
  }
24804
24925
  getEditValue(row, col) {
24805
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);
24806
24934
  return val == null ? '' : String(val);
24807
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
+ }
24808
25027
  formatValue(value, col, _row) {
24809
25028
  if (value == null)
24810
25029
  return '';
@@ -24890,7 +25109,7 @@ class JvGridComponent {
24890
25109
  return (this.resolvedOptions().editable || col.editable === true) && col.editable !== false;
24891
25110
  }
24892
25111
  groupColspan(group) {
24893
- return group.children?.length ?? 1;
25112
+ return this.flattenColumns(group.children ?? []).filter(col => !col.hidden).length || 1;
24894
25113
  }
24895
25114
  isEditingCell(row, col) {
24896
25115
  const es = this.editState();
@@ -24907,30 +25126,28 @@ class JvGridComponent {
24907
25126
  }));
24908
25127
  }
24909
25128
  findColumn(key) {
24910
- 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 };
24911
25130
  }
24912
25131
  toggleSort(col, event) {
24913
- if (!event || !(event instanceof KeyboardEvent && event.key === ' ')) {
24914
- if (col.sortable === false || !this.resolvedOptions().sortable)
24915
- return;
24916
- if (event instanceof KeyboardEvent)
24917
- event.preventDefault();
24918
- const key = this.colKey(col);
24919
- const current = this.sortState();
24920
- let direction;
24921
- if (current.columnKey !== key) {
24922
- direction = 'asc';
24923
- }
24924
- else if (current.direction === 'asc') {
24925
- direction = 'desc';
24926
- }
24927
- else {
24928
- direction = null;
24929
- }
24930
- this.sortState.set({ columnKey: direction ? key : '', direction });
24931
- this.pageIndex.set(0);
24932
- 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';
25141
+ }
25142
+ else if (current.direction === 'asc') {
25143
+ direction = 'desc';
25144
+ }
25145
+ else {
25146
+ direction = null;
24933
25147
  }
25148
+ this.sortState.set({ columnKey: direction ? key : '', direction });
25149
+ this.pageIndex.set(0);
25150
+ this.sortChange.emit({ columnKey: direction ? key : '', direction });
24934
25151
  }
24935
25152
  actionVariant = (v) => v === 'primary' ? 'primary' : v === 'danger' ? 'danger' : 'ghost';
24936
25153
  onSearchInput(value) {
@@ -24948,29 +25165,37 @@ class JvGridComponent {
24948
25165
  }
24949
25166
  toggleRow(row) {
24950
25167
  const id = this.getRowId(row);
24951
- this.internalSelectedIds.update(ids => {
24952
- const set = new Set(ids);
24953
- if (set.has(id))
24954
- set.delete(id);
24955
- else
24956
- set.add(id);
24957
- return Array.from(set);
24958
- });
24959
- 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 });
24960
25185
  }
24961
25186
  toggleSelectAll(checked) {
24962
- this.internalSelectedIds.update(ids => {
24963
- const set = new Set(ids);
24964
- const rows = this.displayData();
24965
- rows.forEach(r => {
24966
- if (checked)
24967
- set.add(this.getRowId(r));
24968
- else
24969
- set.delete(this.getRowId(r));
24970
- });
24971
- 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));
24972
25194
  });
24973
- 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);
24974
25199
  }
24975
25200
  onRowClick(event, row) {
24976
25201
  if (event instanceof Event) {
@@ -25009,33 +25234,52 @@ class JvGridComponent {
25009
25234
  startEdit(row, col, event) {
25010
25235
  if (!this.isCellEditable(col))
25011
25236
  return;
25012
- if (col.type === 'boolean')
25013
- return;
25237
+ const cell = event.currentTarget;
25014
25238
  event.preventDefault();
25015
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
+ });
25016
25246
  }
25017
25247
  commitEdit(row, col, value) {
25018
25248
  const colKey = this.colKey(col);
25019
- if (this.editState()?.colKey === colKey) {
25249
+ if (this.editState()?.rowId === this.getRowId(row) && this.editState()?.colKey === colKey) {
25020
25250
  this.rowEdit.emit({ row, column: col, value });
25021
25251
  this.editState.set(null);
25022
25252
  }
25023
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
+ }
25024
25260
  cancelEdit() {
25025
25261
  this.editState.set(null);
25026
25262
  }
25027
25263
  onEditValueChange(row, col, value) {
25028
- this.commitEdit(row, col, value);
25264
+ const option = col.editOptions?.find(item => String(item.value) === value);
25265
+ this.commitEdit(row, col, option?.value ?? value);
25029
25266
  }
25030
25267
  // Column resizing
25031
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
+ }
25032
25276
  onResizeStart(event, col, _colIdx) {
25033
25277
  event.preventDefault();
25034
25278
  const th = event.target.closest('th');
25035
25279
  const startWidth = th.offsetWidth;
25036
25280
  this.resizeData = { col, startX: event.clientX, startWidth };
25037
- document.addEventListener('mousemove', this.onResizeMove);
25038
- document.addEventListener('mouseup', this.onResizeEnd);
25281
+ this.document.addEventListener('mousemove', this.onResizeMove);
25282
+ this.document.addEventListener('mouseup', this.onResizeEnd);
25039
25283
  }
25040
25284
  onResizeMove = (event) => {
25041
25285
  if (!this.resizeData)
@@ -25054,8 +25298,8 @@ class JvGridComponent {
25054
25298
  this.columnResize.emit({ columnKey: key, width: w });
25055
25299
  }
25056
25300
  this.resizeData = null;
25057
- document.removeEventListener('mousemove', this.onResizeMove);
25058
- document.removeEventListener('mouseup', this.onResizeEnd);
25301
+ this.document.removeEventListener('mousemove', this.onResizeMove);
25302
+ this.document.removeEventListener('mouseup', this.onResizeEnd);
25059
25303
  };
25060
25304
  // Column reordering
25061
25305
  dragIndex = null;
@@ -25089,7 +25333,7 @@ class JvGridComponent {
25089
25333
  }
25090
25334
  // Virtual scrolling
25091
25335
  onVirtualScroll(target) {
25092
- if (!this.effectiveOptions().virtualScroll)
25336
+ if (!this.virtualScrollEnabled())
25093
25337
  return;
25094
25338
  const rowHeight = this.effectiveOptions().virtualScrollRowHeight ?? 48;
25095
25339
  const buffer = 5;
@@ -25161,11 +25405,11 @@ class JvGridComponent {
25161
25405
  } @else {
25162
25406
  <div
25163
25407
  class="jv-grid-table-wrap"
25164
- [class.jv-grid-virtual]="resolvedOptions().virtualScroll"
25408
+ [class.jv-grid-virtual]="virtualScrollEnabled()"
25165
25409
  #tableWrap
25166
25410
  (scroll)="onVirtualScroll($any($event.target))"
25167
25411
  >
25168
- @if (resolvedOptions().virtualScroll) {
25412
+ @if (virtualScrollEnabled()) {
25169
25413
  <div class="jv-grid-virtual-spacer" [style.height.px]="virtualTotalHeight()"></div>
25170
25414
  }
25171
25415
  <table
@@ -25173,9 +25417,9 @@ class JvGridComponent {
25173
25417
  role="grid"
25174
25418
  [attr.aria-label]="resolvedOptions().ariaLabel || gridLabel()"
25175
25419
  [attr.aria-rowcount]="effectiveTotalItems()"
25176
- [attr.aria-colcount]="visibleColumns().length"
25420
+ [attr.aria-colcount]="colspan()"
25177
25421
  [class]="densityClass()"
25178
- [style.position]="resolvedOptions().virtualScroll ? 'relative' : null"
25422
+ [style.position]="virtualScrollEnabled() ? 'relative' : null"
25179
25423
  [style.top.px]="virtualOffsetY()"
25180
25424
  >
25181
25425
  <caption class="sr-only">{{ resolvedOptions().ariaLabel || gridLabel() }}</caption>
@@ -25243,12 +25487,16 @@ class JvGridComponent {
25243
25487
  [style.width]="getColumnWidth(col)"
25244
25488
  [style.min-width]="col.minWidth || undefined"
25245
25489
  [style.max-width]="col.maxWidth || undefined"
25246
- scope="col"
25247
- [attr.aria-sort]="getAriaSort(col)"
25248
- [attr.aria-label]="getSortLabel(col)"
25249
- [tabindex]="col.sortable !== false && resolvedOptions().sortable ? 0 : -1"
25250
- (click)="toggleSort(col)"
25251
- (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)"
25252
25500
  (keydown.space)="toggleSort(col, $event)"
25253
25501
  draggable="{{ resolvedOptions().reorderableColumns ? 'true' : 'false' }}"
25254
25502
  (dragstart)="onDragStart($event, colIdx)"
@@ -25349,9 +25597,10 @@ class JvGridComponent {
25349
25597
  />
25350
25598
  </td>
25351
25599
  }
25352
- @for (col of visibleColumns(); track colKey(col)) {
25600
+ @for (col of visibleColumns(); track colKey(col); let colIdx = $index) {
25353
25601
  <td
25354
25602
  class="jv-grid-td"
25603
+ [attr.role]="resolvedOptions().keyboardNavigation ? 'gridcell' : null"
25355
25604
  [class]="col.cellClass || ''"
25356
25605
  [class.jv-grid-td-sticky-left]="isStickyLeft(col)"
25357
25606
  [class.jv-grid-td-sticky-right]="isStickyRight(col)"
@@ -25359,30 +25608,38 @@ class JvGridComponent {
25359
25608
  [style.text-align]="col.align ?? 'start'"
25360
25609
  (dblclick)="startEdit(row, col, $event)"
25361
25610
  (keydown.enter)="startEdit(row, col, $event)"
25362
- [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)"
25363
25618
  >
25364
25619
  @if (isEditingCell(row, col)) {
25365
- @if (col.editType === 'select' && col.editOptions) {
25620
+ @if (editType(col) === 'select' && col.editOptions) {
25366
25621
  <jv-select
25367
25622
  [options]="getEditOptions(col)"
25368
25623
  [modelValue]="getEditValue(row, col)"
25369
25624
  (selectionChange)="onEditValueChange(row, col, $event)"
25370
25625
  />
25371
- } @else if (col.editType === 'boolean') {
25626
+ } @else if (editType(col) === 'boolean') {
25372
25627
  <input
25373
25628
  type="checkbox"
25374
25629
  class="jv-grid-checkbox"
25375
- [checked]="getCellValue(row, col) === true"
25376
- (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()"
25377
25634
  [attr.aria-label]="col.header"
25378
25635
  />
25379
25636
  } @else {
25380
25637
  <input
25381
25638
  class="jv-grid-edit-input"
25382
- [type]="col.editType === 'number' ? 'number' : 'text'"
25639
+ [type]="inputType(col)"
25383
25640
  [value]="getEditValue(row, col)"
25384
- (blur)="commitEdit(row, col, $any($event.target).value)"
25385
- (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))"
25386
25643
  (keydown.escape)="cancelEdit()"
25387
25644
  autofocus
25388
25645
  />
@@ -25390,10 +25647,10 @@ class JvGridComponent {
25390
25647
  } @else {
25391
25648
  @let value = getCellValue(row, col);
25392
25649
  @if (col.type === 'boolean') {
25393
- @if (value) {
25394
- <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>
25395
25652
  } @else {
25396
- <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>
25397
25654
  }
25398
25655
  } @else if (col.format) {
25399
25656
  {{ col.format(value, row) }}
@@ -25430,13 +25687,21 @@ class JvGridComponent {
25430
25687
  </div>
25431
25688
  }
25432
25689
 
25433
- @if (resolvedOptions().pageable && effectiveTotalPages() > 1 && !resolvedOptions().loading) {
25690
+ @if (resolvedOptions().pageable && !virtualScrollEnabled() && effectiveTotalPages() > 1 && !resolvedOptions().loading) {
25434
25691
  <nav class="jv-grid-pagination" role="navigation" [attr.aria-label]="t('grid.paginationLabel', { label: '' })">
25435
25692
  <div class="jv-grid-pagination-info" aria-live="polite" aria-atomic="true">
25436
25693
  {{ t('grid.itemsShowing', { start: pageStart(), end: pageEnd(), total: effectiveTotalItems() }) }}
25437
25694
  </div>
25438
- <div class="jv-grid-pagination-controls">
25439
- <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
25440
25705
  variant="outline"
25441
25706
  icon="chevrons-left"
25442
25707
  [disabled]="pageIndex() === 0"
@@ -25476,7 +25741,7 @@ class JvGridComponent {
25476
25741
  </nav>
25477
25742
  }
25478
25743
  </div>
25479
- `, 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", "ariaLabel", "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"] }] });
25480
25745
  }
25481
25746
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: JvGridComponent, decorators: [{
25482
25747
  type: Component,
@@ -25512,11 +25777,11 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImpo
25512
25777
  } @else {
25513
25778
  <div
25514
25779
  class="jv-grid-table-wrap"
25515
- [class.jv-grid-virtual]="resolvedOptions().virtualScroll"
25780
+ [class.jv-grid-virtual]="virtualScrollEnabled()"
25516
25781
  #tableWrap
25517
25782
  (scroll)="onVirtualScroll($any($event.target))"
25518
25783
  >
25519
- @if (resolvedOptions().virtualScroll) {
25784
+ @if (virtualScrollEnabled()) {
25520
25785
  <div class="jv-grid-virtual-spacer" [style.height.px]="virtualTotalHeight()"></div>
25521
25786
  }
25522
25787
  <table
@@ -25524,9 +25789,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImpo
25524
25789
  role="grid"
25525
25790
  [attr.aria-label]="resolvedOptions().ariaLabel || gridLabel()"
25526
25791
  [attr.aria-rowcount]="effectiveTotalItems()"
25527
- [attr.aria-colcount]="visibleColumns().length"
25792
+ [attr.aria-colcount]="colspan()"
25528
25793
  [class]="densityClass()"
25529
- [style.position]="resolvedOptions().virtualScroll ? 'relative' : null"
25794
+ [style.position]="virtualScrollEnabled() ? 'relative' : null"
25530
25795
  [style.top.px]="virtualOffsetY()"
25531
25796
  >
25532
25797
  <caption class="sr-only">{{ resolvedOptions().ariaLabel || gridLabel() }}</caption>
@@ -25594,12 +25859,16 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImpo
25594
25859
  [style.width]="getColumnWidth(col)"
25595
25860
  [style.min-width]="col.minWidth || undefined"
25596
25861
  [style.max-width]="col.maxWidth || undefined"
25597
- scope="col"
25598
- [attr.aria-sort]="getAriaSort(col)"
25599
- [attr.aria-label]="getSortLabel(col)"
25600
- [tabindex]="col.sortable !== false && resolvedOptions().sortable ? 0 : -1"
25601
- (click)="toggleSort(col)"
25602
- (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)"
25603
25872
  (keydown.space)="toggleSort(col, $event)"
25604
25873
  draggable="{{ resolvedOptions().reorderableColumns ? 'true' : 'false' }}"
25605
25874
  (dragstart)="onDragStart($event, colIdx)"
@@ -25700,9 +25969,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImpo
25700
25969
  />
25701
25970
  </td>
25702
25971
  }
25703
- @for (col of visibleColumns(); track colKey(col)) {
25972
+ @for (col of visibleColumns(); track colKey(col); let colIdx = $index) {
25704
25973
  <td
25705
25974
  class="jv-grid-td"
25975
+ [attr.role]="resolvedOptions().keyboardNavigation ? 'gridcell' : null"
25706
25976
  [class]="col.cellClass || ''"
25707
25977
  [class.jv-grid-td-sticky-left]="isStickyLeft(col)"
25708
25978
  [class.jv-grid-td-sticky-right]="isStickyRight(col)"
@@ -25710,30 +25980,38 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImpo
25710
25980
  [style.text-align]="col.align ?? 'start'"
25711
25981
  (dblclick)="startEdit(row, col, $event)"
25712
25982
  (keydown.enter)="startEdit(row, col, $event)"
25713
- [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)"
25714
25990
  >
25715
25991
  @if (isEditingCell(row, col)) {
25716
- @if (col.editType === 'select' && col.editOptions) {
25992
+ @if (editType(col) === 'select' && col.editOptions) {
25717
25993
  <jv-select
25718
25994
  [options]="getEditOptions(col)"
25719
25995
  [modelValue]="getEditValue(row, col)"
25720
25996
  (selectionChange)="onEditValueChange(row, col, $event)"
25721
25997
  />
25722
- } @else if (col.editType === 'boolean') {
25998
+ } @else if (editType(col) === 'boolean') {
25723
25999
  <input
25724
26000
  type="checkbox"
25725
26001
  class="jv-grid-checkbox"
25726
- [checked]="getCellValue(row, col) === true"
25727
- (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()"
25728
26006
  [attr.aria-label]="col.header"
25729
26007
  />
25730
26008
  } @else {
25731
26009
  <input
25732
26010
  class="jv-grid-edit-input"
25733
- [type]="col.editType === 'number' ? 'number' : 'text'"
26011
+ [type]="inputType(col)"
25734
26012
  [value]="getEditValue(row, col)"
25735
- (blur)="commitEdit(row, col, $any($event.target).value)"
25736
- (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))"
25737
26015
  (keydown.escape)="cancelEdit()"
25738
26016
  autofocus
25739
26017
  />
@@ -25741,10 +26019,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImpo
25741
26019
  } @else {
25742
26020
  @let value = getCellValue(row, col);
25743
26021
  @if (col.type === 'boolean') {
25744
- @if (value) {
25745
- <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>
25746
26024
  } @else {
25747
- <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>
25748
26026
  }
25749
26027
  } @else if (col.format) {
25750
26028
  {{ col.format(value, row) }}
@@ -25781,13 +26059,21 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImpo
25781
26059
  </div>
25782
26060
  }
25783
26061
 
25784
- @if (resolvedOptions().pageable && effectiveTotalPages() > 1 && !resolvedOptions().loading) {
26062
+ @if (resolvedOptions().pageable && !virtualScrollEnabled() && effectiveTotalPages() > 1 && !resolvedOptions().loading) {
25785
26063
  <nav class="jv-grid-pagination" role="navigation" [attr.aria-label]="t('grid.paginationLabel', { label: '' })">
25786
26064
  <div class="jv-grid-pagination-info" aria-live="polite" aria-atomic="true">
25787
26065
  {{ t('grid.itemsShowing', { start: pageStart(), end: pageEnd(), total: effectiveTotalItems() }) }}
25788
26066
  </div>
25789
- <div class="jv-grid-pagination-controls">
25790
- <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
25791
26077
  variant="outline"
25792
26078
  icon="chevrons-left"
25793
26079
  [disabled]="pageIndex() === 0"
@@ -25827,8 +26113,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImpo
25827
26113
  </nav>
25828
26114
  }
25829
26115
  </div>
25830
- `, 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"] }]
25831
- }], 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"] }] } });
25832
26118
 
25833
26119
  class JvPaginationComponent {
25834
26120
  translationService = inject(JvTranslationService);