@devjuliovilla/jv-ui 1.5.4 → 1.5.5

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.
@@ -18908,6 +18908,7 @@ const ES = {
18908
18908
  'grid.exportCsv': 'CSV',
18909
18909
  'grid.exportExcel': 'Excel',
18910
18910
  'grid.filterPlaceholder': 'Filtrar...',
18911
+ 'grid.filterForColumn': 'Filtrar por ${column}',
18911
18912
  'grid.actionsLabel': 'Acciones',
18912
18913
  'pagination.pageLabel': 'Página {page}',
18913
18914
  'pagination.itemsShowing': '{start}-{end} de {total}',
@@ -18942,6 +18943,7 @@ const EN = {
18942
18943
  'grid.exportCsv': 'CSV',
18943
18944
  'grid.exportExcel': 'Excel',
18944
18945
  'grid.filterPlaceholder': 'Filter...',
18946
+ 'grid.filterForColumn': 'Filter by ${column}',
18945
18947
  'grid.actionsLabel': 'Actions',
18946
18948
  'pagination.pageLabel': 'Page {page}',
18947
18949
  'pagination.itemsShowing': '{start}-{end} of {total}',
@@ -20718,685 +20720,705 @@ class JvGridComponent {
20718
20720
  URL.revokeObjectURL(url);
20719
20721
  }
20720
20722
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: JvGridComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
20721
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.17", type: JvGridComponent, isStandalone: true, selector: "jv-grid", inputs: { data: { classPropertyName: "data", publicName: "data", isSignal: true, isRequired: false, transformFunction: null }, columns: { classPropertyName: "columns", publicName: "columns", isSignal: true, isRequired: false, transformFunction: null }, actions: { classPropertyName: "actions", publicName: "actions", isSignal: true, isRequired: false, transformFunction: null }, options: { classPropertyName: "options", publicName: "options", isSignal: true, isRequired: false, transformFunction: null }, trackBy: { classPropertyName: "trackBy", publicName: "trackBy", isSignal: true, isRequired: false, transformFunction: null }, selectedIds: { classPropertyName: "selectedIds", publicName: "selectedIds", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { rowClick: "rowClick", rowDoubleClick: "rowDoubleClick", actionClick: "actionClick", selectionChange: "selectionChange", pageChange: "pageChange", searchChange: "searchChange", sortChange: "sortChange", columnFilter: "columnFilter", columnResize: "columnResize", columnReorder: "columnReorder", rowEdit: "rowEdit" }, ngImport: i0, template: `
20722
- <div class="jv-grid" role="region" [attr.aria-label]="resolvedOptions().ariaLabel || gridLabel()">
20723
- @if (resolvedOptions().searchable || resolvedOptions().exportable) {
20724
- <div class="jv-grid-toolbar">
20725
- @if (resolvedOptions().searchable) {
20726
- <div class="jv-grid-search">
20727
- <jv-icon name="search" [size]="16" />
20728
- <jv-input
20729
- inputId="grid-search"
20730
- [placeholder]="t('grid.searchPlaceholder')"
20731
- [ngModel]="searchTerm()"
20732
- (ngModelChange)="onSearchInput($event)"
20733
- />
20734
- </div>
20735
- }
20736
- @if (resolvedOptions().exportable) {
20737
- <div class="jv-grid-export-actions">
20738
- <jv-button variant="outline" icon="download" (click)="exportCsv()">{{ t('grid.exportCsv') }}</jv-button>
20739
- <jv-button variant="outline" icon="file-spreadsheet" (click)="exportExcel()">{{ t('grid.exportExcel') }}</jv-button>
20740
- </div>
20741
- }
20742
- </div>
20743
- }
20744
-
20745
- @if (resolvedOptions().loading) {
20746
- <div class="jv-grid-loading" role="status" aria-live="polite">
20747
- <span class="jv-grid-spinner" aria-hidden="true"></span>
20748
- <span>{{ t('grid.loading') }}</span>
20749
- </div>
20750
- } @else {
20751
- <div
20752
- class="jv-grid-table-wrap"
20753
- [class.jv-grid-virtual]="resolvedOptions().virtualScroll"
20754
- #tableWrap
20755
- (scroll)="onVirtualScroll($any($event.target))"
20756
- >
20757
- @if (resolvedOptions().virtualScroll) {
20758
- <div class="jv-grid-virtual-spacer" [style.height.px]="virtualTotalHeight()"></div>
20759
- }
20760
- <table
20761
- class="jv-grid-table"
20762
- role="grid"
20763
- [attr.aria-label]="resolvedOptions().ariaLabel || gridLabel()"
20764
- [class]="densityClass()"
20765
- [style.position]="resolvedOptions().virtualScroll ? 'relative' : null"
20766
- [style.top.px]="virtualOffsetY()"
20767
- >
20768
- <caption class="sr-only">{{ resolvedOptions().ariaLabel || gridLabel() }}</caption>
20769
-
20770
- @let hasGroupedHeaders = groupedHeaders().length > 0;
20771
-
20772
- <thead class="jv-grid-thead">
20773
- @if (hasGroupedHeaders) {
20774
- <tr>
20775
- @if (resolvedOptions().selectable) {
20776
- <th class="jv-grid-th jv-grid-th-select" scope="col" [attr.rowspan]="2">
20777
- <input
20778
- type="checkbox"
20779
- class="jv-grid-checkbox"
20780
- [checked]="allSelected()"
20781
- (change)="toggleSelectAll($any($event.target).checked)"
20782
- [attr.aria-label]="allSelected() ? t('grid.deselectAll') : t('grid.selectAll')"
20783
- [indeterminate]="someSelected()"
20784
- />
20785
- </th>
20786
- }
20787
- @for (group of groupedHeaders(); track colKey(group)) {
20788
- <th
20789
- class="jv-grid-th"
20790
- [class]="group.headerClass || ''"
20791
- [class.jv-grid-th-sticky-left]="isStickyLeft(group)"
20792
- [class.jv-grid-th-sticky-right]="isStickyRight(group)"
20793
- [attr.colspan]="groupColspan(group)"
20794
- scope="colgroup"
20795
- [style.text-align]="group.align ?? 'start'"
20796
- >
20797
- <div class="jv-grid-th-content">{{ group.header }}</div>
20798
- </th>
20799
- }
20800
- @if (actions().length > 0) {
20801
- <th class="jv-grid-th jv-grid-th-actions" scope="col" [attr.rowspan]="2">
20802
- <span class="sr-only">{{ t('grid.actionsLabel', { label: '' }) }}</span>
20803
- </th>
20804
- }
20805
- </tr>
20806
- }
20807
-
20808
- <tr>
20809
- @if (!hasGroupedHeaders && resolvedOptions().selectable) {
20810
- <th class="jv-grid-th jv-grid-th-select" scope="col">
20811
- <input
20812
- type="checkbox"
20813
- class="jv-grid-checkbox"
20814
- [checked]="allSelected()"
20815
- (change)="toggleSelectAll($any($event.target).checked)"
20816
- [attr.aria-label]="allSelected() ? t('grid.deselectAll') : t('grid.selectAll')"
20817
- [indeterminate]="someSelected()"
20818
- />
20819
- </th>
20820
- }
20821
- @for (col of visibleColumns(); track colKey(col); let colIdx = $index) {
20822
- <th
20823
- class="jv-grid-th"
20824
- [class]="col.headerClass || ''"
20825
- [class.jv-grid-th-sortable]="col.sortable !== false && resolvedOptions().sortable"
20826
- [class.jv-grid-th-sticky-left]="isStickyLeft(col)"
20827
- [class.jv-grid-th-sticky-right]="isStickyRight(col)"
20828
- [class.jv-grid-th-reorderable]="resolvedOptions().reorderableColumns"
20829
- [style.text-align]="col.align ?? 'start'"
20830
- [style.width]="getColumnWidth(col)"
20831
- [style.min-width]="col.minWidth || undefined"
20832
- [style.max-width]="col.maxWidth || undefined"
20833
- scope="col"
20834
- [attr.aria-sort]="getAriaSort(col)"
20835
- [attr.aria-label]="getSortLabel(col)"
20836
- [tabindex]="col.sortable !== false && resolvedOptions().sortable ? 0 : -1"
20837
- (click)="toggleSort(col)"
20838
- (keydown.enter)="toggleSort(col)"
20839
- (keydown.space)="toggleSort(col, $event)"
20840
- draggable="{{ resolvedOptions().reorderableColumns ? 'true' : 'false' }}"
20841
- (dragstart)="onDragStart($event, colIdx)"
20842
- (dragover)="onDragOver($event, colIdx)"
20843
- (drop)="onDrop($event, colIdx)"
20844
- (dragend)="onDragEnd($event)"
20845
- >
20846
- <div class="jv-grid-th-content">
20847
- <span>{{ col.header }}</span>
20848
- @if (col.sortable !== false && resolvedOptions().sortable) {
20849
- <span class="jv-grid-sort-icon" aria-hidden="true">
20850
- @if (sortState().columnKey === colKey(col)) {
20851
- @if (sortState().direction === 'asc') { ▲ }
20852
- @if (sortState().direction === 'desc') { ▼ }
20853
- }
20854
- </span>
20855
- }
20856
- </div>
20857
- @if (isColumnResizable(col)) {
20858
- <div
20859
- class="jv-grid-resize-handle"
20860
- (mousedown)="onResizeStart($event, col, colIdx)"
20861
- ></div>
20862
- }
20863
- </th>
20864
- }
20865
- @if (!hasGroupedHeaders && actions().length > 0) {
20866
- <th class="jv-grid-th jv-grid-th-actions" scope="col">
20867
- <span class="sr-only">{{ t('grid.actionsLabel', { label: '' }) }}</span>
20868
- </th>
20869
- }
20870
- </tr>
20871
-
20872
- @if (resolvedOptions().columnFilters) {
20873
- <tr class="jv-grid-filter-row">
20874
- @if (resolvedOptions().selectable) {
20875
- <th class="jv-grid-th jv-grid-th-select"></th>
20876
- }
20877
- @for (col of visibleColumns(); track colKey(col)) {
20878
- <th class="jv-grid-th jv-grid-th-filter">
20879
- @if (col.filterable !== false) {
20880
- <input
20881
- class="jv-grid-filter-input"
20882
- [placeholder]="t('grid.filterPlaceholder')"
20883
- [ngModel]="colFilters()[colKey(col)]?.value ?? ''"
20884
- (ngModelChange)="onColumnFilterChange(col, $event)"
20885
- />
20886
- }
20887
- </th>
20888
- }
20889
- @if (actions().length > 0) {
20890
- <th class="jv-grid-th jv-grid-th-actions"></th>
20891
- }
20892
- </tr>
20893
- }
20894
- </thead>
20895
-
20896
- <tbody class="jv-grid-tbody">
20897
- @if (displayData().length === 0 && !searchTerm().trim() && !hasActiveFilters()) {
20898
- <tr>
20899
- <td class="jv-grid-empty" [attr.colspan]="colspan()">
20900
- {{ resolvedOptions().emptyMessage || t('grid.emptyMessage') }}
20901
- </td>
20902
- </tr>
20903
- } @else if (displayData().length === 0 && (searchTerm().trim() || hasActiveFilters())) {
20904
- <tr>
20905
- <td class="jv-grid-empty" [attr.colspan]="colspan()">
20906
- {{ resolvedOptions().noResultsMessage || t('grid.noResults') }}
20907
- </td>
20908
- </tr>
20909
- } @else {
20910
- @for (row of displayData(); track getRowId(row, $index); let rowIdx = $index) {
20911
- <tr
20912
- class="jv-grid-tr"
20913
- [class.jv-grid-tr-editing]="isRowEditing(row)"
20914
- (click)="onRowClick(row)"
20915
- (dblclick)="onRowDblClick(row)"
20916
- (keydown.enter)="onRowClick(row)"
20917
- (keydown.space)="onRowClick($event, row)"
20918
- [tabindex]="0"
20919
- >
20920
- @if (resolvedOptions().selectable) {
20921
- <td
20922
- class="jv-grid-td jv-grid-td-select"
20923
- [class.jv-grid-td-sticky-left]="resolvedOptions().stickyColumns"
20924
- >
20925
- <input
20926
- type="checkbox"
20927
- class="jv-grid-checkbox"
20928
- [checked]="isSelected(row)"
20929
- (change)="toggleRow(row); $event.stopPropagation()"
20930
- (keydown.space)="$event.stopPropagation()"
20931
- [attr.aria-label]="getRowLabel(row, $index)"
20932
- />
20933
- </td>
20934
- }
20935
- @for (col of visibleColumns(); track colKey(col)) {
20936
- <td
20937
- class="jv-grid-td"
20938
- [class]="col.cellClass || ''"
20939
- [class.jv-grid-td-sticky-left]="isStickyLeft(col)"
20940
- [class.jv-grid-td-sticky-right]="isStickyRight(col)"
20941
- [class.jv-grid-td-editable]="isCellEditable(col)"
20942
- [style.text-align]="col.align ?? 'start'"
20943
- (dblclick)="startEdit(row, col, $event)"
20944
- >
20945
- @if (isEditingCell(row, col)) {
20946
- @if (col.editType === 'select' && col.editOptions) {
20947
- <jv-select
20948
- [options]="getEditOptions(col)"
20949
- [modelValue]="getEditValue(row, col)"
20950
- (selectionChange)="onEditValueChange(row, col, $event)"
20951
- />
20952
- } @else if (col.editType === 'boolean') {
20953
- <input
20954
- type="checkbox"
20955
- class="jv-grid-checkbox"
20956
- [checked]="getCellValue(row, col) === true"
20957
- (change)="commitEdit(row, col, $any($event.target).checked)"
20958
- />
20959
- } @else {
20960
- <input
20961
- class="jv-grid-edit-input"
20962
- [type]="col.editType === 'number' ? 'number' : 'text'"
20963
- [value]="getEditValue(row, col)"
20964
- (blur)="commitEdit(row, col, $any($event.target).value)"
20965
- (keydown.enter)="commitEdit(row, col, $any($event.target).value)"
20966
- (keydown.escape)="cancelEdit()"
20967
- #editInput
20723
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.17", type: JvGridComponent, isStandalone: true, selector: "jv-grid", inputs: { data: { classPropertyName: "data", publicName: "data", isSignal: true, isRequired: false, transformFunction: null }, columns: { classPropertyName: "columns", publicName: "columns", isSignal: true, isRequired: false, transformFunction: null }, actions: { classPropertyName: "actions", publicName: "actions", isSignal: true, isRequired: false, transformFunction: null }, options: { classPropertyName: "options", publicName: "options", isSignal: true, isRequired: false, transformFunction: null }, trackBy: { classPropertyName: "trackBy", publicName: "trackBy", isSignal: true, isRequired: false, transformFunction: null }, selectedIds: { classPropertyName: "selectedIds", publicName: "selectedIds", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { rowClick: "rowClick", rowDoubleClick: "rowDoubleClick", actionClick: "actionClick", selectionChange: "selectionChange", pageChange: "pageChange", searchChange: "searchChange", sortChange: "sortChange", columnFilter: "columnFilter", columnResize: "columnResize", columnReorder: "columnReorder", rowEdit: "rowEdit" }, ngImport: i0, template: `
20724
+ <div class="jv-grid" role="region" [attr.aria-label]="resolvedOptions().ariaLabel || gridLabel()">
20725
+ @if (resolvedOptions().searchable || resolvedOptions().exportable) {
20726
+ <div class="jv-grid-toolbar">
20727
+ @if (resolvedOptions().searchable) {
20728
+ <div class="jv-grid-search">
20729
+ <jv-icon name="search" [size]="16" />
20730
+ <jv-input
20731
+ inputId="grid-search"
20732
+ [placeholder]="t('grid.searchPlaceholder')"
20733
+ [ngModel]="searchTerm()"
20734
+ (ngModelChange)="onSearchInput($event)"
20735
+ />
20736
+ </div>
20737
+ }
20738
+ @if (resolvedOptions().exportable) {
20739
+ <div class="jv-grid-export-actions">
20740
+ <jv-button variant="outline" icon="download" (click)="exportCsv()">{{ t('grid.exportCsv') }}</jv-button>
20741
+ <jv-button variant="outline" icon="file-spreadsheet" (click)="exportExcel()">{{ t('grid.exportExcel') }}</jv-button>
20742
+ </div>
20743
+ }
20744
+ </div>
20745
+ }
20746
+
20747
+ @if (resolvedOptions().loading) {
20748
+ <div class="jv-grid-loading" role="status" aria-live="polite">
20749
+ <span class="jv-grid-spinner" aria-hidden="true"></span>
20750
+ <span>{{ t('grid.loading') }}</span>
20751
+ </div>
20752
+ } @else {
20753
+ <div
20754
+ class="jv-grid-table-wrap"
20755
+ [class.jv-grid-virtual]="resolvedOptions().virtualScroll"
20756
+ #tableWrap
20757
+ (scroll)="onVirtualScroll($any($event.target))"
20758
+ >
20759
+ @if (resolvedOptions().virtualScroll) {
20760
+ <div class="jv-grid-virtual-spacer" [style.height.px]="virtualTotalHeight()"></div>
20761
+ }
20762
+ <table
20763
+ class="jv-grid-table"
20764
+ role="grid"
20765
+ [attr.aria-label]="resolvedOptions().ariaLabel || gridLabel()"
20766
+ [attr.aria-rowcount]="effectiveTotalItems()"
20767
+ [attr.aria-colcount]="visibleColumns().length"
20768
+ [class]="densityClass()"
20769
+ [style.position]="resolvedOptions().virtualScroll ? 'relative' : null"
20770
+ [style.top.px]="virtualOffsetY()"
20771
+ >
20772
+ <caption class="sr-only">{{ resolvedOptions().ariaLabel || gridLabel() }}</caption>
20773
+
20774
+ @let hasGroupedHeaders = groupedHeaders().length > 0;
20775
+
20776
+ <thead class="jv-grid-thead">
20777
+ @if (hasGroupedHeaders) {
20778
+ <tr>
20779
+ @if (resolvedOptions().selectable) {
20780
+ <th class="jv-grid-th jv-grid-th-select" scope="col" [attr.rowspan]="2">
20781
+ <input
20782
+ type="checkbox"
20783
+ class="jv-grid-checkbox"
20784
+ [checked]="allSelected()"
20785
+ (change)="toggleSelectAll($any($event.target).checked)"
20786
+ [attr.aria-label]="allSelected() ? t('grid.deselectAll') : t('grid.selectAll')"
20787
+ [indeterminate]="someSelected()"
20788
+ />
20789
+ </th>
20790
+ }
20791
+ @for (group of groupedHeaders(); track colKey(group)) {
20792
+ <th
20793
+ class="jv-grid-th"
20794
+ [class]="group.headerClass || ''"
20795
+ [class.jv-grid-th-sticky-left]="isStickyLeft(group)"
20796
+ [class.jv-grid-th-sticky-right]="isStickyRight(group)"
20797
+ [attr.colspan]="groupColspan(group)"
20798
+ scope="colgroup"
20799
+ [style.text-align]="group.align ?? 'start'"
20800
+ >
20801
+ <div class="jv-grid-th-content">{{ group.header }}</div>
20802
+ </th>
20803
+ }
20804
+ @if (actions().length > 0) {
20805
+ <th class="jv-grid-th jv-grid-th-actions" scope="col" [attr.rowspan]="2">
20806
+ <span class="sr-only">{{ t('grid.actionsLabel', { label: '' }) }}</span>
20807
+ </th>
20808
+ }
20809
+ </tr>
20810
+ }
20811
+
20812
+ <tr>
20813
+ @if (!hasGroupedHeaders && resolvedOptions().selectable) {
20814
+ <th class="jv-grid-th jv-grid-th-select" scope="col">
20815
+ <input
20816
+ type="checkbox"
20817
+ class="jv-grid-checkbox"
20818
+ [checked]="allSelected()"
20819
+ (change)="toggleSelectAll($any($event.target).checked)"
20820
+ [attr.aria-label]="allSelected() ? t('grid.deselectAll') : t('grid.selectAll')"
20821
+ [indeterminate]="someSelected()"
20822
+ />
20823
+ </th>
20824
+ }
20825
+ @for (col of visibleColumns(); track colKey(col); let colIdx = $index) {
20826
+ <th
20827
+ class="jv-grid-th"
20828
+ [class]="col.headerClass || ''"
20829
+ [class.jv-grid-th-sortable]="col.sortable !== false && resolvedOptions().sortable"
20830
+ [class.jv-grid-th-sticky-left]="isStickyLeft(col)"
20831
+ [class.jv-grid-th-sticky-right]="isStickyRight(col)"
20832
+ [class.jv-grid-th-reorderable]="resolvedOptions().reorderableColumns"
20833
+ [style.text-align]="col.align ?? 'start'"
20834
+ [style.width]="getColumnWidth(col)"
20835
+ [style.min-width]="col.minWidth || undefined"
20836
+ [style.max-width]="col.maxWidth || undefined"
20837
+ scope="col"
20838
+ [attr.aria-sort]="getAriaSort(col)"
20839
+ [attr.aria-label]="getSortLabel(col)"
20840
+ [tabindex]="col.sortable !== false && resolvedOptions().sortable ? 0 : -1"
20841
+ (click)="toggleSort(col)"
20842
+ (keydown.enter)="toggleSort(col)"
20843
+ (keydown.space)="toggleSort(col, $event)"
20844
+ draggable="{{ resolvedOptions().reorderableColumns ? 'true' : 'false' }}"
20845
+ (dragstart)="onDragStart($event, colIdx)"
20846
+ (dragover)="onDragOver($event, colIdx)"
20847
+ (drop)="onDrop($event, colIdx)"
20848
+ (dragend)="onDragEnd($event)"
20849
+ >
20850
+ <div class="jv-grid-th-content">
20851
+ <span>{{ col.header }}</span>
20852
+ @if (col.sortable !== false && resolvedOptions().sortable) {
20853
+ <span class="jv-grid-sort-icon" aria-hidden="true">
20854
+ @if (sortState().columnKey === colKey(col)) {
20855
+ @if (sortState().direction === 'asc') { ▲ }
20856
+ @if (sortState().direction === 'desc') { ▼ }
20857
+ }
20858
+ </span>
20859
+ }
20860
+ </div>
20861
+ @if (isColumnResizable(col)) {
20862
+ <div
20863
+ class="jv-grid-resize-handle"
20864
+ (mousedown)="onResizeStart($event, col, colIdx)"
20865
+ ></div>
20866
+ }
20867
+ </th>
20868
+ }
20869
+ @if (!hasGroupedHeaders && actions().length > 0) {
20870
+ <th class="jv-grid-th jv-grid-th-actions" scope="col">
20871
+ <span class="sr-only">{{ t('grid.actionsLabel', { label: '' }) }}</span>
20872
+ </th>
20873
+ }
20874
+ </tr>
20875
+
20876
+ @if (resolvedOptions().columnFilters) {
20877
+ <tr class="jv-grid-filter-row">
20878
+ @if (resolvedOptions().selectable) {
20879
+ <th class="jv-grid-th jv-grid-th-select"></th>
20880
+ }
20881
+ @for (col of visibleColumns(); track colKey(col)) {
20882
+ <th class="jv-grid-th jv-grid-th-filter">
20883
+ @if (col.filterable !== false) {
20884
+ <input
20885
+ class="jv-grid-filter-input"
20886
+ [placeholder]="t('grid.filterPlaceholder')"
20887
+ [attr.aria-label]="t('grid.filterForColumn', { column: col.header })"
20888
+ [ngModel]="colFilters()[colKey(col)]?.value ?? ''"
20889
+ (ngModelChange)="onColumnFilterChange(col, $event)"
20890
+ />
20891
+ }
20892
+ </th>
20893
+ }
20894
+ @if (actions().length > 0) {
20895
+ <th class="jv-grid-th jv-grid-th-actions"></th>
20896
+ }
20897
+ </tr>
20898
+ }
20899
+ </thead>
20900
+
20901
+ <tbody class="jv-grid-tbody">
20902
+ @if (displayData().length === 0 && !searchTerm().trim() && !hasActiveFilters()) {
20903
+ <tr>
20904
+ <td class="jv-grid-empty" [attr.colspan]="colspan()">
20905
+ {{ resolvedOptions().emptyMessage || t('grid.emptyMessage') }}
20906
+ </td>
20907
+ </tr>
20908
+ } @else if (displayData().length === 0 && (searchTerm().trim() || hasActiveFilters())) {
20909
+ <tr>
20910
+ <td class="jv-grid-empty" [attr.colspan]="colspan()">
20911
+ {{ resolvedOptions().noResultsMessage || t('grid.noResults') }}
20912
+ </td>
20913
+ </tr>
20914
+ } @else {
20915
+ @for (row of displayData(); track getRowId(row, $index); let rowIdx = $index) {
20916
+ <tr
20917
+ class="jv-grid-tr"
20918
+ role="row"
20919
+ [class.jv-grid-tr-editing]="isRowEditing(row)"
20920
+ [class.jv-grid-tr-selected]="isSelected(row)"
20921
+ [attr.aria-selected]="resolvedOptions().selectable ? isSelected(row) : null"
20922
+ (click)="onRowClick(row)"
20923
+ (dblclick)="onRowDblClick(row)"
20924
+ (keydown.enter)="onRowClick(row)"
20925
+ (keydown.space)="onRowClick($event, row)"
20926
+ [tabindex]="-1"
20927
+ >
20928
+ @if (resolvedOptions().selectable) {
20929
+ <td
20930
+ class="jv-grid-td jv-grid-td-select"
20931
+ [class.jv-grid-td-sticky-left]="resolvedOptions().stickyColumns"
20932
+ >
20933
+ <input
20934
+ type="checkbox"
20935
+ class="jv-grid-checkbox"
20936
+ [checked]="isSelected(row)"
20937
+ (change)="toggleRow(row); $event.stopPropagation()"
20938
+ (keydown.space)="$event.stopPropagation()"
20939
+ [attr.aria-label]="getRowLabel(row, $index)"
20940
+ />
20941
+ </td>
20942
+ }
20943
+ @for (col of visibleColumns(); track colKey(col)) {
20944
+ <td
20945
+ class="jv-grid-td"
20946
+ [class]="col.cellClass || ''"
20947
+ [class.jv-grid-td-sticky-left]="isStickyLeft(col)"
20948
+ [class.jv-grid-td-sticky-right]="isStickyRight(col)"
20949
+ [class.jv-grid-td-editable]="isCellEditable(col)"
20950
+ [style.text-align]="col.align ?? 'start'"
20951
+ (dblclick)="startEdit(row, col, $event)"
20952
+ (keydown.enter)="startEdit(row, col, $event)"
20953
+ [tabindex]="isCellEditable(col) ? -1 : null"
20954
+ >
20955
+ @if (isEditingCell(row, col)) {
20956
+ @if (col.editType === 'select' && col.editOptions) {
20957
+ <jv-select
20958
+ [options]="getEditOptions(col)"
20959
+ [modelValue]="getEditValue(row, col)"
20960
+ (selectionChange)="onEditValueChange(row, col, $event)"
20961
+ />
20962
+ } @else if (col.editType === 'boolean') {
20963
+ <input
20964
+ type="checkbox"
20965
+ class="jv-grid-checkbox"
20966
+ [checked]="getCellValue(row, col) === true"
20967
+ (change)="commitEdit(row, col, $any($event.target).checked)"
20968
+ [attr.aria-label]="col.header"
20968
20969
  />
20969
- }
20970
- } @else {
20971
- @let value = getCellValue(row, col);
20972
- @if (col.type === 'boolean') {
20973
- @if (value) {
20974
- <span class="jv-grid-boolean jv-grid-boolean-true" aria-label="true">✓</span>
20975
- } @else {
20976
- <span class="jv-grid-boolean jv-grid-boolean-false" aria-label="false">✗</span>
20977
- }
20978
- } @else if (col.format) {
20979
- {{ col.format(value, row) }}
20980
- } @else {
20981
- {{ formatValue(value, col) }}
20982
- }
20983
- }
20984
- </td>
20985
- }
20986
- @if (actions().length > 0) {
20987
- <td
20988
- class="jv-grid-td jv-grid-td-actions"
20989
- [class.jv-grid-td-sticky-right]="resolvedOptions().stickyColumns"
20990
- >
20991
- <div class="jv-grid-actions-group">
20992
- @for (action of actions(); track action.id) {
20993
- <jv-button
20994
- [variant]="actionVariant(action.variant)"
20995
- [icon]="action.icon ?? null"
20996
- [disabled]="action.disabled?.(row) ?? false"
20997
- [attr.aria-label]="action.label"
20998
- (click)="onActionClick(action, row); $event.stopPropagation()"
20999
- >{{ action.label }}</jv-button>
21000
- }
21001
- </div>
21002
- </td>
21003
- }
21004
- </tr>
21005
- }
21006
- }
21007
- </tbody>
21008
- </table>
21009
- </div>
21010
- }
21011
-
21012
- @if (resolvedOptions().pageable && effectiveTotalPages() > 1 && !resolvedOptions().loading) {
21013
- <nav class="jv-grid-pagination" [attr.aria-label]="t('grid.paginationLabel', { label: '' })">
21014
- <div class="jv-grid-pagination-info">
21015
- {{ t('grid.itemsShowing', { start: pageStart(), end: pageEnd(), total: effectiveTotalItems() }) }}
21016
- </div>
21017
- <div class="jv-grid-pagination-controls">
21018
- <jv-button
21019
- variant="outline"
21020
- icon="chevrons-left"
21021
- [disabled]="pageIndex() === 0"
21022
- (click)="goToPage(0)"
21023
- [attr.aria-label]="t('grid.pageFirst')"
21024
- ></jv-button>
21025
- <jv-button
21026
- variant="outline"
21027
- icon="chevron-left"
21028
- [disabled]="pageIndex() === 0"
21029
- (click)="goToPage(pageIndex() - 1)"
21030
- [attr.aria-label]="t('grid.pagePrev')"
21031
- ></jv-button>
21032
- @for (p of pageRange(); track p) {
21033
- <jv-button
21034
- [variant]="p === pageIndex() ? 'primary' : 'outline'"
21035
- [attr.aria-current]="p === pageIndex() ? 'page' : null"
21036
- [attr.aria-label]="t('grid.pageOfTotal', { page: p + 1, total: effectiveTotalPages() })"
21037
- (click)="goToPage(p)"
21038
- >{{ p + 1 }}</jv-button>
21039
- }
21040
- <jv-button
21041
- variant="outline"
21042
- icon="chevron-right"
21043
- [disabled]="pageIndex() >= effectiveTotalPages() - 1"
21044
- (click)="goToPage(pageIndex() + 1)"
21045
- [attr.aria-label]="t('grid.pageNext')"
21046
- ></jv-button>
21047
- <jv-button
21048
- variant="outline"
21049
- icon="chevrons-right"
21050
- [disabled]="pageIndex() >= effectiveTotalPages() - 1"
21051
- (click)="goToPage(effectiveTotalPages() - 1)"
21052
- [attr.aria-label]="t('grid.pageLast')"
21053
- ></jv-button>
21054
- </div>
21055
- </nav>
21056
- }
21057
- </div>
21058
- `, 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;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-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)}.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"] }] });
20970
+ } @else {
20971
+ <input
20972
+ class="jv-grid-edit-input"
20973
+ [type]="col.editType === 'number' ? 'number' : 'text'"
20974
+ [value]="getEditValue(row, col)"
20975
+ (blur)="commitEdit(row, col, $any($event.target).value)"
20976
+ (keydown.enter)="commitEdit(row, col, $any($event.target).value)"
20977
+ (keydown.escape)="cancelEdit()"
20978
+ autofocus
20979
+ />
20980
+ }
20981
+ } @else {
20982
+ @let value = getCellValue(row, col);
20983
+ @if (col.type === 'boolean') {
20984
+ @if (value) {
20985
+ <span class="jv-grid-boolean jv-grid-boolean-true" aria-label="true">✓</span>
20986
+ } @else {
20987
+ <span class="jv-grid-boolean jv-grid-boolean-false" aria-label="false">✗</span>
20988
+ }
20989
+ } @else if (col.format) {
20990
+ {{ col.format(value, row) }}
20991
+ } @else {
20992
+ {{ formatValue(value, col) }}
20993
+ }
20994
+ }
20995
+ </td>
20996
+ }
20997
+ @if (actions().length > 0) {
20998
+ <td
20999
+ class="jv-grid-td jv-grid-td-actions"
21000
+ [class.jv-grid-td-sticky-right]="resolvedOptions().stickyColumns"
21001
+ >
21002
+ <div class="jv-grid-actions-group">
21003
+ @for (action of actions(); track action.id) {
21004
+ <jv-button
21005
+ [variant]="actionVariant(action.variant)"
21006
+ [icon]="action.icon ?? null"
21007
+ [disabled]="action.disabled?.(row) ?? false"
21008
+ [attr.aria-label]="action.label"
21009
+ [title]="action.label"
21010
+ (click)="onActionClick(action, row); $event.stopPropagation()"
21011
+ ></jv-button>
21012
+ }
21013
+ </div>
21014
+ </td>
21015
+ }
21016
+ </tr>
21017
+ }
21018
+ }
21019
+ </tbody>
21020
+ </table>
21021
+ </div>
21022
+ }
21023
+
21024
+ @if (resolvedOptions().pageable && effectiveTotalPages() > 1 && !resolvedOptions().loading) {
21025
+ <nav class="jv-grid-pagination" role="navigation" [attr.aria-label]="t('grid.paginationLabel', { label: '' })">
21026
+ <div class="jv-grid-pagination-info" aria-live="polite" aria-atomic="true">
21027
+ {{ t('grid.itemsShowing', { start: pageStart(), end: pageEnd(), total: effectiveTotalItems() }) }}
21028
+ </div>
21029
+ <div class="jv-grid-pagination-controls">
21030
+ <jv-button
21031
+ variant="outline"
21032
+ icon="chevrons-left"
21033
+ [disabled]="pageIndex() === 0"
21034
+ (click)="goToPage(0)"
21035
+ [attr.aria-label]="t('grid.pageFirst')"
21036
+ ></jv-button>
21037
+ <jv-button
21038
+ variant="outline"
21039
+ icon="chevron-left"
21040
+ [disabled]="pageIndex() === 0"
21041
+ (click)="goToPage(pageIndex() - 1)"
21042
+ [attr.aria-label]="t('grid.pagePrev')"
21043
+ ></jv-button>
21044
+ @for (p of pageRange(); track p) {
21045
+ <jv-button
21046
+ [variant]="p === pageIndex() ? 'primary' : 'outline'"
21047
+ [attr.aria-current]="p === pageIndex() ? 'page' : null"
21048
+ [attr.aria-label]="t('grid.pageOfTotal', { page: p + 1, total: effectiveTotalPages() })"
21049
+ (click)="goToPage(p)"
21050
+ >{{ p + 1 }}</jv-button>
21051
+ }
21052
+ <jv-button
21053
+ variant="outline"
21054
+ icon="chevron-right"
21055
+ [disabled]="pageIndex() >= effectiveTotalPages() - 1"
21056
+ (click)="goToPage(pageIndex() + 1)"
21057
+ [attr.aria-label]="t('grid.pageNext')"
21058
+ ></jv-button>
21059
+ <jv-button
21060
+ variant="outline"
21061
+ icon="chevrons-right"
21062
+ [disabled]="pageIndex() >= effectiveTotalPages() - 1"
21063
+ (click)="goToPage(effectiveTotalPages() - 1)"
21064
+ [attr.aria-label]="t('grid.pageLast')"
21065
+ ></jv-button>
21066
+ </div>
21067
+ </nav>
21068
+ }
21069
+ </div>
21070
+ `, 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"] }] });
21059
21071
  }
21060
21072
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: JvGridComponent, decorators: [{
21061
21073
  type: Component,
21062
- args: [{ selector: 'jv-grid', standalone: true, imports: [FormsModule, JvButtonComponent, JvIconComponent, JvInputComponent, JvSelectComponent], template: `
21063
- <div class="jv-grid" role="region" [attr.aria-label]="resolvedOptions().ariaLabel || gridLabel()">
21064
- @if (resolvedOptions().searchable || resolvedOptions().exportable) {
21065
- <div class="jv-grid-toolbar">
21066
- @if (resolvedOptions().searchable) {
21067
- <div class="jv-grid-search">
21068
- <jv-icon name="search" [size]="16" />
21069
- <jv-input
21070
- inputId="grid-search"
21071
- [placeholder]="t('grid.searchPlaceholder')"
21072
- [ngModel]="searchTerm()"
21073
- (ngModelChange)="onSearchInput($event)"
21074
- />
21075
- </div>
21076
- }
21077
- @if (resolvedOptions().exportable) {
21078
- <div class="jv-grid-export-actions">
21079
- <jv-button variant="outline" icon="download" (click)="exportCsv()">{{ t('grid.exportCsv') }}</jv-button>
21080
- <jv-button variant="outline" icon="file-spreadsheet" (click)="exportExcel()">{{ t('grid.exportExcel') }}</jv-button>
21081
- </div>
21082
- }
21083
- </div>
21084
- }
21085
-
21086
- @if (resolvedOptions().loading) {
21087
- <div class="jv-grid-loading" role="status" aria-live="polite">
21088
- <span class="jv-grid-spinner" aria-hidden="true"></span>
21089
- <span>{{ t('grid.loading') }}</span>
21090
- </div>
21091
- } @else {
21092
- <div
21093
- class="jv-grid-table-wrap"
21094
- [class.jv-grid-virtual]="resolvedOptions().virtualScroll"
21095
- #tableWrap
21096
- (scroll)="onVirtualScroll($any($event.target))"
21097
- >
21098
- @if (resolvedOptions().virtualScroll) {
21099
- <div class="jv-grid-virtual-spacer" [style.height.px]="virtualTotalHeight()"></div>
21100
- }
21101
- <table
21102
- class="jv-grid-table"
21103
- role="grid"
21104
- [attr.aria-label]="resolvedOptions().ariaLabel || gridLabel()"
21105
- [class]="densityClass()"
21106
- [style.position]="resolvedOptions().virtualScroll ? 'relative' : null"
21107
- [style.top.px]="virtualOffsetY()"
21108
- >
21109
- <caption class="sr-only">{{ resolvedOptions().ariaLabel || gridLabel() }}</caption>
21110
-
21111
- @let hasGroupedHeaders = groupedHeaders().length > 0;
21112
-
21113
- <thead class="jv-grid-thead">
21114
- @if (hasGroupedHeaders) {
21115
- <tr>
21116
- @if (resolvedOptions().selectable) {
21117
- <th class="jv-grid-th jv-grid-th-select" scope="col" [attr.rowspan]="2">
21118
- <input
21119
- type="checkbox"
21120
- class="jv-grid-checkbox"
21121
- [checked]="allSelected()"
21122
- (change)="toggleSelectAll($any($event.target).checked)"
21123
- [attr.aria-label]="allSelected() ? t('grid.deselectAll') : t('grid.selectAll')"
21124
- [indeterminate]="someSelected()"
21125
- />
21126
- </th>
21127
- }
21128
- @for (group of groupedHeaders(); track colKey(group)) {
21129
- <th
21130
- class="jv-grid-th"
21131
- [class]="group.headerClass || ''"
21132
- [class.jv-grid-th-sticky-left]="isStickyLeft(group)"
21133
- [class.jv-grid-th-sticky-right]="isStickyRight(group)"
21134
- [attr.colspan]="groupColspan(group)"
21135
- scope="colgroup"
21136
- [style.text-align]="group.align ?? 'start'"
21137
- >
21138
- <div class="jv-grid-th-content">{{ group.header }}</div>
21139
- </th>
21140
- }
21141
- @if (actions().length > 0) {
21142
- <th class="jv-grid-th jv-grid-th-actions" scope="col" [attr.rowspan]="2">
21143
- <span class="sr-only">{{ t('grid.actionsLabel', { label: '' }) }}</span>
21144
- </th>
21145
- }
21146
- </tr>
21147
- }
21148
-
21149
- <tr>
21150
- @if (!hasGroupedHeaders && resolvedOptions().selectable) {
21151
- <th class="jv-grid-th jv-grid-th-select" scope="col">
21152
- <input
21153
- type="checkbox"
21154
- class="jv-grid-checkbox"
21155
- [checked]="allSelected()"
21156
- (change)="toggleSelectAll($any($event.target).checked)"
21157
- [attr.aria-label]="allSelected() ? t('grid.deselectAll') : t('grid.selectAll')"
21158
- [indeterminate]="someSelected()"
21159
- />
21160
- </th>
21161
- }
21162
- @for (col of visibleColumns(); track colKey(col); let colIdx = $index) {
21163
- <th
21164
- class="jv-grid-th"
21165
- [class]="col.headerClass || ''"
21166
- [class.jv-grid-th-sortable]="col.sortable !== false && resolvedOptions().sortable"
21167
- [class.jv-grid-th-sticky-left]="isStickyLeft(col)"
21168
- [class.jv-grid-th-sticky-right]="isStickyRight(col)"
21169
- [class.jv-grid-th-reorderable]="resolvedOptions().reorderableColumns"
21170
- [style.text-align]="col.align ?? 'start'"
21171
- [style.width]="getColumnWidth(col)"
21172
- [style.min-width]="col.minWidth || undefined"
21173
- [style.max-width]="col.maxWidth || undefined"
21174
- scope="col"
21175
- [attr.aria-sort]="getAriaSort(col)"
21176
- [attr.aria-label]="getSortLabel(col)"
21177
- [tabindex]="col.sortable !== false && resolvedOptions().sortable ? 0 : -1"
21178
- (click)="toggleSort(col)"
21179
- (keydown.enter)="toggleSort(col)"
21180
- (keydown.space)="toggleSort(col, $event)"
21181
- draggable="{{ resolvedOptions().reorderableColumns ? 'true' : 'false' }}"
21182
- (dragstart)="onDragStart($event, colIdx)"
21183
- (dragover)="onDragOver($event, colIdx)"
21184
- (drop)="onDrop($event, colIdx)"
21185
- (dragend)="onDragEnd($event)"
21186
- >
21187
- <div class="jv-grid-th-content">
21188
- <span>{{ col.header }}</span>
21189
- @if (col.sortable !== false && resolvedOptions().sortable) {
21190
- <span class="jv-grid-sort-icon" aria-hidden="true">
21191
- @if (sortState().columnKey === colKey(col)) {
21192
- @if (sortState().direction === 'asc') { ▲ }
21193
- @if (sortState().direction === 'desc') { ▼ }
21194
- }
21195
- </span>
21196
- }
21197
- </div>
21198
- @if (isColumnResizable(col)) {
21199
- <div
21200
- class="jv-grid-resize-handle"
21201
- (mousedown)="onResizeStart($event, col, colIdx)"
21202
- ></div>
21203
- }
21204
- </th>
21205
- }
21206
- @if (!hasGroupedHeaders && actions().length > 0) {
21207
- <th class="jv-grid-th jv-grid-th-actions" scope="col">
21208
- <span class="sr-only">{{ t('grid.actionsLabel', { label: '' }) }}</span>
21209
- </th>
21210
- }
21211
- </tr>
21212
-
21213
- @if (resolvedOptions().columnFilters) {
21214
- <tr class="jv-grid-filter-row">
21215
- @if (resolvedOptions().selectable) {
21216
- <th class="jv-grid-th jv-grid-th-select"></th>
21217
- }
21218
- @for (col of visibleColumns(); track colKey(col)) {
21219
- <th class="jv-grid-th jv-grid-th-filter">
21220
- @if (col.filterable !== false) {
21221
- <input
21222
- class="jv-grid-filter-input"
21223
- [placeholder]="t('grid.filterPlaceholder')"
21224
- [ngModel]="colFilters()[colKey(col)]?.value ?? ''"
21225
- (ngModelChange)="onColumnFilterChange(col, $event)"
21226
- />
21227
- }
21228
- </th>
21229
- }
21230
- @if (actions().length > 0) {
21231
- <th class="jv-grid-th jv-grid-th-actions"></th>
21232
- }
21233
- </tr>
21234
- }
21235
- </thead>
21236
-
21237
- <tbody class="jv-grid-tbody">
21238
- @if (displayData().length === 0 && !searchTerm().trim() && !hasActiveFilters()) {
21239
- <tr>
21240
- <td class="jv-grid-empty" [attr.colspan]="colspan()">
21241
- {{ resolvedOptions().emptyMessage || t('grid.emptyMessage') }}
21242
- </td>
21243
- </tr>
21244
- } @else if (displayData().length === 0 && (searchTerm().trim() || hasActiveFilters())) {
21245
- <tr>
21246
- <td class="jv-grid-empty" [attr.colspan]="colspan()">
21247
- {{ resolvedOptions().noResultsMessage || t('grid.noResults') }}
21248
- </td>
21249
- </tr>
21250
- } @else {
21251
- @for (row of displayData(); track getRowId(row, $index); let rowIdx = $index) {
21252
- <tr
21253
- class="jv-grid-tr"
21254
- [class.jv-grid-tr-editing]="isRowEditing(row)"
21255
- (click)="onRowClick(row)"
21256
- (dblclick)="onRowDblClick(row)"
21257
- (keydown.enter)="onRowClick(row)"
21258
- (keydown.space)="onRowClick($event, row)"
21259
- [tabindex]="0"
21260
- >
21261
- @if (resolvedOptions().selectable) {
21262
- <td
21263
- class="jv-grid-td jv-grid-td-select"
21264
- [class.jv-grid-td-sticky-left]="resolvedOptions().stickyColumns"
21265
- >
21266
- <input
21267
- type="checkbox"
21268
- class="jv-grid-checkbox"
21269
- [checked]="isSelected(row)"
21270
- (change)="toggleRow(row); $event.stopPropagation()"
21271
- (keydown.space)="$event.stopPropagation()"
21272
- [attr.aria-label]="getRowLabel(row, $index)"
21273
- />
21274
- </td>
21275
- }
21276
- @for (col of visibleColumns(); track colKey(col)) {
21277
- <td
21278
- class="jv-grid-td"
21279
- [class]="col.cellClass || ''"
21280
- [class.jv-grid-td-sticky-left]="isStickyLeft(col)"
21281
- [class.jv-grid-td-sticky-right]="isStickyRight(col)"
21282
- [class.jv-grid-td-editable]="isCellEditable(col)"
21283
- [style.text-align]="col.align ?? 'start'"
21284
- (dblclick)="startEdit(row, col, $event)"
21285
- >
21286
- @if (isEditingCell(row, col)) {
21287
- @if (col.editType === 'select' && col.editOptions) {
21288
- <jv-select
21289
- [options]="getEditOptions(col)"
21290
- [modelValue]="getEditValue(row, col)"
21291
- (selectionChange)="onEditValueChange(row, col, $event)"
21292
- />
21293
- } @else if (col.editType === 'boolean') {
21294
- <input
21295
- type="checkbox"
21296
- class="jv-grid-checkbox"
21297
- [checked]="getCellValue(row, col) === true"
21298
- (change)="commitEdit(row, col, $any($event.target).checked)"
21299
- />
21300
- } @else {
21301
- <input
21302
- class="jv-grid-edit-input"
21303
- [type]="col.editType === 'number' ? 'number' : 'text'"
21304
- [value]="getEditValue(row, col)"
21305
- (blur)="commitEdit(row, col, $any($event.target).value)"
21306
- (keydown.enter)="commitEdit(row, col, $any($event.target).value)"
21307
- (keydown.escape)="cancelEdit()"
21308
- #editInput
21074
+ args: [{ selector: 'jv-grid', standalone: true, imports: [FormsModule, JvButtonComponent, JvIconComponent, JvInputComponent, JvSelectComponent], template: `
21075
+ <div class="jv-grid" role="region" [attr.aria-label]="resolvedOptions().ariaLabel || gridLabel()">
21076
+ @if (resolvedOptions().searchable || resolvedOptions().exportable) {
21077
+ <div class="jv-grid-toolbar">
21078
+ @if (resolvedOptions().searchable) {
21079
+ <div class="jv-grid-search">
21080
+ <jv-icon name="search" [size]="16" />
21081
+ <jv-input
21082
+ inputId="grid-search"
21083
+ [placeholder]="t('grid.searchPlaceholder')"
21084
+ [ngModel]="searchTerm()"
21085
+ (ngModelChange)="onSearchInput($event)"
21086
+ />
21087
+ </div>
21088
+ }
21089
+ @if (resolvedOptions().exportable) {
21090
+ <div class="jv-grid-export-actions">
21091
+ <jv-button variant="outline" icon="download" (click)="exportCsv()">{{ t('grid.exportCsv') }}</jv-button>
21092
+ <jv-button variant="outline" icon="file-spreadsheet" (click)="exportExcel()">{{ t('grid.exportExcel') }}</jv-button>
21093
+ </div>
21094
+ }
21095
+ </div>
21096
+ }
21097
+
21098
+ @if (resolvedOptions().loading) {
21099
+ <div class="jv-grid-loading" role="status" aria-live="polite">
21100
+ <span class="jv-grid-spinner" aria-hidden="true"></span>
21101
+ <span>{{ t('grid.loading') }}</span>
21102
+ </div>
21103
+ } @else {
21104
+ <div
21105
+ class="jv-grid-table-wrap"
21106
+ [class.jv-grid-virtual]="resolvedOptions().virtualScroll"
21107
+ #tableWrap
21108
+ (scroll)="onVirtualScroll($any($event.target))"
21109
+ >
21110
+ @if (resolvedOptions().virtualScroll) {
21111
+ <div class="jv-grid-virtual-spacer" [style.height.px]="virtualTotalHeight()"></div>
21112
+ }
21113
+ <table
21114
+ class="jv-grid-table"
21115
+ role="grid"
21116
+ [attr.aria-label]="resolvedOptions().ariaLabel || gridLabel()"
21117
+ [attr.aria-rowcount]="effectiveTotalItems()"
21118
+ [attr.aria-colcount]="visibleColumns().length"
21119
+ [class]="densityClass()"
21120
+ [style.position]="resolvedOptions().virtualScroll ? 'relative' : null"
21121
+ [style.top.px]="virtualOffsetY()"
21122
+ >
21123
+ <caption class="sr-only">{{ resolvedOptions().ariaLabel || gridLabel() }}</caption>
21124
+
21125
+ @let hasGroupedHeaders = groupedHeaders().length > 0;
21126
+
21127
+ <thead class="jv-grid-thead">
21128
+ @if (hasGroupedHeaders) {
21129
+ <tr>
21130
+ @if (resolvedOptions().selectable) {
21131
+ <th class="jv-grid-th jv-grid-th-select" scope="col" [attr.rowspan]="2">
21132
+ <input
21133
+ type="checkbox"
21134
+ class="jv-grid-checkbox"
21135
+ [checked]="allSelected()"
21136
+ (change)="toggleSelectAll($any($event.target).checked)"
21137
+ [attr.aria-label]="allSelected() ? t('grid.deselectAll') : t('grid.selectAll')"
21138
+ [indeterminate]="someSelected()"
21139
+ />
21140
+ </th>
21141
+ }
21142
+ @for (group of groupedHeaders(); track colKey(group)) {
21143
+ <th
21144
+ class="jv-grid-th"
21145
+ [class]="group.headerClass || ''"
21146
+ [class.jv-grid-th-sticky-left]="isStickyLeft(group)"
21147
+ [class.jv-grid-th-sticky-right]="isStickyRight(group)"
21148
+ [attr.colspan]="groupColspan(group)"
21149
+ scope="colgroup"
21150
+ [style.text-align]="group.align ?? 'start'"
21151
+ >
21152
+ <div class="jv-grid-th-content">{{ group.header }}</div>
21153
+ </th>
21154
+ }
21155
+ @if (actions().length > 0) {
21156
+ <th class="jv-grid-th jv-grid-th-actions" scope="col" [attr.rowspan]="2">
21157
+ <span class="sr-only">{{ t('grid.actionsLabel', { label: '' }) }}</span>
21158
+ </th>
21159
+ }
21160
+ </tr>
21161
+ }
21162
+
21163
+ <tr>
21164
+ @if (!hasGroupedHeaders && resolvedOptions().selectable) {
21165
+ <th class="jv-grid-th jv-grid-th-select" scope="col">
21166
+ <input
21167
+ type="checkbox"
21168
+ class="jv-grid-checkbox"
21169
+ [checked]="allSelected()"
21170
+ (change)="toggleSelectAll($any($event.target).checked)"
21171
+ [attr.aria-label]="allSelected() ? t('grid.deselectAll') : t('grid.selectAll')"
21172
+ [indeterminate]="someSelected()"
21173
+ />
21174
+ </th>
21175
+ }
21176
+ @for (col of visibleColumns(); track colKey(col); let colIdx = $index) {
21177
+ <th
21178
+ class="jv-grid-th"
21179
+ [class]="col.headerClass || ''"
21180
+ [class.jv-grid-th-sortable]="col.sortable !== false && resolvedOptions().sortable"
21181
+ [class.jv-grid-th-sticky-left]="isStickyLeft(col)"
21182
+ [class.jv-grid-th-sticky-right]="isStickyRight(col)"
21183
+ [class.jv-grid-th-reorderable]="resolvedOptions().reorderableColumns"
21184
+ [style.text-align]="col.align ?? 'start'"
21185
+ [style.width]="getColumnWidth(col)"
21186
+ [style.min-width]="col.minWidth || undefined"
21187
+ [style.max-width]="col.maxWidth || undefined"
21188
+ scope="col"
21189
+ [attr.aria-sort]="getAriaSort(col)"
21190
+ [attr.aria-label]="getSortLabel(col)"
21191
+ [tabindex]="col.sortable !== false && resolvedOptions().sortable ? 0 : -1"
21192
+ (click)="toggleSort(col)"
21193
+ (keydown.enter)="toggleSort(col)"
21194
+ (keydown.space)="toggleSort(col, $event)"
21195
+ draggable="{{ resolvedOptions().reorderableColumns ? 'true' : 'false' }}"
21196
+ (dragstart)="onDragStart($event, colIdx)"
21197
+ (dragover)="onDragOver($event, colIdx)"
21198
+ (drop)="onDrop($event, colIdx)"
21199
+ (dragend)="onDragEnd($event)"
21200
+ >
21201
+ <div class="jv-grid-th-content">
21202
+ <span>{{ col.header }}</span>
21203
+ @if (col.sortable !== false && resolvedOptions().sortable) {
21204
+ <span class="jv-grid-sort-icon" aria-hidden="true">
21205
+ @if (sortState().columnKey === colKey(col)) {
21206
+ @if (sortState().direction === 'asc') { ▲ }
21207
+ @if (sortState().direction === 'desc') { ▼ }
21208
+ }
21209
+ </span>
21210
+ }
21211
+ </div>
21212
+ @if (isColumnResizable(col)) {
21213
+ <div
21214
+ class="jv-grid-resize-handle"
21215
+ (mousedown)="onResizeStart($event, col, colIdx)"
21216
+ ></div>
21217
+ }
21218
+ </th>
21219
+ }
21220
+ @if (!hasGroupedHeaders && actions().length > 0) {
21221
+ <th class="jv-grid-th jv-grid-th-actions" scope="col">
21222
+ <span class="sr-only">{{ t('grid.actionsLabel', { label: '' }) }}</span>
21223
+ </th>
21224
+ }
21225
+ </tr>
21226
+
21227
+ @if (resolvedOptions().columnFilters) {
21228
+ <tr class="jv-grid-filter-row">
21229
+ @if (resolvedOptions().selectable) {
21230
+ <th class="jv-grid-th jv-grid-th-select"></th>
21231
+ }
21232
+ @for (col of visibleColumns(); track colKey(col)) {
21233
+ <th class="jv-grid-th jv-grid-th-filter">
21234
+ @if (col.filterable !== false) {
21235
+ <input
21236
+ class="jv-grid-filter-input"
21237
+ [placeholder]="t('grid.filterPlaceholder')"
21238
+ [attr.aria-label]="t('grid.filterForColumn', { column: col.header })"
21239
+ [ngModel]="colFilters()[colKey(col)]?.value ?? ''"
21240
+ (ngModelChange)="onColumnFilterChange(col, $event)"
21241
+ />
21242
+ }
21243
+ </th>
21244
+ }
21245
+ @if (actions().length > 0) {
21246
+ <th class="jv-grid-th jv-grid-th-actions"></th>
21247
+ }
21248
+ </tr>
21249
+ }
21250
+ </thead>
21251
+
21252
+ <tbody class="jv-grid-tbody">
21253
+ @if (displayData().length === 0 && !searchTerm().trim() && !hasActiveFilters()) {
21254
+ <tr>
21255
+ <td class="jv-grid-empty" [attr.colspan]="colspan()">
21256
+ {{ resolvedOptions().emptyMessage || t('grid.emptyMessage') }}
21257
+ </td>
21258
+ </tr>
21259
+ } @else if (displayData().length === 0 && (searchTerm().trim() || hasActiveFilters())) {
21260
+ <tr>
21261
+ <td class="jv-grid-empty" [attr.colspan]="colspan()">
21262
+ {{ resolvedOptions().noResultsMessage || t('grid.noResults') }}
21263
+ </td>
21264
+ </tr>
21265
+ } @else {
21266
+ @for (row of displayData(); track getRowId(row, $index); let rowIdx = $index) {
21267
+ <tr
21268
+ class="jv-grid-tr"
21269
+ role="row"
21270
+ [class.jv-grid-tr-editing]="isRowEditing(row)"
21271
+ [class.jv-grid-tr-selected]="isSelected(row)"
21272
+ [attr.aria-selected]="resolvedOptions().selectable ? isSelected(row) : null"
21273
+ (click)="onRowClick(row)"
21274
+ (dblclick)="onRowDblClick(row)"
21275
+ (keydown.enter)="onRowClick(row)"
21276
+ (keydown.space)="onRowClick($event, row)"
21277
+ [tabindex]="-1"
21278
+ >
21279
+ @if (resolvedOptions().selectable) {
21280
+ <td
21281
+ class="jv-grid-td jv-grid-td-select"
21282
+ [class.jv-grid-td-sticky-left]="resolvedOptions().stickyColumns"
21283
+ >
21284
+ <input
21285
+ type="checkbox"
21286
+ class="jv-grid-checkbox"
21287
+ [checked]="isSelected(row)"
21288
+ (change)="toggleRow(row); $event.stopPropagation()"
21289
+ (keydown.space)="$event.stopPropagation()"
21290
+ [attr.aria-label]="getRowLabel(row, $index)"
21291
+ />
21292
+ </td>
21293
+ }
21294
+ @for (col of visibleColumns(); track colKey(col)) {
21295
+ <td
21296
+ class="jv-grid-td"
21297
+ [class]="col.cellClass || ''"
21298
+ [class.jv-grid-td-sticky-left]="isStickyLeft(col)"
21299
+ [class.jv-grid-td-sticky-right]="isStickyRight(col)"
21300
+ [class.jv-grid-td-editable]="isCellEditable(col)"
21301
+ [style.text-align]="col.align ?? 'start'"
21302
+ (dblclick)="startEdit(row, col, $event)"
21303
+ (keydown.enter)="startEdit(row, col, $event)"
21304
+ [tabindex]="isCellEditable(col) ? -1 : null"
21305
+ >
21306
+ @if (isEditingCell(row, col)) {
21307
+ @if (col.editType === 'select' && col.editOptions) {
21308
+ <jv-select
21309
+ [options]="getEditOptions(col)"
21310
+ [modelValue]="getEditValue(row, col)"
21311
+ (selectionChange)="onEditValueChange(row, col, $event)"
21312
+ />
21313
+ } @else if (col.editType === 'boolean') {
21314
+ <input
21315
+ type="checkbox"
21316
+ class="jv-grid-checkbox"
21317
+ [checked]="getCellValue(row, col) === true"
21318
+ (change)="commitEdit(row, col, $any($event.target).checked)"
21319
+ [attr.aria-label]="col.header"
21309
21320
  />
21310
- }
21311
- } @else {
21312
- @let value = getCellValue(row, col);
21313
- @if (col.type === 'boolean') {
21314
- @if (value) {
21315
- <span class="jv-grid-boolean jv-grid-boolean-true" aria-label="true">✓</span>
21316
- } @else {
21317
- <span class="jv-grid-boolean jv-grid-boolean-false" aria-label="false">✗</span>
21318
- }
21319
- } @else if (col.format) {
21320
- {{ col.format(value, row) }}
21321
- } @else {
21322
- {{ formatValue(value, col) }}
21323
- }
21324
- }
21325
- </td>
21326
- }
21327
- @if (actions().length > 0) {
21328
- <td
21329
- class="jv-grid-td jv-grid-td-actions"
21330
- [class.jv-grid-td-sticky-right]="resolvedOptions().stickyColumns"
21331
- >
21332
- <div class="jv-grid-actions-group">
21333
- @for (action of actions(); track action.id) {
21334
- <jv-button
21335
- [variant]="actionVariant(action.variant)"
21336
- [icon]="action.icon ?? null"
21337
- [disabled]="action.disabled?.(row) ?? false"
21338
- [attr.aria-label]="action.label"
21339
- (click)="onActionClick(action, row); $event.stopPropagation()"
21340
- >{{ action.label }}</jv-button>
21341
- }
21342
- </div>
21343
- </td>
21344
- }
21345
- </tr>
21346
- }
21347
- }
21348
- </tbody>
21349
- </table>
21350
- </div>
21351
- }
21352
-
21353
- @if (resolvedOptions().pageable && effectiveTotalPages() > 1 && !resolvedOptions().loading) {
21354
- <nav class="jv-grid-pagination" [attr.aria-label]="t('grid.paginationLabel', { label: '' })">
21355
- <div class="jv-grid-pagination-info">
21356
- {{ t('grid.itemsShowing', { start: pageStart(), end: pageEnd(), total: effectiveTotalItems() }) }}
21357
- </div>
21358
- <div class="jv-grid-pagination-controls">
21359
- <jv-button
21360
- variant="outline"
21361
- icon="chevrons-left"
21362
- [disabled]="pageIndex() === 0"
21363
- (click)="goToPage(0)"
21364
- [attr.aria-label]="t('grid.pageFirst')"
21365
- ></jv-button>
21366
- <jv-button
21367
- variant="outline"
21368
- icon="chevron-left"
21369
- [disabled]="pageIndex() === 0"
21370
- (click)="goToPage(pageIndex() - 1)"
21371
- [attr.aria-label]="t('grid.pagePrev')"
21372
- ></jv-button>
21373
- @for (p of pageRange(); track p) {
21374
- <jv-button
21375
- [variant]="p === pageIndex() ? 'primary' : 'outline'"
21376
- [attr.aria-current]="p === pageIndex() ? 'page' : null"
21377
- [attr.aria-label]="t('grid.pageOfTotal', { page: p + 1, total: effectiveTotalPages() })"
21378
- (click)="goToPage(p)"
21379
- >{{ p + 1 }}</jv-button>
21380
- }
21381
- <jv-button
21382
- variant="outline"
21383
- icon="chevron-right"
21384
- [disabled]="pageIndex() >= effectiveTotalPages() - 1"
21385
- (click)="goToPage(pageIndex() + 1)"
21386
- [attr.aria-label]="t('grid.pageNext')"
21387
- ></jv-button>
21388
- <jv-button
21389
- variant="outline"
21390
- icon="chevrons-right"
21391
- [disabled]="pageIndex() >= effectiveTotalPages() - 1"
21392
- (click)="goToPage(effectiveTotalPages() - 1)"
21393
- [attr.aria-label]="t('grid.pageLast')"
21394
- ></jv-button>
21395
- </div>
21396
- </nav>
21397
- }
21398
- </div>
21399
- `, 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;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-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)}.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"] }]
21321
+ } @else {
21322
+ <input
21323
+ class="jv-grid-edit-input"
21324
+ [type]="col.editType === 'number' ? 'number' : 'text'"
21325
+ [value]="getEditValue(row, col)"
21326
+ (blur)="commitEdit(row, col, $any($event.target).value)"
21327
+ (keydown.enter)="commitEdit(row, col, $any($event.target).value)"
21328
+ (keydown.escape)="cancelEdit()"
21329
+ autofocus
21330
+ />
21331
+ }
21332
+ } @else {
21333
+ @let value = getCellValue(row, col);
21334
+ @if (col.type === 'boolean') {
21335
+ @if (value) {
21336
+ <span class="jv-grid-boolean jv-grid-boolean-true" aria-label="true">✓</span>
21337
+ } @else {
21338
+ <span class="jv-grid-boolean jv-grid-boolean-false" aria-label="false">✗</span>
21339
+ }
21340
+ } @else if (col.format) {
21341
+ {{ col.format(value, row) }}
21342
+ } @else {
21343
+ {{ formatValue(value, col) }}
21344
+ }
21345
+ }
21346
+ </td>
21347
+ }
21348
+ @if (actions().length > 0) {
21349
+ <td
21350
+ class="jv-grid-td jv-grid-td-actions"
21351
+ [class.jv-grid-td-sticky-right]="resolvedOptions().stickyColumns"
21352
+ >
21353
+ <div class="jv-grid-actions-group">
21354
+ @for (action of actions(); track action.id) {
21355
+ <jv-button
21356
+ [variant]="actionVariant(action.variant)"
21357
+ [icon]="action.icon ?? null"
21358
+ [disabled]="action.disabled?.(row) ?? false"
21359
+ [attr.aria-label]="action.label"
21360
+ [title]="action.label"
21361
+ (click)="onActionClick(action, row); $event.stopPropagation()"
21362
+ ></jv-button>
21363
+ }
21364
+ </div>
21365
+ </td>
21366
+ }
21367
+ </tr>
21368
+ }
21369
+ }
21370
+ </tbody>
21371
+ </table>
21372
+ </div>
21373
+ }
21374
+
21375
+ @if (resolvedOptions().pageable && effectiveTotalPages() > 1 && !resolvedOptions().loading) {
21376
+ <nav class="jv-grid-pagination" role="navigation" [attr.aria-label]="t('grid.paginationLabel', { label: '' })">
21377
+ <div class="jv-grid-pagination-info" aria-live="polite" aria-atomic="true">
21378
+ {{ t('grid.itemsShowing', { start: pageStart(), end: pageEnd(), total: effectiveTotalItems() }) }}
21379
+ </div>
21380
+ <div class="jv-grid-pagination-controls">
21381
+ <jv-button
21382
+ variant="outline"
21383
+ icon="chevrons-left"
21384
+ [disabled]="pageIndex() === 0"
21385
+ (click)="goToPage(0)"
21386
+ [attr.aria-label]="t('grid.pageFirst')"
21387
+ ></jv-button>
21388
+ <jv-button
21389
+ variant="outline"
21390
+ icon="chevron-left"
21391
+ [disabled]="pageIndex() === 0"
21392
+ (click)="goToPage(pageIndex() - 1)"
21393
+ [attr.aria-label]="t('grid.pagePrev')"
21394
+ ></jv-button>
21395
+ @for (p of pageRange(); track p) {
21396
+ <jv-button
21397
+ [variant]="p === pageIndex() ? 'primary' : 'outline'"
21398
+ [attr.aria-current]="p === pageIndex() ? 'page' : null"
21399
+ [attr.aria-label]="t('grid.pageOfTotal', { page: p + 1, total: effectiveTotalPages() })"
21400
+ (click)="goToPage(p)"
21401
+ >{{ p + 1 }}</jv-button>
21402
+ }
21403
+ <jv-button
21404
+ variant="outline"
21405
+ icon="chevron-right"
21406
+ [disabled]="pageIndex() >= effectiveTotalPages() - 1"
21407
+ (click)="goToPage(pageIndex() + 1)"
21408
+ [attr.aria-label]="t('grid.pageNext')"
21409
+ ></jv-button>
21410
+ <jv-button
21411
+ variant="outline"
21412
+ icon="chevrons-right"
21413
+ [disabled]="pageIndex() >= effectiveTotalPages() - 1"
21414
+ (click)="goToPage(effectiveTotalPages() - 1)"
21415
+ [attr.aria-label]="t('grid.pageLast')"
21416
+ ></jv-button>
21417
+ </div>
21418
+ </nav>
21419
+ }
21420
+ </div>
21421
+ `, 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"] }]
21400
21422
  }], 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"] }] } });
21401
21423
 
21402
21424
  class JvPaginationComponent {