@devjuliovilla/jv-ui 1.11.0 → 1.11.2

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.
@@ -19936,10 +19936,10 @@ class JvDatePickerComponent {
19936
19936
  visibleMonth = signal(firstOfMonth(new Date()), ...(ngDevMode ? [{ debugName: "visibleMonth" }] : /* istanbul ignore next */ []));
19937
19937
  focusedDate = signal(startOfDay(new Date()), ...(ngDevMode ? [{ debugName: "focusedDate" }] : /* istanbul ignore next */ []));
19938
19938
  dialogId = `jv-date-picker-dialog-${this.instanceId}`;
19939
- value = null;
19939
+ value = signal(null, ...(ngDevMode ? [{ debugName: "value" }] : /* istanbul ignore next */ []));
19940
19940
  disabled = false;
19941
19941
  resolvedDialogTitle = computed(() => this.dialogTitle() || this.translation.translate('datePicker.dialogTitle'), ...(ngDevMode ? [{ debugName: "resolvedDialogTitle" }] : /* istanbul ignore next */ []));
19942
- currentValue = computed(() => this.valueInput() !== undefined ? this.valueInput() : this.value, ...(ngDevMode ? [{ debugName: "currentValue" }] : /* istanbul ignore next */ []));
19942
+ currentValue = computed(() => this.valueInput() !== undefined ? this.valueInput() : this.value(), ...(ngDevMode ? [{ debugName: "currentValue" }] : /* istanbul ignore next */ []));
19943
19943
  triggerAriaLabel = computed(() => {
19944
19944
  const name = this.label() || this.resolvedDialogTitle();
19945
19945
  return `${name}. ${this.displayValue()}`;
@@ -19987,7 +19987,7 @@ class JvDatePickerComponent {
19987
19987
  onChange = () => undefined;
19988
19988
  onTouched = () => undefined;
19989
19989
  writeValue(value) {
19990
- this.value = value ? startOfDay(value) : null;
19990
+ this.value.set(value ? startOfDay(value) : null);
19991
19991
  }
19992
19992
  registerOnChange(fn) {
19993
19993
  this.onChange = fn;
@@ -20093,7 +20093,7 @@ class JvDatePickerComponent {
20093
20093
  this.onTouched();
20094
20094
  }
20095
20095
  updateValue(value) {
20096
- this.value = value;
20096
+ this.value.set(value);
20097
20097
  this.onChange(value);
20098
20098
  this.selectionChange.emit(value);
20099
20099
  }
@@ -21361,172 +21361,172 @@ class JvImageGalleryComponent {
21361
21361
  this.slideDirection.set(direction);
21362
21362
  }
21363
21363
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: JvImageGalleryComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
21364
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.17", type: JvImageGalleryComponent, isStandalone: true, selector: "jv-image-gallery", inputs: { images: { classPropertyName: "images", publicName: "images", isSignal: true, isRequired: false, transformFunction: null }, stripHeightVh: { classPropertyName: "stripHeightVh", publicName: "stripHeightVh", isSignal: true, isRequired: false, transformFunction: null }, actions: { classPropertyName: "actions", publicName: "actions", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { imageClick: "imageClick", actionClick: "actionClick" }, host: { listeners: { "document:keydown": "handleKeydown($event)" } }, viewQueries: [{ propertyName: "strip", first: true, predicate: ["strip"], descendants: true, isSignal: true }], ngImport: i0, template: `
21365
- <div class="jv-gallery">
21366
- <div class="jv-gallery-strip" #strip [style.height]="stripHeightStyle()" (wheel)="onWheel($event)">
21367
- @for (image of images(); track image.src; let i = $index) {
21368
- <button
21369
- type="button"
21370
- class="jv-gallery-thumb"
21371
- [class.jv-gallery-thumb--active]="dialogOpen() && currentIndex() === i"
21372
- [attr.aria-label]="image.alt"
21373
- (wheel)="onWheel($event)"
21374
- (click)="openDialog(i)"
21375
- >
21376
- <img
21377
- class="jv-gallery-thumb-image"
21378
- [src]="image.thumbnail || image.src"
21379
- [alt]="image.alt"
21380
- loading="lazy"
21381
- />
21382
- </button>
21383
- }
21384
- </div>
21385
-
21386
- <jv-dialog
21387
- [open]="dialogOpen()"
21388
- [title]="dialogTitle()"
21389
- (closed)="closeDialog()"
21390
- >
21391
- <div class="jv-gallery-viewer" tabindex="0">
21392
- @if (currentImage(); as image) {
21393
- <img
21394
- class="jv-gallery-viewer-image"
21395
- [class.jv-gallery-viewer-image--slide-left]="slideDirection() === 'left'"
21396
- [class.jv-gallery-viewer-image--slide-right]="slideDirection() === 'right'"
21397
- [src]="image.src"
21398
- [alt]="image.alt"
21399
- />
21400
- }
21401
-
21402
- @if (hasMultiple()) {
21403
- <button
21404
- type="button"
21405
- class="jv-gallery-viewer-nav jv-gallery-viewer-nav--prev"
21406
- [attr.aria-label]="translatedLabels().prev"
21407
- (click)="previous(); $event.stopPropagation()"
21408
- >
21409
- <jv-icon name="chevron-left" [size]="24" />
21410
- </button>
21411
-
21412
- <button
21413
- type="button"
21414
- class="jv-gallery-viewer-nav jv-gallery-viewer-nav--next"
21415
- [attr.aria-label]="translatedLabels().next"
21416
- (click)="next(); $event.stopPropagation()"
21417
- >
21418
- <jv-icon name="chevron-right" [size]="24" />
21419
- </button>
21420
-
21421
- <span class="jv-gallery-viewer-counter">
21422
- {{ currentIndex() + 1 }} / {{ images().length }}
21423
- </span>
21424
- }
21425
- </div>
21426
-
21427
- <div dialog-actions>
21428
- @for (action of actions(); track action.id) {
21429
- <jv-button
21430
- [variant]="action.tone ?? 'outline'"
21431
- (click)="onAction(action)"
21432
- >
21433
- @if (action.icon) {
21434
- <jv-icon [name]="action.icon" [size]="16" />
21435
- }
21436
- {{ action.label }}
21437
- </jv-button>
21438
- }
21439
- <jv-button variant="ghost" (click)="closeDialog()">
21440
- {{ translatedLabels().close }}
21441
- </jv-button>
21442
- </div>
21443
- </jv-dialog>
21444
- </div>
21364
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.17", type: JvImageGalleryComponent, isStandalone: true, selector: "jv-image-gallery", inputs: { images: { classPropertyName: "images", publicName: "images", isSignal: true, isRequired: false, transformFunction: null }, stripHeightVh: { classPropertyName: "stripHeightVh", publicName: "stripHeightVh", isSignal: true, isRequired: false, transformFunction: null }, actions: { classPropertyName: "actions", publicName: "actions", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { imageClick: "imageClick", actionClick: "actionClick" }, host: { listeners: { "document:keydown": "handleKeydown($event)" } }, viewQueries: [{ propertyName: "strip", first: true, predicate: ["strip"], descendants: true, isSignal: true }], ngImport: i0, template: `
21365
+ <div class="jv-gallery">
21366
+ <div class="jv-gallery-strip" #strip [style.height]="stripHeightStyle()" (wheel)="onWheel($event)">
21367
+ @for (image of images(); track image.src; let i = $index) {
21368
+ <button
21369
+ type="button"
21370
+ class="jv-gallery-thumb"
21371
+ [class.jv-gallery-thumb--active]="dialogOpen() && currentIndex() === i"
21372
+ [attr.aria-label]="image.alt"
21373
+ (wheel)="onWheel($event)"
21374
+ (click)="openDialog(i)"
21375
+ >
21376
+ <img
21377
+ class="jv-gallery-thumb-image"
21378
+ [src]="image.thumbnail || image.src"
21379
+ [alt]="image.alt"
21380
+ loading="lazy"
21381
+ />
21382
+ </button>
21383
+ }
21384
+ </div>
21385
+
21386
+ <jv-dialog
21387
+ [open]="dialogOpen()"
21388
+ [title]="dialogTitle()"
21389
+ (closed)="closeDialog()"
21390
+ >
21391
+ <div class="jv-gallery-viewer" tabindex="0">
21392
+ @if (currentImage(); as image) {
21393
+ <img
21394
+ class="jv-gallery-viewer-image"
21395
+ [class.jv-gallery-viewer-image--slide-left]="slideDirection() === 'left'"
21396
+ [class.jv-gallery-viewer-image--slide-right]="slideDirection() === 'right'"
21397
+ [src]="image.src"
21398
+ [alt]="image.alt"
21399
+ />
21400
+ }
21401
+
21402
+ @if (hasMultiple()) {
21403
+ <button
21404
+ type="button"
21405
+ class="jv-gallery-viewer-nav jv-gallery-viewer-nav--prev"
21406
+ [attr.aria-label]="translatedLabels().prev"
21407
+ (click)="previous(); $event.stopPropagation()"
21408
+ >
21409
+ <jv-icon name="chevron-left" [size]="24" />
21410
+ </button>
21411
+
21412
+ <button
21413
+ type="button"
21414
+ class="jv-gallery-viewer-nav jv-gallery-viewer-nav--next"
21415
+ [attr.aria-label]="translatedLabels().next"
21416
+ (click)="next(); $event.stopPropagation()"
21417
+ >
21418
+ <jv-icon name="chevron-right" [size]="24" />
21419
+ </button>
21420
+
21421
+ <span class="jv-gallery-viewer-counter">
21422
+ {{ currentIndex() + 1 }} / {{ images().length }}
21423
+ </span>
21424
+ }
21425
+ </div>
21426
+
21427
+ <div dialog-actions>
21428
+ @for (action of actions(); track action.id) {
21429
+ <jv-button
21430
+ [variant]="action.tone ?? 'outline'"
21431
+ (click)="onAction(action)"
21432
+ >
21433
+ @if (action.icon) {
21434
+ <jv-icon [name]="action.icon" [size]="16" />
21435
+ }
21436
+ {{ action.label }}
21437
+ </jv-button>
21438
+ }
21439
+ <jv-button variant="ghost" (click)="closeDialog()">
21440
+ {{ translatedLabels().close }}
21441
+ </jv-button>
21442
+ </div>
21443
+ </jv-dialog>
21444
+ </div>
21445
21445
  `, isInline: true, styles: [".jv-gallery{display:block;width:100%;max-width:100vw}.jv-gallery-strip{display:flex;gap:5px;width:100%;overflow-x:auto;overflow-y:hidden;overscroll-behavior-x:contain;scrollbar-width:none;-ms-overflow-style:none;scroll-snap-type:x proximity;scroll-behavior:smooth;-webkit-overflow-scrolling:touch}.jv-gallery-strip::-webkit-scrollbar{display:none}.jv-gallery-thumb{flex:0 0 auto;width:calc((100vw - 20px)/4);min-width:120px;height:100%;padding:0;border:2px solid transparent;border-radius:var(--jv-radius-sm);background:transparent;cursor:pointer;overflow:hidden;scroll-snap-align:start;transition:border-color .16s ease,opacity .16s ease}.jv-gallery-thumb:hover{opacity:.85}.jv-gallery-thumb--active{border-color:var(--jv-color-primary)}.jv-gallery-thumb:focus-visible{outline:2px solid var(--jv-color-primary);outline-offset:1px}.jv-gallery-thumb-image{display:block;width:100%;height:100%;object-fit:cover;pointer-events:none}.jv-gallery-viewer{position:relative;display:flex;align-items:center;justify-content:center;width:100%;height:70vh;min-height:300px;background:#000;border-radius:var(--jv-radius-md);overflow:hidden}.jv-gallery-viewer:focus{outline:none}.jv-gallery-viewer-image{display:block;max-width:100%;max-height:100%;object-fit:contain;-webkit-user-select:none;user-select:none}@keyframes gallery-slide-left{0%{transform:translate(100%);opacity:0}to{transform:translate(0);opacity:1}}@keyframes gallery-slide-right{0%{transform:translate(-100%);opacity:0}to{transform:translate(0);opacity:1}}.jv-gallery-viewer-image--slide-left{animation:gallery-slide-left .28s ease-out}.jv-gallery-viewer-image--slide-right{animation:gallery-slide-right .28s ease-out}.jv-gallery-viewer-nav{position:absolute;top:50%;transform:translateY(-50%);display:inline-flex;align-items:center;justify-content:center;width:3rem;height:3rem;border:none;border-radius:var(--jv-radius-md);background:#00000080;color:#fff;cursor:pointer;transition:background-color .16s ease;z-index:1}.jv-gallery-viewer-nav:hover{background:#000000bf}.jv-gallery-viewer-nav:focus-visible{outline:2px solid var(--jv-color-primary);outline-offset:1px}.jv-gallery-viewer-nav--prev{left:var(--jv-spacing-md)}.jv-gallery-viewer-nav--next{right:var(--jv-spacing-md)}.jv-gallery-viewer-counter{position:absolute;bottom:var(--jv-spacing-md);left:50%;transform:translate(-50%);padding:var(--jv-spacing-xs) var(--jv-spacing-sm);border-radius:var(--jv-radius-sm);background:#0009;color:#fff;font-size:.8125rem;font-variant-numeric:tabular-nums;pointer-events:none}@media(max-width:768px){.jv-gallery-thumb{width:calc(100vw - 20px);min-width:200px}.jv-gallery-viewer-nav{width:2.5rem;height:2.5rem}.jv-gallery-viewer-nav--prev{left:var(--jv-spacing-sm)}.jv-gallery-viewer-nav--next{right:var(--jv-spacing-sm)}}@media(prefers-reduced-motion:reduce){.jv-gallery-strip{scroll-behavior:auto}.jv-gallery-viewer-image--slide-left,.jv-gallery-viewer-image--slide-right{animation:none}}\n"], dependencies: [{ kind: "component", type: JvButtonComponent, selector: "jv-button", inputs: ["type", "variant", "icon", "iconPosition", "loading", "disabled"] }, { kind: "component", type: JvDialogComponent, selector: "jv-dialog", inputs: ["open", "title"], outputs: ["closed"] }, { kind: "component", type: JvIconComponent, selector: "jv-icon", inputs: ["name", "size", "strokeWidth", "decorative", "ariaLabel"] }] });
21446
21446
  }
21447
21447
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: JvImageGalleryComponent, decorators: [{
21448
21448
  type: Component,
21449
- args: [{ selector: 'jv-image-gallery', standalone: true, imports: [JvButtonComponent, JvDialogComponent, JvIconComponent], template: `
21450
- <div class="jv-gallery">
21451
- <div class="jv-gallery-strip" #strip [style.height]="stripHeightStyle()" (wheel)="onWheel($event)">
21452
- @for (image of images(); track image.src; let i = $index) {
21453
- <button
21454
- type="button"
21455
- class="jv-gallery-thumb"
21456
- [class.jv-gallery-thumb--active]="dialogOpen() && currentIndex() === i"
21457
- [attr.aria-label]="image.alt"
21458
- (wheel)="onWheel($event)"
21459
- (click)="openDialog(i)"
21460
- >
21461
- <img
21462
- class="jv-gallery-thumb-image"
21463
- [src]="image.thumbnail || image.src"
21464
- [alt]="image.alt"
21465
- loading="lazy"
21466
- />
21467
- </button>
21468
- }
21469
- </div>
21470
-
21471
- <jv-dialog
21472
- [open]="dialogOpen()"
21473
- [title]="dialogTitle()"
21474
- (closed)="closeDialog()"
21475
- >
21476
- <div class="jv-gallery-viewer" tabindex="0">
21477
- @if (currentImage(); as image) {
21478
- <img
21479
- class="jv-gallery-viewer-image"
21480
- [class.jv-gallery-viewer-image--slide-left]="slideDirection() === 'left'"
21481
- [class.jv-gallery-viewer-image--slide-right]="slideDirection() === 'right'"
21482
- [src]="image.src"
21483
- [alt]="image.alt"
21484
- />
21485
- }
21486
-
21487
- @if (hasMultiple()) {
21488
- <button
21489
- type="button"
21490
- class="jv-gallery-viewer-nav jv-gallery-viewer-nav--prev"
21491
- [attr.aria-label]="translatedLabels().prev"
21492
- (click)="previous(); $event.stopPropagation()"
21493
- >
21494
- <jv-icon name="chevron-left" [size]="24" />
21495
- </button>
21496
-
21497
- <button
21498
- type="button"
21499
- class="jv-gallery-viewer-nav jv-gallery-viewer-nav--next"
21500
- [attr.aria-label]="translatedLabels().next"
21501
- (click)="next(); $event.stopPropagation()"
21502
- >
21503
- <jv-icon name="chevron-right" [size]="24" />
21504
- </button>
21505
-
21506
- <span class="jv-gallery-viewer-counter">
21507
- {{ currentIndex() + 1 }} / {{ images().length }}
21508
- </span>
21509
- }
21510
- </div>
21511
-
21512
- <div dialog-actions>
21513
- @for (action of actions(); track action.id) {
21514
- <jv-button
21515
- [variant]="action.tone ?? 'outline'"
21516
- (click)="onAction(action)"
21517
- >
21518
- @if (action.icon) {
21519
- <jv-icon [name]="action.icon" [size]="16" />
21520
- }
21521
- {{ action.label }}
21522
- </jv-button>
21523
- }
21524
- <jv-button variant="ghost" (click)="closeDialog()">
21525
- {{ translatedLabels().close }}
21526
- </jv-button>
21527
- </div>
21528
- </jv-dialog>
21529
- </div>
21449
+ args: [{ selector: 'jv-image-gallery', standalone: true, imports: [JvButtonComponent, JvDialogComponent, JvIconComponent], template: `
21450
+ <div class="jv-gallery">
21451
+ <div class="jv-gallery-strip" #strip [style.height]="stripHeightStyle()" (wheel)="onWheel($event)">
21452
+ @for (image of images(); track image.src; let i = $index) {
21453
+ <button
21454
+ type="button"
21455
+ class="jv-gallery-thumb"
21456
+ [class.jv-gallery-thumb--active]="dialogOpen() && currentIndex() === i"
21457
+ [attr.aria-label]="image.alt"
21458
+ (wheel)="onWheel($event)"
21459
+ (click)="openDialog(i)"
21460
+ >
21461
+ <img
21462
+ class="jv-gallery-thumb-image"
21463
+ [src]="image.thumbnail || image.src"
21464
+ [alt]="image.alt"
21465
+ loading="lazy"
21466
+ />
21467
+ </button>
21468
+ }
21469
+ </div>
21470
+
21471
+ <jv-dialog
21472
+ [open]="dialogOpen()"
21473
+ [title]="dialogTitle()"
21474
+ (closed)="closeDialog()"
21475
+ >
21476
+ <div class="jv-gallery-viewer" tabindex="0">
21477
+ @if (currentImage(); as image) {
21478
+ <img
21479
+ class="jv-gallery-viewer-image"
21480
+ [class.jv-gallery-viewer-image--slide-left]="slideDirection() === 'left'"
21481
+ [class.jv-gallery-viewer-image--slide-right]="slideDirection() === 'right'"
21482
+ [src]="image.src"
21483
+ [alt]="image.alt"
21484
+ />
21485
+ }
21486
+
21487
+ @if (hasMultiple()) {
21488
+ <button
21489
+ type="button"
21490
+ class="jv-gallery-viewer-nav jv-gallery-viewer-nav--prev"
21491
+ [attr.aria-label]="translatedLabels().prev"
21492
+ (click)="previous(); $event.stopPropagation()"
21493
+ >
21494
+ <jv-icon name="chevron-left" [size]="24" />
21495
+ </button>
21496
+
21497
+ <button
21498
+ type="button"
21499
+ class="jv-gallery-viewer-nav jv-gallery-viewer-nav--next"
21500
+ [attr.aria-label]="translatedLabels().next"
21501
+ (click)="next(); $event.stopPropagation()"
21502
+ >
21503
+ <jv-icon name="chevron-right" [size]="24" />
21504
+ </button>
21505
+
21506
+ <span class="jv-gallery-viewer-counter">
21507
+ {{ currentIndex() + 1 }} / {{ images().length }}
21508
+ </span>
21509
+ }
21510
+ </div>
21511
+
21512
+ <div dialog-actions>
21513
+ @for (action of actions(); track action.id) {
21514
+ <jv-button
21515
+ [variant]="action.tone ?? 'outline'"
21516
+ (click)="onAction(action)"
21517
+ >
21518
+ @if (action.icon) {
21519
+ <jv-icon [name]="action.icon" [size]="16" />
21520
+ }
21521
+ {{ action.label }}
21522
+ </jv-button>
21523
+ }
21524
+ <jv-button variant="ghost" (click)="closeDialog()">
21525
+ {{ translatedLabels().close }}
21526
+ </jv-button>
21527
+ </div>
21528
+ </jv-dialog>
21529
+ </div>
21530
21530
  `, styles: [".jv-gallery{display:block;width:100%;max-width:100vw}.jv-gallery-strip{display:flex;gap:5px;width:100%;overflow-x:auto;overflow-y:hidden;overscroll-behavior-x:contain;scrollbar-width:none;-ms-overflow-style:none;scroll-snap-type:x proximity;scroll-behavior:smooth;-webkit-overflow-scrolling:touch}.jv-gallery-strip::-webkit-scrollbar{display:none}.jv-gallery-thumb{flex:0 0 auto;width:calc((100vw - 20px)/4);min-width:120px;height:100%;padding:0;border:2px solid transparent;border-radius:var(--jv-radius-sm);background:transparent;cursor:pointer;overflow:hidden;scroll-snap-align:start;transition:border-color .16s ease,opacity .16s ease}.jv-gallery-thumb:hover{opacity:.85}.jv-gallery-thumb--active{border-color:var(--jv-color-primary)}.jv-gallery-thumb:focus-visible{outline:2px solid var(--jv-color-primary);outline-offset:1px}.jv-gallery-thumb-image{display:block;width:100%;height:100%;object-fit:cover;pointer-events:none}.jv-gallery-viewer{position:relative;display:flex;align-items:center;justify-content:center;width:100%;height:70vh;min-height:300px;background:#000;border-radius:var(--jv-radius-md);overflow:hidden}.jv-gallery-viewer:focus{outline:none}.jv-gallery-viewer-image{display:block;max-width:100%;max-height:100%;object-fit:contain;-webkit-user-select:none;user-select:none}@keyframes gallery-slide-left{0%{transform:translate(100%);opacity:0}to{transform:translate(0);opacity:1}}@keyframes gallery-slide-right{0%{transform:translate(-100%);opacity:0}to{transform:translate(0);opacity:1}}.jv-gallery-viewer-image--slide-left{animation:gallery-slide-left .28s ease-out}.jv-gallery-viewer-image--slide-right{animation:gallery-slide-right .28s ease-out}.jv-gallery-viewer-nav{position:absolute;top:50%;transform:translateY(-50%);display:inline-flex;align-items:center;justify-content:center;width:3rem;height:3rem;border:none;border-radius:var(--jv-radius-md);background:#00000080;color:#fff;cursor:pointer;transition:background-color .16s ease;z-index:1}.jv-gallery-viewer-nav:hover{background:#000000bf}.jv-gallery-viewer-nav:focus-visible{outline:2px solid var(--jv-color-primary);outline-offset:1px}.jv-gallery-viewer-nav--prev{left:var(--jv-spacing-md)}.jv-gallery-viewer-nav--next{right:var(--jv-spacing-md)}.jv-gallery-viewer-counter{position:absolute;bottom:var(--jv-spacing-md);left:50%;transform:translate(-50%);padding:var(--jv-spacing-xs) var(--jv-spacing-sm);border-radius:var(--jv-radius-sm);background:#0009;color:#fff;font-size:.8125rem;font-variant-numeric:tabular-nums;pointer-events:none}@media(max-width:768px){.jv-gallery-thumb{width:calc(100vw - 20px);min-width:200px}.jv-gallery-viewer-nav{width:2.5rem;height:2.5rem}.jv-gallery-viewer-nav--prev{left:var(--jv-spacing-sm)}.jv-gallery-viewer-nav--next{right:var(--jv-spacing-sm)}}@media(prefers-reduced-motion:reduce){.jv-gallery-strip{scroll-behavior:auto}.jv-gallery-viewer-image--slide-left,.jv-gallery-viewer-image--slide-right{animation:none}}\n"] }]
21531
21531
  }], propDecorators: { images: [{ type: i0.Input, args: [{ isSignal: true, alias: "images", required: false }] }], stripHeightVh: [{ type: i0.Input, args: [{ isSignal: true, alias: "stripHeightVh", required: false }] }], actions: [{ type: i0.Input, args: [{ isSignal: true, alias: "actions", required: false }] }], imageClick: [{ type: i0.Output, args: ["imageClick"] }], actionClick: [{ type: i0.Output, args: ["actionClick"] }], strip: [{ type: i0.ViewChild, args: ['strip', { isSignal: true }] }], handleKeydown: [{
21532
21532
  type: HostListener,
@@ -21620,164 +21620,164 @@ class JvImageSliderComponent {
21620
21620
  }
21621
21621
  }
21622
21622
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: JvImageSliderComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
21623
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.17", type: JvImageSliderComponent, isStandalone: true, selector: "jv-image-slider", inputs: { images: { classPropertyName: "images", publicName: "images", isSignal: true, isRequired: false, transformFunction: null }, showThumbnails: { classPropertyName: "showThumbnails", publicName: "showThumbnails", isSignal: true, isRequired: false, transformFunction: null }, showDescription: { classPropertyName: "showDescription", publicName: "showDescription", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { imageChange: "imageChange" }, host: { listeners: { "keydown": "handleKeydown($event)" } }, viewQueries: [{ propertyName: "thumbnailStripRef", first: true, predicate: ["thumbnailStrip"], descendants: true, isSignal: true }], ngImport: i0, template: `
21624
- <div class="jv-slider">
21625
- <div class="jv-slider-main">
21626
- @if (hasMultiple()) {
21627
- <button
21628
- type="button"
21629
- class="jv-slider-nav jv-slider-nav--prev"
21630
- [attr.aria-label]="translatedAriaLabels().prev"
21631
- (click)="previous()"
21632
- >
21633
- <jv-icon name="chevron-left" [size]="20" />
21634
- </button>
21635
- }
21636
-
21637
- <div class="jv-slider-stage">
21638
- @for (image of images(); track image.src; let i = $index) {
21639
- <div
21640
- class="jv-slider-slide"
21641
- [class.jv-slider-slide--active]="i === currentIndex()"
21642
- >
21643
- <img
21644
- class="jv-slider-image"
21645
- [src]="image.src"
21646
- [alt]="image.alt"
21647
- loading="lazy"
21648
- />
21649
- </div>
21650
- }
21651
- </div>
21652
-
21653
- @if (hasMultiple()) {
21654
- <button
21655
- type="button"
21656
- class="jv-slider-nav jv-slider-nav--next"
21657
- [attr.aria-label]="translatedAriaLabels().next"
21658
- (click)="next()"
21659
- >
21660
- <jv-icon name="chevron-right" [size]="20" />
21661
- </button>
21662
- }
21663
- </div>
21664
-
21665
- @if (currentImage(); as image) {
21666
- @if (showDescription() && image.description) {
21667
- <div class="jv-slider-description">
21668
- <p>{{ image.description }}</p>
21669
- </div>
21670
- }
21671
- }
21672
-
21673
- @if (hasMultiple() && showThumbnails()) {
21674
- <div class="jv-slider-thumbnails" #thumbnailStrip role="tablist" [attr.aria-label]="translatedAriaLabels().thumbnailList">
21675
- @for (image of images(); track image.src; let i = $index) {
21676
- <button
21677
- type="button"
21678
- class="jv-slider-thumbnail"
21679
- [class.jv-slider-thumbnail--active]="i === currentIndex()"
21680
- role="tab"
21681
- [attr.aria-selected]="i === currentIndex() ? 'true' : 'false'"
21682
- [attr.aria-label]="image.alt"
21683
- (click)="goTo(i)"
21684
- >
21685
- <img
21686
- class="jv-slider-thumbnail-image"
21687
- [src]="image.thumbnail || image.src"
21688
- [alt]="image.alt"
21689
- loading="lazy"
21690
- />
21691
- </button>
21692
- }
21693
- </div>
21694
-
21695
- <div class="jv-slider-counter">
21696
- {{ currentIndex() + 1 }} / {{ images().length }}
21697
- </div>
21698
- }
21699
- </div>
21623
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.17", type: JvImageSliderComponent, isStandalone: true, selector: "jv-image-slider", inputs: { images: { classPropertyName: "images", publicName: "images", isSignal: true, isRequired: false, transformFunction: null }, showThumbnails: { classPropertyName: "showThumbnails", publicName: "showThumbnails", isSignal: true, isRequired: false, transformFunction: null }, showDescription: { classPropertyName: "showDescription", publicName: "showDescription", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { imageChange: "imageChange" }, host: { listeners: { "keydown": "handleKeydown($event)" } }, viewQueries: [{ propertyName: "thumbnailStripRef", first: true, predicate: ["thumbnailStrip"], descendants: true, isSignal: true }], ngImport: i0, template: `
21624
+ <div class="jv-slider">
21625
+ <div class="jv-slider-main">
21626
+ @if (hasMultiple()) {
21627
+ <button
21628
+ type="button"
21629
+ class="jv-slider-nav jv-slider-nav--prev"
21630
+ [attr.aria-label]="translatedAriaLabels().prev"
21631
+ (click)="previous()"
21632
+ >
21633
+ <jv-icon name="chevron-left" [size]="20" />
21634
+ </button>
21635
+ }
21636
+
21637
+ <div class="jv-slider-stage">
21638
+ @for (image of images(); track image.src; let i = $index) {
21639
+ <div
21640
+ class="jv-slider-slide"
21641
+ [class.jv-slider-slide--active]="i === currentIndex()"
21642
+ >
21643
+ <img
21644
+ class="jv-slider-image"
21645
+ [src]="image.src"
21646
+ [alt]="image.alt"
21647
+ loading="lazy"
21648
+ />
21649
+ </div>
21650
+ }
21651
+ </div>
21652
+
21653
+ @if (hasMultiple()) {
21654
+ <button
21655
+ type="button"
21656
+ class="jv-slider-nav jv-slider-nav--next"
21657
+ [attr.aria-label]="translatedAriaLabels().next"
21658
+ (click)="next()"
21659
+ >
21660
+ <jv-icon name="chevron-right" [size]="20" />
21661
+ </button>
21662
+ }
21663
+ </div>
21664
+
21665
+ @if (currentImage(); as image) {
21666
+ @if (showDescription() && image.description) {
21667
+ <div class="jv-slider-description">
21668
+ <p>{{ image.description }}</p>
21669
+ </div>
21670
+ }
21671
+ }
21672
+
21673
+ @if (hasMultiple() && showThumbnails()) {
21674
+ <div class="jv-slider-thumbnails" #thumbnailStrip role="tablist" [attr.aria-label]="translatedAriaLabels().thumbnailList">
21675
+ @for (image of images(); track image.src; let i = $index) {
21676
+ <button
21677
+ type="button"
21678
+ class="jv-slider-thumbnail"
21679
+ [class.jv-slider-thumbnail--active]="i === currentIndex()"
21680
+ role="tab"
21681
+ [attr.aria-selected]="i === currentIndex() ? 'true' : 'false'"
21682
+ [attr.aria-label]="image.alt"
21683
+ (click)="goTo(i)"
21684
+ >
21685
+ <img
21686
+ class="jv-slider-thumbnail-image"
21687
+ [src]="image.thumbnail || image.src"
21688
+ [alt]="image.alt"
21689
+ loading="lazy"
21690
+ />
21691
+ </button>
21692
+ }
21693
+ </div>
21694
+
21695
+ <div class="jv-slider-counter">
21696
+ {{ currentIndex() + 1 }} / {{ images().length }}
21697
+ </div>
21698
+ }
21699
+ </div>
21700
21700
  `, isInline: true, styles: [".jv-slider{display:grid;gap:var(--jv-spacing-sm);width:100%;max-width:100%}.jv-slider-main{position:relative;display:grid;grid-template-columns:auto 1fr auto;align-items:center;gap:var(--jv-spacing-xs)}.jv-slider-stage{position:relative;aspect-ratio:16 / 9;overflow:hidden;border-radius:var(--jv-radius-md);background:var(--jv-color-surface-muted)}.jv-slider-slide{position:absolute;inset:0;opacity:0;transition:opacity .2s ease;pointer-events:none}.jv-slider-slide--active{opacity:1;pointer-events:auto}.jv-slider-image{display:block;width:100%;height:100%;object-fit:contain;-webkit-user-select:none;user-select:none}.jv-slider-nav{display:inline-flex;align-items:center;justify-content:center;width:2.5rem;height:2.5rem;border:1px solid var(--jv-color-border);border-radius:var(--jv-radius-md);background:var(--jv-color-surface);color:var(--jv-color-foreground);cursor:pointer;transition:background-color .16s ease,border-color .16s ease,color .16s ease;flex-shrink:0;z-index:1}.jv-slider-nav:hover{background:var(--jv-color-surface-muted);border-color:var(--jv-color-foreground-muted)}.jv-slider-nav:focus-visible{outline:2px solid var(--jv-color-primary);outline-offset:1px}.jv-slider-description{padding:var(--jv-spacing-sm) 0}.jv-slider-description p{margin:0;font-size:var(--jv-density-font-size);color:var(--jv-color-foreground-muted);line-height:1.5}.jv-slider-thumbnails{display:flex;gap:var(--jv-spacing-xs);overflow-x:auto;scrollbar-width:none;-ms-overflow-style:none;padding:var(--jv-spacing-xs) 0}.jv-slider-thumbnails::-webkit-scrollbar{display:none}.jv-slider-thumbnail{flex:0 0 auto;width:3.5rem;height:3.5rem;padding:0;border:2px solid transparent;border-radius:var(--jv-radius-sm);background:transparent;cursor:pointer;overflow:hidden;transition:border-color .16s ease,opacity .16s ease;opacity:.6}.jv-slider-thumbnail:hover{opacity:.85}.jv-slider-thumbnail--active{border-color:var(--jv-color-primary);opacity:1}.jv-slider-thumbnail:focus-visible{outline:2px solid var(--jv-color-primary);outline-offset:1px}.jv-slider-thumbnail-image{display:block;width:100%;height:100%;object-fit:cover}.jv-slider-counter{text-align:center;font-size:.8125rem;color:var(--jv-color-foreground-muted);font-variant-numeric:tabular-nums}@media(max-width:640px){.jv-slider-nav{width:2rem;height:2rem}.jv-slider-thumbnail{width:2.75rem;height:2.75rem}}@media(prefers-reduced-motion:reduce){.jv-slider-slide{transition:none}}\n"], dependencies: [{ kind: "component", type: JvIconComponent, selector: "jv-icon", inputs: ["name", "size", "strokeWidth", "decorative", "ariaLabel"] }] });
21701
21701
  }
21702
21702
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: JvImageSliderComponent, decorators: [{
21703
21703
  type: Component,
21704
- args: [{ selector: 'jv-image-slider', standalone: true, imports: [JvIconComponent], template: `
21705
- <div class="jv-slider">
21706
- <div class="jv-slider-main">
21707
- @if (hasMultiple()) {
21708
- <button
21709
- type="button"
21710
- class="jv-slider-nav jv-slider-nav--prev"
21711
- [attr.aria-label]="translatedAriaLabels().prev"
21712
- (click)="previous()"
21713
- >
21714
- <jv-icon name="chevron-left" [size]="20" />
21715
- </button>
21716
- }
21717
-
21718
- <div class="jv-slider-stage">
21719
- @for (image of images(); track image.src; let i = $index) {
21720
- <div
21721
- class="jv-slider-slide"
21722
- [class.jv-slider-slide--active]="i === currentIndex()"
21723
- >
21724
- <img
21725
- class="jv-slider-image"
21726
- [src]="image.src"
21727
- [alt]="image.alt"
21728
- loading="lazy"
21729
- />
21730
- </div>
21731
- }
21732
- </div>
21733
-
21734
- @if (hasMultiple()) {
21735
- <button
21736
- type="button"
21737
- class="jv-slider-nav jv-slider-nav--next"
21738
- [attr.aria-label]="translatedAriaLabels().next"
21739
- (click)="next()"
21740
- >
21741
- <jv-icon name="chevron-right" [size]="20" />
21742
- </button>
21743
- }
21744
- </div>
21745
-
21746
- @if (currentImage(); as image) {
21747
- @if (showDescription() && image.description) {
21748
- <div class="jv-slider-description">
21749
- <p>{{ image.description }}</p>
21750
- </div>
21751
- }
21752
- }
21753
-
21754
- @if (hasMultiple() && showThumbnails()) {
21755
- <div class="jv-slider-thumbnails" #thumbnailStrip role="tablist" [attr.aria-label]="translatedAriaLabels().thumbnailList">
21756
- @for (image of images(); track image.src; let i = $index) {
21757
- <button
21758
- type="button"
21759
- class="jv-slider-thumbnail"
21760
- [class.jv-slider-thumbnail--active]="i === currentIndex()"
21761
- role="tab"
21762
- [attr.aria-selected]="i === currentIndex() ? 'true' : 'false'"
21763
- [attr.aria-label]="image.alt"
21764
- (click)="goTo(i)"
21765
- >
21766
- <img
21767
- class="jv-slider-thumbnail-image"
21768
- [src]="image.thumbnail || image.src"
21769
- [alt]="image.alt"
21770
- loading="lazy"
21771
- />
21772
- </button>
21773
- }
21774
- </div>
21775
-
21776
- <div class="jv-slider-counter">
21777
- {{ currentIndex() + 1 }} / {{ images().length }}
21778
- </div>
21779
- }
21780
- </div>
21704
+ args: [{ selector: 'jv-image-slider', standalone: true, imports: [JvIconComponent], template: `
21705
+ <div class="jv-slider">
21706
+ <div class="jv-slider-main">
21707
+ @if (hasMultiple()) {
21708
+ <button
21709
+ type="button"
21710
+ class="jv-slider-nav jv-slider-nav--prev"
21711
+ [attr.aria-label]="translatedAriaLabels().prev"
21712
+ (click)="previous()"
21713
+ >
21714
+ <jv-icon name="chevron-left" [size]="20" />
21715
+ </button>
21716
+ }
21717
+
21718
+ <div class="jv-slider-stage">
21719
+ @for (image of images(); track image.src; let i = $index) {
21720
+ <div
21721
+ class="jv-slider-slide"
21722
+ [class.jv-slider-slide--active]="i === currentIndex()"
21723
+ >
21724
+ <img
21725
+ class="jv-slider-image"
21726
+ [src]="image.src"
21727
+ [alt]="image.alt"
21728
+ loading="lazy"
21729
+ />
21730
+ </div>
21731
+ }
21732
+ </div>
21733
+
21734
+ @if (hasMultiple()) {
21735
+ <button
21736
+ type="button"
21737
+ class="jv-slider-nav jv-slider-nav--next"
21738
+ [attr.aria-label]="translatedAriaLabels().next"
21739
+ (click)="next()"
21740
+ >
21741
+ <jv-icon name="chevron-right" [size]="20" />
21742
+ </button>
21743
+ }
21744
+ </div>
21745
+
21746
+ @if (currentImage(); as image) {
21747
+ @if (showDescription() && image.description) {
21748
+ <div class="jv-slider-description">
21749
+ <p>{{ image.description }}</p>
21750
+ </div>
21751
+ }
21752
+ }
21753
+
21754
+ @if (hasMultiple() && showThumbnails()) {
21755
+ <div class="jv-slider-thumbnails" #thumbnailStrip role="tablist" [attr.aria-label]="translatedAriaLabels().thumbnailList">
21756
+ @for (image of images(); track image.src; let i = $index) {
21757
+ <button
21758
+ type="button"
21759
+ class="jv-slider-thumbnail"
21760
+ [class.jv-slider-thumbnail--active]="i === currentIndex()"
21761
+ role="tab"
21762
+ [attr.aria-selected]="i === currentIndex() ? 'true' : 'false'"
21763
+ [attr.aria-label]="image.alt"
21764
+ (click)="goTo(i)"
21765
+ >
21766
+ <img
21767
+ class="jv-slider-thumbnail-image"
21768
+ [src]="image.thumbnail || image.src"
21769
+ [alt]="image.alt"
21770
+ loading="lazy"
21771
+ />
21772
+ </button>
21773
+ }
21774
+ </div>
21775
+
21776
+ <div class="jv-slider-counter">
21777
+ {{ currentIndex() + 1 }} / {{ images().length }}
21778
+ </div>
21779
+ }
21780
+ </div>
21781
21781
  `, styles: [".jv-slider{display:grid;gap:var(--jv-spacing-sm);width:100%;max-width:100%}.jv-slider-main{position:relative;display:grid;grid-template-columns:auto 1fr auto;align-items:center;gap:var(--jv-spacing-xs)}.jv-slider-stage{position:relative;aspect-ratio:16 / 9;overflow:hidden;border-radius:var(--jv-radius-md);background:var(--jv-color-surface-muted)}.jv-slider-slide{position:absolute;inset:0;opacity:0;transition:opacity .2s ease;pointer-events:none}.jv-slider-slide--active{opacity:1;pointer-events:auto}.jv-slider-image{display:block;width:100%;height:100%;object-fit:contain;-webkit-user-select:none;user-select:none}.jv-slider-nav{display:inline-flex;align-items:center;justify-content:center;width:2.5rem;height:2.5rem;border:1px solid var(--jv-color-border);border-radius:var(--jv-radius-md);background:var(--jv-color-surface);color:var(--jv-color-foreground);cursor:pointer;transition:background-color .16s ease,border-color .16s ease,color .16s ease;flex-shrink:0;z-index:1}.jv-slider-nav:hover{background:var(--jv-color-surface-muted);border-color:var(--jv-color-foreground-muted)}.jv-slider-nav:focus-visible{outline:2px solid var(--jv-color-primary);outline-offset:1px}.jv-slider-description{padding:var(--jv-spacing-sm) 0}.jv-slider-description p{margin:0;font-size:var(--jv-density-font-size);color:var(--jv-color-foreground-muted);line-height:1.5}.jv-slider-thumbnails{display:flex;gap:var(--jv-spacing-xs);overflow-x:auto;scrollbar-width:none;-ms-overflow-style:none;padding:var(--jv-spacing-xs) 0}.jv-slider-thumbnails::-webkit-scrollbar{display:none}.jv-slider-thumbnail{flex:0 0 auto;width:3.5rem;height:3.5rem;padding:0;border:2px solid transparent;border-radius:var(--jv-radius-sm);background:transparent;cursor:pointer;overflow:hidden;transition:border-color .16s ease,opacity .16s ease;opacity:.6}.jv-slider-thumbnail:hover{opacity:.85}.jv-slider-thumbnail--active{border-color:var(--jv-color-primary);opacity:1}.jv-slider-thumbnail:focus-visible{outline:2px solid var(--jv-color-primary);outline-offset:1px}.jv-slider-thumbnail-image{display:block;width:100%;height:100%;object-fit:cover}.jv-slider-counter{text-align:center;font-size:.8125rem;color:var(--jv-color-foreground-muted);font-variant-numeric:tabular-nums}@media(max-width:640px){.jv-slider-nav{width:2rem;height:2rem}.jv-slider-thumbnail{width:2.75rem;height:2.75rem}}@media(prefers-reduced-motion:reduce){.jv-slider-slide{transition:none}}\n"] }]
21782
21782
  }], ctorParameters: () => [], propDecorators: { thumbnailStripRef: [{ type: i0.ViewChild, args: ['thumbnailStrip', { isSignal: true }] }], images: [{ type: i0.Input, args: [{ isSignal: true, alias: "images", required: false }] }], showThumbnails: [{ type: i0.Input, args: [{ isSignal: true, alias: "showThumbnails", required: false }] }], showDescription: [{ type: i0.Input, args: [{ isSignal: true, alias: "showDescription", required: false }] }], imageChange: [{ type: i0.Output, args: ["imageChange"] }], handleKeydown: [{
21783
21783
  type: HostListener,
@@ -22831,192 +22831,192 @@ class JvTabsComponent {
22831
22831
  return (Array.from(tabList.querySelectorAll('[data-jv-tab-id]')).find((button) => button.dataset['jvTabId'] === tabId) ?? null);
22832
22832
  }
22833
22833
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: JvTabsComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
22834
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.17", type: JvTabsComponent, isStandalone: true, selector: "jv-tabs", inputs: { tabs: { classPropertyName: "tabs", publicName: "tabs", isSignal: true, isRequired: false, transformFunction: null }, activeTabId: { classPropertyName: "activeTabId", publicName: "activeTabId", isSignal: true, isRequired: false, transformFunction: null }, keepMounted: { classPropertyName: "keepMounted", publicName: "keepMounted", isSignal: true, isRequired: false, transformFunction: null }, transition: { classPropertyName: "transition", publicName: "transition", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { activeTabChange: "activeTabChange" }, host: { listeners: { "window:resize": "handleWindowResize()" } }, queries: [{ propertyName: "panelTemplates", predicate: JvTabPanelDirective, isSignal: true }], viewQueries: [{ propertyName: "tabListRef", first: true, predicate: ["tabList"], descendants: true, isSignal: true }], ngImport: i0, template: `
22835
- <div class="jv-tabs">
22836
- <div class="jv-tabs-header">
22837
- <div class="jv-tabs-scroll" #tabList role="tablist" aria-orientation="horizontal" (scroll)="updateScrollControls()">
22838
- @for (tab of tabs(); track tab.id) {
22839
- <button
22840
- type="button"
22841
- class="jv-tab"
22842
- [class.jv-tab--active]="isActive(tab.id)"
22843
- [class.jv-tab--disabled]="tab.disabled ?? false"
22844
- [id]="tabButtonId(tab.id)"
22845
- [attr.data-jv-tab-id]="tab.id"
22846
- role="tab"
22847
- [attr.aria-selected]="isActive(tab.id) ? 'true' : 'false'"
22848
- [attr.aria-controls]="panelId(tab.id)"
22849
- [attr.tabindex]="getTabIndex(tab.id, tab.disabled ?? false)"
22850
- [disabled]="tab.disabled ?? false"
22851
- (click)="activateTab(tab.id)"
22852
- (keydown)="handleKeydown($event, tab.id)"
22853
- >
22854
- @if (tab.icon) {
22855
- <jv-icon [name]="tab.icon" [size]="16" />
22856
- }
22857
- <span>{{ tab.label }}</span>
22858
- </button>
22859
- }
22860
- </div>
22861
-
22862
- @if (showScrollLeft() || showScrollRight()) {
22863
- <div class="jv-tabs-controls" aria-hidden="true">
22864
- <button type="button" class="jv-tabs-scroll-button" [disabled]="!showScrollLeft()" (click)="scrollTabs(-1)">
22865
- <jv-icon name="chevron-left" [size]="16" />
22866
- </button>
22867
- <button type="button" class="jv-tabs-scroll-button" [disabled]="!showScrollRight()" (click)="scrollTabs(1)">
22868
- <jv-icon name="chevron-right" [size]="16" />
22869
- </button>
22870
- </div>
22871
- }
22872
- </div>
22873
-
22874
- <div class="jv-tabs-panels">
22875
- @if (keepMounted()) {
22876
- @for (tab of tabs(); track tab.id) {
22877
- <div
22878
- class="jv-tab-panel"
22879
- [class.jv-tab-panel--hidden]="!isActive(tab.id)"
22880
- [class.jv-tab-panel--fade]="effectiveTransition() === 'fade'"
22881
- [class.jv-tab-panel--slide-left]="effectiveTransition() === 'slide' && slideDirection() === 'left'"
22882
- [class.jv-tab-panel--slide-right]="effectiveTransition() === 'slide' && slideDirection() === 'right'"
22883
- [id]="panelId(tab.id)"
22884
- role="tabpanel"
22885
- [attr.aria-labelledby]="tabButtonId(tab.id)"
22886
- [attr.tabindex]="isActive(tab.id) ? 0 : null"
22887
- >
22888
- <ng-container [ngTemplateOutlet]="panelTemplate(tab.id) ?? null" />
22889
- </div>
22890
- }
22891
- } @else {
22892
- @if (outgoingPanelId(); as outgoingId) {
22893
- <div
22894
- class="jv-tab-panel jv-tab-panel--exit"
22895
- [class.jv-tab-panel--fade]="effectiveTransition() === 'fade'"
22896
- [class.jv-tab-panel--slide-left]="effectiveTransition() === 'slide' && slideDirection() === 'left'"
22897
- [class.jv-tab-panel--slide-right]="effectiveTransition() === 'slide' && slideDirection() === 'right'"
22898
- [id]="panelId(outgoingId)"
22899
- role="tabpanel"
22900
- [attr.aria-labelledby]="tabButtonId(outgoingId)"
22901
- tabindex="-1"
22902
- (transitionend)="handleTransitionEnd($event)"
22903
- >
22904
- <ng-container [ngTemplateOutlet]="panelTemplate(outgoingId) ?? null" />
22905
- </div>
22906
- }
22907
-
22908
- @if (resolvedActiveTabId(); as activeId) {
22909
- <div
22910
- class="jv-tab-panel jv-tab-panel--entering"
22911
- [class.jv-tab-panel--fade]="effectiveTransition() === 'fade'"
22912
- [class.jv-tab-panel--slide-left]="effectiveTransition() === 'slide' && slideDirection() === 'left'"
22913
- [class.jv-tab-panel--slide-right]="effectiveTransition() === 'slide' && slideDirection() === 'right'"
22914
- [id]="panelId(activeId)"
22915
- role="tabpanel"
22916
- [attr.aria-labelledby]="tabButtonId(activeId)"
22917
- tabindex="0"
22918
- >
22919
- <ng-container [ngTemplateOutlet]="panelTemplate(activeId) ?? null" />
22920
- </div>
22921
- }
22922
- }
22923
- </div>
22924
- </div>
22834
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.17", type: JvTabsComponent, isStandalone: true, selector: "jv-tabs", inputs: { tabs: { classPropertyName: "tabs", publicName: "tabs", isSignal: true, isRequired: false, transformFunction: null }, activeTabId: { classPropertyName: "activeTabId", publicName: "activeTabId", isSignal: true, isRequired: false, transformFunction: null }, keepMounted: { classPropertyName: "keepMounted", publicName: "keepMounted", isSignal: true, isRequired: false, transformFunction: null }, transition: { classPropertyName: "transition", publicName: "transition", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { activeTabChange: "activeTabChange" }, host: { listeners: { "window:resize": "handleWindowResize()" } }, queries: [{ propertyName: "panelTemplates", predicate: JvTabPanelDirective, isSignal: true }], viewQueries: [{ propertyName: "tabListRef", first: true, predicate: ["tabList"], descendants: true, isSignal: true }], ngImport: i0, template: `
22835
+ <div class="jv-tabs">
22836
+ <div class="jv-tabs-header">
22837
+ <div class="jv-tabs-scroll" #tabList role="tablist" aria-orientation="horizontal" (scroll)="updateScrollControls()">
22838
+ @for (tab of tabs(); track tab.id) {
22839
+ <button
22840
+ type="button"
22841
+ class="jv-tab"
22842
+ [class.jv-tab--active]="isActive(tab.id)"
22843
+ [class.jv-tab--disabled]="tab.disabled ?? false"
22844
+ [id]="tabButtonId(tab.id)"
22845
+ [attr.data-jv-tab-id]="tab.id"
22846
+ role="tab"
22847
+ [attr.aria-selected]="isActive(tab.id) ? 'true' : 'false'"
22848
+ [attr.aria-controls]="panelId(tab.id)"
22849
+ [attr.tabindex]="getTabIndex(tab.id, tab.disabled ?? false)"
22850
+ [disabled]="tab.disabled ?? false"
22851
+ (click)="activateTab(tab.id)"
22852
+ (keydown)="handleKeydown($event, tab.id)"
22853
+ >
22854
+ @if (tab.icon) {
22855
+ <jv-icon [name]="tab.icon" [size]="16" />
22856
+ }
22857
+ <span>{{ tab.label }}</span>
22858
+ </button>
22859
+ }
22860
+ </div>
22861
+
22862
+ @if (showScrollLeft() || showScrollRight()) {
22863
+ <div class="jv-tabs-controls" aria-hidden="true">
22864
+ <button type="button" class="jv-tabs-scroll-button" [disabled]="!showScrollLeft()" (click)="scrollTabs(-1)">
22865
+ <jv-icon name="chevron-left" [size]="16" />
22866
+ </button>
22867
+ <button type="button" class="jv-tabs-scroll-button" [disabled]="!showScrollRight()" (click)="scrollTabs(1)">
22868
+ <jv-icon name="chevron-right" [size]="16" />
22869
+ </button>
22870
+ </div>
22871
+ }
22872
+ </div>
22873
+
22874
+ <div class="jv-tabs-panels">
22875
+ @if (keepMounted()) {
22876
+ @for (tab of tabs(); track tab.id) {
22877
+ <div
22878
+ class="jv-tab-panel"
22879
+ [class.jv-tab-panel--hidden]="!isActive(tab.id)"
22880
+ [class.jv-tab-panel--fade]="effectiveTransition() === 'fade'"
22881
+ [class.jv-tab-panel--slide-left]="effectiveTransition() === 'slide' && slideDirection() === 'left'"
22882
+ [class.jv-tab-panel--slide-right]="effectiveTransition() === 'slide' && slideDirection() === 'right'"
22883
+ [id]="panelId(tab.id)"
22884
+ role="tabpanel"
22885
+ [attr.aria-labelledby]="tabButtonId(tab.id)"
22886
+ [attr.tabindex]="isActive(tab.id) ? 0 : null"
22887
+ >
22888
+ <ng-container [ngTemplateOutlet]="panelTemplate(tab.id) ?? null" />
22889
+ </div>
22890
+ }
22891
+ } @else {
22892
+ @if (outgoingPanelId(); as outgoingId) {
22893
+ <div
22894
+ class="jv-tab-panel jv-tab-panel--exit"
22895
+ [class.jv-tab-panel--fade]="effectiveTransition() === 'fade'"
22896
+ [class.jv-tab-panel--slide-left]="effectiveTransition() === 'slide' && slideDirection() === 'left'"
22897
+ [class.jv-tab-panel--slide-right]="effectiveTransition() === 'slide' && slideDirection() === 'right'"
22898
+ [id]="panelId(outgoingId)"
22899
+ role="tabpanel"
22900
+ [attr.aria-labelledby]="tabButtonId(outgoingId)"
22901
+ tabindex="-1"
22902
+ (transitionend)="handleTransitionEnd($event)"
22903
+ >
22904
+ <ng-container [ngTemplateOutlet]="panelTemplate(outgoingId) ?? null" />
22905
+ </div>
22906
+ }
22907
+
22908
+ @if (resolvedActiveTabId(); as activeId) {
22909
+ <div
22910
+ class="jv-tab-panel jv-tab-panel--entering"
22911
+ [class.jv-tab-panel--fade]="effectiveTransition() === 'fade'"
22912
+ [class.jv-tab-panel--slide-left]="effectiveTransition() === 'slide' && slideDirection() === 'left'"
22913
+ [class.jv-tab-panel--slide-right]="effectiveTransition() === 'slide' && slideDirection() === 'right'"
22914
+ [id]="panelId(activeId)"
22915
+ role="tabpanel"
22916
+ [attr.aria-labelledby]="tabButtonId(activeId)"
22917
+ tabindex="0"
22918
+ >
22919
+ <ng-container [ngTemplateOutlet]="panelTemplate(activeId) ?? null" />
22920
+ </div>
22921
+ }
22922
+ }
22923
+ </div>
22924
+ </div>
22925
22925
  `, isInline: true, styles: [".jv-tabs{display:grid;gap:var(--jv-spacing-md)}.jv-tabs-header{display:flex;align-items:center;gap:var(--jv-spacing-sm);min-width:0}.jv-tabs-scroll{flex:1;min-width:0;display:flex;align-items:stretch;gap:var(--jv-spacing-xs);overflow-x:auto;overflow-y:hidden;scrollbar-width:none;-ms-overflow-style:none;border-bottom:1px solid var(--jv-color-border);scroll-behavior:smooth}.jv-tabs-scroll::-webkit-scrollbar{display:none}.jv-tab{position:relative;display:inline-flex;align-items:center;gap:var(--jv-spacing-xs);flex:0 0 auto;min-height:var(--jv-density-control-height);padding:0 var(--jv-spacing-md);border:0;border-bottom:2px solid transparent;background:transparent;color:var(--jv-color-foreground-muted);font:inherit;font-weight:600;white-space:nowrap;cursor:pointer;transition:color .16s ease,border-color .16s ease,background-color .16s ease}.jv-tab:hover:not(:disabled){color:var(--jv-color-foreground);background:color-mix(in srgb,var(--jv-color-primary) 6%,transparent)}.jv-tab:focus-visible{outline:2px solid var(--jv-color-primary);outline-offset:-2px;border-radius:var(--jv-radius-sm) var(--jv-radius-sm) 0 0}.jv-tab--active{color:var(--jv-color-primary);border-bottom-color:var(--jv-color-primary)}.jv-tab--disabled{opacity:.5;cursor:not-allowed}.jv-tabs-controls{display:inline-flex;align-items:center;gap:var(--jv-spacing-xs)}.jv-tabs-scroll-button{display:inline-flex;align-items:center;justify-content:center;width:2rem;height:2rem;border:1px solid var(--jv-color-border);border-radius:var(--jv-radius-md);background:var(--jv-color-surface);color:var(--jv-color-foreground);cursor:pointer}.jv-tabs-scroll-button:disabled{opacity:.4;cursor:default}.jv-tab-panel{outline:none}.jv-tab-panel--hidden{display:none}.jv-tab-panel--fade{transition:opacity .18s ease}.jv-tab-panel--hidden.jv-tab-panel--fade{display:block;opacity:0;pointer-events:none}.jv-tab-panel--slide-left,.jv-tab-panel--slide-right{transition:opacity .18s ease,transform .18s ease}.jv-tab-panel--hidden.jv-tab-panel--slide-left{display:block;opacity:0;transform:translate(20px);pointer-events:none}.jv-tab-panel--hidden.jv-tab-panel--slide-right{display:block;opacity:0;transform:translate(-20px);pointer-events:none}.jv-tab-panel--exit.jv-tab-panel--fade{position:absolute;opacity:0;pointer-events:none}.jv-tab-panel--exit.jv-tab-panel--slide-left{position:absolute;opacity:0;transform:translate(-20px);pointer-events:none}.jv-tab-panel--exit.jv-tab-panel--slide-right{position:absolute;opacity:0;transform:translate(20px);pointer-events:none}.jv-tab-panel--entering{animation:jv-tab-enter .18s ease forwards}.jv-tab-panel--entering.jv-tab-panel--slide-left{animation-name:jv-tab-slide-from-right}.jv-tab-panel--entering.jv-tab-panel--slide-right{animation-name:jv-tab-slide-from-left}@keyframes jv-tab-enter{0%{opacity:0}to{opacity:1}}@keyframes jv-tab-slide-from-right{0%{opacity:0;transform:translate(20px)}to{opacity:1;transform:translate(0)}}@keyframes jv-tab-slide-from-left{0%{opacity:0;transform:translate(-20px)}to{opacity:1;transform:translate(0)}}@media(prefers-reduced-motion:reduce){.jv-tab-panel--fade,.jv-tab-panel--slide-left,.jv-tab-panel--slide-right,.jv-tab-panel--entering{transition:none;animation:none}.jv-tab-panel--hidden.jv-tab-panel--fade,.jv-tab-panel--hidden.jv-tab-panel--slide-left,.jv-tab-panel--hidden.jv-tab-panel--slide-right{opacity:0}}\n"], dependencies: [{ kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: JvIconComponent, selector: "jv-icon", inputs: ["name", "size", "strokeWidth", "decorative", "ariaLabel"] }] });
22926
22926
  }
22927
22927
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: JvTabsComponent, decorators: [{
22928
22928
  type: Component,
22929
- args: [{ selector: 'jv-tabs', standalone: true, imports: [NgTemplateOutlet, JvIconComponent], template: `
22930
- <div class="jv-tabs">
22931
- <div class="jv-tabs-header">
22932
- <div class="jv-tabs-scroll" #tabList role="tablist" aria-orientation="horizontal" (scroll)="updateScrollControls()">
22933
- @for (tab of tabs(); track tab.id) {
22934
- <button
22935
- type="button"
22936
- class="jv-tab"
22937
- [class.jv-tab--active]="isActive(tab.id)"
22938
- [class.jv-tab--disabled]="tab.disabled ?? false"
22939
- [id]="tabButtonId(tab.id)"
22940
- [attr.data-jv-tab-id]="tab.id"
22941
- role="tab"
22942
- [attr.aria-selected]="isActive(tab.id) ? 'true' : 'false'"
22943
- [attr.aria-controls]="panelId(tab.id)"
22944
- [attr.tabindex]="getTabIndex(tab.id, tab.disabled ?? false)"
22945
- [disabled]="tab.disabled ?? false"
22946
- (click)="activateTab(tab.id)"
22947
- (keydown)="handleKeydown($event, tab.id)"
22948
- >
22949
- @if (tab.icon) {
22950
- <jv-icon [name]="tab.icon" [size]="16" />
22951
- }
22952
- <span>{{ tab.label }}</span>
22953
- </button>
22954
- }
22955
- </div>
22956
-
22957
- @if (showScrollLeft() || showScrollRight()) {
22958
- <div class="jv-tabs-controls" aria-hidden="true">
22959
- <button type="button" class="jv-tabs-scroll-button" [disabled]="!showScrollLeft()" (click)="scrollTabs(-1)">
22960
- <jv-icon name="chevron-left" [size]="16" />
22961
- </button>
22962
- <button type="button" class="jv-tabs-scroll-button" [disabled]="!showScrollRight()" (click)="scrollTabs(1)">
22963
- <jv-icon name="chevron-right" [size]="16" />
22964
- </button>
22965
- </div>
22966
- }
22967
- </div>
22968
-
22969
- <div class="jv-tabs-panels">
22970
- @if (keepMounted()) {
22971
- @for (tab of tabs(); track tab.id) {
22972
- <div
22973
- class="jv-tab-panel"
22974
- [class.jv-tab-panel--hidden]="!isActive(tab.id)"
22975
- [class.jv-tab-panel--fade]="effectiveTransition() === 'fade'"
22976
- [class.jv-tab-panel--slide-left]="effectiveTransition() === 'slide' && slideDirection() === 'left'"
22977
- [class.jv-tab-panel--slide-right]="effectiveTransition() === 'slide' && slideDirection() === 'right'"
22978
- [id]="panelId(tab.id)"
22979
- role="tabpanel"
22980
- [attr.aria-labelledby]="tabButtonId(tab.id)"
22981
- [attr.tabindex]="isActive(tab.id) ? 0 : null"
22982
- >
22983
- <ng-container [ngTemplateOutlet]="panelTemplate(tab.id) ?? null" />
22984
- </div>
22985
- }
22986
- } @else {
22987
- @if (outgoingPanelId(); as outgoingId) {
22988
- <div
22989
- class="jv-tab-panel jv-tab-panel--exit"
22990
- [class.jv-tab-panel--fade]="effectiveTransition() === 'fade'"
22991
- [class.jv-tab-panel--slide-left]="effectiveTransition() === 'slide' && slideDirection() === 'left'"
22992
- [class.jv-tab-panel--slide-right]="effectiveTransition() === 'slide' && slideDirection() === 'right'"
22993
- [id]="panelId(outgoingId)"
22994
- role="tabpanel"
22995
- [attr.aria-labelledby]="tabButtonId(outgoingId)"
22996
- tabindex="-1"
22997
- (transitionend)="handleTransitionEnd($event)"
22998
- >
22999
- <ng-container [ngTemplateOutlet]="panelTemplate(outgoingId) ?? null" />
23000
- </div>
23001
- }
23002
-
23003
- @if (resolvedActiveTabId(); as activeId) {
23004
- <div
23005
- class="jv-tab-panel jv-tab-panel--entering"
23006
- [class.jv-tab-panel--fade]="effectiveTransition() === 'fade'"
23007
- [class.jv-tab-panel--slide-left]="effectiveTransition() === 'slide' && slideDirection() === 'left'"
23008
- [class.jv-tab-panel--slide-right]="effectiveTransition() === 'slide' && slideDirection() === 'right'"
23009
- [id]="panelId(activeId)"
23010
- role="tabpanel"
23011
- [attr.aria-labelledby]="tabButtonId(activeId)"
23012
- tabindex="0"
23013
- >
23014
- <ng-container [ngTemplateOutlet]="panelTemplate(activeId) ?? null" />
23015
- </div>
23016
- }
23017
- }
23018
- </div>
23019
- </div>
22929
+ args: [{ selector: 'jv-tabs', standalone: true, imports: [NgTemplateOutlet, JvIconComponent], template: `
22930
+ <div class="jv-tabs">
22931
+ <div class="jv-tabs-header">
22932
+ <div class="jv-tabs-scroll" #tabList role="tablist" aria-orientation="horizontal" (scroll)="updateScrollControls()">
22933
+ @for (tab of tabs(); track tab.id) {
22934
+ <button
22935
+ type="button"
22936
+ class="jv-tab"
22937
+ [class.jv-tab--active]="isActive(tab.id)"
22938
+ [class.jv-tab--disabled]="tab.disabled ?? false"
22939
+ [id]="tabButtonId(tab.id)"
22940
+ [attr.data-jv-tab-id]="tab.id"
22941
+ role="tab"
22942
+ [attr.aria-selected]="isActive(tab.id) ? 'true' : 'false'"
22943
+ [attr.aria-controls]="panelId(tab.id)"
22944
+ [attr.tabindex]="getTabIndex(tab.id, tab.disabled ?? false)"
22945
+ [disabled]="tab.disabled ?? false"
22946
+ (click)="activateTab(tab.id)"
22947
+ (keydown)="handleKeydown($event, tab.id)"
22948
+ >
22949
+ @if (tab.icon) {
22950
+ <jv-icon [name]="tab.icon" [size]="16" />
22951
+ }
22952
+ <span>{{ tab.label }}</span>
22953
+ </button>
22954
+ }
22955
+ </div>
22956
+
22957
+ @if (showScrollLeft() || showScrollRight()) {
22958
+ <div class="jv-tabs-controls" aria-hidden="true">
22959
+ <button type="button" class="jv-tabs-scroll-button" [disabled]="!showScrollLeft()" (click)="scrollTabs(-1)">
22960
+ <jv-icon name="chevron-left" [size]="16" />
22961
+ </button>
22962
+ <button type="button" class="jv-tabs-scroll-button" [disabled]="!showScrollRight()" (click)="scrollTabs(1)">
22963
+ <jv-icon name="chevron-right" [size]="16" />
22964
+ </button>
22965
+ </div>
22966
+ }
22967
+ </div>
22968
+
22969
+ <div class="jv-tabs-panels">
22970
+ @if (keepMounted()) {
22971
+ @for (tab of tabs(); track tab.id) {
22972
+ <div
22973
+ class="jv-tab-panel"
22974
+ [class.jv-tab-panel--hidden]="!isActive(tab.id)"
22975
+ [class.jv-tab-panel--fade]="effectiveTransition() === 'fade'"
22976
+ [class.jv-tab-panel--slide-left]="effectiveTransition() === 'slide' && slideDirection() === 'left'"
22977
+ [class.jv-tab-panel--slide-right]="effectiveTransition() === 'slide' && slideDirection() === 'right'"
22978
+ [id]="panelId(tab.id)"
22979
+ role="tabpanel"
22980
+ [attr.aria-labelledby]="tabButtonId(tab.id)"
22981
+ [attr.tabindex]="isActive(tab.id) ? 0 : null"
22982
+ >
22983
+ <ng-container [ngTemplateOutlet]="panelTemplate(tab.id) ?? null" />
22984
+ </div>
22985
+ }
22986
+ } @else {
22987
+ @if (outgoingPanelId(); as outgoingId) {
22988
+ <div
22989
+ class="jv-tab-panel jv-tab-panel--exit"
22990
+ [class.jv-tab-panel--fade]="effectiveTransition() === 'fade'"
22991
+ [class.jv-tab-panel--slide-left]="effectiveTransition() === 'slide' && slideDirection() === 'left'"
22992
+ [class.jv-tab-panel--slide-right]="effectiveTransition() === 'slide' && slideDirection() === 'right'"
22993
+ [id]="panelId(outgoingId)"
22994
+ role="tabpanel"
22995
+ [attr.aria-labelledby]="tabButtonId(outgoingId)"
22996
+ tabindex="-1"
22997
+ (transitionend)="handleTransitionEnd($event)"
22998
+ >
22999
+ <ng-container [ngTemplateOutlet]="panelTemplate(outgoingId) ?? null" />
23000
+ </div>
23001
+ }
23002
+
23003
+ @if (resolvedActiveTabId(); as activeId) {
23004
+ <div
23005
+ class="jv-tab-panel jv-tab-panel--entering"
23006
+ [class.jv-tab-panel--fade]="effectiveTransition() === 'fade'"
23007
+ [class.jv-tab-panel--slide-left]="effectiveTransition() === 'slide' && slideDirection() === 'left'"
23008
+ [class.jv-tab-panel--slide-right]="effectiveTransition() === 'slide' && slideDirection() === 'right'"
23009
+ [id]="panelId(activeId)"
23010
+ role="tabpanel"
23011
+ [attr.aria-labelledby]="tabButtonId(activeId)"
23012
+ tabindex="0"
23013
+ >
23014
+ <ng-container [ngTemplateOutlet]="panelTemplate(activeId) ?? null" />
23015
+ </div>
23016
+ }
23017
+ }
23018
+ </div>
23019
+ </div>
23020
23020
  `, styles: [".jv-tabs{display:grid;gap:var(--jv-spacing-md)}.jv-tabs-header{display:flex;align-items:center;gap:var(--jv-spacing-sm);min-width:0}.jv-tabs-scroll{flex:1;min-width:0;display:flex;align-items:stretch;gap:var(--jv-spacing-xs);overflow-x:auto;overflow-y:hidden;scrollbar-width:none;-ms-overflow-style:none;border-bottom:1px solid var(--jv-color-border);scroll-behavior:smooth}.jv-tabs-scroll::-webkit-scrollbar{display:none}.jv-tab{position:relative;display:inline-flex;align-items:center;gap:var(--jv-spacing-xs);flex:0 0 auto;min-height:var(--jv-density-control-height);padding:0 var(--jv-spacing-md);border:0;border-bottom:2px solid transparent;background:transparent;color:var(--jv-color-foreground-muted);font:inherit;font-weight:600;white-space:nowrap;cursor:pointer;transition:color .16s ease,border-color .16s ease,background-color .16s ease}.jv-tab:hover:not(:disabled){color:var(--jv-color-foreground);background:color-mix(in srgb,var(--jv-color-primary) 6%,transparent)}.jv-tab:focus-visible{outline:2px solid var(--jv-color-primary);outline-offset:-2px;border-radius:var(--jv-radius-sm) var(--jv-radius-sm) 0 0}.jv-tab--active{color:var(--jv-color-primary);border-bottom-color:var(--jv-color-primary)}.jv-tab--disabled{opacity:.5;cursor:not-allowed}.jv-tabs-controls{display:inline-flex;align-items:center;gap:var(--jv-spacing-xs)}.jv-tabs-scroll-button{display:inline-flex;align-items:center;justify-content:center;width:2rem;height:2rem;border:1px solid var(--jv-color-border);border-radius:var(--jv-radius-md);background:var(--jv-color-surface);color:var(--jv-color-foreground);cursor:pointer}.jv-tabs-scroll-button:disabled{opacity:.4;cursor:default}.jv-tab-panel{outline:none}.jv-tab-panel--hidden{display:none}.jv-tab-panel--fade{transition:opacity .18s ease}.jv-tab-panel--hidden.jv-tab-panel--fade{display:block;opacity:0;pointer-events:none}.jv-tab-panel--slide-left,.jv-tab-panel--slide-right{transition:opacity .18s ease,transform .18s ease}.jv-tab-panel--hidden.jv-tab-panel--slide-left{display:block;opacity:0;transform:translate(20px);pointer-events:none}.jv-tab-panel--hidden.jv-tab-panel--slide-right{display:block;opacity:0;transform:translate(-20px);pointer-events:none}.jv-tab-panel--exit.jv-tab-panel--fade{position:absolute;opacity:0;pointer-events:none}.jv-tab-panel--exit.jv-tab-panel--slide-left{position:absolute;opacity:0;transform:translate(-20px);pointer-events:none}.jv-tab-panel--exit.jv-tab-panel--slide-right{position:absolute;opacity:0;transform:translate(20px);pointer-events:none}.jv-tab-panel--entering{animation:jv-tab-enter .18s ease forwards}.jv-tab-panel--entering.jv-tab-panel--slide-left{animation-name:jv-tab-slide-from-right}.jv-tab-panel--entering.jv-tab-panel--slide-right{animation-name:jv-tab-slide-from-left}@keyframes jv-tab-enter{0%{opacity:0}to{opacity:1}}@keyframes jv-tab-slide-from-right{0%{opacity:0;transform:translate(20px)}to{opacity:1;transform:translate(0)}}@keyframes jv-tab-slide-from-left{0%{opacity:0;transform:translate(-20px)}to{opacity:1;transform:translate(0)}}@media(prefers-reduced-motion:reduce){.jv-tab-panel--fade,.jv-tab-panel--slide-left,.jv-tab-panel--slide-right,.jv-tab-panel--entering{transition:none;animation:none}.jv-tab-panel--hidden.jv-tab-panel--fade,.jv-tab-panel--hidden.jv-tab-panel--slide-left,.jv-tab-panel--hidden.jv-tab-panel--slide-right{opacity:0}}\n"] }]
23021
23021
  }], ctorParameters: () => [], propDecorators: { panelTemplates: [{ type: i0.ContentChildren, args: [i0.forwardRef(() => JvTabPanelDirective), { isSignal: true }] }], tabListRef: [{ type: i0.ViewChild, args: ['tabList', { isSignal: true }] }], tabs: [{ type: i0.Input, args: [{ isSignal: true, alias: "tabs", required: false }] }], activeTabId: [{ type: i0.Input, args: [{ isSignal: true, alias: "activeTabId", required: false }] }], keepMounted: [{ type: i0.Input, args: [{ isSignal: true, alias: "keepMounted", required: false }] }], transition: [{ type: i0.Input, args: [{ isSignal: true, alias: "transition", required: false }] }], activeTabChange: [{ type: i0.Output, args: ["activeTabChange"] }], handleWindowResize: [{
23022
23022
  type: HostListener,
@@ -23212,10 +23212,286 @@ const JV_GRID_DEFAULT_OPTIONS = {
23212
23212
  virtualScroll: false,
23213
23213
  virtualScrollRowHeight: 48,
23214
23214
  exportable: false,
23215
+ exportFileName: '',
23215
23216
  columnFilters: false,
23216
23217
  rowDoubleClick: false,
23217
23218
  };
23218
23219
 
23220
+ const MAIN_NS = 'http://schemas.openxmlformats.org/spreadsheetml/2006/main';
23221
+ const REL_NS = 'http://schemas.openxmlformats.org/officeDocument/2006/relationships';
23222
+ const PKG_REL_NS = 'http://schemas.openxmlformats.org/package/2006/relationships';
23223
+ const CONTENT_TYPES_NS = 'http://schemas.openxmlformats.org/package/2006/content-types';
23224
+ const XML_DECL = '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>';
23225
+ /**
23226
+ * Builds a real Office Open XML (.xlsx) spreadsheet as a Blob.
23227
+ *
23228
+ * The package is assembled in memory with the ZIP "store" method (no
23229
+ * compression) so no third-party dependency is required. All cell text is
23230
+ * written as `inlineStr`, which keeps every character (including UTF-8
23231
+ * accented glyphs) intact and is understood by Microsoft Excel and Apple
23232
+ * Numbers alike.
23233
+ */
23234
+ function buildXlsxBlob(sheets) {
23235
+ return new Blob([buildXlsxBuffer(sheets)], {
23236
+ type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
23237
+ });
23238
+ }
23239
+ /**
23240
+ * Returns the raw bytes of a minimal valid .xlsx package. Exposed separately
23241
+ * from the Blob wrapper to make the package easy to assert on in tests.
23242
+ */
23243
+ function buildXlsxBuffer(sheets) {
23244
+ const parts = [];
23245
+ const sheetXml = sheets.map((sheet, index) => buildSheetXml(sheet, index));
23246
+ const sheetNames = sheets.length > 0 ? sheets.map((s) => s.name) : ['Sheet1'];
23247
+ parts.push({ name: '[Content_Types].xml', data: utf8(buildContentTypes(sheets.length)) });
23248
+ parts.push({ name: '_rels/.rels', data: utf8(buildRootRels()) });
23249
+ parts.push({ name: 'xl/workbook.xml', data: utf8(buildWorkbook(sheetNames)) });
23250
+ parts.push({ name: 'xl/_rels/workbook.xml.rels', data: utf8(buildWorkbookRels(sheets.length)) });
23251
+ parts.push({ name: 'xl/styles.xml', data: utf8(buildStyles()) });
23252
+ for (let i = 0; i < sheetXml.length; i++) {
23253
+ parts.push({ name: `xl/worksheets/sheet${i + 1}.xml`, data: utf8(sheetXml[i]) });
23254
+ }
23255
+ return toZip(parts);
23256
+ }
23257
+ function buildContentTypes(sheetCount) {
23258
+ let xml = XML_DECL;
23259
+ xml += `<Types xmlns="${CONTENT_TYPES_NS}">`;
23260
+ xml += `<Default Extension="rels" ContentType="application/vnd.openxmlformats-package.relationships+xml"/>`;
23261
+ xml += `<Default Extension="xml" ContentType="application/xml"/>`;
23262
+ xml += `<Override PartName="/xl/workbook.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml"/>`;
23263
+ for (let i = 0; i < sheetCount; i++) {
23264
+ xml += `<Override PartName="/xl/worksheets/sheet${i + 1}.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml"/>`;
23265
+ }
23266
+ xml += `<Override PartName="/xl/styles.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.styles+xml"/>`;
23267
+ xml += `</Types>`;
23268
+ return xml;
23269
+ }
23270
+ function buildRootRels() {
23271
+ return XML_DECL +
23272
+ `<Relationships xmlns="${PKG_REL_NS}">` +
23273
+ `<Relationship Id="rId1" Type="${REL_NS}/officeDocument" Target="xl/workbook.xml"/>` +
23274
+ `</Relationships>`;
23275
+ }
23276
+ function buildWorkbook(sheetNames) {
23277
+ let xml = XML_DECL;
23278
+ xml += `<workbook xmlns="${MAIN_NS}" xmlns:r="${REL_NS}">`;
23279
+ xml += `<sheets>`;
23280
+ for (let i = 0; i < sheetNames.length; i++) {
23281
+ const name = sanitizeSheetName(sheetNames[i]) || `Sheet${i + 1}`;
23282
+ xml += `<sheet name="${escapeXml(name)}" sheetId="${i + 1}" r:id="rId${i + 1}"/>`;
23283
+ }
23284
+ xml += `</sheets></workbook>`;
23285
+ return xml;
23286
+ }
23287
+ function buildWorkbookRels(sheetCount) {
23288
+ let xml = XML_DECL;
23289
+ xml += `<Relationships xmlns="${PKG_REL_NS}">`;
23290
+ for (let i = 0; i < sheetCount; i++) {
23291
+ xml += `<Relationship Id="rId${i + 1}" Type="${REL_NS}/worksheet" Target="worksheets/sheet${i + 1}.xml"/>`;
23292
+ }
23293
+ xml += `<Relationship Id="rId${sheetCount + 1}" Type="${REL_NS}/styles" Target="styles.xml"/>`;
23294
+ xml += `</Relationships>`;
23295
+ return xml;
23296
+ }
23297
+ function buildSheetXml(sheet, index) {
23298
+ const rows = sheet.rows;
23299
+ const rowCount = rows.length;
23300
+ const colCount = rows.reduce((max, row) => Math.max(max, row.length), 0);
23301
+ const dimension = rowCount === 0 || colCount === 0 ? 'A1' : `A1:${columnName(colCount - 1)}${rowCount}`;
23302
+ let xml = XML_DECL;
23303
+ xml += `<worksheet xmlns="${MAIN_NS}">`;
23304
+ xml += `<dimension ref="${dimension}"/>`;
23305
+ xml += `<sheetViews><sheetView workbookViewId="0"/></sheetViews>`;
23306
+ xml += `<sheetFormatPr defaultRowHeight="15"/>`;
23307
+ xml += `<sheetData>`;
23308
+ for (let r = 0; r < rowCount; r++) {
23309
+ const row = rows[r];
23310
+ xml += `<row r="${r + 1}">`;
23311
+ for (let c = 0; c < row.length; c++) {
23312
+ xml += `<c r="${columnName(c)}${r + 1}" t="inlineStr"><is><t xml:space="preserve">${escapeXml(String(row[c]))}</t></is></c>`;
23313
+ }
23314
+ xml += `</row>`;
23315
+ }
23316
+ xml += `</sheetData></worksheet>`;
23317
+ void index;
23318
+ return xml;
23319
+ }
23320
+ function buildStyles() {
23321
+ return XML_DECL +
23322
+ `<styleSheet xmlns="${MAIN_NS}">` +
23323
+ `<fonts count="1"><font><sz val="11"/><name val="Calibri"/></font></fonts>` +
23324
+ `<fills count="1"><fill><patternFill patternType="none"/></fill></fills>` +
23325
+ `<borders count="1"><border><left/><right/><top/><bottom/><diagonal/></border></borders>` +
23326
+ `<cellStyleXfs count="1"><xf numFmtId="0" fontId="0" fillId="0" borderId="0"/></cellStyleXfs>` +
23327
+ `<cellXfs count="1"><xf numFmtId="0" fontId="0" fillId="0" borderId="0" xfId="0"/></cellXfs>` +
23328
+ `<cellStyles count="1"><cellStyle name="Normal" xfId="0" builtinId="0"/></cellStyles>` +
23329
+ `</styleSheet>`;
23330
+ }
23331
+ function columnName(index) {
23332
+ let value = '';
23333
+ let n = index;
23334
+ while (n >= 0) {
23335
+ value = String.fromCharCode((n % 26) + 65) + value;
23336
+ n = Math.floor(n / 26) - 1;
23337
+ }
23338
+ return value;
23339
+ }
23340
+ function sanitizeSheetName(name) {
23341
+ return name.replace(/[\[\]:*?/\\]/g, '').slice(0, 31);
23342
+ }
23343
+ function escapeXml(value) {
23344
+ return value
23345
+ .replace(/&/g, '&amp;')
23346
+ .replace(/</g, '&lt;')
23347
+ .replace(/>/g, '&gt;')
23348
+ .replace(/"/g, '&quot;')
23349
+ .replace(/'/g, '&apos;');
23350
+ }
23351
+ function utf8(value) {
23352
+ return new TextEncoder().encode(value);
23353
+ }
23354
+ // ---------------------------------------------------------------------------
23355
+ // Minimal ZIP writer ("store" method, no compression)
23356
+ // ---------------------------------------------------------------------------
23357
+ const CRC_TABLE = (() => {
23358
+ const table = new Array(256);
23359
+ for (let n = 0; n < 256; n++) {
23360
+ let c = n;
23361
+ for (let k = 0; k < 8; k++) {
23362
+ c = c & 1 ? 0xedb88320 ^ (c >>> 1) : c >>> 1;
23363
+ }
23364
+ table[n] = c >>> 0;
23365
+ }
23366
+ return table;
23367
+ })();
23368
+ function crc32(bytes) {
23369
+ let crc = 0xffffffff;
23370
+ for (let i = 0; i < bytes.length; i++) {
23371
+ crc = CRC_TABLE[(crc ^ bytes[i]) & 0xff] ^ (crc >>> 8);
23372
+ }
23373
+ return (crc ^ 0xffffffff) >>> 0;
23374
+ }
23375
+ function toZip(files) {
23376
+ const encoder = new TextEncoder();
23377
+ const chunks = [];
23378
+ const centralDirectory = new Uint8Array(0);
23379
+ const centralRecords = [];
23380
+ let offset = 0;
23381
+ for (const file of files) {
23382
+ const nameBytes = encoder.encode(file.name);
23383
+ const crc = crc32(file.data);
23384
+ // Local file header
23385
+ const local = new DataView(new ArrayBuffer(30));
23386
+ local.setUint32(0, 0x04034b50, true);
23387
+ local.setUint16(4, 20, true);
23388
+ local.setUint16(6, 0x0800, true);
23389
+ local.setUint16(8, 0, true); // store (no compression)
23390
+ local.setUint16(10, 0, true);
23391
+ local.setUint16(12, 0, true);
23392
+ local.setUint32(14, crc, true);
23393
+ local.setUint32(18, file.data.length, true);
23394
+ local.setUint32(22, file.data.length, true);
23395
+ local.setUint16(26, nameBytes.length, true);
23396
+ local.setUint16(28, 0, true);
23397
+ chunks.push(new Uint8Array(local.buffer), nameBytes, file.data);
23398
+ // Central directory header
23399
+ const central = new DataView(new ArrayBuffer(46));
23400
+ central.setUint32(0, 0x02014b50, true);
23401
+ central.setUint16(4, 20, true); // version made by
23402
+ central.setUint16(6, 20, true); // version needed
23403
+ central.setUint16(8, 0x0800, true); // UTF-8 filename flag
23404
+ central.setUint16(10, 0, true); // store (no compression)
23405
+ central.setUint16(12, 0, true); // mod time
23406
+ central.setUint16(14, 0, true); // mod date
23407
+ central.setUint32(16, crc, true);
23408
+ central.setUint32(20, file.data.length, true); // compressed size
23409
+ central.setUint32(24, file.data.length, true); // uncompressed size
23410
+ central.setUint16(28, nameBytes.length, true);
23411
+ central.setUint16(30, 0, true); // extra field length
23412
+ central.setUint16(32, 0, true); // comment length
23413
+ central.setUint16(34, 0, true); // disk number start
23414
+ central.setUint16(36, 0, true); // internal file attributes
23415
+ central.setUint32(38, 0, true); // external file attributes
23416
+ central.setUint32(42, offset, true); // local header offset
23417
+ centralRecords.push(new Uint8Array(central.buffer), nameBytes);
23418
+ offset += 30 + nameBytes.length + file.data.length;
23419
+ }
23420
+ const centralSize = centralRecords.reduce((sum, chunk) => sum + chunk.length, 0);
23421
+ const eocd = new DataView(new ArrayBuffer(22));
23422
+ eocd.setUint32(0, 0x06054b50, true);
23423
+ eocd.setUint16(4, 0, true);
23424
+ eocd.setUint16(6, 0, true);
23425
+ eocd.setUint16(8, files.length, true);
23426
+ eocd.setUint16(10, files.length, true);
23427
+ eocd.setUint32(12, centralSize, true);
23428
+ eocd.setUint32(16, offset, true);
23429
+ eocd.setUint16(20, 0, true);
23430
+ const result = new Uint8Array(offset + centralSize + 22);
23431
+ let pointer = 0;
23432
+ for (const chunk of chunks) {
23433
+ result.set(chunk, pointer);
23434
+ pointer += chunk.length;
23435
+ }
23436
+ for (const chunk of centralRecords) {
23437
+ result.set(chunk, pointer);
23438
+ pointer += chunk.length;
23439
+ }
23440
+ result.set(new Uint8Array(eocd.buffer), pointer);
23441
+ void centralDirectory;
23442
+ return result;
23443
+ }
23444
+
23445
+ /**
23446
+ * Triggers a browser download for the given Blob.
23447
+ */
23448
+ function downloadBlob(blob, filename) {
23449
+ const url = URL.createObjectURL(blob);
23450
+ const a = document.createElement('a');
23451
+ a.href = url;
23452
+ a.download = filename;
23453
+ a.click();
23454
+ URL.revokeObjectURL(url);
23455
+ }
23456
+ const CSV_BOM = '\uFEFF';
23457
+ /**
23458
+ * Builds the raw CSV text (including the UTF-8 BOM) for the given header and
23459
+ * rows. Exposed separately from the downloader to keep it easy to test.
23460
+ */
23461
+ function buildCsv(header, rows) {
23462
+ const escapeCell = (value) => {
23463
+ const text = value == null ? '' : String(value);
23464
+ return `"${text.replace(/"/g, '""')}"`;
23465
+ };
23466
+ const lines = [];
23467
+ lines.push(header.map(escapeCell).join(','));
23468
+ for (const row of rows) {
23469
+ lines.push(row.map(escapeCell).join(','));
23470
+ }
23471
+ return CSV_BOM + lines.join('\r\n');
23472
+ }
23473
+ /**
23474
+ * Serializes a grid/table to CSV and downloads it.
23475
+ *
23476
+ * A UTF-8 BOM is prepended so Microsoft Excel recognises the file as UTF-8
23477
+ * and renders accented characters correctly.
23478
+ */
23479
+ function exportGridToCsv(header, rows, filename = 'export.csv') {
23480
+ const csv = buildCsv(header, rows);
23481
+ const blob = new Blob([csv], { type: 'text/csv;charset=utf-8;' });
23482
+ downloadBlob(blob, filename);
23483
+ }
23484
+ /**
23485
+ * Serializes a grid/table to a real Open XML (.xlsx) spreadsheet and downloads it.
23486
+ *
23487
+ * Generates an actual xlsx package (not an HTML file with an .xls extension),
23488
+ * preserving UTF-8 characters. Comma-separated columns are exported as-is.
23489
+ */
23490
+ function exportGridToExcel(header, rows, filename = 'export.xlsx') {
23491
+ const blob = buildXlsxBlob([{ name: 'Sheet1', rows: [header, ...rows] }]);
23492
+ downloadBlob(blob, filename);
23493
+ }
23494
+
23219
23495
  let gridIdSequence = 0;
23220
23496
  class JvGridComponent {
23221
23497
  gridId = `jv-grid-${++gridIdSequence}`;
@@ -23727,41 +24003,31 @@ class JvGridComponent {
23727
24003
  }
23728
24004
  // Export
23729
24005
  exportCsv() {
23730
- const cols = this.visibleColumns().map(c => ({ key: this.colKey(c), header: c.header, column: c }));
23731
- const allData = this.effectiveOptions().serverSide ? this.data() : this.sortedData();
23732
- const headerRow = cols.map(c => `"${c.header}"`).join(',');
23733
- const dataRows = allData.map(row => cols.map(col => {
23734
- const val = row[col.key];
23735
- if (val == null)
23736
- return '';
23737
- return `"${this.getExportValue(val, col.column).replace(/"/g, '""')}"`;
23738
- }).join(','));
23739
- const csv = [headerRow, ...dataRows].join('\r\n');
23740
- const blob = new Blob([csv], { type: 'text/csv;charset=utf-8;' });
23741
- const url = URL.createObjectURL(blob);
23742
- const a = document.createElement('a');
23743
- a.href = url;
23744
- a.download = 'grid-export.csv';
23745
- a.click();
23746
- URL.revokeObjectURL(url);
24006
+ const { header, rows } = this.buildExportMatrix();
24007
+ exportGridToCsv(header, rows, this.resolveExportFileName('csv'));
23747
24008
  }
23748
24009
  exportExcel() {
24010
+ const { header, rows } = this.buildExportMatrix();
24011
+ exportGridToExcel(header, rows, this.resolveExportFileName('xlsx'));
24012
+ }
24013
+ resolveExportFileName(extension) {
24014
+ const configuredName = this.effectiveOptions().exportFileName?.trim();
24015
+ const fallbackName = `grid-export.${extension}`;
24016
+ if (!configuredName) {
24017
+ return fallbackName;
24018
+ }
24019
+ if (configuredName.toLowerCase().endsWith(`.${extension}`)) {
24020
+ return configuredName;
24021
+ }
24022
+ return configuredName.replace(/\.(csv|xlsx)$/i, '') + `.${extension}`;
24023
+ }
24024
+ buildExportMatrix() {
23749
24025
  const cols = this.visibleColumns().map(c => ({ key: this.colKey(c), header: c.header, column: c }));
23750
24026
  const allData = this.effectiveOptions().serverSide ? this.data() : this.sortedData();
23751
- let html = '<table>';
23752
- html += '<thead><tr>' + cols.map(c => `<th>${c.header}</th>`).join('') + '</tr></thead>';
23753
- html += '<tbody>';
23754
- for (const row of allData) {
23755
- html += '<tr>' + cols.map(col => `<td>${this.getExportValue(row[col.key], col.column)}</td>`).join('') + '</tr>';
23756
- }
23757
- html += '</tbody></table>';
23758
- const blob = new Blob([html], { type: 'application/vnd.ms-excel' });
23759
- const url = URL.createObjectURL(blob);
23760
- const a = document.createElement('a');
23761
- a.href = url;
23762
- a.download = 'grid-export.xls';
23763
- a.click();
23764
- URL.revokeObjectURL(url);
24027
+ return {
24028
+ header: cols.map(c => c.header),
24029
+ rows: allData.map(row => cols.map(col => this.getExportValue(row[col.key], col.column))),
24030
+ };
23765
24031
  }
23766
24032
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: JvGridComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
23767
24033
  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: `