@kaufmann-lab/ui 0.1.16 → 0.1.17
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.
|
@@ -6,6 +6,7 @@ import { NG_VALUE_ACCESSOR, FormsModule, NgControl } from '@angular/forms';
|
|
|
6
6
|
import { Overlay, OverlayContainer } from '@angular/cdk/overlay';
|
|
7
7
|
import { TemplatePortal, ComponentPortal } from '@angular/cdk/portal';
|
|
8
8
|
import { Subscription } from 'rxjs';
|
|
9
|
+
import { DomSanitizer } from '@angular/platform-browser';
|
|
9
10
|
import { FocusTrapFactory } from '@angular/cdk/a11y';
|
|
10
11
|
import mapboxgl from 'mapbox-gl';
|
|
11
12
|
|
|
@@ -2596,10 +2597,17 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImpor
|
|
|
2596
2597
|
args: [{ selector: 'kf-content-state', imports: [EmptyState, Skeleton, Spinner], template: "@switch (state()) {\n @case ('ready') {\n <ng-content />\n }\n @case ('loading') {\n <div class=\"kf-content-state__loading\" aria-busy=\"true\">\n @if (load() === 'spinner') {\n <kf-spinner [label]=\"loadingLabel()\" />\n } @else {\n <kf-skeleton />\n <kf-skeleton width=\"80%\" />\n <kf-skeleton width=\"60%\" />\n }\n </div>\n }\n @default {\n <kf-empty-state [tone]=\"state() === 'error' ? 'error' : 'neutral'\">\n @if (title()) {\n <p class=\"kf-content-state__title\">{{ title() }}</p>\n }\n @if (subtitle()) {\n <p class=\"kf-content-state__subtitle\">{{ subtitle() }}</p>\n }\n <div class=\"kf-content-state__actions\"><ng-content select=\"[actions]\" /></div>\n </kf-empty-state>\n }\n}\n", styles: [":host{display:block;font-family:var(--kf-font-body)}.kf-content-state__loading{display:flex;flex-direction:column;gap:var(--kf-space-sm)}.kf-content-state__title{margin:0;font-family:var(--kf-font-title);font-weight:700;color:var(--kf-color-ink)}.kf-content-state__subtitle{margin:0;color:var(--kf-color-text-muted)}.kf-content-state__actions:empty{display:none}.kf-content-state__actions{display:flex;flex-wrap:wrap;justify-content:center;gap:var(--kf-space-sm);margin-top:var(--kf-space-sm)}\n"] }]
|
|
2597
2598
|
}], propDecorators: { state: [{ type: i0.Input, args: [{ isSignal: true, alias: "state", required: true }] }], load: [{ type: i0.Input, args: [{ isSignal: true, alias: "load", required: false }] }], title: [{ type: i0.Input, args: [{ isSignal: true, alias: "title", required: false }] }], subtitle: [{ type: i0.Input, args: [{ isSignal: true, alias: "subtitle", required: false }] }], loadingLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "loadingLabel", required: false }] }] } });
|
|
2598
2599
|
|
|
2600
|
+
const RAW_BASE64_MIME = {
|
|
2601
|
+
image: 'image/jpeg',
|
|
2602
|
+
video: 'video/mp4',
|
|
2603
|
+
pdf: 'application/pdf',
|
|
2604
|
+
};
|
|
2605
|
+
/** Solo contenido ya en memoria: un `<iframe>` con una URL remota arbitraria podría cargar cualquier página. */
|
|
2606
|
+
const LOCAL_PDF_SRC = /^(blob:|data:application\/pdf[;,])/i;
|
|
2599
2607
|
let contentViewerId = 0;
|
|
2600
2608
|
/**
|
|
2601
|
-
* Visor a pantalla completa para ampliar una pieza de contenido
|
|
2602
|
-
*
|
|
2609
|
+
* Visor a pantalla completa para ampliar una pieza de contenido: imagen,
|
|
2610
|
+
* video o PDF (`type="pdf"`, renderizado por el visor nativo del navegador).
|
|
2603
2611
|
* Mismo mecanismo que `kf-modal` (el `<dialog>` nativo resuelve backdrop,
|
|
2604
2612
|
* atrapado de foco, top-layer y `Escape`), pero sin panel/título/footer:
|
|
2605
2613
|
* el contenido ocupa toda la pantalla sobre un backdrop oscuro.
|
|
@@ -2607,10 +2615,17 @@ let contentViewerId = 0;
|
|
|
2607
2615
|
* `src` acepta lo que venga del backend sin que el consumidor tenga que
|
|
2608
2616
|
* normalizarlo primero: una URL (`http(s)://`), un data URI ya armado
|
|
2609
2617
|
* (`data:...`), o base64 crudo (se le antepone el prefijo `data:` según
|
|
2610
|
-
* `type`, asumiendo JPEG para imagen y
|
|
2618
|
+
* `type`, asumiendo JPEG para imagen, MP4 para video y PDF para pdf). Un PDF
|
|
2619
|
+
* solo acepta contenido local (`blob:`, data URI o base64), nunca una URL remota.
|
|
2620
|
+
*
|
|
2621
|
+
* Lo proyectado con el atributo `actions` (p. ej. un botón de descarga) va en
|
|
2622
|
+
* la barra superior, junto al título.
|
|
2611
2623
|
*
|
|
2612
2624
|
* <kf-content-viewer [(open)]="viewerOpen" [src]="viewerSrc()" alt="Foto del vehículo" />
|
|
2613
2625
|
* <kf-content-viewer [(open)]="viewerOpen" type="video" [src]="viewerSrc()" title="Inspección" />
|
|
2626
|
+
* <kf-content-viewer [(open)]="viewerOpen" type="pdf" [src]="blobUrl()" title="Contrato">
|
|
2627
|
+
* <kf-button actions variant="surface" icon="download" (click)="save()">Descargar</kf-button>
|
|
2628
|
+
* </kf-content-viewer>
|
|
2614
2629
|
*/
|
|
2615
2630
|
class ContentViewer {
|
|
2616
2631
|
type = input('image', /* @ts-ignore */
|
|
@@ -2638,13 +2653,22 @@ class ContentViewer {
|
|
|
2638
2653
|
return null;
|
|
2639
2654
|
if (/^(https?:|data:|blob:)/i.test(raw))
|
|
2640
2655
|
return raw;
|
|
2641
|
-
|
|
2642
|
-
return `data:${mime};base64,${raw}`;
|
|
2656
|
+
return `data:${RAW_BASE64_MIME[this.type()]};base64,${raw}`;
|
|
2643
2657
|
}, /* @ts-ignore */
|
|
2644
2658
|
...(ngDevMode ? [{ debugName: "resolvedSrc" }] : /* istanbul ignore next */ []));
|
|
2659
|
+
pdfSrc = computed(() => {
|
|
2660
|
+
const src = this.resolvedSrc();
|
|
2661
|
+
if (this.type() !== 'pdf' || !src || !LOCAL_PDF_SRC.test(src))
|
|
2662
|
+
return null;
|
|
2663
|
+
return this.sanitizer.bypassSecurityTrustResourceUrl(src);
|
|
2664
|
+
}, /* @ts-ignore */
|
|
2665
|
+
...(ngDevMode ? [{ debugName: "pdfSrc" }] : /* istanbul ignore next */ []));
|
|
2666
|
+
fallbackLabel = computed(() => ({ image: 'Visor de imagen', video: 'Visor de video', pdf: 'Visor de documento' })[this.type()], /* @ts-ignore */
|
|
2667
|
+
...(ngDevMode ? [{ debugName: "fallbackLabel" }] : /* istanbul ignore next */ []));
|
|
2645
2668
|
dialog = viewChild('dialog', /* @ts-ignore */
|
|
2646
2669
|
...(ngDevMode ? [{ debugName: "dialog" }] : /* istanbul ignore next */ []));
|
|
2647
2670
|
document = inject(DOCUMENT);
|
|
2671
|
+
sanitizer = inject(DomSanitizer);
|
|
2648
2672
|
constructor() {
|
|
2649
2673
|
afterRenderEffect(() => {
|
|
2650
2674
|
const el = this.dialog()?.nativeElement;
|
|
@@ -2693,11 +2717,11 @@ class ContentViewer {
|
|
|
2693
2717
|
}
|
|
2694
2718
|
}
|
|
2695
2719
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: ContentViewer, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
2696
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.0", type: ContentViewer, isStandalone: true, selector: "kf-content-viewer", inputs: { type: { classPropertyName: "type", publicName: "type", isSignal: true, isRequired: false, transformFunction: null }, src: { classPropertyName: "src", publicName: "src", isSignal: true, isRequired: false, transformFunction: null }, alt: { classPropertyName: "alt", publicName: "alt", isSignal: true, isRequired: false, transformFunction: null }, title: { classPropertyName: "title", publicName: "title", isSignal: true, isRequired: false, transformFunction: null }, open: { classPropertyName: "open", publicName: "open", isSignal: true, isRequired: false, transformFunction: null }, dismissible: { classPropertyName: "dismissible", publicName: "dismissible", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { open: "openChange" }, host: { listeners: { "click": "onHostClick($event)" } }, viewQueries: [{ propertyName: "dialog", first: true, predicate: ["dialog"], descendants: true, isSignal: true }], ngImport: i0, template: "<dialog\n #dialog\n class=\"kf-content-viewer\"\n [attr.aria-labelledby]=\"title() ? titleId : null\"\n [attr.aria-label]=\"title() ? null :
|
|
2720
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.0", type: ContentViewer, isStandalone: true, selector: "kf-content-viewer", inputs: { type: { classPropertyName: "type", publicName: "type", isSignal: true, isRequired: false, transformFunction: null }, src: { classPropertyName: "src", publicName: "src", isSignal: true, isRequired: false, transformFunction: null }, alt: { classPropertyName: "alt", publicName: "alt", isSignal: true, isRequired: false, transformFunction: null }, title: { classPropertyName: "title", publicName: "title", isSignal: true, isRequired: false, transformFunction: null }, open: { classPropertyName: "open", publicName: "open", isSignal: true, isRequired: false, transformFunction: null }, dismissible: { classPropertyName: "dismissible", publicName: "dismissible", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { open: "openChange" }, host: { listeners: { "click": "onHostClick($event)" } }, viewQueries: [{ propertyName: "dialog", first: true, predicate: ["dialog"], descendants: true, isSignal: true }], ngImport: i0, template: "<dialog\n #dialog\n class=\"kf-content-viewer\"\n [attr.aria-labelledby]=\"title() ? titleId : null\"\n [attr.aria-label]=\"title() ? null : fallbackLabel()\"\n (cancel)=\"onCancel($event)\"\n (close)=\"open.set(false)\"\n>\n @if (open()) {\n <div class=\"kf-content-viewer__panel\">\n @if (dismissible()) {\n <button\n type=\"button\"\n class=\"kf-content-viewer__close\"\n aria-label=\"Cerrar visor\"\n (click)=\"open.set(false)\"\n >\n <kf-icon name=\"x\" size=\"md\" />\n </button>\n }\n <div class=\"kf-content-viewer__bar\">\n @if (title()) {\n <span class=\"kf-content-viewer__title\" [id]=\"titleId\">{{ title() }}</span>\n }\n <div class=\"kf-content-viewer__actions\"><ng-content select=\"[actions]\" /></div>\n </div>\n <div class=\"kf-content-viewer__stage\">\n @if (type() === 'pdf') {\n @if (pdfSrc(); as src) {\n <iframe\n class=\"kf-content-viewer__document\"\n [src]=\"src\"\n [title]=\"title() || fallbackLabel()\"\n ></iframe>\n }\n } @else if (resolvedSrc(); as src) {\n @if (type() === 'video') {\n <video class=\"kf-content-viewer__media\" [src]=\"src\" controls autoplay></video>\n } @else {\n <img class=\"kf-content-viewer__media\" [src]=\"src\" [alt]=\"alt()\" />\n }\n }\n </div>\n </div>\n }\n</dialog>\n", styles: [".kf-content-viewer{width:100vw;height:100vh;max-width:100vw;max-height:100vh;margin:0;padding:0;border:none;background:transparent;overflow:visible;font-family:var(--kf-font-body)}.kf-content-viewer::backdrop{background:#0a0c10eb}.kf-content-viewer__panel{position:relative;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:var(--kf-space-md);width:100%;height:100%;padding:var(--kf-space-xl);box-sizing:border-box}.kf-content-viewer__close{position:absolute;top:var(--kf-space-lg);right:var(--kf-space-lg);display:inline-flex;padding:var(--kf-space-xs);border:none;border-radius:var(--kf-radius-pill);background:#ffffff1f;color:var(--kf-color-white);cursor:pointer}.kf-content-viewer__close:hover{background:#ffffff38}.kf-content-viewer__close:focus-visible{outline:none;box-shadow:var(--kf-shadow-focus)}.kf-content-viewer__bar{display:flex;flex-wrap:wrap;align-items:center;justify-content:center;gap:var(--kf-space-md);max-width:calc(100% - 2 * var(--kf-space-xl))}.kf-content-viewer__actions{display:contents}.kf-content-viewer__title{font-size:.9rem;font-weight:600;color:var(--kf-color-white);text-align:center}.kf-content-viewer__stage{flex:1;display:flex;align-items:center;justify-content:center;min-height:0;width:100%}.kf-content-viewer__document{width:min(100%,1100px);height:100%;border:none;border-radius:var(--kf-radius-sm);background:var(--kf-color-white)}.kf-content-viewer__media{max-width:100%;max-height:100%;object-fit:contain;border-radius:var(--kf-radius-sm)}\n"], dependencies: [{ kind: "component", type: Icon, selector: "kf-icon", inputs: ["name", "size", "rotate", "filled"] }] });
|
|
2697
2721
|
}
|
|
2698
2722
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: ContentViewer, decorators: [{
|
|
2699
2723
|
type: Component,
|
|
2700
|
-
args: [{ selector: 'kf-content-viewer', imports: [Icon], template: "<dialog\n #dialog\n class=\"kf-content-viewer\"\n [attr.aria-labelledby]=\"title() ? titleId : null\"\n [attr.aria-label]=\"title() ? null :
|
|
2724
|
+
args: [{ selector: 'kf-content-viewer', imports: [Icon], template: "<dialog\n #dialog\n class=\"kf-content-viewer\"\n [attr.aria-labelledby]=\"title() ? titleId : null\"\n [attr.aria-label]=\"title() ? null : fallbackLabel()\"\n (cancel)=\"onCancel($event)\"\n (close)=\"open.set(false)\"\n>\n @if (open()) {\n <div class=\"kf-content-viewer__panel\">\n @if (dismissible()) {\n <button\n type=\"button\"\n class=\"kf-content-viewer__close\"\n aria-label=\"Cerrar visor\"\n (click)=\"open.set(false)\"\n >\n <kf-icon name=\"x\" size=\"md\" />\n </button>\n }\n <div class=\"kf-content-viewer__bar\">\n @if (title()) {\n <span class=\"kf-content-viewer__title\" [id]=\"titleId\">{{ title() }}</span>\n }\n <div class=\"kf-content-viewer__actions\"><ng-content select=\"[actions]\" /></div>\n </div>\n <div class=\"kf-content-viewer__stage\">\n @if (type() === 'pdf') {\n @if (pdfSrc(); as src) {\n <iframe\n class=\"kf-content-viewer__document\"\n [src]=\"src\"\n [title]=\"title() || fallbackLabel()\"\n ></iframe>\n }\n } @else if (resolvedSrc(); as src) {\n @if (type() === 'video') {\n <video class=\"kf-content-viewer__media\" [src]=\"src\" controls autoplay></video>\n } @else {\n <img class=\"kf-content-viewer__media\" [src]=\"src\" [alt]=\"alt()\" />\n }\n }\n </div>\n </div>\n }\n</dialog>\n", styles: [".kf-content-viewer{width:100vw;height:100vh;max-width:100vw;max-height:100vh;margin:0;padding:0;border:none;background:transparent;overflow:visible;font-family:var(--kf-font-body)}.kf-content-viewer::backdrop{background:#0a0c10eb}.kf-content-viewer__panel{position:relative;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:var(--kf-space-md);width:100%;height:100%;padding:var(--kf-space-xl);box-sizing:border-box}.kf-content-viewer__close{position:absolute;top:var(--kf-space-lg);right:var(--kf-space-lg);display:inline-flex;padding:var(--kf-space-xs);border:none;border-radius:var(--kf-radius-pill);background:#ffffff1f;color:var(--kf-color-white);cursor:pointer}.kf-content-viewer__close:hover{background:#ffffff38}.kf-content-viewer__close:focus-visible{outline:none;box-shadow:var(--kf-shadow-focus)}.kf-content-viewer__bar{display:flex;flex-wrap:wrap;align-items:center;justify-content:center;gap:var(--kf-space-md);max-width:calc(100% - 2 * var(--kf-space-xl))}.kf-content-viewer__actions{display:contents}.kf-content-viewer__title{font-size:.9rem;font-weight:600;color:var(--kf-color-white);text-align:center}.kf-content-viewer__stage{flex:1;display:flex;align-items:center;justify-content:center;min-height:0;width:100%}.kf-content-viewer__document{width:min(100%,1100px);height:100%;border:none;border-radius:var(--kf-radius-sm);background:var(--kf-color-white)}.kf-content-viewer__media{max-width:100%;max-height:100%;object-fit:contain;border-radius:var(--kf-radius-sm)}\n"] }]
|
|
2701
2725
|
}], ctorParameters: () => [], propDecorators: { type: [{ type: i0.Input, args: [{ isSignal: true, alias: "type", required: false }] }], src: [{ type: i0.Input, args: [{ isSignal: true, alias: "src", required: false }] }], alt: [{ type: i0.Input, args: [{ isSignal: true, alias: "alt", required: false }] }], title: [{ type: i0.Input, args: [{ isSignal: true, alias: "title", required: false }] }], open: [{ type: i0.Input, args: [{ isSignal: true, alias: "open", required: false }] }, { type: i0.Output, args: ["openChange"] }], dismissible: [{ type: i0.Input, args: [{ isSignal: true, alias: "dismissible", required: false }] }], dialog: [{ type: i0.ViewChild, args: ['dialog', { isSignal: true }] }], onHostClick: [{
|
|
2702
2726
|
type: HostListener,
|
|
2703
2727
|
args: ['click', ['$event']]
|