@devjuliovilla/jv-ui 1.15.0 → 1.17.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.
Files changed (47) hide show
  1. package/README.md +104 -16
  2. package/fesm2022/devjuliovilla-jv-ui.mjs +971 -1289
  3. package/fesm2022/devjuliovilla-jv-ui.mjs.map +1 -1
  4. package/package.json +85 -1
  5. package/secondary/auth-pages.d.ts +1 -0
  6. package/secondary/auth-pages.mjs +1 -0
  7. package/secondary/buttons.d.ts +1 -0
  8. package/secondary/buttons.mjs +1 -0
  9. package/secondary/calendar.d.ts +1 -0
  10. package/secondary/calendar.mjs +1 -0
  11. package/secondary/collapsible.d.ts +1 -0
  12. package/secondary/collapsible.mjs +1 -0
  13. package/secondary/config.d.ts +1 -0
  14. package/secondary/config.mjs +1 -0
  15. package/secondary/containers.d.ts +1 -0
  16. package/secondary/containers.mjs +1 -0
  17. package/secondary/core.d.ts +1 -0
  18. package/secondary/core.mjs +1 -0
  19. package/secondary/dialogs.d.ts +1 -0
  20. package/secondary/dialogs.mjs +1 -0
  21. package/secondary/feedback.d.ts +1 -0
  22. package/secondary/feedback.mjs +1 -0
  23. package/secondary/forms.d.ts +1 -0
  24. package/secondary/forms.mjs +1 -0
  25. package/secondary/gallery.d.ts +1 -0
  26. package/secondary/gallery.mjs +1 -0
  27. package/secondary/grid.d.ts +1 -0
  28. package/secondary/grid.mjs +1 -0
  29. package/secondary/i18n.d.ts +1 -0
  30. package/secondary/i18n.mjs +1 -0
  31. package/secondary/icons.d.ts +1 -0
  32. package/secondary/icons.mjs +1 -0
  33. package/secondary/layout.d.ts +1 -0
  34. package/secondary/layout.mjs +1 -0
  35. package/secondary/list.d.ts +1 -0
  36. package/secondary/list.mjs +1 -0
  37. package/secondary/pagination.d.ts +1 -0
  38. package/secondary/pagination.mjs +1 -0
  39. package/secondary/services.d.ts +1 -0
  40. package/secondary/services.mjs +1 -0
  41. package/secondary/slider.d.ts +1 -0
  42. package/secondary/slider.mjs +1 -0
  43. package/secondary/tabs.d.ts +1 -0
  44. package/secondary/tabs.mjs +1 -0
  45. package/secondary/tooltip.d.ts +1 -0
  46. package/secondary/tooltip.mjs +1 -0
  47. package/types/devjuliovilla-jv-ui.d.ts +41 -12
@@ -1,5 +1,5 @@
1
1
  import * as i0 from '@angular/core';
2
- import { input, computed, Component, inject, signal, Injectable, InjectionToken, makeEnvironmentProviders, ENVIRONMENT_INITIALIZER, TemplateRef, Directive, contentChild, output, effect, forwardRef, viewChild, untracked, contentChildren, HostListener, viewChildren, numberAttribute, afterNextRender, Renderer2, ElementRef, DestroyRef } from '@angular/core';
2
+ import { input, computed, Component, inject, Renderer2, ElementRef, HostListener, Directive, signal, Injectable, InjectionToken, makeEnvironmentProviders, ENVIRONMENT_INITIALIZER, TemplateRef, contentChild, output, effect, forwardRef, viewChild, untracked, contentChildren, viewChildren, numberAttribute, afterNextRender, 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';
@@ -18009,6 +18009,193 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImpo
18009
18009
  args: [{ selector: 'jv-button-group', standalone: true, template: `<div class="jv-button-group" role="group"><ng-content></ng-content></div>`, styles: [".jv-button-group{display:inline-flex;flex-wrap:wrap;gap:var(--jv-spacing-sm);align-items:center}\n"] }]
18010
18010
  }] });
18011
18011
 
18012
+ let tooltipSequence = 0;
18013
+ class JvTooltipDirective {
18014
+ document = inject(DOCUMENT);
18015
+ renderer = inject(Renderer2);
18016
+ hostRef = inject((ElementRef));
18017
+ tooltipElement = null;
18018
+ showTimeout = null;
18019
+ hideTimeout = null;
18020
+ tooltipId = `jv-tooltip-${++tooltipSequence}`;
18021
+ originalDescribedBy = null;
18022
+ text = input.required({ ...(ngDevMode ? { debugName: "text" } : /* istanbul ignore next */ {}), alias: 'jvTooltip' });
18023
+ position = input('top', { ...(ngDevMode ? { debugName: "position" } : /* istanbul ignore next */ {}), alias: 'jvTooltipPosition' });
18024
+ show() {
18025
+ if (!this.text().trim()) {
18026
+ return;
18027
+ }
18028
+ this.clearHideTimeout();
18029
+ if (this.tooltipElement) {
18030
+ return;
18031
+ }
18032
+ this.showTimeout = setTimeout(() => {
18033
+ this.renderTooltip();
18034
+ this.positionTooltip();
18035
+ this.attachTooltipDescription();
18036
+ }, 120);
18037
+ }
18038
+ hide() {
18039
+ this.clearShowTimeout();
18040
+ if (!this.tooltipElement) {
18041
+ return;
18042
+ }
18043
+ this.hideTimeout = setTimeout(() => this.destroyTooltip(), 80);
18044
+ }
18045
+ handleDocumentKeydown(event) {
18046
+ if (event.key === 'Escape') {
18047
+ this.destroyTooltip();
18048
+ }
18049
+ }
18050
+ handleViewportChange() {
18051
+ if (this.tooltipElement) {
18052
+ this.positionTooltip();
18053
+ }
18054
+ }
18055
+ ngOnDestroy() {
18056
+ this.clearShowTimeout();
18057
+ this.clearHideTimeout();
18058
+ this.destroyTooltip();
18059
+ }
18060
+ renderTooltip() {
18061
+ if (this.tooltipElement) {
18062
+ return;
18063
+ }
18064
+ const tooltip = this.renderer.createElement('div');
18065
+ tooltip.textContent = this.text();
18066
+ this.renderer.setAttribute(tooltip, 'id', this.tooltipId);
18067
+ this.renderer.setAttribute(tooltip, 'role', 'tooltip');
18068
+ this.renderer.setStyle(tooltip, 'position', 'fixed');
18069
+ this.renderer.setStyle(tooltip, 'z-index', '1400');
18070
+ this.renderer.setStyle(tooltip, 'max-width', '18rem');
18071
+ this.renderer.setStyle(tooltip, 'padding', '0.5rem 0.75rem');
18072
+ this.renderer.setStyle(tooltip, 'border-radius', '0.5rem');
18073
+ this.renderer.setStyle(tooltip, 'background', 'rgb(15 23 42)');
18074
+ this.renderer.setStyle(tooltip, 'color', '#ffffff');
18075
+ this.renderer.setStyle(tooltip, 'font-size', '0.8125rem');
18076
+ this.renderer.setStyle(tooltip, 'line-height', '1.4');
18077
+ this.renderer.setStyle(tooltip, 'box-shadow', 'var(--jv-shadow-md)');
18078
+ this.renderer.setStyle(tooltip, 'pointer-events', 'none');
18079
+ this.renderer.setStyle(tooltip, 'opacity', '0');
18080
+ this.renderer.setStyle(tooltip, 'transition', 'opacity 120ms ease');
18081
+ this.renderer.appendChild(this.document.body, tooltip);
18082
+ this.tooltipElement = tooltip;
18083
+ requestAnimationFrame(() => {
18084
+ if (this.tooltipElement) {
18085
+ this.renderer.setStyle(this.tooltipElement, 'opacity', '1');
18086
+ }
18087
+ });
18088
+ }
18089
+ positionTooltip() {
18090
+ const tooltip = this.tooltipElement;
18091
+ if (!tooltip) {
18092
+ return;
18093
+ }
18094
+ const hostRect = this.anchorElement().getBoundingClientRect();
18095
+ const tooltipRect = tooltip.getBoundingClientRect();
18096
+ const gap = 8;
18097
+ let top = hostRect.top - tooltipRect.height - gap;
18098
+ let left = hostRect.left + hostRect.width / 2 - tooltipRect.width / 2;
18099
+ switch (this.position()) {
18100
+ case 'right':
18101
+ top = hostRect.top + hostRect.height / 2 - tooltipRect.height / 2;
18102
+ left = hostRect.right + gap;
18103
+ break;
18104
+ case 'bottom':
18105
+ top = hostRect.bottom + gap;
18106
+ left = hostRect.left + hostRect.width / 2 - tooltipRect.width / 2;
18107
+ break;
18108
+ case 'left':
18109
+ top = hostRect.top + hostRect.height / 2 - tooltipRect.height / 2;
18110
+ left = hostRect.left - tooltipRect.width - gap;
18111
+ break;
18112
+ }
18113
+ this.renderer.setStyle(tooltip, 'top', `${Math.round(top)}px`);
18114
+ this.renderer.setStyle(tooltip, 'left', `${Math.round(left)}px`);
18115
+ }
18116
+ destroyTooltip() {
18117
+ this.clearHideTimeout();
18118
+ this.clearShowTimeout();
18119
+ if (!this.tooltipElement) {
18120
+ return;
18121
+ }
18122
+ this.restoreTooltipDescription();
18123
+ this.renderer.removeChild(this.document.body, this.tooltipElement);
18124
+ this.tooltipElement = null;
18125
+ }
18126
+ attachTooltipDescription() {
18127
+ const anchor = this.anchorElement();
18128
+ this.originalDescribedBy ??= anchor.getAttribute('aria-describedby');
18129
+ const describedBy = [this.originalDescribedBy, this.tooltipId]
18130
+ .filter((value) => !!value)
18131
+ .join(' ')
18132
+ .trim();
18133
+ this.renderer.setAttribute(anchor, 'aria-describedby', describedBy);
18134
+ }
18135
+ restoreTooltipDescription() {
18136
+ const anchor = this.anchorElement();
18137
+ if (this.originalDescribedBy) {
18138
+ this.renderer.setAttribute(anchor, 'aria-describedby', this.originalDescribedBy);
18139
+ }
18140
+ else {
18141
+ this.renderer.removeAttribute(anchor, 'aria-describedby');
18142
+ }
18143
+ this.originalDescribedBy = null;
18144
+ }
18145
+ anchorElement() {
18146
+ const host = this.hostRef.nativeElement;
18147
+ if (isInteractiveElement(host)) {
18148
+ return host;
18149
+ }
18150
+ return host.querySelector('button, a, input, select, textarea, [tabindex]:not([tabindex="-1"])') ?? host;
18151
+ }
18152
+ clearShowTimeout() {
18153
+ if (this.showTimeout) {
18154
+ clearTimeout(this.showTimeout);
18155
+ this.showTimeout = null;
18156
+ }
18157
+ }
18158
+ clearHideTimeout() {
18159
+ if (this.hideTimeout) {
18160
+ clearTimeout(this.hideTimeout);
18161
+ this.hideTimeout = null;
18162
+ }
18163
+ }
18164
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: JvTooltipDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
18165
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.2.17", type: JvTooltipDirective, isStandalone: true, selector: "[jvTooltip]", inputs: { text: { classPropertyName: "text", publicName: "jvTooltip", isSignal: true, isRequired: true, transformFunction: null }, position: { classPropertyName: "position", publicName: "jvTooltipPosition", isSignal: true, isRequired: false, transformFunction: null } }, host: { listeners: { "mouseenter": "show()", "focusin": "show()", "mouseleave": "hide()", "blur": "hide()", "document:keydown": "handleDocumentKeydown($event)", "window:scroll": "handleViewportChange()", "window:resize": "handleViewportChange()" } }, ngImport: i0 });
18166
+ }
18167
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: JvTooltipDirective, decorators: [{
18168
+ type: Directive,
18169
+ args: [{
18170
+ selector: '[jvTooltip]',
18171
+ standalone: true,
18172
+ }]
18173
+ }], propDecorators: { text: [{ type: i0.Input, args: [{ isSignal: true, alias: "jvTooltip", required: true }] }], position: [{ type: i0.Input, args: [{ isSignal: true, alias: "jvTooltipPosition", required: false }] }], show: [{
18174
+ type: HostListener,
18175
+ args: ['mouseenter']
18176
+ }, {
18177
+ type: HostListener,
18178
+ args: ['focusin']
18179
+ }], hide: [{
18180
+ type: HostListener,
18181
+ args: ['mouseleave']
18182
+ }, {
18183
+ type: HostListener,
18184
+ args: ['blur']
18185
+ }], handleDocumentKeydown: [{
18186
+ type: HostListener,
18187
+ args: ['document:keydown', ['$event']]
18188
+ }], handleViewportChange: [{
18189
+ type: HostListener,
18190
+ args: ['window:scroll']
18191
+ }, {
18192
+ type: HostListener,
18193
+ args: ['window:resize']
18194
+ }] } });
18195
+ function isInteractiveElement(element) {
18196
+ return ['BUTTON', 'A', 'INPUT', 'SELECT', 'TEXTAREA'].includes(element.tagName) || element.tabIndex >= 0;
18197
+ }
18198
+
18012
18199
  class JvIconButtonComponent {
18013
18200
  icon = input.required(...(ngDevMode ? [{ debugName: "icon" }] : /* istanbul ignore next */ []));
18014
18201
  ariaLabel = input.required(...(ngDevMode ? [{ debugName: "ariaLabel" }] : /* istanbul ignore next */ []));
@@ -18021,25 +18208,31 @@ class JvIconButtonComponent {
18021
18208
  class="jv-icon-button"
18022
18209
  [class]="'jv-icon-button--' + variant()"
18023
18210
  [attr.aria-label]="ariaLabel()"
18211
+ [attr.title]="ariaLabel()"
18212
+ [jvTooltip]="ariaLabel()"
18024
18213
  [disabled]="disabled()"
18025
18214
  >
18026
18215
  <jv-icon [name]="icon()" [size]="18" />
18216
+ <span class="icon-label">{{ ariaLabel() }}</span>
18027
18217
  </button>
18028
- `, isInline: true, styles: [".jv-icon-button{display:inline-grid;place-items:center;width:var(--jv-density-control-height);height:var(--jv-density-control-height);border:1px solid transparent;border-radius:999px;cursor:pointer;transition:background-color .16s ease,color .16s ease,border-color .16s ease}.jv-icon-button:disabled{opacity:.6;cursor:not-allowed}.jv-icon-button--primary{background:var(--jv-color-primary);color:#fff}.jv-icon-button--secondary{background:var(--jv-color-secondary);color:#fff}.jv-icon-button--outline{border-color:var(--jv-color-border);background:var(--jv-color-surface);color:var(--jv-color-foreground)}.jv-icon-button--ghost,.jv-icon-button--link{background:transparent;color:var(--jv-color-foreground)}.jv-icon-button--danger{background:var(--jv-color-danger);color:#fff}.jv-icon-button--success{background:var(--jv-color-success);color:#fff}.jv-icon-button--warning{background:var(--jv-color-warning);color:#111827}.jv-icon-button--outline:hover,.jv-icon-button--ghost:hover,.jv-icon-button--link:hover{background:var(--jv-color-surface-muted)}\n"], dependencies: [{ kind: "component", type: JvIconComponent, selector: "jv-icon", inputs: ["name", "size", "strokeWidth", "decorative", "ariaLabel"] }] });
18218
+ `, isInline: true, styles: [".jv-icon-button{display:inline-grid;place-items:center;width:var(--jv-density-control-height);height:var(--jv-density-control-height);border:1px solid transparent;border-radius:999px;cursor:pointer;transition:background-color .16s ease,color .16s ease,border-color .16s ease}.icon-label{font-size:0}.jv-icon-button:disabled{opacity:.6;cursor:not-allowed}.jv-icon-button--primary{background:var(--jv-color-primary);color:#fff}.jv-icon-button--secondary{background:var(--jv-color-secondary);color:#fff}.jv-icon-button--outline{border-color:var(--jv-color-border);background:var(--jv-color-surface);color:var(--jv-color-foreground)}.jv-icon-button--ghost,.jv-icon-button--link{background:transparent;color:var(--jv-color-foreground)}.jv-icon-button--danger{background:var(--jv-color-danger);color:#fff}.jv-icon-button--success{background:var(--jv-color-success);color:#fff}.jv-icon-button--warning{background:var(--jv-color-warning);color:#111827}.jv-icon-button--outline:hover,.jv-icon-button--ghost:hover,.jv-icon-button--link:hover{background:var(--jv-color-surface-muted)}\n"], dependencies: [{ kind: "component", type: JvIconComponent, selector: "jv-icon", inputs: ["name", "size", "strokeWidth", "decorative", "ariaLabel"] }, { kind: "directive", type: JvTooltipDirective, selector: "[jvTooltip]", inputs: ["jvTooltip", "jvTooltipPosition"] }] });
18029
18219
  }
18030
18220
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: JvIconButtonComponent, decorators: [{
18031
18221
  type: Component,
18032
- args: [{ selector: 'jv-icon-button', standalone: true, imports: [JvIconComponent], template: `
18222
+ args: [{ selector: 'jv-icon-button', standalone: true, imports: [JvIconComponent, JvTooltipDirective], template: `
18033
18223
  <button
18034
18224
  type="button"
18035
18225
  class="jv-icon-button"
18036
18226
  [class]="'jv-icon-button--' + variant()"
18037
18227
  [attr.aria-label]="ariaLabel()"
18228
+ [attr.title]="ariaLabel()"
18229
+ [jvTooltip]="ariaLabel()"
18038
18230
  [disabled]="disabled()"
18039
18231
  >
18040
18232
  <jv-icon [name]="icon()" [size]="18" />
18233
+ <span class="icon-label">{{ ariaLabel() }}</span>
18041
18234
  </button>
18042
- `, styles: [".jv-icon-button{display:inline-grid;place-items:center;width:var(--jv-density-control-height);height:var(--jv-density-control-height);border:1px solid transparent;border-radius:999px;cursor:pointer;transition:background-color .16s ease,color .16s ease,border-color .16s ease}.jv-icon-button:disabled{opacity:.6;cursor:not-allowed}.jv-icon-button--primary{background:var(--jv-color-primary);color:#fff}.jv-icon-button--secondary{background:var(--jv-color-secondary);color:#fff}.jv-icon-button--outline{border-color:var(--jv-color-border);background:var(--jv-color-surface);color:var(--jv-color-foreground)}.jv-icon-button--ghost,.jv-icon-button--link{background:transparent;color:var(--jv-color-foreground)}.jv-icon-button--danger{background:var(--jv-color-danger);color:#fff}.jv-icon-button--success{background:var(--jv-color-success);color:#fff}.jv-icon-button--warning{background:var(--jv-color-warning);color:#111827}.jv-icon-button--outline:hover,.jv-icon-button--ghost:hover,.jv-icon-button--link:hover{background:var(--jv-color-surface-muted)}\n"] }]
18235
+ `, styles: [".jv-icon-button{display:inline-grid;place-items:center;width:var(--jv-density-control-height);height:var(--jv-density-control-height);border:1px solid transparent;border-radius:999px;cursor:pointer;transition:background-color .16s ease,color .16s ease,border-color .16s ease}.icon-label{font-size:0}.jv-icon-button:disabled{opacity:.6;cursor:not-allowed}.jv-icon-button--primary{background:var(--jv-color-primary);color:#fff}.jv-icon-button--secondary{background:var(--jv-color-secondary);color:#fff}.jv-icon-button--outline{border-color:var(--jv-color-border);background:var(--jv-color-surface);color:var(--jv-color-foreground)}.jv-icon-button--ghost,.jv-icon-button--link{background:transparent;color:var(--jv-color-foreground)}.jv-icon-button--danger{background:var(--jv-color-danger);color:#fff}.jv-icon-button--success{background:var(--jv-color-success);color:#fff}.jv-icon-button--warning{background:var(--jv-color-warning);color:#111827}.jv-icon-button--outline:hover,.jv-icon-button--ghost:hover,.jv-icon-button--link:hover{background:var(--jv-color-surface-muted)}\n"] }]
18043
18236
  }], propDecorators: { icon: [{ type: i0.Input, args: [{ isSignal: true, alias: "icon", required: true }] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabel", required: true }] }], variant: [{ type: i0.Input, args: [{ isSignal: true, alias: "variant", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }] } });
18044
18237
 
18045
18238
  const JV_UI_DEFAULT_CONFIG = {
@@ -22969,24 +23162,15 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImpo
22969
23162
  `, styles: [".jv-breadcrumb ol{display:flex;flex-wrap:wrap;gap:var(--jv-spacing-xs);margin:0;padding:0;list-style:none;color:var(--jv-color-foreground-muted);font-size:.875rem}.jv-breadcrumb li{display:inline-flex;align-items:center;gap:var(--jv-spacing-xs)}.jv-breadcrumb a{color:inherit;text-decoration:none}.jv-breadcrumb a:hover{color:var(--jv-color-foreground);text-decoration:underline}.separator{opacity:.7}\n"] }]
22970
23163
  }], propDecorators: { items: [{ type: i0.Input, args: [{ isSignal: true, alias: "items", required: false }] }] } });
22971
23164
 
22972
- class JvSidebarComponent {
22973
- router = inject(Router);
22974
- expandedSections = signal(new Set(), ...(ngDevMode ? [{ debugName: "expandedSections" }] : /* istanbul ignore next */ []));
22975
- title = input.required(...(ngDevMode ? [{ debugName: "title" }] : /* istanbul ignore next */ []));
22976
- subtitle = input('', ...(ngDevMode ? [{ debugName: "subtitle" }] : /* istanbul ignore next */ []));
22977
- brandIcon = input('monitor', ...(ngDevMode ? [{ debugName: "brandIcon" }] : /* istanbul ignore next */ []));
22978
- items = input([], ...(ngDevMode ? [{ debugName: "items" }] : /* istanbul ignore next */ []));
22979
- collapsed = input(false, ...(ngDevMode ? [{ debugName: "collapsed" }] : /* istanbul ignore next */ []));
22980
- mobileOpen = input(false, ...(ngDevMode ? [{ debugName: "mobileOpen" }] : /* istanbul ignore next */ []));
22981
- ariaLabel = input('Sidebar navigation', ...(ngDevMode ? [{ debugName: "ariaLabel" }] : /* istanbul ignore next */ []));
22982
- currentTheme = input('light', ...(ngDevMode ? [{ debugName: "currentTheme" }] : /* istanbul ignore next */ []));
22983
- themeOptions = input([], ...(ngDevMode ? [{ debugName: "themeOptions" }] : /* istanbul ignore next */ []));
22984
- topbarActions = input([], ...(ngDevMode ? [{ debugName: "topbarActions" }] : /* istanbul ignore next */ []));
22985
- showThemeSelector = input(true, ...(ngDevMode ? [{ debugName: "showThemeSelector" }] : /* istanbul ignore next */ []));
22986
- collapseToggle = output();
22987
- mobileClose = output();
22988
- themeChange = output();
22989
- actionClick = output();
23165
+ class JvNavigationState {
23166
+ items;
23167
+ router;
23168
+ expandActiveSections;
23169
+ sectionOverrides = signal(new Map(), ...(ngDevMode ? [{ debugName: "sectionOverrides" }] : /* istanbul ignore next */ []));
23170
+ normalizedItems = computed(() => this.items().map((item) => ({
23171
+ ...item,
23172
+ children: item.children ?? [],
23173
+ })), ...(ngDevMode ? [{ debugName: "normalizedItems" }] : /* istanbul ignore next */ []));
22990
23174
  activeChildParentIds = computed(() => {
22991
23175
  const url = this.router.url;
22992
23176
  const ids = new Set();
@@ -22999,22 +23183,33 @@ class JvSidebarComponent {
22999
23183
  }
23000
23184
  return ids;
23001
23185
  }, ...(ngDevMode ? [{ debugName: "activeChildParentIds" }] : /* istanbul ignore next */ []));
23002
- normalizedItems = computed(() => this.items().map((item) => ({
23003
- ...item,
23004
- children: item.children ?? [],
23005
- })), ...(ngDevMode ? [{ debugName: "normalizedItems" }] : /* istanbul ignore next */ []));
23006
- isExpanded(id) {
23007
- return this.expandedSections().has(id) || this.activeChildParentIds().has(id);
23186
+ constructor(items, router, expandActiveSections = true) {
23187
+ this.items = items;
23188
+ this.router = router;
23189
+ this.expandActiveSections = expandActiveSections;
23008
23190
  }
23009
- toggleSection(id) {
23010
- this.expandedSections.update((current) => {
23011
- const next = new Set(current);
23012
- if (next.has(id)) {
23013
- next.delete(id);
23014
- }
23015
- else {
23016
- next.add(id);
23191
+ isExpanded(id) {
23192
+ return this.sectionOverrides().get(id) ?? (this.expandActiveSections && this.activeChildParentIds().has(id));
23193
+ }
23194
+ toggleSection(id, exclusive = false) {
23195
+ this.sectionOverrides.update((current) => {
23196
+ const next = new Map(current);
23197
+ const shouldExpand = !this.isExpanded(id);
23198
+ if (exclusive) {
23199
+ for (const item of this.normalizedItems()) {
23200
+ if (item.children.length > 0) {
23201
+ next.set(item.id, false);
23202
+ }
23203
+ }
23017
23204
  }
23205
+ next.set(id, shouldExpand);
23206
+ return next;
23207
+ });
23208
+ }
23209
+ closeSection(id) {
23210
+ this.sectionOverrides.update((current) => {
23211
+ const next = new Map(current);
23212
+ next.set(id, false);
23018
23213
  return next;
23019
23214
  });
23020
23215
  }
@@ -23028,6 +23223,38 @@ class JvSidebarComponent {
23028
23223
  const target = document.querySelector(href);
23029
23224
  target?.scrollIntoView({ block: 'start', behavior: 'smooth' });
23030
23225
  }
23226
+ }
23227
+
23228
+ class JvSidebarComponent {
23229
+ title = input.required(...(ngDevMode ? [{ debugName: "title" }] : /* istanbul ignore next */ []));
23230
+ subtitle = input('', ...(ngDevMode ? [{ debugName: "subtitle" }] : /* istanbul ignore next */ []));
23231
+ brandIcon = input('monitor', ...(ngDevMode ? [{ debugName: "brandIcon" }] : /* istanbul ignore next */ []));
23232
+ items = input([], ...(ngDevMode ? [{ debugName: "items" }] : /* istanbul ignore next */ []));
23233
+ collapsed = input(false, ...(ngDevMode ? [{ debugName: "collapsed" }] : /* istanbul ignore next */ []));
23234
+ mobileOpen = input(false, ...(ngDevMode ? [{ debugName: "mobileOpen" }] : /* istanbul ignore next */ []));
23235
+ ariaLabel = input('Sidebar navigation', ...(ngDevMode ? [{ debugName: "ariaLabel" }] : /* istanbul ignore next */ []));
23236
+ currentTheme = input('light', ...(ngDevMode ? [{ debugName: "currentTheme" }] : /* istanbul ignore next */ []));
23237
+ themeOptions = input([], ...(ngDevMode ? [{ debugName: "themeOptions" }] : /* istanbul ignore next */ []));
23238
+ topbarActions = input([], ...(ngDevMode ? [{ debugName: "topbarActions" }] : /* istanbul ignore next */ []));
23239
+ showThemeSelector = input(true, ...(ngDevMode ? [{ debugName: "showThemeSelector" }] : /* istanbul ignore next */ []));
23240
+ navigation = new JvNavigationState(this.items, inject(Router));
23241
+ collapseToggle = output();
23242
+ mobileClose = output();
23243
+ themeChange = output();
23244
+ actionClick = output();
23245
+ normalizedItems = this.navigation.normalizedItems;
23246
+ isExpanded(id) {
23247
+ return this.navigation.isExpanded(id);
23248
+ }
23249
+ toggleSection(id) {
23250
+ this.navigation.toggleSection(id);
23251
+ }
23252
+ isHashLink(href) {
23253
+ return this.navigation.isHashLink(href);
23254
+ }
23255
+ navigateTo(href) {
23256
+ this.navigation.navigateTo(href);
23257
+ }
23031
23258
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: JvSidebarComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
23032
23259
  static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.17", type: JvSidebarComponent, isStandalone: true, selector: "jv-sidebar", inputs: { title: { classPropertyName: "title", publicName: "title", isSignal: true, isRequired: true, transformFunction: null }, subtitle: { classPropertyName: "subtitle", publicName: "subtitle", isSignal: true, isRequired: false, transformFunction: null }, brandIcon: { classPropertyName: "brandIcon", publicName: "brandIcon", isSignal: true, isRequired: false, transformFunction: null }, items: { classPropertyName: "items", publicName: "items", isSignal: true, isRequired: false, transformFunction: null }, collapsed: { classPropertyName: "collapsed", publicName: "collapsed", isSignal: true, isRequired: false, transformFunction: null }, mobileOpen: { classPropertyName: "mobileOpen", publicName: "mobileOpen", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: false, transformFunction: null }, currentTheme: { classPropertyName: "currentTheme", publicName: "currentTheme", isSignal: true, isRequired: false, transformFunction: null }, themeOptions: { classPropertyName: "themeOptions", publicName: "themeOptions", isSignal: true, isRequired: false, transformFunction: null }, topbarActions: { classPropertyName: "topbarActions", publicName: "topbarActions", isSignal: true, isRequired: false, transformFunction: null }, showThemeSelector: { classPropertyName: "showThemeSelector", publicName: "showThemeSelector", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { collapseToggle: "collapseToggle", mobileClose: "mobileClose", themeChange: "themeChange", actionClick: "actionClick" }, ngImport: i0, template: `
23033
23260
  @if (mobileOpen()) {
@@ -23176,19 +23403,20 @@ class JvSidebarComponent {
23176
23403
 
23177
23404
  <div class="mobile-actions">
23178
23405
  @for (action of topbarActions(); track action.id) {
23179
- <button type="button" class="icon-button" [attr.aria-label]="action.label" (click)="actionClick.emit(action.id); mobileClose.emit()">
23406
+ <button type="button" class="icon-button" [attr.aria-label]="action.label" [attr.title]="action.label" [jvTooltip]="action.label" (click)="actionClick.emit(action.id); mobileClose.emit()">
23180
23407
  <jv-icon [name]="action.icon" [size]="18" />
23408
+ <span>{{ action.label }}</span>
23181
23409
  </button>
23182
23410
  }
23183
23411
  </div>
23184
23412
  </div>
23185
23413
  </div>
23186
23414
  </aside>
23187
- `, isInline: true, styles: [".jv-sidebar-backdrop{display:none}.jv-sidebar{display:grid;grid-template-rows:auto 1fr auto;gap:var(--jv-spacing-lg);width:280px;min-width:280px;height:100vh;padding:var(--jv-spacing-lg);overflow-y:auto;border-right:1px solid var(--jv-color-border);background:var(--jv-color-surface);scrollbar-width:none;-ms-overflow-style:none}.jv-sidebar::-webkit-scrollbar{width:0;height:0;display:none}.jv-sidebar.is-collapsed{width:96px;min-width:96px;padding-inline:var(--jv-spacing-md)}.jv-sidebar-footer{display:grid;gap:var(--jv-spacing-md)}.jv-sidebar-mobile-extras{display:none}.jv-sidebar-brand,.jv-sidebar-link,.jv-sidebar-toggle{display:flex;align-items:center;gap:var(--jv-spacing-sm)}.brand-mark{display:inline-grid;place-items:center;width:var(--jv-density-control-height);height:var(--jv-density-control-height);border-radius:var(--jv-radius-md);background:color-mix(in srgb,var(--jv-color-primary) 16%,var(--jv-color-surface));color:var(--jv-color-primary);flex:0 0 auto}.brand-copy{display:grid;gap:.1rem}.brand-copy span{color:var(--jv-color-foreground-muted);font-size:.875rem}.jv-sidebar-item{display:grid;gap:var(--jv-spacing-xs);list-style:none}.jv-sidebar-list{display:grid;gap:var(--jv-spacing-xs);margin:0;padding:0}.jv-sidebar-nav{display:grid;gap:var(--jv-spacing-xs);align-content:start}.jv-sidebar-link,.jv-sidebar-child-link,.jv-sidebar-toggle{width:100%;display:flex;align-items:center;justify-content:space-between;min-height:var(--jv-density-control-height);padding:0 var(--jv-spacing-md);border:1px solid transparent;border-radius:var(--jv-radius-md);background:transparent;color:var(--jv-color-foreground-muted);text-decoration:none;cursor:pointer}.jv-sidebar-link-main{display:inline-flex;align-items:center;gap:var(--jv-spacing-sm)}.jv-sidebar-link:hover,.jv-sidebar-child-link:hover,.jv-sidebar-toggle:hover{background:var(--jv-color-surface-muted);color:var(--jv-color-foreground)}.jv-sidebar-link.is-active,.jv-sidebar-child-link.is-active{border-color:color-mix(in srgb,var(--jv-color-primary) 25%,var(--jv-color-border));background:color-mix(in srgb,var(--jv-color-primary) 12%,var(--jv-color-surface));color:var(--jv-color-foreground)}.jv-sidebar-children{display:grid;gap:var(--jv-spacing-xs);margin-left:var(--jv-spacing-xl);padding-left:var(--jv-spacing-sm);border-left:1px solid var(--jv-color-border)}.jv-sidebar-child-link{display:flex;align-items:center;min-height:calc(var(--jv-density-control-height) - .25rem);font-size:.9rem}.jv-sidebar-toggle{width:100%;background:var(--jv-color-surface-muted);cursor:pointer}@media(max-width:900px){.jv-sidebar-backdrop{display:block;position:fixed;inset:0;z-index:99;background:#0006}.jv-sidebar{position:fixed;top:0;left:0;z-index:100;width:280px;min-width:280px;height:100vh;border-right:1px solid var(--jv-color-border);border-bottom:none;box-shadow:var(--jv-shadow-lg);transform:translate(-100%);visibility:hidden;transition:transform .24s cubic-bezier(.4,0,.2,1),visibility .24s}.jv-sidebar.is-mobile-open{transform:translate(0);visibility:visible}.jv-sidebar.is-collapsed{width:280px;min-width:280px}.jv-sidebar-mobile-extras{display:flex;flex-direction:column;gap:var(--jv-spacing-md);padding-top:var(--jv-spacing-md);border-top:1px solid var(--jv-color-border)}.jv-sidebar-mobile-extras .theme-group{display:flex;flex-wrap:wrap;gap:var(--jv-spacing-xs)}.jv-sidebar-mobile-extras .theme-pill{display:inline-flex;align-items:center;gap:var(--jv-spacing-xs);min-height:var(--jv-density-control-height);padding:0 var(--jv-spacing-md);border:1px solid var(--jv-color-border);border-radius:999px;background:var(--jv-color-surface-muted);color:var(--jv-color-foreground);cursor:pointer;font:inherit}.jv-sidebar-mobile-extras .theme-pill.is-active{border-color:var(--jv-color-primary);background:color-mix(in srgb,var(--jv-color-primary) 14%,var(--jv-color-surface))}.jv-sidebar-mobile-extras .mobile-actions{display:flex;gap:var(--jv-spacing-sm)}.jv-sidebar-mobile-extras .icon-button{display:inline-grid;place-items:center;width:var(--jv-density-control-height);height:var(--jv-density-control-height);border:1px solid var(--jv-color-border);border-radius:999px;background:var(--jv-color-surface-muted);color:var(--jv-color-foreground);cursor:pointer}}\n"], dependencies: [{ kind: "component", type: JvIconComponent, selector: "jv-icon", inputs: ["name", "size", "strokeWidth", "decorative", "ariaLabel"] }, { kind: "directive", type: RouterLink, selector: "[routerLink]", inputs: ["target", "queryParams", "fragment", "queryParamsHandling", "state", "info", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "routerLink"] }] });
23415
+ `, isInline: true, styles: [".jv-sidebar-backdrop{display:none}.jv-sidebar{display:grid;grid-template-rows:auto 1fr auto;gap:var(--jv-spacing-lg);width:280px;min-width:280px;height:100vh;padding:var(--jv-spacing-lg);overflow-y:auto;border-right:1px solid var(--jv-color-border);background:var(--jv-color-surface);scrollbar-width:none;-ms-overflow-style:none}.jv-sidebar::-webkit-scrollbar{width:0;height:0;display:none}.jv-sidebar.is-collapsed{width:96px;min-width:96px;padding-inline:var(--jv-spacing-md)}.jv-sidebar-footer{display:grid;gap:var(--jv-spacing-md)}.jv-sidebar-mobile-extras{display:none}.jv-sidebar-brand,.jv-sidebar-link,.jv-sidebar-toggle{display:flex;align-items:center;gap:var(--jv-spacing-sm)}.brand-mark{display:inline-grid;place-items:center;width:var(--jv-density-control-height);height:var(--jv-density-control-height);border-radius:var(--jv-radius-md);background:color-mix(in srgb,var(--jv-color-primary) 16%,var(--jv-color-surface));color:var(--jv-color-primary);flex:0 0 auto}.brand-copy{display:grid;gap:.1rem}.brand-copy span{color:var(--jv-color-foreground-muted);font-size:.875rem}.jv-sidebar-item{display:grid;gap:var(--jv-spacing-xs);list-style:none}.jv-sidebar-list{display:grid;gap:var(--jv-spacing-xs);margin:0;padding:0}.jv-sidebar-nav{display:grid;gap:var(--jv-spacing-xs);align-content:start}.jv-sidebar-link,.jv-sidebar-child-link,.jv-sidebar-toggle{width:100%;display:flex;align-items:center;justify-content:space-between;min-height:var(--jv-density-control-height);padding:0 var(--jv-spacing-md);border:1px solid transparent;border-radius:var(--jv-radius-md);background:transparent;color:var(--jv-color-foreground-muted);text-decoration:none;cursor:pointer}.jv-sidebar-link-main{display:inline-flex;align-items:center;gap:var(--jv-spacing-sm)}.jv-sidebar-link:hover,.jv-sidebar-child-link:hover,.jv-sidebar-toggle:hover{background:var(--jv-color-surface-muted);color:var(--jv-color-foreground)}.jv-sidebar-link.is-active,.jv-sidebar-child-link.is-active{border-color:color-mix(in srgb,var(--jv-color-primary) 25%,var(--jv-color-border));background:color-mix(in srgb,var(--jv-color-primary) 12%,var(--jv-color-surface));color:var(--jv-color-foreground)}.jv-sidebar-children{display:grid;gap:var(--jv-spacing-xs);margin-left:var(--jv-spacing-xl);padding-left:var(--jv-spacing-sm);border-left:1px solid var(--jv-color-border)}.jv-sidebar-child-link{display:flex;align-items:center;min-height:calc(var(--jv-density-control-height) - .25rem);font-size:.9rem}.jv-sidebar-toggle{width:100%;background:var(--jv-color-surface-muted);cursor:pointer}@media(max-width:900px){.jv-sidebar-backdrop{display:block;position:fixed;inset:0;z-index:99;background:#0006}.jv-sidebar{position:fixed;top:0;left:0;z-index:100;width:280px;min-width:280px;height:100vh;border-right:1px solid var(--jv-color-border);border-bottom:none;box-shadow:var(--jv-shadow-lg);transform:translate(-100%);visibility:hidden;transition:transform .24s cubic-bezier(.4,0,.2,1),visibility .24s}.jv-sidebar.is-mobile-open{transform:translate(0);visibility:visible}.jv-sidebar.is-collapsed{width:280px;min-width:280px}.jv-sidebar-mobile-extras{display:flex;flex-direction:column;gap:var(--jv-spacing-md);padding-top:var(--jv-spacing-md);border-top:1px solid var(--jv-color-border)}.jv-sidebar-mobile-extras .theme-group{display:flex;flex-wrap:wrap;gap:var(--jv-spacing-xs)}.jv-sidebar-mobile-extras .theme-pill{display:inline-flex;align-items:center;gap:var(--jv-spacing-xs);min-height:var(--jv-density-control-height);padding:0 var(--jv-spacing-md);border:1px solid var(--jv-color-border);border-radius:999px;background:var(--jv-color-surface-muted);color:var(--jv-color-foreground);cursor:pointer;font:inherit}.jv-sidebar-mobile-extras .theme-pill.is-active{border-color:var(--jv-color-primary);background:color-mix(in srgb,var(--jv-color-primary) 14%,var(--jv-color-surface))}.jv-sidebar-mobile-extras .mobile-actions{display:flex;gap:var(--jv-spacing-sm)}.jv-sidebar-mobile-extras .icon-button{display:inline-flex;align-items:center;gap:var(--jv-spacing-xs);min-width:var(--jv-density-control-height);height:var(--jv-density-control-height);padding:0 var(--jv-spacing-sm);border:1px solid var(--jv-color-border);border-radius:999px;background:var(--jv-color-surface-muted);color:var(--jv-color-foreground);cursor:pointer}}\n"], dependencies: [{ kind: "component", type: JvIconComponent, selector: "jv-icon", inputs: ["name", "size", "strokeWidth", "decorative", "ariaLabel"] }, { kind: "directive", type: RouterLink, selector: "[routerLink]", inputs: ["target", "queryParams", "fragment", "queryParamsHandling", "state", "info", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "routerLink"] }, { kind: "directive", type: JvTooltipDirective, selector: "[jvTooltip]", inputs: ["jvTooltip", "jvTooltipPosition"] }] });
23188
23416
  }
23189
23417
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: JvSidebarComponent, decorators: [{
23190
23418
  type: Component,
23191
- args: [{ selector: 'jv-sidebar', standalone: true, imports: [JvIconComponent, RouterLink], template: `
23419
+ args: [{ selector: 'jv-sidebar', standalone: true, imports: [JvIconComponent, RouterLink, JvTooltipDirective], template: `
23192
23420
  @if (mobileOpen()) {
23193
23421
  <div class="jv-sidebar-backdrop" tabindex="-1" (click)="mobileClose.emit()" (keydown.escape)="mobileClose.emit()"></div>
23194
23422
  }
@@ -23335,15 +23563,16 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImpo
23335
23563
 
23336
23564
  <div class="mobile-actions">
23337
23565
  @for (action of topbarActions(); track action.id) {
23338
- <button type="button" class="icon-button" [attr.aria-label]="action.label" (click)="actionClick.emit(action.id); mobileClose.emit()">
23566
+ <button type="button" class="icon-button" [attr.aria-label]="action.label" [attr.title]="action.label" [jvTooltip]="action.label" (click)="actionClick.emit(action.id); mobileClose.emit()">
23339
23567
  <jv-icon [name]="action.icon" [size]="18" />
23568
+ <span>{{ action.label }}</span>
23340
23569
  </button>
23341
23570
  }
23342
23571
  </div>
23343
23572
  </div>
23344
23573
  </div>
23345
23574
  </aside>
23346
- `, styles: [".jv-sidebar-backdrop{display:none}.jv-sidebar{display:grid;grid-template-rows:auto 1fr auto;gap:var(--jv-spacing-lg);width:280px;min-width:280px;height:100vh;padding:var(--jv-spacing-lg);overflow-y:auto;border-right:1px solid var(--jv-color-border);background:var(--jv-color-surface);scrollbar-width:none;-ms-overflow-style:none}.jv-sidebar::-webkit-scrollbar{width:0;height:0;display:none}.jv-sidebar.is-collapsed{width:96px;min-width:96px;padding-inline:var(--jv-spacing-md)}.jv-sidebar-footer{display:grid;gap:var(--jv-spacing-md)}.jv-sidebar-mobile-extras{display:none}.jv-sidebar-brand,.jv-sidebar-link,.jv-sidebar-toggle{display:flex;align-items:center;gap:var(--jv-spacing-sm)}.brand-mark{display:inline-grid;place-items:center;width:var(--jv-density-control-height);height:var(--jv-density-control-height);border-radius:var(--jv-radius-md);background:color-mix(in srgb,var(--jv-color-primary) 16%,var(--jv-color-surface));color:var(--jv-color-primary);flex:0 0 auto}.brand-copy{display:grid;gap:.1rem}.brand-copy span{color:var(--jv-color-foreground-muted);font-size:.875rem}.jv-sidebar-item{display:grid;gap:var(--jv-spacing-xs);list-style:none}.jv-sidebar-list{display:grid;gap:var(--jv-spacing-xs);margin:0;padding:0}.jv-sidebar-nav{display:grid;gap:var(--jv-spacing-xs);align-content:start}.jv-sidebar-link,.jv-sidebar-child-link,.jv-sidebar-toggle{width:100%;display:flex;align-items:center;justify-content:space-between;min-height:var(--jv-density-control-height);padding:0 var(--jv-spacing-md);border:1px solid transparent;border-radius:var(--jv-radius-md);background:transparent;color:var(--jv-color-foreground-muted);text-decoration:none;cursor:pointer}.jv-sidebar-link-main{display:inline-flex;align-items:center;gap:var(--jv-spacing-sm)}.jv-sidebar-link:hover,.jv-sidebar-child-link:hover,.jv-sidebar-toggle:hover{background:var(--jv-color-surface-muted);color:var(--jv-color-foreground)}.jv-sidebar-link.is-active,.jv-sidebar-child-link.is-active{border-color:color-mix(in srgb,var(--jv-color-primary) 25%,var(--jv-color-border));background:color-mix(in srgb,var(--jv-color-primary) 12%,var(--jv-color-surface));color:var(--jv-color-foreground)}.jv-sidebar-children{display:grid;gap:var(--jv-spacing-xs);margin-left:var(--jv-spacing-xl);padding-left:var(--jv-spacing-sm);border-left:1px solid var(--jv-color-border)}.jv-sidebar-child-link{display:flex;align-items:center;min-height:calc(var(--jv-density-control-height) - .25rem);font-size:.9rem}.jv-sidebar-toggle{width:100%;background:var(--jv-color-surface-muted);cursor:pointer}@media(max-width:900px){.jv-sidebar-backdrop{display:block;position:fixed;inset:0;z-index:99;background:#0006}.jv-sidebar{position:fixed;top:0;left:0;z-index:100;width:280px;min-width:280px;height:100vh;border-right:1px solid var(--jv-color-border);border-bottom:none;box-shadow:var(--jv-shadow-lg);transform:translate(-100%);visibility:hidden;transition:transform .24s cubic-bezier(.4,0,.2,1),visibility .24s}.jv-sidebar.is-mobile-open{transform:translate(0);visibility:visible}.jv-sidebar.is-collapsed{width:280px;min-width:280px}.jv-sidebar-mobile-extras{display:flex;flex-direction:column;gap:var(--jv-spacing-md);padding-top:var(--jv-spacing-md);border-top:1px solid var(--jv-color-border)}.jv-sidebar-mobile-extras .theme-group{display:flex;flex-wrap:wrap;gap:var(--jv-spacing-xs)}.jv-sidebar-mobile-extras .theme-pill{display:inline-flex;align-items:center;gap:var(--jv-spacing-xs);min-height:var(--jv-density-control-height);padding:0 var(--jv-spacing-md);border:1px solid var(--jv-color-border);border-radius:999px;background:var(--jv-color-surface-muted);color:var(--jv-color-foreground);cursor:pointer;font:inherit}.jv-sidebar-mobile-extras .theme-pill.is-active{border-color:var(--jv-color-primary);background:color-mix(in srgb,var(--jv-color-primary) 14%,var(--jv-color-surface))}.jv-sidebar-mobile-extras .mobile-actions{display:flex;gap:var(--jv-spacing-sm)}.jv-sidebar-mobile-extras .icon-button{display:inline-grid;place-items:center;width:var(--jv-density-control-height);height:var(--jv-density-control-height);border:1px solid var(--jv-color-border);border-radius:999px;background:var(--jv-color-surface-muted);color:var(--jv-color-foreground);cursor:pointer}}\n"] }]
23575
+ `, styles: [".jv-sidebar-backdrop{display:none}.jv-sidebar{display:grid;grid-template-rows:auto 1fr auto;gap:var(--jv-spacing-lg);width:280px;min-width:280px;height:100vh;padding:var(--jv-spacing-lg);overflow-y:auto;border-right:1px solid var(--jv-color-border);background:var(--jv-color-surface);scrollbar-width:none;-ms-overflow-style:none}.jv-sidebar::-webkit-scrollbar{width:0;height:0;display:none}.jv-sidebar.is-collapsed{width:96px;min-width:96px;padding-inline:var(--jv-spacing-md)}.jv-sidebar-footer{display:grid;gap:var(--jv-spacing-md)}.jv-sidebar-mobile-extras{display:none}.jv-sidebar-brand,.jv-sidebar-link,.jv-sidebar-toggle{display:flex;align-items:center;gap:var(--jv-spacing-sm)}.brand-mark{display:inline-grid;place-items:center;width:var(--jv-density-control-height);height:var(--jv-density-control-height);border-radius:var(--jv-radius-md);background:color-mix(in srgb,var(--jv-color-primary) 16%,var(--jv-color-surface));color:var(--jv-color-primary);flex:0 0 auto}.brand-copy{display:grid;gap:.1rem}.brand-copy span{color:var(--jv-color-foreground-muted);font-size:.875rem}.jv-sidebar-item{display:grid;gap:var(--jv-spacing-xs);list-style:none}.jv-sidebar-list{display:grid;gap:var(--jv-spacing-xs);margin:0;padding:0}.jv-sidebar-nav{display:grid;gap:var(--jv-spacing-xs);align-content:start}.jv-sidebar-link,.jv-sidebar-child-link,.jv-sidebar-toggle{width:100%;display:flex;align-items:center;justify-content:space-between;min-height:var(--jv-density-control-height);padding:0 var(--jv-spacing-md);border:1px solid transparent;border-radius:var(--jv-radius-md);background:transparent;color:var(--jv-color-foreground-muted);text-decoration:none;cursor:pointer}.jv-sidebar-link-main{display:inline-flex;align-items:center;gap:var(--jv-spacing-sm)}.jv-sidebar-link:hover,.jv-sidebar-child-link:hover,.jv-sidebar-toggle:hover{background:var(--jv-color-surface-muted);color:var(--jv-color-foreground)}.jv-sidebar-link.is-active,.jv-sidebar-child-link.is-active{border-color:color-mix(in srgb,var(--jv-color-primary) 25%,var(--jv-color-border));background:color-mix(in srgb,var(--jv-color-primary) 12%,var(--jv-color-surface));color:var(--jv-color-foreground)}.jv-sidebar-children{display:grid;gap:var(--jv-spacing-xs);margin-left:var(--jv-spacing-xl);padding-left:var(--jv-spacing-sm);border-left:1px solid var(--jv-color-border)}.jv-sidebar-child-link{display:flex;align-items:center;min-height:calc(var(--jv-density-control-height) - .25rem);font-size:.9rem}.jv-sidebar-toggle{width:100%;background:var(--jv-color-surface-muted);cursor:pointer}@media(max-width:900px){.jv-sidebar-backdrop{display:block;position:fixed;inset:0;z-index:99;background:#0006}.jv-sidebar{position:fixed;top:0;left:0;z-index:100;width:280px;min-width:280px;height:100vh;border-right:1px solid var(--jv-color-border);border-bottom:none;box-shadow:var(--jv-shadow-lg);transform:translate(-100%);visibility:hidden;transition:transform .24s cubic-bezier(.4,0,.2,1),visibility .24s}.jv-sidebar.is-mobile-open{transform:translate(0);visibility:visible}.jv-sidebar.is-collapsed{width:280px;min-width:280px}.jv-sidebar-mobile-extras{display:flex;flex-direction:column;gap:var(--jv-spacing-md);padding-top:var(--jv-spacing-md);border-top:1px solid var(--jv-color-border)}.jv-sidebar-mobile-extras .theme-group{display:flex;flex-wrap:wrap;gap:var(--jv-spacing-xs)}.jv-sidebar-mobile-extras .theme-pill{display:inline-flex;align-items:center;gap:var(--jv-spacing-xs);min-height:var(--jv-density-control-height);padding:0 var(--jv-spacing-md);border:1px solid var(--jv-color-border);border-radius:999px;background:var(--jv-color-surface-muted);color:var(--jv-color-foreground);cursor:pointer;font:inherit}.jv-sidebar-mobile-extras .theme-pill.is-active{border-color:var(--jv-color-primary);background:color-mix(in srgb,var(--jv-color-primary) 14%,var(--jv-color-surface))}.jv-sidebar-mobile-extras .mobile-actions{display:flex;gap:var(--jv-spacing-sm)}.jv-sidebar-mobile-extras .icon-button{display:inline-flex;align-items:center;gap:var(--jv-spacing-xs);min-width:var(--jv-density-control-height);height:var(--jv-density-control-height);padding:0 var(--jv-spacing-sm);border:1px solid var(--jv-color-border);border-radius:999px;background:var(--jv-color-surface-muted);color:var(--jv-color-foreground);cursor:pointer}}\n"] }]
23347
23576
  }], propDecorators: { title: [{ type: i0.Input, args: [{ isSignal: true, alias: "title", required: true }] }], subtitle: [{ type: i0.Input, args: [{ isSignal: true, alias: "subtitle", required: false }] }], brandIcon: [{ type: i0.Input, args: [{ isSignal: true, alias: "brandIcon", required: false }] }], items: [{ type: i0.Input, args: [{ isSignal: true, alias: "items", required: false }] }], collapsed: [{ type: i0.Input, args: [{ isSignal: true, alias: "collapsed", required: false }] }], mobileOpen: [{ type: i0.Input, args: [{ isSignal: true, alias: "mobileOpen", required: false }] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabel", required: false }] }], currentTheme: [{ type: i0.Input, args: [{ isSignal: true, alias: "currentTheme", required: false }] }], themeOptions: [{ type: i0.Input, args: [{ isSignal: true, alias: "themeOptions", required: false }] }], topbarActions: [{ type: i0.Input, args: [{ isSignal: true, alias: "topbarActions", required: false }] }], showThemeSelector: [{ type: i0.Input, args: [{ isSignal: true, alias: "showThemeSelector", required: false }] }], collapseToggle: [{ type: i0.Output, args: ["collapseToggle"] }], mobileClose: [{ type: i0.Output, args: ["mobileClose"] }], themeChange: [{ type: i0.Output, args: ["themeChange"] }], actionClick: [{ type: i0.Output, args: ["actionClick"] }] } });
23348
23577
 
23349
23578
  class JvTopbarComponent {
@@ -23397,17 +23626,18 @@ class JvTopbarComponent {
23397
23626
  }
23398
23627
 
23399
23628
  @for (action of visibleActions(); track action.id) {
23400
- <button type="button" class="icon-button" [attr.aria-label]="action.label" (click)="actionClick.emit(action.id)">
23629
+ <button type="button" class="icon-button" [attr.aria-label]="action.label" [attr.title]="action.label" [jvTooltip]="action.label" (click)="actionClick.emit(action.id)">
23401
23630
  <jv-icon [name]="action.icon" [size]="18" />
23631
+ <span class="action-label">{{ action.label }}</span>
23402
23632
  </button>
23403
23633
  }
23404
23634
  </div>
23405
23635
  </header>
23406
- `, isInline: true, styles: [".jv-topbar{display:flex;align-items:center;justify-content:space-between;gap:var(--jv-spacing-md);padding:var(--jv-spacing-md) var(--jv-spacing-lg);border-bottom:1px solid var(--jv-color-border);background:var(--jv-color-surface)}.jv-topbar-start,.jv-topbar-end,.theme-group{display:flex;align-items:center;gap:var(--jv-spacing-sm)}.jv-topbar-end{flex-wrap:wrap;justify-content:flex-end}.title-block{display:grid;gap:.1rem}.title-block span{color:var(--jv-color-foreground-muted);font-size:.875rem}.icon-button,.theme-pill{border:1px solid var(--jv-color-border);background:var(--jv-color-surface-muted);color:var(--jv-color-foreground)}.icon-button{display:inline-grid;place-items:center;width:var(--jv-density-control-height);height:var(--jv-density-control-height);border-radius:999px}.theme-pill{display:inline-flex;align-items:center;gap:var(--jv-spacing-xs);min-height:var(--jv-density-control-height);padding:0 var(--jv-spacing-md);border-radius:999px}.theme-pill.is-active{border-color:var(--jv-color-primary);background:color-mix(in srgb,var(--jv-color-primary) 14%,var(--jv-color-surface))}@media(max-width:900px){.jv-topbar{align-items:center;flex-direction:row;justify-content:flex-start}.jv-topbar-end{display:none}}\n"], dependencies: [{ kind: "component", type: JvIconComponent, selector: "jv-icon", inputs: ["name", "size", "strokeWidth", "decorative", "ariaLabel"] }] });
23636
+ `, isInline: true, styles: [".jv-topbar{display:flex;align-items:center;justify-content:space-between;gap:var(--jv-spacing-md);padding:var(--jv-spacing-md) var(--jv-spacing-lg);border-bottom:1px solid var(--jv-color-border);background:var(--jv-color-surface)}.jv-topbar-start,.jv-topbar-end,.theme-group{display:flex;align-items:center;gap:var(--jv-spacing-sm)}.jv-topbar-end{flex-wrap:wrap;justify-content:flex-end}.title-block{display:grid;gap:.1rem}.title-block span{color:var(--jv-color-foreground-muted);font-size:.875rem}.icon-button,.theme-pill{border:1px solid var(--jv-color-border);background:var(--jv-color-surface-muted);color:var(--jv-color-foreground)}.icon-button{display:inline-flex;align-items:center;justify-content:center;gap:var(--jv-spacing-xs);min-width:var(--jv-density-control-height);height:var(--jv-density-control-height);padding:0 var(--jv-spacing-sm);border-radius:999px;cursor:pointer}.theme-pill{display:inline-flex;align-items:center;gap:var(--jv-spacing-xs);min-height:var(--jv-density-control-height);padding:0 var(--jv-spacing-md);border-radius:999px}.theme-pill.is-active{border-color:var(--jv-color-primary);background:color-mix(in srgb,var(--jv-color-primary) 14%,var(--jv-color-surface))}@media(max-width:900px){.jv-topbar{align-items:center;flex-direction:row;justify-content:flex-start}.jv-topbar-end{display:none}}\n"], dependencies: [{ kind: "component", type: JvIconComponent, selector: "jv-icon", inputs: ["name", "size", "strokeWidth", "decorative", "ariaLabel"] }, { kind: "directive", type: JvTooltipDirective, selector: "[jvTooltip]", inputs: ["jvTooltip", "jvTooltipPosition"] }] });
23407
23637
  }
23408
23638
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: JvTopbarComponent, decorators: [{
23409
23639
  type: Component,
23410
- args: [{ selector: 'jv-topbar', standalone: true, imports: [JvIconComponent], template: `
23640
+ args: [{ selector: 'jv-topbar', standalone: true, imports: [JvIconComponent, JvTooltipDirective], template: `
23411
23641
  <header class="jv-topbar">
23412
23642
  <div class="jv-topbar-start">
23413
23643
  <button type="button" class="icon-button" (click)="menuClick.emit()" aria-label="Toggle navigation menu">
@@ -23446,24 +23676,272 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImpo
23446
23676
  }
23447
23677
 
23448
23678
  @for (action of visibleActions(); track action.id) {
23449
- <button type="button" class="icon-button" [attr.aria-label]="action.label" (click)="actionClick.emit(action.id)">
23679
+ <button type="button" class="icon-button" [attr.aria-label]="action.label" [attr.title]="action.label" [jvTooltip]="action.label" (click)="actionClick.emit(action.id)">
23450
23680
  <jv-icon [name]="action.icon" [size]="18" />
23681
+ <span class="action-label">{{ action.label }}</span>
23451
23682
  </button>
23452
23683
  }
23453
23684
  </div>
23454
23685
  </header>
23455
- `, styles: [".jv-topbar{display:flex;align-items:center;justify-content:space-between;gap:var(--jv-spacing-md);padding:var(--jv-spacing-md) var(--jv-spacing-lg);border-bottom:1px solid var(--jv-color-border);background:var(--jv-color-surface)}.jv-topbar-start,.jv-topbar-end,.theme-group{display:flex;align-items:center;gap:var(--jv-spacing-sm)}.jv-topbar-end{flex-wrap:wrap;justify-content:flex-end}.title-block{display:grid;gap:.1rem}.title-block span{color:var(--jv-color-foreground-muted);font-size:.875rem}.icon-button,.theme-pill{border:1px solid var(--jv-color-border);background:var(--jv-color-surface-muted);color:var(--jv-color-foreground)}.icon-button{display:inline-grid;place-items:center;width:var(--jv-density-control-height);height:var(--jv-density-control-height);border-radius:999px}.theme-pill{display:inline-flex;align-items:center;gap:var(--jv-spacing-xs);min-height:var(--jv-density-control-height);padding:0 var(--jv-spacing-md);border-radius:999px}.theme-pill.is-active{border-color:var(--jv-color-primary);background:color-mix(in srgb,var(--jv-color-primary) 14%,var(--jv-color-surface))}@media(max-width:900px){.jv-topbar{align-items:center;flex-direction:row;justify-content:flex-start}.jv-topbar-end{display:none}}\n"] }]
23686
+ `, styles: [".jv-topbar{display:flex;align-items:center;justify-content:space-between;gap:var(--jv-spacing-md);padding:var(--jv-spacing-md) var(--jv-spacing-lg);border-bottom:1px solid var(--jv-color-border);background:var(--jv-color-surface)}.jv-topbar-start,.jv-topbar-end,.theme-group{display:flex;align-items:center;gap:var(--jv-spacing-sm)}.jv-topbar-end{flex-wrap:wrap;justify-content:flex-end}.title-block{display:grid;gap:.1rem}.title-block span{color:var(--jv-color-foreground-muted);font-size:.875rem}.icon-button,.theme-pill{border:1px solid var(--jv-color-border);background:var(--jv-color-surface-muted);color:var(--jv-color-foreground)}.icon-button{display:inline-flex;align-items:center;justify-content:center;gap:var(--jv-spacing-xs);min-width:var(--jv-density-control-height);height:var(--jv-density-control-height);padding:0 var(--jv-spacing-sm);border-radius:999px;cursor:pointer}.theme-pill{display:inline-flex;align-items:center;gap:var(--jv-spacing-xs);min-height:var(--jv-density-control-height);padding:0 var(--jv-spacing-md);border-radius:999px}.theme-pill.is-active{border-color:var(--jv-color-primary);background:color-mix(in srgb,var(--jv-color-primary) 14%,var(--jv-color-surface))}@media(max-width:900px){.jv-topbar{align-items:center;flex-direction:row;justify-content:flex-start}.jv-topbar-end{display:none}}\n"] }]
23456
23687
  }], propDecorators: { title: [{ type: i0.Input, args: [{ isSignal: true, alias: "title", required: false }] }], subtitle: [{ type: i0.Input, args: [{ isSignal: true, alias: "subtitle", required: false }] }], currentTheme: [{ type: i0.Input, args: [{ isSignal: true, alias: "currentTheme", required: false }] }], themeOptions: [{ type: i0.Input, args: [{ isSignal: true, alias: "themeOptions", required: false }] }], actions: [{ type: i0.Input, args: [{ isSignal: true, alias: "actions", required: false }] }], showThemeSelector: [{ type: i0.Input, args: [{ isSignal: true, alias: "showThemeSelector", required: false }] }], menuClick: [{ type: i0.Output, args: ["menuClick"] }], themeChange: [{ type: i0.Output, args: ["themeChange"] }], actionClick: [{ type: i0.Output, args: ["actionClick"] }] } });
23457
23688
 
23458
- class JvDashboardShellComponent {
23689
+ class JvTopbarMenuComponent {
23459
23690
  title = input.required(...(ngDevMode ? [{ debugName: "title" }] : /* istanbul ignore next */ []));
23460
- sidebarSubtitle = input('', ...(ngDevMode ? [{ debugName: "sidebarSubtitle" }] : /* istanbul ignore next */ []));
23691
+ subtitle = input('', ...(ngDevMode ? [{ debugName: "subtitle" }] : /* istanbul ignore next */ []));
23461
23692
  brandIcon = input('monitor', ...(ngDevMode ? [{ debugName: "brandIcon" }] : /* istanbul ignore next */ []));
23462
- navItems = input([], ...(ngDevMode ? [{ debugName: "navItems" }] : /* istanbul ignore next */ []));
23463
- sidebarCollapsed = input(false, ...(ngDevMode ? [{ debugName: "sidebarCollapsed" }] : /* istanbul ignore next */ []));
23464
- mobileSidebarOpen = input(false, ...(ngDevMode ? [{ debugName: "mobileSidebarOpen" }] : /* istanbul ignore next */ []));
23465
- topbarTitle = input('', ...(ngDevMode ? [{ debugName: "topbarTitle" }] : /* istanbul ignore next */ []));
23466
- topbarSubtitle = input('', ...(ngDevMode ? [{ debugName: "topbarSubtitle" }] : /* istanbul ignore next */ []));
23693
+ items = input([], ...(ngDevMode ? [{ debugName: "items" }] : /* istanbul ignore next */ []));
23694
+ mobileOpen = input(false, ...(ngDevMode ? [{ debugName: "mobileOpen" }] : /* istanbul ignore next */ []));
23695
+ ariaLabel = input('Topbar navigation', ...(ngDevMode ? [{ debugName: "ariaLabel" }] : /* istanbul ignore next */ []));
23696
+ currentTheme = input('light', ...(ngDevMode ? [{ debugName: "currentTheme" }] : /* istanbul ignore next */ []));
23697
+ themeOptions = input([], ...(ngDevMode ? [{ debugName: "themeOptions" }] : /* istanbul ignore next */ []));
23698
+ actions = input([], ...(ngDevMode ? [{ debugName: "actions" }] : /* istanbul ignore next */ []));
23699
+ showThemeSelector = input(true, ...(ngDevMode ? [{ debugName: "showThemeSelector" }] : /* istanbul ignore next */ []));
23700
+ menuClick = output();
23701
+ mobileClose = output();
23702
+ themeChange = output();
23703
+ actionClick = output();
23704
+ navigation = new JvNavigationState(this.items, inject(Router), false);
23705
+ normalizedItems = this.navigation.normalizedItems;
23706
+ isExpanded(id) {
23707
+ return this.navigation.isExpanded(id);
23708
+ }
23709
+ toggleSection(id) {
23710
+ this.navigation.toggleSection(id, true);
23711
+ }
23712
+ isHashLink(href) {
23713
+ return this.navigation.isHashLink(href);
23714
+ }
23715
+ navigateTo(href, parentId) {
23716
+ this.navigation.navigateTo(href);
23717
+ if (parentId) {
23718
+ this.navigation.closeSection(parentId);
23719
+ }
23720
+ this.mobileClose.emit();
23721
+ }
23722
+ closeSection(id) {
23723
+ this.navigation.closeSection(id);
23724
+ }
23725
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: JvTopbarMenuComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
23726
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.17", type: JvTopbarMenuComponent, isStandalone: true, selector: "jv-topbar-menu", inputs: { title: { classPropertyName: "title", publicName: "title", isSignal: true, isRequired: true, transformFunction: null }, subtitle: { classPropertyName: "subtitle", publicName: "subtitle", isSignal: true, isRequired: false, transformFunction: null }, brandIcon: { classPropertyName: "brandIcon", publicName: "brandIcon", isSignal: true, isRequired: false, transformFunction: null }, items: { classPropertyName: "items", publicName: "items", isSignal: true, isRequired: false, transformFunction: null }, mobileOpen: { classPropertyName: "mobileOpen", publicName: "mobileOpen", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: false, transformFunction: null }, currentTheme: { classPropertyName: "currentTheme", publicName: "currentTheme", isSignal: true, isRequired: false, transformFunction: null }, themeOptions: { classPropertyName: "themeOptions", publicName: "themeOptions", isSignal: true, isRequired: false, transformFunction: null }, actions: { classPropertyName: "actions", publicName: "actions", isSignal: true, isRequired: false, transformFunction: null }, showThemeSelector: { classPropertyName: "showThemeSelector", publicName: "showThemeSelector", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { menuClick: "menuClick", mobileClose: "mobileClose", themeChange: "themeChange", actionClick: "actionClick" }, ngImport: i0, template: `
23727
+ @if (mobileOpen()) {
23728
+ <div class="jv-topbar-menu-backdrop" tabindex="-1" (click)="mobileClose.emit()" (keydown.escape)="mobileClose.emit()"></div>
23729
+ }
23730
+
23731
+ <header class="jv-topbar-menu" [class.is-mobile-open]="mobileOpen()">
23732
+ <div class="jv-topbar-menu-brand">
23733
+ <div class="brand-mark">
23734
+ <jv-icon [name]="brandIcon()" [size]="18" />
23735
+ </div>
23736
+ <div class="brand-copy">
23737
+ <strong>{{ title() }}</strong>
23738
+ @if (subtitle()) {
23739
+ <span>{{ subtitle() }}</span>
23740
+ }
23741
+ </div>
23742
+ </div>
23743
+
23744
+ <button type="button" class="mobile-menu-button" aria-label="Toggle navigation menu" (click)="menuClick.emit()">
23745
+ <jv-icon name="menu" [size]="18" />
23746
+ </button>
23747
+
23748
+ <nav class="jv-topbar-menu-nav" [attr.aria-label]="ariaLabel()">
23749
+ <ul class="jv-topbar-menu-list">
23750
+ @for (item of normalizedItems(); track item.id) {
23751
+ <li class="jv-topbar-menu-item">
23752
+ @if (item.children.length > 0) {
23753
+ <button
23754
+ type="button"
23755
+ class="jv-topbar-menu-link"
23756
+ [class.is-active]="item.active"
23757
+ [attr.aria-expanded]="isExpanded(item.id) ? 'true' : 'false'"
23758
+ [attr.aria-controls]="'jv-topbar-menu-section-' + item.id"
23759
+ (click)="toggleSection(item.id)"
23760
+ >
23761
+ @if (item.icon) {
23762
+ <jv-icon [name]="item.icon" [size]="17" />
23763
+ }
23764
+ <span>{{ item.label }}</span>
23765
+ <jv-icon [name]="isExpanded(item.id) ? 'chevron-up' : 'chevron-down'" [size]="15" />
23766
+ </button>
23767
+
23768
+ @if (isExpanded(item.id)) {
23769
+ <div class="jv-topbar-menu-dropdown" [id]="'jv-topbar-menu-section-' + item.id">
23770
+ @for (child of item.children; track child.id) {
23771
+ @if (isHashLink(child.href)) {
23772
+ <button type="button" class="jv-topbar-menu-child" [class.is-active]="child.active" (click)="navigateTo(child.href, item.id)">
23773
+ {{ child.label }}
23774
+ </button>
23775
+ } @else {
23776
+ <a class="jv-topbar-menu-child" [class.is-active]="child.active" [routerLink]="child.href" [attr.aria-current]="child.active ? 'page' : null" (click)="closeSection(item.id); mobileClose.emit()">
23777
+ {{ child.label }}
23778
+ </a>
23779
+ }
23780
+ }
23781
+ </div>
23782
+ }
23783
+ } @else {
23784
+ @if (isHashLink(item.href)) {
23785
+ <button type="button" class="jv-topbar-menu-link" [class.is-active]="item.active" (click)="navigateTo(item.href)">
23786
+ @if (item.icon) {
23787
+ <jv-icon [name]="item.icon" [size]="17" />
23788
+ }
23789
+ <span>{{ item.label }}</span>
23790
+ </button>
23791
+ } @else {
23792
+ <a class="jv-topbar-menu-link" [class.is-active]="item.active" [routerLink]="item.href" [attr.aria-current]="item.active ? 'page' : null" (click)="mobileClose.emit()">
23793
+ @if (item.icon) {
23794
+ <jv-icon [name]="item.icon" [size]="17" />
23795
+ }
23796
+ <span>{{ item.label }}</span>
23797
+ </a>
23798
+ }
23799
+ }
23800
+ </li>
23801
+ }
23802
+ </ul>
23803
+ </nav>
23804
+
23805
+ <div class="jv-topbar-menu-actions">
23806
+ @if (showThemeSelector()) {
23807
+ <div class="theme-group" aria-label="Theme selector">
23808
+ @for (option of themeOptions(); track option.value) {
23809
+ <button type="button" class="theme-pill" [class.is-active]="currentTheme() === option.value" (click)="themeChange.emit(option.value)">
23810
+ @if (option.icon) {
23811
+ <jv-icon [name]="option.icon" [size]="16" />
23812
+ }
23813
+ <span>{{ option.label }}</span>
23814
+ </button>
23815
+ }
23816
+ </div>
23817
+ }
23818
+
23819
+ @for (action of actions(); track action.id) {
23820
+ <button type="button" class="icon-button" [attr.aria-label]="action.label" [attr.title]="action.label" [jvTooltip]="action.label" (click)="actionClick.emit(action.id)">
23821
+ <jv-icon [name]="action.icon" [size]="18" />
23822
+ <span class="action-label">{{ action.label }}</span>
23823
+ </button>
23824
+ }
23825
+ </div>
23826
+ </header>
23827
+ `, isInline: true, styles: [":host{display:block;position:relative;z-index:20}.jv-topbar-menu,.jv-topbar-menu-brand,.jv-topbar-menu-list,.jv-topbar-menu-actions,.theme-group{display:flex;align-items:center}.jv-topbar-menu{gap:var(--jv-spacing-lg);min-height:4.5rem;padding:var(--jv-spacing-md) var(--jv-spacing-xl);min-width:0;border-bottom:1px solid var(--jv-color-border);background:var(--jv-color-surface)}.jv-topbar-menu-brand{gap:var(--jv-spacing-sm);flex:0 0 auto}.brand-mark{display:inline-grid;place-items:center;width:var(--jv-density-control-height);height:var(--jv-density-control-height);border-radius:var(--jv-radius-md);background:color-mix(in srgb,var(--jv-color-primary) 16%,var(--jv-color-surface));color:var(--jv-color-primary)}.brand-copy{display:grid;gap:.1rem}.brand-copy span{color:var(--jv-color-foreground-muted);font-size:.8125rem}.jv-topbar-menu-nav{min-width:0;flex:1 1 0}.jv-topbar-menu-list{gap:var(--jv-spacing-xs);margin:0;padding:0;list-style:none;min-width:0;flex-wrap:wrap}.jv-topbar-menu-item{position:relative;min-width:0;flex:0 1 auto}.jv-topbar-menu-link,.jv-topbar-menu-child,.icon-button,.theme-pill,.mobile-menu-button{border:1px solid transparent;background:transparent;color:var(--jv-color-foreground-muted);cursor:pointer;font:inherit;text-decoration:none}.jv-topbar-menu-link{display:inline-flex;align-items:center;gap:var(--jv-spacing-xs);min-height:var(--jv-density-control-height);min-width:0;width:100%;max-width:15rem;padding:0 var(--jv-spacing-sm);border-radius:var(--jv-radius-md);white-space:nowrap}.jv-topbar-menu-link span{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.jv-topbar-menu-link:hover,.jv-topbar-menu-child:hover,.jv-topbar-menu-link.is-active,.jv-topbar-menu-child.is-active{background:color-mix(in srgb,var(--jv-color-primary) 12%,var(--jv-color-surface));color:var(--jv-color-foreground)}.jv-topbar-menu-dropdown{position:absolute;top:calc(100% + var(--jv-spacing-xs));left:0;display:grid;min-width:14rem;max-height:min(70vh,32rem);gap:var(--jv-spacing-xs);padding:var(--jv-spacing-sm);overflow-y:auto;border:1px solid var(--jv-color-border);border-radius:var(--jv-radius-md);background:var(--jv-color-surface);box-shadow:var(--jv-shadow-lg);z-index:30}.jv-topbar-menu-child{display:block;padding:var(--jv-spacing-sm) var(--jv-spacing-md);border-radius:var(--jv-radius-sm);text-align:left;white-space:nowrap}.jv-topbar-menu-actions{justify-content:flex-end;gap:var(--jv-spacing-sm);flex:0 0 auto}.theme-group{gap:var(--jv-spacing-xs)}.icon-button,.mobile-menu-button{display:inline-flex;align-items:center;justify-content:center;gap:var(--jv-spacing-xs);min-width:var(--jv-density-control-height);height:var(--jv-density-control-height);padding:0 var(--jv-spacing-sm);border-color:var(--jv-color-border);border-radius:999px;background:var(--jv-color-surface-muted);color:var(--jv-color-foreground)}.icon-button{cursor:pointer}.theme-pill{display:inline-flex;align-items:center;gap:var(--jv-spacing-xs);min-height:var(--jv-density-control-height);padding:0 var(--jv-spacing-sm);border-color:var(--jv-color-border);border-radius:999px;background:var(--jv-color-surface-muted);color:var(--jv-color-foreground);font-size:.8125rem}.theme-pill.is-active{border-color:var(--jv-color-primary);background:color-mix(in srgb,var(--jv-color-primary) 14%,var(--jv-color-surface))}.mobile-menu-button,.jv-topbar-menu-backdrop{display:none}@media(max-width:1100px){.jv-topbar-menu{gap:var(--jv-spacing-md);padding-inline:var(--jv-spacing-lg)}.jv-topbar-menu-link{max-width:12rem;padding-inline:var(--jv-spacing-xs)}}@media(max-width:1400px){.jv-topbar-menu-actions .theme-group{display:none}}@media(max-width:900px){.jv-topbar-menu-backdrop{display:block;position:fixed;inset:0;z-index:19;background:#0006}.jv-topbar-menu{position:relative;justify-content:space-between;min-height:4rem;padding:var(--jv-spacing-sm) var(--jv-spacing-md)}.mobile-menu-button{display:inline-grid;order:3}.jv-topbar-menu-nav{position:fixed;top:4rem;right:0;bottom:0;z-index:21;width:min(22rem,88vw);padding:var(--jv-spacing-lg);overflow-y:auto;border-left:1px solid var(--jv-color-border);background:var(--jv-color-surface);box-shadow:var(--jv-shadow-lg);transform:translate(100%);visibility:hidden;transition:transform .24s ease,visibility .24s}.jv-topbar-menu.is-mobile-open .jv-topbar-menu-nav{transform:translate(0);visibility:visible}.jv-topbar-menu-list{display:grid;align-items:stretch}.jv-topbar-menu-link{width:100%;justify-content:flex-start;padding-inline:var(--jv-spacing-md)}.jv-topbar-menu-dropdown{position:static;min-width:0;margin-left:var(--jv-spacing-md);box-shadow:none}.jv-topbar-menu-actions{margin-left:auto}}\n"], dependencies: [{ kind: "component", type: JvIconComponent, selector: "jv-icon", inputs: ["name", "size", "strokeWidth", "decorative", "ariaLabel"] }, { kind: "directive", type: RouterLink, selector: "[routerLink]", inputs: ["target", "queryParams", "fragment", "queryParamsHandling", "state", "info", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "routerLink"] }, { kind: "directive", type: JvTooltipDirective, selector: "[jvTooltip]", inputs: ["jvTooltip", "jvTooltipPosition"] }] });
23828
+ }
23829
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: JvTopbarMenuComponent, decorators: [{
23830
+ type: Component,
23831
+ args: [{ selector: 'jv-topbar-menu', standalone: true, imports: [JvIconComponent, RouterLink, JvTooltipDirective], template: `
23832
+ @if (mobileOpen()) {
23833
+ <div class="jv-topbar-menu-backdrop" tabindex="-1" (click)="mobileClose.emit()" (keydown.escape)="mobileClose.emit()"></div>
23834
+ }
23835
+
23836
+ <header class="jv-topbar-menu" [class.is-mobile-open]="mobileOpen()">
23837
+ <div class="jv-topbar-menu-brand">
23838
+ <div class="brand-mark">
23839
+ <jv-icon [name]="brandIcon()" [size]="18" />
23840
+ </div>
23841
+ <div class="brand-copy">
23842
+ <strong>{{ title() }}</strong>
23843
+ @if (subtitle()) {
23844
+ <span>{{ subtitle() }}</span>
23845
+ }
23846
+ </div>
23847
+ </div>
23848
+
23849
+ <button type="button" class="mobile-menu-button" aria-label="Toggle navigation menu" (click)="menuClick.emit()">
23850
+ <jv-icon name="menu" [size]="18" />
23851
+ </button>
23852
+
23853
+ <nav class="jv-topbar-menu-nav" [attr.aria-label]="ariaLabel()">
23854
+ <ul class="jv-topbar-menu-list">
23855
+ @for (item of normalizedItems(); track item.id) {
23856
+ <li class="jv-topbar-menu-item">
23857
+ @if (item.children.length > 0) {
23858
+ <button
23859
+ type="button"
23860
+ class="jv-topbar-menu-link"
23861
+ [class.is-active]="item.active"
23862
+ [attr.aria-expanded]="isExpanded(item.id) ? 'true' : 'false'"
23863
+ [attr.aria-controls]="'jv-topbar-menu-section-' + item.id"
23864
+ (click)="toggleSection(item.id)"
23865
+ >
23866
+ @if (item.icon) {
23867
+ <jv-icon [name]="item.icon" [size]="17" />
23868
+ }
23869
+ <span>{{ item.label }}</span>
23870
+ <jv-icon [name]="isExpanded(item.id) ? 'chevron-up' : 'chevron-down'" [size]="15" />
23871
+ </button>
23872
+
23873
+ @if (isExpanded(item.id)) {
23874
+ <div class="jv-topbar-menu-dropdown" [id]="'jv-topbar-menu-section-' + item.id">
23875
+ @for (child of item.children; track child.id) {
23876
+ @if (isHashLink(child.href)) {
23877
+ <button type="button" class="jv-topbar-menu-child" [class.is-active]="child.active" (click)="navigateTo(child.href, item.id)">
23878
+ {{ child.label }}
23879
+ </button>
23880
+ } @else {
23881
+ <a class="jv-topbar-menu-child" [class.is-active]="child.active" [routerLink]="child.href" [attr.aria-current]="child.active ? 'page' : null" (click)="closeSection(item.id); mobileClose.emit()">
23882
+ {{ child.label }}
23883
+ </a>
23884
+ }
23885
+ }
23886
+ </div>
23887
+ }
23888
+ } @else {
23889
+ @if (isHashLink(item.href)) {
23890
+ <button type="button" class="jv-topbar-menu-link" [class.is-active]="item.active" (click)="navigateTo(item.href)">
23891
+ @if (item.icon) {
23892
+ <jv-icon [name]="item.icon" [size]="17" />
23893
+ }
23894
+ <span>{{ item.label }}</span>
23895
+ </button>
23896
+ } @else {
23897
+ <a class="jv-topbar-menu-link" [class.is-active]="item.active" [routerLink]="item.href" [attr.aria-current]="item.active ? 'page' : null" (click)="mobileClose.emit()">
23898
+ @if (item.icon) {
23899
+ <jv-icon [name]="item.icon" [size]="17" />
23900
+ }
23901
+ <span>{{ item.label }}</span>
23902
+ </a>
23903
+ }
23904
+ }
23905
+ </li>
23906
+ }
23907
+ </ul>
23908
+ </nav>
23909
+
23910
+ <div class="jv-topbar-menu-actions">
23911
+ @if (showThemeSelector()) {
23912
+ <div class="theme-group" aria-label="Theme selector">
23913
+ @for (option of themeOptions(); track option.value) {
23914
+ <button type="button" class="theme-pill" [class.is-active]="currentTheme() === option.value" (click)="themeChange.emit(option.value)">
23915
+ @if (option.icon) {
23916
+ <jv-icon [name]="option.icon" [size]="16" />
23917
+ }
23918
+ <span>{{ option.label }}</span>
23919
+ </button>
23920
+ }
23921
+ </div>
23922
+ }
23923
+
23924
+ @for (action of actions(); track action.id) {
23925
+ <button type="button" class="icon-button" [attr.aria-label]="action.label" [attr.title]="action.label" [jvTooltip]="action.label" (click)="actionClick.emit(action.id)">
23926
+ <jv-icon [name]="action.icon" [size]="18" />
23927
+ <span class="action-label">{{ action.label }}</span>
23928
+ </button>
23929
+ }
23930
+ </div>
23931
+ </header>
23932
+ `, styles: [":host{display:block;position:relative;z-index:20}.jv-topbar-menu,.jv-topbar-menu-brand,.jv-topbar-menu-list,.jv-topbar-menu-actions,.theme-group{display:flex;align-items:center}.jv-topbar-menu{gap:var(--jv-spacing-lg);min-height:4.5rem;padding:var(--jv-spacing-md) var(--jv-spacing-xl);min-width:0;border-bottom:1px solid var(--jv-color-border);background:var(--jv-color-surface)}.jv-topbar-menu-brand{gap:var(--jv-spacing-sm);flex:0 0 auto}.brand-mark{display:inline-grid;place-items:center;width:var(--jv-density-control-height);height:var(--jv-density-control-height);border-radius:var(--jv-radius-md);background:color-mix(in srgb,var(--jv-color-primary) 16%,var(--jv-color-surface));color:var(--jv-color-primary)}.brand-copy{display:grid;gap:.1rem}.brand-copy span{color:var(--jv-color-foreground-muted);font-size:.8125rem}.jv-topbar-menu-nav{min-width:0;flex:1 1 0}.jv-topbar-menu-list{gap:var(--jv-spacing-xs);margin:0;padding:0;list-style:none;min-width:0;flex-wrap:wrap}.jv-topbar-menu-item{position:relative;min-width:0;flex:0 1 auto}.jv-topbar-menu-link,.jv-topbar-menu-child,.icon-button,.theme-pill,.mobile-menu-button{border:1px solid transparent;background:transparent;color:var(--jv-color-foreground-muted);cursor:pointer;font:inherit;text-decoration:none}.jv-topbar-menu-link{display:inline-flex;align-items:center;gap:var(--jv-spacing-xs);min-height:var(--jv-density-control-height);min-width:0;width:100%;max-width:15rem;padding:0 var(--jv-spacing-sm);border-radius:var(--jv-radius-md);white-space:nowrap}.jv-topbar-menu-link span{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.jv-topbar-menu-link:hover,.jv-topbar-menu-child:hover,.jv-topbar-menu-link.is-active,.jv-topbar-menu-child.is-active{background:color-mix(in srgb,var(--jv-color-primary) 12%,var(--jv-color-surface));color:var(--jv-color-foreground)}.jv-topbar-menu-dropdown{position:absolute;top:calc(100% + var(--jv-spacing-xs));left:0;display:grid;min-width:14rem;max-height:min(70vh,32rem);gap:var(--jv-spacing-xs);padding:var(--jv-spacing-sm);overflow-y:auto;border:1px solid var(--jv-color-border);border-radius:var(--jv-radius-md);background:var(--jv-color-surface);box-shadow:var(--jv-shadow-lg);z-index:30}.jv-topbar-menu-child{display:block;padding:var(--jv-spacing-sm) var(--jv-spacing-md);border-radius:var(--jv-radius-sm);text-align:left;white-space:nowrap}.jv-topbar-menu-actions{justify-content:flex-end;gap:var(--jv-spacing-sm);flex:0 0 auto}.theme-group{gap:var(--jv-spacing-xs)}.icon-button,.mobile-menu-button{display:inline-flex;align-items:center;justify-content:center;gap:var(--jv-spacing-xs);min-width:var(--jv-density-control-height);height:var(--jv-density-control-height);padding:0 var(--jv-spacing-sm);border-color:var(--jv-color-border);border-radius:999px;background:var(--jv-color-surface-muted);color:var(--jv-color-foreground)}.icon-button{cursor:pointer}.theme-pill{display:inline-flex;align-items:center;gap:var(--jv-spacing-xs);min-height:var(--jv-density-control-height);padding:0 var(--jv-spacing-sm);border-color:var(--jv-color-border);border-radius:999px;background:var(--jv-color-surface-muted);color:var(--jv-color-foreground);font-size:.8125rem}.theme-pill.is-active{border-color:var(--jv-color-primary);background:color-mix(in srgb,var(--jv-color-primary) 14%,var(--jv-color-surface))}.mobile-menu-button,.jv-topbar-menu-backdrop{display:none}@media(max-width:1100px){.jv-topbar-menu{gap:var(--jv-spacing-md);padding-inline:var(--jv-spacing-lg)}.jv-topbar-menu-link{max-width:12rem;padding-inline:var(--jv-spacing-xs)}}@media(max-width:1400px){.jv-topbar-menu-actions .theme-group{display:none}}@media(max-width:900px){.jv-topbar-menu-backdrop{display:block;position:fixed;inset:0;z-index:19;background:#0006}.jv-topbar-menu{position:relative;justify-content:space-between;min-height:4rem;padding:var(--jv-spacing-sm) var(--jv-spacing-md)}.mobile-menu-button{display:inline-grid;order:3}.jv-topbar-menu-nav{position:fixed;top:4rem;right:0;bottom:0;z-index:21;width:min(22rem,88vw);padding:var(--jv-spacing-lg);overflow-y:auto;border-left:1px solid var(--jv-color-border);background:var(--jv-color-surface);box-shadow:var(--jv-shadow-lg);transform:translate(100%);visibility:hidden;transition:transform .24s ease,visibility .24s}.jv-topbar-menu.is-mobile-open .jv-topbar-menu-nav{transform:translate(0);visibility:visible}.jv-topbar-menu-list{display:grid;align-items:stretch}.jv-topbar-menu-link{width:100%;justify-content:flex-start;padding-inline:var(--jv-spacing-md)}.jv-topbar-menu-dropdown{position:static;min-width:0;margin-left:var(--jv-spacing-md);box-shadow:none}.jv-topbar-menu-actions{margin-left:auto}}\n"] }]
23933
+ }], propDecorators: { title: [{ type: i0.Input, args: [{ isSignal: true, alias: "title", required: true }] }], subtitle: [{ type: i0.Input, args: [{ isSignal: true, alias: "subtitle", required: false }] }], brandIcon: [{ type: i0.Input, args: [{ isSignal: true, alias: "brandIcon", required: false }] }], items: [{ type: i0.Input, args: [{ isSignal: true, alias: "items", required: false }] }], mobileOpen: [{ type: i0.Input, args: [{ isSignal: true, alias: "mobileOpen", required: false }] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabel", required: false }] }], currentTheme: [{ type: i0.Input, args: [{ isSignal: true, alias: "currentTheme", required: false }] }], themeOptions: [{ type: i0.Input, args: [{ isSignal: true, alias: "themeOptions", required: false }] }], actions: [{ type: i0.Input, args: [{ isSignal: true, alias: "actions", required: false }] }], showThemeSelector: [{ type: i0.Input, args: [{ isSignal: true, alias: "showThemeSelector", required: false }] }], menuClick: [{ type: i0.Output, args: ["menuClick"] }], mobileClose: [{ type: i0.Output, args: ["mobileClose"] }], themeChange: [{ type: i0.Output, args: ["themeChange"] }], actionClick: [{ type: i0.Output, args: ["actionClick"] }] } });
23934
+
23935
+ class JvDashboardShellComponent {
23936
+ title = input.required(...(ngDevMode ? [{ debugName: "title" }] : /* istanbul ignore next */ []));
23937
+ navigationMode = input('sidebar', ...(ngDevMode ? [{ debugName: "navigationMode" }] : /* istanbul ignore next */ []));
23938
+ sidebarSubtitle = input('', ...(ngDevMode ? [{ debugName: "sidebarSubtitle" }] : /* istanbul ignore next */ []));
23939
+ brandIcon = input('monitor', ...(ngDevMode ? [{ debugName: "brandIcon" }] : /* istanbul ignore next */ []));
23940
+ navItems = input([], ...(ngDevMode ? [{ debugName: "navItems" }] : /* istanbul ignore next */ []));
23941
+ sidebarCollapsed = input(false, ...(ngDevMode ? [{ debugName: "sidebarCollapsed" }] : /* istanbul ignore next */ []));
23942
+ mobileSidebarOpen = input(false, ...(ngDevMode ? [{ debugName: "mobileSidebarOpen" }] : /* istanbul ignore next */ []));
23943
+ topbarTitle = input('', ...(ngDevMode ? [{ debugName: "topbarTitle" }] : /* istanbul ignore next */ []));
23944
+ topbarSubtitle = input('', ...(ngDevMode ? [{ debugName: "topbarSubtitle" }] : /* istanbul ignore next */ []));
23467
23945
  currentTheme = input('light', ...(ngDevMode ? [{ debugName: "currentTheme" }] : /* istanbul ignore next */ []));
23468
23946
  themeOptions = input([], ...(ngDevMode ? [{ debugName: "themeOptions" }] : /* istanbul ignore next */ []));
23469
23947
  topbarActions = input([], ...(ngDevMode ? [{ debugName: "topbarActions" }] : /* istanbul ignore next */ []));
@@ -23474,90 +23952,130 @@ class JvDashboardShellComponent {
23474
23952
  themeChange = output();
23475
23953
  topbarActionClick = output();
23476
23954
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: JvDashboardShellComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
23477
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "21.2.17", type: JvDashboardShellComponent, isStandalone: true, selector: "jv-dashboard-shell", inputs: { title: { classPropertyName: "title", publicName: "title", isSignal: true, isRequired: true, transformFunction: null }, sidebarSubtitle: { classPropertyName: "sidebarSubtitle", publicName: "sidebarSubtitle", isSignal: true, isRequired: false, transformFunction: null }, brandIcon: { classPropertyName: "brandIcon", publicName: "brandIcon", isSignal: true, isRequired: false, transformFunction: null }, navItems: { classPropertyName: "navItems", publicName: "navItems", isSignal: true, isRequired: false, transformFunction: null }, sidebarCollapsed: { classPropertyName: "sidebarCollapsed", publicName: "sidebarCollapsed", isSignal: true, isRequired: false, transformFunction: null }, mobileSidebarOpen: { classPropertyName: "mobileSidebarOpen", publicName: "mobileSidebarOpen", isSignal: true, isRequired: false, transformFunction: null }, topbarTitle: { classPropertyName: "topbarTitle", publicName: "topbarTitle", isSignal: true, isRequired: false, transformFunction: null }, topbarSubtitle: { classPropertyName: "topbarSubtitle", publicName: "topbarSubtitle", isSignal: true, isRequired: false, transformFunction: null }, currentTheme: { classPropertyName: "currentTheme", publicName: "currentTheme", isSignal: true, isRequired: false, transformFunction: null }, themeOptions: { classPropertyName: "themeOptions", publicName: "themeOptions", isSignal: true, isRequired: false, transformFunction: null }, topbarActions: { classPropertyName: "topbarActions", publicName: "topbarActions", isSignal: true, isRequired: false, transformFunction: null }, showThemeSelector: { classPropertyName: "showThemeSelector", publicName: "showThemeSelector", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { menuClick: "menuClick", mobileClose: "mobileClose", sidebarCollapseToggle: "sidebarCollapseToggle", themeChange: "themeChange", topbarActionClick: "topbarActionClick" }, ngImport: i0, template: `
23478
- <div class="jv-dashboard-shell" [class.is-sidebar-collapsed]="sidebarCollapsed()">
23479
- <jv-sidebar
23480
- [title]="title()"
23481
- [subtitle]="sidebarSubtitle()"
23482
- [brandIcon]="brandIcon()"
23483
- [items]="navItems()"
23484
- [collapsed]="sidebarCollapsed()"
23485
- [mobileOpen]="mobileSidebarOpen()"
23486
- [currentTheme]="currentTheme()"
23487
- [themeOptions]="themeOptions()"
23488
- [topbarActions]="topbarActions()"
23489
- [showThemeSelector]="showThemeSelector()"
23490
- (collapseToggle)="sidebarCollapseToggle.emit()"
23491
- (mobileClose)="mobileClose.emit()"
23492
- (themeChange)="themeChange.emit($event)"
23493
- (actionClick)="topbarActionClick.emit($event)"
23494
- />
23495
-
23496
- <div class="jv-dashboard-main">
23497
- <jv-topbar
23498
- [title]="topbarTitle()"
23499
- [subtitle]="topbarSubtitle()"
23955
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.17", type: JvDashboardShellComponent, isStandalone: true, selector: "jv-dashboard-shell", inputs: { title: { classPropertyName: "title", publicName: "title", isSignal: true, isRequired: true, transformFunction: null }, navigationMode: { classPropertyName: "navigationMode", publicName: "navigationMode", isSignal: true, isRequired: false, transformFunction: null }, sidebarSubtitle: { classPropertyName: "sidebarSubtitle", publicName: "sidebarSubtitle", isSignal: true, isRequired: false, transformFunction: null }, brandIcon: { classPropertyName: "brandIcon", publicName: "brandIcon", isSignal: true, isRequired: false, transformFunction: null }, navItems: { classPropertyName: "navItems", publicName: "navItems", isSignal: true, isRequired: false, transformFunction: null }, sidebarCollapsed: { classPropertyName: "sidebarCollapsed", publicName: "sidebarCollapsed", isSignal: true, isRequired: false, transformFunction: null }, mobileSidebarOpen: { classPropertyName: "mobileSidebarOpen", publicName: "mobileSidebarOpen", isSignal: true, isRequired: false, transformFunction: null }, topbarTitle: { classPropertyName: "topbarTitle", publicName: "topbarTitle", isSignal: true, isRequired: false, transformFunction: null }, topbarSubtitle: { classPropertyName: "topbarSubtitle", publicName: "topbarSubtitle", isSignal: true, isRequired: false, transformFunction: null }, currentTheme: { classPropertyName: "currentTheme", publicName: "currentTheme", isSignal: true, isRequired: false, transformFunction: null }, themeOptions: { classPropertyName: "themeOptions", publicName: "themeOptions", isSignal: true, isRequired: false, transformFunction: null }, topbarActions: { classPropertyName: "topbarActions", publicName: "topbarActions", isSignal: true, isRequired: false, transformFunction: null }, showThemeSelector: { classPropertyName: "showThemeSelector", publicName: "showThemeSelector", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { menuClick: "menuClick", mobileClose: "mobileClose", sidebarCollapseToggle: "sidebarCollapseToggle", themeChange: "themeChange", topbarActionClick: "topbarActionClick" }, ngImport: i0, template: `
23956
+ <div class="jv-dashboard-shell" [class.is-sidebar-collapsed]="sidebarCollapsed()" [class.is-topbar-navigation]="navigationMode() === 'topbar'">
23957
+ @if (navigationMode() === 'sidebar') {
23958
+ <jv-sidebar
23959
+ [title]="title()"
23960
+ [subtitle]="sidebarSubtitle()"
23961
+ [brandIcon]="brandIcon()"
23962
+ [items]="navItems()"
23963
+ [collapsed]="sidebarCollapsed()"
23964
+ [mobileOpen]="mobileSidebarOpen()"
23500
23965
  [currentTheme]="currentTheme()"
23501
23966
  [themeOptions]="themeOptions()"
23502
- [actions]="topbarActions()"
23967
+ [topbarActions]="topbarActions()"
23503
23968
  [showThemeSelector]="showThemeSelector()"
23504
- (menuClick)="menuClick.emit()"
23969
+ (collapseToggle)="sidebarCollapseToggle.emit()"
23970
+ (mobileClose)="mobileClose.emit()"
23505
23971
  (themeChange)="themeChange.emit($event)"
23506
23972
  (actionClick)="topbarActionClick.emit($event)"
23507
- >
23508
- <ng-content select="[topbar-actions]" topbar-actions></ng-content>
23509
- </jv-topbar>
23973
+ />
23974
+ }
23975
+
23976
+ <div class="jv-dashboard-main">
23977
+ @if (navigationMode() === 'topbar') {
23978
+ <jv-topbar-menu
23979
+ [title]="title()"
23980
+ [subtitle]="sidebarSubtitle()"
23981
+ [brandIcon]="brandIcon()"
23982
+ [items]="navItems()"
23983
+ [mobileOpen]="mobileSidebarOpen()"
23984
+ [currentTheme]="currentTheme()"
23985
+ [themeOptions]="themeOptions()"
23986
+ [actions]="topbarActions()"
23987
+ [showThemeSelector]="showThemeSelector()"
23988
+ (menuClick)="menuClick.emit()"
23989
+ (mobileClose)="mobileClose.emit()"
23990
+ (themeChange)="themeChange.emit($event)"
23991
+ (actionClick)="topbarActionClick.emit($event)"
23992
+ />
23993
+ } @else {
23994
+ <jv-topbar
23995
+ [title]="topbarTitle()"
23996
+ [subtitle]="topbarSubtitle()"
23997
+ [currentTheme]="currentTheme()"
23998
+ [themeOptions]="themeOptions()"
23999
+ [actions]="topbarActions()"
24000
+ [showThemeSelector]="showThemeSelector()"
24001
+ (menuClick)="menuClick.emit()"
24002
+ (themeChange)="themeChange.emit($event)"
24003
+ (actionClick)="topbarActionClick.emit($event)"
24004
+ >
24005
+ <ng-content select="[topbar-actions]" topbar-actions></ng-content>
24006
+ </jv-topbar>
24007
+ }
23510
24008
 
23511
24009
  <main class="jv-dashboard-content">
23512
24010
  <ng-content></ng-content>
23513
24011
  </main>
23514
24012
  </div>
23515
24013
  </div>
23516
- `, isInline: true, styles: [":host{display:block;height:100dvh;overflow:hidden}.jv-dashboard-shell{display:grid;height:100%;grid-template-columns:auto 1fr;background:var(--jv-color-background);overflow:hidden}.jv-dashboard-main{display:grid;grid-template-rows:auto 1fr;min-width:0;min-height:0;overflow:hidden}.jv-dashboard-content{overflow:auto;min-width:0;min-height:0;padding:var(--jv-spacing-xl);scrollbar-width:none;-ms-overflow-style:none}.jv-dashboard-content::-webkit-scrollbar{width:0;height:0;display:none}@media(max-width:900px){.jv-dashboard-shell{grid-template-columns:1fr}.jv-dashboard-content{padding:var(--jv-spacing-lg)}}\n"], dependencies: [{ kind: "component", type: JvSidebarComponent, selector: "jv-sidebar", inputs: ["title", "subtitle", "brandIcon", "items", "collapsed", "mobileOpen", "ariaLabel", "currentTheme", "themeOptions", "topbarActions", "showThemeSelector"], outputs: ["collapseToggle", "mobileClose", "themeChange", "actionClick"] }, { kind: "component", type: JvTopbarComponent, selector: "jv-topbar", inputs: ["title", "subtitle", "currentTheme", "themeOptions", "actions", "showThemeSelector"], outputs: ["menuClick", "themeChange", "actionClick"] }] });
24014
+ `, isInline: true, styles: [":host{display:block;height:100dvh;overflow:hidden}.jv-dashboard-shell{display:grid;height:100%;grid-template-columns:auto 1fr;background:var(--jv-color-background);overflow:hidden}.jv-dashboard-shell.is-topbar-navigation{grid-template-columns:1fr}.jv-dashboard-main{display:grid;grid-template-rows:auto 1fr;min-width:0;min-height:0;overflow:hidden}.jv-dashboard-content{overflow:auto;min-width:0;min-height:0;padding:var(--jv-spacing-xl);scrollbar-width:none;-ms-overflow-style:none}.jv-dashboard-content::-webkit-scrollbar{width:0;height:0;display:none}@media(max-width:900px){.jv-dashboard-shell{grid-template-columns:1fr}.jv-dashboard-content{padding:var(--jv-spacing-lg)}}\n"], dependencies: [{ kind: "component", type: JvSidebarComponent, selector: "jv-sidebar", inputs: ["title", "subtitle", "brandIcon", "items", "collapsed", "mobileOpen", "ariaLabel", "currentTheme", "themeOptions", "topbarActions", "showThemeSelector"], outputs: ["collapseToggle", "mobileClose", "themeChange", "actionClick"] }, { kind: "component", type: JvTopbarComponent, selector: "jv-topbar", inputs: ["title", "subtitle", "currentTheme", "themeOptions", "actions", "showThemeSelector"], outputs: ["menuClick", "themeChange", "actionClick"] }, { kind: "component", type: JvTopbarMenuComponent, selector: "jv-topbar-menu", inputs: ["title", "subtitle", "brandIcon", "items", "mobileOpen", "ariaLabel", "currentTheme", "themeOptions", "actions", "showThemeSelector"], outputs: ["menuClick", "mobileClose", "themeChange", "actionClick"] }] });
23517
24015
  }
23518
24016
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: JvDashboardShellComponent, decorators: [{
23519
24017
  type: Component,
23520
- args: [{ selector: 'jv-dashboard-shell', standalone: true, imports: [JvSidebarComponent, JvTopbarComponent], template: `
23521
- <div class="jv-dashboard-shell" [class.is-sidebar-collapsed]="sidebarCollapsed()">
23522
- <jv-sidebar
23523
- [title]="title()"
23524
- [subtitle]="sidebarSubtitle()"
23525
- [brandIcon]="brandIcon()"
23526
- [items]="navItems()"
23527
- [collapsed]="sidebarCollapsed()"
23528
- [mobileOpen]="mobileSidebarOpen()"
23529
- [currentTheme]="currentTheme()"
23530
- [themeOptions]="themeOptions()"
23531
- [topbarActions]="topbarActions()"
23532
- [showThemeSelector]="showThemeSelector()"
23533
- (collapseToggle)="sidebarCollapseToggle.emit()"
23534
- (mobileClose)="mobileClose.emit()"
23535
- (themeChange)="themeChange.emit($event)"
23536
- (actionClick)="topbarActionClick.emit($event)"
23537
- />
23538
-
23539
- <div class="jv-dashboard-main">
23540
- <jv-topbar
23541
- [title]="topbarTitle()"
23542
- [subtitle]="topbarSubtitle()"
24018
+ args: [{ selector: 'jv-dashboard-shell', standalone: true, imports: [JvSidebarComponent, JvTopbarComponent, JvTopbarMenuComponent], template: `
24019
+ <div class="jv-dashboard-shell" [class.is-sidebar-collapsed]="sidebarCollapsed()" [class.is-topbar-navigation]="navigationMode() === 'topbar'">
24020
+ @if (navigationMode() === 'sidebar') {
24021
+ <jv-sidebar
24022
+ [title]="title()"
24023
+ [subtitle]="sidebarSubtitle()"
24024
+ [brandIcon]="brandIcon()"
24025
+ [items]="navItems()"
24026
+ [collapsed]="sidebarCollapsed()"
24027
+ [mobileOpen]="mobileSidebarOpen()"
23543
24028
  [currentTheme]="currentTheme()"
23544
24029
  [themeOptions]="themeOptions()"
23545
- [actions]="topbarActions()"
24030
+ [topbarActions]="topbarActions()"
23546
24031
  [showThemeSelector]="showThemeSelector()"
23547
- (menuClick)="menuClick.emit()"
24032
+ (collapseToggle)="sidebarCollapseToggle.emit()"
24033
+ (mobileClose)="mobileClose.emit()"
23548
24034
  (themeChange)="themeChange.emit($event)"
23549
24035
  (actionClick)="topbarActionClick.emit($event)"
23550
- >
23551
- <ng-content select="[topbar-actions]" topbar-actions></ng-content>
23552
- </jv-topbar>
24036
+ />
24037
+ }
24038
+
24039
+ <div class="jv-dashboard-main">
24040
+ @if (navigationMode() === 'topbar') {
24041
+ <jv-topbar-menu
24042
+ [title]="title()"
24043
+ [subtitle]="sidebarSubtitle()"
24044
+ [brandIcon]="brandIcon()"
24045
+ [items]="navItems()"
24046
+ [mobileOpen]="mobileSidebarOpen()"
24047
+ [currentTheme]="currentTheme()"
24048
+ [themeOptions]="themeOptions()"
24049
+ [actions]="topbarActions()"
24050
+ [showThemeSelector]="showThemeSelector()"
24051
+ (menuClick)="menuClick.emit()"
24052
+ (mobileClose)="mobileClose.emit()"
24053
+ (themeChange)="themeChange.emit($event)"
24054
+ (actionClick)="topbarActionClick.emit($event)"
24055
+ />
24056
+ } @else {
24057
+ <jv-topbar
24058
+ [title]="topbarTitle()"
24059
+ [subtitle]="topbarSubtitle()"
24060
+ [currentTheme]="currentTheme()"
24061
+ [themeOptions]="themeOptions()"
24062
+ [actions]="topbarActions()"
24063
+ [showThemeSelector]="showThemeSelector()"
24064
+ (menuClick)="menuClick.emit()"
24065
+ (themeChange)="themeChange.emit($event)"
24066
+ (actionClick)="topbarActionClick.emit($event)"
24067
+ >
24068
+ <ng-content select="[topbar-actions]" topbar-actions></ng-content>
24069
+ </jv-topbar>
24070
+ }
23553
24071
 
23554
24072
  <main class="jv-dashboard-content">
23555
24073
  <ng-content></ng-content>
23556
24074
  </main>
23557
24075
  </div>
23558
24076
  </div>
23559
- `, styles: [":host{display:block;height:100dvh;overflow:hidden}.jv-dashboard-shell{display:grid;height:100%;grid-template-columns:auto 1fr;background:var(--jv-color-background);overflow:hidden}.jv-dashboard-main{display:grid;grid-template-rows:auto 1fr;min-width:0;min-height:0;overflow:hidden}.jv-dashboard-content{overflow:auto;min-width:0;min-height:0;padding:var(--jv-spacing-xl);scrollbar-width:none;-ms-overflow-style:none}.jv-dashboard-content::-webkit-scrollbar{width:0;height:0;display:none}@media(max-width:900px){.jv-dashboard-shell{grid-template-columns:1fr}.jv-dashboard-content{padding:var(--jv-spacing-lg)}}\n"] }]
23560
- }], propDecorators: { title: [{ type: i0.Input, args: [{ isSignal: true, alias: "title", required: true }] }], sidebarSubtitle: [{ type: i0.Input, args: [{ isSignal: true, alias: "sidebarSubtitle", required: false }] }], brandIcon: [{ type: i0.Input, args: [{ isSignal: true, alias: "brandIcon", required: false }] }], navItems: [{ type: i0.Input, args: [{ isSignal: true, alias: "navItems", required: false }] }], sidebarCollapsed: [{ type: i0.Input, args: [{ isSignal: true, alias: "sidebarCollapsed", required: false }] }], mobileSidebarOpen: [{ type: i0.Input, args: [{ isSignal: true, alias: "mobileSidebarOpen", required: false }] }], topbarTitle: [{ type: i0.Input, args: [{ isSignal: true, alias: "topbarTitle", required: false }] }], topbarSubtitle: [{ type: i0.Input, args: [{ isSignal: true, alias: "topbarSubtitle", required: false }] }], currentTheme: [{ type: i0.Input, args: [{ isSignal: true, alias: "currentTheme", required: false }] }], themeOptions: [{ type: i0.Input, args: [{ isSignal: true, alias: "themeOptions", required: false }] }], topbarActions: [{ type: i0.Input, args: [{ isSignal: true, alias: "topbarActions", required: false }] }], showThemeSelector: [{ type: i0.Input, args: [{ isSignal: true, alias: "showThemeSelector", required: false }] }], menuClick: [{ type: i0.Output, args: ["menuClick"] }], mobileClose: [{ type: i0.Output, args: ["mobileClose"] }], sidebarCollapseToggle: [{ type: i0.Output, args: ["sidebarCollapseToggle"] }], themeChange: [{ type: i0.Output, args: ["themeChange"] }], topbarActionClick: [{ type: i0.Output, args: ["topbarActionClick"] }] } });
24077
+ `, styles: [":host{display:block;height:100dvh;overflow:hidden}.jv-dashboard-shell{display:grid;height:100%;grid-template-columns:auto 1fr;background:var(--jv-color-background);overflow:hidden}.jv-dashboard-shell.is-topbar-navigation{grid-template-columns:1fr}.jv-dashboard-main{display:grid;grid-template-rows:auto 1fr;min-width:0;min-height:0;overflow:hidden}.jv-dashboard-content{overflow:auto;min-width:0;min-height:0;padding:var(--jv-spacing-xl);scrollbar-width:none;-ms-overflow-style:none}.jv-dashboard-content::-webkit-scrollbar{width:0;height:0;display:none}@media(max-width:900px){.jv-dashboard-shell{grid-template-columns:1fr}.jv-dashboard-content{padding:var(--jv-spacing-lg)}}\n"] }]
24078
+ }], propDecorators: { title: [{ type: i0.Input, args: [{ isSignal: true, alias: "title", required: true }] }], navigationMode: [{ type: i0.Input, args: [{ isSignal: true, alias: "navigationMode", required: false }] }], sidebarSubtitle: [{ type: i0.Input, args: [{ isSignal: true, alias: "sidebarSubtitle", required: false }] }], brandIcon: [{ type: i0.Input, args: [{ isSignal: true, alias: "brandIcon", required: false }] }], navItems: [{ type: i0.Input, args: [{ isSignal: true, alias: "navItems", required: false }] }], sidebarCollapsed: [{ type: i0.Input, args: [{ isSignal: true, alias: "sidebarCollapsed", required: false }] }], mobileSidebarOpen: [{ type: i0.Input, args: [{ isSignal: true, alias: "mobileSidebarOpen", required: false }] }], topbarTitle: [{ type: i0.Input, args: [{ isSignal: true, alias: "topbarTitle", required: false }] }], topbarSubtitle: [{ type: i0.Input, args: [{ isSignal: true, alias: "topbarSubtitle", required: false }] }], currentTheme: [{ type: i0.Input, args: [{ isSignal: true, alias: "currentTheme", required: false }] }], themeOptions: [{ type: i0.Input, args: [{ isSignal: true, alias: "themeOptions", required: false }] }], topbarActions: [{ type: i0.Input, args: [{ isSignal: true, alias: "topbarActions", required: false }] }], showThemeSelector: [{ type: i0.Input, args: [{ isSignal: true, alias: "showThemeSelector", required: false }] }], menuClick: [{ type: i0.Output, args: ["menuClick"] }], mobileClose: [{ type: i0.Output, args: ["mobileClose"] }], sidebarCollapseToggle: [{ type: i0.Output, args: ["sidebarCollapseToggle"] }], themeChange: [{ type: i0.Output, args: ["themeChange"] }], topbarActionClick: [{ type: i0.Output, args: ["topbarActionClick"] }] } });
23561
24079
 
23562
24080
  class JvPageComponent {
23563
24081
  eyebrow = input('', ...(ngDevMode ? [{ debugName: "eyebrow" }] : /* istanbul ignore next */ []));
@@ -24076,209 +24594,22 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImpo
24076
24594
  [class.jv-tab-panel--fade]="effectiveTransition() === 'fade'"
24077
24595
  [class.jv-tab-panel--slide-left]="effectiveTransition() === 'slide' && slideDirection() === 'left'"
24078
24596
  [class.jv-tab-panel--slide-right]="effectiveTransition() === 'slide' && slideDirection() === 'right'"
24079
- [id]="panelId(activeId)"
24080
- role="tabpanel"
24081
- [attr.aria-labelledby]="tabButtonId(activeId)"
24082
- tabindex="0"
24083
- >
24084
- <ng-container [ngTemplateOutlet]="panelTemplate(activeId) ?? null" />
24085
- </div>
24086
- }
24087
- }
24088
- </div>
24089
- </div>
24090
- `, styles: [".jv-tabs{display:grid;gap:var(--jv-spacing-md)}.jv-tabs-header{display:flex;align-items:center;gap:var(--jv-spacing-sm);min-width:0}.jv-tabs-scroll{flex:1;min-width:0;display:flex;align-items:stretch;gap:var(--jv-spacing-xs);overflow-x:auto;overflow-y:hidden;scrollbar-width:none;-ms-overflow-style:none;border-bottom:1px solid var(--jv-color-border);scroll-behavior:smooth}.jv-tabs-scroll::-webkit-scrollbar{display:none}.jv-tab{position:relative;display:inline-flex;align-items:center;gap:var(--jv-spacing-xs);flex:0 0 auto;min-height:var(--jv-density-control-height);padding:0 var(--jv-spacing-md);border:0;border-bottom:2px solid transparent;background:transparent;color:var(--jv-color-foreground-muted);font:inherit;font-weight:600;white-space:nowrap;cursor:pointer;transition:color .16s ease,border-color .16s ease,background-color .16s ease}.jv-tab:hover:not(:disabled){color:var(--jv-color-foreground);background:color-mix(in srgb,var(--jv-color-primary) 6%,transparent)}.jv-tab:focus-visible{outline:2px solid var(--jv-color-primary);outline-offset:-2px;border-radius:var(--jv-radius-sm) var(--jv-radius-sm) 0 0}.jv-tab--active{color:var(--jv-color-primary);border-bottom-color:var(--jv-color-primary)}.jv-tab--disabled{opacity:.5;cursor:not-allowed}.jv-tabs-controls{display:inline-flex;align-items:center;gap:var(--jv-spacing-xs)}.jv-tabs-scroll-button{display:inline-flex;align-items:center;justify-content:center;width:2rem;height:2rem;border:1px solid var(--jv-color-border);border-radius:var(--jv-radius-md);background:var(--jv-color-surface);color:var(--jv-color-foreground);cursor:pointer}.jv-tabs-scroll-button:disabled{opacity:.4;cursor:default}.jv-tab-panel{outline:none}.jv-tab-panel--hidden{display:none}.jv-tab-panel--fade{transition:opacity .18s ease}.jv-tab-panel--hidden.jv-tab-panel--fade{display:block;opacity:0;pointer-events:none}.jv-tab-panel--slide-left,.jv-tab-panel--slide-right{transition:opacity .18s ease,transform .18s ease}.jv-tab-panel--hidden.jv-tab-panel--slide-left{display:block;opacity:0;transform:translate(20px);pointer-events:none}.jv-tab-panel--hidden.jv-tab-panel--slide-right{display:block;opacity:0;transform:translate(-20px);pointer-events:none}.jv-tab-panel--exit.jv-tab-panel--fade{position:absolute;opacity:0;pointer-events:none}.jv-tab-panel--exit.jv-tab-panel--slide-left{position:absolute;opacity:0;transform:translate(-20px);pointer-events:none}.jv-tab-panel--exit.jv-tab-panel--slide-right{position:absolute;opacity:0;transform:translate(20px);pointer-events:none}.jv-tab-panel--entering{animation:jv-tab-enter .18s ease forwards}.jv-tab-panel--entering.jv-tab-panel--slide-left{animation-name:jv-tab-slide-from-right}.jv-tab-panel--entering.jv-tab-panel--slide-right{animation-name:jv-tab-slide-from-left}@keyframes jv-tab-enter{0%{opacity:0}to{opacity:1}}@keyframes jv-tab-slide-from-right{0%{opacity:0;transform:translate(20px)}to{opacity:1;transform:translate(0)}}@keyframes jv-tab-slide-from-left{0%{opacity:0;transform:translate(-20px)}to{opacity:1;transform:translate(0)}}@media(prefers-reduced-motion:reduce){.jv-tab-panel--fade,.jv-tab-panel--slide-left,.jv-tab-panel--slide-right,.jv-tab-panel--entering{transition:none;animation:none}.jv-tab-panel--hidden.jv-tab-panel--fade,.jv-tab-panel--hidden.jv-tab-panel--slide-left,.jv-tab-panel--hidden.jv-tab-panel--slide-right{opacity:0}}\n"] }]
24091
- }], ctorParameters: () => [], propDecorators: { panelTemplates: [{ type: i0.ContentChildren, args: [i0.forwardRef(() => JvTabPanelDirective), { isSignal: true }] }], tabListRef: [{ type: i0.ViewChild, args: ['tabList', { isSignal: true }] }], tabs: [{ type: i0.Input, args: [{ isSignal: true, alias: "tabs", required: false }] }], activeTabId: [{ type: i0.Input, args: [{ isSignal: true, alias: "activeTabId", required: false }] }], keepMounted: [{ type: i0.Input, args: [{ isSignal: true, alias: "keepMounted", required: false }] }], transition: [{ type: i0.Input, args: [{ isSignal: true, alias: "transition", required: false }] }], activeTabChange: [{ type: i0.Output, args: ["activeTabChange"] }], handleWindowResize: [{
24092
- type: HostListener,
24093
- args: ['window:resize']
24094
- }] } });
24095
-
24096
- let tooltipSequence = 0;
24097
- class JvTooltipDirective {
24098
- document = inject(DOCUMENT);
24099
- renderer = inject(Renderer2);
24100
- hostRef = inject((ElementRef));
24101
- tooltipElement = null;
24102
- showTimeout = null;
24103
- hideTimeout = null;
24104
- tooltipId = `jv-tooltip-${++tooltipSequence}`;
24105
- originalDescribedBy = null;
24106
- text = input.required({ ...(ngDevMode ? { debugName: "text" } : /* istanbul ignore next */ {}), alias: 'jvTooltip' });
24107
- position = input('top', { ...(ngDevMode ? { debugName: "position" } : /* istanbul ignore next */ {}), alias: 'jvTooltipPosition' });
24108
- show() {
24109
- if (!this.text().trim()) {
24110
- return;
24111
- }
24112
- this.clearHideTimeout();
24113
- if (this.tooltipElement) {
24114
- return;
24115
- }
24116
- this.showTimeout = setTimeout(() => {
24117
- this.renderTooltip();
24118
- this.positionTooltip();
24119
- this.attachTooltipDescription();
24120
- }, 120);
24121
- }
24122
- hide() {
24123
- this.clearShowTimeout();
24124
- if (!this.tooltipElement) {
24125
- return;
24126
- }
24127
- this.hideTimeout = setTimeout(() => this.destroyTooltip(), 80);
24128
- }
24129
- handleDocumentKeydown(event) {
24130
- if (event.key === 'Escape') {
24131
- this.destroyTooltip();
24132
- }
24133
- }
24134
- handleViewportChange() {
24135
- if (this.tooltipElement) {
24136
- this.positionTooltip();
24137
- }
24138
- }
24139
- ngOnDestroy() {
24140
- this.clearShowTimeout();
24141
- this.clearHideTimeout();
24142
- this.destroyTooltip();
24143
- }
24144
- renderTooltip() {
24145
- if (this.tooltipElement) {
24146
- return;
24147
- }
24148
- const tooltip = this.renderer.createElement('div');
24149
- tooltip.textContent = this.text();
24150
- this.renderer.setAttribute(tooltip, 'id', this.tooltipId);
24151
- this.renderer.setAttribute(tooltip, 'role', 'tooltip');
24152
- this.renderer.setStyle(tooltip, 'position', 'fixed');
24153
- this.renderer.setStyle(tooltip, 'z-index', '1400');
24154
- this.renderer.setStyle(tooltip, 'max-width', '18rem');
24155
- this.renderer.setStyle(tooltip, 'padding', '0.5rem 0.75rem');
24156
- this.renderer.setStyle(tooltip, 'border-radius', '0.5rem');
24157
- this.renderer.setStyle(tooltip, 'background', 'rgb(15 23 42)');
24158
- this.renderer.setStyle(tooltip, 'color', '#ffffff');
24159
- this.renderer.setStyle(tooltip, 'font-size', '0.8125rem');
24160
- this.renderer.setStyle(tooltip, 'line-height', '1.4');
24161
- this.renderer.setStyle(tooltip, 'box-shadow', 'var(--jv-shadow-md)');
24162
- this.renderer.setStyle(tooltip, 'pointer-events', 'none');
24163
- this.renderer.setStyle(tooltip, 'opacity', '0');
24164
- this.renderer.setStyle(tooltip, 'transition', 'opacity 120ms ease');
24165
- this.renderer.appendChild(this.document.body, tooltip);
24166
- this.tooltipElement = tooltip;
24167
- requestAnimationFrame(() => {
24168
- if (this.tooltipElement) {
24169
- this.renderer.setStyle(this.tooltipElement, 'opacity', '1');
24170
- }
24171
- });
24172
- }
24173
- positionTooltip() {
24174
- const tooltip = this.tooltipElement;
24175
- if (!tooltip) {
24176
- return;
24177
- }
24178
- const hostRect = this.anchorElement().getBoundingClientRect();
24179
- const tooltipRect = tooltip.getBoundingClientRect();
24180
- const gap = 8;
24181
- let top = hostRect.top - tooltipRect.height - gap;
24182
- let left = hostRect.left + hostRect.width / 2 - tooltipRect.width / 2;
24183
- switch (this.position()) {
24184
- case 'right':
24185
- top = hostRect.top + hostRect.height / 2 - tooltipRect.height / 2;
24186
- left = hostRect.right + gap;
24187
- break;
24188
- case 'bottom':
24189
- top = hostRect.bottom + gap;
24190
- left = hostRect.left + hostRect.width / 2 - tooltipRect.width / 2;
24191
- break;
24192
- case 'left':
24193
- top = hostRect.top + hostRect.height / 2 - tooltipRect.height / 2;
24194
- left = hostRect.left - tooltipRect.width - gap;
24195
- break;
24196
- }
24197
- this.renderer.setStyle(tooltip, 'top', `${Math.round(top)}px`);
24198
- this.renderer.setStyle(tooltip, 'left', `${Math.round(left)}px`);
24199
- }
24200
- destroyTooltip() {
24201
- this.clearHideTimeout();
24202
- this.clearShowTimeout();
24203
- if (!this.tooltipElement) {
24204
- return;
24205
- }
24206
- this.restoreTooltipDescription();
24207
- this.renderer.removeChild(this.document.body, this.tooltipElement);
24208
- this.tooltipElement = null;
24209
- }
24210
- attachTooltipDescription() {
24211
- const anchor = this.anchorElement();
24212
- this.originalDescribedBy ??= anchor.getAttribute('aria-describedby');
24213
- const describedBy = [this.originalDescribedBy, this.tooltipId]
24214
- .filter((value) => !!value)
24215
- .join(' ')
24216
- .trim();
24217
- this.renderer.setAttribute(anchor, 'aria-describedby', describedBy);
24218
- }
24219
- restoreTooltipDescription() {
24220
- const anchor = this.anchorElement();
24221
- if (this.originalDescribedBy) {
24222
- this.renderer.setAttribute(anchor, 'aria-describedby', this.originalDescribedBy);
24223
- }
24224
- else {
24225
- this.renderer.removeAttribute(anchor, 'aria-describedby');
24226
- }
24227
- this.originalDescribedBy = null;
24228
- }
24229
- anchorElement() {
24230
- const host = this.hostRef.nativeElement;
24231
- if (isInteractiveElement(host)) {
24232
- return host;
24233
- }
24234
- return host.querySelector('button, a, input, select, textarea, [tabindex]:not([tabindex="-1"])') ?? host;
24235
- }
24236
- clearShowTimeout() {
24237
- if (this.showTimeout) {
24238
- clearTimeout(this.showTimeout);
24239
- this.showTimeout = null;
24240
- }
24241
- }
24242
- clearHideTimeout() {
24243
- if (this.hideTimeout) {
24244
- clearTimeout(this.hideTimeout);
24245
- this.hideTimeout = null;
24597
+ [id]="panelId(activeId)"
24598
+ role="tabpanel"
24599
+ [attr.aria-labelledby]="tabButtonId(activeId)"
24600
+ tabindex="0"
24601
+ >
24602
+ <ng-container [ngTemplateOutlet]="panelTemplate(activeId) ?? null" />
24603
+ </div>
24604
+ }
24246
24605
  }
24247
- }
24248
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: JvTooltipDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
24249
- static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.2.17", type: JvTooltipDirective, isStandalone: true, selector: "[jvTooltip]", inputs: { text: { classPropertyName: "text", publicName: "jvTooltip", isSignal: true, isRequired: true, transformFunction: null }, position: { classPropertyName: "position", publicName: "jvTooltipPosition", isSignal: true, isRequired: false, transformFunction: null } }, host: { listeners: { "mouseenter": "show()", "focusin": "show()", "mouseleave": "hide()", "blur": "hide()", "document:keydown": "handleDocumentKeydown($event)", "window:scroll": "handleViewportChange()", "window:resize": "handleViewportChange()" } }, ngImport: i0 });
24250
- }
24251
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: JvTooltipDirective, decorators: [{
24252
- type: Directive,
24253
- args: [{
24254
- selector: '[jvTooltip]',
24255
- standalone: true,
24256
- }]
24257
- }], propDecorators: { text: [{ type: i0.Input, args: [{ isSignal: true, alias: "jvTooltip", required: true }] }], position: [{ type: i0.Input, args: [{ isSignal: true, alias: "jvTooltipPosition", required: false }] }], show: [{
24258
- type: HostListener,
24259
- args: ['mouseenter']
24260
- }, {
24261
- type: HostListener,
24262
- args: ['focusin']
24263
- }], hide: [{
24264
- type: HostListener,
24265
- args: ['mouseleave']
24266
- }, {
24267
- type: HostListener,
24268
- args: ['blur']
24269
- }], handleDocumentKeydown: [{
24270
- type: HostListener,
24271
- args: ['document:keydown', ['$event']]
24272
- }], handleViewportChange: [{
24273
- type: HostListener,
24274
- args: ['window:scroll']
24275
- }, {
24606
+ </div>
24607
+ </div>
24608
+ `, styles: [".jv-tabs{display:grid;gap:var(--jv-spacing-md)}.jv-tabs-header{display:flex;align-items:center;gap:var(--jv-spacing-sm);min-width:0}.jv-tabs-scroll{flex:1;min-width:0;display:flex;align-items:stretch;gap:var(--jv-spacing-xs);overflow-x:auto;overflow-y:hidden;scrollbar-width:none;-ms-overflow-style:none;border-bottom:1px solid var(--jv-color-border);scroll-behavior:smooth}.jv-tabs-scroll::-webkit-scrollbar{display:none}.jv-tab{position:relative;display:inline-flex;align-items:center;gap:var(--jv-spacing-xs);flex:0 0 auto;min-height:var(--jv-density-control-height);padding:0 var(--jv-spacing-md);border:0;border-bottom:2px solid transparent;background:transparent;color:var(--jv-color-foreground-muted);font:inherit;font-weight:600;white-space:nowrap;cursor:pointer;transition:color .16s ease,border-color .16s ease,background-color .16s ease}.jv-tab:hover:not(:disabled){color:var(--jv-color-foreground);background:color-mix(in srgb,var(--jv-color-primary) 6%,transparent)}.jv-tab:focus-visible{outline:2px solid var(--jv-color-primary);outline-offset:-2px;border-radius:var(--jv-radius-sm) var(--jv-radius-sm) 0 0}.jv-tab--active{color:var(--jv-color-primary);border-bottom-color:var(--jv-color-primary)}.jv-tab--disabled{opacity:.5;cursor:not-allowed}.jv-tabs-controls{display:inline-flex;align-items:center;gap:var(--jv-spacing-xs)}.jv-tabs-scroll-button{display:inline-flex;align-items:center;justify-content:center;width:2rem;height:2rem;border:1px solid var(--jv-color-border);border-radius:var(--jv-radius-md);background:var(--jv-color-surface);color:var(--jv-color-foreground);cursor:pointer}.jv-tabs-scroll-button:disabled{opacity:.4;cursor:default}.jv-tab-panel{outline:none}.jv-tab-panel--hidden{display:none}.jv-tab-panel--fade{transition:opacity .18s ease}.jv-tab-panel--hidden.jv-tab-panel--fade{display:block;opacity:0;pointer-events:none}.jv-tab-panel--slide-left,.jv-tab-panel--slide-right{transition:opacity .18s ease,transform .18s ease}.jv-tab-panel--hidden.jv-tab-panel--slide-left{display:block;opacity:0;transform:translate(20px);pointer-events:none}.jv-tab-panel--hidden.jv-tab-panel--slide-right{display:block;opacity:0;transform:translate(-20px);pointer-events:none}.jv-tab-panel--exit.jv-tab-panel--fade{position:absolute;opacity:0;pointer-events:none}.jv-tab-panel--exit.jv-tab-panel--slide-left{position:absolute;opacity:0;transform:translate(-20px);pointer-events:none}.jv-tab-panel--exit.jv-tab-panel--slide-right{position:absolute;opacity:0;transform:translate(20px);pointer-events:none}.jv-tab-panel--entering{animation:jv-tab-enter .18s ease forwards}.jv-tab-panel--entering.jv-tab-panel--slide-left{animation-name:jv-tab-slide-from-right}.jv-tab-panel--entering.jv-tab-panel--slide-right{animation-name:jv-tab-slide-from-left}@keyframes jv-tab-enter{0%{opacity:0}to{opacity:1}}@keyframes jv-tab-slide-from-right{0%{opacity:0;transform:translate(20px)}to{opacity:1;transform:translate(0)}}@keyframes jv-tab-slide-from-left{0%{opacity:0;transform:translate(-20px)}to{opacity:1;transform:translate(0)}}@media(prefers-reduced-motion:reduce){.jv-tab-panel--fade,.jv-tab-panel--slide-left,.jv-tab-panel--slide-right,.jv-tab-panel--entering{transition:none;animation:none}.jv-tab-panel--hidden.jv-tab-panel--fade,.jv-tab-panel--hidden.jv-tab-panel--slide-left,.jv-tab-panel--hidden.jv-tab-panel--slide-right{opacity:0}}\n"] }]
24609
+ }], ctorParameters: () => [], propDecorators: { panelTemplates: [{ type: i0.ContentChildren, args: [i0.forwardRef(() => JvTabPanelDirective), { isSignal: true }] }], tabListRef: [{ type: i0.ViewChild, args: ['tabList', { isSignal: true }] }], tabs: [{ type: i0.Input, args: [{ isSignal: true, alias: "tabs", required: false }] }], activeTabId: [{ type: i0.Input, args: [{ isSignal: true, alias: "activeTabId", required: false }] }], keepMounted: [{ type: i0.Input, args: [{ isSignal: true, alias: "keepMounted", required: false }] }], transition: [{ type: i0.Input, args: [{ isSignal: true, alias: "transition", required: false }] }], activeTabChange: [{ type: i0.Output, args: ["activeTabChange"] }], handleWindowResize: [{
24276
24610
  type: HostListener,
24277
24611
  args: ['window:resize']
24278
24612
  }] } });
24279
- function isInteractiveElement(element) {
24280
- return ['BUTTON', 'A', 'INPUT', 'SELECT', 'TEXTAREA'].includes(element.tagName) || element.tabIndex >= 0;
24281
- }
24282
24613
 
24283
24614
  const JV_GRID_DEFAULT_OPTIONS = {
24284
24615
  searchable: true,
@@ -24583,6 +24914,266 @@ function exportGridToExcel(header, rows, filename = 'export.xlsx') {
24583
24914
  downloadBlob(blob, filename);
24584
24915
  }
24585
24916
 
24917
+ function flattenGridColumns(columns) {
24918
+ return columns.flatMap((column) => column.children?.length ? flattenGridColumns(column.children) : [column]);
24919
+ }
24920
+ function getVisibleGridColumns(columns, columnOrder) {
24921
+ const visibleColumns = flattenGridColumns(columns).filter((column) => !column.hidden);
24922
+ if (columnOrder.length === 0)
24923
+ return visibleColumns;
24924
+ return columnOrder
24925
+ .map((key) => visibleColumns.find((column) => String(column.key) === key))
24926
+ .filter((column) => Boolean(column));
24927
+ }
24928
+ function getGroupedGridColumns(columns) {
24929
+ return columns.filter((column) => column.children && column.children.length > 0);
24930
+ }
24931
+
24932
+ function getCellKeyboardTarget(key, rowIndex, columnIndex, rowCount, columnCount) {
24933
+ if (key === 'ArrowUp' && rowIndex === 0) {
24934
+ return { kind: 'header', row: 0, column: columnIndex };
24935
+ }
24936
+ const movement = {
24937
+ ArrowDown: [1, 0],
24938
+ ArrowLeft: [0, -1],
24939
+ ArrowRight: [0, 1],
24940
+ ArrowUp: [-1, 0],
24941
+ };
24942
+ const delta = movement[key];
24943
+ if (!delta)
24944
+ return null;
24945
+ const target = { row: rowIndex + delta[0], column: columnIndex + delta[1] };
24946
+ if (target.row < 0 ||
24947
+ target.row >= rowCount ||
24948
+ target.column < 0 ||
24949
+ target.column >= columnCount) {
24950
+ return null;
24951
+ }
24952
+ return { kind: 'cell', ...target };
24953
+ }
24954
+ function getHeaderKeyboardTarget(key, columnIndex, columnCount) {
24955
+ if (key === 'ArrowDown')
24956
+ return { kind: 'cell', row: 0, column: columnIndex };
24957
+ const delta = key === 'ArrowLeft' ? -1 : key === 'ArrowRight' ? 1 : 0;
24958
+ if (!delta)
24959
+ return null;
24960
+ const targetColumn = columnIndex + delta;
24961
+ if (targetColumn < 0 || targetColumn >= columnCount)
24962
+ return null;
24963
+ return { kind: 'header', row: 0, column: targetColumn };
24964
+ }
24965
+
24966
+ function getGridEditType(column) {
24967
+ if (column.editType)
24968
+ return column.editType;
24969
+ switch (column.type) {
24970
+ case 'integer':
24971
+ case 'number':
24972
+ case 'currency':
24973
+ case 'percent':
24974
+ return 'number';
24975
+ case 'date':
24976
+ return 'date';
24977
+ case 'time':
24978
+ return 'time';
24979
+ case 'datetime':
24980
+ return 'datetime';
24981
+ case 'boolean':
24982
+ return 'boolean';
24983
+ default:
24984
+ return 'text';
24985
+ }
24986
+ }
24987
+ function getGridInputType(column) {
24988
+ switch (getGridEditType(column)) {
24989
+ case 'number':
24990
+ return 'number';
24991
+ case 'date':
24992
+ return 'date';
24993
+ case 'time':
24994
+ return 'time';
24995
+ case 'datetime':
24996
+ return 'datetime-local';
24997
+ default:
24998
+ return 'text';
24999
+ }
25000
+ }
25001
+ function isGridBooleanTrue(value) {
25002
+ return value === true || value === 'true' || value === 1 || value === '1';
25003
+ }
25004
+ function getGridEditValue(value, column, toDate) {
25005
+ const editType = getGridEditType(column);
25006
+ if (editType === 'date')
25007
+ return toGridDateInputValue(value, toDate);
25008
+ if (editType === 'datetime')
25009
+ return toGridDateTimeInputValue(value, toDate);
25010
+ if (editType === 'time')
25011
+ return toGridTimeInputValue(value, toDate);
25012
+ return value == null ? '' : String(value);
25013
+ }
25014
+ function normalizeGridEditValue(column, value, toDate) {
25015
+ switch (getGridEditType(column)) {
25016
+ case 'number': {
25017
+ if (value === '' || value == null)
25018
+ return null;
25019
+ const number = Number(value);
25020
+ return Number.isFinite(number) ? number : null;
25021
+ }
25022
+ case 'date':
25023
+ return toGridDateInputValue(value, toDate) || null;
25024
+ case 'datetime': {
25025
+ if (value === '' || value == null)
25026
+ return null;
25027
+ const date = new Date(String(value));
25028
+ return Number.isNaN(date.getTime()) ? null : date.toISOString();
25029
+ }
25030
+ case 'time': {
25031
+ if (value === '' || value == null)
25032
+ return null;
25033
+ const time = String(value);
25034
+ return /^\d{2}:\d{2}$/.test(time) ? `${time}:00` : time;
25035
+ }
25036
+ case 'boolean':
25037
+ return Boolean(value);
25038
+ default:
25039
+ return value;
25040
+ }
25041
+ }
25042
+ function toGridDateInputValue(value, toDate) {
25043
+ if (typeof value === 'string') {
25044
+ const match = value.match(/^\d{4}-\d{2}-\d{2}/);
25045
+ if (match)
25046
+ return match[0];
25047
+ }
25048
+ const date = toDate(value);
25049
+ if (!date)
25050
+ return '';
25051
+ return `${date.getUTCFullYear()}-${String(date.getUTCMonth() + 1).padStart(2, '0')}-${String(date.getUTCDate()).padStart(2, '0')}`;
25052
+ }
25053
+ function toGridDateTimeInputValue(value, toDate) {
25054
+ const date = toDate(value);
25055
+ if (!date)
25056
+ return '';
25057
+ const pad = (part) => String(part).padStart(2, '0');
25058
+ return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())}T${pad(date.getHours())}:${pad(date.getMinutes())}`;
25059
+ }
25060
+ function toGridTimeInputValue(value, toDate) {
25061
+ if (typeof value === 'string' && /^\d{2}:\d{2}(:\d{2})?$/.test(value))
25062
+ return value.slice(0, 5);
25063
+ const date = toDate(value);
25064
+ if (!date)
25065
+ return '';
25066
+ return `${String(date.getHours()).padStart(2, '0')}:${String(date.getMinutes()).padStart(2, '0')}`;
25067
+ }
25068
+
25069
+ function filterGridData(data, columns, searchTerm, filters, getColumnKey, getRawCellValue, getSearchValue) {
25070
+ let items = data;
25071
+ const term = searchTerm.trim().toLowerCase();
25072
+ if (term) {
25073
+ items = items.filter((row) => columns.some((column) => {
25074
+ if (column.searchable === false)
25075
+ return false;
25076
+ const value = getRawCellValue(row, column);
25077
+ if (value == null)
25078
+ return false;
25079
+ return getSearchValue(value, column).includes(term);
25080
+ }));
25081
+ }
25082
+ const hasFilters = Object.values(filters).some((filter) => filter.value.trim().length > 0);
25083
+ if (!hasFilters)
25084
+ return items;
25085
+ return items.filter((row) => Object.entries(filters).every(([key, filter]) => {
25086
+ if (!filter.value.trim())
25087
+ return true;
25088
+ const column = columns.find((candidate) => getColumnKey(candidate) === key);
25089
+ if (!column || column.filterable === false)
25090
+ return true;
25091
+ const value = getSearchValue(getRawCellValue(row, column), column);
25092
+ const filterValue = filter.value.toLowerCase();
25093
+ switch (filter.operator) {
25094
+ case 'contains':
25095
+ return value.includes(filterValue);
25096
+ case 'equals':
25097
+ return value === filterValue;
25098
+ case 'startsWith':
25099
+ return value.startsWith(filterValue);
25100
+ case 'endsWith':
25101
+ return value.endsWith(filterValue);
25102
+ default:
25103
+ return value.includes(filterValue);
25104
+ }
25105
+ }));
25106
+ }
25107
+ function sortGridData(data, sortState, findColumn, getRawCellValue, getComparableValue) {
25108
+ const { columnKey, direction } = sortState;
25109
+ if (!columnKey || !direction)
25110
+ return data;
25111
+ const column = findColumn(columnKey);
25112
+ return [...data].sort((a, b) => {
25113
+ const valueA = getComparableValue(getRawCellValue(a, column), column);
25114
+ const valueB = getComparableValue(getRawCellValue(b, column), column);
25115
+ if (valueA == null)
25116
+ return 1;
25117
+ if (valueB == null)
25118
+ return -1;
25119
+ const comparison = valueA < valueB ? -1 : valueA > valueB ? 1 : 0;
25120
+ return direction === 'asc' ? comparison : -comparison;
25121
+ });
25122
+ }
25123
+ function paginateGridData(data, pageIndex, pageSize, totalPages) {
25124
+ const page = Math.min(pageIndex, totalPages - 1);
25125
+ return data.slice(page * pageSize, page * pageSize + pageSize);
25126
+ }
25127
+
25128
+ function resolveGridSelection(controlledIds, internalIds) {
25129
+ return new Set(controlledIds ?? internalIds);
25130
+ }
25131
+ function areAllGridRowsSelected(rows, selectedIds, getRowId) {
25132
+ return rows.length > 0 && rows.every((row) => selectedIds.has(getRowId(row)));
25133
+ }
25134
+ function areSomeGridRowsSelected(rows, selectedIds, getRowId) {
25135
+ return (rows.some((row) => selectedIds.has(getRowId(row))) &&
25136
+ !areAllGridRowsSelected(rows, selectedIds, getRowId));
25137
+ }
25138
+ function toggleGridRowSelection(selectedIds, rowId) {
25139
+ const next = new Set(selectedIds);
25140
+ if (next.has(rowId))
25141
+ next.delete(rowId);
25142
+ else
25143
+ next.add(rowId);
25144
+ return Array.from(next);
25145
+ }
25146
+ function toggleGridRowsSelection(selectedIds, rows, checked, getRowId) {
25147
+ const next = new Set(selectedIds);
25148
+ rows.forEach((row) => {
25149
+ const id = getRowId(row);
25150
+ if (checked)
25151
+ next.add(id);
25152
+ else
25153
+ next.delete(id);
25154
+ });
25155
+ return Array.from(next);
25156
+ }
25157
+
25158
+ function getGridVirtualRange(scrollTop, clientHeight, rowHeight, itemCount, buffer = 5) {
25159
+ const start = Math.max(0, Math.floor(scrollTop / rowHeight) - buffer);
25160
+ const end = Math.min(itemCount, Math.ceil((scrollTop + clientHeight) / rowHeight) + buffer);
25161
+ return { start, end };
25162
+ }
25163
+ function getGridVirtualSlice(items, start, end, enabled) {
25164
+ if (!enabled)
25165
+ return items;
25166
+ if (start >= items.length)
25167
+ return [];
25168
+ return items.slice(start, Math.min(end, items.length));
25169
+ }
25170
+ function getGridVirtualTotalHeight(itemCount, rowHeight) {
25171
+ return itemCount * rowHeight;
25172
+ }
25173
+ function getGridVirtualOffset(start, rowHeight) {
25174
+ return start * rowHeight;
25175
+ }
25176
+
24586
25177
  let gridIdSequence = 0;
24587
25178
  class JvGridComponent {
24588
25179
  gridId = `jv-grid-${++gridIdSequence}`;
@@ -24608,7 +25199,10 @@ class JvGridComponent {
24608
25199
  columnResize = output();
24609
25200
  columnReorder = output();
24610
25201
  rowEdit = output();
24611
- effectiveOptions = computed(() => ({ ...JV_GRID_DEFAULT_OPTIONS, ...this.options() }), ...(ngDevMode ? [{ debugName: "effectiveOptions" }] : /* istanbul ignore next */ []));
25202
+ effectiveOptions = computed(() => ({
25203
+ ...JV_GRID_DEFAULT_OPTIONS,
25204
+ ...this.options(),
25205
+ }), ...(ngDevMode ? [{ debugName: "effectiveOptions" }] : /* istanbul ignore next */ []));
24612
25206
  idKey = computed(() => {
24613
25207
  const tb = this.trackBy();
24614
25208
  if (typeof tb === 'string')
@@ -24622,14 +25216,9 @@ class JvGridComponent {
24622
25216
  columnWidths = signal({}, ...(ngDevMode ? [{ debugName: "columnWidths" }] : /* istanbul ignore next */ []));
24623
25217
  activeCell = signal({ row: 0, column: 0 }, ...(ngDevMode ? [{ debugName: "activeCell" }] : /* istanbul ignore next */ []));
24624
25218
  visibleColumns = computed(() => {
24625
- const cols = this.flattenColumns(this.columns()).filter(col => !col.hidden);
24626
- const order = this.columnOrder();
24627
- if (order.length > 0) {
24628
- return order.map(key => cols.find(c => this.colKey(c) === key)).filter(Boolean);
24629
- }
24630
- return cols;
25219
+ return getVisibleGridColumns(this.columns(), this.columnOrder());
24631
25220
  }, ...(ngDevMode ? [{ debugName: "visibleColumns" }] : /* istanbul ignore next */ []));
24632
- groupedHeaders = computed(() => this.columns().filter(col => col.children && col.children.length > 0), ...(ngDevMode ? [{ debugName: "groupedHeaders" }] : /* istanbul ignore next */ []));
25221
+ groupedHeaders = computed(() => getGroupedGridColumns(this.columns()), ...(ngDevMode ? [{ debugName: "groupedHeaders" }] : /* istanbul ignore next */ []));
24633
25222
  internalSelectedIds = signal([], ...(ngDevMode ? [{ debugName: "internalSelectedIds" }] : /* istanbul ignore next */ []));
24634
25223
  searchTerm = signal('', ...(ngDevMode ? [{ debugName: "searchTerm" }] : /* istanbul ignore next */ []));
24635
25224
  sortState = signal({ columnKey: '', direction: null }, ...(ngDevMode ? [{ debugName: "sortState" }] : /* istanbul ignore next */ []));
@@ -24638,8 +25227,8 @@ class JvGridComponent {
24638
25227
  pageSize = computed(() => this.internalPageSize() ?? this.effectiveOptions().pageSize ?? 20, ...(ngDevMode ? [{ debugName: "pageSize" }] : /* istanbul ignore next */ []));
24639
25228
  pageSizeStr = computed(() => String(this.pageSize()), ...(ngDevMode ? [{ debugName: "pageSizeStr" }] : /* istanbul ignore next */ []));
24640
25229
  pageSizeOptions = computed(() => (this.effectiveOptions().pageSizeOptions ?? [])
24641
- .filter(size => Number.isInteger(size) && size > 0)
24642
- .map(size => ({ label: String(size), value: String(size) })), ...(ngDevMode ? [{ debugName: "pageSizeOptions" }] : /* istanbul ignore next */ []));
25230
+ .filter((size) => Number.isInteger(size) && size > 0)
25231
+ .map((size) => ({ label: String(size), value: String(size) })), ...(ngDevMode ? [{ debugName: "pageSizeOptions" }] : /* istanbul ignore next */ []));
24643
25232
  pageSizeInputSync = effect(() => {
24644
25233
  void this.options().pageSize;
24645
25234
  this.internalPageSize.set(null);
@@ -24654,71 +25243,18 @@ class JvGridComponent {
24654
25243
  this.pageIndex.set(Math.max(0, lastPage));
24655
25244
  }, ...(ngDevMode ? [{ debugName: "pageBoundsGuard" }] : /* istanbul ignore next */ []));
24656
25245
  selectedIdSet = computed(() => {
24657
- const fromInput = this.selectedIds();
24658
- const fromInternal = this.internalSelectedIds();
24659
- const ids = fromInput !== null ? fromInput : fromInternal;
24660
- return new Set(ids);
25246
+ return resolveGridSelection(this.selectedIds(), this.internalSelectedIds());
24661
25247
  }, ...(ngDevMode ? [{ debugName: "selectedIdSet" }] : /* istanbul ignore next */ []));
24662
- hasActiveFilters = computed(() => Object.values(this.colFilters()).some(f => f.value.trim().length > 0), ...(ngDevMode ? [{ debugName: "hasActiveFilters" }] : /* istanbul ignore next */ []));
25248
+ hasActiveFilters = computed(() => Object.values(this.colFilters()).some((f) => f.value.trim().length > 0), ...(ngDevMode ? [{ debugName: "hasActiveFilters" }] : /* istanbul ignore next */ []));
24663
25249
  filteredData = computed(() => {
24664
25250
  if (this.effectiveOptions().serverSide)
24665
25251
  return this.data();
24666
- let items = this.data();
24667
- const term = this.searchTerm().trim().toLowerCase();
24668
- if (term) {
24669
- items = items.filter(row => {
24670
- return this.visibleColumns().some(col => {
24671
- if (col.searchable === false)
24672
- return false;
24673
- const value = this.getRawCellValue(row, col);
24674
- if (value == null)
24675
- return false;
24676
- return this.getSearchValue(value, col).includes(term);
24677
- });
24678
- });
24679
- }
24680
- const filters = this.colFilters();
24681
- const hasFilters = Object.values(filters).some(f => f.value.trim().length > 0);
24682
- if (hasFilters) {
24683
- items = items.filter(row => {
24684
- return Object.entries(filters).every(([key, filter]) => {
24685
- if (!filter.value.trim())
24686
- return true;
24687
- const col = this.columns().find(c => this.colKey(c) === key);
24688
- if (!col || col.filterable === false)
24689
- return true;
24690
- const value = this.getSearchValue(this.getRawCellValue(row, col), col);
24691
- const fv = filter.value.toLowerCase();
24692
- switch (filter.operator) {
24693
- case 'contains': return value.includes(fv);
24694
- case 'equals': return value === fv;
24695
- case 'startsWith': return value.startsWith(fv);
24696
- case 'endsWith': return value.endsWith(fv);
24697
- default: return value.includes(fv);
24698
- }
24699
- });
24700
- });
24701
- }
24702
- return items;
25252
+ return filterGridData(this.data(), this.visibleColumns(), this.searchTerm(), this.colFilters(), (column) => this.colKey(column), (row, column) => this.getRawCellValue(row, column), (value, column) => this.getSearchValue(value, column));
24703
25253
  }, ...(ngDevMode ? [{ debugName: "filteredData" }] : /* istanbul ignore next */ []));
24704
25254
  sortedData = computed(() => {
24705
25255
  if (this.effectiveOptions().serverSide)
24706
25256
  return this.filteredData();
24707
- const items = this.filteredData();
24708
- const { columnKey, direction } = this.sortState();
24709
- if (!columnKey || !direction)
24710
- return items;
24711
- return [...items].sort((a, b) => {
24712
- const col = this.findColumn(columnKey);
24713
- const va = this.getComparableValue(this.getRawCellValue(a, col), col);
24714
- const vb = this.getComparableValue(this.getRawCellValue(b, col), col);
24715
- if (va == null)
24716
- return 1;
24717
- if (vb == null)
24718
- return -1;
24719
- const cmp = va < vb ? -1 : va > vb ? 1 : 0;
24720
- return direction === 'asc' ? cmp : -cmp;
24721
- });
25257
+ return sortGridData(this.filteredData(), this.sortState(), (key) => this.findColumn(key), (row, column) => this.getRawCellValue(row, column), (value, column) => this.getComparableValue(value, column));
24722
25258
  }, ...(ngDevMode ? [{ debugName: "sortedData" }] : /* istanbul ignore next */ []));
24723
25259
  effectiveTotalItems = computed(() => {
24724
25260
  if (this.effectiveOptions().serverSide) {
@@ -24732,10 +25268,7 @@ class JvGridComponent {
24732
25268
  return this.virtualDisplayData();
24733
25269
  if (!this.effectiveOptions().pageable)
24734
25270
  return this.sortedData();
24735
- const items = this.sortedData();
24736
- const ps = this.pageSize();
24737
- const page = Math.min(this.pageIndex(), this.effectiveTotalPages() - 1);
24738
- return items.slice(page * ps, page * ps + ps);
25271
+ return paginateGridData(this.sortedData(), this.pageIndex(), this.pageSize(), this.effectiveTotalPages());
24739
25272
  }, ...(ngDevMode ? [{ debugName: "displayData" }] : /* istanbul ignore next */ []));
24740
25273
  virtualScrollEnabled = computed(() => this.effectiveOptions().virtualScroll && !this.effectiveOptions().serverSide, ...(ngDevMode ? [{ debugName: "virtualScrollEnabled" }] : /* istanbul ignore next */ []));
24741
25274
  virtualStartIndex = signal(0, ...(ngDevMode ? [{ debugName: "virtualStartIndex" }] : /* istanbul ignore next */ []));
@@ -24746,23 +25279,16 @@ class JvGridComponent {
24746
25279
  this.virtualEndIndex.set(Math.min(20, length));
24747
25280
  }, ...(ngDevMode ? [{ debugName: "virtualRangeResetter" }] : /* istanbul ignore next */ []));
24748
25281
  virtualDisplayData = computed(() => {
24749
- if (!this.virtualScrollEnabled())
24750
- return this.sortedData();
24751
- const items = this.sortedData();
24752
- const start = this.virtualStartIndex();
24753
- const end = this.virtualEndIndex();
24754
- if (start >= items.length)
24755
- return [];
24756
- return items.slice(start, Math.min(end, items.length));
25282
+ return getGridVirtualSlice(this.sortedData(), this.virtualStartIndex(), this.virtualEndIndex(), !!this.virtualScrollEnabled());
24757
25283
  }, ...(ngDevMode ? [{ debugName: "virtualDisplayData" }] : /* istanbul ignore next */ []));
24758
25284
  virtualTotalHeight = computed(() => {
24759
25285
  const count = this.effectiveOptions().serverSide
24760
25286
  ? this.effectiveTotalItems()
24761
25287
  : this.sortedData().length;
24762
- return count * (this.effectiveOptions().virtualScrollRowHeight ?? 48);
25288
+ return getGridVirtualTotalHeight(count, this.effectiveOptions().virtualScrollRowHeight ?? 48);
24763
25289
  }, ...(ngDevMode ? [{ debugName: "virtualTotalHeight" }] : /* istanbul ignore next */ []));
24764
25290
  virtualOffsetY = computed(() => {
24765
- return this.virtualStartIndex() * (this.effectiveOptions().virtualScrollRowHeight ?? 48);
25291
+ return getGridVirtualOffset(this.virtualStartIndex(), this.effectiveOptions().virtualScrollRowHeight ?? 48);
24766
25292
  }, ...(ngDevMode ? [{ debugName: "virtualOffsetY" }] : /* istanbul ignore next */ []));
24767
25293
  pageStart = computed(() => this.effectiveTotalItems() === 0 ? 0 : this.pageIndex() * this.pageSize() + 1, ...(ngDevMode ? [{ debugName: "pageStart" }] : /* istanbul ignore next */ []));
24768
25294
  pageEnd = computed(() => Math.min((this.pageIndex() + 1) * this.pageSize(), this.effectiveTotalItems()), ...(ngDevMode ? [{ debugName: "pageEnd" }] : /* istanbul ignore next */ []));
@@ -24778,11 +25304,11 @@ class JvGridComponent {
24778
25304
  }, ...(ngDevMode ? [{ debugName: "pageRange" }] : /* istanbul ignore next */ []));
24779
25305
  allSelected = computed(() => {
24780
25306
  const rows = this.displayData();
24781
- return rows.length > 0 && rows.every(r => this.selectedIdSet().has(this.getRowId(r)));
25307
+ return areAllGridRowsSelected(rows, this.selectedIdSet(), (row) => this.getRowId(row));
24782
25308
  }, ...(ngDevMode ? [{ debugName: "allSelected" }] : /* istanbul ignore next */ []));
24783
25309
  someSelected = computed(() => {
24784
25310
  const rows = this.displayData();
24785
- return rows.some(r => this.selectedIdSet().has(this.getRowId(r))) && !this.allSelected();
25311
+ return areSomeGridRowsSelected(rows, this.selectedIdSet(), (row) => this.getRowId(row));
24786
25312
  }, ...(ngDevMode ? [{ debugName: "someSelected" }] : /* istanbul ignore next */ []));
24787
25313
  colspan = computed(() => {
24788
25314
  let count = this.visibleColumns().length;
@@ -24800,9 +25326,6 @@ class JvGridComponent {
24800
25326
  const active = this.activeCell();
24801
25327
  return active.row === rowIndex && active.column === columnIndex ? 0 : -1;
24802
25328
  }
24803
- flattenColumns(columns) {
24804
- return columns.flatMap(col => col.children?.length ? this.flattenColumns(col.children) : [col]);
24805
- }
24806
25329
  onCellFocus(rowIndex, columnIndex) {
24807
25330
  if (this.resolvedOptions().keyboardNavigation) {
24808
25331
  this.activeCell.set({ row: rowIndex, column: columnIndex });
@@ -24812,34 +25335,26 @@ class JvGridComponent {
24812
25335
  if (!this.resolvedOptions().keyboardNavigation)
24813
25336
  return;
24814
25337
  const target = event.target;
24815
- if (target !== event.currentTarget && target.closest('button, input, select, textarea, [contenteditable="true"]')) {
25338
+ if (target !== event.currentTarget &&
25339
+ target.closest('button, input, select, textarea, [contenteditable="true"]')) {
24816
25340
  return;
24817
25341
  }
24818
- const movement = {
24819
- ArrowDown: [1, 0],
24820
- ArrowLeft: [0, -1],
24821
- ArrowRight: [0, 1],
24822
- };
24823
- if (event.key === 'ArrowUp' && rowIndex === 0) {
25342
+ const targetPosition = getCellKeyboardTarget(event.key, rowIndex, columnIndex, this.displayData().length, this.visibleColumns().length);
25343
+ if (!targetPosition)
25344
+ return;
25345
+ if (targetPosition.kind === 'header') {
24824
25346
  event.preventDefault();
24825
- this.activeCell.set({ row: 0, column: columnIndex });
25347
+ this.activeCell.set({ row: targetPosition.row, column: targetPosition.column });
24826
25348
  const table = event.currentTarget.closest('table');
24827
- table?.querySelector(`[data-column-index="${columnIndex}"][role="columnheader"]`)?.focus();
25349
+ table
25350
+ ?.querySelector(`[data-column-index="${targetPosition.column}"][role="columnheader"]`)
25351
+ ?.focus();
24828
25352
  return;
24829
25353
  }
24830
- const delta = event.key === 'ArrowUp' ? [-1, 0] : movement[event.key];
24831
- if (!delta)
24832
- return;
24833
- const nextRow = rowIndex + delta[0];
24834
- const nextColumn = columnIndex + delta[1];
24835
- const rows = this.displayData();
24836
- const columns = this.visibleColumns();
24837
- if (nextRow < 0 || nextRow >= rows.length || nextColumn < 0 || nextColumn >= columns.length)
24838
- return;
24839
25354
  event.preventDefault();
24840
- this.activeCell.set({ row: nextRow, column: nextColumn });
25355
+ this.activeCell.set({ row: targetPosition.row, column: targetPosition.column });
24841
25356
  const table = event.currentTarget.closest('table');
24842
- const nextCell = table?.querySelector(`.jv-grid-keyboard-cell[data-row-index="${nextRow}"][data-column-index="${nextColumn}"]`);
25357
+ const nextCell = table?.querySelector(`.jv-grid-keyboard-cell[data-row-index="${targetPosition.row}"][data-column-index="${targetPosition.column}"]`);
24843
25358
  nextCell?.focus();
24844
25359
  }
24845
25360
  onCellF2(event, rowIndex, columnIndex) {
@@ -24861,7 +25376,10 @@ class JvGridComponent {
24861
25376
  onHeaderKeydown(event, columnIndex) {
24862
25377
  if (!this.resolvedOptions().keyboardNavigation)
24863
25378
  return;
24864
- if (event.key === 'ArrowDown') {
25379
+ const targetPosition = getHeaderKeyboardTarget(event.key, columnIndex, this.visibleColumns().length);
25380
+ if (!targetPosition)
25381
+ return;
25382
+ if (targetPosition.kind === 'cell') {
24865
25383
  const table = event.currentTarget.closest('table');
24866
25384
  const firstCell = table?.querySelector(`.jv-grid-keyboard-cell[data-row-index="0"][data-column-index="${columnIndex}"]`);
24867
25385
  if (firstCell) {
@@ -24871,16 +25389,12 @@ class JvGridComponent {
24871
25389
  }
24872
25390
  return;
24873
25391
  }
24874
- const delta = event.key === 'ArrowLeft' ? -1 : event.key === 'ArrowRight' ? 1 : 0;
24875
- if (!delta)
24876
- return;
24877
- const nextColumn = columnIndex + delta;
24878
- if (nextColumn < 0 || nextColumn >= this.visibleColumns().length)
24879
- return;
24880
25392
  event.preventDefault();
24881
- this.activeCell.set({ row: 0, column: nextColumn });
25393
+ this.activeCell.set({ row: targetPosition.row, column: targetPosition.column });
24882
25394
  const table = event.currentTarget.closest('table');
24883
- table?.querySelector(`[data-column-index="${nextColumn}"][role="columnheader"]`)?.focus();
25395
+ table
25396
+ ?.querySelector(`[data-column-index="${targetPosition.column}"][role="columnheader"]`)
25397
+ ?.focus();
24884
25398
  }
24885
25399
  editState = signal(null, ...(ngDevMode ? [{ debugName: "editState" }] : /* istanbul ignore next */ []));
24886
25400
  t = (key, params) => this.translationService.translate(key, params);
@@ -24892,7 +25406,7 @@ class JvGridComponent {
24892
25406
  const tb = this.trackBy();
24893
25407
  if (typeof tb === 'function')
24894
25408
  return tb(row, index ?? 0);
24895
- if (row && typeof row === 'object' && ('id' in row)) {
25409
+ if (row && typeof row === 'object' && 'id' in row) {
24896
25410
  const id = record['id'];
24897
25411
  if (typeof id === 'string' || typeof id === 'number')
24898
25412
  return id;
@@ -24912,106 +25426,22 @@ class JvGridComponent {
24912
25426
  return this.getRawCellValue(row, col);
24913
25427
  }
24914
25428
  getEditValue(row, col) {
24915
- const val = this.getCellValue(row, col);
24916
- const editType = this.editType(col);
24917
- if (editType === 'date')
24918
- return this.toDateInputValue(val);
24919
- if (editType === 'datetime')
24920
- return this.toDateTimeInputValue(val);
24921
- if (editType === 'time')
24922
- return this.toTimeInputValue(val);
24923
- return val == null ? '' : String(val);
25429
+ return getGridEditValue(this.getCellValue(row, col), col, (value) => this.formatService.toDate(value));
24924
25430
  }
24925
25431
  getBooleanEditValue(row, col) {
24926
- const value = this.getCellValue(row, col);
24927
- return value === true || value === 'true' || value === 1 || value === '1';
25432
+ return isGridBooleanTrue(this.getCellValue(row, col));
24928
25433
  }
24929
25434
  isBooleanTrue(value) {
24930
- return value === true || value === 'true' || value === 1 || value === '1';
25435
+ return isGridBooleanTrue(value);
24931
25436
  }
24932
25437
  editType(col) {
24933
- if (col.editType)
24934
- return col.editType;
24935
- switch (col.type) {
24936
- case 'integer':
24937
- case 'number':
24938
- case 'currency':
24939
- case 'percent':
24940
- return 'number';
24941
- case 'date':
24942
- return 'date';
24943
- case 'time':
24944
- return 'time';
24945
- case 'datetime':
24946
- return 'datetime';
24947
- case 'boolean':
24948
- return 'boolean';
24949
- default:
24950
- return 'text';
24951
- }
25438
+ return getGridEditType(col);
24952
25439
  }
24953
25440
  inputType(col) {
24954
- switch (this.editType(col)) {
24955
- case 'number': return 'number';
24956
- case 'date': return 'date';
24957
- case 'time': return 'time';
24958
- case 'datetime': return 'datetime-local';
24959
- default: return 'text';
24960
- }
25441
+ return getGridInputType(col);
24961
25442
  }
24962
25443
  normalizeEditValue(col, value) {
24963
- switch (this.editType(col)) {
24964
- case 'number': {
24965
- if (value === '' || value == null)
24966
- return null;
24967
- const number = Number(value);
24968
- return Number.isFinite(number) ? number : null;
24969
- }
24970
- case 'date':
24971
- return this.toDateInputValue(value) || null;
24972
- case 'datetime': {
24973
- if (value === '' || value == null)
24974
- return null;
24975
- const date = new Date(String(value));
24976
- return Number.isNaN(date.getTime()) ? null : date.toISOString();
24977
- }
24978
- case 'time': {
24979
- if (value === '' || value == null)
24980
- return null;
24981
- const time = String(value);
24982
- return /^\d{2}:\d{2}$/.test(time) ? `${time}:00` : time;
24983
- }
24984
- case 'boolean':
24985
- return Boolean(value);
24986
- default:
24987
- return value;
24988
- }
24989
- }
24990
- toDateInputValue(value) {
24991
- if (typeof value === 'string') {
24992
- const match = value.match(/^\d{4}-\d{2}-\d{2}/);
24993
- if (match)
24994
- return match[0];
24995
- }
24996
- const date = this.formatService.toDate(value);
24997
- if (!date)
24998
- return '';
24999
- return `${date.getUTCFullYear()}-${String(date.getUTCMonth() + 1).padStart(2, '0')}-${String(date.getUTCDate()).padStart(2, '0')}`;
25000
- }
25001
- toDateTimeInputValue(value) {
25002
- const date = this.formatService.toDate(value);
25003
- if (!date)
25004
- return '';
25005
- const pad = (part) => String(part).padStart(2, '0');
25006
- return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())}T${pad(date.getHours())}:${pad(date.getMinutes())}`;
25007
- }
25008
- toTimeInputValue(value) {
25009
- if (typeof value === 'string' && /^\d{2}:\d{2}(:\d{2})?$/.test(value))
25010
- return value.slice(0, 5);
25011
- const date = this.formatService.toDate(value);
25012
- if (!date)
25013
- return '';
25014
- return `${String(date.getHours()).padStart(2, '0')}:${String(date.getMinutes()).padStart(2, '0')}`;
25444
+ return normalizeGridEditValue(col, value, (input) => this.formatService.toDate(input));
25015
25445
  }
25016
25446
  formatValue(value, col, _row) {
25017
25447
  if (value == null)
@@ -25098,7 +25528,7 @@ class JvGridComponent {
25098
25528
  return (this.resolvedOptions().editable || col.editable === true) && col.editable !== false;
25099
25529
  }
25100
25530
  groupColspan(group) {
25101
- return this.flattenColumns(group.children ?? []).filter(col => !col.hidden).length || 1;
25531
+ return flattenGridColumns(group.children ?? []).filter((col) => !col.hidden).length || 1;
25102
25532
  }
25103
25533
  isEditingCell(row, col) {
25104
25534
  const es = this.editState();
@@ -25109,13 +25539,13 @@ class JvGridComponent {
25109
25539
  return es !== null && es.rowId === this.getRowId(row);
25110
25540
  }
25111
25541
  getEditOptions(col) {
25112
- return (col.editOptions || []).map(opt => ({
25542
+ return (col.editOptions || []).map((opt) => ({
25113
25543
  label: opt.label,
25114
25544
  value: String(opt.value),
25115
25545
  }));
25116
25546
  }
25117
25547
  findColumn(key) {
25118
- return this.visibleColumns().find(c => this.colKey(c) === key) ?? { key, header: key };
25548
+ return this.visibleColumns().find((c) => this.colKey(c) === key) ?? { key, header: key };
25119
25549
  }
25120
25550
  toggleSort(col, event) {
25121
25551
  if (col.sortable === false || !this.resolvedOptions().sortable)
@@ -25153,13 +25583,7 @@ class JvGridComponent {
25153
25583
  return this.selectedIdSet().has(this.getRowId(row));
25154
25584
  }
25155
25585
  toggleRow(row) {
25156
- const id = this.getRowId(row);
25157
- const set = new Set(this.selectedIdSet());
25158
- if (set.has(id))
25159
- set.delete(id);
25160
- else
25161
- set.add(id);
25162
- const nextIds = Array.from(set);
25586
+ const nextIds = toggleGridRowSelection(this.selectedIdSet(), this.getRowId(row));
25163
25587
  if (this.selectedIds() === null)
25164
25588
  this.internalSelectedIds.set(nextIds);
25165
25589
  this.selectionChange.emit(nextIds);
@@ -25173,15 +25597,7 @@ class JvGridComponent {
25173
25597
  this.pageChange.emit({ pageIndex: 0, pageSize });
25174
25598
  }
25175
25599
  toggleSelectAll(checked) {
25176
- const set = new Set(this.selectedIdSet());
25177
- const rows = this.displayData();
25178
- rows.forEach(r => {
25179
- if (checked)
25180
- set.add(this.getRowId(r));
25181
- else
25182
- set.delete(this.getRowId(r));
25183
- });
25184
- const nextIds = Array.from(set);
25600
+ const nextIds = toggleGridRowsSelection(this.selectedIdSet(), this.displayData(), checked, (row) => this.getRowId(row));
25185
25601
  if (this.selectedIds() === null)
25186
25602
  this.internalSelectedIds.set(nextIds);
25187
25603
  this.selectionChange.emit(nextIds);
@@ -25205,7 +25621,7 @@ class JvGridComponent {
25205
25621
  }
25206
25622
  onColumnFilterChange(col, value) {
25207
25623
  const key = this.colKey(col);
25208
- this.colFilters.update(f => ({
25624
+ this.colFilters.update((f) => ({
25209
25625
  ...f,
25210
25626
  [key]: { key, value, operator: 'contains' },
25211
25627
  }));
@@ -25215,8 +25631,8 @@ class JvGridComponent {
25215
25631
  getRowLabel(row, _index) {
25216
25632
  const label = this.visibleColumns()
25217
25633
  .slice(0, 2)
25218
- .map(col => this.getCellValue(row, col))
25219
- .filter(v => v != null)
25634
+ .map((col) => this.getCellValue(row, col))
25635
+ .filter((v) => v != null)
25220
25636
  .join(' - ');
25221
25637
  return `${label}${this.isSelected(row) ? ` (${this.t('grid.selected')})` : ''}`;
25222
25638
  }
@@ -25250,7 +25666,7 @@ class JvGridComponent {
25250
25666
  this.editState.set(null);
25251
25667
  }
25252
25668
  onEditValueChange(row, col, value) {
25253
- const option = col.editOptions?.find(item => String(item.value) === value);
25669
+ const option = col.editOptions?.find((item) => String(item.value) === value);
25254
25670
  this.commitEdit(row, col, option?.value ?? value);
25255
25671
  }
25256
25672
  // Column resizing
@@ -25275,7 +25691,7 @@ class JvGridComponent {
25275
25691
  return;
25276
25692
  const diff = event.clientX - this.resizeData.startX;
25277
25693
  const newWidth = Math.max(30, this.resizeData.startWidth + diff);
25278
- this.columnWidths.update(w => ({
25694
+ this.columnWidths.update((w) => ({
25279
25695
  ...w,
25280
25696
  [this.colKey(this.resizeData.col)]: `${newWidth}px`,
25281
25697
  }));
@@ -25310,7 +25726,7 @@ class JvGridComponent {
25310
25726
  return;
25311
25727
  if (this.dragIndex === targetIdx)
25312
25728
  return;
25313
- const cols = this.visibleColumns().map(c => this.colKey(c));
25729
+ const cols = this.visibleColumns().map((c) => this.colKey(c));
25314
25730
  const [moved] = cols.splice(this.dragIndex, 1);
25315
25731
  cols.splice(targetIdx, 0, moved);
25316
25732
  this.columnOrder.set(cols);
@@ -25325,13 +25741,9 @@ class JvGridComponent {
25325
25741
  if (!this.virtualScrollEnabled())
25326
25742
  return;
25327
25743
  const rowHeight = this.effectiveOptions().virtualScrollRowHeight ?? 48;
25328
- const buffer = 5;
25329
- const scrollTop = target.scrollTop;
25330
- const clientHeight = target.clientHeight;
25331
- const start = Math.max(0, Math.floor(scrollTop / rowHeight) - buffer);
25332
- const end = Math.min(this.sortedData().length, Math.ceil((scrollTop + clientHeight) / rowHeight) + buffer);
25333
- this.virtualStartIndex.set(start);
25334
- this.virtualEndIndex.set(end);
25744
+ const range = getGridVirtualRange(target.scrollTop, target.clientHeight, rowHeight, this.sortedData().length);
25745
+ this.virtualStartIndex.set(range.start);
25746
+ this.virtualEndIndex.set(range.end);
25335
25747
  }
25336
25748
  // Export
25337
25749
  exportCsv() {
@@ -25354,753 +25766,23 @@ class JvGridComponent {
25354
25766
  return configuredName.replace(/\.(csv|xlsx)$/i, '') + `.${extension}`;
25355
25767
  }
25356
25768
  buildExportMatrix() {
25357
- const cols = this.visibleColumns().map(c => ({ key: this.colKey(c), header: c.header, column: c }));
25769
+ const cols = this.visibleColumns().map((c) => ({
25770
+ key: this.colKey(c),
25771
+ header: c.header,
25772
+ column: c,
25773
+ }));
25358
25774
  const allData = this.effectiveOptions().serverSide ? this.data() : this.sortedData();
25359
25775
  return {
25360
- header: cols.map(c => c.header),
25361
- rows: allData.map(row => cols.map(col => this.getExportValue(row[col.key], col.column))),
25776
+ header: cols.map((c) => c.header),
25777
+ rows: allData.map((row) => cols.map((col) => this.getExportValue(row[col.key], col.column))),
25362
25778
  };
25363
25779
  }
25364
25780
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: JvGridComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
25365
- 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: `
25366
- <div class="jv-grid" role="region" [attr.aria-label]="resolvedOptions().ariaLabel || gridLabel()">
25367
- @if (resolvedOptions().searchable || resolvedOptions().exportable) {
25368
- <div class="jv-grid-toolbar">
25369
- @if (resolvedOptions().searchable) {
25370
- <div class="jv-grid-search">
25371
- <jv-icon name="search" [size]="16" />
25372
- <jv-input
25373
- [inputId]="gridSearchInputId"
25374
- [placeholder]="t('grid.searchPlaceholder')"
25375
- [ngModel]="searchTerm()"
25376
- (ngModelChange)="onSearchInput($event)"
25377
- />
25378
- </div>
25379
- }
25380
- @if (resolvedOptions().exportable) {
25381
- <div class="jv-grid-export-actions">
25382
- <jv-button variant="outline" icon="download" (click)="exportCsv()">{{ t('grid.exportCsv') }}</jv-button>
25383
- <jv-button variant="outline" icon="file-spreadsheet" (click)="exportExcel()">{{ t('grid.exportExcel') }}</jv-button>
25384
- </div>
25385
- }
25386
- </div>
25387
- }
25388
-
25389
- @if (resolvedOptions().loading) {
25390
- <div class="jv-grid-loading" role="status" aria-live="polite">
25391
- <span class="jv-grid-spinner" aria-hidden="true"></span>
25392
- <span>{{ t('grid.loading') }}</span>
25393
- </div>
25394
- } @else {
25395
- <div
25396
- class="jv-grid-table-wrap"
25397
- [class.jv-grid-virtual]="virtualScrollEnabled()"
25398
- #tableWrap
25399
- (scroll)="onVirtualScroll($any($event.target))"
25400
- >
25401
- @if (virtualScrollEnabled()) {
25402
- <div class="jv-grid-virtual-spacer" [style.height.px]="virtualTotalHeight()"></div>
25403
- }
25404
- <table
25405
- class="jv-grid-table"
25406
- role="grid"
25407
- [attr.aria-label]="resolvedOptions().ariaLabel || gridLabel()"
25408
- [attr.aria-rowcount]="effectiveTotalItems()"
25409
- [attr.aria-colcount]="colspan()"
25410
- [class]="densityClass()"
25411
- [style.position]="virtualScrollEnabled() ? 'relative' : null"
25412
- [style.top.px]="virtualOffsetY()"
25413
- >
25414
- <caption class="sr-only">{{ resolvedOptions().ariaLabel || gridLabel() }}</caption>
25415
-
25416
- @let hasGroupedHeaders = groupedHeaders().length > 0;
25417
-
25418
- <thead class="jv-grid-thead">
25419
- @if (hasGroupedHeaders) {
25420
- <tr>
25421
- @if (resolvedOptions().selectable) {
25422
- <th class="jv-grid-th jv-grid-th-select" scope="col" [attr.rowspan]="2">
25423
- <input
25424
- type="checkbox"
25425
- class="jv-grid-checkbox"
25426
- [checked]="allSelected()"
25427
- (change)="toggleSelectAll($any($event.target).checked)"
25428
- [attr.aria-label]="allSelected() ? t('grid.deselectAll') : t('grid.selectAll')"
25429
- [indeterminate]="someSelected()"
25430
- />
25431
- </th>
25432
- }
25433
- @for (group of groupedHeaders(); track colKey(group)) {
25434
- <th
25435
- class="jv-grid-th"
25436
- [class]="group.headerClass || ''"
25437
- [class.jv-grid-th-sticky-left]="isStickyLeft(group)"
25438
- [class.jv-grid-th-sticky-right]="isStickyRight(group)"
25439
- [attr.colspan]="groupColspan(group)"
25440
- scope="colgroup"
25441
- [style.text-align]="group.align ?? 'start'"
25442
- >
25443
- <div class="jv-grid-th-content">{{ group.header }}</div>
25444
- </th>
25445
- }
25446
- @if (actions().length > 0) {
25447
- <th class="jv-grid-th jv-grid-th-actions" scope="col" [attr.rowspan]="2">
25448
- <span class="sr-only">{{ t('grid.actionsLabel', { label: '' }) }}</span>
25449
- </th>
25450
- }
25451
- </tr>
25452
- }
25453
-
25454
- <tr>
25455
- @if (!hasGroupedHeaders && resolvedOptions().selectable) {
25456
- <th class="jv-grid-th jv-grid-th-select" scope="col">
25457
- <input
25458
- type="checkbox"
25459
- class="jv-grid-checkbox"
25460
- [checked]="allSelected()"
25461
- (change)="toggleSelectAll($any($event.target).checked)"
25462
- [attr.aria-label]="allSelected() ? t('grid.deselectAll') : t('grid.selectAll')"
25463
- [indeterminate]="someSelected()"
25464
- />
25465
- </th>
25466
- }
25467
- @for (col of visibleColumns(); track colKey(col); let colIdx = $index) {
25468
- <th
25469
- class="jv-grid-th"
25470
- [class]="col.headerClass || ''"
25471
- [class.jv-grid-th-sortable]="col.sortable !== false && resolvedOptions().sortable"
25472
- [class.jv-grid-th-sticky-left]="isStickyLeft(col)"
25473
- [class.jv-grid-th-sticky-right]="isStickyRight(col)"
25474
- [class.jv-grid-th-reorderable]="resolvedOptions().reorderableColumns"
25475
- [style.text-align]="col.align ?? 'start'"
25476
- [style.width]="getColumnWidth(col)"
25477
- [style.min-width]="col.minWidth || undefined"
25478
- [style.max-width]="col.maxWidth || undefined"
25479
- scope="col"
25480
- role="columnheader"
25481
- [attr.data-column-index]="colIdx"
25482
- [attr.aria-sort]="getAriaSort(col)"
25483
- [attr.aria-label]="getSortLabel(col)"
25484
- [tabindex]="resolvedOptions().keyboardNavigation ? (activeCell().column === colIdx ? 0 : -1) : (col.sortable !== false && resolvedOptions().sortable ? 0 : -1)"
25485
- (click)="toggleSort(col)"
25486
- (focus)="onHeaderFocus(colIdx)"
25487
- (keydown)="onHeaderKeydown($event, colIdx)"
25488
- (keydown.enter)="toggleSort(col)"
25489
- (keydown.space)="toggleSort(col, $event)"
25490
- draggable="{{ resolvedOptions().reorderableColumns ? 'true' : 'false' }}"
25491
- (dragstart)="onDragStart($event, colIdx)"
25492
- (dragover)="onDragOver($event, colIdx)"
25493
- (drop)="onDrop($event, colIdx)"
25494
- (dragend)="onDragEnd($event)"
25495
- >
25496
- <div class="jv-grid-th-content">
25497
- <span>{{ col.header }}</span>
25498
- @if (col.sortable !== false && resolvedOptions().sortable) {
25499
- <span class="jv-grid-sort-icon" aria-hidden="true">
25500
- @if (sortState().columnKey === colKey(col)) {
25501
- @if (sortState().direction === 'asc') { ▲ }
25502
- @if (sortState().direction === 'desc') { ▼ }
25503
- }
25504
- </span>
25505
- }
25506
- </div>
25507
- @if (isColumnResizable(col)) {
25508
- <div
25509
- class="jv-grid-resize-handle"
25510
- (mousedown)="onResizeStart($event, col, colIdx)"
25511
- ></div>
25512
- }
25513
- </th>
25514
- }
25515
- @if (!hasGroupedHeaders && actions().length > 0) {
25516
- <th class="jv-grid-th jv-grid-th-actions" scope="col">
25517
- <span class="sr-only">{{ t('grid.actionsLabel', { label: '' }) }}</span>
25518
- </th>
25519
- }
25520
- </tr>
25521
-
25522
- @if (resolvedOptions().columnFilters) {
25523
- <tr class="jv-grid-filter-row">
25524
- @if (resolvedOptions().selectable) {
25525
- <th class="jv-grid-th jv-grid-th-select"></th>
25526
- }
25527
- @for (col of visibleColumns(); track colKey(col)) {
25528
- <th class="jv-grid-th jv-grid-th-filter">
25529
- @if (col.filterable !== false) {
25530
- <input
25531
- class="jv-grid-filter-input"
25532
- [placeholder]="t('grid.filterPlaceholder')"
25533
- [attr.aria-label]="t('grid.filterForColumn', { column: col.header })"
25534
- [ngModel]="colFilters()[colKey(col)]?.value ?? ''"
25535
- (ngModelChange)="onColumnFilterChange(col, $event)"
25536
- />
25537
- }
25538
- </th>
25539
- }
25540
- @if (actions().length > 0) {
25541
- <th class="jv-grid-th jv-grid-th-actions"></th>
25542
- }
25543
- </tr>
25544
- }
25545
- </thead>
25546
-
25547
- <tbody class="jv-grid-tbody">
25548
- @if (displayData().length === 0 && !searchTerm().trim() && !hasActiveFilters()) {
25549
- <tr>
25550
- <td class="jv-grid-empty" [attr.colspan]="colspan()">
25551
- {{ resolvedOptions().emptyMessage || t('grid.emptyMessage') }}
25552
- </td>
25553
- </tr>
25554
- } @else if (displayData().length === 0 && (searchTerm().trim() || hasActiveFilters())) {
25555
- <tr>
25556
- <td class="jv-grid-empty" [attr.colspan]="colspan()">
25557
- {{ resolvedOptions().noResultsMessage || t('grid.noResults') }}
25558
- </td>
25559
- </tr>
25560
- } @else {
25561
- @for (row of displayData(); track getRowId(row, $index); let rowIdx = $index) {
25562
- <tr
25563
- class="jv-grid-tr"
25564
- role="row"
25565
- [class.jv-grid-tr-editing]="isRowEditing(row)"
25566
- [class.jv-grid-tr-selected]="isSelected(row)"
25567
- [attr.aria-selected]="resolvedOptions().selectable ? isSelected(row) : null"
25568
- (click)="onRowClick(row)"
25569
- (dblclick)="onRowDblClick(row)"
25570
- (keydown.enter)="onRowClick(row)"
25571
- (keydown.space)="onRowClick($event, row)"
25572
- [tabindex]="-1"
25573
- >
25574
- @if (resolvedOptions().selectable) {
25575
- <td
25576
- class="jv-grid-td jv-grid-td-select"
25577
- [class.jv-grid-td-sticky-left]="resolvedOptions().stickyColumns"
25578
- >
25579
- <input
25580
- type="checkbox"
25581
- class="jv-grid-checkbox"
25582
- [checked]="isSelected(row)"
25583
- (change)="toggleRow(row); $event.stopPropagation()"
25584
- (keydown.space)="$event.stopPropagation()"
25585
- [attr.aria-label]="getRowLabel(row, $index)"
25586
- />
25587
- </td>
25588
- }
25589
- @for (col of visibleColumns(); track colKey(col); let colIdx = $index) {
25590
- <td
25591
- class="jv-grid-td"
25592
- [attr.role]="resolvedOptions().keyboardNavigation ? 'gridcell' : null"
25593
- [class]="col.cellClass || ''"
25594
- [class.jv-grid-td-sticky-left]="isStickyLeft(col)"
25595
- [class.jv-grid-td-sticky-right]="isStickyRight(col)"
25596
- [class.jv-grid-td-editable]="isCellEditable(col)"
25597
- [style.text-align]="col.align ?? 'start'"
25598
- (dblclick)="startEdit(row, col, $event)"
25599
- (keydown.enter)="startEdit(row, col, $event)"
25600
- [attr.data-row-index]="rowIdx"
25601
- [attr.data-column-index]="colIdx"
25602
- [class.jv-grid-keyboard-cell]="resolvedOptions().keyboardNavigation"
25603
- [attr.tabindex]="cellTabIndex(rowIdx, colIdx, col)"
25604
- (focus)="onCellFocus(rowIdx, colIdx)"
25605
- (keydown.f2)="onCellF2($event, rowIdx, colIdx)"
25606
- (keydown)="onCellKeydown($event, rowIdx, colIdx)"
25607
- >
25608
- @if (isEditingCell(row, col)) {
25609
- @if (editType(col) === 'select' && col.editOptions) {
25610
- <jv-select
25611
- [options]="getEditOptions(col)"
25612
- [modelValue]="getEditValue(row, col)"
25613
- (selectionChange)="onEditValueChange(row, col, $event)"
25614
- />
25615
- } @else if (editType(col) === 'boolean') {
25616
- <input
25617
- type="checkbox"
25618
- class="jv-grid-checkbox"
25619
- [checked]="getBooleanEditValue(row, col)"
25620
- (change)="commitEdit(row, col, normalizeEditValue(col, $any($event.target).checked))"
25621
- (keydown.enter)="commitEditFromKeyboard($event, row, col, normalizeEditValue(col, $any($event.target).checked))"
25622
- (keydown.escape)="cancelEdit()"
25623
- [attr.aria-label]="col.header"
25624
- />
25625
- } @else {
25626
- <input
25627
- class="jv-grid-edit-input"
25628
- [type]="inputType(col)"
25629
- [value]="getEditValue(row, col)"
25630
- (blur)="commitEdit(row, col, normalizeEditValue(col, $any($event.target).value))"
25631
- (keydown.enter)="commitEditFromKeyboard($event, row, col, normalizeEditValue(col, $any($event.target).value))"
25632
- (keydown.escape)="cancelEdit()"
25633
- />
25634
- }
25635
- } @else {
25636
- @let value = getCellValue(row, col);
25637
- @if (col.type === 'boolean') {
25638
- @if (isBooleanTrue(value)) {
25639
- <span class="jv-grid-boolean jv-grid-boolean-true" [attr.aria-label]="t('grid.booleanYes')">{{ t('grid.booleanYes') }}</span>
25640
- } @else {
25641
- <span class="jv-grid-boolean jv-grid-boolean-false" [attr.aria-label]="t('grid.booleanNo')">{{ t('grid.booleanNo') }}</span>
25642
- }
25643
- } @else if (col.format) {
25644
- {{ col.format(value, row) }}
25645
- } @else {
25646
- {{ formatValue(value, col, row) }}
25647
- }
25648
- }
25649
- </td>
25650
- }
25651
- @if (actions().length > 0) {
25652
- <td
25653
- class="jv-grid-td jv-grid-td-actions"
25654
- [class.jv-grid-td-sticky-right]="resolvedOptions().stickyColumns"
25655
- >
25656
- <div class="jv-grid-actions-group">
25657
- @for (action of actions(); track action.id) {
25658
- <jv-button
25659
- [variant]="actionVariant(action.variant)"
25660
- [icon]="action.icon ?? null"
25661
- [disabled]="action.disabled?.(row) ?? false"
25662
- [attr.aria-label]="action.label"
25663
- [title]="action.label"
25664
- (click)="onActionClick(action, row); $event.stopPropagation()"
25665
- ></jv-button>
25666
- }
25667
- </div>
25668
- </td>
25669
- }
25670
- </tr>
25671
- }
25672
- }
25673
- </tbody>
25674
- </table>
25675
- </div>
25676
- }
25677
-
25678
- @if (resolvedOptions().pageable && !virtualScrollEnabled() && effectiveTotalPages() > 1 && !resolvedOptions().loading) {
25679
- <nav class="jv-grid-pagination" role="navigation" [attr.aria-label]="t('grid.paginationLabel', { label: '' })">
25680
- <div class="jv-grid-pagination-info" aria-live="polite" aria-atomic="true">
25681
- {{ t('grid.itemsShowing', { start: pageStart(), end: pageEnd(), total: effectiveTotalItems() }) }}
25682
- </div>
25683
- <div class="jv-grid-pagination-controls">
25684
- @if (pageSizeOptions().length > 0) {
25685
- <jv-select
25686
- [options]="pageSizeOptions()"
25687
- [modelValue]="pageSizeStr()"
25688
- [ariaLabel]="t('grid.itemsPerPage')"
25689
- (selectionChange)="onPageSizeChange($event)"
25690
- />
25691
- }
25692
- <jv-button
25693
- variant="outline"
25694
- icon="chevrons-left"
25695
- [disabled]="pageIndex() === 0"
25696
- (click)="goToPage(0)"
25697
- [attr.aria-label]="t('grid.pageFirst')"
25698
- ></jv-button>
25699
- <jv-button
25700
- variant="outline"
25701
- icon="chevron-left"
25702
- [disabled]="pageIndex() === 0"
25703
- (click)="goToPage(pageIndex() - 1)"
25704
- [attr.aria-label]="t('grid.pagePrev')"
25705
- ></jv-button>
25706
- @for (p of pageRange(); track p) {
25707
- <jv-button
25708
- [variant]="p === pageIndex() ? 'primary' : 'outline'"
25709
- [attr.aria-current]="p === pageIndex() ? 'page' : null"
25710
- [attr.aria-label]="t('grid.pageOfTotal', { page: p + 1, total: effectiveTotalPages() })"
25711
- (click)="goToPage(p)"
25712
- >{{ p + 1 }}</jv-button>
25713
- }
25714
- <jv-button
25715
- variant="outline"
25716
- icon="chevron-right"
25717
- [disabled]="pageIndex() >= effectiveTotalPages() - 1"
25718
- (click)="goToPage(pageIndex() + 1)"
25719
- [attr.aria-label]="t('grid.pageNext')"
25720
- ></jv-button>
25721
- <jv-button
25722
- variant="outline"
25723
- icon="chevrons-right"
25724
- [disabled]="pageIndex() >= effectiveTotalPages() - 1"
25725
- (click)="goToPage(effectiveTotalPages() - 1)"
25726
- [attr.aria-label]="t('grid.pageLast')"
25727
- ></jv-button>
25728
- </div>
25729
- </nav>
25730
- }
25731
- </div>
25732
- `, 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"] }] });
25781
+ 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: "<div class=\"jv-grid\" role=\"region\" [attr.aria-label]=\"resolvedOptions().ariaLabel || gridLabel()\">\n @if (resolvedOptions().searchable || resolvedOptions().exportable) {\n <div class=\"jv-grid-toolbar\">\n @if (resolvedOptions().searchable) {\n <div class=\"jv-grid-search\">\n <jv-icon name=\"search\" [size]=\"16\" />\n <jv-input\n [inputId]=\"gridSearchInputId\"\n [placeholder]=\"t('grid.searchPlaceholder')\"\n [ngModel]=\"searchTerm()\"\n (ngModelChange)=\"onSearchInput($event)\"\n />\n </div>\n }\n @if (resolvedOptions().exportable) {\n <div class=\"jv-grid-export-actions\">\n <jv-button variant=\"outline\" icon=\"download\" (click)=\"exportCsv()\">{{\n t('grid.exportCsv')\n }}</jv-button>\n <jv-button variant=\"outline\" icon=\"file-spreadsheet\" (click)=\"exportExcel()\">{{\n t('grid.exportExcel')\n }}</jv-button>\n </div>\n }\n </div>\n }\n\n @if (resolvedOptions().loading) {\n <div class=\"jv-grid-loading\" role=\"status\" aria-live=\"polite\">\n <span class=\"jv-grid-spinner\" aria-hidden=\"true\"></span>\n <span>{{ t('grid.loading') }}</span>\n </div>\n } @else {\n <div\n class=\"jv-grid-table-wrap\"\n [class.jv-grid-virtual]=\"virtualScrollEnabled()\"\n #tableWrap\n (scroll)=\"onVirtualScroll($any($event.target))\"\n >\n @if (virtualScrollEnabled()) {\n <div class=\"jv-grid-virtual-spacer\" [style.height.px]=\"virtualTotalHeight()\"></div>\n }\n <table\n class=\"jv-grid-table\"\n role=\"grid\"\n [attr.aria-label]=\"resolvedOptions().ariaLabel || gridLabel()\"\n [attr.aria-rowcount]=\"effectiveTotalItems()\"\n [attr.aria-colcount]=\"colspan()\"\n [class]=\"densityClass()\"\n [style.position]=\"virtualScrollEnabled() ? 'relative' : null\"\n [style.top.px]=\"virtualOffsetY()\"\n >\n <caption class=\"sr-only\">\n {{\n resolvedOptions().ariaLabel || gridLabel()\n }}\n </caption>\n\n @let hasGroupedHeaders = groupedHeaders().length > 0;\n\n <thead class=\"jv-grid-thead\">\n @if (hasGroupedHeaders) {\n <tr>\n @if (resolvedOptions().selectable) {\n <th class=\"jv-grid-th jv-grid-th-select\" scope=\"col\" [attr.rowspan]=\"2\">\n <input\n type=\"checkbox\"\n class=\"jv-grid-checkbox\"\n [checked]=\"allSelected()\"\n (change)=\"toggleSelectAll($any($event.target).checked)\"\n [attr.aria-label]=\"allSelected() ? t('grid.deselectAll') : t('grid.selectAll')\"\n [indeterminate]=\"someSelected()\"\n />\n </th>\n }\n @for (group of groupedHeaders(); track colKey(group)) {\n <th\n class=\"jv-grid-th\"\n [class]=\"group.headerClass || ''\"\n [class.jv-grid-th-sticky-left]=\"isStickyLeft(group)\"\n [class.jv-grid-th-sticky-right]=\"isStickyRight(group)\"\n [attr.colspan]=\"groupColspan(group)\"\n scope=\"colgroup\"\n [style.text-align]=\"group.align ?? 'start'\"\n >\n <div class=\"jv-grid-th-content\">{{ group.header }}</div>\n </th>\n }\n @if (actions().length > 0) {\n <th class=\"jv-grid-th jv-grid-th-actions\" scope=\"col\" [attr.rowspan]=\"2\">\n <span class=\"sr-only\">{{ t('grid.actionsLabel', { label: '' }) }}</span>\n </th>\n }\n </tr>\n }\n\n <tr>\n @if (!hasGroupedHeaders && resolvedOptions().selectable) {\n <th class=\"jv-grid-th jv-grid-th-select\" scope=\"col\">\n <input\n type=\"checkbox\"\n class=\"jv-grid-checkbox\"\n [checked]=\"allSelected()\"\n (change)=\"toggleSelectAll($any($event.target).checked)\"\n [attr.aria-label]=\"allSelected() ? t('grid.deselectAll') : t('grid.selectAll')\"\n [indeterminate]=\"someSelected()\"\n />\n </th>\n }\n @for (col of visibleColumns(); track colKey(col); let colIdx = $index) {\n <th\n class=\"jv-grid-th\"\n [class]=\"col.headerClass || ''\"\n [class.jv-grid-th-sortable]=\"col.sortable !== false && resolvedOptions().sortable\"\n [class.jv-grid-th-sticky-left]=\"isStickyLeft(col)\"\n [class.jv-grid-th-sticky-right]=\"isStickyRight(col)\"\n [class.jv-grid-th-reorderable]=\"resolvedOptions().reorderableColumns\"\n [style.text-align]=\"col.align ?? 'start'\"\n [style.width]=\"getColumnWidth(col)\"\n [style.min-width]=\"col.minWidth || undefined\"\n [style.max-width]=\"col.maxWidth || undefined\"\n scope=\"col\"\n role=\"columnheader\"\n [attr.data-column-index]=\"colIdx\"\n [attr.aria-sort]=\"getAriaSort(col)\"\n [attr.aria-label]=\"getSortLabel(col)\"\n [tabindex]=\"\n resolvedOptions().keyboardNavigation\n ? activeCell().column === colIdx\n ? 0\n : -1\n : col.sortable !== false && resolvedOptions().sortable\n ? 0\n : -1\n \"\n (click)=\"toggleSort(col)\"\n (focus)=\"onHeaderFocus(colIdx)\"\n (keydown)=\"onHeaderKeydown($event, colIdx)\"\n (keydown.enter)=\"toggleSort(col)\"\n (keydown.space)=\"toggleSort(col, $event)\"\n draggable=\"{{ resolvedOptions().reorderableColumns ? 'true' : 'false' }}\"\n (dragstart)=\"onDragStart($event, colIdx)\"\n (dragover)=\"onDragOver($event, colIdx)\"\n (drop)=\"onDrop($event, colIdx)\"\n (dragend)=\"onDragEnd($event)\"\n >\n <div class=\"jv-grid-th-content\">\n <span>{{ col.header }}</span>\n @if (col.sortable !== false && resolvedOptions().sortable) {\n <span class=\"jv-grid-sort-icon\" aria-hidden=\"true\">\n @if (sortState().columnKey === colKey(col)) {\n @if (sortState().direction === 'asc') {\n \u25B2\n }\n @if (sortState().direction === 'desc') {\n \u25BC\n }\n }\n </span>\n }\n </div>\n @if (isColumnResizable(col)) {\n <div\n class=\"jv-grid-resize-handle\"\n (mousedown)=\"onResizeStart($event, col, colIdx)\"\n ></div>\n }\n </th>\n }\n @if (!hasGroupedHeaders && actions().length > 0) {\n <th class=\"jv-grid-th jv-grid-th-actions\" scope=\"col\">\n <span class=\"sr-only\">{{ t('grid.actionsLabel', { label: '' }) }}</span>\n </th>\n }\n </tr>\n\n @if (resolvedOptions().columnFilters) {\n <tr class=\"jv-grid-filter-row\">\n @if (resolvedOptions().selectable) {\n <th class=\"jv-grid-th jv-grid-th-select\"></th>\n }\n @for (col of visibleColumns(); track colKey(col)) {\n <th class=\"jv-grid-th jv-grid-th-filter\">\n @if (col.filterable !== false) {\n <input\n class=\"jv-grid-filter-input\"\n [placeholder]=\"t('grid.filterPlaceholder')\"\n [attr.aria-label]=\"t('grid.filterForColumn', { column: col.header })\"\n [ngModel]=\"colFilters()[colKey(col)]?.value ?? ''\"\n (ngModelChange)=\"onColumnFilterChange(col, $event)\"\n />\n }\n </th>\n }\n @if (actions().length > 0) {\n <th class=\"jv-grid-th jv-grid-th-actions\"></th>\n }\n </tr>\n }\n </thead>\n\n <tbody class=\"jv-grid-tbody\">\n @if (displayData().length === 0 && !searchTerm().trim() && !hasActiveFilters()) {\n <tr>\n <td class=\"jv-grid-empty\" [attr.colspan]=\"colspan()\">\n {{ resolvedOptions().emptyMessage || t('grid.emptyMessage') }}\n </td>\n </tr>\n } @else if (displayData().length === 0 && (searchTerm().trim() || hasActiveFilters())) {\n <tr>\n <td class=\"jv-grid-empty\" [attr.colspan]=\"colspan()\">\n {{ resolvedOptions().noResultsMessage || t('grid.noResults') }}\n </td>\n </tr>\n } @else {\n @for (row of displayData(); track getRowId(row, $index); let rowIdx = $index) {\n <tr\n class=\"jv-grid-tr\"\n role=\"row\"\n [class.jv-grid-tr-editing]=\"isRowEditing(row)\"\n [class.jv-grid-tr-selected]=\"isSelected(row)\"\n [attr.aria-selected]=\"resolvedOptions().selectable ? isSelected(row) : null\"\n (click)=\"onRowClick(row)\"\n (dblclick)=\"onRowDblClick(row)\"\n (keydown.enter)=\"onRowClick(row)\"\n (keydown.space)=\"onRowClick($event, row)\"\n [tabindex]=\"-1\"\n >\n @if (resolvedOptions().selectable) {\n <td\n class=\"jv-grid-td jv-grid-td-select\"\n [class.jv-grid-td-sticky-left]=\"resolvedOptions().stickyColumns\"\n >\n <input\n type=\"checkbox\"\n class=\"jv-grid-checkbox\"\n [checked]=\"isSelected(row)\"\n (change)=\"toggleRow(row); $event.stopPropagation()\"\n (keydown.space)=\"$event.stopPropagation()\"\n [attr.aria-label]=\"getRowLabel(row, $index)\"\n />\n </td>\n }\n @for (col of visibleColumns(); track colKey(col); let colIdx = $index) {\n <td\n class=\"jv-grid-td\"\n [attr.role]=\"resolvedOptions().keyboardNavigation ? 'gridcell' : null\"\n [class]=\"col.cellClass || ''\"\n [class.jv-grid-td-sticky-left]=\"isStickyLeft(col)\"\n [class.jv-grid-td-sticky-right]=\"isStickyRight(col)\"\n [class.jv-grid-td-editable]=\"isCellEditable(col)\"\n [style.text-align]=\"col.align ?? 'start'\"\n (dblclick)=\"startEdit(row, col, $event)\"\n (keydown.enter)=\"startEdit(row, col, $event)\"\n [attr.data-row-index]=\"rowIdx\"\n [attr.data-column-index]=\"colIdx\"\n [class.jv-grid-keyboard-cell]=\"resolvedOptions().keyboardNavigation\"\n [attr.tabindex]=\"cellTabIndex(rowIdx, colIdx, col)\"\n (focus)=\"onCellFocus(rowIdx, colIdx)\"\n (keydown.f2)=\"onCellF2($event, rowIdx, colIdx)\"\n (keydown)=\"onCellKeydown($event, rowIdx, colIdx)\"\n >\n @if (isEditingCell(row, col)) {\n @if (editType(col) === 'select' && col.editOptions) {\n <jv-select\n [options]=\"getEditOptions(col)\"\n [modelValue]=\"getEditValue(row, col)\"\n (selectionChange)=\"onEditValueChange(row, col, $event)\"\n />\n } @else if (editType(col) === 'boolean') {\n <input\n type=\"checkbox\"\n class=\"jv-grid-checkbox\"\n [checked]=\"getBooleanEditValue(row, col)\"\n (change)=\"\n commitEdit(\n row,\n col,\n normalizeEditValue(col, $any($event.target).checked)\n )\n \"\n (keydown.enter)=\"\n commitEditFromKeyboard(\n $event,\n row,\n col,\n normalizeEditValue(col, $any($event.target).checked)\n )\n \"\n (keydown.escape)=\"cancelEdit()\"\n [attr.aria-label]=\"col.header\"\n />\n } @else {\n <input\n class=\"jv-grid-edit-input\"\n [type]=\"inputType(col)\"\n [value]=\"getEditValue(row, col)\"\n (blur)=\"\n commitEdit(row, col, normalizeEditValue(col, $any($event.target).value))\n \"\n (keydown.enter)=\"\n commitEditFromKeyboard(\n $event,\n row,\n col,\n normalizeEditValue(col, $any($event.target).value)\n )\n \"\n (keydown.escape)=\"cancelEdit()\"\n />\n }\n } @else {\n @let value = getCellValue(row, col);\n @if (col.type === 'boolean') {\n @if (isBooleanTrue(value)) {\n <span\n class=\"jv-grid-boolean jv-grid-boolean-true\"\n [attr.aria-label]=\"t('grid.booleanYes')\"\n >{{ t('grid.booleanYes') }}</span\n >\n } @else {\n <span\n class=\"jv-grid-boolean jv-grid-boolean-false\"\n [attr.aria-label]=\"t('grid.booleanNo')\"\n >{{ t('grid.booleanNo') }}</span\n >\n }\n } @else if (col.format) {\n {{ col.format(value, row) }}\n } @else {\n {{ formatValue(value, col, row) }}\n }\n }\n </td>\n }\n @if (actions().length > 0) {\n <td\n class=\"jv-grid-td jv-grid-td-actions\"\n [class.jv-grid-td-sticky-right]=\"resolvedOptions().stickyColumns\"\n >\n <div class=\"jv-grid-actions-group\">\n @for (action of actions(); track action.id) {\n <jv-button\n [variant]=\"actionVariant(action.variant)\"\n [icon]=\"action.icon ?? null\"\n [disabled]=\"action.disabled?.(row) ?? false\"\n [attr.aria-label]=\"action.label\"\n [title]=\"action.label\"\n (click)=\"onActionClick(action, row); $event.stopPropagation()\"\n ></jv-button>\n }\n </div>\n </td>\n }\n </tr>\n }\n }\n </tbody>\n </table>\n </div>\n }\n\n @if (\n resolvedOptions().pageable &&\n !virtualScrollEnabled() &&\n effectiveTotalPages() > 1 &&\n !resolvedOptions().loading\n ) {\n <nav\n class=\"jv-grid-pagination\"\n role=\"navigation\"\n [attr.aria-label]=\"t('grid.paginationLabel', { label: '' })\"\n >\n <div class=\"jv-grid-pagination-info\" aria-live=\"polite\" aria-atomic=\"true\">\n {{\n t('grid.itemsShowing', {\n start: pageStart(),\n end: pageEnd(),\n total: effectiveTotalItems(),\n })\n }}\n </div>\n <div class=\"jv-grid-pagination-controls\">\n @if (pageSizeOptions().length > 0) {\n <jv-select\n [options]=\"pageSizeOptions()\"\n [modelValue]=\"pageSizeStr()\"\n [ariaLabel]=\"t('grid.itemsPerPage')\"\n (selectionChange)=\"onPageSizeChange($event)\"\n />\n }\n <jv-button\n variant=\"outline\"\n icon=\"chevrons-left\"\n [disabled]=\"pageIndex() === 0\"\n (click)=\"goToPage(0)\"\n [attr.aria-label]=\"t('grid.pageFirst')\"\n ></jv-button>\n <jv-button\n variant=\"outline\"\n icon=\"chevron-left\"\n [disabled]=\"pageIndex() === 0\"\n (click)=\"goToPage(pageIndex() - 1)\"\n [attr.aria-label]=\"t('grid.pagePrev')\"\n ></jv-button>\n @for (p of pageRange(); track p) {\n <jv-button\n [variant]=\"p === pageIndex() ? 'primary' : 'outline'\"\n [attr.aria-current]=\"p === pageIndex() ? 'page' : null\"\n [attr.aria-label]=\"t('grid.pageOfTotal', { page: p + 1, total: effectiveTotalPages() })\"\n (click)=\"goToPage(p)\"\n >{{ p + 1 }}</jv-button\n >\n }\n <jv-button\n variant=\"outline\"\n icon=\"chevron-right\"\n [disabled]=\"pageIndex() >= effectiveTotalPages() - 1\"\n (click)=\"goToPage(pageIndex() + 1)\"\n [attr.aria-label]=\"t('grid.pageNext')\"\n ></jv-button>\n <jv-button\n variant=\"outline\"\n icon=\"chevrons-right\"\n [disabled]=\"pageIndex() >= effectiveTotalPages() - 1\"\n (click)=\"goToPage(effectiveTotalPages() - 1)\"\n [attr.aria-label]=\"t('grid.pageLast')\"\n ></jv-button>\n </div>\n </nav>\n }\n</div>\n", 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"] }] });
25733
25782
  }
25734
25783
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: JvGridComponent, decorators: [{
25735
25784
  type: Component,
25736
- args: [{ selector: 'jv-grid', standalone: true, imports: [FormsModule, JvButtonComponent, JvIconComponent, JvInputComponent, JvSelectComponent], template: `
25737
- <div class="jv-grid" role="region" [attr.aria-label]="resolvedOptions().ariaLabel || gridLabel()">
25738
- @if (resolvedOptions().searchable || resolvedOptions().exportable) {
25739
- <div class="jv-grid-toolbar">
25740
- @if (resolvedOptions().searchable) {
25741
- <div class="jv-grid-search">
25742
- <jv-icon name="search" [size]="16" />
25743
- <jv-input
25744
- [inputId]="gridSearchInputId"
25745
- [placeholder]="t('grid.searchPlaceholder')"
25746
- [ngModel]="searchTerm()"
25747
- (ngModelChange)="onSearchInput($event)"
25748
- />
25749
- </div>
25750
- }
25751
- @if (resolvedOptions().exportable) {
25752
- <div class="jv-grid-export-actions">
25753
- <jv-button variant="outline" icon="download" (click)="exportCsv()">{{ t('grid.exportCsv') }}</jv-button>
25754
- <jv-button variant="outline" icon="file-spreadsheet" (click)="exportExcel()">{{ t('grid.exportExcel') }}</jv-button>
25755
- </div>
25756
- }
25757
- </div>
25758
- }
25759
-
25760
- @if (resolvedOptions().loading) {
25761
- <div class="jv-grid-loading" role="status" aria-live="polite">
25762
- <span class="jv-grid-spinner" aria-hidden="true"></span>
25763
- <span>{{ t('grid.loading') }}</span>
25764
- </div>
25765
- } @else {
25766
- <div
25767
- class="jv-grid-table-wrap"
25768
- [class.jv-grid-virtual]="virtualScrollEnabled()"
25769
- #tableWrap
25770
- (scroll)="onVirtualScroll($any($event.target))"
25771
- >
25772
- @if (virtualScrollEnabled()) {
25773
- <div class="jv-grid-virtual-spacer" [style.height.px]="virtualTotalHeight()"></div>
25774
- }
25775
- <table
25776
- class="jv-grid-table"
25777
- role="grid"
25778
- [attr.aria-label]="resolvedOptions().ariaLabel || gridLabel()"
25779
- [attr.aria-rowcount]="effectiveTotalItems()"
25780
- [attr.aria-colcount]="colspan()"
25781
- [class]="densityClass()"
25782
- [style.position]="virtualScrollEnabled() ? 'relative' : null"
25783
- [style.top.px]="virtualOffsetY()"
25784
- >
25785
- <caption class="sr-only">{{ resolvedOptions().ariaLabel || gridLabel() }}</caption>
25786
-
25787
- @let hasGroupedHeaders = groupedHeaders().length > 0;
25788
-
25789
- <thead class="jv-grid-thead">
25790
- @if (hasGroupedHeaders) {
25791
- <tr>
25792
- @if (resolvedOptions().selectable) {
25793
- <th class="jv-grid-th jv-grid-th-select" scope="col" [attr.rowspan]="2">
25794
- <input
25795
- type="checkbox"
25796
- class="jv-grid-checkbox"
25797
- [checked]="allSelected()"
25798
- (change)="toggleSelectAll($any($event.target).checked)"
25799
- [attr.aria-label]="allSelected() ? t('grid.deselectAll') : t('grid.selectAll')"
25800
- [indeterminate]="someSelected()"
25801
- />
25802
- </th>
25803
- }
25804
- @for (group of groupedHeaders(); track colKey(group)) {
25805
- <th
25806
- class="jv-grid-th"
25807
- [class]="group.headerClass || ''"
25808
- [class.jv-grid-th-sticky-left]="isStickyLeft(group)"
25809
- [class.jv-grid-th-sticky-right]="isStickyRight(group)"
25810
- [attr.colspan]="groupColspan(group)"
25811
- scope="colgroup"
25812
- [style.text-align]="group.align ?? 'start'"
25813
- >
25814
- <div class="jv-grid-th-content">{{ group.header }}</div>
25815
- </th>
25816
- }
25817
- @if (actions().length > 0) {
25818
- <th class="jv-grid-th jv-grid-th-actions" scope="col" [attr.rowspan]="2">
25819
- <span class="sr-only">{{ t('grid.actionsLabel', { label: '' }) }}</span>
25820
- </th>
25821
- }
25822
- </tr>
25823
- }
25824
-
25825
- <tr>
25826
- @if (!hasGroupedHeaders && resolvedOptions().selectable) {
25827
- <th class="jv-grid-th jv-grid-th-select" scope="col">
25828
- <input
25829
- type="checkbox"
25830
- class="jv-grid-checkbox"
25831
- [checked]="allSelected()"
25832
- (change)="toggleSelectAll($any($event.target).checked)"
25833
- [attr.aria-label]="allSelected() ? t('grid.deselectAll') : t('grid.selectAll')"
25834
- [indeterminate]="someSelected()"
25835
- />
25836
- </th>
25837
- }
25838
- @for (col of visibleColumns(); track colKey(col); let colIdx = $index) {
25839
- <th
25840
- class="jv-grid-th"
25841
- [class]="col.headerClass || ''"
25842
- [class.jv-grid-th-sortable]="col.sortable !== false && resolvedOptions().sortable"
25843
- [class.jv-grid-th-sticky-left]="isStickyLeft(col)"
25844
- [class.jv-grid-th-sticky-right]="isStickyRight(col)"
25845
- [class.jv-grid-th-reorderable]="resolvedOptions().reorderableColumns"
25846
- [style.text-align]="col.align ?? 'start'"
25847
- [style.width]="getColumnWidth(col)"
25848
- [style.min-width]="col.minWidth || undefined"
25849
- [style.max-width]="col.maxWidth || undefined"
25850
- scope="col"
25851
- role="columnheader"
25852
- [attr.data-column-index]="colIdx"
25853
- [attr.aria-sort]="getAriaSort(col)"
25854
- [attr.aria-label]="getSortLabel(col)"
25855
- [tabindex]="resolvedOptions().keyboardNavigation ? (activeCell().column === colIdx ? 0 : -1) : (col.sortable !== false && resolvedOptions().sortable ? 0 : -1)"
25856
- (click)="toggleSort(col)"
25857
- (focus)="onHeaderFocus(colIdx)"
25858
- (keydown)="onHeaderKeydown($event, colIdx)"
25859
- (keydown.enter)="toggleSort(col)"
25860
- (keydown.space)="toggleSort(col, $event)"
25861
- draggable="{{ resolvedOptions().reorderableColumns ? 'true' : 'false' }}"
25862
- (dragstart)="onDragStart($event, colIdx)"
25863
- (dragover)="onDragOver($event, colIdx)"
25864
- (drop)="onDrop($event, colIdx)"
25865
- (dragend)="onDragEnd($event)"
25866
- >
25867
- <div class="jv-grid-th-content">
25868
- <span>{{ col.header }}</span>
25869
- @if (col.sortable !== false && resolvedOptions().sortable) {
25870
- <span class="jv-grid-sort-icon" aria-hidden="true">
25871
- @if (sortState().columnKey === colKey(col)) {
25872
- @if (sortState().direction === 'asc') { ▲ }
25873
- @if (sortState().direction === 'desc') { ▼ }
25874
- }
25875
- </span>
25876
- }
25877
- </div>
25878
- @if (isColumnResizable(col)) {
25879
- <div
25880
- class="jv-grid-resize-handle"
25881
- (mousedown)="onResizeStart($event, col, colIdx)"
25882
- ></div>
25883
- }
25884
- </th>
25885
- }
25886
- @if (!hasGroupedHeaders && actions().length > 0) {
25887
- <th class="jv-grid-th jv-grid-th-actions" scope="col">
25888
- <span class="sr-only">{{ t('grid.actionsLabel', { label: '' }) }}</span>
25889
- </th>
25890
- }
25891
- </tr>
25892
-
25893
- @if (resolvedOptions().columnFilters) {
25894
- <tr class="jv-grid-filter-row">
25895
- @if (resolvedOptions().selectable) {
25896
- <th class="jv-grid-th jv-grid-th-select"></th>
25897
- }
25898
- @for (col of visibleColumns(); track colKey(col)) {
25899
- <th class="jv-grid-th jv-grid-th-filter">
25900
- @if (col.filterable !== false) {
25901
- <input
25902
- class="jv-grid-filter-input"
25903
- [placeholder]="t('grid.filterPlaceholder')"
25904
- [attr.aria-label]="t('grid.filterForColumn', { column: col.header })"
25905
- [ngModel]="colFilters()[colKey(col)]?.value ?? ''"
25906
- (ngModelChange)="onColumnFilterChange(col, $event)"
25907
- />
25908
- }
25909
- </th>
25910
- }
25911
- @if (actions().length > 0) {
25912
- <th class="jv-grid-th jv-grid-th-actions"></th>
25913
- }
25914
- </tr>
25915
- }
25916
- </thead>
25917
-
25918
- <tbody class="jv-grid-tbody">
25919
- @if (displayData().length === 0 && !searchTerm().trim() && !hasActiveFilters()) {
25920
- <tr>
25921
- <td class="jv-grid-empty" [attr.colspan]="colspan()">
25922
- {{ resolvedOptions().emptyMessage || t('grid.emptyMessage') }}
25923
- </td>
25924
- </tr>
25925
- } @else if (displayData().length === 0 && (searchTerm().trim() || hasActiveFilters())) {
25926
- <tr>
25927
- <td class="jv-grid-empty" [attr.colspan]="colspan()">
25928
- {{ resolvedOptions().noResultsMessage || t('grid.noResults') }}
25929
- </td>
25930
- </tr>
25931
- } @else {
25932
- @for (row of displayData(); track getRowId(row, $index); let rowIdx = $index) {
25933
- <tr
25934
- class="jv-grid-tr"
25935
- role="row"
25936
- [class.jv-grid-tr-editing]="isRowEditing(row)"
25937
- [class.jv-grid-tr-selected]="isSelected(row)"
25938
- [attr.aria-selected]="resolvedOptions().selectable ? isSelected(row) : null"
25939
- (click)="onRowClick(row)"
25940
- (dblclick)="onRowDblClick(row)"
25941
- (keydown.enter)="onRowClick(row)"
25942
- (keydown.space)="onRowClick($event, row)"
25943
- [tabindex]="-1"
25944
- >
25945
- @if (resolvedOptions().selectable) {
25946
- <td
25947
- class="jv-grid-td jv-grid-td-select"
25948
- [class.jv-grid-td-sticky-left]="resolvedOptions().stickyColumns"
25949
- >
25950
- <input
25951
- type="checkbox"
25952
- class="jv-grid-checkbox"
25953
- [checked]="isSelected(row)"
25954
- (change)="toggleRow(row); $event.stopPropagation()"
25955
- (keydown.space)="$event.stopPropagation()"
25956
- [attr.aria-label]="getRowLabel(row, $index)"
25957
- />
25958
- </td>
25959
- }
25960
- @for (col of visibleColumns(); track colKey(col); let colIdx = $index) {
25961
- <td
25962
- class="jv-grid-td"
25963
- [attr.role]="resolvedOptions().keyboardNavigation ? 'gridcell' : null"
25964
- [class]="col.cellClass || ''"
25965
- [class.jv-grid-td-sticky-left]="isStickyLeft(col)"
25966
- [class.jv-grid-td-sticky-right]="isStickyRight(col)"
25967
- [class.jv-grid-td-editable]="isCellEditable(col)"
25968
- [style.text-align]="col.align ?? 'start'"
25969
- (dblclick)="startEdit(row, col, $event)"
25970
- (keydown.enter)="startEdit(row, col, $event)"
25971
- [attr.data-row-index]="rowIdx"
25972
- [attr.data-column-index]="colIdx"
25973
- [class.jv-grid-keyboard-cell]="resolvedOptions().keyboardNavigation"
25974
- [attr.tabindex]="cellTabIndex(rowIdx, colIdx, col)"
25975
- (focus)="onCellFocus(rowIdx, colIdx)"
25976
- (keydown.f2)="onCellF2($event, rowIdx, colIdx)"
25977
- (keydown)="onCellKeydown($event, rowIdx, colIdx)"
25978
- >
25979
- @if (isEditingCell(row, col)) {
25980
- @if (editType(col) === 'select' && col.editOptions) {
25981
- <jv-select
25982
- [options]="getEditOptions(col)"
25983
- [modelValue]="getEditValue(row, col)"
25984
- (selectionChange)="onEditValueChange(row, col, $event)"
25985
- />
25986
- } @else if (editType(col) === 'boolean') {
25987
- <input
25988
- type="checkbox"
25989
- class="jv-grid-checkbox"
25990
- [checked]="getBooleanEditValue(row, col)"
25991
- (change)="commitEdit(row, col, normalizeEditValue(col, $any($event.target).checked))"
25992
- (keydown.enter)="commitEditFromKeyboard($event, row, col, normalizeEditValue(col, $any($event.target).checked))"
25993
- (keydown.escape)="cancelEdit()"
25994
- [attr.aria-label]="col.header"
25995
- />
25996
- } @else {
25997
- <input
25998
- class="jv-grid-edit-input"
25999
- [type]="inputType(col)"
26000
- [value]="getEditValue(row, col)"
26001
- (blur)="commitEdit(row, col, normalizeEditValue(col, $any($event.target).value))"
26002
- (keydown.enter)="commitEditFromKeyboard($event, row, col, normalizeEditValue(col, $any($event.target).value))"
26003
- (keydown.escape)="cancelEdit()"
26004
- />
26005
- }
26006
- } @else {
26007
- @let value = getCellValue(row, col);
26008
- @if (col.type === 'boolean') {
26009
- @if (isBooleanTrue(value)) {
26010
- <span class="jv-grid-boolean jv-grid-boolean-true" [attr.aria-label]="t('grid.booleanYes')">{{ t('grid.booleanYes') }}</span>
26011
- } @else {
26012
- <span class="jv-grid-boolean jv-grid-boolean-false" [attr.aria-label]="t('grid.booleanNo')">{{ t('grid.booleanNo') }}</span>
26013
- }
26014
- } @else if (col.format) {
26015
- {{ col.format(value, row) }}
26016
- } @else {
26017
- {{ formatValue(value, col, row) }}
26018
- }
26019
- }
26020
- </td>
26021
- }
26022
- @if (actions().length > 0) {
26023
- <td
26024
- class="jv-grid-td jv-grid-td-actions"
26025
- [class.jv-grid-td-sticky-right]="resolvedOptions().stickyColumns"
26026
- >
26027
- <div class="jv-grid-actions-group">
26028
- @for (action of actions(); track action.id) {
26029
- <jv-button
26030
- [variant]="actionVariant(action.variant)"
26031
- [icon]="action.icon ?? null"
26032
- [disabled]="action.disabled?.(row) ?? false"
26033
- [attr.aria-label]="action.label"
26034
- [title]="action.label"
26035
- (click)="onActionClick(action, row); $event.stopPropagation()"
26036
- ></jv-button>
26037
- }
26038
- </div>
26039
- </td>
26040
- }
26041
- </tr>
26042
- }
26043
- }
26044
- </tbody>
26045
- </table>
26046
- </div>
26047
- }
26048
-
26049
- @if (resolvedOptions().pageable && !virtualScrollEnabled() && effectiveTotalPages() > 1 && !resolvedOptions().loading) {
26050
- <nav class="jv-grid-pagination" role="navigation" [attr.aria-label]="t('grid.paginationLabel', { label: '' })">
26051
- <div class="jv-grid-pagination-info" aria-live="polite" aria-atomic="true">
26052
- {{ t('grid.itemsShowing', { start: pageStart(), end: pageEnd(), total: effectiveTotalItems() }) }}
26053
- </div>
26054
- <div class="jv-grid-pagination-controls">
26055
- @if (pageSizeOptions().length > 0) {
26056
- <jv-select
26057
- [options]="pageSizeOptions()"
26058
- [modelValue]="pageSizeStr()"
26059
- [ariaLabel]="t('grid.itemsPerPage')"
26060
- (selectionChange)="onPageSizeChange($event)"
26061
- />
26062
- }
26063
- <jv-button
26064
- variant="outline"
26065
- icon="chevrons-left"
26066
- [disabled]="pageIndex() === 0"
26067
- (click)="goToPage(0)"
26068
- [attr.aria-label]="t('grid.pageFirst')"
26069
- ></jv-button>
26070
- <jv-button
26071
- variant="outline"
26072
- icon="chevron-left"
26073
- [disabled]="pageIndex() === 0"
26074
- (click)="goToPage(pageIndex() - 1)"
26075
- [attr.aria-label]="t('grid.pagePrev')"
26076
- ></jv-button>
26077
- @for (p of pageRange(); track p) {
26078
- <jv-button
26079
- [variant]="p === pageIndex() ? 'primary' : 'outline'"
26080
- [attr.aria-current]="p === pageIndex() ? 'page' : null"
26081
- [attr.aria-label]="t('grid.pageOfTotal', { page: p + 1, total: effectiveTotalPages() })"
26082
- (click)="goToPage(p)"
26083
- >{{ p + 1 }}</jv-button>
26084
- }
26085
- <jv-button
26086
- variant="outline"
26087
- icon="chevron-right"
26088
- [disabled]="pageIndex() >= effectiveTotalPages() - 1"
26089
- (click)="goToPage(pageIndex() + 1)"
26090
- [attr.aria-label]="t('grid.pageNext')"
26091
- ></jv-button>
26092
- <jv-button
26093
- variant="outline"
26094
- icon="chevrons-right"
26095
- [disabled]="pageIndex() >= effectiveTotalPages() - 1"
26096
- (click)="goToPage(effectiveTotalPages() - 1)"
26097
- [attr.aria-label]="t('grid.pageLast')"
26098
- ></jv-button>
26099
- </div>
26100
- </nav>
26101
- }
26102
- </div>
26103
- `, 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"] }]
25785
+ args: [{ selector: 'jv-grid', standalone: true, imports: [FormsModule, JvButtonComponent, JvIconComponent, JvInputComponent, JvSelectComponent], template: "<div class=\"jv-grid\" role=\"region\" [attr.aria-label]=\"resolvedOptions().ariaLabel || gridLabel()\">\n @if (resolvedOptions().searchable || resolvedOptions().exportable) {\n <div class=\"jv-grid-toolbar\">\n @if (resolvedOptions().searchable) {\n <div class=\"jv-grid-search\">\n <jv-icon name=\"search\" [size]=\"16\" />\n <jv-input\n [inputId]=\"gridSearchInputId\"\n [placeholder]=\"t('grid.searchPlaceholder')\"\n [ngModel]=\"searchTerm()\"\n (ngModelChange)=\"onSearchInput($event)\"\n />\n </div>\n }\n @if (resolvedOptions().exportable) {\n <div class=\"jv-grid-export-actions\">\n <jv-button variant=\"outline\" icon=\"download\" (click)=\"exportCsv()\">{{\n t('grid.exportCsv')\n }}</jv-button>\n <jv-button variant=\"outline\" icon=\"file-spreadsheet\" (click)=\"exportExcel()\">{{\n t('grid.exportExcel')\n }}</jv-button>\n </div>\n }\n </div>\n }\n\n @if (resolvedOptions().loading) {\n <div class=\"jv-grid-loading\" role=\"status\" aria-live=\"polite\">\n <span class=\"jv-grid-spinner\" aria-hidden=\"true\"></span>\n <span>{{ t('grid.loading') }}</span>\n </div>\n } @else {\n <div\n class=\"jv-grid-table-wrap\"\n [class.jv-grid-virtual]=\"virtualScrollEnabled()\"\n #tableWrap\n (scroll)=\"onVirtualScroll($any($event.target))\"\n >\n @if (virtualScrollEnabled()) {\n <div class=\"jv-grid-virtual-spacer\" [style.height.px]=\"virtualTotalHeight()\"></div>\n }\n <table\n class=\"jv-grid-table\"\n role=\"grid\"\n [attr.aria-label]=\"resolvedOptions().ariaLabel || gridLabel()\"\n [attr.aria-rowcount]=\"effectiveTotalItems()\"\n [attr.aria-colcount]=\"colspan()\"\n [class]=\"densityClass()\"\n [style.position]=\"virtualScrollEnabled() ? 'relative' : null\"\n [style.top.px]=\"virtualOffsetY()\"\n >\n <caption class=\"sr-only\">\n {{\n resolvedOptions().ariaLabel || gridLabel()\n }}\n </caption>\n\n @let hasGroupedHeaders = groupedHeaders().length > 0;\n\n <thead class=\"jv-grid-thead\">\n @if (hasGroupedHeaders) {\n <tr>\n @if (resolvedOptions().selectable) {\n <th class=\"jv-grid-th jv-grid-th-select\" scope=\"col\" [attr.rowspan]=\"2\">\n <input\n type=\"checkbox\"\n class=\"jv-grid-checkbox\"\n [checked]=\"allSelected()\"\n (change)=\"toggleSelectAll($any($event.target).checked)\"\n [attr.aria-label]=\"allSelected() ? t('grid.deselectAll') : t('grid.selectAll')\"\n [indeterminate]=\"someSelected()\"\n />\n </th>\n }\n @for (group of groupedHeaders(); track colKey(group)) {\n <th\n class=\"jv-grid-th\"\n [class]=\"group.headerClass || ''\"\n [class.jv-grid-th-sticky-left]=\"isStickyLeft(group)\"\n [class.jv-grid-th-sticky-right]=\"isStickyRight(group)\"\n [attr.colspan]=\"groupColspan(group)\"\n scope=\"colgroup\"\n [style.text-align]=\"group.align ?? 'start'\"\n >\n <div class=\"jv-grid-th-content\">{{ group.header }}</div>\n </th>\n }\n @if (actions().length > 0) {\n <th class=\"jv-grid-th jv-grid-th-actions\" scope=\"col\" [attr.rowspan]=\"2\">\n <span class=\"sr-only\">{{ t('grid.actionsLabel', { label: '' }) }}</span>\n </th>\n }\n </tr>\n }\n\n <tr>\n @if (!hasGroupedHeaders && resolvedOptions().selectable) {\n <th class=\"jv-grid-th jv-grid-th-select\" scope=\"col\">\n <input\n type=\"checkbox\"\n class=\"jv-grid-checkbox\"\n [checked]=\"allSelected()\"\n (change)=\"toggleSelectAll($any($event.target).checked)\"\n [attr.aria-label]=\"allSelected() ? t('grid.deselectAll') : t('grid.selectAll')\"\n [indeterminate]=\"someSelected()\"\n />\n </th>\n }\n @for (col of visibleColumns(); track colKey(col); let colIdx = $index) {\n <th\n class=\"jv-grid-th\"\n [class]=\"col.headerClass || ''\"\n [class.jv-grid-th-sortable]=\"col.sortable !== false && resolvedOptions().sortable\"\n [class.jv-grid-th-sticky-left]=\"isStickyLeft(col)\"\n [class.jv-grid-th-sticky-right]=\"isStickyRight(col)\"\n [class.jv-grid-th-reorderable]=\"resolvedOptions().reorderableColumns\"\n [style.text-align]=\"col.align ?? 'start'\"\n [style.width]=\"getColumnWidth(col)\"\n [style.min-width]=\"col.minWidth || undefined\"\n [style.max-width]=\"col.maxWidth || undefined\"\n scope=\"col\"\n role=\"columnheader\"\n [attr.data-column-index]=\"colIdx\"\n [attr.aria-sort]=\"getAriaSort(col)\"\n [attr.aria-label]=\"getSortLabel(col)\"\n [tabindex]=\"\n resolvedOptions().keyboardNavigation\n ? activeCell().column === colIdx\n ? 0\n : -1\n : col.sortable !== false && resolvedOptions().sortable\n ? 0\n : -1\n \"\n (click)=\"toggleSort(col)\"\n (focus)=\"onHeaderFocus(colIdx)\"\n (keydown)=\"onHeaderKeydown($event, colIdx)\"\n (keydown.enter)=\"toggleSort(col)\"\n (keydown.space)=\"toggleSort(col, $event)\"\n draggable=\"{{ resolvedOptions().reorderableColumns ? 'true' : 'false' }}\"\n (dragstart)=\"onDragStart($event, colIdx)\"\n (dragover)=\"onDragOver($event, colIdx)\"\n (drop)=\"onDrop($event, colIdx)\"\n (dragend)=\"onDragEnd($event)\"\n >\n <div class=\"jv-grid-th-content\">\n <span>{{ col.header }}</span>\n @if (col.sortable !== false && resolvedOptions().sortable) {\n <span class=\"jv-grid-sort-icon\" aria-hidden=\"true\">\n @if (sortState().columnKey === colKey(col)) {\n @if (sortState().direction === 'asc') {\n \u25B2\n }\n @if (sortState().direction === 'desc') {\n \u25BC\n }\n }\n </span>\n }\n </div>\n @if (isColumnResizable(col)) {\n <div\n class=\"jv-grid-resize-handle\"\n (mousedown)=\"onResizeStart($event, col, colIdx)\"\n ></div>\n }\n </th>\n }\n @if (!hasGroupedHeaders && actions().length > 0) {\n <th class=\"jv-grid-th jv-grid-th-actions\" scope=\"col\">\n <span class=\"sr-only\">{{ t('grid.actionsLabel', { label: '' }) }}</span>\n </th>\n }\n </tr>\n\n @if (resolvedOptions().columnFilters) {\n <tr class=\"jv-grid-filter-row\">\n @if (resolvedOptions().selectable) {\n <th class=\"jv-grid-th jv-grid-th-select\"></th>\n }\n @for (col of visibleColumns(); track colKey(col)) {\n <th class=\"jv-grid-th jv-grid-th-filter\">\n @if (col.filterable !== false) {\n <input\n class=\"jv-grid-filter-input\"\n [placeholder]=\"t('grid.filterPlaceholder')\"\n [attr.aria-label]=\"t('grid.filterForColumn', { column: col.header })\"\n [ngModel]=\"colFilters()[colKey(col)]?.value ?? ''\"\n (ngModelChange)=\"onColumnFilterChange(col, $event)\"\n />\n }\n </th>\n }\n @if (actions().length > 0) {\n <th class=\"jv-grid-th jv-grid-th-actions\"></th>\n }\n </tr>\n }\n </thead>\n\n <tbody class=\"jv-grid-tbody\">\n @if (displayData().length === 0 && !searchTerm().trim() && !hasActiveFilters()) {\n <tr>\n <td class=\"jv-grid-empty\" [attr.colspan]=\"colspan()\">\n {{ resolvedOptions().emptyMessage || t('grid.emptyMessage') }}\n </td>\n </tr>\n } @else if (displayData().length === 0 && (searchTerm().trim() || hasActiveFilters())) {\n <tr>\n <td class=\"jv-grid-empty\" [attr.colspan]=\"colspan()\">\n {{ resolvedOptions().noResultsMessage || t('grid.noResults') }}\n </td>\n </tr>\n } @else {\n @for (row of displayData(); track getRowId(row, $index); let rowIdx = $index) {\n <tr\n class=\"jv-grid-tr\"\n role=\"row\"\n [class.jv-grid-tr-editing]=\"isRowEditing(row)\"\n [class.jv-grid-tr-selected]=\"isSelected(row)\"\n [attr.aria-selected]=\"resolvedOptions().selectable ? isSelected(row) : null\"\n (click)=\"onRowClick(row)\"\n (dblclick)=\"onRowDblClick(row)\"\n (keydown.enter)=\"onRowClick(row)\"\n (keydown.space)=\"onRowClick($event, row)\"\n [tabindex]=\"-1\"\n >\n @if (resolvedOptions().selectable) {\n <td\n class=\"jv-grid-td jv-grid-td-select\"\n [class.jv-grid-td-sticky-left]=\"resolvedOptions().stickyColumns\"\n >\n <input\n type=\"checkbox\"\n class=\"jv-grid-checkbox\"\n [checked]=\"isSelected(row)\"\n (change)=\"toggleRow(row); $event.stopPropagation()\"\n (keydown.space)=\"$event.stopPropagation()\"\n [attr.aria-label]=\"getRowLabel(row, $index)\"\n />\n </td>\n }\n @for (col of visibleColumns(); track colKey(col); let colIdx = $index) {\n <td\n class=\"jv-grid-td\"\n [attr.role]=\"resolvedOptions().keyboardNavigation ? 'gridcell' : null\"\n [class]=\"col.cellClass || ''\"\n [class.jv-grid-td-sticky-left]=\"isStickyLeft(col)\"\n [class.jv-grid-td-sticky-right]=\"isStickyRight(col)\"\n [class.jv-grid-td-editable]=\"isCellEditable(col)\"\n [style.text-align]=\"col.align ?? 'start'\"\n (dblclick)=\"startEdit(row, col, $event)\"\n (keydown.enter)=\"startEdit(row, col, $event)\"\n [attr.data-row-index]=\"rowIdx\"\n [attr.data-column-index]=\"colIdx\"\n [class.jv-grid-keyboard-cell]=\"resolvedOptions().keyboardNavigation\"\n [attr.tabindex]=\"cellTabIndex(rowIdx, colIdx, col)\"\n (focus)=\"onCellFocus(rowIdx, colIdx)\"\n (keydown.f2)=\"onCellF2($event, rowIdx, colIdx)\"\n (keydown)=\"onCellKeydown($event, rowIdx, colIdx)\"\n >\n @if (isEditingCell(row, col)) {\n @if (editType(col) === 'select' && col.editOptions) {\n <jv-select\n [options]=\"getEditOptions(col)\"\n [modelValue]=\"getEditValue(row, col)\"\n (selectionChange)=\"onEditValueChange(row, col, $event)\"\n />\n } @else if (editType(col) === 'boolean') {\n <input\n type=\"checkbox\"\n class=\"jv-grid-checkbox\"\n [checked]=\"getBooleanEditValue(row, col)\"\n (change)=\"\n commitEdit(\n row,\n col,\n normalizeEditValue(col, $any($event.target).checked)\n )\n \"\n (keydown.enter)=\"\n commitEditFromKeyboard(\n $event,\n row,\n col,\n normalizeEditValue(col, $any($event.target).checked)\n )\n \"\n (keydown.escape)=\"cancelEdit()\"\n [attr.aria-label]=\"col.header\"\n />\n } @else {\n <input\n class=\"jv-grid-edit-input\"\n [type]=\"inputType(col)\"\n [value]=\"getEditValue(row, col)\"\n (blur)=\"\n commitEdit(row, col, normalizeEditValue(col, $any($event.target).value))\n \"\n (keydown.enter)=\"\n commitEditFromKeyboard(\n $event,\n row,\n col,\n normalizeEditValue(col, $any($event.target).value)\n )\n \"\n (keydown.escape)=\"cancelEdit()\"\n />\n }\n } @else {\n @let value = getCellValue(row, col);\n @if (col.type === 'boolean') {\n @if (isBooleanTrue(value)) {\n <span\n class=\"jv-grid-boolean jv-grid-boolean-true\"\n [attr.aria-label]=\"t('grid.booleanYes')\"\n >{{ t('grid.booleanYes') }}</span\n >\n } @else {\n <span\n class=\"jv-grid-boolean jv-grid-boolean-false\"\n [attr.aria-label]=\"t('grid.booleanNo')\"\n >{{ t('grid.booleanNo') }}</span\n >\n }\n } @else if (col.format) {\n {{ col.format(value, row) }}\n } @else {\n {{ formatValue(value, col, row) }}\n }\n }\n </td>\n }\n @if (actions().length > 0) {\n <td\n class=\"jv-grid-td jv-grid-td-actions\"\n [class.jv-grid-td-sticky-right]=\"resolvedOptions().stickyColumns\"\n >\n <div class=\"jv-grid-actions-group\">\n @for (action of actions(); track action.id) {\n <jv-button\n [variant]=\"actionVariant(action.variant)\"\n [icon]=\"action.icon ?? null\"\n [disabled]=\"action.disabled?.(row) ?? false\"\n [attr.aria-label]=\"action.label\"\n [title]=\"action.label\"\n (click)=\"onActionClick(action, row); $event.stopPropagation()\"\n ></jv-button>\n }\n </div>\n </td>\n }\n </tr>\n }\n }\n </tbody>\n </table>\n </div>\n }\n\n @if (\n resolvedOptions().pageable &&\n !virtualScrollEnabled() &&\n effectiveTotalPages() > 1 &&\n !resolvedOptions().loading\n ) {\n <nav\n class=\"jv-grid-pagination\"\n role=\"navigation\"\n [attr.aria-label]=\"t('grid.paginationLabel', { label: '' })\"\n >\n <div class=\"jv-grid-pagination-info\" aria-live=\"polite\" aria-atomic=\"true\">\n {{\n t('grid.itemsShowing', {\n start: pageStart(),\n end: pageEnd(),\n total: effectiveTotalItems(),\n })\n }}\n </div>\n <div class=\"jv-grid-pagination-controls\">\n @if (pageSizeOptions().length > 0) {\n <jv-select\n [options]=\"pageSizeOptions()\"\n [modelValue]=\"pageSizeStr()\"\n [ariaLabel]=\"t('grid.itemsPerPage')\"\n (selectionChange)=\"onPageSizeChange($event)\"\n />\n }\n <jv-button\n variant=\"outline\"\n icon=\"chevrons-left\"\n [disabled]=\"pageIndex() === 0\"\n (click)=\"goToPage(0)\"\n [attr.aria-label]=\"t('grid.pageFirst')\"\n ></jv-button>\n <jv-button\n variant=\"outline\"\n icon=\"chevron-left\"\n [disabled]=\"pageIndex() === 0\"\n (click)=\"goToPage(pageIndex() - 1)\"\n [attr.aria-label]=\"t('grid.pagePrev')\"\n ></jv-button>\n @for (p of pageRange(); track p) {\n <jv-button\n [variant]=\"p === pageIndex() ? 'primary' : 'outline'\"\n [attr.aria-current]=\"p === pageIndex() ? 'page' : null\"\n [attr.aria-label]=\"t('grid.pageOfTotal', { page: p + 1, total: effectiveTotalPages() })\"\n (click)=\"goToPage(p)\"\n >{{ p + 1 }}</jv-button\n >\n }\n <jv-button\n variant=\"outline\"\n icon=\"chevron-right\"\n [disabled]=\"pageIndex() >= effectiveTotalPages() - 1\"\n (click)=\"goToPage(pageIndex() + 1)\"\n [attr.aria-label]=\"t('grid.pageNext')\"\n ></jv-button>\n <jv-button\n variant=\"outline\"\n icon=\"chevrons-right\"\n [disabled]=\"pageIndex() >= effectiveTotalPages() - 1\"\n (click)=\"goToPage(effectiveTotalPages() - 1)\"\n [attr.aria-label]=\"t('grid.pageLast')\"\n ></jv-button>\n </div>\n </nav>\n }\n</div>\n", 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"] }]
26104
25786
  }], 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"] }] } });
26105
25787
 
26106
25788
  class JvPaginationComponent {
@@ -26274,5 +25956,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImpo
26274
25956
  * Generated bundle index. Do not edit.
26275
25957
  */
26276
25958
 
26277
- export { EN, ES, JV_DEFAULT_LOCALE, JV_FALLBACK_ICON_NAME, JV_GRID_DEFAULT_OPTIONS, JV_LOCALE_DICTIONARIES, JV_LUCIDE_ICON_REGISTRY, JV_UI_CONFIG, JV_UI_CONFIG_DEFAULTS, JV_UI_DEFAULT_CONFIG, JvAccordionComponent, JvAccordionPanelDirective, JvAlertComponent, JvAnnouncementService, JvBadgeComponent, JvBreadcrumbComponent, JvButtonComponent, JvButtonGroupComponent, JvCardComponent, JvChangePasswordPageComponent, JvCheckboxComponent, JvCodeComponent, JvCollapsiblePanelComponent, JvConfirmDialogComponent, JvDashboardShellComponent, JvDatePickerComponent, JvDayPlannerComponent, JvDayPlannerDetailDirective, JvDayPlannerItemDirective, JvDialogComponent, JvDialogService, JvDividerComponent, JvFileInputComponent, JvForgotPasswordPageComponent, JvFormContainerComponent, JvFormatService, JvGridComponent, JvIconButtonComponent, JvIconComponent, JvImageGalleryComponent, JvImageSliderComponent, JvInputComponent, JvListComponent, JvListItemComponent, JvLoaderComponent, JvLoaderService, JvLoginPageComponent, JvModalSelectorComponent, JvMonthCalendarComponent, JvMonthCalendarDayDirective, JvPageComponent, JvPaginationComponent, JvRadioComponent, JvSectionComponent, JvSelectComponent, JvSidebarComponent, JvSwitchComponent, JvTabPanelDirective, JvTabsComponent, JvTextareaComponent, JvThemeService, JvToastComponent, JvToastService, JvTooltipDirective, JvTopbarComponent, JvTranslationService, JvWindowModalComponent, provideJvUi };
25959
+ export { EN, ES, JV_DEFAULT_LOCALE, JV_FALLBACK_ICON_NAME, JV_GRID_DEFAULT_OPTIONS, JV_LOCALE_DICTIONARIES, JV_LUCIDE_ICON_REGISTRY, JV_UI_CONFIG, JV_UI_CONFIG_DEFAULTS, JV_UI_DEFAULT_CONFIG, JvAccordionComponent, JvAccordionPanelDirective, JvAlertComponent, JvAnnouncementService, JvBadgeComponent, JvBreadcrumbComponent, JvButtonComponent, JvButtonGroupComponent, JvCardComponent, JvChangePasswordPageComponent, JvCheckboxComponent, JvCodeComponent, JvCollapsiblePanelComponent, JvConfirmDialogComponent, JvDashboardShellComponent, JvDatePickerComponent, JvDayPlannerComponent, JvDayPlannerDetailDirective, JvDayPlannerItemDirective, JvDialogComponent, JvDialogService, JvDividerComponent, JvFileInputComponent, JvForgotPasswordPageComponent, JvFormContainerComponent, JvFormatService, JvGridComponent, JvIconButtonComponent, JvIconComponent, JvImageGalleryComponent, JvImageSliderComponent, JvInputComponent, JvListComponent, JvListItemComponent, JvLoaderComponent, JvLoaderService, JvLoginPageComponent, JvModalSelectorComponent, JvMonthCalendarComponent, JvMonthCalendarDayDirective, JvPageComponent, JvPaginationComponent, JvRadioComponent, JvSectionComponent, JvSelectComponent, JvSidebarComponent, JvSwitchComponent, JvTabPanelDirective, JvTabsComponent, JvTextareaComponent, JvThemeService, JvToastComponent, JvToastService, JvTooltipDirective, JvTopbarComponent, JvTopbarMenuComponent, JvTranslationService, JvWindowModalComponent, provideJvUi };
26278
25960
  //# sourceMappingURL=devjuliovilla-jv-ui.mjs.map