@m1z23r/ngx-ui 1.1.57 → 1.1.60

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,8 +1,9 @@
1
1
  import * as i0 from '@angular/core';
2
- import { inject, PLATFORM_ID, signal, Injectable, computed, InjectionToken, ApplicationRef, EnvironmentInjector, createComponent, Injector, input, output, ChangeDetectionStrategy, Component, ElementRef, HostListener, effect, Directive, model, Pipe, contentChildren, TemplateRef, contentChild, ViewChild, viewChild, afterRenderEffect, DestroyRef } from '@angular/core';
2
+ import { inject, PLATFORM_ID, signal, Injectable, computed, InjectionToken, ApplicationRef, EnvironmentInjector, createComponent, Injector, input, model, ChangeDetectionStrategy, Component, viewChild, afterNextRender, HostListener, output, ElementRef, effect, Directive, Pipe, contentChildren, TemplateRef, contentChild, ViewChild, afterRenderEffect, DestroyRef } from '@angular/core';
3
3
  import { isPlatformBrowser, DOCUMENT, NgTemplateOutlet, NgComponentOutlet, DecimalPipe } from '@angular/common';
4
4
  import * as i1 from '@angular/forms';
5
5
  import { FormsModule } from '@angular/forms';
6
+ import { RouterLink } from '@angular/router';
6
7
 
7
8
  /**
8
9
  * Collection of built-in validators for form inputs
@@ -426,6 +427,207 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.1", ngImpor
426
427
  args: [{ providedIn: 'root' }]
427
428
  }] });
428
429
 
430
+ class CarouselComponent {
431
+ images = input.required(...(ngDevMode ? [{ debugName: "images" }] : []));
432
+ startIndex = input(0, ...(ngDevMode ? [{ debugName: "startIndex" }] : []));
433
+ loop = input(true, ...(ngDevMode ? [{ debugName: "loop" }] : []));
434
+ showCounter = input(true, ...(ngDevMode ? [{ debugName: "showCounter" }] : []));
435
+ ariaLabel = input('Image carousel', ...(ngDevMode ? [{ debugName: "ariaLabel" }] : []));
436
+ index = model(...(ngDevMode ? [undefined, { debugName: "index" }] : []));
437
+ /** Horizontal drag offset in px while a swipe is in progress */
438
+ dragOffset = signal(0, ...(ngDevMode ? [{ debugName: "dragOffset" }] : []));
439
+ dragging = signal(false, ...(ngDevMode ? [{ debugName: "dragging" }] : []));
440
+ pointerId = null;
441
+ dragStartX = 0;
442
+ dragStartY = 0;
443
+ dragAxis = null;
444
+ currentIndex = computed(() => {
445
+ const count = this.images().length;
446
+ if (count === 0)
447
+ return 0;
448
+ const index = this.index() ?? this.startIndex();
449
+ return Math.max(0, Math.min(count - 1, index));
450
+ }, ...(ngDevMode ? [{ debugName: "currentIndex" }] : []));
451
+ hasPrev = computed(() => {
452
+ return this.loop() ? this.images().length > 1 : this.currentIndex() > 0;
453
+ }, ...(ngDevMode ? [{ debugName: "hasPrev" }] : []));
454
+ hasNext = computed(() => {
455
+ return this.loop()
456
+ ? this.images().length > 1
457
+ : this.currentIndex() < this.images().length - 1;
458
+ }, ...(ngDevMode ? [{ debugName: "hasNext" }] : []));
459
+ prev() {
460
+ if (!this.hasPrev())
461
+ return;
462
+ const count = this.images().length;
463
+ this.index.set((this.currentIndex() - 1 + count) % count);
464
+ }
465
+ next() {
466
+ if (!this.hasNext())
467
+ return;
468
+ const count = this.images().length;
469
+ this.index.set((this.currentIndex() + 1) % count);
470
+ }
471
+ handleKeyDown(event) {
472
+ switch (event.key) {
473
+ case 'ArrowLeft':
474
+ event.preventDefault();
475
+ this.prev();
476
+ break;
477
+ case 'ArrowRight':
478
+ event.preventDefault();
479
+ this.next();
480
+ break;
481
+ case 'Home':
482
+ event.preventDefault();
483
+ this.index.set(0);
484
+ break;
485
+ case 'End':
486
+ event.preventDefault();
487
+ this.index.set(this.images().length - 1);
488
+ break;
489
+ }
490
+ }
491
+ onPointerDown(event) {
492
+ if (this.images().length < 2)
493
+ return;
494
+ this.pointerId = event.pointerId;
495
+ this.dragStartX = event.clientX;
496
+ this.dragStartY = event.clientY;
497
+ this.dragAxis = null;
498
+ this.dragging.set(true);
499
+ }
500
+ onPointerMove(event) {
501
+ if (this.pointerId !== event.pointerId)
502
+ return;
503
+ const deltaX = event.clientX - this.dragStartX;
504
+ const deltaY = event.clientY - this.dragStartY;
505
+ // Lock the gesture axis once movement is unambiguous, so vertical
506
+ // page scrolling on touch devices is not hijacked
507
+ if (this.dragAxis === null && (Math.abs(deltaX) > 8 || Math.abs(deltaY) > 8)) {
508
+ this.dragAxis = Math.abs(deltaX) > Math.abs(deltaY) ? 'x' : 'y';
509
+ if (this.dragAxis === 'x') {
510
+ event.target.setPointerCapture?.(event.pointerId);
511
+ }
512
+ }
513
+ if (this.dragAxis === 'x') {
514
+ this.dragOffset.set(deltaX);
515
+ }
516
+ }
517
+ onPointerUp(event) {
518
+ if (this.pointerId !== event.pointerId)
519
+ return;
520
+ const offset = this.dragOffset();
521
+ const threshold = 48;
522
+ if (this.dragAxis === 'x') {
523
+ if (offset <= -threshold) {
524
+ this.next();
525
+ }
526
+ else if (offset >= threshold) {
527
+ this.prev();
528
+ }
529
+ }
530
+ this.pointerId = null;
531
+ this.dragAxis = null;
532
+ this.dragOffset.set(0);
533
+ this.dragging.set(false);
534
+ }
535
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.1", ngImport: i0, type: CarouselComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
536
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.1", type: CarouselComponent, isStandalone: true, selector: "ui-carousel", inputs: { images: { classPropertyName: "images", publicName: "images", isSignal: true, isRequired: true, transformFunction: null }, startIndex: { classPropertyName: "startIndex", publicName: "startIndex", isSignal: true, isRequired: false, transformFunction: null }, loop: { classPropertyName: "loop", publicName: "loop", isSignal: true, isRequired: false, transformFunction: null }, showCounter: { classPropertyName: "showCounter", publicName: "showCounter", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: false, transformFunction: null }, index: { classPropertyName: "index", publicName: "index", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { index: "indexChange" }, ngImport: i0, template: "<div\n class=\"ui-carousel\"\n role=\"group\"\n aria-roledescription=\"carousel\"\n [attr.aria-label]=\"ariaLabel()\"\n>\n <div\n class=\"ui-carousel__viewport\"\n tabindex=\"0\"\n (keydown)=\"handleKeyDown($event)\"\n (pointerdown)=\"onPointerDown($event)\"\n (pointermove)=\"onPointerMove($event)\"\n (pointerup)=\"onPointerUp($event)\"\n (pointercancel)=\"onPointerUp($event)\"\n >\n <div\n class=\"ui-carousel__track\"\n [class.ui-carousel__track--dragging]=\"dragging()\"\n [style.transform]=\"'translateX(calc(-' + currentIndex() * 100 + '% + ' + dragOffset() + 'px))'\"\n >\n @for (image of images(); track image.src; let i = $index) {\n <div\n class=\"ui-carousel__slide\"\n role=\"group\"\n aria-roledescription=\"slide\"\n [attr.aria-label]=\"(i + 1) + ' of ' + images().length\"\n [attr.aria-hidden]=\"i !== currentIndex()\"\n >\n <img\n class=\"ui-carousel__image\"\n [src]=\"image.src\"\n [alt]=\"image.alt || ''\"\n draggable=\"false\"\n [attr.loading]=\"i === currentIndex() ? null : 'lazy'\"\n />\n </div>\n }\n </div>\n </div>\n\n @if (hasPrev()) {\n <button\n type=\"button\"\n class=\"ui-carousel__arrow ui-carousel__arrow--prev\"\n aria-label=\"Previous image\"\n (click)=\"prev()\"\n >\n <svg width=\"20\" height=\"20\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\">\n <path d=\"M15 18l-6-6 6-6\"/>\n </svg>\n </button>\n }\n @if (hasNext()) {\n <button\n type=\"button\"\n class=\"ui-carousel__arrow ui-carousel__arrow--next\"\n aria-label=\"Next image\"\n (click)=\"next()\"\n >\n <svg width=\"20\" height=\"20\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\">\n <path d=\"M9 6l6 6-6 6\"/>\n </svg>\n </button>\n }\n\n @if (showCounter() && images().length > 0) {\n <span class=\"ui-carousel__counter\" aria-live=\"polite\">\n {{ currentIndex() + 1 }}/{{ images().length }}\n </span>\n }\n</div>\n", styles: [":host{display:block}.ui-carousel{position:relative;overflow:hidden;height:100%;border-radius:var(--ui-radius-lg);background-color:var(--ui-bg-tertiary)}.ui-carousel__viewport{overflow:hidden;touch-action:pan-y;height:100%}.ui-carousel__viewport:focus-visible{outline:2px solid var(--ui-primary);outline-offset:-2px}.ui-carousel__track{display:flex;height:100%;transition:transform var(--ui-transition-normal)}.ui-carousel__track--dragging{transition:none}.ui-carousel__slide{flex:0 0 100%;min-width:0;display:flex;align-items:center;justify-content:center}.ui-carousel__image{max-width:100%;max-height:100%;width:100%;height:100%;object-fit:cover;-webkit-user-select:none;user-select:none}.ui-carousel__arrow{position:absolute;top:50%;transform:translateY(-50%);display:flex;align-items:center;justify-content:center;width:36px;height:36px;padding:0;border:none;border-radius:50%;background-color:var(--ui-carousel-control-bg);color:var(--ui-carousel-control-text);cursor:pointer;opacity:0;transition:opacity var(--ui-transition-fast),background-color var(--ui-transition-fast)}.ui-carousel__arrow--prev{left:var(--ui-spacing-sm)}.ui-carousel__arrow--next{right:var(--ui-spacing-sm)}.ui-carousel:hover .ui-carousel__arrow,.ui-carousel__arrow:focus-visible{opacity:1}@media(hover:none){.ui-carousel__arrow{opacity:1}}.ui-carousel__arrow:hover{background-color:color-mix(in srgb,var(--ui-carousel-control-bg) 80%,#000000)}.ui-carousel__arrow:focus-visible{outline:2px solid var(--ui-primary);outline-offset:2px}.ui-carousel__counter{position:absolute;right:var(--ui-spacing-sm);bottom:var(--ui-spacing-sm);padding:2px var(--ui-spacing-sm);border-radius:var(--ui-radius-sm);background-color:var(--ui-carousel-counter-bg);color:var(--ui-carousel-control-text);font-size:var(--ui-font-xs);font-weight:500;pointer-events:none}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush });
537
+ }
538
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.1", ngImport: i0, type: CarouselComponent, decorators: [{
539
+ type: Component,
540
+ args: [{ selector: 'ui-carousel', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, template: "<div\n class=\"ui-carousel\"\n role=\"group\"\n aria-roledescription=\"carousel\"\n [attr.aria-label]=\"ariaLabel()\"\n>\n <div\n class=\"ui-carousel__viewport\"\n tabindex=\"0\"\n (keydown)=\"handleKeyDown($event)\"\n (pointerdown)=\"onPointerDown($event)\"\n (pointermove)=\"onPointerMove($event)\"\n (pointerup)=\"onPointerUp($event)\"\n (pointercancel)=\"onPointerUp($event)\"\n >\n <div\n class=\"ui-carousel__track\"\n [class.ui-carousel__track--dragging]=\"dragging()\"\n [style.transform]=\"'translateX(calc(-' + currentIndex() * 100 + '% + ' + dragOffset() + 'px))'\"\n >\n @for (image of images(); track image.src; let i = $index) {\n <div\n class=\"ui-carousel__slide\"\n role=\"group\"\n aria-roledescription=\"slide\"\n [attr.aria-label]=\"(i + 1) + ' of ' + images().length\"\n [attr.aria-hidden]=\"i !== currentIndex()\"\n >\n <img\n class=\"ui-carousel__image\"\n [src]=\"image.src\"\n [alt]=\"image.alt || ''\"\n draggable=\"false\"\n [attr.loading]=\"i === currentIndex() ? null : 'lazy'\"\n />\n </div>\n }\n </div>\n </div>\n\n @if (hasPrev()) {\n <button\n type=\"button\"\n class=\"ui-carousel__arrow ui-carousel__arrow--prev\"\n aria-label=\"Previous image\"\n (click)=\"prev()\"\n >\n <svg width=\"20\" height=\"20\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\">\n <path d=\"M15 18l-6-6 6-6\"/>\n </svg>\n </button>\n }\n @if (hasNext()) {\n <button\n type=\"button\"\n class=\"ui-carousel__arrow ui-carousel__arrow--next\"\n aria-label=\"Next image\"\n (click)=\"next()\"\n >\n <svg width=\"20\" height=\"20\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\">\n <path d=\"M9 6l6 6-6 6\"/>\n </svg>\n </button>\n }\n\n @if (showCounter() && images().length > 0) {\n <span class=\"ui-carousel__counter\" aria-live=\"polite\">\n {{ currentIndex() + 1 }}/{{ images().length }}\n </span>\n }\n</div>\n", styles: [":host{display:block}.ui-carousel{position:relative;overflow:hidden;height:100%;border-radius:var(--ui-radius-lg);background-color:var(--ui-bg-tertiary)}.ui-carousel__viewport{overflow:hidden;touch-action:pan-y;height:100%}.ui-carousel__viewport:focus-visible{outline:2px solid var(--ui-primary);outline-offset:-2px}.ui-carousel__track{display:flex;height:100%;transition:transform var(--ui-transition-normal)}.ui-carousel__track--dragging{transition:none}.ui-carousel__slide{flex:0 0 100%;min-width:0;display:flex;align-items:center;justify-content:center}.ui-carousel__image{max-width:100%;max-height:100%;width:100%;height:100%;object-fit:cover;-webkit-user-select:none;user-select:none}.ui-carousel__arrow{position:absolute;top:50%;transform:translateY(-50%);display:flex;align-items:center;justify-content:center;width:36px;height:36px;padding:0;border:none;border-radius:50%;background-color:var(--ui-carousel-control-bg);color:var(--ui-carousel-control-text);cursor:pointer;opacity:0;transition:opacity var(--ui-transition-fast),background-color var(--ui-transition-fast)}.ui-carousel__arrow--prev{left:var(--ui-spacing-sm)}.ui-carousel__arrow--next{right:var(--ui-spacing-sm)}.ui-carousel:hover .ui-carousel__arrow,.ui-carousel__arrow:focus-visible{opacity:1}@media(hover:none){.ui-carousel__arrow{opacity:1}}.ui-carousel__arrow:hover{background-color:color-mix(in srgb,var(--ui-carousel-control-bg) 80%,#000000)}.ui-carousel__arrow:focus-visible{outline:2px solid var(--ui-primary);outline-offset:2px}.ui-carousel__counter{position:absolute;right:var(--ui-spacing-sm);bottom:var(--ui-spacing-sm);padding:2px var(--ui-spacing-sm);border-radius:var(--ui-radius-sm);background-color:var(--ui-carousel-counter-bg);color:var(--ui-carousel-control-text);font-size:var(--ui-font-xs);font-weight:500;pointer-events:none}\n"] }]
541
+ }], propDecorators: { images: [{ type: i0.Input, args: [{ isSignal: true, alias: "images", required: true }] }], startIndex: [{ type: i0.Input, args: [{ isSignal: true, alias: "startIndex", required: false }] }], loop: [{ type: i0.Input, args: [{ isSignal: true, alias: "loop", required: false }] }], showCounter: [{ type: i0.Input, args: [{ isSignal: true, alias: "showCounter", required: false }] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabel", required: false }] }], index: [{ type: i0.Input, args: [{ isSignal: true, alias: "index", required: false }] }, { type: i0.Output, args: ["indexChange"] }] } });
542
+
543
+ /**
544
+ * Fullscreen image lightbox. Opened via LightboxService — not meant to be
545
+ * embedded directly in templates.
546
+ */
547
+ class LightboxComponent {
548
+ config = inject(DIALOG_DATA);
549
+ dialogRef = inject(DIALOG_REF);
550
+ carousel = viewChild.required(CarouselComponent);
551
+ viewport = viewChild.required('container');
552
+ /** Tracks if mousedown started on the backdrop (to prevent close on drag-out) */
553
+ mouseDownOnBackdrop = signal(false, ...(ngDevMode ? [{ debugName: "mouseDownOnBackdrop" }] : []));
554
+ constructor() {
555
+ afterNextRender(() => {
556
+ this.viewport().nativeElement.focus();
557
+ });
558
+ }
559
+ onKeyDown(event) {
560
+ switch (event.key) {
561
+ case 'Escape':
562
+ event.preventDefault();
563
+ this.close();
564
+ break;
565
+ case 'ArrowLeft':
566
+ event.preventDefault();
567
+ this.carousel().prev();
568
+ break;
569
+ case 'ArrowRight':
570
+ event.preventDefault();
571
+ this.carousel().next();
572
+ break;
573
+ }
574
+ }
575
+ onBackdropMouseDown(event) {
576
+ this.mouseDownOnBackdrop.set(event.target === event.currentTarget);
577
+ }
578
+ onBackdropClick(event) {
579
+ if (this.mouseDownOnBackdrop() && event.target === event.currentTarget) {
580
+ this.close();
581
+ }
582
+ this.mouseDownOnBackdrop.set(false);
583
+ }
584
+ close() {
585
+ this.dialogRef.close();
586
+ }
587
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.1", ngImport: i0, type: LightboxComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
588
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.2.0", version: "21.1.1", type: LightboxComponent, isStandalone: true, selector: "ui-lightbox", host: { listeners: { "document:keydown": "onKeyDown($event)" } }, viewQueries: [{ propertyName: "carousel", first: true, predicate: CarouselComponent, descendants: true, isSignal: true }, { propertyName: "viewport", first: true, predicate: ["container"], descendants: true, isSignal: true }], ngImport: i0, template: "<div\n class=\"ui-lightbox__backdrop\"\n (mousedown)=\"onBackdropMouseDown($event)\"\n (click)=\"onBackdropClick($event)\"\n>\n <div\n #container\n class=\"ui-lightbox__container\"\n role=\"dialog\"\n aria-modal=\"true\"\n aria-label=\"Image gallery\"\n tabindex=\"-1\"\n >\n <button\n type=\"button\"\n class=\"ui-lightbox__close\"\n aria-label=\"Close\"\n (click)=\"close()\"\n >\n <svg width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\">\n <path d=\"M18 6L6 18M6 6l12 12\"/>\n </svg>\n </button>\n\n <ui-carousel\n class=\"ui-lightbox__carousel\"\n [images]=\"config.images\"\n [startIndex]=\"config.startIndex ?? 0\"\n [showCounter]=\"true\"\n />\n </div>\n</div>\n", styles: [".ui-lightbox__backdrop{position:fixed;inset:0;z-index:1000;display:flex;align-items:center;justify-content:center;background-color:var(--ui-lightbox-backdrop);animation:fadeIn var(--ui-transition-fast)}.ui-lightbox__container{position:relative;width:100%;height:100%;display:flex;align-items:center;justify-content:center;padding:var(--ui-spacing-xl);outline:none;pointer-events:none}.ui-lightbox__carousel{width:100%;height:100%;max-width:100%;max-height:100%;pointer-events:auto}.ui-lightbox__carousel ::ng-deep .ui-carousel{height:100%;background:transparent;border-radius:0}.ui-lightbox__carousel ::ng-deep .ui-carousel__image{object-fit:contain}.ui-lightbox__carousel ::ng-deep .ui-carousel__counter{right:50%;transform:translate(50%);font-size:var(--ui-font-sm)}.ui-lightbox__close{position:absolute;top:var(--ui-spacing-md);right:var(--ui-spacing-md);z-index:1;display:flex;align-items:center;justify-content:center;width:40px;height:40px;padding:0;border:none;border-radius:50%;background-color:var(--ui-lightbox-control-bg);color:var(--ui-lightbox-control-text);cursor:pointer;pointer-events:auto;transition:background-color var(--ui-transition-fast)}.ui-lightbox__close:hover{background-color:color-mix(in srgb,var(--ui-lightbox-control-bg) 80%,#000000)}.ui-lightbox__close:focus-visible{outline:2px solid var(--ui-lightbox-control-text);outline-offset:2px}@keyframes fadeIn{0%{opacity:0}to{opacity:1}}\n"], dependencies: [{ kind: "component", type: CarouselComponent, selector: "ui-carousel", inputs: ["images", "startIndex", "loop", "showCounter", "ariaLabel", "index"], outputs: ["indexChange"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
589
+ }
590
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.1", ngImport: i0, type: LightboxComponent, decorators: [{
591
+ type: Component,
592
+ args: [{ selector: 'ui-lightbox', standalone: true, imports: [CarouselComponent], changeDetection: ChangeDetectionStrategy.OnPush, template: "<div\n class=\"ui-lightbox__backdrop\"\n (mousedown)=\"onBackdropMouseDown($event)\"\n (click)=\"onBackdropClick($event)\"\n>\n <div\n #container\n class=\"ui-lightbox__container\"\n role=\"dialog\"\n aria-modal=\"true\"\n aria-label=\"Image gallery\"\n tabindex=\"-1\"\n >\n <button\n type=\"button\"\n class=\"ui-lightbox__close\"\n aria-label=\"Close\"\n (click)=\"close()\"\n >\n <svg width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\">\n <path d=\"M18 6L6 18M6 6l12 12\"/>\n </svg>\n </button>\n\n <ui-carousel\n class=\"ui-lightbox__carousel\"\n [images]=\"config.images\"\n [startIndex]=\"config.startIndex ?? 0\"\n [showCounter]=\"true\"\n />\n </div>\n</div>\n", styles: [".ui-lightbox__backdrop{position:fixed;inset:0;z-index:1000;display:flex;align-items:center;justify-content:center;background-color:var(--ui-lightbox-backdrop);animation:fadeIn var(--ui-transition-fast)}.ui-lightbox__container{position:relative;width:100%;height:100%;display:flex;align-items:center;justify-content:center;padding:var(--ui-spacing-xl);outline:none;pointer-events:none}.ui-lightbox__carousel{width:100%;height:100%;max-width:100%;max-height:100%;pointer-events:auto}.ui-lightbox__carousel ::ng-deep .ui-carousel{height:100%;background:transparent;border-radius:0}.ui-lightbox__carousel ::ng-deep .ui-carousel__image{object-fit:contain}.ui-lightbox__carousel ::ng-deep .ui-carousel__counter{right:50%;transform:translate(50%);font-size:var(--ui-font-sm)}.ui-lightbox__close{position:absolute;top:var(--ui-spacing-md);right:var(--ui-spacing-md);z-index:1;display:flex;align-items:center;justify-content:center;width:40px;height:40px;padding:0;border:none;border-radius:50%;background-color:var(--ui-lightbox-control-bg);color:var(--ui-lightbox-control-text);cursor:pointer;pointer-events:auto;transition:background-color var(--ui-transition-fast)}.ui-lightbox__close:hover{background-color:color-mix(in srgb,var(--ui-lightbox-control-bg) 80%,#000000)}.ui-lightbox__close:focus-visible{outline:2px solid var(--ui-lightbox-control-text);outline-offset:2px}@keyframes fadeIn{0%{opacity:0}to{opacity:1}}\n"] }]
593
+ }], ctorParameters: () => [], propDecorators: { carousel: [{ type: i0.ViewChild, args: [i0.forwardRef(() => CarouselComponent), { isSignal: true }] }], viewport: [{ type: i0.ViewChild, args: ['container', { isSignal: true }] }], onKeyDown: [{
594
+ type: HostListener,
595
+ args: ['document:keydown', ['$event']]
596
+ }] } });
597
+
598
+ /**
599
+ * Service for opening a fullscreen image lightbox.
600
+ *
601
+ * @example
602
+ * ```typescript
603
+ * const ref = this.lightboxService.open({
604
+ * images: [{ src: '/photos/1.jpg', alt: 'Living room' }],
605
+ * startIndex: 0,
606
+ * });
607
+ *
608
+ * await ref.afterClosed();
609
+ * ```
610
+ */
611
+ class LightboxService {
612
+ dialogService = inject(DialogService);
613
+ /**
614
+ * Opens the lightbox with the given images.
615
+ *
616
+ * @returns A DialogRef that resolves when the lightbox is closed
617
+ */
618
+ open(config) {
619
+ return this.dialogService.open(LightboxComponent, {
620
+ data: config,
621
+ });
622
+ }
623
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.1", ngImport: i0, type: LightboxService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
624
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.1.1", ngImport: i0, type: LightboxService, providedIn: 'root' });
625
+ }
626
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.1", ngImport: i0, type: LightboxService, decorators: [{
627
+ type: Injectable,
628
+ args: [{ providedIn: 'root' }]
629
+ }] });
630
+
429
631
  class ToastComponent {
430
632
  data = input.required(...(ngDevMode ? [{ debugName: "data" }] : []));
431
633
  dismissed = output();
@@ -4251,6 +4453,256 @@ function pad(n) {
4251
4453
  return n < 10 ? `0${n}` : String(n);
4252
4454
  }
4253
4455
 
4456
+ class NumberRangeSliderComponent {
4457
+ min = input(0, ...(ngDevMode ? [{ debugName: "min" }] : []));
4458
+ max = input(100, ...(ngDevMode ? [{ debugName: "max" }] : []));
4459
+ step = input(1, ...(ngDevMode ? [{ debugName: "step" }] : []));
4460
+ size = input('md', ...(ngDevMode ? [{ debugName: "size" }] : []));
4461
+ label = input('', ...(ngDevMode ? [{ debugName: "label" }] : []));
4462
+ disabled = input(false, ...(ngDevMode ? [{ debugName: "disabled" }] : []));
4463
+ bubbles = input('always', ...(ngDevMode ? [{ debugName: "bubbles" }] : []));
4464
+ showRange = input(false, ...(ngDevMode ? [{ debugName: "showRange" }] : []));
4465
+ prefix = input('', ...(ngDevMode ? [{ debugName: "prefix" }] : []));
4466
+ suffix = input('', ...(ngDevMode ? [{ debugName: "suffix" }] : []));
4467
+ decimals = input(0, ...(ngDevMode ? [{ debugName: "decimals" }] : []));
4468
+ formatValue = input(null, ...(ngDevMode ? [{ debugName: "formatValue" }] : []));
4469
+ value = model(null, ...(ngDevMode ? [{ debugName: "value" }] : []));
4470
+ valueCommit = output();
4471
+ trackEl = viewChild('track', ...(ngDevMode ? [{ debugName: "trackEl" }] : []));
4472
+ startVal = signal(0, ...(ngDevMode ? [{ debugName: "startVal" }] : []));
4473
+ endVal = signal(0, ...(ngDevMode ? [{ debugName: "endVal" }] : []));
4474
+ dragging = signal(null, ...(ngDevMode ? [{ debugName: "dragging" }] : []));
4475
+ focusedThumb = signal(null, ...(ngDevMode ? [{ debugName: "focusedThumb" }] : []));
4476
+ hoveredThumb = signal(null, ...(ngDevMode ? [{ debugName: "hoveredThumb" }] : []));
4477
+ lastEmitted = null;
4478
+ range = computed(() => Math.max(0, this.max() - this.min()), ...(ngDevMode ? [{ debugName: "range" }] : []));
4479
+ startPercent = computed(() => this.toPercent(this.startVal()), ...(ngDevMode ? [{ debugName: "startPercent" }] : []));
4480
+ endPercent = computed(() => this.toPercent(this.endVal()), ...(ngDevMode ? [{ debugName: "endPercent" }] : []));
4481
+ fillStyle = computed(() => {
4482
+ const start = this.startPercent();
4483
+ const end = this.endPercent();
4484
+ return { left: `${start}%`, width: `${Math.max(0, end - start)}%` };
4485
+ }, ...(ngDevMode ? [{ debugName: "fillStyle" }] : []));
4486
+ startLabel = computed(() => this.format(this.startVal()), ...(ngDevMode ? [{ debugName: "startLabel" }] : []));
4487
+ endLabel = computed(() => this.format(this.endVal()), ...(ngDevMode ? [{ debugName: "endLabel" }] : []));
4488
+ bubbleVisible = computed(() => {
4489
+ const mode = this.bubbles();
4490
+ if (mode === 'never')
4491
+ return { start: false, end: false };
4492
+ if (mode === 'always')
4493
+ return { start: true, end: true };
4494
+ const active = (t) => this.dragging() === t || this.focusedThumb() === t || this.hoveredThumb() === t;
4495
+ return { start: active('start'), end: active('end') };
4496
+ }, ...(ngDevMode ? [{ debugName: "bubbleVisible" }] : []));
4497
+ rootClasses = computed(() => {
4498
+ const classes = ['ui-number-range-slider', `ui-number-range-slider--${this.size()}`];
4499
+ if (this.disabled())
4500
+ classes.push('ui-number-range-slider--disabled');
4501
+ return classes.join(' ');
4502
+ }, ...(ngDevMode ? [{ debugName: "rootClasses" }] : []));
4503
+ constructor() {
4504
+ // Sync external value → internal thumb positions. Falls back to [min, max] when null.
4505
+ effect(() => {
4506
+ const min = this.min();
4507
+ const max = this.max();
4508
+ const v = this.value();
4509
+ if (v) {
4510
+ this.startVal.set(this.snap(this.clamp(v.start)));
4511
+ this.endVal.set(this.snap(this.clamp(v.end)));
4512
+ this.lastEmitted = v;
4513
+ }
4514
+ else {
4515
+ this.startVal.set(min);
4516
+ this.endVal.set(max);
4517
+ this.lastEmitted = null;
4518
+ }
4519
+ });
4520
+ }
4521
+ /** Push current thumb state into the value model (live, during interaction). */
4522
+ writeValue() {
4523
+ const next = { start: this.startVal(), end: this.endVal() };
4524
+ const prev = this.lastEmitted;
4525
+ if (prev && prev.start === next.start && prev.end === next.end) {
4526
+ return;
4527
+ }
4528
+ this.lastEmitted = next;
4529
+ this.value.set(next);
4530
+ }
4531
+ // -- Pointer handling -------------------------------------------------
4532
+ onThumbPointerDown(event, thumb) {
4533
+ if (this.disabled())
4534
+ return;
4535
+ event.preventDefault();
4536
+ const target = event.target;
4537
+ target.setPointerCapture(event.pointerId);
4538
+ this.dragging.set(thumb);
4539
+ this.focusedThumb.set(thumb);
4540
+ target.focus();
4541
+ }
4542
+ onThumbPointerMove(event, thumb) {
4543
+ if (this.dragging() !== thumb)
4544
+ return;
4545
+ event.preventDefault();
4546
+ const val = this.pixelToVal(event.clientX);
4547
+ this.setThumb(thumb, val);
4548
+ }
4549
+ onThumbPointerUp(event, thumb) {
4550
+ if (this.dragging() !== thumb)
4551
+ return;
4552
+ const target = event.target;
4553
+ if (target.hasPointerCapture(event.pointerId)) {
4554
+ target.releasePointerCapture(event.pointerId);
4555
+ }
4556
+ this.dragging.set(null);
4557
+ this.commit();
4558
+ }
4559
+ onTrackPointerDown(event) {
4560
+ if (this.disabled())
4561
+ return;
4562
+ if (event.target.dataset['thumb'])
4563
+ return; // ignore thumb hits
4564
+ const val = this.pixelToVal(event.clientX);
4565
+ const thumb = Math.abs(val - this.startVal()) <= Math.abs(val - this.endVal()) ? 'start' : 'end';
4566
+ this.setThumb(thumb, val);
4567
+ this.commit();
4568
+ }
4569
+ // -- Keyboard ---------------------------------------------------------
4570
+ onThumbKeyDown(event, thumb) {
4571
+ if (this.disabled())
4572
+ return;
4573
+ const step = this.effectiveStep();
4574
+ const big = step * 10;
4575
+ let delta = 0;
4576
+ let target = null;
4577
+ switch (event.key) {
4578
+ case 'ArrowLeft':
4579
+ case 'ArrowDown':
4580
+ delta = event.shiftKey ? -big : -step;
4581
+ break;
4582
+ case 'ArrowRight':
4583
+ case 'ArrowUp':
4584
+ delta = event.shiftKey ? big : step;
4585
+ break;
4586
+ case 'Home':
4587
+ target = thumb === 'start' ? this.min() : this.startVal();
4588
+ break;
4589
+ case 'End':
4590
+ target = thumb === 'end' ? this.max() : this.endVal();
4591
+ break;
4592
+ default:
4593
+ return;
4594
+ }
4595
+ event.preventDefault();
4596
+ if (target !== null) {
4597
+ this.setThumb(thumb, target);
4598
+ }
4599
+ else {
4600
+ const current = thumb === 'start' ? this.startVal() : this.endVal();
4601
+ this.setThumb(thumb, current + delta);
4602
+ }
4603
+ }
4604
+ onThumbKeyUp(event) {
4605
+ const keys = ['ArrowLeft', 'ArrowRight', 'ArrowUp', 'ArrowDown', 'Home', 'End'];
4606
+ if (keys.includes(event.key)) {
4607
+ this.commit();
4608
+ }
4609
+ }
4610
+ onThumbFocus(thumb) {
4611
+ this.focusedThumb.set(thumb);
4612
+ }
4613
+ onThumbBlur() {
4614
+ this.focusedThumb.set(null);
4615
+ }
4616
+ onThumbEnter(thumb) {
4617
+ this.hoveredThumb.set(thumb);
4618
+ }
4619
+ onThumbLeave() {
4620
+ this.hoveredThumb.set(null);
4621
+ }
4622
+ // -- Internal helpers -------------------------------------------------
4623
+ effectiveStep() {
4624
+ const step = this.step();
4625
+ return step > 0 ? step : 1;
4626
+ }
4627
+ setThumb(thumb, raw) {
4628
+ const snapped = this.snap(this.clamp(raw));
4629
+ let changed = false;
4630
+ if (thumb === 'start') {
4631
+ const next = Math.min(snapped, this.endVal());
4632
+ if (next !== this.startVal()) {
4633
+ this.startVal.set(next);
4634
+ changed = true;
4635
+ }
4636
+ }
4637
+ else {
4638
+ const next = Math.max(snapped, this.startVal());
4639
+ if (next !== this.endVal()) {
4640
+ this.endVal.set(next);
4641
+ changed = true;
4642
+ }
4643
+ }
4644
+ if (changed)
4645
+ this.writeValue();
4646
+ }
4647
+ commit() {
4648
+ if (this.lastEmitted) {
4649
+ this.valueCommit.emit(this.lastEmitted);
4650
+ }
4651
+ }
4652
+ clamp(val) {
4653
+ return Math.min(this.max(), Math.max(this.min(), val));
4654
+ }
4655
+ snap(val) {
4656
+ const min = this.min();
4657
+ const max = this.max();
4658
+ const step = this.step();
4659
+ if (step <= 0 || min >= max)
4660
+ return val;
4661
+ const offset = val - min;
4662
+ const snapped = min + Math.round(offset / step) * step;
4663
+ if (snapped > max)
4664
+ return max;
4665
+ if (snapped < min)
4666
+ return min;
4667
+ return snapped;
4668
+ }
4669
+ toPercent(val) {
4670
+ const range = this.range();
4671
+ if (range <= 0)
4672
+ return 0;
4673
+ return ((val - this.min()) / range) * 100;
4674
+ }
4675
+ pixelToVal(clientX) {
4676
+ const el = this.trackEl()?.nativeElement;
4677
+ if (!el)
4678
+ return this.min();
4679
+ const rect = el.getBoundingClientRect();
4680
+ if (rect.width <= 0)
4681
+ return this.min();
4682
+ const ratio = (clientX - rect.left) / rect.width;
4683
+ const clamped = Math.min(1, Math.max(0, ratio));
4684
+ return this.min() + clamped * this.range();
4685
+ }
4686
+ // -- Formatting -------------------------------------------------------
4687
+ format(val) {
4688
+ const fn = this.formatValue();
4689
+ if (fn)
4690
+ return fn(val);
4691
+ const decimals = this.decimals();
4692
+ const body = val.toLocaleString(undefined, {
4693
+ minimumFractionDigits: decimals,
4694
+ maximumFractionDigits: decimals,
4695
+ });
4696
+ return `${this.prefix()}${body}${this.suffix()}`;
4697
+ }
4698
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.1", ngImport: i0, type: NumberRangeSliderComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
4699
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.1", type: NumberRangeSliderComponent, isStandalone: true, selector: "ui-number-range-slider", inputs: { min: { classPropertyName: "min", publicName: "min", isSignal: true, isRequired: false, transformFunction: null }, max: { classPropertyName: "max", publicName: "max", isSignal: true, isRequired: false, transformFunction: null }, step: { classPropertyName: "step", publicName: "step", isSignal: true, isRequired: false, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, bubbles: { classPropertyName: "bubbles", publicName: "bubbles", isSignal: true, isRequired: false, transformFunction: null }, showRange: { classPropertyName: "showRange", publicName: "showRange", isSignal: true, isRequired: false, transformFunction: null }, prefix: { classPropertyName: "prefix", publicName: "prefix", isSignal: true, isRequired: false, transformFunction: null }, suffix: { classPropertyName: "suffix", publicName: "suffix", isSignal: true, isRequired: false, transformFunction: null }, decimals: { classPropertyName: "decimals", publicName: "decimals", isSignal: true, isRequired: false, transformFunction: null }, formatValue: { classPropertyName: "formatValue", publicName: "formatValue", isSignal: true, isRequired: false, transformFunction: null }, value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange", valueCommit: "valueCommit" }, viewQueries: [{ propertyName: "trackEl", first: true, predicate: ["track"], descendants: true, isSignal: true }], ngImport: i0, template: "@let isDisabled = disabled();\n@let bubbleVis = bubbleVisible();\n@let rangeOn = showRange();\n@let startPct = startPercent();\n@let endPct = endPercent();\n@let fill = fillStyle();\n\n<div [class]=\"rootClasses()\">\n @if (label()) {\n <label class=\"ui-number-range-slider__label\">{{ label() }}</label>\n }\n\n <div\n #track\n class=\"ui-number-range-slider__track\"\n (pointerdown)=\"onTrackPointerDown($event)\"\n >\n <div\n class=\"ui-number-range-slider__fill\"\n [style.left]=\"fill.left\"\n [style.width]=\"fill.width\"\n ></div>\n\n <div\n class=\"ui-number-range-slider__thumb ui-number-range-slider__thumb--start\"\n role=\"slider\"\n tabindex=\"0\"\n data-thumb=\"start\"\n [attr.aria-valuemin]=\"min()\"\n [attr.aria-valuemax]=\"endVal()\"\n [attr.aria-valuenow]=\"startVal()\"\n [attr.aria-valuetext]=\"startLabel()\"\n [attr.aria-disabled]=\"isDisabled\"\n aria-label=\"Start\"\n aria-orientation=\"horizontal\"\n [style.left.%]=\"startPct\"\n (pointerdown)=\"onThumbPointerDown($event, 'start')\"\n (pointermove)=\"onThumbPointerMove($event, 'start')\"\n (pointerup)=\"onThumbPointerUp($event, 'start')\"\n (pointercancel)=\"onThumbPointerUp($event, 'start')\"\n (pointerenter)=\"onThumbEnter('start')\"\n (pointerleave)=\"onThumbLeave()\"\n (keydown)=\"onThumbKeyDown($event, 'start')\"\n (keyup)=\"onThumbKeyUp($event)\"\n (focus)=\"onThumbFocus('start')\"\n (blur)=\"onThumbBlur()\"\n >\n @if (bubbleVis.start) {\n <span class=\"ui-number-range-slider__bubble\">{{ startLabel() }}</span>\n }\n </div>\n\n <div\n class=\"ui-number-range-slider__thumb ui-number-range-slider__thumb--end\"\n role=\"slider\"\n tabindex=\"0\"\n data-thumb=\"end\"\n [attr.aria-valuemin]=\"startVal()\"\n [attr.aria-valuemax]=\"max()\"\n [attr.aria-valuenow]=\"endVal()\"\n [attr.aria-valuetext]=\"endLabel()\"\n [attr.aria-disabled]=\"isDisabled\"\n aria-label=\"End\"\n aria-orientation=\"horizontal\"\n [style.left.%]=\"endPct\"\n (pointerdown)=\"onThumbPointerDown($event, 'end')\"\n (pointermove)=\"onThumbPointerMove($event, 'end')\"\n (pointerup)=\"onThumbPointerUp($event, 'end')\"\n (pointercancel)=\"onThumbPointerUp($event, 'end')\"\n (pointerenter)=\"onThumbEnter('end')\"\n (pointerleave)=\"onThumbLeave()\"\n (keydown)=\"onThumbKeyDown($event, 'end')\"\n (keyup)=\"onThumbKeyUp($event)\"\n (focus)=\"onThumbFocus('end')\"\n (blur)=\"onThumbBlur()\"\n >\n @if (bubbleVis.end) {\n <span class=\"ui-number-range-slider__bubble\">{{ endLabel() }}</span>\n }\n </div>\n </div>\n\n @if (rangeOn) {\n <div class=\"ui-number-range-slider__range\">\n <span>{{ startLabel() }}</span>\n <span class=\"ui-number-range-slider__range-arrow\" aria-hidden=\"true\">\u2192</span>\n <span>{{ endLabel() }}</span>\n </div>\n }\n</div>\n", styles: [":host{display:block}.ui-number-range-slider{display:flex;flex-direction:column;gap:var(--ui-spacing-sm);user-select:none;-webkit-user-select:none}.ui-number-range-slider__label{font-size:var(--ui-font-sm);font-weight:500;color:var(--ui-text)}.ui-number-range-slider__track{position:relative;width:100%;background:var(--ui-bg-tertiary);border-radius:var(--ui-radius-xl);cursor:pointer;touch-action:none;margin-top:var(--ui-spacing-lg)}.ui-number-range-slider__fill{position:absolute;top:0;bottom:0;background:var(--ui-primary);border-radius:var(--ui-radius-xl);pointer-events:none}.ui-number-range-slider__thumb{position:absolute;top:50%;border-radius:50%;background:var(--ui-primary);border:2px solid var(--ui-bg);box-shadow:var(--ui-shadow-sm);cursor:grab;outline:none;transition:box-shadow var(--ui-transition-fast),transform var(--ui-transition-fast);touch-action:none;transform:translate(-50%,-50%)}.ui-number-range-slider__thumb:hover:not([aria-disabled=true]){transform:translate(-50%,-50%) scale(1.1)}.ui-number-range-slider__thumb:focus-visible{box-shadow:0 0 0 3px color-mix(in srgb,var(--ui-primary) 20%,transparent)}.ui-number-range-slider__thumb:active:not([aria-disabled=true]){cursor:grabbing;transform:translate(-50%,-50%) scale(1.15);box-shadow:0 0 0 3px color-mix(in srgb,var(--ui-primary) 20%,transparent);z-index:3}.ui-number-range-slider__bubble{position:absolute;bottom:calc(100% + 8px);left:50%;transform:translate(-50%);background:var(--ui-bg);border:1px solid var(--ui-border);color:var(--ui-text);font-size:var(--ui-font-xs);font-weight:500;padding:2px var(--ui-spacing-sm);border-radius:var(--ui-radius-sm);box-shadow:var(--ui-shadow-sm);white-space:nowrap;pointer-events:none;font-variant-numeric:tabular-nums;animation:ui-number-range-slider-bubble-in var(--ui-transition-fast) ease-out}.ui-number-range-slider__bubble:after{content:\"\";position:absolute;top:100%;left:50%;transform:translate(-50%);border:4px solid transparent;border-top-color:var(--ui-border)}.ui-number-range-slider__bubble:before{content:\"\";position:absolute;top:calc(100% - 1px);left:50%;transform:translate(-50%);border:4px solid transparent;border-top-color:var(--ui-bg);z-index:1}.ui-number-range-slider__range{display:flex;align-items:center;gap:var(--ui-spacing-xs);font-size:var(--ui-font-sm);color:var(--ui-text-muted);font-variant-numeric:tabular-nums}.ui-number-range-slider__range-arrow{color:var(--ui-text-disabled)}.ui-number-range-slider--sm .ui-number-range-slider__track{height:4px}.ui-number-range-slider--sm .ui-number-range-slider__thumb{width:14px;height:14px}.ui-number-range-slider--md .ui-number-range-slider__track{height:6px}.ui-number-range-slider--md .ui-number-range-slider__thumb{width:18px;height:18px}.ui-number-range-slider--lg .ui-number-range-slider__track{height:8px}.ui-number-range-slider--lg .ui-number-range-slider__thumb{width:24px;height:24px}.ui-number-range-slider--disabled{opacity:.5}.ui-number-range-slider--disabled .ui-number-range-slider__track,.ui-number-range-slider--disabled .ui-number-range-slider__thumb{cursor:not-allowed}.ui-number-range-slider--disabled .ui-number-range-slider__thumb:hover,.ui-number-range-slider--disabled .ui-number-range-slider__thumb:active{transform:translate(-50%,-50%);box-shadow:var(--ui-shadow-sm)}@keyframes ui-number-range-slider-bubble-in{0%{opacity:0;transform:translate(-50%) translateY(4px)}to{opacity:1;transform:translate(-50%) translateY(0)}}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush });
4700
+ }
4701
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.1", ngImport: i0, type: NumberRangeSliderComponent, decorators: [{
4702
+ type: Component,
4703
+ args: [{ selector: 'ui-number-range-slider', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, template: "@let isDisabled = disabled();\n@let bubbleVis = bubbleVisible();\n@let rangeOn = showRange();\n@let startPct = startPercent();\n@let endPct = endPercent();\n@let fill = fillStyle();\n\n<div [class]=\"rootClasses()\">\n @if (label()) {\n <label class=\"ui-number-range-slider__label\">{{ label() }}</label>\n }\n\n <div\n #track\n class=\"ui-number-range-slider__track\"\n (pointerdown)=\"onTrackPointerDown($event)\"\n >\n <div\n class=\"ui-number-range-slider__fill\"\n [style.left]=\"fill.left\"\n [style.width]=\"fill.width\"\n ></div>\n\n <div\n class=\"ui-number-range-slider__thumb ui-number-range-slider__thumb--start\"\n role=\"slider\"\n tabindex=\"0\"\n data-thumb=\"start\"\n [attr.aria-valuemin]=\"min()\"\n [attr.aria-valuemax]=\"endVal()\"\n [attr.aria-valuenow]=\"startVal()\"\n [attr.aria-valuetext]=\"startLabel()\"\n [attr.aria-disabled]=\"isDisabled\"\n aria-label=\"Start\"\n aria-orientation=\"horizontal\"\n [style.left.%]=\"startPct\"\n (pointerdown)=\"onThumbPointerDown($event, 'start')\"\n (pointermove)=\"onThumbPointerMove($event, 'start')\"\n (pointerup)=\"onThumbPointerUp($event, 'start')\"\n (pointercancel)=\"onThumbPointerUp($event, 'start')\"\n (pointerenter)=\"onThumbEnter('start')\"\n (pointerleave)=\"onThumbLeave()\"\n (keydown)=\"onThumbKeyDown($event, 'start')\"\n (keyup)=\"onThumbKeyUp($event)\"\n (focus)=\"onThumbFocus('start')\"\n (blur)=\"onThumbBlur()\"\n >\n @if (bubbleVis.start) {\n <span class=\"ui-number-range-slider__bubble\">{{ startLabel() }}</span>\n }\n </div>\n\n <div\n class=\"ui-number-range-slider__thumb ui-number-range-slider__thumb--end\"\n role=\"slider\"\n tabindex=\"0\"\n data-thumb=\"end\"\n [attr.aria-valuemin]=\"startVal()\"\n [attr.aria-valuemax]=\"max()\"\n [attr.aria-valuenow]=\"endVal()\"\n [attr.aria-valuetext]=\"endLabel()\"\n [attr.aria-disabled]=\"isDisabled\"\n aria-label=\"End\"\n aria-orientation=\"horizontal\"\n [style.left.%]=\"endPct\"\n (pointerdown)=\"onThumbPointerDown($event, 'end')\"\n (pointermove)=\"onThumbPointerMove($event, 'end')\"\n (pointerup)=\"onThumbPointerUp($event, 'end')\"\n (pointercancel)=\"onThumbPointerUp($event, 'end')\"\n (pointerenter)=\"onThumbEnter('end')\"\n (pointerleave)=\"onThumbLeave()\"\n (keydown)=\"onThumbKeyDown($event, 'end')\"\n (keyup)=\"onThumbKeyUp($event)\"\n (focus)=\"onThumbFocus('end')\"\n (blur)=\"onThumbBlur()\"\n >\n @if (bubbleVis.end) {\n <span class=\"ui-number-range-slider__bubble\">{{ endLabel() }}</span>\n }\n </div>\n </div>\n\n @if (rangeOn) {\n <div class=\"ui-number-range-slider__range\">\n <span>{{ startLabel() }}</span>\n <span class=\"ui-number-range-slider__range-arrow\" aria-hidden=\"true\">\u2192</span>\n <span>{{ endLabel() }}</span>\n </div>\n }\n</div>\n", styles: [":host{display:block}.ui-number-range-slider{display:flex;flex-direction:column;gap:var(--ui-spacing-sm);user-select:none;-webkit-user-select:none}.ui-number-range-slider__label{font-size:var(--ui-font-sm);font-weight:500;color:var(--ui-text)}.ui-number-range-slider__track{position:relative;width:100%;background:var(--ui-bg-tertiary);border-radius:var(--ui-radius-xl);cursor:pointer;touch-action:none;margin-top:var(--ui-spacing-lg)}.ui-number-range-slider__fill{position:absolute;top:0;bottom:0;background:var(--ui-primary);border-radius:var(--ui-radius-xl);pointer-events:none}.ui-number-range-slider__thumb{position:absolute;top:50%;border-radius:50%;background:var(--ui-primary);border:2px solid var(--ui-bg);box-shadow:var(--ui-shadow-sm);cursor:grab;outline:none;transition:box-shadow var(--ui-transition-fast),transform var(--ui-transition-fast);touch-action:none;transform:translate(-50%,-50%)}.ui-number-range-slider__thumb:hover:not([aria-disabled=true]){transform:translate(-50%,-50%) scale(1.1)}.ui-number-range-slider__thumb:focus-visible{box-shadow:0 0 0 3px color-mix(in srgb,var(--ui-primary) 20%,transparent)}.ui-number-range-slider__thumb:active:not([aria-disabled=true]){cursor:grabbing;transform:translate(-50%,-50%) scale(1.15);box-shadow:0 0 0 3px color-mix(in srgb,var(--ui-primary) 20%,transparent);z-index:3}.ui-number-range-slider__bubble{position:absolute;bottom:calc(100% + 8px);left:50%;transform:translate(-50%);background:var(--ui-bg);border:1px solid var(--ui-border);color:var(--ui-text);font-size:var(--ui-font-xs);font-weight:500;padding:2px var(--ui-spacing-sm);border-radius:var(--ui-radius-sm);box-shadow:var(--ui-shadow-sm);white-space:nowrap;pointer-events:none;font-variant-numeric:tabular-nums;animation:ui-number-range-slider-bubble-in var(--ui-transition-fast) ease-out}.ui-number-range-slider__bubble:after{content:\"\";position:absolute;top:100%;left:50%;transform:translate(-50%);border:4px solid transparent;border-top-color:var(--ui-border)}.ui-number-range-slider__bubble:before{content:\"\";position:absolute;top:calc(100% - 1px);left:50%;transform:translate(-50%);border:4px solid transparent;border-top-color:var(--ui-bg);z-index:1}.ui-number-range-slider__range{display:flex;align-items:center;gap:var(--ui-spacing-xs);font-size:var(--ui-font-sm);color:var(--ui-text-muted);font-variant-numeric:tabular-nums}.ui-number-range-slider__range-arrow{color:var(--ui-text-disabled)}.ui-number-range-slider--sm .ui-number-range-slider__track{height:4px}.ui-number-range-slider--sm .ui-number-range-slider__thumb{width:14px;height:14px}.ui-number-range-slider--md .ui-number-range-slider__track{height:6px}.ui-number-range-slider--md .ui-number-range-slider__thumb{width:18px;height:18px}.ui-number-range-slider--lg .ui-number-range-slider__track{height:8px}.ui-number-range-slider--lg .ui-number-range-slider__thumb{width:24px;height:24px}.ui-number-range-slider--disabled{opacity:.5}.ui-number-range-slider--disabled .ui-number-range-slider__track,.ui-number-range-slider--disabled .ui-number-range-slider__thumb{cursor:not-allowed}.ui-number-range-slider--disabled .ui-number-range-slider__thumb:hover,.ui-number-range-slider--disabled .ui-number-range-slider__thumb:active{transform:translate(-50%,-50%);box-shadow:var(--ui-shadow-sm)}@keyframes ui-number-range-slider-bubble-in{0%{opacity:0;transform:translate(-50%) translateY(4px)}to{opacity:1;transform:translate(-50%) translateY(0)}}\n"] }]
4704
+ }], ctorParameters: () => [], propDecorators: { min: [{ type: i0.Input, args: [{ isSignal: true, alias: "min", required: false }] }], max: [{ type: i0.Input, args: [{ isSignal: true, alias: "max", required: false }] }], step: [{ type: i0.Input, args: [{ isSignal: true, alias: "step", required: false }] }], size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }], label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], bubbles: [{ type: i0.Input, args: [{ isSignal: true, alias: "bubbles", required: false }] }], showRange: [{ type: i0.Input, args: [{ isSignal: true, alias: "showRange", required: false }] }], prefix: [{ type: i0.Input, args: [{ isSignal: true, alias: "prefix", required: false }] }], suffix: [{ type: i0.Input, args: [{ isSignal: true, alias: "suffix", required: false }] }], decimals: [{ type: i0.Input, args: [{ isSignal: true, alias: "decimals", required: false }] }], formatValue: [{ type: i0.Input, args: [{ isSignal: true, alias: "formatValue", required: false }] }], value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }, { type: i0.Output, args: ["valueChange"] }], valueCommit: [{ type: i0.Output, args: ["valueCommit"] }], trackEl: [{ type: i0.ViewChild, args: ['track', { isSignal: true }] }] } });
4705
+
4254
4706
  /** Directive to mark a custom chip template */
4255
4707
  class ChipTemplateDirective {
4256
4708
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.1", ngImport: i0, type: ChipTemplateDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
@@ -6391,6 +6843,435 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.1", ngImpor
6391
6843
  args: ['document:click', ['$event']]
6392
6844
  }] } });
6393
6845
 
6846
+ class SegmentedComponent {
6847
+ options = input([], ...(ngDevMode ? [{ debugName: "options" }] : []));
6848
+ size = input('md', ...(ngDevMode ? [{ debugName: "size" }] : []));
6849
+ fullWidth = input(false, ...(ngDevMode ? [{ debugName: "fullWidth" }] : []));
6850
+ disabled = input(false, ...(ngDevMode ? [{ debugName: "disabled" }] : []));
6851
+ ariaLabel = input('', ...(ngDevMode ? [{ debugName: "ariaLabel" }] : []));
6852
+ value = model(...(ngDevMode ? [undefined, { debugName: "value" }] : []));
6853
+ track = viewChild.required('track');
6854
+ segmentedClasses = computed(() => {
6855
+ const classes = [`ui-segmented--${this.size()}`];
6856
+ if (this.fullWidth()) {
6857
+ classes.push('ui-segmented--full-width');
6858
+ }
6859
+ if (this.disabled()) {
6860
+ classes.push('ui-segmented--disabled');
6861
+ }
6862
+ return classes.join(' ');
6863
+ }, ...(ngDevMode ? [{ debugName: "segmentedClasses" }] : []));
6864
+ selectedIndex = computed(() => {
6865
+ return this.options().findIndex(option => option.value === this.value());
6866
+ }, ...(ngDevMode ? [{ debugName: "selectedIndex" }] : []));
6867
+ isSegmentDisabled(option) {
6868
+ return this.disabled() || !!option.disabled;
6869
+ }
6870
+ isTabbable(index) {
6871
+ const selected = this.selectedIndex();
6872
+ if (selected >= 0)
6873
+ return index === selected;
6874
+ // No selection yet: first enabled segment holds the tab stop
6875
+ const firstEnabled = this.options().findIndex(option => !this.isSegmentDisabled(option));
6876
+ return index === firstEnabled;
6877
+ }
6878
+ selectSegment(option) {
6879
+ if (this.isSegmentDisabled(option))
6880
+ return;
6881
+ this.value.set(option.value);
6882
+ }
6883
+ handleKeyDown(event, currentIndex) {
6884
+ const enabledSegments = this.options()
6885
+ .map((option, index) => ({ option, index }))
6886
+ .filter(({ option }) => !this.isSegmentDisabled(option));
6887
+ if (enabledSegments.length === 0)
6888
+ return;
6889
+ const currentEnabledIndex = enabledSegments.findIndex(({ index }) => index === currentIndex);
6890
+ let nextEnabledIndex = -1;
6891
+ switch (event.key) {
6892
+ case 'ArrowRight':
6893
+ case 'ArrowDown':
6894
+ event.preventDefault();
6895
+ nextEnabledIndex = (currentEnabledIndex + 1) % enabledSegments.length;
6896
+ break;
6897
+ case 'ArrowLeft':
6898
+ case 'ArrowUp':
6899
+ event.preventDefault();
6900
+ nextEnabledIndex = (currentEnabledIndex - 1 + enabledSegments.length) % enabledSegments.length;
6901
+ break;
6902
+ case 'Home':
6903
+ event.preventDefault();
6904
+ nextEnabledIndex = 0;
6905
+ break;
6906
+ case 'End':
6907
+ event.preventDefault();
6908
+ nextEnabledIndex = enabledSegments.length - 1;
6909
+ break;
6910
+ default:
6911
+ return;
6912
+ }
6913
+ if (nextEnabledIndex >= 0) {
6914
+ const { option, index } = enabledSegments[nextEnabledIndex];
6915
+ this.selectSegment(option);
6916
+ this.focusSegment(index);
6917
+ }
6918
+ }
6919
+ focusSegment(index) {
6920
+ const buttons = this.track().nativeElement.querySelectorAll('.ui-segmented__segment');
6921
+ buttons[index]?.focus();
6922
+ }
6923
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.1", ngImport: i0, type: SegmentedComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
6924
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.1", type: SegmentedComponent, isStandalone: true, selector: "ui-segmented", inputs: { options: { classPropertyName: "options", publicName: "options", isSignal: true, isRequired: false, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null }, fullWidth: { classPropertyName: "fullWidth", publicName: "fullWidth", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: false, transformFunction: null }, value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange" }, viewQueries: [{ propertyName: "track", first: true, predicate: ["track"], descendants: true, isSignal: true }], ngImport: i0, template: "<div\n #track\n class=\"ui-segmented\"\n [class]=\"segmentedClasses()\"\n role=\"radiogroup\"\n [attr.aria-label]=\"ariaLabel() || null\"\n [attr.aria-disabled]=\"disabled() || null\"\n>\n @for (option of options(); track option.value; let i = $index) {\n <button\n type=\"button\"\n class=\"ui-segmented__segment\"\n role=\"radio\"\n [class.ui-segmented__segment--active]=\"i === selectedIndex()\"\n [class.ui-segmented__segment--disabled]=\"isSegmentDisabled(option)\"\n [attr.aria-checked]=\"i === selectedIndex()\"\n [attr.tabindex]=\"isTabbable(i) ? 0 : -1\"\n [disabled]=\"isSegmentDisabled(option)\"\n (click)=\"selectSegment(option)\"\n (keydown)=\"handleKeyDown($event, i)\"\n >\n {{ option.label }}\n </button>\n }\n</div>\n", styles: [":host{display:inline-block}.ui-segmented{display:inline-flex;padding:3px;gap:2px;background-color:var(--ui-segmented-track-bg);border-radius:var(--ui-radius-md)}.ui-segmented__segment{flex:0 1 auto;border:none;background:transparent;color:var(--ui-text-muted);font-weight:500;white-space:nowrap;cursor:pointer;border-radius:var(--ui-radius-sm);transition:background-color var(--ui-transition-fast),color var(--ui-transition-fast)}.ui-segmented__segment:hover:not(:disabled):not(.ui-segmented__segment--active){color:var(--ui-text)}.ui-segmented__segment:focus-visible{outline:2px solid var(--ui-primary);outline-offset:2px}.ui-segmented__segment--active{background-color:var(--ui-segmented-active-bg);color:var(--ui-segmented-active-text);box-shadow:var(--ui-shadow-sm)}.ui-segmented__segment--disabled{opacity:.5;cursor:not-allowed}.ui-segmented--disabled{opacity:.7}:host:has(.ui-segmented--full-width){display:block}.ui-segmented--full-width{display:flex;width:100%}.ui-segmented--full-width .ui-segmented__segment{flex:1 1 0}.ui-segmented--sm .ui-segmented__segment{padding:.25rem .75rem;font-size:var(--ui-font-sm)}.ui-segmented--md .ui-segmented__segment{padding:.375rem 1rem;font-size:var(--ui-font-sm)}.ui-segmented--lg .ui-segmented__segment{padding:.5rem 1.25rem;font-size:var(--ui-font-md)}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush });
6925
+ }
6926
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.1", ngImport: i0, type: SegmentedComponent, decorators: [{
6927
+ type: Component,
6928
+ args: [{ selector: 'ui-segmented', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, template: "<div\n #track\n class=\"ui-segmented\"\n [class]=\"segmentedClasses()\"\n role=\"radiogroup\"\n [attr.aria-label]=\"ariaLabel() || null\"\n [attr.aria-disabled]=\"disabled() || null\"\n>\n @for (option of options(); track option.value; let i = $index) {\n <button\n type=\"button\"\n class=\"ui-segmented__segment\"\n role=\"radio\"\n [class.ui-segmented__segment--active]=\"i === selectedIndex()\"\n [class.ui-segmented__segment--disabled]=\"isSegmentDisabled(option)\"\n [attr.aria-checked]=\"i === selectedIndex()\"\n [attr.tabindex]=\"isTabbable(i) ? 0 : -1\"\n [disabled]=\"isSegmentDisabled(option)\"\n (click)=\"selectSegment(option)\"\n (keydown)=\"handleKeyDown($event, i)\"\n >\n {{ option.label }}\n </button>\n }\n</div>\n", styles: [":host{display:inline-block}.ui-segmented{display:inline-flex;padding:3px;gap:2px;background-color:var(--ui-segmented-track-bg);border-radius:var(--ui-radius-md)}.ui-segmented__segment{flex:0 1 auto;border:none;background:transparent;color:var(--ui-text-muted);font-weight:500;white-space:nowrap;cursor:pointer;border-radius:var(--ui-radius-sm);transition:background-color var(--ui-transition-fast),color var(--ui-transition-fast)}.ui-segmented__segment:hover:not(:disabled):not(.ui-segmented__segment--active){color:var(--ui-text)}.ui-segmented__segment:focus-visible{outline:2px solid var(--ui-primary);outline-offset:2px}.ui-segmented__segment--active{background-color:var(--ui-segmented-active-bg);color:var(--ui-segmented-active-text);box-shadow:var(--ui-shadow-sm)}.ui-segmented__segment--disabled{opacity:.5;cursor:not-allowed}.ui-segmented--disabled{opacity:.7}:host:has(.ui-segmented--full-width){display:block}.ui-segmented--full-width{display:flex;width:100%}.ui-segmented--full-width .ui-segmented__segment{flex:1 1 0}.ui-segmented--sm .ui-segmented__segment{padding:.25rem .75rem;font-size:var(--ui-font-sm)}.ui-segmented--md .ui-segmented__segment{padding:.375rem 1rem;font-size:var(--ui-font-sm)}.ui-segmented--lg .ui-segmented__segment{padding:.5rem 1.25rem;font-size:var(--ui-font-md)}\n"] }]
6929
+ }], propDecorators: { options: [{ type: i0.Input, args: [{ isSignal: true, alias: "options", required: false }] }], size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }], fullWidth: [{ type: i0.Input, args: [{ isSignal: true, alias: "fullWidth", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabel", required: false }] }], value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }, { type: i0.Output, args: ["valueChange"] }], track: [{ type: i0.ViewChild, args: ['track', { isSignal: true }] }] } });
6930
+
6931
+ class StepperComponent {
6932
+ steps = input.required(...(ngDevMode ? [{ debugName: "steps" }] : []));
6933
+ linear = input(true, ...(ngDevMode ? [{ debugName: "linear" }] : []));
6934
+ ariaLabel = input('Progress', ...(ngDevMode ? [{ debugName: "ariaLabel" }] : []));
6935
+ activeIndex = model(0, ...(ngDevMode ? [{ debugName: "activeIndex" }] : []));
6936
+ stepClick = output();
6937
+ stepStates = computed(() => {
6938
+ const active = this.activeIndex();
6939
+ return this.steps().map((step, index) => ({
6940
+ step,
6941
+ index,
6942
+ completed: index < active,
6943
+ active: index === active,
6944
+ clickable: this.linear() ? index < active : index !== active,
6945
+ }));
6946
+ }, ...(ngDevMode ? [{ debugName: "stepStates" }] : []));
6947
+ selectStep(index, clickable) {
6948
+ if (!clickable)
6949
+ return;
6950
+ this.activeIndex.set(index);
6951
+ this.stepClick.emit(index);
6952
+ }
6953
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.1", ngImport: i0, type: StepperComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
6954
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.1", type: StepperComponent, isStandalone: true, selector: "ui-stepper", inputs: { steps: { classPropertyName: "steps", publicName: "steps", isSignal: true, isRequired: true, transformFunction: null }, linear: { classPropertyName: "linear", publicName: "linear", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: false, transformFunction: null }, activeIndex: { classPropertyName: "activeIndex", publicName: "activeIndex", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { activeIndex: "activeIndexChange", stepClick: "stepClick" }, ngImport: i0, template: "<ol class=\"ui-stepper\" [attr.aria-label]=\"ariaLabel()\">\n @for (state of stepStates(); track state.step.key) {\n <li\n class=\"ui-stepper__step\"\n [class.ui-stepper__step--completed]=\"state.completed\"\n [class.ui-stepper__step--active]=\"state.active\"\n [attr.aria-current]=\"state.active ? 'step' : null\"\n >\n @if (!$first) {\n <span class=\"ui-stepper__connector\" aria-hidden=\"true\"></span>\n }\n @if (state.clickable) {\n <button\n type=\"button\"\n class=\"ui-stepper__button\"\n (click)=\"selectStep(state.index, true)\"\n >\n <ng-container\n [ngTemplateOutlet]=\"stepContent\"\n [ngTemplateOutletContext]=\"{ $implicit: state }\"\n />\n </button>\n } @else {\n <span class=\"ui-stepper__button ui-stepper__button--static\">\n <ng-container\n [ngTemplateOutlet]=\"stepContent\"\n [ngTemplateOutletContext]=\"{ $implicit: state }\"\n />\n </span>\n }\n </li>\n }\n</ol>\n\n<ng-template #stepContent let-state>\n <span class=\"ui-stepper__circle\" aria-hidden=\"true\">\n @if (state.completed) {\n <svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"3\">\n <path d=\"M20 6L9 17l-5-5\"/>\n </svg>\n } @else {\n {{ state.index + 1 }}\n }\n </span>\n <span class=\"ui-stepper__label\">{{ state.step.label }}</span>\n</ng-template>\n", styles: [":host{display:block}.ui-stepper{display:flex;margin:0;padding:0;list-style:none}.ui-stepper__step{display:flex;align-items:center;flex:0 1 auto;min-width:0}.ui-stepper__step:not(:first-child){flex:1 1 0}.ui-stepper__connector{flex:1 1 0;height:2px;min-width:var(--ui-spacing-md);margin:0 var(--ui-spacing-sm);background-color:var(--ui-stepper-connector);transition:background-color var(--ui-transition-normal)}.ui-stepper__step--completed .ui-stepper__connector,.ui-stepper__step--active .ui-stepper__connector{background-color:var(--ui-stepper-completed)}.ui-stepper__button{display:inline-flex;align-items:center;gap:var(--ui-spacing-sm);padding:var(--ui-spacing-xs);border:none;background:transparent;font:inherit;color:var(--ui-text-muted);border-radius:var(--ui-radius-md);white-space:nowrap;min-width:0}button.ui-stepper__button{cursor:pointer;transition:color var(--ui-transition-fast)}button.ui-stepper__button:hover{color:var(--ui-text)}button.ui-stepper__button:focus-visible{outline:2px solid var(--ui-primary);outline-offset:2px}.ui-stepper__circle{display:inline-flex;align-items:center;justify-content:center;flex-shrink:0;width:var(--ui-stepper-circle-size);height:var(--ui-stepper-circle-size);border:2px solid var(--ui-stepper-connector);border-radius:50%;font-size:var(--ui-font-sm);font-weight:600;color:var(--ui-text-muted);background-color:var(--ui-bg);transition:all var(--ui-transition-normal)}.ui-stepper__label{font-size:var(--ui-font-sm);font-weight:500;overflow:hidden;text-overflow:ellipsis}.ui-stepper__step--completed .ui-stepper__circle{border-color:var(--ui-stepper-completed);background-color:var(--ui-stepper-completed);color:var(--ui-stepper-completed-text)}.ui-stepper__step--completed .ui-stepper__label{color:var(--ui-text)}.ui-stepper__step--active .ui-stepper__circle{border-color:var(--ui-primary);color:var(--ui-primary)}.ui-stepper__step--active .ui-stepper__label{color:var(--ui-text);font-weight:600}@media(max-width:768px){.ui-stepper__step:not(.ui-stepper__step--active) .ui-stepper__label{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}}\n"], dependencies: [{ kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
6955
+ }
6956
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.1", ngImport: i0, type: StepperComponent, decorators: [{
6957
+ type: Component,
6958
+ args: [{ selector: 'ui-stepper', standalone: true, imports: [NgTemplateOutlet], changeDetection: ChangeDetectionStrategy.OnPush, template: "<ol class=\"ui-stepper\" [attr.aria-label]=\"ariaLabel()\">\n @for (state of stepStates(); track state.step.key) {\n <li\n class=\"ui-stepper__step\"\n [class.ui-stepper__step--completed]=\"state.completed\"\n [class.ui-stepper__step--active]=\"state.active\"\n [attr.aria-current]=\"state.active ? 'step' : null\"\n >\n @if (!$first) {\n <span class=\"ui-stepper__connector\" aria-hidden=\"true\"></span>\n }\n @if (state.clickable) {\n <button\n type=\"button\"\n class=\"ui-stepper__button\"\n (click)=\"selectStep(state.index, true)\"\n >\n <ng-container\n [ngTemplateOutlet]=\"stepContent\"\n [ngTemplateOutletContext]=\"{ $implicit: state }\"\n />\n </button>\n } @else {\n <span class=\"ui-stepper__button ui-stepper__button--static\">\n <ng-container\n [ngTemplateOutlet]=\"stepContent\"\n [ngTemplateOutletContext]=\"{ $implicit: state }\"\n />\n </span>\n }\n </li>\n }\n</ol>\n\n<ng-template #stepContent let-state>\n <span class=\"ui-stepper__circle\" aria-hidden=\"true\">\n @if (state.completed) {\n <svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"3\">\n <path d=\"M20 6L9 17l-5-5\"/>\n </svg>\n } @else {\n {{ state.index + 1 }}\n }\n </span>\n <span class=\"ui-stepper__label\">{{ state.step.label }}</span>\n</ng-template>\n", styles: [":host{display:block}.ui-stepper{display:flex;margin:0;padding:0;list-style:none}.ui-stepper__step{display:flex;align-items:center;flex:0 1 auto;min-width:0}.ui-stepper__step:not(:first-child){flex:1 1 0}.ui-stepper__connector{flex:1 1 0;height:2px;min-width:var(--ui-spacing-md);margin:0 var(--ui-spacing-sm);background-color:var(--ui-stepper-connector);transition:background-color var(--ui-transition-normal)}.ui-stepper__step--completed .ui-stepper__connector,.ui-stepper__step--active .ui-stepper__connector{background-color:var(--ui-stepper-completed)}.ui-stepper__button{display:inline-flex;align-items:center;gap:var(--ui-spacing-sm);padding:var(--ui-spacing-xs);border:none;background:transparent;font:inherit;color:var(--ui-text-muted);border-radius:var(--ui-radius-md);white-space:nowrap;min-width:0}button.ui-stepper__button{cursor:pointer;transition:color var(--ui-transition-fast)}button.ui-stepper__button:hover{color:var(--ui-text)}button.ui-stepper__button:focus-visible{outline:2px solid var(--ui-primary);outline-offset:2px}.ui-stepper__circle{display:inline-flex;align-items:center;justify-content:center;flex-shrink:0;width:var(--ui-stepper-circle-size);height:var(--ui-stepper-circle-size);border:2px solid var(--ui-stepper-connector);border-radius:50%;font-size:var(--ui-font-sm);font-weight:600;color:var(--ui-text-muted);background-color:var(--ui-bg);transition:all var(--ui-transition-normal)}.ui-stepper__label{font-size:var(--ui-font-sm);font-weight:500;overflow:hidden;text-overflow:ellipsis}.ui-stepper__step--completed .ui-stepper__circle{border-color:var(--ui-stepper-completed);background-color:var(--ui-stepper-completed);color:var(--ui-stepper-completed-text)}.ui-stepper__step--completed .ui-stepper__label{color:var(--ui-text)}.ui-stepper__step--active .ui-stepper__circle{border-color:var(--ui-primary);color:var(--ui-primary)}.ui-stepper__step--active .ui-stepper__label{color:var(--ui-text);font-weight:600}@media(max-width:768px){.ui-stepper__step:not(.ui-stepper__step--active) .ui-stepper__label{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}}\n"] }]
6959
+ }], propDecorators: { steps: [{ type: i0.Input, args: [{ isSignal: true, alias: "steps", required: true }] }], linear: [{ type: i0.Input, args: [{ isSignal: true, alias: "linear", required: false }] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabel", required: false }] }], activeIndex: [{ type: i0.Input, args: [{ isSignal: true, alias: "activeIndex", required: false }] }, { type: i0.Output, args: ["activeIndexChange"] }], stepClick: [{ type: i0.Output, args: ["stepClick"] }] } });
6960
+
6961
+ class BreadcrumbSeparatorDirective {
6962
+ templateRef = inject((TemplateRef));
6963
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.1", ngImport: i0, type: BreadcrumbSeparatorDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
6964
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.1.1", type: BreadcrumbSeparatorDirective, isStandalone: true, selector: "[uiBreadcrumbSeparator]", ngImport: i0 });
6965
+ }
6966
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.1", ngImport: i0, type: BreadcrumbSeparatorDirective, decorators: [{
6967
+ type: Directive,
6968
+ args: [{
6969
+ selector: '[uiBreadcrumbSeparator]',
6970
+ standalone: true,
6971
+ }]
6972
+ }] });
6973
+
6974
+ class BreadcrumbComponent {
6975
+ items = input.required(...(ngDevMode ? [{ debugName: "items" }] : []));
6976
+ separator = input('/', ...(ngDevMode ? [{ debugName: "separator" }] : []));
6977
+ separatorTemplate = contentChild(BreadcrumbSeparatorDirective, ...(ngDevMode ? [{ debugName: "separatorTemplate" }] : []));
6978
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.1", ngImport: i0, type: BreadcrumbComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
6979
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.1", type: BreadcrumbComponent, isStandalone: true, selector: "ui-breadcrumb", inputs: { items: { classPropertyName: "items", publicName: "items", isSignal: true, isRequired: true, transformFunction: null }, separator: { classPropertyName: "separator", publicName: "separator", isSignal: true, isRequired: false, transformFunction: null } }, queries: [{ propertyName: "separatorTemplate", first: true, predicate: BreadcrumbSeparatorDirective, descendants: true, isSignal: true }], ngImport: i0, template: "<nav class=\"ui-breadcrumb\" aria-label=\"Breadcrumb\">\n <ol class=\"ui-breadcrumb__list\">\n @for (item of items(); track $index) {\n <li class=\"ui-breadcrumb__item\">\n @if (!$first) {\n <span class=\"ui-breadcrumb__separator\" aria-hidden=\"true\">\n @if (separatorTemplate(); as tpl) {\n <ng-container *ngTemplateOutlet=\"tpl.templateRef\" />\n } @else {\n {{ separator() }}\n }\n </span>\n }\n @if (item.url && !$last) {\n <a class=\"ui-breadcrumb__link\" [routerLink]=\"item.url\">{{ item.label }}</a>\n } @else {\n <span\n class=\"ui-breadcrumb__current\"\n [attr.aria-current]=\"$last ? 'page' : null\"\n >{{ item.label }}</span>\n }\n </li>\n }\n </ol>\n</nav>\n", styles: [":host{display:block}.ui-breadcrumb__list{display:flex;align-items:center;flex-wrap:wrap;margin:0;padding:0;list-style:none}.ui-breadcrumb__item{display:flex;align-items:center;min-width:0;font-size:var(--ui-font-sm)}.ui-breadcrumb__separator{display:inline-flex;align-items:center;margin:0 var(--ui-spacing-sm);color:var(--ui-text-muted);-webkit-user-select:none;user-select:none}.ui-breadcrumb__link,.ui-breadcrumb__current{max-width:16rem;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.ui-breadcrumb__link{color:var(--ui-text-muted);text-decoration:none;border-radius:var(--ui-radius-sm);transition:color var(--ui-transition-fast)}.ui-breadcrumb__link:hover{color:var(--ui-text);text-decoration:underline}.ui-breadcrumb__link:focus-visible{outline:2px solid var(--ui-primary);outline-offset:2px}.ui-breadcrumb__current{color:var(--ui-text);font-weight:500}\n"], dependencies: [{ kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "directive", type: RouterLink, selector: "[routerLink]", inputs: ["target", "queryParams", "fragment", "queryParamsHandling", "state", "info", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "routerLink"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
6980
+ }
6981
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.1", ngImport: i0, type: BreadcrumbComponent, decorators: [{
6982
+ type: Component,
6983
+ args: [{ selector: 'ui-breadcrumb', standalone: true, imports: [NgTemplateOutlet, RouterLink], changeDetection: ChangeDetectionStrategy.OnPush, template: "<nav class=\"ui-breadcrumb\" aria-label=\"Breadcrumb\">\n <ol class=\"ui-breadcrumb__list\">\n @for (item of items(); track $index) {\n <li class=\"ui-breadcrumb__item\">\n @if (!$first) {\n <span class=\"ui-breadcrumb__separator\" aria-hidden=\"true\">\n @if (separatorTemplate(); as tpl) {\n <ng-container *ngTemplateOutlet=\"tpl.templateRef\" />\n } @else {\n {{ separator() }}\n }\n </span>\n }\n @if (item.url && !$last) {\n <a class=\"ui-breadcrumb__link\" [routerLink]=\"item.url\">{{ item.label }}</a>\n } @else {\n <span\n class=\"ui-breadcrumb__current\"\n [attr.aria-current]=\"$last ? 'page' : null\"\n >{{ item.label }}</span>\n }\n </li>\n }\n </ol>\n</nav>\n", styles: [":host{display:block}.ui-breadcrumb__list{display:flex;align-items:center;flex-wrap:wrap;margin:0;padding:0;list-style:none}.ui-breadcrumb__item{display:flex;align-items:center;min-width:0;font-size:var(--ui-font-sm)}.ui-breadcrumb__separator{display:inline-flex;align-items:center;margin:0 var(--ui-spacing-sm);color:var(--ui-text-muted);-webkit-user-select:none;user-select:none}.ui-breadcrumb__link,.ui-breadcrumb__current{max-width:16rem;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.ui-breadcrumb__link{color:var(--ui-text-muted);text-decoration:none;border-radius:var(--ui-radius-sm);transition:color var(--ui-transition-fast)}.ui-breadcrumb__link:hover{color:var(--ui-text);text-decoration:underline}.ui-breadcrumb__link:focus-visible{outline:2px solid var(--ui-primary);outline-offset:2px}.ui-breadcrumb__current{color:var(--ui-text);font-weight:500}\n"] }]
6984
+ }], propDecorators: { items: [{ type: i0.Input, args: [{ isSignal: true, alias: "items", required: true }] }], separator: [{ type: i0.Input, args: [{ isSignal: true, alias: "separator", required: false }] }], separatorTemplate: [{ type: i0.ContentChild, args: [i0.forwardRef(() => BreadcrumbSeparatorDirective), { isSignal: true }] }] } });
6985
+
6986
+ class SkeletonComponent {
6987
+ variant = input('line', ...(ngDevMode ? [{ debugName: "variant" }] : []));
6988
+ width = input(...(ngDevMode ? [undefined, { debugName: "width" }] : []));
6989
+ height = input(...(ngDevMode ? [undefined, { debugName: "height" }] : []));
6990
+ count = input(1, ...(ngDevMode ? [{ debugName: "count" }] : []));
6991
+ items = computed(() => {
6992
+ return Array.from({ length: Math.max(1, this.count()) }, (_, i) => i);
6993
+ }, ...(ngDevMode ? [{ debugName: "items" }] : []));
6994
+ itemWidth(index) {
6995
+ // Last line of a multi-line paragraph placeholder is shortened
6996
+ if (this.variant() === 'line' && this.count() > 1 && index === this.count() - 1) {
6997
+ return '60%';
6998
+ }
6999
+ return this.width() ?? null;
7000
+ }
7001
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.1", ngImport: i0, type: SkeletonComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
7002
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.1", type: SkeletonComponent, isStandalone: true, selector: "ui-skeleton", inputs: { variant: { classPropertyName: "variant", publicName: "variant", isSignal: true, isRequired: false, transformFunction: null }, width: { classPropertyName: "width", publicName: "width", isSignal: true, isRequired: false, transformFunction: null }, height: { classPropertyName: "height", publicName: "height", isSignal: true, isRequired: false, transformFunction: null }, count: { classPropertyName: "count", publicName: "count", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: "<div class=\"ui-skeleton\" aria-hidden=\"true\">\n @for (item of items(); track item) {\n <span\n class=\"ui-skeleton__item\"\n [class]=\"'ui-skeleton__item--' + variant()\"\n [style.width]=\"itemWidth(item)\"\n [style.height]=\"height() ?? null\"\n ></span>\n }\n</div>\n", styles: [":host{display:block}.ui-skeleton{display:flex;flex-direction:column;gap:var(--ui-spacing-sm)}.ui-skeleton__item{display:block;background:linear-gradient(90deg,var(--ui-skeleton-base) 25%,var(--ui-skeleton-highlight) 37%,var(--ui-skeleton-base) 63%);background-size:400% 100%;animation:ui-skeleton-shimmer 1.4s ease infinite}.ui-skeleton__item--line{width:100%;height:.875rem;border-radius:var(--ui-radius-sm)}.ui-skeleton__item--box{width:100%;height:6rem;border-radius:var(--ui-radius-md)}.ui-skeleton__item--circle{width:3rem;height:3rem;border-radius:50%}@keyframes ui-skeleton-shimmer{0%{background-position:100% 50%}to{background-position:0 50%}}@media(prefers-reduced-motion:reduce){.ui-skeleton__item{background:var(--ui-skeleton-base);animation:ui-skeleton-pulse 2s ease-in-out infinite}@keyframes ui-skeleton-pulse{0%,to{opacity:1}50%{opacity:.6}}}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush });
7003
+ }
7004
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.1", ngImport: i0, type: SkeletonComponent, decorators: [{
7005
+ type: Component,
7006
+ args: [{ selector: 'ui-skeleton', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"ui-skeleton\" aria-hidden=\"true\">\n @for (item of items(); track item) {\n <span\n class=\"ui-skeleton__item\"\n [class]=\"'ui-skeleton__item--' + variant()\"\n [style.width]=\"itemWidth(item)\"\n [style.height]=\"height() ?? null\"\n ></span>\n }\n</div>\n", styles: [":host{display:block}.ui-skeleton{display:flex;flex-direction:column;gap:var(--ui-spacing-sm)}.ui-skeleton__item{display:block;background:linear-gradient(90deg,var(--ui-skeleton-base) 25%,var(--ui-skeleton-highlight) 37%,var(--ui-skeleton-base) 63%);background-size:400% 100%;animation:ui-skeleton-shimmer 1.4s ease infinite}.ui-skeleton__item--line{width:100%;height:.875rem;border-radius:var(--ui-radius-sm)}.ui-skeleton__item--box{width:100%;height:6rem;border-radius:var(--ui-radius-md)}.ui-skeleton__item--circle{width:3rem;height:3rem;border-radius:50%}@keyframes ui-skeleton-shimmer{0%{background-position:100% 50%}to{background-position:0 50%}}@media(prefers-reduced-motion:reduce){.ui-skeleton__item{background:var(--ui-skeleton-base);animation:ui-skeleton-pulse 2s ease-in-out infinite}@keyframes ui-skeleton-pulse{0%,to{opacity:1}50%{opacity:.6}}}\n"] }]
7007
+ }], propDecorators: { variant: [{ type: i0.Input, args: [{ isSignal: true, alias: "variant", required: false }] }], width: [{ type: i0.Input, args: [{ isSignal: true, alias: "width", required: false }] }], height: [{ type: i0.Input, args: [{ isSignal: true, alias: "height", required: false }] }], count: [{ type: i0.Input, args: [{ isSignal: true, alias: "count", required: false }] }] } });
7008
+
7009
+ class ImageUploaderComponent {
7010
+ maxCount = input(30, ...(ngDevMode ? [{ debugName: "maxCount" }] : []));
7011
+ accept = input('image/*', ...(ngDevMode ? [{ debugName: "accept" }] : []));
7012
+ maxFileSizeMb = input(...(ngDevMode ? [undefined, { debugName: "maxFileSizeMb" }] : []));
7013
+ disabled = input(false, ...(ngDevMode ? [{ debugName: "disabled" }] : []));
7014
+ coverLabel = input('Cover', ...(ngDevMode ? [{ debugName: "coverLabel" }] : []));
7015
+ addLabel = input('Add images', ...(ngDevMode ? [{ debugName: "addLabel" }] : []));
7016
+ items = model([], ...(ngDevMode ? [{ debugName: "items" }] : []));
7017
+ filesAdded = output();
7018
+ filesRejected = output();
7019
+ removed = output();
7020
+ reordered = output();
7021
+ fileInput = viewChild.required('fileInput');
7022
+ grid = viewChild.required('grid');
7023
+ dragOver = signal(false, ...(ngDevMode ? [{ debugName: "dragOver" }] : []));
7024
+ /** Id of the tile currently grabbed for keyboard reordering */
7025
+ grabbedId = signal(null, ...(ngDevMode ? [{ debugName: "grabbedId" }] : []));
7026
+ /** Announcement for screen readers after a keyboard move */
7027
+ liveMessage = signal('', ...(ngDevMode ? [{ debugName: "liveMessage" }] : []));
7028
+ /** Id of the tile being pointer-dragged */
7029
+ dragId = signal(null, ...(ngDevMode ? [{ debugName: "dragId" }] : []));
7030
+ ghostX = signal(0, ...(ngDevMode ? [{ debugName: "ghostX" }] : []));
7031
+ ghostY = signal(0, ...(ngDevMode ? [{ debugName: "ghostY" }] : []));
7032
+ ghostSize = signal(0, ...(ngDevMode ? [{ debugName: "ghostSize" }] : []));
7033
+ pointerId = null;
7034
+ pointerDownX = 0;
7035
+ pointerDownY = 0;
7036
+ pointerDownId = null;
7037
+ dragStarted = false;
7038
+ canAdd = computed(() => {
7039
+ return !this.disabled() && this.items().length < this.maxCount();
7040
+ }, ...(ngDevMode ? [{ debugName: "canAdd" }] : []));
7041
+ dragItem = computed(() => {
7042
+ const id = this.dragId();
7043
+ return id === null ? null : this.items().find(item => item.id === id) ?? null;
7044
+ }, ...(ngDevMode ? [{ debugName: "dragItem" }] : []));
7045
+ tileLabel(item, index) {
7046
+ const position = `Image ${index + 1} of ${this.items().length}`;
7047
+ const cover = index === 0 ? ', cover image' : '';
7048
+ const error = item.error ? `, error: ${item.error}` : '';
7049
+ return `${position}${cover}${error}`;
7050
+ }
7051
+ // --- Adding files ---
7052
+ openFilePicker() {
7053
+ if (this.canAdd()) {
7054
+ this.fileInput().nativeElement.click();
7055
+ }
7056
+ }
7057
+ onFileSelected(event) {
7058
+ const input = event.target;
7059
+ if (input.files && input.files.length > 0) {
7060
+ this.processFiles(Array.from(input.files));
7061
+ input.value = '';
7062
+ }
7063
+ }
7064
+ onDragOver(event) {
7065
+ event.preventDefault();
7066
+ event.stopPropagation();
7067
+ if (this.canAdd()) {
7068
+ this.dragOver.set(true);
7069
+ }
7070
+ }
7071
+ onDragLeave(event) {
7072
+ event.preventDefault();
7073
+ event.stopPropagation();
7074
+ this.dragOver.set(false);
7075
+ }
7076
+ onDrop(event) {
7077
+ event.preventDefault();
7078
+ event.stopPropagation();
7079
+ this.dragOver.set(false);
7080
+ if (!this.canAdd())
7081
+ return;
7082
+ const files = event.dataTransfer?.files;
7083
+ if (files && files.length > 0) {
7084
+ this.processFiles(Array.from(files));
7085
+ }
7086
+ }
7087
+ processFiles(files) {
7088
+ const accepted = [];
7089
+ const rejected = [];
7090
+ const remaining = this.maxCount() - this.items().length;
7091
+ const maxBytes = this.maxFileSizeMb() != null ? this.maxFileSizeMb() * 1024 * 1024 : null;
7092
+ for (const file of files) {
7093
+ if (accepted.length >= remaining) {
7094
+ rejected.push({ file, reason: `Maximum ${this.maxCount()} image(s) allowed` });
7095
+ continue;
7096
+ }
7097
+ if (!this.isFileTypeAccepted(file)) {
7098
+ rejected.push({ file, reason: 'File type not accepted' });
7099
+ continue;
7100
+ }
7101
+ if (maxBytes !== null && file.size > maxBytes) {
7102
+ rejected.push({ file, reason: `File exceeds maximum size of ${this.maxFileSizeMb()} MB` });
7103
+ continue;
7104
+ }
7105
+ accepted.push(file);
7106
+ }
7107
+ if (accepted.length > 0) {
7108
+ this.filesAdded.emit(accepted);
7109
+ }
7110
+ if (rejected.length > 0) {
7111
+ this.filesRejected.emit(rejected);
7112
+ }
7113
+ }
7114
+ isFileTypeAccepted(file) {
7115
+ const accept = this.accept();
7116
+ if (!accept)
7117
+ return true;
7118
+ const acceptedTypes = accept.split(',').map(t => t.trim().toLowerCase());
7119
+ for (const type of acceptedTypes) {
7120
+ if (type === file.type.toLowerCase())
7121
+ return true;
7122
+ if (type.endsWith('/*')) {
7123
+ const category = type.slice(0, -2);
7124
+ if (file.type.toLowerCase().startsWith(category + '/'))
7125
+ return true;
7126
+ }
7127
+ if (type.startsWith('.')) {
7128
+ const ext = file.name.toLowerCase().slice(file.name.lastIndexOf('.'));
7129
+ if (ext === type)
7130
+ return true;
7131
+ }
7132
+ }
7133
+ return false;
7134
+ }
7135
+ // --- Removing ---
7136
+ removeItem(event, id) {
7137
+ event.stopPropagation();
7138
+ if (this.disabled())
7139
+ return;
7140
+ this.items.update(items => items.filter(item => item.id !== id));
7141
+ this.removed.emit(id);
7142
+ }
7143
+ // --- Pointer drag to reorder ---
7144
+ onTilePointerDown(event, id) {
7145
+ if (this.disabled())
7146
+ return;
7147
+ // Ignore drags starting on the remove button
7148
+ if (event.target.closest('.ui-image-uploader__remove'))
7149
+ return;
7150
+ this.pointerId = event.pointerId;
7151
+ this.pointerDownX = event.clientX;
7152
+ this.pointerDownY = event.clientY;
7153
+ this.pointerDownId = id;
7154
+ this.dragStarted = false;
7155
+ event.currentTarget.setPointerCapture?.(event.pointerId);
7156
+ }
7157
+ onTilePointerMove(event) {
7158
+ if (this.pointerId !== event.pointerId || this.pointerDownId === null)
7159
+ return;
7160
+ if (!this.dragStarted) {
7161
+ const distance = Math.hypot(event.clientX - this.pointerDownX, event.clientY - this.pointerDownY);
7162
+ if (distance < 6)
7163
+ return;
7164
+ this.dragStarted = true;
7165
+ this.dragId.set(this.pointerDownId);
7166
+ const tile = this.findTileElement(this.pointerDownId);
7167
+ this.ghostSize.set(tile?.offsetWidth ?? 0);
7168
+ }
7169
+ this.ghostX.set(event.clientX);
7170
+ this.ghostY.set(event.clientY);
7171
+ const targetIndex = this.tileIndexAtPoint(event.clientX, event.clientY);
7172
+ if (targetIndex === null)
7173
+ return;
7174
+ const currentIndex = this.items().findIndex(item => item.id === this.dragId());
7175
+ if (currentIndex >= 0 && targetIndex !== currentIndex) {
7176
+ this.moveItem(currentIndex, targetIndex, false);
7177
+ }
7178
+ }
7179
+ onTilePointerUp(event) {
7180
+ if (this.pointerId !== event.pointerId)
7181
+ return;
7182
+ if (this.dragStarted) {
7183
+ this.reordered.emit(this.items().map(item => item.id));
7184
+ }
7185
+ this.pointerId = null;
7186
+ this.pointerDownId = null;
7187
+ this.dragStarted = false;
7188
+ this.dragId.set(null);
7189
+ }
7190
+ findTileElement(id) {
7191
+ return this.grid().nativeElement.querySelector(`.ui-image-uploader__tile[data-id="${CSS.escape(id)}"]`);
7192
+ }
7193
+ tileIndexAtPoint(x, y) {
7194
+ const tiles = this.grid().nativeElement.querySelectorAll('.ui-image-uploader__tile');
7195
+ for (let i = 0; i < tiles.length; i++) {
7196
+ const rect = tiles[i].getBoundingClientRect();
7197
+ if (x >= rect.left && x <= rect.right && y >= rect.top && y <= rect.bottom) {
7198
+ return i;
7199
+ }
7200
+ }
7201
+ return null;
7202
+ }
7203
+ // --- Keyboard reorder ---
7204
+ onTileKeyDown(event, id, index) {
7205
+ if (this.disabled())
7206
+ return;
7207
+ switch (event.key) {
7208
+ case ' ':
7209
+ case 'Enter':
7210
+ event.preventDefault();
7211
+ if (this.grabbedId() === id) {
7212
+ this.grabbedId.set(null);
7213
+ this.liveMessage.set(`Image dropped at position ${index + 1}`);
7214
+ }
7215
+ else {
7216
+ this.grabbedId.set(id);
7217
+ this.liveMessage.set(`Image ${index + 1} grabbed. Use arrow keys to move, Space to drop, Escape to cancel.`);
7218
+ }
7219
+ break;
7220
+ case 'Escape':
7221
+ if (this.grabbedId() === id) {
7222
+ event.preventDefault();
7223
+ this.grabbedId.set(null);
7224
+ this.liveMessage.set('Move cancelled');
7225
+ }
7226
+ break;
7227
+ case 'ArrowLeft':
7228
+ case 'ArrowUp':
7229
+ if (this.grabbedId() === id) {
7230
+ event.preventDefault();
7231
+ this.moveByKeyboard(index, index - 1, id);
7232
+ }
7233
+ break;
7234
+ case 'ArrowRight':
7235
+ case 'ArrowDown':
7236
+ if (this.grabbedId() === id) {
7237
+ event.preventDefault();
7238
+ this.moveByKeyboard(index, index + 1, id);
7239
+ }
7240
+ break;
7241
+ case 'Delete':
7242
+ case 'Backspace':
7243
+ event.preventDefault();
7244
+ this.removeItem(event, id);
7245
+ break;
7246
+ }
7247
+ }
7248
+ moveByKeyboard(from, to, id) {
7249
+ if (to < 0 || to >= this.items().length)
7250
+ return;
7251
+ this.moveItem(from, to, true);
7252
+ this.liveMessage.set(`Image moved to position ${to + 1} of ${this.items().length}`);
7253
+ // Keep focus on the moved tile after the DOM reorders
7254
+ setTimeout(() => this.findTileElement(id)?.focus());
7255
+ }
7256
+ moveItem(from, to, emit) {
7257
+ this.items.update(items => {
7258
+ const next = [...items];
7259
+ const [moved] = next.splice(from, 1);
7260
+ next.splice(to, 0, moved);
7261
+ return next;
7262
+ });
7263
+ if (emit) {
7264
+ this.reordered.emit(this.items().map(item => item.id));
7265
+ }
7266
+ }
7267
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.1", ngImport: i0, type: ImageUploaderComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
7268
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.1", type: ImageUploaderComponent, isStandalone: true, selector: "ui-image-uploader", inputs: { maxCount: { classPropertyName: "maxCount", publicName: "maxCount", isSignal: true, isRequired: false, transformFunction: null }, accept: { classPropertyName: "accept", publicName: "accept", isSignal: true, isRequired: false, transformFunction: null }, maxFileSizeMb: { classPropertyName: "maxFileSizeMb", publicName: "maxFileSizeMb", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, coverLabel: { classPropertyName: "coverLabel", publicName: "coverLabel", isSignal: true, isRequired: false, transformFunction: null }, addLabel: { classPropertyName: "addLabel", publicName: "addLabel", isSignal: true, isRequired: false, transformFunction: null }, items: { classPropertyName: "items", publicName: "items", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { items: "itemsChange", filesAdded: "filesAdded", filesRejected: "filesRejected", removed: "removed", reordered: "reordered" }, viewQueries: [{ propertyName: "fileInput", first: true, predicate: ["fileInput"], descendants: true, isSignal: true }, { propertyName: "grid", first: true, predicate: ["grid"], descendants: true, isSignal: true }], ngImport: i0, template: "<ul\n #grid\n class=\"ui-image-uploader\"\n [class.ui-image-uploader--drag-over]=\"dragOver()\"\n [class.ui-image-uploader--disabled]=\"disabled()\"\n role=\"list\"\n (dragover)=\"onDragOver($event)\"\n (dragleave)=\"onDragLeave($event)\"\n (drop)=\"onDrop($event)\"\n>\n @for (item of items(); track item.id; let i = $index) {\n <li\n class=\"ui-image-uploader__tile\"\n [class.ui-image-uploader__tile--error]=\"item.error\"\n [class.ui-image-uploader__tile--grabbed]=\"grabbedId() === item.id\"\n [class.ui-image-uploader__tile--dragging]=\"dragId() === item.id\"\n [attr.data-id]=\"item.id\"\n [attr.tabindex]=\"disabled() ? null : 0\"\n [attr.aria-label]=\"tileLabel(item, i)\"\n [attr.title]=\"item.error || null\"\n (pointerdown)=\"onTilePointerDown($event, item.id)\"\n (pointermove)=\"onTilePointerMove($event)\"\n (pointerup)=\"onTilePointerUp($event)\"\n (pointercancel)=\"onTilePointerUp($event)\"\n (keydown)=\"onTileKeyDown($event, item.id, i)\"\n >\n <img class=\"ui-image-uploader__image\" [src]=\"item.previewUrl\" alt=\"\" draggable=\"false\" />\n\n @if (i === 0) {\n <span class=\"ui-image-uploader__cover-badge\">{{ coverLabel() }}</span>\n }\n\n @if (item.progress != null && item.progress < 100) {\n <span class=\"ui-image-uploader__progress\" aria-hidden=\"true\">\n <span class=\"ui-image-uploader__progress-bar\">\n <span\n class=\"ui-image-uploader__progress-fill\"\n [style.width.%]=\"item.progress\"\n ></span>\n </span>\n <span class=\"ui-image-uploader__progress-text\">{{ item.progress }}%</span>\n </span>\n }\n\n @if (item.error) {\n <span class=\"ui-image-uploader__error-icon\" aria-hidden=\"true\">\n <svg width=\"20\" height=\"20\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\">\n <circle cx=\"12\" cy=\"12\" r=\"10\"/>\n <path d=\"M12 8v4M12 16h.01\"/>\n </svg>\n </span>\n }\n\n @if (!disabled()) {\n <button\n type=\"button\"\n class=\"ui-image-uploader__remove\"\n aria-label=\"Remove image\"\n (click)=\"removeItem($event, item.id)\"\n >\n <svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\">\n <path d=\"M18 6L6 18M6 6l12 12\"/>\n </svg>\n </button>\n }\n </li>\n }\n\n @if (canAdd()) {\n <li class=\"ui-image-uploader__add-item\">\n <button\n type=\"button\"\n class=\"ui-image-uploader__add\"\n [attr.aria-label]=\"addLabel()\"\n (click)=\"openFilePicker()\"\n >\n <svg width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\">\n <path d=\"M12 5v14M5 12h14\"/>\n </svg>\n <span class=\"ui-image-uploader__add-text\">{{ addLabel() }}</span>\n </button>\n </li>\n }\n</ul>\n\n@if (dragItem(); as item) {\n <img\n class=\"ui-image-uploader__ghost\"\n [src]=\"item.previewUrl\"\n alt=\"\"\n aria-hidden=\"true\"\n [style.width.px]=\"ghostSize()\"\n [style.height.px]=\"ghostSize()\"\n [style.left.px]=\"ghostX()\"\n [style.top.px]=\"ghostY()\"\n />\n}\n\n<span class=\"ui-visually-hidden\" aria-live=\"polite\">{{ liveMessage() }}</span>\n\n<input\n #fileInput\n type=\"file\"\n class=\"ui-image-uploader__input\"\n [accept]=\"accept()\"\n multiple\n (change)=\"onFileSelected($event)\"\n/>\n", styles: [":host{display:block}.ui-image-uploader{display:grid;grid-template-columns:repeat(auto-fill,minmax(7.5rem,1fr));gap:var(--ui-spacing-sm);margin:0;padding:var(--ui-spacing-sm);list-style:none;border:2px dashed transparent;border-radius:var(--ui-radius-lg);transition:border-color var(--ui-transition-fast),background-color var(--ui-transition-fast)}.ui-image-uploader--drag-over{border-color:var(--ui-primary);background-color:color-mix(in srgb,var(--ui-primary) 6%,transparent)}.ui-image-uploader--disabled{opacity:.6}.ui-image-uploader__tile{position:relative;aspect-ratio:1;overflow:hidden;border:1px solid var(--ui-image-uploader-tile-border);border-radius:var(--ui-radius-md);background-color:var(--ui-image-uploader-tile-bg);cursor:grab;touch-action:none;-webkit-user-select:none;user-select:none}.ui-image-uploader__tile:focus-visible{outline:2px solid var(--ui-primary);outline-offset:2px}.ui-image-uploader__tile--grabbed{outline:2px solid var(--ui-primary);outline-offset:2px;box-shadow:var(--ui-shadow-md)}.ui-image-uploader__tile--dragging{opacity:.4;cursor:grabbing}.ui-image-uploader__tile--error{border:2px solid var(--ui-danger)}.ui-image-uploader__image{width:100%;height:100%;object-fit:cover;display:block;pointer-events:none}.ui-image-uploader__tile--error .ui-image-uploader__image{opacity:.5}.ui-image-uploader__cover-badge{position:absolute;left:var(--ui-spacing-xs);bottom:var(--ui-spacing-xs);padding:1px var(--ui-spacing-sm);border-radius:var(--ui-radius-sm);background-color:var(--ui-primary);color:var(--ui-primary-text);font-size:var(--ui-font-xs);font-weight:600;pointer-events:none}.ui-image-uploader__remove{position:absolute;top:var(--ui-spacing-xs);right:var(--ui-spacing-xs);display:flex;align-items:center;justify-content:center;width:24px;height:24px;padding:0;border:none;border-radius:50%;background-color:var(--ui-image-uploader-overlay-bg);color:#fff;cursor:pointer;transition:background-color var(--ui-transition-fast)}.ui-image-uploader__remove:hover{background-color:var(--ui-danger)}.ui-image-uploader__remove:focus-visible{outline:2px solid var(--ui-primary);outline-offset:2px}.ui-image-uploader__progress{position:absolute;inset:0;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:var(--ui-spacing-xs);padding:var(--ui-spacing-sm);background-color:var(--ui-image-uploader-overlay-bg);pointer-events:none}.ui-image-uploader__progress-bar{width:80%;height:4px;border-radius:2px;background-color:#ffffff4d;overflow:hidden}.ui-image-uploader__progress-fill{display:block;height:100%;border-radius:2px;background-color:var(--ui-primary);transition:width var(--ui-transition-fast)}.ui-image-uploader__progress-text{color:#fff;font-size:var(--ui-font-xs);font-weight:600}.ui-image-uploader__error-icon{position:absolute;inset:0;display:flex;align-items:center;justify-content:center;color:var(--ui-danger);pointer-events:none}.ui-image-uploader__add-item{aspect-ratio:1}.ui-image-uploader__add{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:var(--ui-spacing-xs);width:100%;height:100%;border:2px dashed var(--ui-border);border-radius:var(--ui-radius-md);background:transparent;color:var(--ui-text-muted);cursor:pointer;transition:border-color var(--ui-transition-fast),color var(--ui-transition-fast)}.ui-image-uploader__add:hover{border-color:var(--ui-border-hover);color:var(--ui-text)}.ui-image-uploader__add:focus-visible{outline:2px solid var(--ui-primary);outline-offset:2px}.ui-image-uploader__add-text{font-size:var(--ui-font-xs);font-weight:500}.ui-image-uploader__ghost{position:fixed;z-index:1000;transform:translate(-50%,-50%) scale(.9);border-radius:var(--ui-radius-md);object-fit:cover;box-shadow:var(--ui-shadow-lg);opacity:.9;pointer-events:none}.ui-image-uploader__input{display:none}.ui-visually-hidden{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush });
7269
+ }
7270
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.1", ngImport: i0, type: ImageUploaderComponent, decorators: [{
7271
+ type: Component,
7272
+ args: [{ selector: 'ui-image-uploader', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, template: "<ul\n #grid\n class=\"ui-image-uploader\"\n [class.ui-image-uploader--drag-over]=\"dragOver()\"\n [class.ui-image-uploader--disabled]=\"disabled()\"\n role=\"list\"\n (dragover)=\"onDragOver($event)\"\n (dragleave)=\"onDragLeave($event)\"\n (drop)=\"onDrop($event)\"\n>\n @for (item of items(); track item.id; let i = $index) {\n <li\n class=\"ui-image-uploader__tile\"\n [class.ui-image-uploader__tile--error]=\"item.error\"\n [class.ui-image-uploader__tile--grabbed]=\"grabbedId() === item.id\"\n [class.ui-image-uploader__tile--dragging]=\"dragId() === item.id\"\n [attr.data-id]=\"item.id\"\n [attr.tabindex]=\"disabled() ? null : 0\"\n [attr.aria-label]=\"tileLabel(item, i)\"\n [attr.title]=\"item.error || null\"\n (pointerdown)=\"onTilePointerDown($event, item.id)\"\n (pointermove)=\"onTilePointerMove($event)\"\n (pointerup)=\"onTilePointerUp($event)\"\n (pointercancel)=\"onTilePointerUp($event)\"\n (keydown)=\"onTileKeyDown($event, item.id, i)\"\n >\n <img class=\"ui-image-uploader__image\" [src]=\"item.previewUrl\" alt=\"\" draggable=\"false\" />\n\n @if (i === 0) {\n <span class=\"ui-image-uploader__cover-badge\">{{ coverLabel() }}</span>\n }\n\n @if (item.progress != null && item.progress < 100) {\n <span class=\"ui-image-uploader__progress\" aria-hidden=\"true\">\n <span class=\"ui-image-uploader__progress-bar\">\n <span\n class=\"ui-image-uploader__progress-fill\"\n [style.width.%]=\"item.progress\"\n ></span>\n </span>\n <span class=\"ui-image-uploader__progress-text\">{{ item.progress }}%</span>\n </span>\n }\n\n @if (item.error) {\n <span class=\"ui-image-uploader__error-icon\" aria-hidden=\"true\">\n <svg width=\"20\" height=\"20\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\">\n <circle cx=\"12\" cy=\"12\" r=\"10\"/>\n <path d=\"M12 8v4M12 16h.01\"/>\n </svg>\n </span>\n }\n\n @if (!disabled()) {\n <button\n type=\"button\"\n class=\"ui-image-uploader__remove\"\n aria-label=\"Remove image\"\n (click)=\"removeItem($event, item.id)\"\n >\n <svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\">\n <path d=\"M18 6L6 18M6 6l12 12\"/>\n </svg>\n </button>\n }\n </li>\n }\n\n @if (canAdd()) {\n <li class=\"ui-image-uploader__add-item\">\n <button\n type=\"button\"\n class=\"ui-image-uploader__add\"\n [attr.aria-label]=\"addLabel()\"\n (click)=\"openFilePicker()\"\n >\n <svg width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\">\n <path d=\"M12 5v14M5 12h14\"/>\n </svg>\n <span class=\"ui-image-uploader__add-text\">{{ addLabel() }}</span>\n </button>\n </li>\n }\n</ul>\n\n@if (dragItem(); as item) {\n <img\n class=\"ui-image-uploader__ghost\"\n [src]=\"item.previewUrl\"\n alt=\"\"\n aria-hidden=\"true\"\n [style.width.px]=\"ghostSize()\"\n [style.height.px]=\"ghostSize()\"\n [style.left.px]=\"ghostX()\"\n [style.top.px]=\"ghostY()\"\n />\n}\n\n<span class=\"ui-visually-hidden\" aria-live=\"polite\">{{ liveMessage() }}</span>\n\n<input\n #fileInput\n type=\"file\"\n class=\"ui-image-uploader__input\"\n [accept]=\"accept()\"\n multiple\n (change)=\"onFileSelected($event)\"\n/>\n", styles: [":host{display:block}.ui-image-uploader{display:grid;grid-template-columns:repeat(auto-fill,minmax(7.5rem,1fr));gap:var(--ui-spacing-sm);margin:0;padding:var(--ui-spacing-sm);list-style:none;border:2px dashed transparent;border-radius:var(--ui-radius-lg);transition:border-color var(--ui-transition-fast),background-color var(--ui-transition-fast)}.ui-image-uploader--drag-over{border-color:var(--ui-primary);background-color:color-mix(in srgb,var(--ui-primary) 6%,transparent)}.ui-image-uploader--disabled{opacity:.6}.ui-image-uploader__tile{position:relative;aspect-ratio:1;overflow:hidden;border:1px solid var(--ui-image-uploader-tile-border);border-radius:var(--ui-radius-md);background-color:var(--ui-image-uploader-tile-bg);cursor:grab;touch-action:none;-webkit-user-select:none;user-select:none}.ui-image-uploader__tile:focus-visible{outline:2px solid var(--ui-primary);outline-offset:2px}.ui-image-uploader__tile--grabbed{outline:2px solid var(--ui-primary);outline-offset:2px;box-shadow:var(--ui-shadow-md)}.ui-image-uploader__tile--dragging{opacity:.4;cursor:grabbing}.ui-image-uploader__tile--error{border:2px solid var(--ui-danger)}.ui-image-uploader__image{width:100%;height:100%;object-fit:cover;display:block;pointer-events:none}.ui-image-uploader__tile--error .ui-image-uploader__image{opacity:.5}.ui-image-uploader__cover-badge{position:absolute;left:var(--ui-spacing-xs);bottom:var(--ui-spacing-xs);padding:1px var(--ui-spacing-sm);border-radius:var(--ui-radius-sm);background-color:var(--ui-primary);color:var(--ui-primary-text);font-size:var(--ui-font-xs);font-weight:600;pointer-events:none}.ui-image-uploader__remove{position:absolute;top:var(--ui-spacing-xs);right:var(--ui-spacing-xs);display:flex;align-items:center;justify-content:center;width:24px;height:24px;padding:0;border:none;border-radius:50%;background-color:var(--ui-image-uploader-overlay-bg);color:#fff;cursor:pointer;transition:background-color var(--ui-transition-fast)}.ui-image-uploader__remove:hover{background-color:var(--ui-danger)}.ui-image-uploader__remove:focus-visible{outline:2px solid var(--ui-primary);outline-offset:2px}.ui-image-uploader__progress{position:absolute;inset:0;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:var(--ui-spacing-xs);padding:var(--ui-spacing-sm);background-color:var(--ui-image-uploader-overlay-bg);pointer-events:none}.ui-image-uploader__progress-bar{width:80%;height:4px;border-radius:2px;background-color:#ffffff4d;overflow:hidden}.ui-image-uploader__progress-fill{display:block;height:100%;border-radius:2px;background-color:var(--ui-primary);transition:width var(--ui-transition-fast)}.ui-image-uploader__progress-text{color:#fff;font-size:var(--ui-font-xs);font-weight:600}.ui-image-uploader__error-icon{position:absolute;inset:0;display:flex;align-items:center;justify-content:center;color:var(--ui-danger);pointer-events:none}.ui-image-uploader__add-item{aspect-ratio:1}.ui-image-uploader__add{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:var(--ui-spacing-xs);width:100%;height:100%;border:2px dashed var(--ui-border);border-radius:var(--ui-radius-md);background:transparent;color:var(--ui-text-muted);cursor:pointer;transition:border-color var(--ui-transition-fast),color var(--ui-transition-fast)}.ui-image-uploader__add:hover{border-color:var(--ui-border-hover);color:var(--ui-text)}.ui-image-uploader__add:focus-visible{outline:2px solid var(--ui-primary);outline-offset:2px}.ui-image-uploader__add-text{font-size:var(--ui-font-xs);font-weight:500}.ui-image-uploader__ghost{position:fixed;z-index:1000;transform:translate(-50%,-50%) scale(.9);border-radius:var(--ui-radius-md);object-fit:cover;box-shadow:var(--ui-shadow-lg);opacity:.9;pointer-events:none}.ui-image-uploader__input{display:none}.ui-visually-hidden{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}\n"] }]
7273
+ }], propDecorators: { maxCount: [{ type: i0.Input, args: [{ isSignal: true, alias: "maxCount", required: false }] }], accept: [{ type: i0.Input, args: [{ isSignal: true, alias: "accept", required: false }] }], maxFileSizeMb: [{ type: i0.Input, args: [{ isSignal: true, alias: "maxFileSizeMb", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], coverLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "coverLabel", required: false }] }], addLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "addLabel", required: false }] }], items: [{ type: i0.Input, args: [{ isSignal: true, alias: "items", required: false }] }, { type: i0.Output, args: ["itemsChange"] }], filesAdded: [{ type: i0.Output, args: ["filesAdded"] }], filesRejected: [{ type: i0.Output, args: ["filesRejected"] }], removed: [{ type: i0.Output, args: ["removed"] }], reordered: [{ type: i0.Output, args: ["reordered"] }], fileInput: [{ type: i0.ViewChild, args: ['fileInput', { isSignal: true }] }], grid: [{ type: i0.ViewChild, args: ['grid', { isSignal: true }] }] } });
7274
+
6394
7275
  class ShellComponent {
6395
7276
  variant = input('default', ...(ngDevMode ? [{ debugName: "variant" }] : []));
6396
7277
  sidebarService = inject(SidebarService);
@@ -7729,5 +8610,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.1", ngImpor
7729
8610
  * Generated bundle index. Do not edit.
7730
8611
  */
7731
8612
 
7732
- export { AccordionComponent, AccordionHeaderDirective, AccordionItemComponent, AlertComponent, BadgeComponent, ButtonComponent, CardComponent, CellTemplateDirective, CellValuePipe, CheckboxComponent, ChipInputComponent, ChipTemplateDirective, CircularProgressComponent, ContentComponent, ContextMenuDirective, DIALOG_DATA, DIALOG_REF, DatepickerComponent, DatetimepickerComponent, DialogRef, DialogService, DropdownComponent, DropdownDividerComponent, DropdownItemComponent, DropdownTriggerDirective, DynamicTabsComponent, FileChooserComponent, FilePreviewPipe, FileSizePipe, FooterComponent, InputComponent, JsonTreeComponent, LOADABLE, LoadingDirective, LoadingService, ModalComponent, NavbarComponent, OptionComponent, OptionTemplateDirective, PaginationComponent, ProgressComponent, RadioComponent, RadioGroupComponent, RangeSliderComponent, SelectComponent, ShellComponent, SidebarComponent, SidebarService, SidebarToggleComponent, SliderComponent, SpinnerComponent, SplitComponent, SplitPaneComponent, SwitchComponent, TAB_DATA, TAB_REF, TREE_HOST, TabActivePipe, TabComponent, TabIconDirective, TabRef, TableComponent, TabsComponent, TabsService, TemplateInputComponent, TemplateInputSuffixDirective, TextareaComponent, TimepickerComponent, ToastRef, ToastService, TooltipDirective, TreeComponent, TreeNodeComponent, Validators, VariablePopoverDirective, jsonToTreeNodes };
8613
+ export { AccordionComponent, AccordionHeaderDirective, AccordionItemComponent, AlertComponent, BadgeComponent, BreadcrumbComponent, BreadcrumbSeparatorDirective, ButtonComponent, CardComponent, CarouselComponent, CellTemplateDirective, CellValuePipe, CheckboxComponent, ChipInputComponent, ChipTemplateDirective, CircularProgressComponent, ContentComponent, ContextMenuDirective, DIALOG_DATA, DIALOG_REF, DatepickerComponent, DatetimepickerComponent, DialogRef, DialogService, DropdownComponent, DropdownDividerComponent, DropdownItemComponent, DropdownTriggerDirective, DynamicTabsComponent, FileChooserComponent, FilePreviewPipe, FileSizePipe, FooterComponent, ImageUploaderComponent, InputComponent, JsonTreeComponent, LOADABLE, LightboxComponent, LightboxService, LoadingDirective, LoadingService, ModalComponent, NavbarComponent, NumberRangeSliderComponent, OptionComponent, OptionTemplateDirective, PaginationComponent, ProgressComponent, RadioComponent, RadioGroupComponent, RangeSliderComponent, SegmentedComponent, SelectComponent, ShellComponent, SidebarComponent, SidebarService, SidebarToggleComponent, SkeletonComponent, SliderComponent, SpinnerComponent, SplitComponent, SplitPaneComponent, StepperComponent, SwitchComponent, TAB_DATA, TAB_REF, TREE_HOST, TabActivePipe, TabComponent, TabIconDirective, TabRef, TableComponent, TabsComponent, TabsService, TemplateInputComponent, TemplateInputSuffixDirective, TextareaComponent, TimepickerComponent, ToastRef, ToastService, TooltipDirective, TreeComponent, TreeNodeComponent, Validators, VariablePopoverDirective, jsonToTreeNodes };
7733
8614
  //# sourceMappingURL=m1z23r-ngx-ui.mjs.map