@smartbit4all/ng-client 7.3.40 → 7.3.42

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,5 +1,5 @@
1
1
  import * as i0 from '@angular/core';
2
- import { InjectionToken, reflectComponentType, Injectable, inject, DOCUMENT, PLATFORM_ID, Optional, Inject, SkipSelf, NgModule, RendererStyleFlags2, input, computed, Component, HostListener, HostBinding, Directive, ViewContainerRef, ElementRef, Renderer2, ChangeDetectorRef, Input, output, Injector, signal, afterNextRender, CSP_NONCE, ViewChild, ViewChildren, effect, untracked, NgZone, forwardRef, createComponent, EnvironmentInjector, ApplicationRef, DestroyRef, ChangeDetectionStrategy, ViewEncapsulation, Pipe, viewChild, runInInjectionContext, EventEmitter, Output, makeEnvironmentProviders, importProvidersFrom, provideEnvironmentInitializer } from '@angular/core';
2
+ import { InjectionToken, reflectComponentType, Injectable, inject, DOCUMENT, PLATFORM_ID, Optional, Inject, SkipSelf, NgModule, RendererStyleFlags2, input, computed, Component, HostListener, HostBinding, Directive, ViewContainerRef, ElementRef, Renderer2, ChangeDetectorRef, Input, output, Injector, signal, afterNextRender, CSP_NONCE, ViewChild, ViewChildren, effect, untracked, viewChild, NgZone, DestroyRef, forwardRef, createComponent, EnvironmentInjector, ApplicationRef, ChangeDetectionStrategy, ViewEncapsulation, Pipe, runInInjectionContext, EventEmitter, Output, makeEnvironmentProviders, importProvidersFrom, provideEnvironmentInitializer } from '@angular/core';
3
3
  import * as i1 from '@angular/common/http';
4
4
  import { HttpHeaders, HttpContext, HttpErrorResponse, HttpParams, provideHttpClient, withInterceptors, withInterceptorsFromDi } from '@angular/common/http';
5
5
  import { DateAdapter, MAT_DATE_FORMATS, MAT_DATE_LOCALE } from '@angular/material/core';
@@ -3481,7 +3481,13 @@ class SmartTooltipDirective {
3481
3481
  ...(ngDevMode ? [undefined, { debugName: "smartTooltipClass" }] : /* istanbul ignore next */ []));
3482
3482
  this.cursor = 'pointer';
3483
3483
  }
3484
- // A keyboard user reaches the element by focus, never by hover.
3484
+ // A keyboard user reaches the element by focus, never by hover; a focus a script gives
3485
+ // (a dialog restoring it, the focus restore after a push) is no reason to show.
3486
+ focusin(event) {
3487
+ if (event.target.matches(':focus-visible')) {
3488
+ this.mouseover();
3489
+ }
3490
+ }
3485
3491
  mouseover() {
3486
3492
  const smartTooltip = this.smartTooltip();
3487
3493
  if (!smartTooltip)
@@ -3515,7 +3521,7 @@ class SmartTooltipDirective {
3515
3521
  return delay ? delay : 2000;
3516
3522
  }
3517
3523
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: SmartTooltipDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
3518
- static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "22.0.8", type: SmartTooltipDirective, isStandalone: true, selector: "[smartTooltip]", inputs: { smartTooltip: { classPropertyName: "smartTooltip", publicName: "smartTooltip", isSignal: true, isRequired: false, transformFunction: null }, smartTooltipClass: { classPropertyName: "smartTooltipClass", publicName: "smartTooltipClass", isSignal: true, isRequired: false, transformFunction: null } }, host: { listeners: { "focusin": "mouseover()", "mouseover": "mouseover()", "focusout": "mouseleave()", "mouseleave": "mouseleave()" }, properties: { "style.cursor": "this.cursor" } }, providers: [MatTooltip], ngImport: i0 }); }
3524
+ static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "22.0.8", type: SmartTooltipDirective, isStandalone: true, selector: "[smartTooltip]", inputs: { smartTooltip: { classPropertyName: "smartTooltip", publicName: "smartTooltip", isSignal: true, isRequired: false, transformFunction: null }, smartTooltipClass: { classPropertyName: "smartTooltipClass", publicName: "smartTooltipClass", isSignal: true, isRequired: false, transformFunction: null } }, host: { listeners: { "focusin": "focusin($event)", "mouseover": "mouseover()", "focusout": "mouseleave()", "mouseleave": "mouseleave()" }, properties: { "style.cursor": "this.cursor" } }, providers: [MatTooltip], ngImport: i0 }); }
3519
3525
  }
3520
3526
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: SmartTooltipDirective, decorators: [{
3521
3527
  type: Directive,
@@ -3526,10 +3532,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImpor
3526
3532
  }], propDecorators: { smartTooltip: [{ type: i0.Input, args: [{ isSignal: true, alias: "smartTooltip", required: false }] }], smartTooltipClass: [{ type: i0.Input, args: [{ isSignal: true, alias: "smartTooltipClass", required: false }] }], cursor: [{
3527
3533
  type: HostBinding,
3528
3534
  args: ['style.cursor']
3529
- }], mouseover: [{
3535
+ }], focusin: [{
3530
3536
  type: HostListener,
3531
- args: ['focusin']
3532
- }, {
3537
+ args: ['focusin', ['$event']]
3538
+ }], mouseover: [{
3533
3539
  type: HostListener,
3534
3540
  args: ['mouseover']
3535
3541
  }], mouseleave: [{
@@ -7341,8 +7347,8 @@ class SmartdialogService {
7341
7347
  });
7342
7348
  return this.dialogRef;
7343
7349
  }
7344
- createDialogRef(smartDialog, component) {
7345
- const dialogRef = this.dialog.open(component, { data: smartDialog });
7350
+ createDialogRef(smartDialog, component, config) {
7351
+ const dialogRef = this.dialog.open(component, { ...config, data: smartDialog });
7346
7352
  dialogRef.afterClosed().subscribe((result) => {
7347
7353
  this.handleAfterClosed(result);
7348
7354
  dialogRef.close();
@@ -7773,6 +7779,86 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImpor
7773
7779
 
7774
7780
  const APIS$3 = [DefaultService];
7775
7781
 
7782
+ /**
7783
+ * Form layout definition
7784
+ * Contains form layout definition objects.
7785
+ *
7786
+ * The version of the OpenAPI document: 1.0.0
7787
+ * Contact: info@it4all.hu
7788
+ *
7789
+ * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
7790
+ * https://openapi-generator.tech
7791
+ * Do not edit the class manually.
7792
+ */
7793
+ /**
7794
+ * When the camera or the microphone starts: AUTO the moment the capture UI opens (the recorder starts recording), MANUAL on a switch-on button.
7795
+ */
7796
+ var CaptureDeviceStart;
7797
+ (function (CaptureDeviceStart) {
7798
+ CaptureDeviceStart["AUTO"] = "AUTO";
7799
+ CaptureDeviceStart["MANUAL"] = "MANUAL";
7800
+ })(CaptureDeviceStart || (CaptureDeviceStart = {}));
7801
+
7802
+ /**
7803
+ * Form layout definition
7804
+ * Contains form layout definition objects.
7805
+ *
7806
+ * The version of the OpenAPI document: 1.0.0
7807
+ * Contact: info@it4all.hu
7808
+ *
7809
+ * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
7810
+ * https://openapi-generator.tech
7811
+ * Do not edit the class manually.
7812
+ */
7813
+ /**
7814
+ * What happens to a taken photo, a stopped video or a stopped recording: CONFIRM shows it for confirmation (retake / use, playback / save), IMMEDIATE hands it to the widget at once.
7815
+ */
7816
+ var CaptureHandover;
7817
+ (function (CaptureHandover) {
7818
+ CaptureHandover["CONFIRM"] = "CONFIRM";
7819
+ CaptureHandover["IMMEDIATE"] = "IMMEDIATE";
7820
+ })(CaptureHandover || (CaptureHandover = {}));
7821
+
7822
+ /**
7823
+ * Form layout definition
7824
+ * Contains form layout definition objects.
7825
+ *
7826
+ * The version of the OpenAPI document: 1.0.0
7827
+ * Contact: info@it4all.hu
7828
+ *
7829
+ * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
7830
+ * https://openapi-generator.tech
7831
+ * Do not edit the class manually.
7832
+ */
7833
+ /**
7834
+ * Where the capture UI of a file widget opens: INLINE under the capture buttons, DIALOG in a dialog of its own.
7835
+ */
7836
+ var CaptureMode;
7837
+ (function (CaptureMode) {
7838
+ CaptureMode["INLINE"] = "INLINE";
7839
+ CaptureMode["DIALOG"] = "DIALOG";
7840
+ })(CaptureMode || (CaptureMode = {}));
7841
+
7842
+ /**
7843
+ * Form layout definition
7844
+ * Contains form layout definition objects.
7845
+ *
7846
+ * The version of the OpenAPI document: 1.0.0
7847
+ * Contact: info@it4all.hu
7848
+ *
7849
+ * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
7850
+ * https://openapi-generator.tech
7851
+ * Do not edit the class manually.
7852
+ */
7853
+ /**
7854
+ * The side the inline recorder\'s waveform strip grows to from its microphone button: LEFT to the left with the button at the right edge, RIGHT to the right with the button at the left edge.
7855
+ */
7856
+ var CaptureOpenDirection;
7857
+ (function (CaptureOpenDirection) {
7858
+ CaptureOpenDirection["LEFT"] = "LEFT";
7859
+ CaptureOpenDirection["RIGHT"] = "RIGHT";
7860
+ })(CaptureOpenDirection || (CaptureOpenDirection = {}));
7861
+
7776
7862
  /**
7777
7863
  * Form layout definition
7778
7864
  * Contains form layout definition objects.
@@ -7805,6 +7891,48 @@ var CodeEditorLang;
7805
7891
  * Do not edit the class manually.
7806
7892
  */
7807
7893
 
7894
+ /**
7895
+ * Form layout definition
7896
+ * Contains form layout definition objects.
7897
+ *
7898
+ * The version of the OpenAPI document: 1.0.0
7899
+ * Contact: info@it4all.hu
7900
+ *
7901
+ * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
7902
+ * https://openapi-generator.tech
7903
+ * Do not edit the class manually.
7904
+ */
7905
+ /**
7906
+ * A place a file widget takes a file from: FILE the file picker, CAMERA_PHOTO the device camera for a photo, CAMERA_VIDEO the device camera for a video, MICROPHONE the microphone.
7907
+ */
7908
+ var FileUploaderSource;
7909
+ (function (FileUploaderSource) {
7910
+ FileUploaderSource["FILE"] = "FILE";
7911
+ FileUploaderSource["CAMERA_PHOTO"] = "CAMERA_PHOTO";
7912
+ FileUploaderSource["CAMERA_VIDEO"] = "CAMERA_VIDEO";
7913
+ FileUploaderSource["MICROPHONE"] = "MICROPHONE";
7914
+ })(FileUploaderSource || (FileUploaderSource = {}));
7915
+
7916
+ /**
7917
+ * Form layout definition
7918
+ * Contains form layout definition objects.
7919
+ *
7920
+ * The version of the OpenAPI document: 1.0.0
7921
+ * Contact: info@it4all.hu
7922
+ *
7923
+ * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
7924
+ * https://openapi-generator.tech
7925
+ * Do not edit the class manually.
7926
+ */
7927
+ /**
7928
+ * Whether the widget shows the stored file itself (image, video player, audio player) on the card: NONE shows the icon, the name and the size only, FULL shows the file above the name line.
7929
+ */
7930
+ var FileUploaderThumbnail;
7931
+ (function (FileUploaderThumbnail) {
7932
+ FileUploaderThumbnail["NONE"] = "NONE";
7933
+ FileUploaderThumbnail["FULL"] = "FULL";
7934
+ })(FileUploaderThumbnail || (FileUploaderThumbnail = {}));
7935
+
7808
7936
  /**
7809
7937
  * Form layout definition
7810
7938
  * Contains form layout definition objects.
@@ -9057,11 +9185,81 @@ class UploadWidgetUtils {
9057
9185
  const value = parseFloat((bytes / Math.pow(k, i)).toFixed(1));
9058
9186
  return `${value} ${sizes[i]}`;
9059
9187
  }
9060
- }
9188
+ /** The size of a capture with a decimal comma, as the Capture Dialog's captions show it. */
9189
+ static formatCapturedSize(bytes) {
9190
+ return UploadWidgetUtils.formatSize(bytes).replace('.', ',');
9191
+ }
9192
+ /** A length of time as mm:ss. */
9193
+ static formatDuration(milliseconds) {
9194
+ return [Math.floor((milliseconds % 3600000) / 60000), Math.floor((milliseconds % 60000) / 1000)]
9195
+ .map((v) => (v < 10 ? '0' + v : v))
9196
+ .join(':');
9197
+ }
9198
+ /** The recording mime of a media kind: webm where the browser records it, else mp4 (Safari). */
9199
+ static chooseRecorderMimeType(kind) {
9200
+ return MediaRecorder.isTypeSupported(`${kind}/webm`) ? `${kind}/webm` : `${kind}/mp4`;
9201
+ }
9202
+ /** A recording as a file named by the prefix and the local time, its extension by its mime. */
9203
+ static createRecordingFile(blob, prefix, fallbackMimeType) {
9204
+ const formattedDate = new Date().toLocaleString('sv-SE').replace(' ', '_').replace(/:/g, '-');
9205
+ const mimeType = blob.type || fallbackMimeType;
9206
+ const ext = mimeType.includes('webm') ? 'webm' : 'mp4';
9207
+ return new File([blob], `${prefix}_${formattedDate}.${ext}`, { type: mimeType });
9208
+ }
9209
+ /** The zone's texts: a descriptor's text per key, the client's default for a missing one. */
9210
+ static resolveTexts(texts) {
9211
+ return { ...UPLOAD_ZONE_DEFAULT_TEXTS, ...texts };
9212
+ }
9213
+ /** Fills the `{name}` placeholders of a text, which the server sends raw. */
9214
+ static fillPlaceholders(text, values) {
9215
+ return text.replace(/\{(\w+)\}/g, (placeholder, name) => name in values ? String(values[name]) : placeholder);
9216
+ }
9217
+ }
9218
+ /** The client's text of every key, shown where the descriptor's texts lack the key. */
9219
+ const UPLOAD_ZONE_DEFAULT_TEXTS = {
9220
+ addFile: 'Dokumentum hozzáadása',
9221
+ browseOrDrag: 'tallózás vagy behúzás',
9222
+ maxSize: 'max. {size} MB',
9223
+ upload: 'Feltöltés',
9224
+ unsupportedFormat: '"{fileName}" - nem támogatott formátum',
9225
+ fileTooLarge: '"{fileName}" - túl nagy fájl (max {size} MB)',
9226
+ capturePhoto: 'Fotó készítése',
9227
+ captureVideo: 'Videó készítése',
9228
+ recordAudio: 'Hangfelvétel',
9229
+ photo: 'fotó',
9230
+ video: 'videó',
9231
+ audio: 'hangfelvétel',
9232
+ or: 'vagy',
9233
+ captureOnlyHint: '{sources} készítése',
9234
+ takePicture: 'Kép készítése',
9235
+ cameraOn: 'Kamera bekapcsolása',
9236
+ cameraOff: 'Kamera kikapcsolása',
9237
+ switchOnCameraHint: 'Kapcsolja be a kamerát a kép készítéséhez!',
9238
+ switchOnForVideo: 'Kapcsolja be a kamerát a videó felvételéhez!',
9239
+ startRecording: 'Felvétel indítása',
9240
+ stopRecording: 'Felvétel leállítása',
9241
+ recording: 'Felvétel folyamatban…',
9242
+ pickVideo: 'Videó kiválasztása',
9243
+ cameraError: 'A kamera nem érhető el.',
9244
+ microphoneError: 'A mikrofon nem érhető el.',
9245
+ play: 'Lejátszás',
9246
+ photoDialogTitle: 'Fénykép készítése',
9247
+ videoDialogTitle: 'Videó felvétele',
9248
+ audioDialogTitle: 'Hangfelvétel',
9249
+ cancel: 'Mégse',
9250
+ retake: 'Újra',
9251
+ use: 'Használ',
9252
+ save: 'Mentés',
9253
+ capturedPhoto: 'Elkészült kép',
9254
+ capturedVideo: 'Felvétel',
9255
+ recordingHint: 'A lejátszás ikon visszajátssza, a mikrofon gomb új felvételt indít (felülírja ezt).',
9256
+ microphoneRetryHint: 'A mikrofon gomb újra kéri az engedélyt.',
9257
+ };
9061
9258
 
9062
9259
  class SmartfileuploaderComponent {
9063
9260
  constructor() {
9064
9261
  this._snackBar = inject(MatSnackBar);
9262
+ this.changeDetectorRef = inject(ChangeDetectorRef);
9065
9263
  this.files = [];
9066
9264
  this.uploadCallback = input.required(/* @ts-ignore */
9067
9265
  ...(ngDevMode ? [{ debugName: "uploadCallback" }] : /* istanbul ignore next */ []));
@@ -9077,6 +9275,10 @@ class SmartfileuploaderComponent {
9077
9275
  ...(ngDevMode ? [{ debugName: "autoUpload" }] : /* istanbul ignore next */ []));
9078
9276
  this.isDisabled = input(false, /* @ts-ignore */
9079
9277
  ...(ngDevMode ? [{ debugName: "isDisabled" }] : /* istanbul ignore next */ []));
9278
+ /** False in a capture-only zone: no picker and drop zone, the list and the checks stay. The
9279
+ * upload button goes with the picker, so a host hiding it needs autoUpload. */
9280
+ this.pickerShown = input(true, /* @ts-ignore */
9281
+ ...(ngDevMode ? [{ debugName: "pickerShown" }] : /* istanbul ignore next */ []));
9080
9282
  this.isDragOver = false;
9081
9283
  this.errors = [];
9082
9284
  this.previewUrls = new Map();
@@ -9084,13 +9286,14 @@ class SmartfileuploaderComponent {
9084
9286
  ngOnInit() {
9085
9287
  const defaultI18n = {
9086
9288
  addFile: 'dokumentum hozzáadása',
9087
- browseOrDrag: 'tallózás vagy behúzás',
9289
+ browseOrDrag: UPLOAD_ZONE_DEFAULT_TEXTS.browseOrDrag,
9088
9290
  maxSize: `max. ${this.maxSizeMb()} MB`,
9089
9291
  formats: 'PDF, docx, xls formátum',
9090
- upload: 'Feltöltés',
9292
+ upload: UPLOAD_ZONE_DEFAULT_TEXTS.upload,
9091
9293
  };
9092
9294
  this.i18n = { ...defaultI18n, ...this.i18n };
9093
9295
  }
9296
+ /** Checks the formats and the size of picked, dropped and captured files alike. */
9094
9297
  processFiles(fileList) {
9095
9298
  this.errors = [];
9096
9299
  var validFiles = [];
@@ -9104,12 +9307,17 @@ class SmartfileuploaderComponent {
9104
9307
  .map((f) => f.replace('.', '').trim().toLowerCase());
9105
9308
  var isAllowed = trimmedFormats.includes(ext);
9106
9309
  if (!isAllowed) {
9107
- this.errors.push(`"${file.name}" - nem támogatott formátum`);
9310
+ const message = this.i18n?.unsupportedFormat ?? UPLOAD_ZONE_DEFAULT_TEXTS.unsupportedFormat;
9311
+ this.errors.push(UploadWidgetUtils.fillPlaceholders(message, { fileName: file.name }));
9108
9312
  hasError = true;
9109
9313
  }
9110
9314
  }
9111
9315
  if (file.size > this.maxSizeMb() * 1024 * 1024) {
9112
- this.errors.push(`"${file.name}" - túl nagy fájl (max ${this.maxSizeMb()} MB)`);
9316
+ const message = this.i18n?.fileTooLarge ?? UPLOAD_ZONE_DEFAULT_TEXTS.fileTooLarge;
9317
+ this.errors.push(UploadWidgetUtils.fillPlaceholders(message, {
9318
+ fileName: file.name,
9319
+ size: this.maxSizeMb(),
9320
+ }));
9113
9321
  hasError = true;
9114
9322
  }
9115
9323
  if (!hasError) {
@@ -9125,6 +9333,8 @@ class SmartfileuploaderComponent {
9125
9333
  if (this.autoUpload() && this.errors.length == 0) {
9126
9334
  this.uploadFile();
9127
9335
  }
9336
+ // A Capture Dialog hands its file over outside any template event of this view.
9337
+ this.changeDetectorRef.markForCheck();
9128
9338
  }
9129
9339
  getFile(event) {
9130
9340
  if (event.target.files && event.target.files.length) {
@@ -9134,6 +9344,9 @@ class SmartfileuploaderComponent {
9134
9344
  }
9135
9345
  }
9136
9346
  onDrop(event) {
9347
+ if (!this.pickerShown()) {
9348
+ return;
9349
+ }
9137
9350
  event.preventDefault();
9138
9351
  this.isDragOver = false;
9139
9352
  if (!event.dataTransfer?.files)
@@ -9142,6 +9355,9 @@ class SmartfileuploaderComponent {
9142
9355
  this.processFiles(fileList);
9143
9356
  }
9144
9357
  onDragOver(event) {
9358
+ if (!this.pickerShown()) {
9359
+ return;
9360
+ }
9145
9361
  event.preventDefault();
9146
9362
  this.isDragOver = true;
9147
9363
  }
@@ -9228,68 +9444,232 @@ class SmartfileuploaderComponent {
9228
9444
  };
9229
9445
  }
9230
9446
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: SmartfileuploaderComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
9231
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.8", type: SmartfileuploaderComponent, isStandalone: true, selector: "smartfileuploader", inputs: { uploadCallback: { classPropertyName: "uploadCallback", publicName: "uploadCallback", isSignal: true, isRequired: true, transformFunction: null }, fileFormats: { classPropertyName: "fileFormats", publicName: "fileFormats", isSignal: true, isRequired: false, transformFunction: null }, maxSizeMb: { classPropertyName: "maxSizeMb", publicName: "maxSizeMb", isSignal: true, isRequired: false, transformFunction: null }, i18n: { classPropertyName: "i18n", publicName: "i18n", isSignal: false, isRequired: false, transformFunction: null }, useIconButton: { classPropertyName: "useIconButton", publicName: "useIconButton", isSignal: true, isRequired: false, transformFunction: null }, isMultiple: { classPropertyName: "isMultiple", publicName: "isMultiple", isSignal: true, isRequired: false, transformFunction: null }, autoUpload: { classPropertyName: "autoUpload", publicName: "autoUpload", isSignal: true, isRequired: false, transformFunction: null }, isDisabled: { classPropertyName: "isDisabled", publicName: "isDisabled", isSignal: true, isRequired: false, transformFunction: null } }, viewQueries: [{ propertyName: "fileInput", first: true, predicate: ["fileInput"], descendants: true }], ngImport: i0, template: "<div\n class=\"container\"\n [ngClass]=\"{ disabledWidget: isDisabled() }\"\n (dragover)=\"onDragOver($event)\"\n (dragleave)=\"onDragLeave($event)\"\n (drop)=\"onDrop($event)\"\n [class.dragover]=\"isDragOver\"\n>\n <div class=\"fileContainer\" (click)=\"fileInput.click()\">\n <input\n #fileInput\n id=\"addFile\"\n placeholder=\"fileInput\"\n type=\"file\"\n (change)=\"getFile($event)\"\n class=\"file\"\n accept=\"{{ fileFormats()?.join(', ') }}\"\n [size]=\"maxSizeMb()\"\n [multiple]=\"isMultiple()\"\n [disabled]=\"isDisabled()\"\n style=\"display: none\"\n />\n <div class=\"fileUploadContentContainer\">\n <div class=\"icon\">\n @if (useIconButton()) {\n <mat-label class=\"addFileButton\">\n <mat-icon fontSet=\"material-symbols-outlined\" color=\"primary\" class=\"addCircle\"\n >add_circle</mat-icon\n >\n </mat-label>\n }\n @if (!useIconButton()) {\n <button mat-raised-button color=\"primary\" class=\"addFileButton\">\n {{ i18n!.addFile }}\n </button>\n }\n </div>\n <div class=\"labels\">\n @if (useIconButton()) {\n <mat-label class=\"label primary title\">\n {{ i18n!.addFile }}\n </mat-label>\n }\n <mat-label class=\"label secondary\"> {{ i18n!.browseOrDrag }} </mat-label>\n <mat-label class=\"subLabel primary\"> {{ i18n!.maxSize }} </mat-label>\n <mat-label class=\"subLabel primary\"> {{ i18n!.formats }} </mat-label>\n </div>\n @if (files.length) {\n <div class=\"uploadButton\">\n <ui-action-button\n (actionClick)=\"uploadFile($event)\"\n [code]=\"'default-upload'\"\n [descriptor]=\"uploadButton\"\n >\n </ui-action-button>\n </div>\n }\n </div>\n </div>\n\n @if (errors.length) {\n <div class=\"errorMessage\">\n <smart-icon [icon]=\"'error'\" [color]=\"'warn'\"></smart-icon>\n <div class=\"errors\">\n @for (error of errors; track error) {\n <span>{{ error }}</span>\n }\n </div>\n </div>\n }\n\n @if (files.length) {\n <div class=\"uploadedFilesContainer\">\n @for (file of files; track file; let i = $index) {\n <div class=\"uploadedFile\">\n @if (filePreviewUrl(file)) {\n <img [src]=\"filePreviewUrl(file)\" [alt]=\"file.name\" width=\"50\" height=\"50\" />\n } @else {\n <smart-icon [icon]=\"'insert_drive_file'\"></smart-icon>\n }\n <div class=\"fileData\">\n <div class=\"fileDataContainer\">\n <span class=\"fileName\">{{ file.name }}</span>\n <span class=\"fileSize\">{{ formatSize(file.size) }}</span>\n </div>\n </div>\n <div class=\"fileActions\">\n <smart-icon\n class=\"downloadIcon\"\n icon=\"download\"\n (click)=\"downloadFile(file)\"\n [attr.data-testid]=\"'default_download'\"\n ></smart-icon>\n <smart-icon\n class=\"removeIcon\"\n icon=\"delete\"\n [attr.data-testid]=\"'default_remove'\"\n (click)=\"remove(i)\"\n ></smart-icon>\n </div>\n </div>\n }\n </div>\n }\n</div>\n", styles: [":host{--border-color: #a6aabd60;--def-border-radius: .5rem}.container{display:flex;flex-direction:column;border:2px dashed var(--border-color);border-radius:var(--def-border-radius)}.fileContainer{cursor:pointer;padding:1rem;display:flex;flex-direction:column}.fileContainer:hover{background-color:rgb(from var(--border-color) r g b / .1)}.disabledWidget ::ng-deep .fileContainer:hover{background-color:unset;cursor:unset}.disabledWidget ::ng-deep *{opacity:.85}.dragover{background-color:rgb(from var(--border-color) r g b / .1)}.fileUploadContentContainer{display:flex;flex-direction:row;gap:1rem}.icon mat-icon{font-size:3rem;width:unset;height:unset}.icon{align-content:center}.labels{flex:1;display:flex;flex-direction:column;justify-content:center;width:fit-content}.uploadButton{align-content:center}.primary{color:var(--primary-color)}.secondary{color:var(--gray)}.label{margin:unset}.subLabel{font-size:smaller}.title{padding-bottom:.3rem}.errorMessage,.uploadedFilesContainer{border-top:2px solid var(--border-color)}.errorMessage{display:flex;flex-direction:row;align-items:center;color:var(--warn-color);padding:.5rem 1rem;gap:1rem}.errorMessage ::ng-deep mat-icon{color:var(--warn-color)}.errors{display:flex;flex-direction:column}.uploadedFile{display:flex;flex-direction:row;justify-content:flex-start;align-items:center;padding:1rem;border-bottom:1px solid #e0e0e0;gap:1rem}.uploadedFile:last-child{border-bottom:none}.fileSize{display:flex;flex-direction:row;font-size:smaller}.fileData{flex:1;display:flex;flex-direction:column}.fileDataContainer{display:flex;flex-direction:column;justify-content:flex-end;width:fit-content}.removeIcon ::ng-deep mat-icon{color:var(--warn-color)}smart-icon ::ng-deep mat-icon{font-size:3rem;width:unset;height:unset}.fileActions{display:flex;flex-direction:row;justify-content:flex-end;gap:.5rem}.removeIcon ::ng-deep i,.removeIcon ::ng-deep mat-icon{color:var(--warn-color)}.removeIcon ::ng-deep mat-icon,.downloadIcon ::ng-deep mat-icon{font-size:2rem}.removeIcon:hover,.downloadIcon:hover{cursor:pointer}smart-icon{display:flex;align-items:center}\n"], dependencies: [{ kind: "directive", type: NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: MatLabel, selector: "mat-label" }, { kind: "component", type: MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "component", type: MatButton, selector: " button[matButton], a[matButton], button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button], a[mat-button], a[mat-raised-button], a[mat-flat-button], a[mat-stroked-button] ", inputs: ["matButton"], exportAs: ["matButton", "matAnchor"] }, { kind: "component", type: UiActionButtonComponent, selector: "ui-action-button", inputs: ["disabled", "descriptor", "code", "identifier", "viewActivated", "tab", "rowState", "addedCssClass", "addBasicClasses", "iconPlaceholder", "chords"], outputs: ["actionClick", "actionDoubleClick"] }, { kind: "component", type: SmartIconComponent, selector: "smart-icon", inputs: ["icon", "color", "imageResource"] }] }); }
9447
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.8", type: SmartfileuploaderComponent, isStandalone: true, selector: "smartfileuploader", inputs: { uploadCallback: { classPropertyName: "uploadCallback", publicName: "uploadCallback", isSignal: true, isRequired: true, transformFunction: null }, fileFormats: { classPropertyName: "fileFormats", publicName: "fileFormats", isSignal: true, isRequired: false, transformFunction: null }, maxSizeMb: { classPropertyName: "maxSizeMb", publicName: "maxSizeMb", isSignal: true, isRequired: false, transformFunction: null }, i18n: { classPropertyName: "i18n", publicName: "i18n", isSignal: false, isRequired: false, transformFunction: null }, useIconButton: { classPropertyName: "useIconButton", publicName: "useIconButton", isSignal: true, isRequired: false, transformFunction: null }, isMultiple: { classPropertyName: "isMultiple", publicName: "isMultiple", isSignal: true, isRequired: false, transformFunction: null }, autoUpload: { classPropertyName: "autoUpload", publicName: "autoUpload", isSignal: true, isRequired: false, transformFunction: null }, isDisabled: { classPropertyName: "isDisabled", publicName: "isDisabled", isSignal: true, isRequired: false, transformFunction: null }, pickerShown: { classPropertyName: "pickerShown", publicName: "pickerShown", isSignal: true, isRequired: false, transformFunction: null } }, viewQueries: [{ propertyName: "fileInput", first: true, predicate: ["fileInput"], descendants: true }], ngImport: i0, template: "<div\n class=\"container\"\n [ngClass]=\"{ disabledWidget: isDisabled() }\"\n (dragover)=\"onDragOver($event)\"\n (dragleave)=\"onDragLeave($event)\"\n (drop)=\"onDrop($event)\"\n [class.dragover]=\"isDragOver\"\n [class.captureOnly]=\"!pickerShown()\"\n>\n @if (pickerShown()) {\n <div class=\"fileContainer\" (click)=\"fileInput.click()\">\n <input\n #fileInput\n id=\"addFile\"\n placeholder=\"fileInput\"\n type=\"file\"\n (change)=\"getFile($event)\"\n class=\"file\"\n accept=\"{{ fileFormats()?.join(', ') }}\"\n [size]=\"maxSizeMb()\"\n [multiple]=\"isMultiple()\"\n [disabled]=\"isDisabled()\"\n style=\"display: none\"\n />\n <div class=\"fileUploadContentContainer\">\n <div class=\"icon\">\n @if (useIconButton()) {\n <mat-label class=\"addFileButton\">\n <mat-icon fontSet=\"material-symbols-outlined\" color=\"primary\" class=\"addCircle\"\n >add_circle</mat-icon\n >\n </mat-label>\n }\n @if (!useIconButton()) {\n <button mat-raised-button color=\"primary\" class=\"addFileButton\">\n {{ i18n!.addFile }}\n </button>\n }\n </div>\n <div class=\"labels\">\n @if (useIconButton()) {\n <mat-label class=\"label primary title\">\n {{ i18n!.addFile }}\n </mat-label>\n }\n <mat-label class=\"label secondary\"> {{ i18n!.browseOrDrag }} </mat-label>\n <mat-label class=\"subLabel primary\"> {{ i18n!.maxSize }} </mat-label>\n <mat-label class=\"subLabel primary\"> {{ i18n!.formats }} </mat-label>\n </div>\n @if (files.length) {\n <div class=\"uploadButton\">\n <ui-action-button\n (actionClick)=\"uploadFile($event)\"\n [code]=\"'default-upload'\"\n [descriptor]=\"uploadButton\"\n >\n </ui-action-button>\n </div>\n }\n </div>\n </div>\n }\n\n @if (errors.length) {\n <div class=\"errorMessage\">\n <smart-icon [icon]=\"'error'\" [color]=\"'warn'\"></smart-icon>\n <div class=\"errors\">\n @for (error of errors; track error) {\n <span>{{ error }}</span>\n }\n </div>\n </div>\n }\n\n @if (files.length) {\n <div class=\"uploadedFilesContainer\">\n @for (file of files; track file; let i = $index) {\n <div class=\"uploadedFile\">\n @if (filePreviewUrl(file)) {\n <img [src]=\"filePreviewUrl(file)\" [alt]=\"file.name\" width=\"50\" height=\"50\" />\n } @else {\n <smart-icon [icon]=\"'insert_drive_file'\"></smart-icon>\n }\n <div class=\"fileData\">\n <div class=\"fileDataContainer\">\n <span class=\"fileName\">{{ file.name }}</span>\n <span class=\"fileSize\">{{ formatSize(file.size) }}</span>\n </div>\n </div>\n <div class=\"fileActions\">\n <smart-icon\n class=\"downloadIcon\"\n icon=\"download\"\n (click)=\"downloadFile(file)\"\n [attr.data-testid]=\"'default_download'\"\n ></smart-icon>\n <smart-icon\n class=\"removeIcon\"\n icon=\"delete\"\n [attr.data-testid]=\"'default_remove'\"\n (click)=\"remove(i)\"\n ></smart-icon>\n </div>\n </div>\n }\n </div>\n }\n</div>\n", styles: [":host{--border-color: #a6aabd60;--def-border-radius: .5rem}.container{display:flex;flex-direction:column;border:2px dashed var(--border-color);border-radius:var(--def-border-radius)}.container.captureOnly{border:none}.fileContainer{cursor:pointer;padding:1rem;display:flex;flex-direction:column}.fileContainer:hover{background-color:rgb(from var(--border-color) r g b / .1)}.disabledWidget ::ng-deep .fileContainer:hover{background-color:unset;cursor:unset}.disabledWidget ::ng-deep *{opacity:.85}.dragover{background-color:rgb(from var(--border-color) r g b / .1)}.fileUploadContentContainer{display:flex;flex-direction:row;gap:1rem}.icon mat-icon{font-size:3rem;width:unset;height:unset}.icon .addCircle{color:var(--primary-color)}.icon{align-content:center}.labels{flex:1;display:flex;flex-direction:column;justify-content:center;width:fit-content}.uploadButton{align-content:center}.primary{color:var(--primary-color)}.secondary{color:var(--gray)}.label{margin:unset}.subLabel{font-size:smaller}.title{padding-bottom:.3rem}.errorMessage,.uploadedFilesContainer{border-top:2px solid var(--border-color)}.errorMessage{display:flex;flex-direction:row;align-items:center;color:var(--warn-color);padding:.5rem 1rem;gap:1rem}.errorMessage ::ng-deep mat-icon{color:var(--warn-color)}.errors{display:flex;flex-direction:column}.uploadedFile{display:flex;flex-direction:row;justify-content:flex-start;align-items:center;padding:1rem;border-bottom:1px solid #e0e0e0;gap:1rem}.uploadedFile:last-child{border-bottom:none}.fileSize{display:flex;flex-direction:row;font-size:smaller}.fileData{flex:1;display:flex;flex-direction:column}.fileDataContainer{display:flex;flex-direction:column;justify-content:flex-end;width:fit-content}.removeIcon ::ng-deep mat-icon{color:var(--warn-color)}smart-icon ::ng-deep mat-icon{font-size:3rem;width:unset;height:unset}.fileActions{display:flex;flex-direction:row;justify-content:flex-end;gap:.5rem}.removeIcon ::ng-deep i,.removeIcon ::ng-deep mat-icon{color:var(--warn-color)}.removeIcon ::ng-deep mat-icon,.downloadIcon ::ng-deep mat-icon{font-size:2rem}.removeIcon:hover,.downloadIcon:hover{cursor:pointer}smart-icon{display:flex;align-items:center}\n"], dependencies: [{ kind: "directive", type: NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: MatLabel, selector: "mat-label" }, { kind: "component", type: MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "component", type: MatButton, selector: " button[matButton], a[matButton], button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button], a[mat-button], a[mat-raised-button], a[mat-flat-button], a[mat-stroked-button] ", inputs: ["matButton"], exportAs: ["matButton", "matAnchor"] }, { kind: "component", type: UiActionButtonComponent, selector: "ui-action-button", inputs: ["disabled", "descriptor", "code", "identifier", "viewActivated", "tab", "rowState", "addedCssClass", "addBasicClasses", "iconPlaceholder", "chords"], outputs: ["actionClick", "actionDoubleClick"] }, { kind: "component", type: SmartIconComponent, selector: "smart-icon", inputs: ["icon", "color", "imageResource"] }] }); }
9232
9448
  }
9233
9449
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: SmartfileuploaderComponent, decorators: [{
9234
9450
  type: Component,
9235
- args: [{ selector: 'smartfileuploader', imports: [NgClass, MatLabel, MatIcon, MatButton, UiActionButtonComponent, SmartIconComponent], template: "<div\n class=\"container\"\n [ngClass]=\"{ disabledWidget: isDisabled() }\"\n (dragover)=\"onDragOver($event)\"\n (dragleave)=\"onDragLeave($event)\"\n (drop)=\"onDrop($event)\"\n [class.dragover]=\"isDragOver\"\n>\n <div class=\"fileContainer\" (click)=\"fileInput.click()\">\n <input\n #fileInput\n id=\"addFile\"\n placeholder=\"fileInput\"\n type=\"file\"\n (change)=\"getFile($event)\"\n class=\"file\"\n accept=\"{{ fileFormats()?.join(', ') }}\"\n [size]=\"maxSizeMb()\"\n [multiple]=\"isMultiple()\"\n [disabled]=\"isDisabled()\"\n style=\"display: none\"\n />\n <div class=\"fileUploadContentContainer\">\n <div class=\"icon\">\n @if (useIconButton()) {\n <mat-label class=\"addFileButton\">\n <mat-icon fontSet=\"material-symbols-outlined\" color=\"primary\" class=\"addCircle\"\n >add_circle</mat-icon\n >\n </mat-label>\n }\n @if (!useIconButton()) {\n <button mat-raised-button color=\"primary\" class=\"addFileButton\">\n {{ i18n!.addFile }}\n </button>\n }\n </div>\n <div class=\"labels\">\n @if (useIconButton()) {\n <mat-label class=\"label primary title\">\n {{ i18n!.addFile }}\n </mat-label>\n }\n <mat-label class=\"label secondary\"> {{ i18n!.browseOrDrag }} </mat-label>\n <mat-label class=\"subLabel primary\"> {{ i18n!.maxSize }} </mat-label>\n <mat-label class=\"subLabel primary\"> {{ i18n!.formats }} </mat-label>\n </div>\n @if (files.length) {\n <div class=\"uploadButton\">\n <ui-action-button\n (actionClick)=\"uploadFile($event)\"\n [code]=\"'default-upload'\"\n [descriptor]=\"uploadButton\"\n >\n </ui-action-button>\n </div>\n }\n </div>\n </div>\n\n @if (errors.length) {\n <div class=\"errorMessage\">\n <smart-icon [icon]=\"'error'\" [color]=\"'warn'\"></smart-icon>\n <div class=\"errors\">\n @for (error of errors; track error) {\n <span>{{ error }}</span>\n }\n </div>\n </div>\n }\n\n @if (files.length) {\n <div class=\"uploadedFilesContainer\">\n @for (file of files; track file; let i = $index) {\n <div class=\"uploadedFile\">\n @if (filePreviewUrl(file)) {\n <img [src]=\"filePreviewUrl(file)\" [alt]=\"file.name\" width=\"50\" height=\"50\" />\n } @else {\n <smart-icon [icon]=\"'insert_drive_file'\"></smart-icon>\n }\n <div class=\"fileData\">\n <div class=\"fileDataContainer\">\n <span class=\"fileName\">{{ file.name }}</span>\n <span class=\"fileSize\">{{ formatSize(file.size) }}</span>\n </div>\n </div>\n <div class=\"fileActions\">\n <smart-icon\n class=\"downloadIcon\"\n icon=\"download\"\n (click)=\"downloadFile(file)\"\n [attr.data-testid]=\"'default_download'\"\n ></smart-icon>\n <smart-icon\n class=\"removeIcon\"\n icon=\"delete\"\n [attr.data-testid]=\"'default_remove'\"\n (click)=\"remove(i)\"\n ></smart-icon>\n </div>\n </div>\n }\n </div>\n }\n</div>\n", styles: [":host{--border-color: #a6aabd60;--def-border-radius: .5rem}.container{display:flex;flex-direction:column;border:2px dashed var(--border-color);border-radius:var(--def-border-radius)}.fileContainer{cursor:pointer;padding:1rem;display:flex;flex-direction:column}.fileContainer:hover{background-color:rgb(from var(--border-color) r g b / .1)}.disabledWidget ::ng-deep .fileContainer:hover{background-color:unset;cursor:unset}.disabledWidget ::ng-deep *{opacity:.85}.dragover{background-color:rgb(from var(--border-color) r g b / .1)}.fileUploadContentContainer{display:flex;flex-direction:row;gap:1rem}.icon mat-icon{font-size:3rem;width:unset;height:unset}.icon{align-content:center}.labels{flex:1;display:flex;flex-direction:column;justify-content:center;width:fit-content}.uploadButton{align-content:center}.primary{color:var(--primary-color)}.secondary{color:var(--gray)}.label{margin:unset}.subLabel{font-size:smaller}.title{padding-bottom:.3rem}.errorMessage,.uploadedFilesContainer{border-top:2px solid var(--border-color)}.errorMessage{display:flex;flex-direction:row;align-items:center;color:var(--warn-color);padding:.5rem 1rem;gap:1rem}.errorMessage ::ng-deep mat-icon{color:var(--warn-color)}.errors{display:flex;flex-direction:column}.uploadedFile{display:flex;flex-direction:row;justify-content:flex-start;align-items:center;padding:1rem;border-bottom:1px solid #e0e0e0;gap:1rem}.uploadedFile:last-child{border-bottom:none}.fileSize{display:flex;flex-direction:row;font-size:smaller}.fileData{flex:1;display:flex;flex-direction:column}.fileDataContainer{display:flex;flex-direction:column;justify-content:flex-end;width:fit-content}.removeIcon ::ng-deep mat-icon{color:var(--warn-color)}smart-icon ::ng-deep mat-icon{font-size:3rem;width:unset;height:unset}.fileActions{display:flex;flex-direction:row;justify-content:flex-end;gap:.5rem}.removeIcon ::ng-deep i,.removeIcon ::ng-deep mat-icon{color:var(--warn-color)}.removeIcon ::ng-deep mat-icon,.downloadIcon ::ng-deep mat-icon{font-size:2rem}.removeIcon:hover,.downloadIcon:hover{cursor:pointer}smart-icon{display:flex;align-items:center}\n"] }]
9451
+ args: [{ selector: 'smartfileuploader', imports: [NgClass, MatLabel, MatIcon, MatButton, UiActionButtonComponent, SmartIconComponent], template: "<div\n class=\"container\"\n [ngClass]=\"{ disabledWidget: isDisabled() }\"\n (dragover)=\"onDragOver($event)\"\n (dragleave)=\"onDragLeave($event)\"\n (drop)=\"onDrop($event)\"\n [class.dragover]=\"isDragOver\"\n [class.captureOnly]=\"!pickerShown()\"\n>\n @if (pickerShown()) {\n <div class=\"fileContainer\" (click)=\"fileInput.click()\">\n <input\n #fileInput\n id=\"addFile\"\n placeholder=\"fileInput\"\n type=\"file\"\n (change)=\"getFile($event)\"\n class=\"file\"\n accept=\"{{ fileFormats()?.join(', ') }}\"\n [size]=\"maxSizeMb()\"\n [multiple]=\"isMultiple()\"\n [disabled]=\"isDisabled()\"\n style=\"display: none\"\n />\n <div class=\"fileUploadContentContainer\">\n <div class=\"icon\">\n @if (useIconButton()) {\n <mat-label class=\"addFileButton\">\n <mat-icon fontSet=\"material-symbols-outlined\" color=\"primary\" class=\"addCircle\"\n >add_circle</mat-icon\n >\n </mat-label>\n }\n @if (!useIconButton()) {\n <button mat-raised-button color=\"primary\" class=\"addFileButton\">\n {{ i18n!.addFile }}\n </button>\n }\n </div>\n <div class=\"labels\">\n @if (useIconButton()) {\n <mat-label class=\"label primary title\">\n {{ i18n!.addFile }}\n </mat-label>\n }\n <mat-label class=\"label secondary\"> {{ i18n!.browseOrDrag }} </mat-label>\n <mat-label class=\"subLabel primary\"> {{ i18n!.maxSize }} </mat-label>\n <mat-label class=\"subLabel primary\"> {{ i18n!.formats }} </mat-label>\n </div>\n @if (files.length) {\n <div class=\"uploadButton\">\n <ui-action-button\n (actionClick)=\"uploadFile($event)\"\n [code]=\"'default-upload'\"\n [descriptor]=\"uploadButton\"\n >\n </ui-action-button>\n </div>\n }\n </div>\n </div>\n }\n\n @if (errors.length) {\n <div class=\"errorMessage\">\n <smart-icon [icon]=\"'error'\" [color]=\"'warn'\"></smart-icon>\n <div class=\"errors\">\n @for (error of errors; track error) {\n <span>{{ error }}</span>\n }\n </div>\n </div>\n }\n\n @if (files.length) {\n <div class=\"uploadedFilesContainer\">\n @for (file of files; track file; let i = $index) {\n <div class=\"uploadedFile\">\n @if (filePreviewUrl(file)) {\n <img [src]=\"filePreviewUrl(file)\" [alt]=\"file.name\" width=\"50\" height=\"50\" />\n } @else {\n <smart-icon [icon]=\"'insert_drive_file'\"></smart-icon>\n }\n <div class=\"fileData\">\n <div class=\"fileDataContainer\">\n <span class=\"fileName\">{{ file.name }}</span>\n <span class=\"fileSize\">{{ formatSize(file.size) }}</span>\n </div>\n </div>\n <div class=\"fileActions\">\n <smart-icon\n class=\"downloadIcon\"\n icon=\"download\"\n (click)=\"downloadFile(file)\"\n [attr.data-testid]=\"'default_download'\"\n ></smart-icon>\n <smart-icon\n class=\"removeIcon\"\n icon=\"delete\"\n [attr.data-testid]=\"'default_remove'\"\n (click)=\"remove(i)\"\n ></smart-icon>\n </div>\n </div>\n }\n </div>\n }\n</div>\n", styles: [":host{--border-color: #a6aabd60;--def-border-radius: .5rem}.container{display:flex;flex-direction:column;border:2px dashed var(--border-color);border-radius:var(--def-border-radius)}.container.captureOnly{border:none}.fileContainer{cursor:pointer;padding:1rem;display:flex;flex-direction:column}.fileContainer:hover{background-color:rgb(from var(--border-color) r g b / .1)}.disabledWidget ::ng-deep .fileContainer:hover{background-color:unset;cursor:unset}.disabledWidget ::ng-deep *{opacity:.85}.dragover{background-color:rgb(from var(--border-color) r g b / .1)}.fileUploadContentContainer{display:flex;flex-direction:row;gap:1rem}.icon mat-icon{font-size:3rem;width:unset;height:unset}.icon .addCircle{color:var(--primary-color)}.icon{align-content:center}.labels{flex:1;display:flex;flex-direction:column;justify-content:center;width:fit-content}.uploadButton{align-content:center}.primary{color:var(--primary-color)}.secondary{color:var(--gray)}.label{margin:unset}.subLabel{font-size:smaller}.title{padding-bottom:.3rem}.errorMessage,.uploadedFilesContainer{border-top:2px solid var(--border-color)}.errorMessage{display:flex;flex-direction:row;align-items:center;color:var(--warn-color);padding:.5rem 1rem;gap:1rem}.errorMessage ::ng-deep mat-icon{color:var(--warn-color)}.errors{display:flex;flex-direction:column}.uploadedFile{display:flex;flex-direction:row;justify-content:flex-start;align-items:center;padding:1rem;border-bottom:1px solid #e0e0e0;gap:1rem}.uploadedFile:last-child{border-bottom:none}.fileSize{display:flex;flex-direction:row;font-size:smaller}.fileData{flex:1;display:flex;flex-direction:column}.fileDataContainer{display:flex;flex-direction:column;justify-content:flex-end;width:fit-content}.removeIcon ::ng-deep mat-icon{color:var(--warn-color)}smart-icon ::ng-deep mat-icon{font-size:3rem;width:unset;height:unset}.fileActions{display:flex;flex-direction:row;justify-content:flex-end;gap:.5rem}.removeIcon ::ng-deep i,.removeIcon ::ng-deep mat-icon{color:var(--warn-color)}.removeIcon ::ng-deep mat-icon,.downloadIcon ::ng-deep mat-icon{font-size:2rem}.removeIcon:hover,.downloadIcon:hover{cursor:pointer}smart-icon{display:flex;align-items:center}\n"] }]
9236
9452
  }], propDecorators: { fileInput: [{
9237
9453
  type: ViewChild,
9238
9454
  args: ['fileInput']
9239
9455
  }], uploadCallback: [{ type: i0.Input, args: [{ isSignal: true, alias: "uploadCallback", required: true }] }], fileFormats: [{ type: i0.Input, args: [{ isSignal: true, alias: "fileFormats", required: false }] }], maxSizeMb: [{ type: i0.Input, args: [{ isSignal: true, alias: "maxSizeMb", required: false }] }], i18n: [{
9240
9456
  type: Input
9241
- }], useIconButton: [{ type: i0.Input, args: [{ isSignal: true, alias: "useIconButton", required: false }] }], isMultiple: [{ type: i0.Input, args: [{ isSignal: true, alias: "isMultiple", required: false }] }], autoUpload: [{ type: i0.Input, args: [{ isSignal: true, alias: "autoUpload", required: false }] }], isDisabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "isDisabled", required: false }] }] } });
9457
+ }], useIconButton: [{ type: i0.Input, args: [{ isSignal: true, alias: "useIconButton", required: false }] }], isMultiple: [{ type: i0.Input, args: [{ isSignal: true, alias: "isMultiple", required: false }] }], autoUpload: [{ type: i0.Input, args: [{ isSignal: true, alias: "autoUpload", required: false }] }], isDisabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "isDisabled", required: false }] }], pickerShown: [{ type: i0.Input, args: [{ isSignal: true, alias: "pickerShown", required: false }] }] } });
9242
9458
 
9243
9459
  class PhotoCaptureWidgetComponent {
9244
- get cameraActive() {
9245
- return this.cameraActiveState();
9246
- }
9247
- set cameraActive(active) {
9248
- this.cameraActiveState.set(active);
9249
- }
9250
- get isCaptured() {
9251
- return this.isCapturedState();
9252
- }
9253
- set isCaptured(captured) {
9254
- this.isCapturedState.set(captured);
9255
- }
9256
9460
  constructor() {
9461
+ this.injector = inject(Injector);
9462
+ /** The take, record, retake and use buttons, for a host that draws them itself. */
9463
+ this.actionButtonsTemplate = viewChild('actionButtons', /* @ts-ignore */
9464
+ ...(ngDevMode ? [{ debugName: "actionButtonsTemplate" }] : /* istanbul ignore next */ []));
9257
9465
  this.maxFileSize = input(/* @ts-ignore */
9258
9466
  ...(ngDevMode ? [undefined, { debugName: "maxFileSize" }] : /* istanbul ignore next */ []));
9467
+ /** PHOTO takes a picture; VIDEO records a video with the webcam or picks one on a desktop. */
9468
+ this.mode = input('PHOTO', /* @ts-ignore */
9469
+ ...(ngDevMode ? [{ debugName: "mode" }] : /* istanbul ignore next */ []));
9470
+ /** True when the zone's capture row carries the button: the widget then opens only by
9471
+ * openCapture(). */
9472
+ this.externalTrigger = input(false, /* @ts-ignore */
9473
+ ...(ngDevMode ? [{ debugName: "externalTrigger" }] : /* istanbul ignore next */ []));
9474
+ /** The zone's texts; the buttons follow them. */
9475
+ this.texts = input(UPLOAD_ZONE_DEFAULT_TEXTS, /* @ts-ignore */
9476
+ ...(ngDevMode ? [{ debugName: "texts" }] : /* istanbul ignore next */ []));
9477
+ /** AUTO asks for the camera when the widget opens, MANUAL at the switch-on button. */
9478
+ this.deviceStart = input(CaptureDeviceStart.MANUAL, /* @ts-ignore */
9479
+ ...(ngDevMode ? [{ debugName: "deviceStart" }] : /* istanbul ignore next */ []));
9480
+ /** CONFIRM keeps a capture for retake or use, IMMEDIATE hands it over at the take or the stop. */
9481
+ this.handover = input(CaptureHandover.IMMEDIATE, /* @ts-ignore */
9482
+ ...(ngDevMode ? [{ debugName: "handover" }] : /* istanbul ignore next */ []));
9483
+ /** True in a host that draws actionButtonsTemplate: the widget then lays out as the Capture
9484
+ * Dialog's camera view, with the switch inside the view. */
9485
+ this.actionsInHost = input(false, /* @ts-ignore */
9486
+ ...(ngDevMode ? [{ debugName: "actionsInHost" }] : /* istanbul ignore next */ []));
9259
9487
  this.photoCaptured = output();
9488
+ /** The desktop webcam panel of an externally triggered photo widget, opened by openCapture(). */
9489
+ this.isPanelOpen = signal(false, /* @ts-ignore */
9490
+ ...(ngDevMode ? [{ debugName: "isPanelOpen" }] : /* istanbul ignore next */ []));
9260
9491
  /**
9261
9492
  * Both are written after an `await` — the camera stream, the compression — so a signal
9262
9493
  * each: the click that started it has long been checked by then.
9263
9494
  */
9264
9495
  this.cameraActiveState = signal(false, /* @ts-ignore */
9265
9496
  ...(ngDevMode ? [{ debugName: "cameraActiveState" }] : /* istanbul ignore next */ []));
9497
+ /** Set when the browser gives no camera, shown in place of the switch-on hint. */
9498
+ this.cameraError = signal(false, /* @ts-ignore */
9499
+ ...(ngDevMode ? [{ debugName: "cameraError" }] : /* istanbul ignore next */ []));
9500
+ /** True while a camera request runs: AUTO then shows neither the switch nor its hint. */
9501
+ this.isCameraStarting = signal(false, /* @ts-ignore */
9502
+ ...(ngDevMode ? [{ debugName: "isCameraStarting" }] : /* istanbul ignore next */ []));
9503
+ this.isRecordingState = signal(false, /* @ts-ignore */
9504
+ ...(ngDevMode ? [{ debugName: "isRecordingState" }] : /* istanbul ignore next */ []));
9266
9505
  this.isCapturedState = signal(false, /* @ts-ignore */
9267
9506
  ...(ngDevMode ? [{ debugName: "isCapturedState" }] : /* istanbul ignore next */ []));
9268
- this.captureButton = {
9269
- type: UiActionButtonType.RAISED,
9270
- title: 'Kép készítése',
9271
- icon: 'camera',
9272
- iconPosition: IconPosition.PRE,
9273
- color: 'primary',
9274
- };
9275
- this.updateToggleButton();
9507
+ /** A capture kept for retake or use under the CONFIRM handover. */
9508
+ this.keptFiles = signal(undefined, /* @ts-ignore */
9509
+ ...(ngDevMode ? [{ debugName: "keptFiles" }] : /* istanbul ignore next */ []));
9510
+ /** The playback address of a kept video. */
9511
+ this.keptVideoUrl = signal(undefined, /* @ts-ignore */
9512
+ ...(ngDevMode ? [{ debugName: "keptVideoUrl" }] : /* istanbul ignore next */ []));
9513
+ /** The milliseconds of the running video, kept as the length of the stopped one. */
9514
+ this.recordingTime = signal(0, /* @ts-ignore */
9515
+ ...(ngDevMode ? [{ debugName: "recordingTime" }] : /* istanbul ignore next */ []));
9516
+ /** The line on the stage of a running video: a timer in a host, the message on the panel. */
9517
+ this.recordingCaption = computed(() => this.actionsInHost()
9518
+ ? UploadWidgetUtils.formatDuration(this.recordingTime())
9519
+ : this.texts().recording, /* @ts-ignore */
9520
+ ...(ngDevMode ? [{ debugName: "recordingCaption" }] : /* istanbul ignore next */ []));
9521
+ /** The kept capture's line on the stage of a host; the panel has none. */
9522
+ this.capturedCaption = computed(() => {
9523
+ const files = this.keptFiles();
9524
+ if (!files || !this.actionsInHost()) {
9525
+ return undefined;
9526
+ }
9527
+ const size = UploadWidgetUtils.formatCapturedSize(files[0].size);
9528
+ if (this.mode() === 'PHOTO') {
9529
+ return `${this.texts().capturedPhoto} · ${size}`;
9530
+ }
9531
+ const duration = UploadWidgetUtils.formatDuration(this.recordingTime());
9532
+ return `${this.texts().capturedVideo} · ${duration} · ${size}`;
9533
+ }, /* @ts-ignore */
9534
+ ...(ngDevMode ? [{ debugName: "capturedCaption" }] : /* istanbul ignore next */ []));
9535
+ /** True while a host may not lose the capture by accident: a device is asked for or runs, or
9536
+ * a capture waits for confirmation. */
9537
+ this.isCaptureUnderWay = computed(() => this.isCameraStarting() || this.cameraActive || this.isRecording || !!this.keptFiles(), /* @ts-ignore */
9538
+ ...(ngDevMode ? [{ debugName: "isCaptureUnderWay" }] : /* istanbul ignore next */ []));
9539
+ /** Under AUTO and in a host the switch returns when the camera is off, so a second shot stays
9540
+ * possible. */
9541
+ this.isCameraSwitchShown = computed(() => !this.keptFiles() &&
9542
+ ((this.deviceStart() === CaptureDeviceStart.MANUAL && !this.actionsInHost()) ||
9543
+ !(this.cameraActive || this.isCameraStarting())), /* @ts-ignore */
9544
+ ...(ngDevMode ? [{ debugName: "isCameraSwitchShown" }] : /* istanbul ignore next */ []));
9545
+ this.isSwitchOnHintShown = computed(() => this.isCameraSwitchShown() && !this.cameraActive && !this.isCaptured, /* @ts-ignore */
9546
+ ...(ngDevMode ? [{ debugName: "isSwitchOnHintShown" }] : /* istanbul ignore next */ []));
9547
+ this.recordingStartTime = 0;
9548
+ /** Advanced by stopCamera(), so a stream the browser gives after a close is stopped at once. */
9549
+ this.cameraRequest = 0;
9550
+ this.toggleCameraButton = computed(() => this.createButton(this.cameraActive ? this.texts().cameraOff : this.texts().cameraOn, 'power_settings_new', this.cameraActive ? 'red' : 'primary', this.getButtonType(UiActionButtonType.NORMAL)), /* @ts-ignore */
9551
+ ...(ngDevMode ? [{ debugName: "toggleCameraButton" }] : /* istanbul ignore next */ []));
9552
+ this.captureButton = computed(() => this.createButton(this.texts().takePicture, 'camera'), /* @ts-ignore */
9553
+ ...(ngDevMode ? [{ debugName: "captureButton" }] : /* istanbul ignore next */ []));
9554
+ this.startRecordingButton = computed(() => this.createButton(this.texts().startRecording, 'fiber_manual_record'), /* @ts-ignore */
9555
+ ...(ngDevMode ? [{ debugName: "startRecordingButton" }] : /* istanbul ignore next */ []));
9556
+ this.stopRecordingButton = computed(() => this.createButton(this.texts().stopRecording, 'stop', this.actionsInHost() ? 'warn' : 'red'), /* @ts-ignore */
9557
+ ...(ngDevMode ? [{ debugName: "stopRecordingButton" }] : /* istanbul ignore next */ []));
9558
+ this.pickVideoButton = computed(() => this.createButton(this.texts().pickVideo, 'video_library'), /* @ts-ignore */
9559
+ ...(ngDevMode ? [{ debugName: "pickVideoButton" }] : /* istanbul ignore next */ []));
9560
+ this.retakeButton = computed(() => this.createButton(this.texts().retake, 'replay', 'primary', this.getButtonType(UiActionButtonType.NORMAL)), /* @ts-ignore */
9561
+ ...(ngDevMode ? [{ debugName: "retakeButton" }] : /* istanbul ignore next */ []));
9562
+ this.useButton = computed(() => this.createButton(this.texts().use, 'check'), /* @ts-ignore */
9563
+ ...(ngDevMode ? [{ debugName: "useButton" }] : /* istanbul ignore next */ []));
9564
+ }
9565
+ get cameraActive() {
9566
+ return this.cameraActiveState();
9567
+ }
9568
+ set cameraActive(active) {
9569
+ this.cameraActiveState.set(active);
9570
+ }
9571
+ get isRecording() {
9572
+ return this.isRecordingState();
9573
+ }
9574
+ set isRecording(recording) {
9575
+ this.isRecordingState.set(recording);
9576
+ }
9577
+ get isCaptured() {
9578
+ return this.isCapturedState();
9579
+ }
9580
+ set isCaptured(captured) {
9581
+ this.isCapturedState.set(captured);
9276
9582
  }
9277
9583
  isMobile() {
9278
9584
  return /iPhone|iPad|iPod|Android/i.test(navigator.userAgent);
9279
9585
  }
9586
+ /** A phone opens its own camera, a desktop the webcam panel. */
9587
+ openCapture() {
9588
+ if (this.isMobile()) {
9589
+ this.fileInputRef.nativeElement.click();
9590
+ return;
9591
+ }
9592
+ if (this.isPanelOpen()) {
9593
+ this.stopCamera();
9594
+ this.discardKeptCapture();
9595
+ }
9596
+ else {
9597
+ this.startCameraAfterRender();
9598
+ }
9599
+ this.isPanelOpen.update((open) => !open);
9600
+ }
9601
+ ngAfterViewInit() {
9602
+ if (!this.externalTrigger()) {
9603
+ this.startCameraAfterRender();
9604
+ }
9605
+ }
9606
+ ngOnDestroy() {
9607
+ this.stopCamera();
9608
+ this.discardKeptCapture();
9609
+ }
9610
+ /** AUTO waits for the render: the stream goes into the panel's video element. */
9611
+ startCameraAfterRender() {
9612
+ if (this.deviceStart() !== CaptureDeviceStart.AUTO || this.isMobile()) {
9613
+ return;
9614
+ }
9615
+ afterNextRender(() => {
9616
+ if (!this.externalTrigger() || this.isPanelOpen()) {
9617
+ this.startCamera();
9618
+ }
9619
+ }, { injector: this.injector });
9620
+ }
9280
9621
  async startCamera() {
9281
- this.stream = await navigator.mediaDevices.getUserMedia({ video: true });
9622
+ this.cameraError.set(false);
9623
+ this.isCameraStarting.set(true);
9624
+ const request = ++this.cameraRequest;
9625
+ let stream;
9626
+ try {
9627
+ stream = await this.openCameraStream();
9628
+ }
9629
+ catch {
9630
+ if (request === this.cameraRequest) {
9631
+ this.isCameraStarting.set(false);
9632
+ this.cameraError.set(true);
9633
+ }
9634
+ return;
9635
+ }
9636
+ if (request !== this.cameraRequest) {
9637
+ stream.getTracks().forEach((track) => track.stop());
9638
+ return;
9639
+ }
9640
+ this.stream = stream;
9282
9641
  this.videoRef.nativeElement.srcObject = this.stream;
9283
9642
  this.videoRef.nativeElement.play();
9643
+ this.isCameraStarting.set(false);
9284
9644
  this.cameraActive = true;
9285
9645
  this.isCaptured = false;
9286
- this.updateToggleButton();
9287
9646
  }
9647
+ /** A video asks for the sound too; a desktop without a microphone records a silent one. */
9648
+ async openCameraStream() {
9649
+ if (this.mode() === 'VIDEO') {
9650
+ try {
9651
+ return await navigator.mediaDevices.getUserMedia({ video: true, audio: true });
9652
+ }
9653
+ catch {
9654
+ // No microphone answers: the camera alone is asked below.
9655
+ }
9656
+ }
9657
+ return navigator.mediaDevices.getUserMedia({ video: true });
9658
+ }
9659
+ /** Closing the camera mid-recording discards the recording. */
9288
9660
  stopCamera() {
9661
+ this.cameraRequest++;
9662
+ this.isCameraStarting.set(false);
9663
+ clearInterval(this.recordingClock);
9664
+ if (this.recorder) {
9665
+ this.recorder.onstop = null;
9666
+ this.recorder.stop();
9667
+ this.recorder = undefined;
9668
+ }
9669
+ this.isRecording = false;
9289
9670
  this.stream?.getTracks().forEach((track) => track.stop());
9290
9671
  this.stream = undefined;
9291
9672
  this.cameraActive = false;
9292
- this.updateToggleButton();
9293
9673
  }
9294
9674
  async capturePhoto() {
9295
9675
  const video = this.videoRef.nativeElement;
@@ -9308,10 +9688,74 @@ class PhotoCaptureWidgetComponent {
9308
9688
  if (maxFileSize && file.size > maxFileSize) {
9309
9689
  file = await this.compressImageFile(file, maxFileSize);
9310
9690
  }
9311
- this.photoCaptured.emit([file]);
9691
+ this.handOverCapture([file]);
9312
9692
  this.isCaptured = true;
9313
9693
  this.stopCamera();
9314
9694
  }
9695
+ /** A browser that records neither webm nor mp4 refuses the recorder: the camera error shows. */
9696
+ startRecording() {
9697
+ let recorder;
9698
+ try {
9699
+ recorder = new MediaRecorder(this.stream, {
9700
+ mimeType: UploadWidgetUtils.chooseRecorderMimeType('video'),
9701
+ });
9702
+ }
9703
+ catch {
9704
+ this.cameraError.set(true);
9705
+ return;
9706
+ }
9707
+ const chunks = [];
9708
+ recorder.ondataavailable = (event) => chunks.push(event.data);
9709
+ recorder.onstop = () => {
9710
+ const blob = new Blob(chunks, { type: recorder.mimeType });
9711
+ this.recorder = undefined;
9712
+ this.updateRecordingTime();
9713
+ this.handOverCapture([UploadWidgetUtils.createRecordingFile(blob, 'video', 'video/mp4')]);
9714
+ this.stopCamera();
9715
+ };
9716
+ recorder.start();
9717
+ this.recorder = recorder;
9718
+ this.isRecording = true;
9719
+ this.recordingStartTime = Date.now();
9720
+ this.recordingTime.set(0);
9721
+ this.recordingClock = setInterval(() => this.updateRecordingTime(), 1000);
9722
+ }
9723
+ updateRecordingTime() {
9724
+ this.recordingTime.set(Date.now() - this.recordingStartTime);
9725
+ }
9726
+ /** The recording state holds until onstop hands the video over and stops the camera. */
9727
+ stopRecording() {
9728
+ this.recorder?.stop();
9729
+ }
9730
+ handOverCapture(files) {
9731
+ if (this.handover() === CaptureHandover.IMMEDIATE) {
9732
+ this.photoCaptured.emit(files);
9733
+ return;
9734
+ }
9735
+ this.keptFiles.set(files);
9736
+ if (this.mode() === 'VIDEO') {
9737
+ this.keptVideoUrl.set(URL.createObjectURL(files[0]));
9738
+ }
9739
+ }
9740
+ retakeCapture() {
9741
+ this.discardKeptCapture();
9742
+ if (this.deviceStart() === CaptureDeviceStart.AUTO) {
9743
+ this.startCamera();
9744
+ }
9745
+ }
9746
+ useCapture() {
9747
+ this.photoCaptured.emit(this.keptFiles());
9748
+ this.discardKeptCapture();
9749
+ }
9750
+ discardKeptCapture() {
9751
+ const videoUrl = this.keptVideoUrl();
9752
+ if (videoUrl) {
9753
+ URL.revokeObjectURL(videoUrl);
9754
+ }
9755
+ this.keptVideoUrl.set(undefined);
9756
+ this.keptFiles.set(undefined);
9757
+ this.isCaptured = false;
9758
+ }
9315
9759
  getPhotoDataURL() {
9316
9760
  return this.canvasRef.nativeElement.toDataURL('image/png');
9317
9761
  }
@@ -9346,26 +9790,6 @@ class PhotoCaptureWidgetComponent {
9346
9790
  this.startCamera();
9347
9791
  }
9348
9792
  }
9349
- updateToggleButton() {
9350
- let icon = 'power_settings_new';
9351
- let title;
9352
- let color;
9353
- if (this.cameraActive) {
9354
- title = 'Kamera kikapcsolása';
9355
- color = 'red';
9356
- }
9357
- else {
9358
- title = 'Kamera bekapcsolása';
9359
- color = 'primary';
9360
- }
9361
- this.toggleCameraButton = {
9362
- type: UiActionButtonType.RAISED,
9363
- title: title,
9364
- icon: icon,
9365
- color,
9366
- iconPosition: IconPosition.PRE,
9367
- };
9368
- }
9369
9793
  async compressImageFile(file, maxFileSize) {
9370
9794
  if (!file.type.startsWith('image/'))
9371
9795
  return file;
@@ -9423,13 +9847,20 @@ class PhotoCaptureWidgetComponent {
9423
9847
  return file;
9424
9848
  }
9425
9849
  }
9850
+ /** The inline bar's buttons are all raised; a host's view tells them apart by type. */
9851
+ getButtonType(typeInHost) {
9852
+ return this.actionsInHost() ? typeInHost : UiActionButtonType.RAISED;
9853
+ }
9854
+ createButton(title, icon, color = 'primary', type = UiActionButtonType.RAISED) {
9855
+ return { type, title, icon, iconPosition: IconPosition.PRE, color };
9856
+ }
9426
9857
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: PhotoCaptureWidgetComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
9427
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.8", type: PhotoCaptureWidgetComponent, isStandalone: true, selector: "photo-capture-widget", inputs: { maxFileSize: { classPropertyName: "maxFileSize", publicName: "maxFileSize", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { photoCaptured: "photoCaptured" }, viewQueries: [{ propertyName: "videoRef", first: true, predicate: ["video"], descendants: true }, { propertyName: "canvasRef", first: true, predicate: ["canvas"], descendants: true }, { propertyName: "fileInputRef", first: true, predicate: ["fileInput"], descendants: true }], ngImport: i0, template: "<div class=\"photoWidgetContainer\">\n @if (!isMobile()) {\n <video #video autoplay [hidden]=\"isCaptured || !cameraActive\"></video>\n <canvas #canvas [hidden]=\"!isCaptured || cameraActive\"></canvas>\n @if (!cameraActive && !isCaptured) {\n <p class=\"message\">Kapcsolja be a kamer\u00E1t a k\u00E9p k\u00E9sz\u00EDt\u00E9s\u00E9hez!</p>\n }\n <div class=\"controls\">\n <!-- START CAMERA -->\n <ui-action-button [descriptor]=\"toggleCameraButton\" (actionClick)=\"toggleCamera()\">\n </ui-action-button>\n <!-- CAPTURE CAMERA -->\n <ui-action-button\n [disabled]=\"!cameraActive\"\n [descriptor]=\"captureButton\"\n (actionClick)=\"capturePhoto()\"\n >\n </ui-action-button>\n </div>\n }\n\n @if (isMobile()) {\n <div class=\"controls\">\n <input\n #fileInput\n type=\"file\"\n accept=\"image/*\"\n capture=\"environment\"\n (change)=\"onFileSelected($event)\"\n style=\"display: none\"\n />\n <ui-action-button [descriptor]=\"captureButton\" (actionClick)=\"fileInput.click()\">\n </ui-action-button>\n </div>\n }\n</div>\n", styles: [".photoWidgetContainer{border-radius:5px;width:100%;max-width:100%;display:flex;flex-direction:column;justify-content:center;border:1px solid #dee2e6}.message{text-align:center;color:#6b7280}video,canvas{border-top-right-radius:5px;border-top-left-radius:5px;width:100%}.controls{padding:.5rem;display:flex;gap:1rem;flex-direction:row;justify-content:center;background:#f8f9fa;border-top:1px solid #dee2e6;border-bottom-right-radius:5px;border-bottom-left-radius:5px}.controls ::ng-deep button{border-radius:3px}\n"], dependencies: [{ kind: "component", type: UiActionButtonComponent, selector: "ui-action-button", inputs: ["disabled", "descriptor", "code", "identifier", "viewActivated", "tab", "rowState", "addedCssClass", "addBasicClasses", "iconPlaceholder", "chords"], outputs: ["actionClick", "actionDoubleClick"] }] }); }
9858
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.8", type: PhotoCaptureWidgetComponent, isStandalone: true, selector: "photo-capture-widget", inputs: { maxFileSize: { classPropertyName: "maxFileSize", publicName: "maxFileSize", isSignal: true, isRequired: false, transformFunction: null }, mode: { classPropertyName: "mode", publicName: "mode", isSignal: true, isRequired: false, transformFunction: null }, externalTrigger: { classPropertyName: "externalTrigger", publicName: "externalTrigger", isSignal: true, isRequired: false, transformFunction: null }, texts: { classPropertyName: "texts", publicName: "texts", isSignal: true, isRequired: false, transformFunction: null }, deviceStart: { classPropertyName: "deviceStart", publicName: "deviceStart", isSignal: true, isRequired: false, transformFunction: null }, handover: { classPropertyName: "handover", publicName: "handover", isSignal: true, isRequired: false, transformFunction: null }, actionsInHost: { classPropertyName: "actionsInHost", publicName: "actionsInHost", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { photoCaptured: "photoCaptured" }, viewQueries: [{ propertyName: "actionButtonsTemplate", first: true, predicate: ["actionButtons"], descendants: true, isSignal: true }, { propertyName: "videoRef", first: true, predicate: ["video"], descendants: true }, { propertyName: "canvasRef", first: true, predicate: ["canvas"], descendants: true }, { propertyName: "fileInputRef", first: true, predicate: ["fileInput"], descendants: true }], ngImport: i0, template: "@if (isMobile()) {\n <input\n #fileInput\n type=\"file\"\n [accept]=\"mode() === 'VIDEO' ? 'video/*' : 'image/*'\"\n capture=\"environment\"\n (change)=\"onFileSelected($event)\"\n style=\"display: none\"\n />\n @if (!externalTrigger()) {\n <div class=\"photoWidgetContainer\">\n <div class=\"controls\">\n <ui-action-button [descriptor]=\"captureButton()\" (actionClick)=\"fileInput.click()\">\n </ui-action-button>\n </div>\n </div>\n }\n} @else if (!externalTrigger() || isPanelOpen()) {\n <div class=\"photoWidgetContainer\" [class.actionsInHost]=\"actionsInHost()\">\n <div class=\"cameraStage\">\n <video #video autoplay [muted]=\"true\" [hidden]=\"isCaptured || !cameraActive\"></video>\n <canvas #canvas [hidden]=\"!isCaptured || cameraActive\"></canvas>\n @if (keptVideoUrl(); as videoUrl) {\n <video controls [src]=\"videoUrl\"></video>\n }\n @if (cameraError()) {\n <p class=\"message cameraErrorMessage\">{{ texts().cameraError }}</p>\n } @else if (capturedCaption(); as caption) {\n <p class=\"message capturedCaption\">{{ caption }}</p>\n } @else if (isRecording) {\n <p class=\"message recordingMessage\">{{ recordingCaption() }}</p>\n } @else if (isSwitchOnHintShown()) {\n @if (mode() === 'VIDEO') {\n <p class=\"message\">{{ texts().switchOnForVideo }}</p>\n } @else {\n <p class=\"message\">{{ texts().switchOnCameraHint }}</p>\n }\n }\n @if (actionsInHost()) {\n <ng-container [ngTemplateOutlet]=\"cameraSwitch\" />\n }\n </div>\n @if (!actionsInHost()) {\n <div class=\"controls\">\n <ng-container [ngTemplateOutlet]=\"cameraSwitch\" />\n <ng-container [ngTemplateOutlet]=\"actionButtons\" />\n @if (mode() === 'VIDEO' && !keptFiles()) {\n <!-- PICK VIDEO FILE -->\n <input\n #fileInput\n type=\"file\"\n accept=\"video/*\"\n (change)=\"onFileSelected($event)\"\n style=\"display: none\"\n />\n <ui-action-button\n class=\"pickVideoButton\"\n [disabled]=\"isRecording\"\n [descriptor]=\"pickVideoButton()\"\n (actionClick)=\"fileInput.click()\"\n >\n </ui-action-button>\n }\n </div>\n }\n </div>\n}\n\n<ng-template #cameraSwitch>\n @if (isCameraSwitchShown()) {\n <!-- START CAMERA -->\n <ui-action-button\n class=\"toggleCameraButton\"\n [disabled]=\"isRecording\"\n [descriptor]=\"toggleCameraButton()\"\n (actionClick)=\"toggleCamera()\"\n >\n </ui-action-button>\n }\n</ng-template>\n\n<ng-template #actionButtons>\n @if (keptFiles()) {\n <ui-action-button\n class=\"retakeButton\"\n [descriptor]=\"retakeButton()\"\n (actionClick)=\"retakeCapture()\"\n >\n </ui-action-button>\n <ui-action-button\n class=\"useButton\"\n [descriptor]=\"useButton()\"\n (actionClick)=\"useCapture()\"\n >\n </ui-action-button>\n } @else if (mode() === 'VIDEO') {\n <!-- RECORD / STOP VIDEO -->\n @if (isRecording) {\n <ui-action-button\n class=\"stopRecordingButton\"\n [descriptor]=\"stopRecordingButton()\"\n (actionClick)=\"stopRecording()\"\n >\n </ui-action-button>\n } @else {\n <ui-action-button\n class=\"startRecordingButton\"\n [disabled]=\"!cameraActive\"\n [descriptor]=\"startRecordingButton()\"\n (actionClick)=\"startRecording()\"\n >\n </ui-action-button>\n }\n } @else {\n <!-- CAPTURE CAMERA -->\n <ui-action-button\n class=\"capturePhotoButton\"\n [disabled]=\"!cameraActive\"\n [descriptor]=\"captureButton()\"\n (actionClick)=\"capturePhoto()\"\n >\n </ui-action-button>\n }\n</ng-template>\n", styles: [".photoWidgetContainer{border-radius:5px;width:100%;max-width:100%;display:flex;flex-direction:column;justify-content:center;border:1px solid #dee2e6}.message{text-align:center;color:#6b7280}video,canvas{border-top-right-radius:5px;border-top-left-radius:5px;width:100%}.controls{padding:.5rem;display:flex;gap:1rem;flex-direction:row;justify-content:center;background:#f8f9fa;border-top:1px solid #dee2e6;border-bottom-right-radius:5px;border-bottom-left-radius:5px}.controls ::ng-deep button{border-radius:3px}.cameraStage{display:contents}.actionsInHost{border:unset}.actionsInHost .cameraStage{position:relative;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:.5rem;aspect-ratio:16 / 9;overflow:hidden;border-radius:4px;--stage-background: var( --capture-stage-background, color-mix(in srgb, var(--primary-color) 30%, black) );background:var(--stage-background);color:var(--capture-stage-text, color-mix(in srgb, var(--primary-color) 10%, white))}.actionsInHost .cameraStage video,.actionsInHost .cameraStage canvas{position:absolute;inset:0;width:100%;height:100%;object-fit:contain;border-radius:unset}.actionsInHost .toggleCameraButton ::ng-deep button{background:var(--color-surface)}.actionsInHost .message{margin:0;color:inherit}.actionsInHost .cameraErrorMessage{position:relative;color:color-mix(in srgb,var(--warn-color) 65%,white)}.actionsInHost .recordingMessage,.actionsInHost .capturedCaption{position:absolute;left:.5rem;top:.5rem;padding:.125rem .5rem;border-radius:.5rem;font-size:.75rem;font-weight:500}.actionsInHost .recordingMessage{background:color-mix(in srgb,var(--stage-background) 67%,transparent);font-family:monospace}.actionsInHost .capturedCaption{background:color-mix(in srgb,var(--success-color) 50%,black)}.actionsInHost .recordingMessage:before{content:\"\";display:inline-block;width:.5rem;height:.5rem;margin-right:.3125rem;border-radius:50%;background:var(--warn-color)}\n"], dependencies: [{ kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: UiActionButtonComponent, selector: "ui-action-button", inputs: ["disabled", "descriptor", "code", "identifier", "viewActivated", "tab", "rowState", "addedCssClass", "addBasicClasses", "iconPlaceholder", "chords"], outputs: ["actionClick", "actionDoubleClick"] }] }); }
9428
9859
  }
9429
9860
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: PhotoCaptureWidgetComponent, decorators: [{
9430
9861
  type: Component,
9431
- args: [{ selector: 'photo-capture-widget', imports: [UiActionButtonComponent], template: "<div class=\"photoWidgetContainer\">\n @if (!isMobile()) {\n <video #video autoplay [hidden]=\"isCaptured || !cameraActive\"></video>\n <canvas #canvas [hidden]=\"!isCaptured || cameraActive\"></canvas>\n @if (!cameraActive && !isCaptured) {\n <p class=\"message\">Kapcsolja be a kamer\u00E1t a k\u00E9p k\u00E9sz\u00EDt\u00E9s\u00E9hez!</p>\n }\n <div class=\"controls\">\n <!-- START CAMERA -->\n <ui-action-button [descriptor]=\"toggleCameraButton\" (actionClick)=\"toggleCamera()\">\n </ui-action-button>\n <!-- CAPTURE CAMERA -->\n <ui-action-button\n [disabled]=\"!cameraActive\"\n [descriptor]=\"captureButton\"\n (actionClick)=\"capturePhoto()\"\n >\n </ui-action-button>\n </div>\n }\n\n @if (isMobile()) {\n <div class=\"controls\">\n <input\n #fileInput\n type=\"file\"\n accept=\"image/*\"\n capture=\"environment\"\n (change)=\"onFileSelected($event)\"\n style=\"display: none\"\n />\n <ui-action-button [descriptor]=\"captureButton\" (actionClick)=\"fileInput.click()\">\n </ui-action-button>\n </div>\n }\n</div>\n", styles: [".photoWidgetContainer{border-radius:5px;width:100%;max-width:100%;display:flex;flex-direction:column;justify-content:center;border:1px solid #dee2e6}.message{text-align:center;color:#6b7280}video,canvas{border-top-right-radius:5px;border-top-left-radius:5px;width:100%}.controls{padding:.5rem;display:flex;gap:1rem;flex-direction:row;justify-content:center;background:#f8f9fa;border-top:1px solid #dee2e6;border-bottom-right-radius:5px;border-bottom-left-radius:5px}.controls ::ng-deep button{border-radius:3px}\n"] }]
9432
- }], ctorParameters: () => [], propDecorators: { videoRef: [{
9862
+ args: [{ selector: 'photo-capture-widget', imports: [NgTemplateOutlet, UiActionButtonComponent], template: "@if (isMobile()) {\n <input\n #fileInput\n type=\"file\"\n [accept]=\"mode() === 'VIDEO' ? 'video/*' : 'image/*'\"\n capture=\"environment\"\n (change)=\"onFileSelected($event)\"\n style=\"display: none\"\n />\n @if (!externalTrigger()) {\n <div class=\"photoWidgetContainer\">\n <div class=\"controls\">\n <ui-action-button [descriptor]=\"captureButton()\" (actionClick)=\"fileInput.click()\">\n </ui-action-button>\n </div>\n </div>\n }\n} @else if (!externalTrigger() || isPanelOpen()) {\n <div class=\"photoWidgetContainer\" [class.actionsInHost]=\"actionsInHost()\">\n <div class=\"cameraStage\">\n <video #video autoplay [muted]=\"true\" [hidden]=\"isCaptured || !cameraActive\"></video>\n <canvas #canvas [hidden]=\"!isCaptured || cameraActive\"></canvas>\n @if (keptVideoUrl(); as videoUrl) {\n <video controls [src]=\"videoUrl\"></video>\n }\n @if (cameraError()) {\n <p class=\"message cameraErrorMessage\">{{ texts().cameraError }}</p>\n } @else if (capturedCaption(); as caption) {\n <p class=\"message capturedCaption\">{{ caption }}</p>\n } @else if (isRecording) {\n <p class=\"message recordingMessage\">{{ recordingCaption() }}</p>\n } @else if (isSwitchOnHintShown()) {\n @if (mode() === 'VIDEO') {\n <p class=\"message\">{{ texts().switchOnForVideo }}</p>\n } @else {\n <p class=\"message\">{{ texts().switchOnCameraHint }}</p>\n }\n }\n @if (actionsInHost()) {\n <ng-container [ngTemplateOutlet]=\"cameraSwitch\" />\n }\n </div>\n @if (!actionsInHost()) {\n <div class=\"controls\">\n <ng-container [ngTemplateOutlet]=\"cameraSwitch\" />\n <ng-container [ngTemplateOutlet]=\"actionButtons\" />\n @if (mode() === 'VIDEO' && !keptFiles()) {\n <!-- PICK VIDEO FILE -->\n <input\n #fileInput\n type=\"file\"\n accept=\"video/*\"\n (change)=\"onFileSelected($event)\"\n style=\"display: none\"\n />\n <ui-action-button\n class=\"pickVideoButton\"\n [disabled]=\"isRecording\"\n [descriptor]=\"pickVideoButton()\"\n (actionClick)=\"fileInput.click()\"\n >\n </ui-action-button>\n }\n </div>\n }\n </div>\n}\n\n<ng-template #cameraSwitch>\n @if (isCameraSwitchShown()) {\n <!-- START CAMERA -->\n <ui-action-button\n class=\"toggleCameraButton\"\n [disabled]=\"isRecording\"\n [descriptor]=\"toggleCameraButton()\"\n (actionClick)=\"toggleCamera()\"\n >\n </ui-action-button>\n }\n</ng-template>\n\n<ng-template #actionButtons>\n @if (keptFiles()) {\n <ui-action-button\n class=\"retakeButton\"\n [descriptor]=\"retakeButton()\"\n (actionClick)=\"retakeCapture()\"\n >\n </ui-action-button>\n <ui-action-button\n class=\"useButton\"\n [descriptor]=\"useButton()\"\n (actionClick)=\"useCapture()\"\n >\n </ui-action-button>\n } @else if (mode() === 'VIDEO') {\n <!-- RECORD / STOP VIDEO -->\n @if (isRecording) {\n <ui-action-button\n class=\"stopRecordingButton\"\n [descriptor]=\"stopRecordingButton()\"\n (actionClick)=\"stopRecording()\"\n >\n </ui-action-button>\n } @else {\n <ui-action-button\n class=\"startRecordingButton\"\n [disabled]=\"!cameraActive\"\n [descriptor]=\"startRecordingButton()\"\n (actionClick)=\"startRecording()\"\n >\n </ui-action-button>\n }\n } @else {\n <!-- CAPTURE CAMERA -->\n <ui-action-button\n class=\"capturePhotoButton\"\n [disabled]=\"!cameraActive\"\n [descriptor]=\"captureButton()\"\n (actionClick)=\"capturePhoto()\"\n >\n </ui-action-button>\n }\n</ng-template>\n", styles: [".photoWidgetContainer{border-radius:5px;width:100%;max-width:100%;display:flex;flex-direction:column;justify-content:center;border:1px solid #dee2e6}.message{text-align:center;color:#6b7280}video,canvas{border-top-right-radius:5px;border-top-left-radius:5px;width:100%}.controls{padding:.5rem;display:flex;gap:1rem;flex-direction:row;justify-content:center;background:#f8f9fa;border-top:1px solid #dee2e6;border-bottom-right-radius:5px;border-bottom-left-radius:5px}.controls ::ng-deep button{border-radius:3px}.cameraStage{display:contents}.actionsInHost{border:unset}.actionsInHost .cameraStage{position:relative;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:.5rem;aspect-ratio:16 / 9;overflow:hidden;border-radius:4px;--stage-background: var( --capture-stage-background, color-mix(in srgb, var(--primary-color) 30%, black) );background:var(--stage-background);color:var(--capture-stage-text, color-mix(in srgb, var(--primary-color) 10%, white))}.actionsInHost .cameraStage video,.actionsInHost .cameraStage canvas{position:absolute;inset:0;width:100%;height:100%;object-fit:contain;border-radius:unset}.actionsInHost .toggleCameraButton ::ng-deep button{background:var(--color-surface)}.actionsInHost .message{margin:0;color:inherit}.actionsInHost .cameraErrorMessage{position:relative;color:color-mix(in srgb,var(--warn-color) 65%,white)}.actionsInHost .recordingMessage,.actionsInHost .capturedCaption{position:absolute;left:.5rem;top:.5rem;padding:.125rem .5rem;border-radius:.5rem;font-size:.75rem;font-weight:500}.actionsInHost .recordingMessage{background:color-mix(in srgb,var(--stage-background) 67%,transparent);font-family:monospace}.actionsInHost .capturedCaption{background:color-mix(in srgb,var(--success-color) 50%,black)}.actionsInHost .recordingMessage:before{content:\"\";display:inline-block;width:.5rem;height:.5rem;margin-right:.3125rem;border-radius:50%;background:var(--warn-color)}\n"] }]
9863
+ }], propDecorators: { videoRef: [{
9433
9864
  type: ViewChild,
9434
9865
  args: ['video']
9435
9866
  }], canvasRef: [{
@@ -9438,20 +9869,51 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImpor
9438
9869
  }], fileInputRef: [{
9439
9870
  type: ViewChild,
9440
9871
  args: ['fileInput']
9441
- }], maxFileSize: [{ type: i0.Input, args: [{ isSignal: true, alias: "maxFileSize", required: false }] }], photoCaptured: [{ type: i0.Output, args: ["photoCaptured"] }] } });
9872
+ }], actionButtonsTemplate: [{ type: i0.ViewChild, args: ['actionButtons', { isSignal: true }] }], maxFileSize: [{ type: i0.Input, args: [{ isSignal: true, alias: "maxFileSize", required: false }] }], mode: [{ type: i0.Input, args: [{ isSignal: true, alias: "mode", required: false }] }], externalTrigger: [{ type: i0.Input, args: [{ isSignal: true, alias: "externalTrigger", required: false }] }], texts: [{ type: i0.Input, args: [{ isSignal: true, alias: "texts", required: false }] }], deviceStart: [{ type: i0.Input, args: [{ isSignal: true, alias: "deviceStart", required: false }] }], handover: [{ type: i0.Input, args: [{ isSignal: true, alias: "handover", required: false }] }], actionsInHost: [{ type: i0.Input, args: [{ isSignal: true, alias: "actionsInHost", required: false }] }], photoCaptured: [{ type: i0.Output, args: ["photoCaptured"] }] } });
9442
9873
 
9443
9874
  class VoiceRecordWidgetComponent {
9444
9875
  constructor() {
9445
9876
  this.ngZone = inject(NgZone);
9446
9877
  this.changeDetectorRef = inject(ChangeDetectorRef);
9878
+ /** The stop and save buttons, for a host that draws them itself. */
9879
+ this.actionButtonsTemplate = viewChild('actionButtons', /* @ts-ignore */
9880
+ ...(ngDevMode ? [{ debugName: "actionButtonsTemplate" }] : /* istanbul ignore next */ []));
9447
9881
  this.openDirection = input('RIGHT', /* @ts-ignore */
9448
9882
  ...(ngDevMode ? [{ debugName: "openDirection" }] : /* istanbul ignore next */ []));
9883
+ /** The zone's texts, the microphone error line's among them. */
9884
+ this.texts = input(UPLOAD_ZONE_DEFAULT_TEXTS, /* @ts-ignore */
9885
+ ...(ngDevMode ? [{ debugName: "texts" }] : /* istanbul ignore next */ []));
9886
+ /** True in the upload zone only; the deprecated recordingUploader shows no error line. */
9887
+ this.showMicrophoneError = input(false, /* @ts-ignore */
9888
+ ...(ngDevMode ? [{ debugName: "showMicrophoneError" }] : /* istanbul ignore next */ []));
9889
+ /** AUTO starts the recording when the widget opens, MANUAL at the microphone button. */
9890
+ this.deviceStart = input(CaptureDeviceStart.MANUAL, /* @ts-ignore */
9891
+ ...(ngDevMode ? [{ debugName: "deviceStart" }] : /* istanbul ignore next */ []));
9892
+ /** CONFIRM keeps a stopped recording for playback and save, IMMEDIATE hands it over. */
9893
+ this.handover = input(CaptureHandover.CONFIRM, /* @ts-ignore */
9894
+ ...(ngDevMode ? [{ debugName: "handover" }] : /* istanbul ignore next */ []));
9895
+ /** True under the zone's Capture Settings; everywhere else the save stays an icon. */
9896
+ this.showSaveTitle = input(false, /* @ts-ignore */
9897
+ ...(ngDevMode ? [{ debugName: "showSaveTitle" }] : /* istanbul ignore next */ []));
9898
+ /** True in a host that draws actionButtonsTemplate: the strip keeps no save of its own. */
9899
+ this.actionsInHost = input(false, /* @ts-ignore */
9900
+ ...(ngDevMode ? [{ debugName: "actionsInHost" }] : /* istanbul ignore next */ []));
9449
9901
  this.recordingSaved = output();
9450
- this.isRecording = false;
9451
- this.recordedBlob = null;
9902
+ /** Signals, as the host reads them through isCaptureUnderWay and actionButtonsTemplate. */
9903
+ this.isRecording = signal(false, /* @ts-ignore */
9904
+ ...(ngDevMode ? [{ debugName: "isRecording" }] : /* istanbul ignore next */ []));
9905
+ this.recordedBlob = signal(null, /* @ts-ignore */
9906
+ ...(ngDevMode ? [{ debugName: "recordedBlob" }] : /* istanbul ignore next */ []));
9907
+ /** True while a host may not lose the capture by accident: the microphone is asked for, the
9908
+ * recording runs or waits for the save. */
9909
+ this.isCaptureUnderWay = computed(() => this.isRecording() || !!this.recordedBlob(), /* @ts-ignore */
9910
+ ...(ngDevMode ? [{ debugName: "isCaptureUnderWay" }] : /* istanbul ignore next */ []));
9452
9911
  this.time = '00:00';
9453
9912
  this.isPlaying = false;
9454
9913
  this.isWaveformOpen = false;
9914
+ /** Set when the browser gives no microphone, until the next start. */
9915
+ this.microphoneError = signal(false, /* @ts-ignore */
9916
+ ...(ngDevMode ? [{ debugName: "microphoneError" }] : /* istanbul ignore next */ []));
9455
9917
  this.micButton = {
9456
9918
  title: '',
9457
9919
  type: UiActionButtonType.ICON,
@@ -9467,6 +9929,9 @@ class VoiceRecordWidgetComponent {
9467
9929
  iconColor: 'red',
9468
9930
  color: 'primary',
9469
9931
  };
9932
+ /** Only a theme colour's name paints the icon: the deprecated recorder keeps its look. */
9933
+ this.effectiveStopButton = computed(() => this.actionsInHost() ? { ...this.stopButton, color: 'warn', iconColor: 'warn' } : this.stopButton, /* @ts-ignore */
9934
+ ...(ngDevMode ? [{ debugName: "effectiveStopButton" }] : /* istanbul ignore next */ []));
9470
9935
  this.saveButton = {
9471
9936
  title: '',
9472
9937
  type: UiActionButtonType.ICON,
@@ -9474,6 +9939,18 @@ class VoiceRecordWidgetComponent {
9474
9939
  iconPosition: IconPosition.PRE,
9475
9940
  color: 'primary',
9476
9941
  };
9942
+ this.effectiveSaveButton = computed(() => this.showSaveTitle()
9943
+ ? { ...this.saveButton, title: this.texts().save, type: UiActionButtonType.RAISED }
9944
+ : this.saveButton, /* @ts-ignore */
9945
+ ...(ngDevMode ? [{ debugName: "effectiveSaveButton" }] : /* istanbul ignore next */ []));
9946
+ this.stopRecordingButton = computed(() => ({
9947
+ title: this.texts().stopRecording,
9948
+ type: UiActionButtonType.RAISED,
9949
+ icon: 'stop',
9950
+ iconPosition: IconPosition.PRE,
9951
+ color: 'warn',
9952
+ }), /* @ts-ignore */
9953
+ ...(ngDevMode ? [{ debugName: "stopRecordingButton" }] : /* istanbul ignore next */ []));
9477
9954
  }
9478
9955
  ngOnInit() {
9479
9956
  this.playButton = {
@@ -9488,10 +9965,14 @@ class VoiceRecordWidgetComponent {
9488
9965
  type: UiActionButtonType.ICON,
9489
9966
  icon: 'stop_circle',
9490
9967
  iconPosition: IconPosition.PRE,
9491
- color: 'red',
9968
+ color: this.actionsInHost() ? 'warn' : 'red',
9492
9969
  };
9493
9970
  }
9494
- ngAfterViewInit() { }
9971
+ ngAfterViewInit() {
9972
+ if (this.deviceStart() === CaptureDeviceStart.AUTO) {
9973
+ this.requestRecording();
9974
+ }
9975
+ }
9495
9976
  get isLeftDirection() {
9496
9977
  return this.openDirection() === 'LEFT';
9497
9978
  }
@@ -9500,12 +9981,16 @@ class VoiceRecordWidgetComponent {
9500
9981
  return;
9501
9982
  }
9502
9983
  await new Promise((resolve) => requestAnimationFrame(() => resolve()));
9984
+ if (!this.pendingStart) {
9985
+ return;
9986
+ }
9503
9987
  this.waveformRef.nativeElement.style.padding = '0.1rem';
9504
9988
  this.initWaveSurfer();
9505
9989
  }
9506
9990
  initWaveSurfer() {
9507
9991
  let body = document.querySelector('body');
9508
- let primaryColor = getComputedStyle(document.documentElement)
9992
+ // A host theme sets the token on the body, which the element inherits and html does not see.
9993
+ let primaryColor = getComputedStyle(this.actionsInHost() ? this.waveformRef.nativeElement : document.documentElement)
9509
9994
  .getPropertyValue('--primary-color')
9510
9995
  .trim();
9511
9996
  let el = this.mainButton.nativeElement.querySelector('.sb4-primary');
@@ -9521,15 +10006,15 @@ class VoiceRecordWidgetComponent {
9521
10006
  barRadius: 5,
9522
10007
  });
9523
10008
  this.wavesurfer.setVolume(1);
9524
- //So we support Safari
9525
- const mimeType = MediaRecorder.isTypeSupported('audio/webm') ? 'audio/webm' : 'audio/mp4';
9526
- this.record = this.wavesurfer.registerPlugin(RecordPlugin.create({
10009
+ const mimeType = UploadWidgetUtils.chooseRecorderMimeType('audio');
10010
+ const record = this.wavesurfer.registerPlugin(RecordPlugin.create({
9527
10011
  renderRecordedAudio: true,
9528
10012
  scrollingWaveform: true,
9529
10013
  continuousWaveform: false,
9530
10014
  continuousWaveformDuration: 15,
9531
10015
  mimeType,
9532
10016
  }));
10017
+ this.record = record;
9533
10018
  this.wavesurfer.on('dblclick', () => {
9534
10019
  this.updateViewState(() => {
9535
10020
  this.wavesurfer?.play();
@@ -9537,10 +10022,16 @@ class VoiceRecordWidgetComponent {
9537
10022
  });
9538
10023
  });
9539
10024
  this.record.on('record-end', (blob) => {
10025
+ if (record !== this.record) {
10026
+ return;
10027
+ }
9540
10028
  this.updateViewState(() => {
9541
- this.recordedBlob = blob;
9542
- this.isRecording = false;
10029
+ this.recordedBlob.set(blob);
10030
+ this.isRecording.set(false);
9543
10031
  });
10032
+ if (this.handover() === CaptureHandover.IMMEDIATE) {
10033
+ this.saveRecording();
10034
+ }
9544
10035
  });
9545
10036
  this.record.on('record-progress', (time) => {
9546
10037
  this.updateViewState(() => {
@@ -9553,32 +10044,57 @@ class VoiceRecordWidgetComponent {
9553
10044
  });
9554
10045
  });
9555
10046
  }
10047
+ /** Starts from a click or on open: a refused microphone shows its error line, no rejection. */
10048
+ requestRecording() {
10049
+ this.startRecording().catch(() => { });
10050
+ }
9556
10051
  async startRecording() {
9557
10052
  const confirmed = this.checkHasRecorded();
9558
- if (!confirmed || this.isRecording)
10053
+ if (!confirmed || this.isRecording())
9559
10054
  return;
10055
+ const start = {};
10056
+ this.pendingStart = start;
9560
10057
  // Initialize first (still within the gesture tick on most browsers)
9561
10058
  await this.initializeWaveSurferIfNeeded();
9562
- if (!this.record)
10059
+ if (this.pendingStart !== start) {
10060
+ return;
10061
+ }
10062
+ const record = this.record;
10063
+ if (!record)
9563
10064
  throw new Error('Voice recorder plugin is not initialized.');
9564
10065
  this.updateViewState(() => {
10066
+ this.microphoneError.set(false);
9565
10067
  this.isWaveformOpen = true;
9566
- this.isRecording = true;
10068
+ this.isRecording.set(true);
9567
10069
  this.isPlaying = false;
9568
- this.recordedBlob = null;
10070
+ this.recordedBlob.set(null);
9569
10071
  this.time = '00:00';
9570
10072
  });
9571
10073
  try {
9572
10074
  // Force native mic permission first so it works with WKWebView
9573
10075
  const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
9574
10076
  stream.getTracks().forEach((track) => track.stop());
9575
- await this.record.startRecording();
10077
+ if (this.pendingStart !== start) {
10078
+ return;
10079
+ }
10080
+ await record.startRecording();
10081
+ if (this.pendingStart !== start) {
10082
+ record.stopRecording();
10083
+ record.stopMic();
10084
+ return;
10085
+ }
10086
+ this.pendingStart = undefined;
9576
10087
  }
9577
10088
  catch (error) {
10089
+ if (this.pendingStart !== start) {
10090
+ return;
10091
+ }
9578
10092
  console.error('Mic failed', error);
10093
+ this.pendingStart = undefined;
9579
10094
  this.updateViewState(() => {
9580
- this.isRecording = false;
9581
- this.isWaveformOpen = !!this.recordedBlob;
10095
+ this.isRecording.set(false);
10096
+ this.isWaveformOpen = !!this.recordedBlob();
10097
+ this.microphoneError.set(true);
9582
10098
  });
9583
10099
  throw error;
9584
10100
  }
@@ -9589,40 +10105,53 @@ class VoiceRecordWidgetComponent {
9589
10105
  * Kept, rather than inlined away, so the place stays visible; see A37.
9590
10106
  */
9591
10107
  checkHasRecorded() {
9592
- if (this.recordedBlob) {
10108
+ if (this.recordedBlob()) {
9593
10109
  }
9594
10110
  return true;
9595
10111
  }
9596
10112
  updateProgress(time) {
9597
- let formattedTime = [Math.floor((time % 3600000) / 60000), Math.floor((time % 60000) / 1000)]
9598
- .map((v) => (v < 10 ? '0' + v : v))
9599
- .join(':');
9600
- this.time = formattedTime;
10113
+ this.time = UploadWidgetUtils.formatDuration(time);
9601
10114
  }
9602
10115
  stopRecording() {
9603
- if (this.record && this.isRecording) {
10116
+ if (this.pendingStart) {
10117
+ this.cancelPendingStart();
10118
+ }
10119
+ else if (this.record && this.isRecording()) {
9604
10120
  this.record.stopRecording();
9605
10121
  this.updateViewState(() => {
9606
- this.isRecording = false;
10122
+ this.isRecording.set(false);
9607
10123
  });
9608
10124
  }
9609
10125
  }
10126
+ /** A stop before the recording runs has nothing to keep: the strip goes back to idle. */
10127
+ cancelPendingStart() {
10128
+ this.pendingStart = undefined;
10129
+ this.destroyRecorder();
10130
+ this.updateViewState(() => {
10131
+ this.isRecording.set(false);
10132
+ this.isWaveformOpen = false;
10133
+ });
10134
+ }
10135
+ /** The plugin's destroy alone closes a running microphone's AudioContext twice, which rejects;
10136
+ * after the stop and the stopMic it has nothing left to close. */
10137
+ destroyRecorder() {
10138
+ const wavesurfer = this.wavesurfer;
10139
+ const record = this.record;
10140
+ this.wavesurfer = undefined;
10141
+ this.record = undefined;
10142
+ record?.stopRecording();
10143
+ record?.stopMic();
10144
+ wavesurfer?.destroy();
10145
+ }
9610
10146
  saveRecording() {
9611
- if (this.recordedBlob) {
9612
- let now = new Date();
9613
- let formattedDate = now.toLocaleString('sv-SE').replace(' ', '_').replace(/:/g, '-');
9614
- //Safari support
9615
- let mimeType = this.recordedBlob.type || 'audio/mp4';
9616
- let ext = mimeType.includes('webm') ? 'webm' : 'mp4';
9617
- let fileName = `recording_${formattedDate}.${ext}`;
9618
- let file = new File([this.recordedBlob], fileName, { type: mimeType });
10147
+ const recordedBlob = this.recordedBlob();
10148
+ if (recordedBlob) {
10149
+ const file = UploadWidgetUtils.createRecordingFile(recordedBlob, 'recording', 'audio/mp4');
9619
10150
  this.recordingSaved.emit(file);
9620
10151
  // Destroy completely so next recording initializes fresh
9621
- this.wavesurfer?.destroy();
9622
- this.wavesurfer = undefined;
9623
- this.record = undefined;
10152
+ this.destroyRecorder();
9624
10153
  this.updateViewState(() => {
9625
- this.recordedBlob = null;
10154
+ this.recordedBlob.set(null);
9626
10155
  this.time = '00:00';
9627
10156
  this.isPlaying = false;
9628
10157
  this.isWaveformOpen = false;
@@ -9630,7 +10159,7 @@ class VoiceRecordWidgetComponent {
9630
10159
  }
9631
10160
  }
9632
10161
  replayRecording() {
9633
- if (this.recordedBlob) {
10162
+ if (this.recordedBlob()) {
9634
10163
  this.updateViewState(() => {
9635
10164
  this.isPlaying = !this.isPlaying;
9636
10165
  this.wavesurfer?.playPause();
@@ -9638,9 +10167,8 @@ class VoiceRecordWidgetComponent {
9638
10167
  }
9639
10168
  }
9640
10169
  ngOnDestroy() {
9641
- if (this.wavesurfer) {
9642
- this.wavesurfer.destroy();
9643
- }
10170
+ this.pendingStart = undefined;
10171
+ this.destroyRecorder();
9644
10172
  }
9645
10173
  addAlphaToColor(hexColor, alpha) {
9646
10174
  if (/^#([a-f\d])([a-f\d])([a-f\d])$/i.test(hexColor)) {
@@ -9670,29 +10198,136 @@ class VoiceRecordWidgetComponent {
9670
10198
  });
9671
10199
  }
9672
10200
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: VoiceRecordWidgetComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
9673
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.8", type: VoiceRecordWidgetComponent, isStandalone: true, selector: "voice-record-widget", inputs: { openDirection: { classPropertyName: "openDirection", publicName: "openDirection", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { recordingSaved: "recordingSaved" }, viewQueries: [{ propertyName: "waveformRef", first: true, predicate: ["waveform"], descendants: true }, { propertyName: "mainButton", first: true, predicate: ["mainButton"], descendants: true, read: ElementRef }], ngImport: i0, template: "<div\n class=\"recorderContainer\"\n [class.direction-left]=\"isLeftDirection\"\n [class.direction-right]=\"!isLeftDirection\"\n [class.active]=\"isWaveformOpen || isRecording || !!recordedBlob\"\n [class.isRecording]=\"isRecording\"\n [class.hasRecording]=\"recordedBlob\"\n >\n <div class=\"recorderMain\">\n <div class=\"recorderToolbar\">\n <!-- Start Recording -->\n @if (!isRecording) {\n <ui-action-button\n class=\"mainButton\"\n #mainButton\n [descriptor]=\"micButton\"\n (actionClick)=\"startRecording()\"\n >\n </ui-action-button>\n }\n\n <!-- Stop Recording -->\n @if (isRecording) {\n <ui-action-button\n class=\"mainButton\"\n #mainButton\n [descriptor]=\"stopButton\"\n (actionClick)=\"stopRecording()\"\n >\n </ui-action-button>\n }\n\n <!-- Save Recording -->\n @if (recordedBlob) {\n <ui-action-button\n class=\"mainButton\"\n [descriptor]=\"saveButton\"\n (actionClick)=\"saveRecording()\"\n >\n </ui-action-button>\n }\n\n <!-- Start Play -->\n @if (recordedBlob && !isPlaying) {\n <ui-action-button\n class=\"mainButton\"\n [descriptor]=\"playButton\"\n (actionClick)=\"replayRecording()\"\n ></ui-action-button>\n }\n\n <!-- Stop Play -->\n @if (recordedBlob && isPlaying) {\n <ui-action-button\n class=\"mainButton\"\n [descriptor]=\"stopPlaybackButton\"\n (actionClick)=\"replayRecording()\"\n ></ui-action-button>\n }\n\n @if (isRecording) {\n <div class=\"progress\">{{ time }}</div>\n }\n </div>\n\n <div class=\"waveformBand\" [class.open]=\"isWaveformOpen || isRecording || !!recordedBlob\">\n <div class=\"waveformGrid\"></div>\n <div class=\"waveform\" #waveform></div>\n </div>\n </div>\n</div>\n", styles: [":host{display:block;width:100%;min-width:0}:host ::ng-deep .waveform{height:unset!important}.recorderContainer{display:flex;flex-direction:row;justify-content:space-between;width:100%;min-width:0;align-items:center;gap:.5rem}.active ::ng-deep .recorderToolbar{border:1px solid #e5e7eb;border-radius:.5rem}.isRecording ::ng-deep .recorderToolbar{padding-right:1rem}.recorderToolbar ::ng-deep .sb4-red,.recorderToolbar ::ng-deep .sb4-primary{border:1px solid #e5e7eb;box-shadow:unset}.active ::ng-deep .recorderToolbar ::ng-deep .sb4-red,.active ::ng-deep .recorderToolbar ::ng-deep .sb4-primary{border:unset}.mainButton ::ng-deep .sb4-red,.mainButton ::ng-deep .sb4-primary{padding:.5rem .75rem}.recorderMain{display:flex;align-items:center;min-width:0;flex:1}.direction-right .recorderMain{flex-direction:row}.direction-left .recorderMain{flex-direction:row-reverse}.waveform{width:100%;height:45px}.waveformBand{position:relative;display:grid;align-items:center;width:0;max-width:0;min-width:0;opacity:0;overflow:hidden;border-radius:5px;flex:1 1 0;transform:scaleX(.96);will-change:opacity,transform,margin;transition:max-width .22s ease,opacity .18s ease,margin .22s ease,transform .22s ease}.waveformBand.open{min-width:35px;max-width:100%;opacity:1;transform:scaleX(1)}.direction-right .waveformBand{margin-left:0}.direction-left .waveformBand{margin-right:0}.direction-right .waveformBand.open{margin-left:.45rem}.direction-left .waveformBand.open{margin-right:.45rem}.waveformGrid{position:absolute;inset:0;border:1px solid #e5e7eb;border-radius:.5rem;background:repeating-linear-gradient(to right,rgba(148,163,184,.35) 0,rgba(148,163,184,.35) 2px,transparent 2px,transparent 8px),linear-gradient(to bottom,#94a3b814,#94a3b805);pointer-events:none}.recorderToolbar{background-color:transparent;display:flex;flex-direction:row;align-items:center}.progress{align-content:center;font-size:1rem;height:100%;color:#6b7280}.hasRecording ::ng-deep .recorderToolbar ui-action-button{border-right:1px solid #e5e7eb}.hasRecording ::ng-deep .recorderToolbar ui-action-button:last-child{border-right:unset}\n"], dependencies: [{ kind: "component", type: UiActionButtonComponent, selector: "ui-action-button", inputs: ["disabled", "descriptor", "code", "identifier", "viewActivated", "tab", "rowState", "addedCssClass", "addBasicClasses", "iconPlaceholder", "chords"], outputs: ["actionClick", "actionDoubleClick"] }] }); }
10201
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.8", type: VoiceRecordWidgetComponent, isStandalone: true, selector: "voice-record-widget", inputs: { openDirection: { classPropertyName: "openDirection", publicName: "openDirection", isSignal: true, isRequired: false, transformFunction: null }, texts: { classPropertyName: "texts", publicName: "texts", isSignal: true, isRequired: false, transformFunction: null }, showMicrophoneError: { classPropertyName: "showMicrophoneError", publicName: "showMicrophoneError", isSignal: true, isRequired: false, transformFunction: null }, deviceStart: { classPropertyName: "deviceStart", publicName: "deviceStart", isSignal: true, isRequired: false, transformFunction: null }, handover: { classPropertyName: "handover", publicName: "handover", isSignal: true, isRequired: false, transformFunction: null }, showSaveTitle: { classPropertyName: "showSaveTitle", publicName: "showSaveTitle", isSignal: true, isRequired: false, transformFunction: null }, actionsInHost: { classPropertyName: "actionsInHost", publicName: "actionsInHost", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { recordingSaved: "recordingSaved" }, host: { properties: { "class.actionsInHost": "actionsInHost()" } }, viewQueries: [{ propertyName: "actionButtonsTemplate", first: true, predicate: ["actionButtons"], descendants: true, isSignal: true }, { propertyName: "waveformRef", first: true, predicate: ["waveform"], descendants: true }, { propertyName: "mainButton", first: true, predicate: ["mainButton"], descendants: true, read: ElementRef }], ngImport: i0, template: "<div\n class=\"recorderContainer\"\n [class.direction-left]=\"isLeftDirection\"\n [class.direction-right]=\"!isLeftDirection\"\n [class.active]=\"isWaveformOpen || isRecording() || !!recordedBlob()\"\n [class.isRecording]=\"isRecording()\"\n [class.hasRecording]=\"recordedBlob()\"\n >\n <div class=\"recorderMain\">\n <div class=\"recorderToolbar\">\n <!-- Start Recording -->\n @if (!isRecording()) {\n <ui-action-button\n class=\"mainButton\"\n #mainButton\n [descriptor]=\"micButton\"\n (actionClick)=\"requestRecording()\"\n >\n </ui-action-button>\n }\n\n <!-- Stop Recording -->\n @if (isRecording()) {\n <ui-action-button\n class=\"mainButton\"\n #mainButton\n [descriptor]=\"effectiveStopButton()\"\n (actionClick)=\"stopRecording()\"\n >\n </ui-action-button>\n }\n\n <!-- Save Recording -->\n @if (recordedBlob() && !actionsInHost()) {\n <ui-action-button\n class=\"mainButton\"\n [descriptor]=\"effectiveSaveButton()\"\n (actionClick)=\"saveRecording()\"\n >\n </ui-action-button>\n }\n\n <!-- Start Play -->\n @if (recordedBlob() && !isPlaying) {\n <ui-action-button\n class=\"mainButton\"\n [descriptor]=\"playButton\"\n (actionClick)=\"replayRecording()\"\n ></ui-action-button>\n }\n\n <!-- Stop Play -->\n @if (recordedBlob() && isPlaying) {\n <ui-action-button\n class=\"mainButton\"\n [descriptor]=\"stopPlaybackButton\"\n (actionClick)=\"replayRecording()\"\n ></ui-action-button>\n }\n\n @if (isRecording()) {\n <div class=\"progress\">{{ time }}</div>\n }\n </div>\n\n <div class=\"waveformBand\" [class.open]=\"isWaveformOpen || isRecording() || !!recordedBlob()\">\n <div class=\"waveformGrid\"></div>\n <div class=\"waveform\" #waveform></div>\n </div>\n </div>\n</div>\n@if (showMicrophoneError() && microphoneError()) {\n <p class=\"message\">{{ texts().microphoneError }}</p>\n @if (actionsInHost()) {\n <p class=\"hint\">{{ texts().microphoneRetryHint }}</p>\n }\n}\n@if (actionsInHost() && recordedBlob()) {\n <p class=\"hint\">{{ texts().recordingHint }}</p>\n}\n\n<ng-template #actionButtons>\n @if (isRecording()) {\n <ui-action-button\n class=\"stopRecordingButton\"\n [descriptor]=\"stopRecordingButton()\"\n (actionClick)=\"stopRecording()\"\n >\n </ui-action-button>\n }\n @if (recordedBlob()) {\n <ui-action-button\n class=\"saveRecordingButton\"\n [descriptor]=\"effectiveSaveButton()\"\n (actionClick)=\"saveRecording()\"\n >\n </ui-action-button>\n }\n</ng-template>\n", styles: [":host{display:block;width:100%;min-width:0}.message{text-align:center;color:#6b7280}:host ::ng-deep .waveform{height:unset!important}.recorderContainer{display:flex;flex-direction:row;justify-content:space-between;width:100%;min-width:0;align-items:center;gap:.5rem}.active ::ng-deep .recorderToolbar{border:1px solid #e5e7eb;border-radius:.5rem}.isRecording ::ng-deep .recorderToolbar{padding-right:1rem}.recorderToolbar ::ng-deep .sb4-red,.recorderToolbar ::ng-deep .sb4-primary{border:1px solid #e5e7eb;box-shadow:unset}.active ::ng-deep .recorderToolbar ::ng-deep .sb4-red,.active ::ng-deep .recorderToolbar ::ng-deep .sb4-primary{border:unset}.mainButton ::ng-deep .sb4-red,.mainButton ::ng-deep .sb4-primary{padding:.5rem .75rem}.recorderMain{display:flex;align-items:center;min-width:0;flex:1}.direction-right .recorderMain{flex-direction:row}.direction-left .recorderMain{flex-direction:row-reverse}.waveform{width:100%;height:45px}.waveformBand{position:relative;display:grid;align-items:center;width:0;max-width:0;min-width:0;opacity:0;overflow:hidden;border-radius:5px;flex:1 1 0;transform:scaleX(.96);will-change:opacity,transform,margin;transition:max-width .22s ease,opacity .18s ease,margin .22s ease,transform .22s ease}.waveformBand.open{min-width:35px;max-width:100%;opacity:1;transform:scaleX(1)}.direction-right .waveformBand{margin-left:0}.direction-left .waveformBand{margin-right:0}.direction-right .waveformBand.open{margin-left:.45rem}.direction-left .waveformBand.open{margin-right:.45rem}.waveformGrid{position:absolute;inset:0;border:1px solid #e5e7eb;border-radius:.5rem;background:repeating-linear-gradient(to right,rgba(148,163,184,.35) 0,rgba(148,163,184,.35) 2px,transparent 2px,transparent 8px),linear-gradient(to bottom,#94a3b814,#94a3b805);pointer-events:none}.recorderToolbar{background-color:transparent;display:flex;flex-direction:row;align-items:center}.progress{align-content:center;font-size:1rem;height:100%;color:#6b7280}.hasRecording ::ng-deep .recorderToolbar ui-action-button{border-right:1px solid #e5e7eb}.hasRecording ::ng-deep .recorderToolbar ui-action-button:last-child{border-right:unset}:host(.actionsInHost) .message{color:var(--warn-color)}:host(.actionsInHost) .hint{margin:.5rem 0 0;text-align:center;font-size:.75rem;color:var(--color-text-muted)}:host(.actionsInHost) .progress{color:var(--color-text-muted)}:host(.actionsInHost) .isRecording .progress:before{content:\"\";display:inline-block;width:.5rem;height:.5rem;margin-right:.3125rem;border-radius:50%;background:var(--warn-color)}:host(.actionsInHost) .active ::ng-deep .recorderToolbar{border-color:var(--color-border)}:host(.actionsInHost) .waveformGrid{border-color:var(--color-border);background:repeating-linear-gradient(to right,color-mix(in srgb,var(--color-text-muted) 35%,transparent) 0,color-mix(in srgb,var(--color-text-muted) 35%,transparent) 2px,transparent 2px,transparent 8px),linear-gradient(to bottom,color-mix(in srgb,var(--color-text-muted) 8%,transparent),color-mix(in srgb,var(--color-text-muted) 2%,transparent))}:host(.actionsInHost) .recorderToolbar ::ng-deep .sb4-warn,:host(.actionsInHost) .recorderToolbar ::ng-deep .sb4-primary,:host(.actionsInHost) .recorderToolbar ::ng-deep ui-action-button{border:unset}:host(.actionsInHost) .mainButton ::ng-deep .sb4-warn{padding:.5rem .75rem}\n"], dependencies: [{ kind: "component", type: UiActionButtonComponent, selector: "ui-action-button", inputs: ["disabled", "descriptor", "code", "identifier", "viewActivated", "tab", "rowState", "addedCssClass", "addBasicClasses", "iconPlaceholder", "chords"], outputs: ["actionClick", "actionDoubleClick"] }] }); }
9674
10202
  }
9675
10203
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: VoiceRecordWidgetComponent, decorators: [{
9676
10204
  type: Component,
9677
- args: [{ selector: 'voice-record-widget', imports: [UiActionButtonComponent], template: "<div\n class=\"recorderContainer\"\n [class.direction-left]=\"isLeftDirection\"\n [class.direction-right]=\"!isLeftDirection\"\n [class.active]=\"isWaveformOpen || isRecording || !!recordedBlob\"\n [class.isRecording]=\"isRecording\"\n [class.hasRecording]=\"recordedBlob\"\n >\n <div class=\"recorderMain\">\n <div class=\"recorderToolbar\">\n <!-- Start Recording -->\n @if (!isRecording) {\n <ui-action-button\n class=\"mainButton\"\n #mainButton\n [descriptor]=\"micButton\"\n (actionClick)=\"startRecording()\"\n >\n </ui-action-button>\n }\n\n <!-- Stop Recording -->\n @if (isRecording) {\n <ui-action-button\n class=\"mainButton\"\n #mainButton\n [descriptor]=\"stopButton\"\n (actionClick)=\"stopRecording()\"\n >\n </ui-action-button>\n }\n\n <!-- Save Recording -->\n @if (recordedBlob) {\n <ui-action-button\n class=\"mainButton\"\n [descriptor]=\"saveButton\"\n (actionClick)=\"saveRecording()\"\n >\n </ui-action-button>\n }\n\n <!-- Start Play -->\n @if (recordedBlob && !isPlaying) {\n <ui-action-button\n class=\"mainButton\"\n [descriptor]=\"playButton\"\n (actionClick)=\"replayRecording()\"\n ></ui-action-button>\n }\n\n <!-- Stop Play -->\n @if (recordedBlob && isPlaying) {\n <ui-action-button\n class=\"mainButton\"\n [descriptor]=\"stopPlaybackButton\"\n (actionClick)=\"replayRecording()\"\n ></ui-action-button>\n }\n\n @if (isRecording) {\n <div class=\"progress\">{{ time }}</div>\n }\n </div>\n\n <div class=\"waveformBand\" [class.open]=\"isWaveformOpen || isRecording || !!recordedBlob\">\n <div class=\"waveformGrid\"></div>\n <div class=\"waveform\" #waveform></div>\n </div>\n </div>\n</div>\n", styles: [":host{display:block;width:100%;min-width:0}:host ::ng-deep .waveform{height:unset!important}.recorderContainer{display:flex;flex-direction:row;justify-content:space-between;width:100%;min-width:0;align-items:center;gap:.5rem}.active ::ng-deep .recorderToolbar{border:1px solid #e5e7eb;border-radius:.5rem}.isRecording ::ng-deep .recorderToolbar{padding-right:1rem}.recorderToolbar ::ng-deep .sb4-red,.recorderToolbar ::ng-deep .sb4-primary{border:1px solid #e5e7eb;box-shadow:unset}.active ::ng-deep .recorderToolbar ::ng-deep .sb4-red,.active ::ng-deep .recorderToolbar ::ng-deep .sb4-primary{border:unset}.mainButton ::ng-deep .sb4-red,.mainButton ::ng-deep .sb4-primary{padding:.5rem .75rem}.recorderMain{display:flex;align-items:center;min-width:0;flex:1}.direction-right .recorderMain{flex-direction:row}.direction-left .recorderMain{flex-direction:row-reverse}.waveform{width:100%;height:45px}.waveformBand{position:relative;display:grid;align-items:center;width:0;max-width:0;min-width:0;opacity:0;overflow:hidden;border-radius:5px;flex:1 1 0;transform:scaleX(.96);will-change:opacity,transform,margin;transition:max-width .22s ease,opacity .18s ease,margin .22s ease,transform .22s ease}.waveformBand.open{min-width:35px;max-width:100%;opacity:1;transform:scaleX(1)}.direction-right .waveformBand{margin-left:0}.direction-left .waveformBand{margin-right:0}.direction-right .waveformBand.open{margin-left:.45rem}.direction-left .waveformBand.open{margin-right:.45rem}.waveformGrid{position:absolute;inset:0;border:1px solid #e5e7eb;border-radius:.5rem;background:repeating-linear-gradient(to right,rgba(148,163,184,.35) 0,rgba(148,163,184,.35) 2px,transparent 2px,transparent 8px),linear-gradient(to bottom,#94a3b814,#94a3b805);pointer-events:none}.recorderToolbar{background-color:transparent;display:flex;flex-direction:row;align-items:center}.progress{align-content:center;font-size:1rem;height:100%;color:#6b7280}.hasRecording ::ng-deep .recorderToolbar ui-action-button{border-right:1px solid #e5e7eb}.hasRecording ::ng-deep .recorderToolbar ui-action-button:last-child{border-right:unset}\n"] }]
10205
+ args: [{ selector: 'voice-record-widget', imports: [UiActionButtonComponent], host: { '[class.actionsInHost]': 'actionsInHost()' }, template: "<div\n class=\"recorderContainer\"\n [class.direction-left]=\"isLeftDirection\"\n [class.direction-right]=\"!isLeftDirection\"\n [class.active]=\"isWaveformOpen || isRecording() || !!recordedBlob()\"\n [class.isRecording]=\"isRecording()\"\n [class.hasRecording]=\"recordedBlob()\"\n >\n <div class=\"recorderMain\">\n <div class=\"recorderToolbar\">\n <!-- Start Recording -->\n @if (!isRecording()) {\n <ui-action-button\n class=\"mainButton\"\n #mainButton\n [descriptor]=\"micButton\"\n (actionClick)=\"requestRecording()\"\n >\n </ui-action-button>\n }\n\n <!-- Stop Recording -->\n @if (isRecording()) {\n <ui-action-button\n class=\"mainButton\"\n #mainButton\n [descriptor]=\"effectiveStopButton()\"\n (actionClick)=\"stopRecording()\"\n >\n </ui-action-button>\n }\n\n <!-- Save Recording -->\n @if (recordedBlob() && !actionsInHost()) {\n <ui-action-button\n class=\"mainButton\"\n [descriptor]=\"effectiveSaveButton()\"\n (actionClick)=\"saveRecording()\"\n >\n </ui-action-button>\n }\n\n <!-- Start Play -->\n @if (recordedBlob() && !isPlaying) {\n <ui-action-button\n class=\"mainButton\"\n [descriptor]=\"playButton\"\n (actionClick)=\"replayRecording()\"\n ></ui-action-button>\n }\n\n <!-- Stop Play -->\n @if (recordedBlob() && isPlaying) {\n <ui-action-button\n class=\"mainButton\"\n [descriptor]=\"stopPlaybackButton\"\n (actionClick)=\"replayRecording()\"\n ></ui-action-button>\n }\n\n @if (isRecording()) {\n <div class=\"progress\">{{ time }}</div>\n }\n </div>\n\n <div class=\"waveformBand\" [class.open]=\"isWaveformOpen || isRecording() || !!recordedBlob()\">\n <div class=\"waveformGrid\"></div>\n <div class=\"waveform\" #waveform></div>\n </div>\n </div>\n</div>\n@if (showMicrophoneError() && microphoneError()) {\n <p class=\"message\">{{ texts().microphoneError }}</p>\n @if (actionsInHost()) {\n <p class=\"hint\">{{ texts().microphoneRetryHint }}</p>\n }\n}\n@if (actionsInHost() && recordedBlob()) {\n <p class=\"hint\">{{ texts().recordingHint }}</p>\n}\n\n<ng-template #actionButtons>\n @if (isRecording()) {\n <ui-action-button\n class=\"stopRecordingButton\"\n [descriptor]=\"stopRecordingButton()\"\n (actionClick)=\"stopRecording()\"\n >\n </ui-action-button>\n }\n @if (recordedBlob()) {\n <ui-action-button\n class=\"saveRecordingButton\"\n [descriptor]=\"effectiveSaveButton()\"\n (actionClick)=\"saveRecording()\"\n >\n </ui-action-button>\n }\n</ng-template>\n", styles: [":host{display:block;width:100%;min-width:0}.message{text-align:center;color:#6b7280}:host ::ng-deep .waveform{height:unset!important}.recorderContainer{display:flex;flex-direction:row;justify-content:space-between;width:100%;min-width:0;align-items:center;gap:.5rem}.active ::ng-deep .recorderToolbar{border:1px solid #e5e7eb;border-radius:.5rem}.isRecording ::ng-deep .recorderToolbar{padding-right:1rem}.recorderToolbar ::ng-deep .sb4-red,.recorderToolbar ::ng-deep .sb4-primary{border:1px solid #e5e7eb;box-shadow:unset}.active ::ng-deep .recorderToolbar ::ng-deep .sb4-red,.active ::ng-deep .recorderToolbar ::ng-deep .sb4-primary{border:unset}.mainButton ::ng-deep .sb4-red,.mainButton ::ng-deep .sb4-primary{padding:.5rem .75rem}.recorderMain{display:flex;align-items:center;min-width:0;flex:1}.direction-right .recorderMain{flex-direction:row}.direction-left .recorderMain{flex-direction:row-reverse}.waveform{width:100%;height:45px}.waveformBand{position:relative;display:grid;align-items:center;width:0;max-width:0;min-width:0;opacity:0;overflow:hidden;border-radius:5px;flex:1 1 0;transform:scaleX(.96);will-change:opacity,transform,margin;transition:max-width .22s ease,opacity .18s ease,margin .22s ease,transform .22s ease}.waveformBand.open{min-width:35px;max-width:100%;opacity:1;transform:scaleX(1)}.direction-right .waveformBand{margin-left:0}.direction-left .waveformBand{margin-right:0}.direction-right .waveformBand.open{margin-left:.45rem}.direction-left .waveformBand.open{margin-right:.45rem}.waveformGrid{position:absolute;inset:0;border:1px solid #e5e7eb;border-radius:.5rem;background:repeating-linear-gradient(to right,rgba(148,163,184,.35) 0,rgba(148,163,184,.35) 2px,transparent 2px,transparent 8px),linear-gradient(to bottom,#94a3b814,#94a3b805);pointer-events:none}.recorderToolbar{background-color:transparent;display:flex;flex-direction:row;align-items:center}.progress{align-content:center;font-size:1rem;height:100%;color:#6b7280}.hasRecording ::ng-deep .recorderToolbar ui-action-button{border-right:1px solid #e5e7eb}.hasRecording ::ng-deep .recorderToolbar ui-action-button:last-child{border-right:unset}:host(.actionsInHost) .message{color:var(--warn-color)}:host(.actionsInHost) .hint{margin:.5rem 0 0;text-align:center;font-size:.75rem;color:var(--color-text-muted)}:host(.actionsInHost) .progress{color:var(--color-text-muted)}:host(.actionsInHost) .isRecording .progress:before{content:\"\";display:inline-block;width:.5rem;height:.5rem;margin-right:.3125rem;border-radius:50%;background:var(--warn-color)}:host(.actionsInHost) .active ::ng-deep .recorderToolbar{border-color:var(--color-border)}:host(.actionsInHost) .waveformGrid{border-color:var(--color-border);background:repeating-linear-gradient(to right,color-mix(in srgb,var(--color-text-muted) 35%,transparent) 0,color-mix(in srgb,var(--color-text-muted) 35%,transparent) 2px,transparent 2px,transparent 8px),linear-gradient(to bottom,color-mix(in srgb,var(--color-text-muted) 8%,transparent),color-mix(in srgb,var(--color-text-muted) 2%,transparent))}:host(.actionsInHost) .recorderToolbar ::ng-deep .sb4-warn,:host(.actionsInHost) .recorderToolbar ::ng-deep .sb4-primary,:host(.actionsInHost) .recorderToolbar ::ng-deep ui-action-button{border:unset}:host(.actionsInHost) .mainButton ::ng-deep .sb4-warn{padding:.5rem .75rem}\n"] }]
9678
10206
  }], propDecorators: { waveformRef: [{
9679
10207
  type: ViewChild,
9680
10208
  args: ['waveform']
9681
10209
  }], mainButton: [{
9682
10210
  type: ViewChild,
9683
10211
  args: ['mainButton', { read: ElementRef }]
9684
- }], openDirection: [{ type: i0.Input, args: [{ isSignal: true, alias: "openDirection", required: false }] }], recordingSaved: [{ type: i0.Output, args: ["recordingSaved"] }] } });
10212
+ }], actionButtonsTemplate: [{ type: i0.ViewChild, args: ['actionButtons', { isSignal: true }] }], openDirection: [{ type: i0.Input, args: [{ isSignal: true, alias: "openDirection", required: false }] }], texts: [{ type: i0.Input, args: [{ isSignal: true, alias: "texts", required: false }] }], showMicrophoneError: [{ type: i0.Input, args: [{ isSignal: true, alias: "showMicrophoneError", required: false }] }], deviceStart: [{ type: i0.Input, args: [{ isSignal: true, alias: "deviceStart", required: false }] }], handover: [{ type: i0.Input, args: [{ isSignal: true, alias: "handover", required: false }] }], showSaveTitle: [{ type: i0.Input, args: [{ isSignal: true, alias: "showSaveTitle", required: false }] }], actionsInHost: [{ type: i0.Input, args: [{ isSignal: true, alias: "actionsInHost", required: false }] }], recordingSaved: [{ type: i0.Output, args: ["recordingSaved"] }] } });
10213
+
10214
+ /** The Capture Dialog of a File Field: closes with the files of a use or a save, else empty. */
10215
+ class CaptureDialogComponent {
10216
+ constructor() {
10217
+ this.dialogRef = inject(MatDialogRef);
10218
+ this.data = inject(MAT_DIALOG_DATA);
10219
+ this.isMicrophone = this.data.source === FileUploaderSource.MICROPHONE;
10220
+ this.cameraMode = this.data.source === FileUploaderSource.CAMERA_VIDEO ? 'VIDEO' : 'PHOTO';
10221
+ this.cameraWidget = viewChild(PhotoCaptureWidgetComponent, /* @ts-ignore */
10222
+ ...(ngDevMode ? [{ debugName: "cameraWidget" }] : /* istanbul ignore next */ []));
10223
+ this.recorderWidget = viewChild(VoiceRecordWidgetComponent, /* @ts-ignore */
10224
+ ...(ngDevMode ? [{ debugName: "recorderWidget" }] : /* istanbul ignore next */ []));
10225
+ this.captureWidget = computed(() => this.cameraWidget() ?? this.recorderWidget(), /* @ts-ignore */
10226
+ ...(ngDevMode ? [{ debugName: "captureWidget" }] : /* istanbul ignore next */ []));
10227
+ /** The widget's buttons go to the footer of the dialog. */
10228
+ this.actionButtons = computed(() => this.captureWidget()?.actionButtonsTemplate() ?? null, /* @ts-ignore */
10229
+ ...(ngDevMode ? [{ debugName: "actionButtons" }] : /* istanbul ignore next */ []));
10230
+ /** The recorder waits for the open: it reads the waveform's height from its rendered button. */
10231
+ this.isOpened = signal(false, /* @ts-ignore */
10232
+ ...(ngDevMode ? [{ debugName: "isOpened" }] : /* istanbul ignore next */ []));
10233
+ this.closeButton = {
10234
+ title: '',
10235
+ type: UiActionButtonType.ICON,
10236
+ icon: 'close',
10237
+ iconPosition: IconPosition.PRE,
10238
+ color: 'primary',
10239
+ };
10240
+ this.cancelButton = {
10241
+ title: this.data.texts.cancel,
10242
+ type: UiActionButtonType.FLAT,
10243
+ color: 'primary',
10244
+ };
10245
+ effect(() => {
10246
+ this.dialogRef.disableClose = this.captureWidget()?.isCaptureUnderWay() ?? false;
10247
+ });
10248
+ this.dialogRef.afterOpened().subscribe(() => this.isOpened.set(true));
10249
+ // disableClose stops the keyboard too, but only the backdrop is meant to wait for the capture.
10250
+ this.dialogRef.keydownEvents().subscribe((event) => {
10251
+ if (event.key === 'Escape' && this.dialogRef.disableClose) {
10252
+ this.discardCapture();
10253
+ }
10254
+ });
10255
+ }
10256
+ /** The close destroys the capture widget, which stops the device. */
10257
+ discardCapture() {
10258
+ this.dialogRef.close();
10259
+ }
10260
+ handOverCapture(files) {
10261
+ this.dialogRef.close(files);
10262
+ }
10263
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: CaptureDialogComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
10264
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.8", type: CaptureDialogComponent, isStandalone: true, selector: "smart-capture-dialog", viewQueries: [{ propertyName: "cameraWidget", first: true, predicate: PhotoCaptureWidgetComponent, descendants: true, isSignal: true }, { propertyName: "recorderWidget", first: true, predicate: VoiceRecordWidgetComponent, descendants: true, isSignal: true }], ngImport: i0, template: "<div class=\"dialog-container captureDialog\">\n <div class=\"dialog-header\">\n <h2 class=\"dialog-title\">{{ data.content.title }}</h2>\n <ui-action-button\n class=\"closeCaptureButton\"\n [descriptor]=\"closeButton\"\n (actionClick)=\"discardCapture()\"\n ></ui-action-button>\n </div>\n\n <mat-dialog-content class=\"dialog-content\">\n @if (isMicrophone) {\n <div class=\"recorderSlot\">\n @if (isOpened()) {\n <voice-record-widget\n [deviceStart]=\"data.settings.deviceStart\"\n [handover]=\"data.settings.handover\"\n [actionsInHost]=\"true\"\n [showSaveTitle]=\"true\"\n [texts]=\"data.texts\"\n [showMicrophoneError]=\"true\"\n (recordingSaved)=\"handOverCapture([$event])\"\n ></voice-record-widget>\n }\n </div>\n } @else {\n <photo-capture-widget\n [mode]=\"cameraMode\"\n [deviceStart]=\"data.settings.deviceStart\"\n [handover]=\"data.settings.handover\"\n [actionsInHost]=\"true\"\n [texts]=\"data.texts\"\n [maxFileSize]=\"data.maxFileSize\"\n (photoCaptured)=\"handOverCapture($event)\"\n ></photo-capture-widget>\n }\n </mat-dialog-content>\n\n <div class=\"dialog-footer\">\n <ui-action-button\n class=\"cancelCaptureButton\"\n [descriptor]=\"cancelButton\"\n (actionClick)=\"discardCapture()\"\n ></ui-action-button>\n <ng-container [ngTemplateOutlet]=\"actionButtons()\" />\n </div>\n</div>\n", styles: [".dialog-container{display:flex;flex-direction:column;--sb4-dialog-default-padding: .5rem;--mat-dialog-content-padding: var(--sb4-dialog-default-padding)}.dialog-header{display:flex;align-items:center;gap:var(--sb4-dialog-default-padding);padding:var(--sb4-dialog-default-padding)}.mat-mdc-dialog-content{max-height:var(--sb4-dialog-default-max-height, 80vh);max-width:var(--sb4-dialog-default-max-width, unset);min-width:var(--sb4-dialog-default-min-width, unset)}.dialog-title{flex:1;margin:0}.dialog-footer{padding:var(--sb4-dialog-default-padding)}\n", ".captureDialog{--sb4-dialog-default-padding: 1rem}.captureDialog .dialog-footer{display:flex;flex-wrap:wrap;justify-content:flex-end;align-items:center;gap:.5rem}.closeCaptureButton ::ng-deep button{width:40px;min-width:40px;height:40px;min-height:40px;padding:0}.captureDialog ::ng-deep .stopRecordingButton{--text-color: color-mix(in srgb, var(--warn-color) 5%, white)}photo-capture-widget{display:block;width:100%}.recorderSlot{min-height:3.25rem}\n"], dependencies: [{ kind: "directive", type: MatDialogContent, selector: "[mat-dialog-content], mat-dialog-content, [matDialogContent]" }, { kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: PhotoCaptureWidgetComponent, selector: "photo-capture-widget", inputs: ["maxFileSize", "mode", "externalTrigger", "texts", "deviceStart", "handover", "actionsInHost"], outputs: ["photoCaptured"] }, { kind: "component", type: UiActionButtonComponent, selector: "ui-action-button", inputs: ["disabled", "descriptor", "code", "identifier", "viewActivated", "tab", "rowState", "addedCssClass", "addBasicClasses", "iconPlaceholder", "chords"], outputs: ["actionClick", "actionDoubleClick"] }, { kind: "component", type: VoiceRecordWidgetComponent, selector: "voice-record-widget", inputs: ["openDirection", "texts", "showMicrophoneError", "deviceStart", "handover", "showSaveTitle", "actionsInHost"], outputs: ["recordingSaved"] }] }); }
10265
+ }
10266
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: CaptureDialogComponent, decorators: [{
10267
+ type: Component,
10268
+ args: [{ selector: 'smart-capture-dialog', imports: [
10269
+ MatDialogContent,
10270
+ NgTemplateOutlet,
10271
+ PhotoCaptureWidgetComponent,
10272
+ UiActionButtonComponent,
10273
+ VoiceRecordWidgetComponent,
10274
+ ], template: "<div class=\"dialog-container captureDialog\">\n <div class=\"dialog-header\">\n <h2 class=\"dialog-title\">{{ data.content.title }}</h2>\n <ui-action-button\n class=\"closeCaptureButton\"\n [descriptor]=\"closeButton\"\n (actionClick)=\"discardCapture()\"\n ></ui-action-button>\n </div>\n\n <mat-dialog-content class=\"dialog-content\">\n @if (isMicrophone) {\n <div class=\"recorderSlot\">\n @if (isOpened()) {\n <voice-record-widget\n [deviceStart]=\"data.settings.deviceStart\"\n [handover]=\"data.settings.handover\"\n [actionsInHost]=\"true\"\n [showSaveTitle]=\"true\"\n [texts]=\"data.texts\"\n [showMicrophoneError]=\"true\"\n (recordingSaved)=\"handOverCapture([$event])\"\n ></voice-record-widget>\n }\n </div>\n } @else {\n <photo-capture-widget\n [mode]=\"cameraMode\"\n [deviceStart]=\"data.settings.deviceStart\"\n [handover]=\"data.settings.handover\"\n [actionsInHost]=\"true\"\n [texts]=\"data.texts\"\n [maxFileSize]=\"data.maxFileSize\"\n (photoCaptured)=\"handOverCapture($event)\"\n ></photo-capture-widget>\n }\n </mat-dialog-content>\n\n <div class=\"dialog-footer\">\n <ui-action-button\n class=\"cancelCaptureButton\"\n [descriptor]=\"cancelButton\"\n (actionClick)=\"discardCapture()\"\n ></ui-action-button>\n <ng-container [ngTemplateOutlet]=\"actionButtons()\" />\n </div>\n</div>\n", styles: [".dialog-container{display:flex;flex-direction:column;--sb4-dialog-default-padding: .5rem;--mat-dialog-content-padding: var(--sb4-dialog-default-padding)}.dialog-header{display:flex;align-items:center;gap:var(--sb4-dialog-default-padding);padding:var(--sb4-dialog-default-padding)}.mat-mdc-dialog-content{max-height:var(--sb4-dialog-default-max-height, 80vh);max-width:var(--sb4-dialog-default-max-width, unset);min-width:var(--sb4-dialog-default-min-width, unset)}.dialog-title{flex:1;margin:0}.dialog-footer{padding:var(--sb4-dialog-default-padding)}\n", ".captureDialog{--sb4-dialog-default-padding: 1rem}.captureDialog .dialog-footer{display:flex;flex-wrap:wrap;justify-content:flex-end;align-items:center;gap:.5rem}.closeCaptureButton ::ng-deep button{width:40px;min-width:40px;height:40px;min-height:40px;padding:0}.captureDialog ::ng-deep .stopRecordingButton{--text-color: color-mix(in srgb, var(--warn-color) 5%, white)}photo-capture-widget{display:block;width:100%}.recorderSlot{min-height:3.25rem}\n"] }]
10275
+ }], ctorParameters: () => [], propDecorators: { cameraWidget: [{ type: i0.ViewChild, args: [i0.forwardRef(() => PhotoCaptureWidgetComponent), { isSignal: true }] }], recorderWidget: [{ type: i0.ViewChild, args: [i0.forwardRef(() => VoiceRecordWidgetComponent), { isSignal: true }] }] } });
10276
+
10277
+ class CaptureDialogService extends SmartdialogService {
10278
+ constructor() {
10279
+ super(inject(MatDialog), inject(Injector));
10280
+ }
10281
+ /** The returned dialog closes with the handed over files, or empty at a discard. */
10282
+ openCaptureDialog(data) {
10283
+ return this.createDialogRef(data, CaptureDialogComponent, {
10284
+ width: '560px',
10285
+ maxWidth: '90vw',
10286
+ autoFocus: 'dialog',
10287
+ });
10288
+ }
10289
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: CaptureDialogService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
10290
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: CaptureDialogService, providedIn: 'root' }); }
10291
+ }
10292
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: CaptureDialogService, decorators: [{
10293
+ type: Injectable,
10294
+ args: [{ providedIn: 'root' }]
10295
+ }], ctorParameters: () => [] });
9685
10296
 
9686
10297
  class UploadWidgetComponent {
9687
10298
  constructor() {
10299
+ this.captureDialogService = inject(CaptureDialogService);
9688
10300
  this.uploadDescriptor = input(this.defaultUploadDescriptor, /* @ts-ignore */
9689
10301
  ...(ngDevMode ? [{ debugName: "uploadDescriptor" }] : /* istanbul ignore next */ []));
9690
10302
  this.isMultiple = input.required(/* @ts-ignore */
9691
10303
  ...(ngDevMode ? [{ debugName: "isMultiple" }] : /* istanbul ignore next */ []));
9692
10304
  this.isDisabled = input(false, /* @ts-ignore */
9693
10305
  ...(ngDevMode ? [{ debugName: "isDisabled" }] : /* istanbul ignore next */ []));
10306
+ this.sources = input(undefined, /* @ts-ignore */
10307
+ ...(ngDevMode ? [{ debugName: "sources" }] : /* istanbul ignore next */ []));
10308
+ this.capture = input(undefined, /* @ts-ignore */
10309
+ ...(ngDevMode ? [{ debugName: "capture" }] : /* istanbul ignore next */ []));
9694
10310
  this.uploadFilesEvent = output();
10311
+ /** The Capture Settings in force: a missing bean or field takes its default here only. */
10312
+ this.captureSettings = computed(() => {
10313
+ const capture = this.capture();
10314
+ return {
10315
+ mode: capture?.mode ?? CaptureMode.DIALOG,
10316
+ openDirection: capture?.openDirection ?? CaptureOpenDirection.RIGHT,
10317
+ deviceStart: capture?.deviceStart ?? CaptureDeviceStart.AUTO,
10318
+ handover: capture?.handover ?? CaptureHandover.CONFIRM,
10319
+ };
10320
+ }, /* @ts-ignore */
10321
+ ...(ngDevMode ? [{ debugName: "captureSettings" }] : /* istanbul ignore next */ []));
9695
10322
  this.uploadWidgetType = UploadWidgetType;
10323
+ this.fileUploaderSource = FileUploaderSource;
10324
+ /** The recorder opens below the capture row at a click on the microphone button. */
10325
+ this.isRecorderOpen = signal(false, /* @ts-ignore */
10326
+ ...(ngDevMode ? [{ debugName: "isRecorderOpen" }] : /* istanbul ignore next */ []));
10327
+ /** The descriptor's texts over the client's defaults, handed down to the capture widgets. */
10328
+ this.texts = UPLOAD_ZONE_DEFAULT_TEXTS;
10329
+ /** Cached per text set, so change detection hands each button the same descriptor. */
10330
+ this.captureButtons = new Map();
9696
10331
  this.maxSizeMb = 25;
9697
10332
  }
9698
10333
  ngOnInit() {
@@ -9709,13 +10344,17 @@ class UploadWidgetComponent {
9709
10344
  this.fileFormats = [uploadDescriptor.formats];
9710
10345
  }
9711
10346
  // fileFormats = this.descriptor.upload?.formats;
10347
+ this.texts = UploadWidgetUtils.resolveTexts(uploadDescriptor?.texts);
9712
10348
  this.i18n = {
9713
- addFile: uploadDescriptor?.title ?? 'Dokumentum hozzáadása',
9714
- browseOrDrag: uploadDescriptor?.description ?? 'tallózás vagy behúzás',
10349
+ addFile: uploadDescriptor?.title ?? this.texts.addFile,
10350
+ browseOrDrag: uploadDescriptor?.description ?? this.texts.browseOrDrag,
9715
10351
  formats: uploadDescriptor?.formats ?? '',
9716
- maxSize: `max. ${this.maxSizeMb} MB`,
9717
- upload: uploadDescriptor?.uploadButtonTitle ?? 'Feltöltés',
10352
+ maxSize: UploadWidgetUtils.fillPlaceholders(this.texts.maxSize, { size: this.maxSizeMb }),
10353
+ upload: uploadDescriptor?.uploadButtonTitle ?? this.texts.upload,
10354
+ unsupportedFormat: this.texts.unsupportedFormat,
10355
+ fileTooLarge: this.texts.fileTooLarge,
9718
10356
  };
10357
+ this.captureButtons.clear();
9719
10358
  }
9720
10359
  upload(files) {
9721
10360
  this.uploadFilesEvent.emit({
@@ -9724,23 +10363,103 @@ class UploadWidgetComponent {
9724
10363
  });
9725
10364
  this.fileUploadMaterial.files = [];
9726
10365
  }
10366
+ /** A captured file takes the picker's path: the same checks, the list or the upload. */
9727
10367
  uploadRecording(file) {
9728
- this.loadFilesIntoWidget([file]);
10368
+ this.fileUploadMaterial.processFiles([file]);
9729
10369
  }
9730
10370
  uploadImage(files) {
9731
- this.loadFilesIntoWidget(files);
10371
+ this.fileUploadMaterial.processFiles(files);
9732
10372
  }
9733
- loadFilesIntoWidget(files) {
9734
- if (this.autoUpload) {
9735
- this.uploadFilesEvent.emit({
9736
- files: files,
9737
- uploadDescriptor: this.uploadDescriptor(),
9738
- });
10373
+ /** True when the sources decide the zone: they are set and not the default FILE alone beside
10374
+ * uploadWidgets. */
10375
+ hasSources() {
10376
+ const sources = this.sources() ?? [];
10377
+ const isDefaultOnly = sources.length === 1 && sources[0] === FileUploaderSource.FILE;
10378
+ return !!sources.length && !(isDefaultOnly && !!this.uploadDescriptor()?.uploadWidgets?.length);
10379
+ }
10380
+ isSourceNeeded(source) {
10381
+ return this.sources()?.includes(source) ?? false;
10382
+ }
10383
+ isPickerShown() {
10384
+ return !this.hasSources() || this.isSourceNeeded(FileUploaderSource.FILE);
10385
+ }
10386
+ isCaptureOnly() {
10387
+ return !this.isPickerShown();
10388
+ }
10389
+ /** Capture-only uploads at once whatever the descriptor says, as there is no upload button. */
10390
+ isUploadImmediate() {
10391
+ return !!this.autoUpload || this.isCaptureOnly();
10392
+ }
10393
+ getCaptureSources() {
10394
+ return (this.sources() ?? []).filter((source) => source !== FileUploaderSource.FILE);
10395
+ }
10396
+ /** On a phone a camera button stays the device's own camera, whatever the Capture Mode says. */
10397
+ startCapture(source) {
10398
+ const camera = this.getCameraWidget(source);
10399
+ if (this.captureSettings().mode === CaptureMode.DIALOG && !camera?.isMobile()) {
10400
+ this.openCaptureDialog(source);
9739
10401
  }
9740
- else {
9741
- this.fileUploadMaterial.files.push(...files);
10402
+ else if (camera) {
10403
+ camera.openCapture();
10404
+ }
10405
+ else if (source === FileUploaderSource.MICROPHONE) {
10406
+ this.isRecorderOpen.update((open) => !open);
9742
10407
  }
9743
10408
  }
10409
+ getCameraWidget(source) {
10410
+ if (source === FileUploaderSource.MICROPHONE) {
10411
+ return undefined;
10412
+ }
10413
+ return source === FileUploaderSource.CAMERA_PHOTO ? this.photoCapture : this.videoCapture;
10414
+ }
10415
+ openCaptureDialog(source) {
10416
+ this.captureDialogService
10417
+ .openCaptureDialog({
10418
+ size: {},
10419
+ content: { title: this.texts[CAPTURE_BUTTONS[source].dialogTitle] },
10420
+ source,
10421
+ settings: this.captureSettings(),
10422
+ texts: this.texts,
10423
+ maxFileSize: source === FileUploaderSource.CAMERA_PHOTO ? this.getMaxFileSize() : undefined,
10424
+ })
10425
+ .afterClosed()
10426
+ .subscribe((files) => {
10427
+ if (files) {
10428
+ this.uploadImage(files);
10429
+ }
10430
+ });
10431
+ }
10432
+ getMaxFileSize() {
10433
+ return this.maxSizeMb * 1024 * 1024;
10434
+ }
10435
+ getCaptureButtonClass(source) {
10436
+ return CAPTURE_BUTTONS[source].cssClass;
10437
+ }
10438
+ getCaptureButton(source) {
10439
+ if (!this.captureButtons.has(source)) {
10440
+ const button = CAPTURE_BUTTONS[source];
10441
+ this.captureButtons.set(source, {
10442
+ title: '',
10443
+ type: UiActionButtonType.ICON,
10444
+ icon: button.icon,
10445
+ iconPosition: IconPosition.PRE,
10446
+ color: 'primary',
10447
+ tooltip: { tooltip: this.texts[button.tooltip] },
10448
+ });
10449
+ }
10450
+ return this.captureButtons.get(source);
10451
+ }
10452
+ /** The hint of a capture-only zone names its sources, as the picker's lines name the files.
10453
+ * Its grammar is owned by the captureOnlyHint and or texts. */
10454
+ getCaptureOnlyHint() {
10455
+ const nouns = this.getCaptureSources().map((source) => this.texts[CAPTURE_BUTTONS[source].noun]);
10456
+ const last = nouns.pop();
10457
+ const joined = nouns.length ? `${nouns.join(', ')} ${this.texts.or} ${last}` : last;
10458
+ const hint = UploadWidgetUtils.fillPlaceholders(this.texts.captureOnlyHint, {
10459
+ sources: joined,
10460
+ });
10461
+ return `${hint.charAt(0).toUpperCase()}${hint.slice(1)} · ${this.i18n?.maxSize}`;
10462
+ }
9744
10463
  widgetNeeded(type) {
9745
10464
  return this.uploadDescriptor()?.uploadWidgets?.includes(type) ?? false;
9746
10465
  }
@@ -9749,26 +10468,175 @@ class UploadWidgetComponent {
9749
10468
  }
9750
10469
  get defaultUploadDescriptor() {
9751
10470
  return {
9752
- title: 'Dokumentum hozzáadása',
9753
- description: 'tallózás vagy behúzás',
10471
+ title: UPLOAD_ZONE_DEFAULT_TEXTS.addFile,
10472
+ description: UPLOAD_ZONE_DEFAULT_TEXTS.browseOrDrag,
9754
10473
  maxSize: '25',
9755
10474
  autoUpload: false,
9756
10475
  };
9757
10476
  }
9758
10477
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: UploadWidgetComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
9759
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.8", type: UploadWidgetComponent, isStandalone: true, selector: "smart-upload-widget", inputs: { uploadDescriptor: { classPropertyName: "uploadDescriptor", publicName: "uploadDescriptor", isSignal: true, isRequired: false, transformFunction: null }, isMultiple: { classPropertyName: "isMultiple", publicName: "isMultiple", isSignal: true, isRequired: true, transformFunction: null }, isDisabled: { classPropertyName: "isDisabled", publicName: "isDisabled", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { uploadFilesEvent: "uploadFilesEvent" }, viewQueries: [{ propertyName: "fileUploadMaterial", first: true, predicate: ["fileUploadMaterial"], descendants: true }], ngImport: i0, template: "<div class=\"sb4UploadWidget\">\n <!-- IMAGE TYPE WIDGET -->\n @if (widgetNeeded(uploadWidgetType.IMAGE) || widgetNeeded(uploadWidgetType.ALL)) {\n <photo-capture-widget\n [maxFileSize]=\"maxSizeMb * 1024 * 1024\"\n (photoCaptured)=\"uploadImage($event)\"\n >\n </photo-capture-widget>\n }\n\n <!-- SOUND TYPE WIDGET -->\n @if (widgetNeeded(uploadWidgetType.SOUND) || widgetNeeded(uploadWidgetType.ALL)) {\n <voice-record-widget (recordingSaved)=\"uploadRecording($event)\"> </voice-record-widget>\n }\n <smartfileuploader\n #fileUploadMaterial\n [i18n]=\"i18n\"\n [fileFormats]=\"fileFormats\"\n [maxSizeMb]=\"maxSizeMb\"\n [uploadCallback]=\"upload.bind(this)\"\n [isMultiple]=\"isMultiple()\"\n [autoUpload]=\"autoUpload\"\n [isDisabled]=\"isDisabled()\"\n ></smartfileuploader>\n</div>\n", styles: ["photo-capture-widget{width:100%;max-width:100%;overflow:hidden;display:flex;flex-direction:column;align-items:center}voice-record-widget{padding:.5rem;border-bottom:1px solid #e5e7eb}voice-record-widget ::ng-deep .sb4-red,voice-record-widget ::ng-deep .sb4-primary,voice-record-widget ::ng-deep ui-action-button{border:unset!important}@media(max-width:900px){:host{width:100%}}\n"], dependencies: [{ kind: "component", type: PhotoCaptureWidgetComponent, selector: "photo-capture-widget", inputs: ["maxFileSize"], outputs: ["photoCaptured"] }, { kind: "component", type: VoiceRecordWidgetComponent, selector: "voice-record-widget", inputs: ["openDirection"], outputs: ["recordingSaved"] }, { kind: "component", type: SmartfileuploaderComponent, selector: "smartfileuploader", inputs: ["uploadCallback", "fileFormats", "maxSizeMb", "i18n", "useIconButton", "isMultiple", "autoUpload", "isDisabled"] }] }); }
10478
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.8", type: UploadWidgetComponent, isStandalone: true, selector: "smart-upload-widget", inputs: { uploadDescriptor: { classPropertyName: "uploadDescriptor", publicName: "uploadDescriptor", isSignal: true, isRequired: false, transformFunction: null }, isMultiple: { classPropertyName: "isMultiple", publicName: "isMultiple", isSignal: true, isRequired: true, transformFunction: null }, isDisabled: { classPropertyName: "isDisabled", publicName: "isDisabled", isSignal: true, isRequired: false, transformFunction: null }, sources: { classPropertyName: "sources", publicName: "sources", isSignal: true, isRequired: false, transformFunction: null }, capture: { classPropertyName: "capture", publicName: "capture", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { uploadFilesEvent: "uploadFilesEvent" }, viewQueries: [{ propertyName: "fileUploadMaterial", first: true, predicate: ["fileUploadMaterial"], descendants: true }, { propertyName: "photoCapture", first: true, predicate: ["photoCapture"], descendants: true }, { propertyName: "videoCapture", first: true, predicate: ["videoCapture"], descendants: true }], ngImport: i0, template: "<div class=\"sb4UploadWidget\">\n @if (hasSources()) {\n <!-- CAPTURE ROW OF THE SOURCES, hidden in view mode -->\n @if (getCaptureSources().length && !isDisabled()) {\n <div class=\"captureRow\" [class.captureOnly]=\"isCaptureOnly()\">\n <div class=\"captureButtons\">\n @for (source of getCaptureSources(); track source) {\n <ui-action-button\n class=\"{{ getCaptureButtonClass(source) }}\"\n [descriptor]=\"getCaptureButton(source)\"\n (actionClick)=\"startCapture(source)\"\n ></ui-action-button>\n }\n </div>\n @if (isCaptureOnly()) {\n <div class=\"captureHint\">{{ getCaptureOnlyHint() }}</div>\n }\n </div>\n }\n @if (isSourceNeeded(fileUploaderSource.CAMERA_PHOTO)) {\n <photo-capture-widget\n #photoCapture\n [mode]=\"'PHOTO'\"\n [externalTrigger]=\"true\"\n [deviceStart]=\"captureSettings().deviceStart\"\n [handover]=\"captureSettings().handover\"\n [texts]=\"texts\"\n [maxFileSize]=\"getMaxFileSize()\"\n (photoCaptured)=\"uploadImage($event)\"\n ></photo-capture-widget>\n }\n @if (isSourceNeeded(fileUploaderSource.CAMERA_VIDEO)) {\n <photo-capture-widget\n #videoCapture\n [mode]=\"'VIDEO'\"\n [externalTrigger]=\"true\"\n [deviceStart]=\"captureSettings().deviceStart\"\n [handover]=\"captureSettings().handover\"\n [texts]=\"texts\"\n (photoCaptured)=\"uploadImage($event)\"\n ></photo-capture-widget>\n }\n @if (isRecorderOpen()) {\n <voice-record-widget\n [openDirection]=\"captureSettings().openDirection\"\n [deviceStart]=\"captureSettings().deviceStart\"\n [handover]=\"captureSettings().handover\"\n [showSaveTitle]=\"true\"\n [texts]=\"texts\"\n [showMicrophoneError]=\"true\"\n (recordingSaved)=\"uploadRecording($event)\"\n ></voice-record-widget>\n }\n } @else {\n <!-- IMAGE TYPE WIDGET -->\n @if (widgetNeeded(uploadWidgetType.IMAGE) || widgetNeeded(uploadWidgetType.ALL)) {\n <photo-capture-widget\n [maxFileSize]=\"getMaxFileSize()\"\n [texts]=\"texts\"\n (photoCaptured)=\"uploadImage($event)\"\n >\n </photo-capture-widget>\n }\n\n <!-- SOUND TYPE WIDGET -->\n @if (widgetNeeded(uploadWidgetType.SOUND) || widgetNeeded(uploadWidgetType.ALL)) {\n <voice-record-widget\n [texts]=\"texts\"\n [showMicrophoneError]=\"true\"\n (recordingSaved)=\"uploadRecording($event)\"\n ></voice-record-widget>\n }\n }\n <smartfileuploader\n #fileUploadMaterial\n [i18n]=\"i18n\"\n [fileFormats]=\"fileFormats\"\n [maxSizeMb]=\"maxSizeMb\"\n [uploadCallback]=\"upload.bind(this)\"\n [isMultiple]=\"isMultiple()\"\n [autoUpload]=\"isUploadImmediate()\"\n [isDisabled]=\"isDisabled()\"\n [pickerShown]=\"isPickerShown()\"\n ></smartfileuploader>\n</div>\n", styles: ["photo-capture-widget{width:100%;max-width:100%;overflow:hidden;display:flex;flex-direction:column;align-items:center}voice-record-widget{box-sizing:border-box;padding:.5rem;border-bottom:1px solid #e5e7eb}voice-record-widget ::ng-deep .sb4-red,voice-record-widget ::ng-deep .sb4-primary,voice-record-widget ::ng-deep ui-action-button{border:unset!important}@media(max-width:900px){:host{width:100%}}.captureRow{display:flex;justify-content:center;padding:.5rem;border-bottom:1px solid #e5e7eb}.captureRow.captureOnly{border-bottom:unset;flex-direction:column;align-items:center;padding:.875rem .5rem;gap:.375rem}.captureButtons{display:flex;gap:.5rem}.captureButtons ::ng-deep button{border:unset}.captureHint{font-size:12px;color:#6b7280}\n"], dependencies: [{ kind: "component", type: PhotoCaptureWidgetComponent, selector: "photo-capture-widget", inputs: ["maxFileSize", "mode", "externalTrigger", "texts", "deviceStart", "handover", "actionsInHost"], outputs: ["photoCaptured"] }, { kind: "component", type: VoiceRecordWidgetComponent, selector: "voice-record-widget", inputs: ["openDirection", "texts", "showMicrophoneError", "deviceStart", "handover", "showSaveTitle", "actionsInHost"], outputs: ["recordingSaved"] }, { kind: "component", type: SmartfileuploaderComponent, selector: "smartfileuploader", inputs: ["uploadCallback", "fileFormats", "maxSizeMb", "i18n", "useIconButton", "isMultiple", "autoUpload", "isDisabled", "pickerShown"] }, { kind: "component", type: UiActionButtonComponent, selector: "ui-action-button", inputs: ["disabled", "descriptor", "code", "identifier", "viewActivated", "tab", "rowState", "addedCssClass", "addBasicClasses", "iconPlaceholder", "chords"], outputs: ["actionClick", "actionDoubleClick"] }] }); }
9760
10479
  }
9761
10480
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: UploadWidgetComponent, decorators: [{
9762
10481
  type: Component,
9763
- args: [{ selector: 'smart-upload-widget', imports: [PhotoCaptureWidgetComponent, VoiceRecordWidgetComponent, SmartfileuploaderComponent], template: "<div class=\"sb4UploadWidget\">\n <!-- IMAGE TYPE WIDGET -->\n @if (widgetNeeded(uploadWidgetType.IMAGE) || widgetNeeded(uploadWidgetType.ALL)) {\n <photo-capture-widget\n [maxFileSize]=\"maxSizeMb * 1024 * 1024\"\n (photoCaptured)=\"uploadImage($event)\"\n >\n </photo-capture-widget>\n }\n\n <!-- SOUND TYPE WIDGET -->\n @if (widgetNeeded(uploadWidgetType.SOUND) || widgetNeeded(uploadWidgetType.ALL)) {\n <voice-record-widget (recordingSaved)=\"uploadRecording($event)\"> </voice-record-widget>\n }\n <smartfileuploader\n #fileUploadMaterial\n [i18n]=\"i18n\"\n [fileFormats]=\"fileFormats\"\n [maxSizeMb]=\"maxSizeMb\"\n [uploadCallback]=\"upload.bind(this)\"\n [isMultiple]=\"isMultiple()\"\n [autoUpload]=\"autoUpload\"\n [isDisabled]=\"isDisabled()\"\n ></smartfileuploader>\n</div>\n", styles: ["photo-capture-widget{width:100%;max-width:100%;overflow:hidden;display:flex;flex-direction:column;align-items:center}voice-record-widget{padding:.5rem;border-bottom:1px solid #e5e7eb}voice-record-widget ::ng-deep .sb4-red,voice-record-widget ::ng-deep .sb4-primary,voice-record-widget ::ng-deep ui-action-button{border:unset!important}@media(max-width:900px){:host{width:100%}}\n"] }]
10482
+ args: [{ selector: 'smart-upload-widget', imports: [
10483
+ PhotoCaptureWidgetComponent,
10484
+ VoiceRecordWidgetComponent,
10485
+ SmartfileuploaderComponent,
10486
+ UiActionButtonComponent,
10487
+ ], template: "<div class=\"sb4UploadWidget\">\n @if (hasSources()) {\n <!-- CAPTURE ROW OF THE SOURCES, hidden in view mode -->\n @if (getCaptureSources().length && !isDisabled()) {\n <div class=\"captureRow\" [class.captureOnly]=\"isCaptureOnly()\">\n <div class=\"captureButtons\">\n @for (source of getCaptureSources(); track source) {\n <ui-action-button\n class=\"{{ getCaptureButtonClass(source) }}\"\n [descriptor]=\"getCaptureButton(source)\"\n (actionClick)=\"startCapture(source)\"\n ></ui-action-button>\n }\n </div>\n @if (isCaptureOnly()) {\n <div class=\"captureHint\">{{ getCaptureOnlyHint() }}</div>\n }\n </div>\n }\n @if (isSourceNeeded(fileUploaderSource.CAMERA_PHOTO)) {\n <photo-capture-widget\n #photoCapture\n [mode]=\"'PHOTO'\"\n [externalTrigger]=\"true\"\n [deviceStart]=\"captureSettings().deviceStart\"\n [handover]=\"captureSettings().handover\"\n [texts]=\"texts\"\n [maxFileSize]=\"getMaxFileSize()\"\n (photoCaptured)=\"uploadImage($event)\"\n ></photo-capture-widget>\n }\n @if (isSourceNeeded(fileUploaderSource.CAMERA_VIDEO)) {\n <photo-capture-widget\n #videoCapture\n [mode]=\"'VIDEO'\"\n [externalTrigger]=\"true\"\n [deviceStart]=\"captureSettings().deviceStart\"\n [handover]=\"captureSettings().handover\"\n [texts]=\"texts\"\n (photoCaptured)=\"uploadImage($event)\"\n ></photo-capture-widget>\n }\n @if (isRecorderOpen()) {\n <voice-record-widget\n [openDirection]=\"captureSettings().openDirection\"\n [deviceStart]=\"captureSettings().deviceStart\"\n [handover]=\"captureSettings().handover\"\n [showSaveTitle]=\"true\"\n [texts]=\"texts\"\n [showMicrophoneError]=\"true\"\n (recordingSaved)=\"uploadRecording($event)\"\n ></voice-record-widget>\n }\n } @else {\n <!-- IMAGE TYPE WIDGET -->\n @if (widgetNeeded(uploadWidgetType.IMAGE) || widgetNeeded(uploadWidgetType.ALL)) {\n <photo-capture-widget\n [maxFileSize]=\"getMaxFileSize()\"\n [texts]=\"texts\"\n (photoCaptured)=\"uploadImage($event)\"\n >\n </photo-capture-widget>\n }\n\n <!-- SOUND TYPE WIDGET -->\n @if (widgetNeeded(uploadWidgetType.SOUND) || widgetNeeded(uploadWidgetType.ALL)) {\n <voice-record-widget\n [texts]=\"texts\"\n [showMicrophoneError]=\"true\"\n (recordingSaved)=\"uploadRecording($event)\"\n ></voice-record-widget>\n }\n }\n <smartfileuploader\n #fileUploadMaterial\n [i18n]=\"i18n\"\n [fileFormats]=\"fileFormats\"\n [maxSizeMb]=\"maxSizeMb\"\n [uploadCallback]=\"upload.bind(this)\"\n [isMultiple]=\"isMultiple()\"\n [autoUpload]=\"isUploadImmediate()\"\n [isDisabled]=\"isDisabled()\"\n [pickerShown]=\"isPickerShown()\"\n ></smartfileuploader>\n</div>\n", styles: ["photo-capture-widget{width:100%;max-width:100%;overflow:hidden;display:flex;flex-direction:column;align-items:center}voice-record-widget{box-sizing:border-box;padding:.5rem;border-bottom:1px solid #e5e7eb}voice-record-widget ::ng-deep .sb4-red,voice-record-widget ::ng-deep .sb4-primary,voice-record-widget ::ng-deep ui-action-button{border:unset!important}@media(max-width:900px){:host{width:100%}}.captureRow{display:flex;justify-content:center;padding:.5rem;border-bottom:1px solid #e5e7eb}.captureRow.captureOnly{border-bottom:unset;flex-direction:column;align-items:center;padding:.875rem .5rem;gap:.375rem}.captureButtons{display:flex;gap:.5rem}.captureButtons ::ng-deep button{border:unset}.captureHint{font-size:12px;color:#6b7280}\n"] }]
9764
10488
  }], propDecorators: { fileUploadMaterial: [{
9765
10489
  type: ViewChild,
9766
10490
  args: ['fileUploadMaterial']
9767
- }], uploadDescriptor: [{ type: i0.Input, args: [{ isSignal: true, alias: "uploadDescriptor", required: false }] }], isMultiple: [{ type: i0.Input, args: [{ isSignal: true, alias: "isMultiple", required: true }] }], isDisabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "isDisabled", required: false }] }], uploadFilesEvent: [{ type: i0.Output, args: ["uploadFilesEvent"] }] } });
10491
+ }], photoCapture: [{
10492
+ type: ViewChild,
10493
+ args: ['photoCapture']
10494
+ }], videoCapture: [{
10495
+ type: ViewChild,
10496
+ args: ['videoCapture']
10497
+ }], uploadDescriptor: [{ type: i0.Input, args: [{ isSignal: true, alias: "uploadDescriptor", required: false }] }], isMultiple: [{ type: i0.Input, args: [{ isSignal: true, alias: "isMultiple", required: true }] }], isDisabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "isDisabled", required: false }] }], sources: [{ type: i0.Input, args: [{ isSignal: true, alias: "sources", required: false }] }], capture: [{ type: i0.Input, args: [{ isSignal: true, alias: "capture", required: false }] }], uploadFilesEvent: [{ type: i0.Output, args: ["uploadFilesEvent"] }] } });
10498
+ const CAPTURE_BUTTONS = {
10499
+ [FileUploaderSource.CAMERA_PHOTO]: {
10500
+ icon: 'photo_camera',
10501
+ cssClass: 'capturePhotoButton',
10502
+ tooltip: 'capturePhoto',
10503
+ noun: 'photo',
10504
+ dialogTitle: 'photoDialogTitle',
10505
+ },
10506
+ [FileUploaderSource.CAMERA_VIDEO]: {
10507
+ icon: 'videocam',
10508
+ cssClass: 'captureVideoButton',
10509
+ tooltip: 'captureVideo',
10510
+ noun: 'video',
10511
+ dialogTitle: 'videoDialogTitle',
10512
+ },
10513
+ [FileUploaderSource.MICROPHONE]: {
10514
+ icon: 'mic',
10515
+ cssClass: 'recordAudioButton',
10516
+ tooltip: 'recordAudio',
10517
+ noun: 'audio',
10518
+ dialogTitle: 'audioDialogTitle',
10519
+ },
10520
+ };
10521
+
10522
+ const FILE_PREVIEW_KEY_PREFIX = 'filePreview.';
10523
+ /** The preview element of a mime type, or `undefined` when the file has no preview. */
10524
+ function getFilePreviewKind(mimeType) {
10525
+ const type = mimeType?.split('/')[0];
10526
+ return type === 'image' || type === 'video' || type === 'audio' ? type : undefined;
10527
+ }
10528
+ /** The server's preview key of a single-file widget (`FileWidgetHandlerApi.getPreviewItemKey`). */
10529
+ function createSingleFilePreviewKey(widgetKey) {
10530
+ return FILE_PREVIEW_KEY_PREFIX + widgetKey;
10531
+ }
10532
+ /** The server's preview key of a list row: the widget key and the object id of the dataUri. */
10533
+ function createListFilePreviewKey(widgetKey, dataUri) {
10534
+ const path = dataUri.split(/[?#]/)[0];
10535
+ const objectId = path.substring(path.lastIndexOf('/') + 1).split('.')[0];
10536
+ return `${FILE_PREVIEW_KEY_PREFIX}${widgetKey}.${objectId}`;
10537
+ }
10538
+ /**
10539
+ * Loads the previews of a file editor through the view's authenticated download and owns their
10540
+ * blob URLs: a URL is revoked when its file leaves the requests and when the editor is destroyed.
10541
+ * Created in the editor's injection context; the view uuid is the editor's client's.
10542
+ */
10543
+ class FilePreviewLoader {
10544
+ constructor() {
10545
+ this.viewContext = inject(SmartViewContextService);
10546
+ this.smartComponent = injectSmartComponent();
10547
+ this.requests = signal([], /* @ts-ignore */
10548
+ ...(ngDevMode ? [{ debugName: "requests" }] : /* istanbul ignore next */ []));
10549
+ this.previewsState = signal(new Map(), /* @ts-ignore */
10550
+ ...(ngDevMode ? [{ debugName: "previewsState" }] : /* istanbul ignore next */ []));
10551
+ this.loadingDataUris = new Set();
10552
+ /** Not fetched again while it stays in the requests, as every model write sets them anew. */
10553
+ this.failedDataUris = new Set();
10554
+ this.destroyed = false;
10555
+ effect(() => this.loadPreviews(this.smartComponent?.widgetUuid(), this.requests()));
10556
+ inject(DestroyRef).onDestroy(() => {
10557
+ this.destroyed = true;
10558
+ this.previewsState().forEach((preview) => URL.revokeObjectURL(preview.url));
10559
+ });
10560
+ }
10561
+ /** Sets the files to preview; a file of no preview kind is ignored. */
10562
+ setRequests(requests) {
10563
+ this.requests.set(requests.filter((request) => getFilePreviewKind(request.mimeType)));
10564
+ }
10565
+ /** The loaded preview of a file, `undefined` while loading or without one. */
10566
+ getPreview(dataUri) {
10567
+ return dataUri ? this.previewsState().get(dataUri) : undefined;
10568
+ }
10569
+ /**
10570
+ * Turns a video preview whose media reports no video track into an audio preview of the same
10571
+ * blob URL, as a voice note may be stored with a video mime type; call it at `loadedmetadata`.
10572
+ */
10573
+ applyVideoMetadata(dataUri, video) {
10574
+ const preview = this.previewsState().get(dataUri);
10575
+ if (preview?.kind === 'video' && video.videoWidth === 0 && video.videoHeight === 0) {
10576
+ const previews = new Map(this.previewsState());
10577
+ previews.set(dataUri, { ...preview, kind: 'audio' });
10578
+ this.previewsState.set(previews);
10579
+ }
10580
+ }
10581
+ loadPreviews(viewUuid, requests) {
10582
+ untracked(() => {
10583
+ const previews = new Map(this.previewsState());
10584
+ previews.forEach((preview, dataUri) => {
10585
+ if (!this.isRequested(dataUri)) {
10586
+ URL.revokeObjectURL(preview.url);
10587
+ previews.delete(dataUri);
10588
+ }
10589
+ });
10590
+ if (previews.size !== this.previewsState().size) {
10591
+ this.previewsState.set(previews);
10592
+ }
10593
+ this.failedDataUris.forEach((dataUri) => {
10594
+ if (!this.isRequested(dataUri)) {
10595
+ this.failedDataUris.delete(dataUri);
10596
+ }
10597
+ });
10598
+ if (viewUuid) {
10599
+ requests
10600
+ .filter((request) => !previews.has(request.dataUri))
10601
+ .filter((request) => !this.loadingDataUris.has(request.dataUri))
10602
+ .filter((request) => !this.failedDataUris.has(request.dataUri))
10603
+ .forEach((request) => this.loadPreview(viewUuid, request));
10604
+ }
10605
+ });
10606
+ }
10607
+ async loadPreview(viewUuid, request) {
10608
+ this.loadingDataUris.add(request.dataUri);
10609
+ let data;
10610
+ try {
10611
+ data = await this.viewContext.downloadResource(viewUuid, request.key);
10612
+ }
10613
+ catch {
10614
+ // A file the server has not registered keeps the card without its preview.
10615
+ if (this.isRequested(request.dataUri)) {
10616
+ this.failedDataUris.add(request.dataUri);
10617
+ }
10618
+ return;
10619
+ }
10620
+ finally {
10621
+ this.loadingDataUris.delete(request.dataUri);
10622
+ }
10623
+ if (this.destroyed || !this.isRequested(request.dataUri)) {
10624
+ return;
10625
+ }
10626
+ const url = URL.createObjectURL(new Blob([data], { type: request.mimeType }));
10627
+ const previews = new Map(this.previewsState());
10628
+ previews.set(request.dataUri, { kind: getFilePreviewKind(request.mimeType), url });
10629
+ this.previewsState.set(previews);
10630
+ }
10631
+ isRequested(dataUri) {
10632
+ return this.requests().some((request) => request.dataUri === dataUri);
10633
+ }
10634
+ }
9768
10635
 
9769
10636
  class SmartFileEditorComponent {
9770
10637
  constructor() {
9771
10638
  this.cdr = inject(ChangeDetectorRef);
10639
+ this.previewLoader = new FilePreviewLoader();
9772
10640
  this.uploadFilesEvent = output();
9773
10641
  // ---- ControlValueAccessor implementation ----
9774
10642
  this.onChange = (value) => { };
@@ -9796,8 +10664,23 @@ class SmartFileEditorComponent {
9796
10664
  undoIcon: { identifier: 'undo' },
9797
10665
  };
9798
10666
  }
10667
+ this.requestPreview();
9799
10668
  this.cdr.markForCheck();
9800
10669
  }
10670
+ /** With thumbnail FULL the current value is previewed; any other value drops the preview. */
10671
+ requestPreview() {
10672
+ const value = this._widgetInstance.value;
10673
+ const isFull = this._widgetInstance.fileUploaderProperties?.thumbnail === FileUploaderThumbnail.FULL;
10674
+ this.previewLoader.setRequests(isFull && this.isTypeSupported(value)
10675
+ ? [
10676
+ {
10677
+ key: createSingleFilePreviewKey(this._widgetInstance.key),
10678
+ dataUri: value.dataUri,
10679
+ mimeType: value.mimeType,
10680
+ },
10681
+ ]
10682
+ : []);
10683
+ }
9801
10684
  upload(event) {
9802
10685
  if (!event.files || event.files.length == 0) {
9803
10686
  return;
@@ -9818,10 +10701,12 @@ class SmartFileEditorComponent {
9818
10701
  clear() {
9819
10702
  this.clearedFile = this._widgetInstance.value;
9820
10703
  this._widgetInstance.value = null;
10704
+ this.requestPreview();
9821
10705
  this.onChange(null);
9822
10706
  }
9823
10707
  undoClear() {
9824
10708
  this._widgetInstance.value = this.clearedFile;
10709
+ this.requestPreview();
9825
10710
  this.onChange(this.clearedFile);
9826
10711
  this.clearedFile = undefined;
9827
10712
  }
@@ -9871,6 +10756,7 @@ class SmartFileEditorComponent {
9871
10756
  if (value) {
9872
10757
  this.clearedFile = undefined;
9873
10758
  }
10759
+ this.requestPreview();
9874
10760
  this.cdr.markForCheck();
9875
10761
  }
9876
10762
  }
@@ -9896,6 +10782,15 @@ class SmartFileEditorComponent {
9896
10782
  get fileIcon() {
9897
10783
  return 'insert_drive_file';
9898
10784
  }
10785
+ get preview() {
10786
+ return this.previewLoader.getPreview(this._widgetInstance.value?.dataUri);
10787
+ }
10788
+ onPreviewMetadata(event) {
10789
+ const dataUri = this._widgetInstance.value?.dataUri;
10790
+ if (dataUri) {
10791
+ this.previewLoader.applyVideoMetadata(dataUri, event.target);
10792
+ }
10793
+ }
9899
10794
  get changeButton() {
9900
10795
  var props = this._widgetInstance.fileUploaderProperties;
9901
10796
  var baseAction = props.changeAction ?? props.uploadAction;
@@ -9940,7 +10835,7 @@ class SmartFileEditorComponent {
9940
10835
  useExisting: forwardRef(() => SmartFileEditorComponent),
9941
10836
  multi: true,
9942
10837
  },
9943
- ], ngImport: i0, template: "@if (widgetInstance && widgetInstance.fileUploaderProperties) {\n @if (widgetInstance.showLabel) {\n <h4\n class=\"labelContainer {{ widgetInstance.cssLabelClass ?? '' }}\"\n [ngClass]=\"getLabelNgClass()\"\n [ngStyle]=\"getLabelStyle()\"\n >\n {{ widgetInstance.label }}\n </h4>\n }\n\n <div class=\"fileEditorComponent\">\n @if (widgetInstance.value && !isTypeSupported(widgetInstance.value)) {\n <div class=\"errorMessage\">\n <smart-icon [icon]=\"errorIcon\" [color]=\"'var(--warn-color)'\"></smart-icon>\n <span> Unsupported widgetInstance.value type for this widget.</span>\n </div>\n } @else if (!widgetInstance.value || fileToChange) {\n <smart-upload-widget\n class=\"widgetContent {{ widgetInstance.cssClass ?? '' }}\"\n [ngClass]=\"getNgClass()\"\n [ngStyle]=\"getStyle()\"\n [uploadDescriptor]=\"widgetInstance.fileUploaderProperties.uploadDescriptor!\"\n [isMultiple]=\"false\"\n [isDisabled]=\"isDisabled\"\n (uploadFilesEvent)=\"upload($event)\"\n />\n @if (clearedFile && !isDisabled) {\n <div class=\"undoClearContainer\">\n <ui-action-button\n class=\"removeIcon undoClearButton\"\n (actionClick)=\"undoClear()\"\n [descriptor]=\"removeButton\"\n ></ui-action-button>\n </div>\n }\n }\n\n <div\n [ngClass]=\"{ disabledWidget: isDisabled }\"\n class=\"uploadedFileContainer\"\n [style.display]=\"\n widgetInstance.value && isTypeSupported(widgetInstance.value) ? 'block' : 'none'\n \"\n >\n <div class=\"uploadedFile\">\n <smart-icon class=\"uploadedFileIcon\" [icon]=\"fileIcon\"></smart-icon>\n\n <div class=\"fileData\">\n <div class=\"fileDataContainer\">\n <span class=\"fileName\">{{ widgetInstance.value?.fileName }}</span>\n <span class=\"fileSize\">{{ formatSize($safeNavigationMigration(widgetInstance.value?.size)) }}</span>\n </div>\n </div>\n\n <div class=\"fileActions\">\n <smart-ui-action-toolbar [id]=\"widgetInstance.toolbarId\"></smart-ui-action-toolbar>\n\n @if (!fileToChange) {\n <ui-action-button\n class=\"changeButton\"\n (actionClick)=\"change(widgetInstance.value)\"\n [disabled]=\"isDisabled\"\n [descriptor]=\"changeButton\"\n >\n </ui-action-button>\n @if (!isDisabled && isClearEnabled) {\n <ui-action-button\n class=\"deleteButton\"\n (actionClick)=\"clear()\"\n [descriptor]=\"deleteButton\"\n ></ui-action-button>\n }\n } @else {\n <ui-action-button\n class=\"removeIcon\"\n (actionClick)=\"cancelChange()\"\n [descriptor]=\"removeButton\"\n ></ui-action-button>\n }\n </div>\n </div>\n </div>\n </div>\n}\n", styles: [":host{--border-color: #a6aabd60;--def-border-radius: .5rem;width:100%}.fileEditorComponent{border:2px dashed var(--border-color);border-radius:var(--def-border-radius)}.fileEditorComponent ::ng-deep smartfileuploader ::ng-deep .container{border:unset;border-radius:unset}.uploadedFileContainer{display:flex;flex-direction:column}.uploadedFile{display:flex;flex-direction:row;justify-content:flex-start;align-items:center;padding:1rem;gap:1rem}.fileEditorComponent:has(::ng-deep smartfileuploader) ::ng-deep .uploadedFile{border-top:1px solid #e0e0e0}.fileSize{display:flex;flex-direction:row;font-size:smaller}.fileData{flex:1;display:flex;flex-direction:column}.fileDataContainer{display:flex;flex-direction:column;justify-content:flex-end;width:fit-content}.fileActions{display:flex;flex-direction:row;justify-content:flex-end;gap:.5rem}.fileActions ::ng-deep button,.undoClearContainer ::ng-deep button{border:unset}.fileActions ::ng-deep button:hover,.undoClearContainer ::ng-deep button:hover{border:unset}.undoClearContainer{display:flex;justify-content:flex-end;padding:0 1rem .5rem}.removeIcon ::ng-deep mat-icon{color:var(--warn-color)}smart-icon ::ng-deep mat-icon{font-size:3rem;width:unset;height:unset}.uploadedFileIcon{width:3rem;display:flex;justify-content:center}.fileEditorComponent:has(::ng-deep .errorMessage){border-color:var(--warn-color)}.errorMessage{padding:1rem;font-size:1.5rem;color:var(--warn-color);display:flex;flex-direction:row;align-items:center;gap:1rem}:host ::ng-deep .disabledWidget ::ng-deep .changeButton *,:host ::ng-deep .disabledWidget ::ng-deep .fileData *{opacity:.7}:host ::ng-deep smartfileuploader ::ng-deep .uploadedFile{background:repeating-linear-gradient(45deg,#fafafa,#fafafa 12px,#f1f1f1 12px 24px)}\n"], dependencies: [{ kind: "directive", type: NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "component", type: SmartIconComponent, selector: "smart-icon", inputs: ["icon", "color", "imageResource"] }, { kind: "component", type: UploadWidgetComponent, selector: "smart-upload-widget", inputs: ["uploadDescriptor", "isMultiple", "isDisabled"], outputs: ["uploadFilesEvent"] }, { kind: "component", type: UiActionToolbarComponent, selector: "smart-ui-action-toolbar", inputs: ["uiActionModels", "uiActionDescriptorService", "id", "executor", "widgetId", "nodeId", "actionParams", "scrollOnWrap", "toolbarPropertes"], outputs: ["actionFired"] }, { kind: "component", type: UiActionButtonComponent, selector: "ui-action-button", inputs: ["disabled", "descriptor", "code", "identifier", "viewActivated", "tab", "rowState", "addedCssClass", "addBasicClasses", "iconPlaceholder", "chords"], outputs: ["actionClick", "actionDoubleClick"] }] }); }
10838
+ ], ngImport: i0, template: "@if (widgetInstance && widgetInstance.fileUploaderProperties) {\n @if (widgetInstance.showLabel) {\n <h4\n class=\"labelContainer {{ widgetInstance.cssLabelClass ?? '' }}\"\n [ngClass]=\"getLabelNgClass()\"\n [ngStyle]=\"getLabelStyle()\"\n >\n {{ widgetInstance.label }}\n </h4>\n }\n\n <div class=\"fileEditorComponent\">\n @if (widgetInstance.value && !isTypeSupported(widgetInstance.value)) {\n <div class=\"errorMessage\">\n <smart-icon [icon]=\"errorIcon\" [color]=\"'var(--warn-color)'\"></smart-icon>\n <span> Unsupported widgetInstance.value type for this widget.</span>\n </div>\n } @else if (!widgetInstance.value || fileToChange) {\n <smart-upload-widget\n class=\"widgetContent {{ widgetInstance.cssClass ?? '' }}\"\n [ngClass]=\"getNgClass()\"\n [ngStyle]=\"getStyle()\"\n [uploadDescriptor]=\"widgetInstance.fileUploaderProperties.uploadDescriptor!\"\n [sources]=\"widgetInstance.fileUploaderProperties.sources\"\n [capture]=\"widgetInstance.fileUploaderProperties.capture\"\n [isMultiple]=\"false\"\n [isDisabled]=\"isDisabled\"\n (uploadFilesEvent)=\"upload($event)\"\n />\n @if (clearedFile && !isDisabled) {\n <div class=\"undoClearContainer\">\n <ui-action-button\n class=\"removeIcon undoClearButton\"\n (actionClick)=\"undoClear()\"\n [descriptor]=\"removeButton\"\n ></ui-action-button>\n </div>\n }\n }\n\n <div\n [ngClass]=\"{ disabledWidget: isDisabled }\"\n class=\"uploadedFileContainer\"\n [style.display]=\"\n widgetInstance.value && isTypeSupported(widgetInstance.value) ? 'block' : 'none'\n \"\n >\n @if (preview; as filePreview) {\n <div class=\"filePreview\">\n @switch (filePreview.kind) {\n @case ('image') {\n <img [src]=\"filePreview.url\" [alt]=\"widgetInstance.value?.fileName\" />\n }\n @case ('video') {\n <video\n controls\n [src]=\"filePreview.url\"\n (loadedmetadata)=\"onPreviewMetadata($event)\"\n ></video>\n }\n @case ('audio') {\n <audio controls [src]=\"filePreview.url\"></audio>\n }\n }\n </div>\n }\n <div class=\"uploadedFile\">\n @if (!preview) {\n <smart-icon class=\"uploadedFileIcon\" [icon]=\"fileIcon\"></smart-icon>\n }\n\n <div class=\"fileData\">\n <div class=\"fileDataContainer\">\n <span class=\"fileName\">{{ widgetInstance.value?.fileName }}</span>\n <span class=\"fileSize\">{{ formatSize($safeNavigationMigration(widgetInstance.value?.size)) }}</span>\n </div>\n </div>\n\n <div class=\"fileActions\">\n <smart-ui-action-toolbar [id]=\"widgetInstance.toolbarId\"></smart-ui-action-toolbar>\n\n @if (!fileToChange) {\n <ui-action-button\n class=\"changeButton\"\n (actionClick)=\"change(widgetInstance.value)\"\n [disabled]=\"isDisabled\"\n [descriptor]=\"changeButton\"\n >\n </ui-action-button>\n @if (!isDisabled && isClearEnabled) {\n <ui-action-button\n class=\"deleteButton\"\n (actionClick)=\"clear()\"\n [descriptor]=\"deleteButton\"\n ></ui-action-button>\n }\n } @else {\n <ui-action-button\n class=\"removeIcon\"\n (actionClick)=\"cancelChange()\"\n [descriptor]=\"removeButton\"\n ></ui-action-button>\n }\n </div>\n </div>\n </div>\n </div>\n}\n", styles: [":host{--border-color: #a6aabd60;--def-border-radius: .5rem;width:100%}.fileEditorComponent{border:2px dashed var(--border-color);border-radius:var(--def-border-radius)}.fileEditorComponent ::ng-deep smartfileuploader ::ng-deep .container{border:unset;border-radius:unset}.uploadedFileContainer{display:flex;flex-direction:column}.uploadedFile{display:flex;flex-direction:row;justify-content:flex-start;align-items:center;padding:1rem;gap:1rem}.fileEditorComponent:has(::ng-deep smartfileuploader) ::ng-deep .uploadedFile{border-top:1px solid #e0e0e0}.fileSize{display:flex;flex-direction:row;font-size:smaller}.fileData{flex:1;display:flex;flex-direction:column}.fileDataContainer{display:flex;flex-direction:column;justify-content:flex-end;width:fit-content}.fileActions{display:flex;flex-direction:row;justify-content:flex-end;gap:.5rem}.fileActions ::ng-deep button,.undoClearContainer ::ng-deep button{border:unset}.fileActions ::ng-deep button:hover,.undoClearContainer ::ng-deep button:hover{border:unset}.undoClearContainer{display:flex;justify-content:flex-end;padding:0 1rem .5rem}.removeIcon ::ng-deep mat-icon{color:var(--warn-color)}smart-icon ::ng-deep mat-icon{font-size:3rem;width:unset;height:unset}.uploadedFileIcon{width:3rem;display:flex;justify-content:center}.filePreview{padding:.75rem 1rem 0}.filePreview img,.filePreview video{display:block;width:100%;max-height:200px;object-fit:contain;background:var(--color-surface-alt);border-radius:4px}.filePreview audio{display:block;width:100%}.fileEditorComponent:has(::ng-deep .errorMessage){border-color:var(--warn-color)}.errorMessage{padding:1rem;font-size:1.5rem;color:var(--warn-color);display:flex;flex-direction:row;align-items:center;gap:1rem}:host ::ng-deep .disabledWidget ::ng-deep .changeButton *,:host ::ng-deep .disabledWidget ::ng-deep .fileData *{opacity:.7}:host ::ng-deep smartfileuploader ::ng-deep .uploadedFile{background:repeating-linear-gradient(45deg,#fafafa,#fafafa 12px,#f1f1f1 12px 24px)}\n"], dependencies: [{ kind: "directive", type: NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "component", type: SmartIconComponent, selector: "smart-icon", inputs: ["icon", "color", "imageResource"] }, { kind: "component", type: UploadWidgetComponent, selector: "smart-upload-widget", inputs: ["uploadDescriptor", "isMultiple", "isDisabled", "sources", "capture"], outputs: ["uploadFilesEvent"] }, { kind: "component", type: UiActionToolbarComponent, selector: "smart-ui-action-toolbar", inputs: ["uiActionModels", "uiActionDescriptorService", "id", "executor", "widgetId", "nodeId", "actionParams", "scrollOnWrap", "toolbarPropertes"], outputs: ["actionFired"] }, { kind: "component", type: UiActionButtonComponent, selector: "ui-action-button", inputs: ["disabled", "descriptor", "code", "identifier", "viewActivated", "tab", "rowState", "addedCssClass", "addBasicClasses", "iconPlaceholder", "chords"], outputs: ["actionClick", "actionDoubleClick"] }] }); }
9944
10839
  }
9945
10840
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: SmartFileEditorComponent, decorators: [{
9946
10841
  type: Component,
@@ -9950,7 +10845,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImpor
9950
10845
  useExisting: forwardRef(() => SmartFileEditorComponent),
9951
10846
  multi: true,
9952
10847
  },
9953
- ], imports: [NgClass, NgStyle, SmartIconComponent, UploadWidgetComponent, UiActionToolbarComponent, UiActionButtonComponent], template: "@if (widgetInstance && widgetInstance.fileUploaderProperties) {\n @if (widgetInstance.showLabel) {\n <h4\n class=\"labelContainer {{ widgetInstance.cssLabelClass ?? '' }}\"\n [ngClass]=\"getLabelNgClass()\"\n [ngStyle]=\"getLabelStyle()\"\n >\n {{ widgetInstance.label }}\n </h4>\n }\n\n <div class=\"fileEditorComponent\">\n @if (widgetInstance.value && !isTypeSupported(widgetInstance.value)) {\n <div class=\"errorMessage\">\n <smart-icon [icon]=\"errorIcon\" [color]=\"'var(--warn-color)'\"></smart-icon>\n <span> Unsupported widgetInstance.value type for this widget.</span>\n </div>\n } @else if (!widgetInstance.value || fileToChange) {\n <smart-upload-widget\n class=\"widgetContent {{ widgetInstance.cssClass ?? '' }}\"\n [ngClass]=\"getNgClass()\"\n [ngStyle]=\"getStyle()\"\n [uploadDescriptor]=\"widgetInstance.fileUploaderProperties.uploadDescriptor!\"\n [isMultiple]=\"false\"\n [isDisabled]=\"isDisabled\"\n (uploadFilesEvent)=\"upload($event)\"\n />\n @if (clearedFile && !isDisabled) {\n <div class=\"undoClearContainer\">\n <ui-action-button\n class=\"removeIcon undoClearButton\"\n (actionClick)=\"undoClear()\"\n [descriptor]=\"removeButton\"\n ></ui-action-button>\n </div>\n }\n }\n\n <div\n [ngClass]=\"{ disabledWidget: isDisabled }\"\n class=\"uploadedFileContainer\"\n [style.display]=\"\n widgetInstance.value && isTypeSupported(widgetInstance.value) ? 'block' : 'none'\n \"\n >\n <div class=\"uploadedFile\">\n <smart-icon class=\"uploadedFileIcon\" [icon]=\"fileIcon\"></smart-icon>\n\n <div class=\"fileData\">\n <div class=\"fileDataContainer\">\n <span class=\"fileName\">{{ widgetInstance.value?.fileName }}</span>\n <span class=\"fileSize\">{{ formatSize($safeNavigationMigration(widgetInstance.value?.size)) }}</span>\n </div>\n </div>\n\n <div class=\"fileActions\">\n <smart-ui-action-toolbar [id]=\"widgetInstance.toolbarId\"></smart-ui-action-toolbar>\n\n @if (!fileToChange) {\n <ui-action-button\n class=\"changeButton\"\n (actionClick)=\"change(widgetInstance.value)\"\n [disabled]=\"isDisabled\"\n [descriptor]=\"changeButton\"\n >\n </ui-action-button>\n @if (!isDisabled && isClearEnabled) {\n <ui-action-button\n class=\"deleteButton\"\n (actionClick)=\"clear()\"\n [descriptor]=\"deleteButton\"\n ></ui-action-button>\n }\n } @else {\n <ui-action-button\n class=\"removeIcon\"\n (actionClick)=\"cancelChange()\"\n [descriptor]=\"removeButton\"\n ></ui-action-button>\n }\n </div>\n </div>\n </div>\n </div>\n}\n", styles: [":host{--border-color: #a6aabd60;--def-border-radius: .5rem;width:100%}.fileEditorComponent{border:2px dashed var(--border-color);border-radius:var(--def-border-radius)}.fileEditorComponent ::ng-deep smartfileuploader ::ng-deep .container{border:unset;border-radius:unset}.uploadedFileContainer{display:flex;flex-direction:column}.uploadedFile{display:flex;flex-direction:row;justify-content:flex-start;align-items:center;padding:1rem;gap:1rem}.fileEditorComponent:has(::ng-deep smartfileuploader) ::ng-deep .uploadedFile{border-top:1px solid #e0e0e0}.fileSize{display:flex;flex-direction:row;font-size:smaller}.fileData{flex:1;display:flex;flex-direction:column}.fileDataContainer{display:flex;flex-direction:column;justify-content:flex-end;width:fit-content}.fileActions{display:flex;flex-direction:row;justify-content:flex-end;gap:.5rem}.fileActions ::ng-deep button,.undoClearContainer ::ng-deep button{border:unset}.fileActions ::ng-deep button:hover,.undoClearContainer ::ng-deep button:hover{border:unset}.undoClearContainer{display:flex;justify-content:flex-end;padding:0 1rem .5rem}.removeIcon ::ng-deep mat-icon{color:var(--warn-color)}smart-icon ::ng-deep mat-icon{font-size:3rem;width:unset;height:unset}.uploadedFileIcon{width:3rem;display:flex;justify-content:center}.fileEditorComponent:has(::ng-deep .errorMessage){border-color:var(--warn-color)}.errorMessage{padding:1rem;font-size:1.5rem;color:var(--warn-color);display:flex;flex-direction:row;align-items:center;gap:1rem}:host ::ng-deep .disabledWidget ::ng-deep .changeButton *,:host ::ng-deep .disabledWidget ::ng-deep .fileData *{opacity:.7}:host ::ng-deep smartfileuploader ::ng-deep .uploadedFile{background:repeating-linear-gradient(45deg,#fafafa,#fafafa 12px,#f1f1f1 12px 24px)}\n"] }]
10848
+ ], imports: [NgClass, NgStyle, SmartIconComponent, UploadWidgetComponent, UiActionToolbarComponent, UiActionButtonComponent], template: "@if (widgetInstance && widgetInstance.fileUploaderProperties) {\n @if (widgetInstance.showLabel) {\n <h4\n class=\"labelContainer {{ widgetInstance.cssLabelClass ?? '' }}\"\n [ngClass]=\"getLabelNgClass()\"\n [ngStyle]=\"getLabelStyle()\"\n >\n {{ widgetInstance.label }}\n </h4>\n }\n\n <div class=\"fileEditorComponent\">\n @if (widgetInstance.value && !isTypeSupported(widgetInstance.value)) {\n <div class=\"errorMessage\">\n <smart-icon [icon]=\"errorIcon\" [color]=\"'var(--warn-color)'\"></smart-icon>\n <span> Unsupported widgetInstance.value type for this widget.</span>\n </div>\n } @else if (!widgetInstance.value || fileToChange) {\n <smart-upload-widget\n class=\"widgetContent {{ widgetInstance.cssClass ?? '' }}\"\n [ngClass]=\"getNgClass()\"\n [ngStyle]=\"getStyle()\"\n [uploadDescriptor]=\"widgetInstance.fileUploaderProperties.uploadDescriptor!\"\n [sources]=\"widgetInstance.fileUploaderProperties.sources\"\n [capture]=\"widgetInstance.fileUploaderProperties.capture\"\n [isMultiple]=\"false\"\n [isDisabled]=\"isDisabled\"\n (uploadFilesEvent)=\"upload($event)\"\n />\n @if (clearedFile && !isDisabled) {\n <div class=\"undoClearContainer\">\n <ui-action-button\n class=\"removeIcon undoClearButton\"\n (actionClick)=\"undoClear()\"\n [descriptor]=\"removeButton\"\n ></ui-action-button>\n </div>\n }\n }\n\n <div\n [ngClass]=\"{ disabledWidget: isDisabled }\"\n class=\"uploadedFileContainer\"\n [style.display]=\"\n widgetInstance.value && isTypeSupported(widgetInstance.value) ? 'block' : 'none'\n \"\n >\n @if (preview; as filePreview) {\n <div class=\"filePreview\">\n @switch (filePreview.kind) {\n @case ('image') {\n <img [src]=\"filePreview.url\" [alt]=\"widgetInstance.value?.fileName\" />\n }\n @case ('video') {\n <video\n controls\n [src]=\"filePreview.url\"\n (loadedmetadata)=\"onPreviewMetadata($event)\"\n ></video>\n }\n @case ('audio') {\n <audio controls [src]=\"filePreview.url\"></audio>\n }\n }\n </div>\n }\n <div class=\"uploadedFile\">\n @if (!preview) {\n <smart-icon class=\"uploadedFileIcon\" [icon]=\"fileIcon\"></smart-icon>\n }\n\n <div class=\"fileData\">\n <div class=\"fileDataContainer\">\n <span class=\"fileName\">{{ widgetInstance.value?.fileName }}</span>\n <span class=\"fileSize\">{{ formatSize($safeNavigationMigration(widgetInstance.value?.size)) }}</span>\n </div>\n </div>\n\n <div class=\"fileActions\">\n <smart-ui-action-toolbar [id]=\"widgetInstance.toolbarId\"></smart-ui-action-toolbar>\n\n @if (!fileToChange) {\n <ui-action-button\n class=\"changeButton\"\n (actionClick)=\"change(widgetInstance.value)\"\n [disabled]=\"isDisabled\"\n [descriptor]=\"changeButton\"\n >\n </ui-action-button>\n @if (!isDisabled && isClearEnabled) {\n <ui-action-button\n class=\"deleteButton\"\n (actionClick)=\"clear()\"\n [descriptor]=\"deleteButton\"\n ></ui-action-button>\n }\n } @else {\n <ui-action-button\n class=\"removeIcon\"\n (actionClick)=\"cancelChange()\"\n [descriptor]=\"removeButton\"\n ></ui-action-button>\n }\n </div>\n </div>\n </div>\n </div>\n}\n", styles: [":host{--border-color: #a6aabd60;--def-border-radius: .5rem;width:100%}.fileEditorComponent{border:2px dashed var(--border-color);border-radius:var(--def-border-radius)}.fileEditorComponent ::ng-deep smartfileuploader ::ng-deep .container{border:unset;border-radius:unset}.uploadedFileContainer{display:flex;flex-direction:column}.uploadedFile{display:flex;flex-direction:row;justify-content:flex-start;align-items:center;padding:1rem;gap:1rem}.fileEditorComponent:has(::ng-deep smartfileuploader) ::ng-deep .uploadedFile{border-top:1px solid #e0e0e0}.fileSize{display:flex;flex-direction:row;font-size:smaller}.fileData{flex:1;display:flex;flex-direction:column}.fileDataContainer{display:flex;flex-direction:column;justify-content:flex-end;width:fit-content}.fileActions{display:flex;flex-direction:row;justify-content:flex-end;gap:.5rem}.fileActions ::ng-deep button,.undoClearContainer ::ng-deep button{border:unset}.fileActions ::ng-deep button:hover,.undoClearContainer ::ng-deep button:hover{border:unset}.undoClearContainer{display:flex;justify-content:flex-end;padding:0 1rem .5rem}.removeIcon ::ng-deep mat-icon{color:var(--warn-color)}smart-icon ::ng-deep mat-icon{font-size:3rem;width:unset;height:unset}.uploadedFileIcon{width:3rem;display:flex;justify-content:center}.filePreview{padding:.75rem 1rem 0}.filePreview img,.filePreview video{display:block;width:100%;max-height:200px;object-fit:contain;background:var(--color-surface-alt);border-radius:4px}.filePreview audio{display:block;width:100%}.fileEditorComponent:has(::ng-deep .errorMessage){border-color:var(--warn-color)}.errorMessage{padding:1rem;font-size:1.5rem;color:var(--warn-color);display:flex;flex-direction:row;align-items:center;gap:1rem}:host ::ng-deep .disabledWidget ::ng-deep .changeButton *,:host ::ng-deep .disabledWidget ::ng-deep .fileData *{opacity:.7}:host ::ng-deep smartfileuploader ::ng-deep .uploadedFile{background:repeating-linear-gradient(45deg,#fafafa,#fafafa 12px,#f1f1f1 12px 24px)}\n"] }]
9954
10849
  }], propDecorators: { widgetInstance: [{
9955
10850
  type: Input
9956
10851
  }], uploadFilesEvent: [{ type: i0.Output, args: ["uploadFilesEvent"] }] } });
@@ -10859,6 +11754,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImpor
10859
11754
  class SmartMultiFileEditorComponent {
10860
11755
  constructor() {
10861
11756
  this.cdr = inject(ChangeDetectorRef);
11757
+ this.previewLoader = new FilePreviewLoader();
10862
11758
  this.uploadFilesEvent = output();
10863
11759
  this.fileEvent = output();
10864
11760
  // ---- ControlValueAccessor implementation ----
@@ -10881,8 +11777,28 @@ class SmartMultiFileEditorComponent {
10881
11777
  ? structuredClone(value)
10882
11778
  : JSON.parse(JSON.stringify(value));
10883
11779
  this.isDisabled = !!value.isDisabled;
11780
+ const texts = value.multiFileUploaderProperties?.uploadDescriptor?.texts;
11781
+ this.playTooltip = { tooltip: UploadWidgetUtils.resolveTexts(texts).play };
11782
+ this.requestPreviews();
10884
11783
  this.cdr.markForCheck();
10885
11784
  }
11785
+ /** With thumbnail FULL the image rows and the expanded player's row are previewed. */
11786
+ requestPreviews() {
11787
+ const files = this.isFullThumbnail && Array.isArray(this._widgetInstance.value)
11788
+ ? this._widgetInstance.value
11789
+ : [];
11790
+ this.previewLoader.setRequests(files
11791
+ .filter((file) => this.getPreviewKind(file) === 'image' || file.dataUri === this.expandedDataUri)
11792
+ .map((file) => ({
11793
+ key: createListFilePreviewKey(this._widgetInstance.key, file.dataUri),
11794
+ dataUri: file.dataUri,
11795
+ mimeType: file.mimeType,
11796
+ })));
11797
+ }
11798
+ togglePlayer(dataUri) {
11799
+ this.expandedDataUri = this.expandedDataUri === dataUri ? undefined : dataUri;
11800
+ this.requestPreviews();
11801
+ }
10886
11802
  upload(event) {
10887
11803
  if (!event.files || event.files.length == 0) {
10888
11804
  return;
@@ -10956,6 +11872,7 @@ class SmartMultiFileEditorComponent {
10956
11872
  writeValue(value) {
10957
11873
  if (value !== undefined && this._widgetInstance) {
10958
11874
  this._widgetInstance.value = value;
11875
+ this.requestPreviews();
10959
11876
  this.cdr.markForCheck();
10960
11877
  }
10961
11878
  }
@@ -10976,6 +11893,19 @@ class SmartMultiFileEditorComponent {
10976
11893
  get fileIcon() {
10977
11894
  return 'insert_drive_file';
10978
11895
  }
11896
+ get isFullThumbnail() {
11897
+ return (this._widgetInstance.multiFileUploaderProperties?.thumbnail === FileUploaderThumbnail.FULL);
11898
+ }
11899
+ /** The preview kind of a row, `undefined` without thumbnail FULL or for another file type. */
11900
+ getPreviewKind(file) {
11901
+ return this.isFullThumbnail ? getFilePreviewKind(file?.mimeType) : undefined;
11902
+ }
11903
+ getPreview(dataUri) {
11904
+ return this.previewLoader.getPreview(dataUri);
11905
+ }
11906
+ onPreviewMetadata(dataUri, event) {
11907
+ this.previewLoader.applyVideoMetadata(dataUri, event.target);
11908
+ }
10979
11909
  get errorIcon() {
10980
11910
  return 'error';
10981
11911
  }
@@ -10986,7 +11916,7 @@ class SmartMultiFileEditorComponent {
10986
11916
  useExisting: forwardRef(() => SmartMultiFileEditorComponent),
10987
11917
  multi: true,
10988
11918
  },
10989
- ], ngImport: i0, template: "@if (widgetInstance && widgetInstance.multiFileUploaderProperties) {\n @if (widgetInstance.showLabel) {\n <h4\n class=\"labelContainer {{ widgetInstance.cssLabelClass ?? '' }}\"\n [ngClass]=\"getLabelNgClass()\"\n [ngStyle]=\"getLabelStyle()\"\n >\n {{ widgetInstance.label }}\n </h4>\n }\n <div class=\"fileEditorComponent\">\n @if (widgetInstance.value && !isTypeSupported(widgetInstance.value)) {\n <div class=\"errorMessage\">\n <smart-icon [icon]=\"errorIcon\" [color]=\"'var(--warn-color)'\"></smart-icon>\n <span> Unsupported widgetInstance.value type for this widget.</span>\n </div>\n } @else {\n <smart-upload-widget\n class=\"widgetContent {{ widgetInstance.cssClass ?? '' }}\"\n [ngClass]=\"getNgClass()\"\n [ngStyle]=\"getStyle()\"\n [uploadDescriptor]=\"widgetInstance.multiFileUploaderProperties.uploadDescriptor!\"\n [isMultiple]=\"true\"\n [isDisabled]=\"isDisabled\"\n (uploadFilesEvent)=\"upload($event)\"\n />\n }\n\n <div\n [ngClass]=\"{ disabledWidget: isDisabled }\"\n class=\"uploadedFileContainer\"\n [style.display]=\"\n widgetInstance.value && isTypeSupported(widgetInstance.value) ? 'block' : 'none'\n \"\n >\n <div class=\"scrollable\">\n @for (file of widgetInstance.value; track file; let i = $index) {\n <div class=\"uploadedFile\">\n <smart-icon class=\"uploadedFileIcon\" [icon]=\"fileIcon\"></smart-icon>\n <div class=\"fileData\">\n <div class=\"fileDataContainer\">\n <span class=\"fileName\">{{ file?.fileName }}</span>\n <span class=\"fileSize\">{{ formatSize($safeNavigationMigration(file?.size)) }}</span>\n </div>\n </div>\n <div class=\"fileActions\">\n <!--\n One toolbar per file, each resolving the widget's actions for itself and\n carrying its own file. The hidden sibling whose list these used to read is\n gone with the collection machinery (ADR-0006).\n -->\n <smart-ui-action-toolbar\n [id]=\"widgetInstance.toolbarId\"\n [actionParams]=\"{ _dataUri: file.dataUri }\"\n ></smart-ui-action-toolbar>\n <smart-icon\n class=\"downloadIcon\"\n icon=\"download\"\n (click)=\"downloadFile(file.dataUri)\"\n ></smart-icon>\n @if (!file.readOnly && !isDisabled) {\n <smart-icon\n class=\"removeIcon\"\n [icon]=\"removeIcon\"\n (click)=\"file.readOnly === false && deleteFile(file.dataUri)\"\n ></smart-icon>\n }\n </div>\n </div>\n }\n </div>\n </div>\n </div>\n}\n", styles: [":host{--border-color: #a6aabd60;--def-border-radius: .5rem;width:100%}.fileEditorComponent{border:2px dashed var(--border-color);border-radius:var(--def-border-radius);overflow:hidden}.fileEditorComponent ::ng-deep smartfileuploader ::ng-deep .container{border:unset;border-radius:unset}.uploadedFileContainer{display:flex;flex-direction:column;border-top:1px solid #e0e0e0}.uploadedFile{display:flex;flex-direction:row;justify-content:flex-start;align-items:center;padding:1rem;gap:1rem}.scrollable{max-height:30vh;overflow-y:auto}.scrollable>div:not(:last-child){border-bottom:1px solid #e0e0e0}.fileSize{display:flex;flex-direction:row;font-size:smaller}.fileData{flex:1;display:flex;flex-direction:column}.fileDataContainer{display:flex;flex-direction:column;justify-content:flex-end;width:fit-content}.fileActions{display:flex;flex-direction:row;justify-content:flex-end;gap:.5rem}.fileActions ::ng-deep button{border:unset}.fileActions ::ng-deep button:hover{border:unset}.removeIcon ::ng-deep i,.removeIcon ::ng-deep mat-icon{color:var(--warn-color)}.removeIcon ::ng-deep mat-icon,.downloadIcon ::ng-deep mat-icon{font-size:2rem}.removeIcon:hover,.downloadIcon:hover{cursor:pointer}smart-icon{display:flex;align-items:center}smart-icon ::ng-deep mat-icon{font-size:3rem;width:unset;height:unset}.uploadedFileIcon{width:3rem;display:flex;justify-content:center}.fileEditorComponent:has(::ng-deep .errorMessage){border-color:var(--warn-color)}.errorMessage{padding:1rem;font-size:1.5rem;color:var(--warn-color);display:flex;flex-direction:row;align-items:center;gap:1rem}:host ::ng-deep .disabledWidget ::ng-deep .fileData *{opacity:.7}:host ::ng-deep smartfileuploader ::ng-deep .uploadedFile{background:repeating-linear-gradient(45deg,#fafafa,#fafafa 6px,#f1f1f1 6px 12px)}\n"], dependencies: [{ kind: "directive", type: NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "component", type: UiActionToolbarComponent, selector: "smart-ui-action-toolbar", inputs: ["uiActionModels", "uiActionDescriptorService", "id", "executor", "widgetId", "nodeId", "actionParams", "scrollOnWrap", "toolbarPropertes"], outputs: ["actionFired"] }, { kind: "component", type: SmartIconComponent, selector: "smart-icon", inputs: ["icon", "color", "imageResource"] }, { kind: "component", type: UploadWidgetComponent, selector: "smart-upload-widget", inputs: ["uploadDescriptor", "isMultiple", "isDisabled"], outputs: ["uploadFilesEvent"] }] }); }
11919
+ ], ngImport: i0, template: "@if (widgetInstance && widgetInstance.multiFileUploaderProperties) {\n @if (widgetInstance.showLabel) {\n <h4\n class=\"labelContainer {{ widgetInstance.cssLabelClass ?? '' }}\"\n [ngClass]=\"getLabelNgClass()\"\n [ngStyle]=\"getLabelStyle()\"\n >\n {{ widgetInstance.label }}\n </h4>\n }\n <div class=\"fileEditorComponent\">\n @if (widgetInstance.value && !isTypeSupported(widgetInstance.value)) {\n <div class=\"errorMessage\">\n <smart-icon [icon]=\"errorIcon\" [color]=\"'var(--warn-color)'\"></smart-icon>\n <span> Unsupported widgetInstance.value type for this widget.</span>\n </div>\n } @else {\n <smart-upload-widget\n class=\"widgetContent {{ widgetInstance.cssClass ?? '' }}\"\n [ngClass]=\"getNgClass()\"\n [ngStyle]=\"getStyle()\"\n [uploadDescriptor]=\"widgetInstance.multiFileUploaderProperties.uploadDescriptor!\"\n [sources]=\"widgetInstance.multiFileUploaderProperties.sources\"\n [capture]=\"widgetInstance.multiFileUploaderProperties.capture\"\n [isMultiple]=\"true\"\n [isDisabled]=\"isDisabled\"\n (uploadFilesEvent)=\"upload($event)\"\n />\n }\n\n <div\n [ngClass]=\"{ disabledWidget: isDisabled }\"\n class=\"uploadedFileContainer\"\n [style.display]=\"\n widgetInstance.value && isTypeSupported(widgetInstance.value) ? 'block' : 'none'\n \"\n >\n <div class=\"scrollable\">\n @for (file of widgetInstance.value; track file; let i = $index) {\n <div class=\"uploadedFile\">\n @let previewKind = getPreviewKind(file);\n @let thumbnail = previewKind === 'image' ? getPreview(file.dataUri) : undefined;\n @if (thumbnail) {\n <img class=\"fileThumbnail\" [src]=\"thumbnail.url\" [alt]=\"file.fileName\" />\n } @else if (previewKind === 'video' || previewKind === 'audio') {\n <button\n type=\"button\"\n class=\"playTile\"\n [class.audioTile]=\"previewKind === 'audio'\"\n [class.open]=\"expandedDataUri === file.dataUri\"\n [smartTooltip]=\"playTooltip\"\n (click)=\"togglePlayer(file.dataUri)\"\n >\n <smart-icon\n [icon]=\"previewKind === 'audio' ? 'graphic_eq' : 'play_arrow'\"\n ></smart-icon>\n </button>\n } @else {\n <smart-icon class=\"uploadedFileIcon\" [icon]=\"fileIcon\"></smart-icon>\n }\n <div class=\"fileData\">\n <div class=\"fileDataContainer\">\n <span class=\"fileName\">{{ file?.fileName }}</span>\n <span class=\"fileSize\">{{ formatSize($safeNavigationMigration(file?.size)) }}</span>\n </div>\n </div>\n <div class=\"fileActions\">\n <!--\n One toolbar per file, each resolving the widget's actions for itself and\n carrying its own file. The hidden sibling whose list these used to read is\n gone with the collection machinery (ADR-0006).\n -->\n <smart-ui-action-toolbar\n [id]=\"widgetInstance.toolbarId\"\n [actionParams]=\"{ _dataUri: file.dataUri }\"\n ></smart-ui-action-toolbar>\n <smart-icon\n class=\"downloadIcon\"\n icon=\"download\"\n (click)=\"downloadFile(file.dataUri)\"\n ></smart-icon>\n @if (!file.readOnly && !isDisabled) {\n <smart-icon\n class=\"removeIcon\"\n [icon]=\"removeIcon\"\n (click)=\"file.readOnly === false && deleteFile(file.dataUri)\"\n ></smart-icon>\n }\n </div>\n </div>\n @if (expandedDataUri === file.dataUri) {\n @if (getPreview(file.dataUri); as player) {\n <div class=\"filePlayer\">\n @if (player.kind === 'audio') {\n <audio controls [src]=\"player.url\"></audio>\n } @else {\n <video\n controls\n [src]=\"player.url\"\n (loadedmetadata)=\"onPreviewMetadata(file.dataUri, $event)\"\n ></video>\n }\n </div>\n }\n }\n }\n </div>\n </div>\n </div>\n}\n", styles: [":host{--border-color: #a6aabd60;--def-border-radius: .5rem;width:100%}.fileEditorComponent{border:2px dashed var(--border-color);border-radius:var(--def-border-radius);overflow:hidden}.fileEditorComponent ::ng-deep smartfileuploader ::ng-deep .container{border:unset;border-radius:unset}.uploadedFileContainer{display:flex;flex-direction:column;border-top:1px solid #e0e0e0}.uploadedFile{display:flex;flex-direction:row;justify-content:flex-start;align-items:center;padding:1rem;gap:1rem}.scrollable{max-height:30vh;overflow-y:auto}.scrollable>div:not(:last-child){border-bottom:1px solid #e0e0e0}.fileSize{display:flex;flex-direction:row;font-size:smaller}.fileData{flex:1;display:flex;flex-direction:column}.fileDataContainer{display:flex;flex-direction:column;justify-content:flex-end;width:fit-content}.fileActions{display:flex;flex-direction:row;justify-content:flex-end;gap:.5rem}.fileActions ::ng-deep button{border:unset}.fileActions ::ng-deep button:hover{border:unset}.removeIcon ::ng-deep i,.removeIcon ::ng-deep mat-icon{color:var(--warn-color)}.removeIcon ::ng-deep mat-icon,.downloadIcon ::ng-deep mat-icon{font-size:2rem}.removeIcon:hover,.downloadIcon:hover{cursor:pointer}smart-icon{display:flex;align-items:center}smart-icon ::ng-deep mat-icon{font-size:3rem;width:unset;height:unset}.uploadedFileIcon{width:3rem;display:flex;justify-content:center}.fileThumbnail,.playTile{width:3rem;height:3rem;flex:none;border-radius:4px}.fileThumbnail{object-fit:cover;background:var(--color-surface-alt)}.playTile{display:flex;align-items:center;justify-content:center;padding:0;border:none;background:#2f3550;color:#fff;cursor:pointer}.playTile.audioTile{background:var(--color-surface-alt);color:var(--primary-color)}.playTile.open{outline:2px solid var(--primary-color);outline-offset:1px}.playTile smart-icon,.playTile smart-icon ::ng-deep mat-icon{font-size:2rem;color:inherit}.scrollable>.uploadedFile:has(+.filePlayer){border-bottom:none}.filePlayer{padding:0 1rem .75rem 5rem}.filePlayer video{display:block;width:100%;max-height:160px;background:#000;border-radius:4px}.filePlayer audio{display:block;width:100%}.fileEditorComponent:has(::ng-deep .errorMessage){border-color:var(--warn-color)}.errorMessage{padding:1rem;font-size:1.5rem;color:var(--warn-color);display:flex;flex-direction:row;align-items:center;gap:1rem}:host ::ng-deep .disabledWidget ::ng-deep .fileData *{opacity:.7}:host ::ng-deep smartfileuploader ::ng-deep .uploadedFile{background:repeating-linear-gradient(45deg,#fafafa,#fafafa 6px,#f1f1f1 6px 12px)}\n"], dependencies: [{ kind: "directive", type: NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "directive", type: SmartTooltipDirective, selector: "[smartTooltip]", inputs: ["smartTooltip", "smartTooltipClass"] }, { kind: "component", type: UiActionToolbarComponent, selector: "smart-ui-action-toolbar", inputs: ["uiActionModels", "uiActionDescriptorService", "id", "executor", "widgetId", "nodeId", "actionParams", "scrollOnWrap", "toolbarPropertes"], outputs: ["actionFired"] }, { kind: "component", type: SmartIconComponent, selector: "smart-icon", inputs: ["icon", "color", "imageResource"] }, { kind: "component", type: UploadWidgetComponent, selector: "smart-upload-widget", inputs: ["uploadDescriptor", "isMultiple", "isDisabled", "sources", "capture"], outputs: ["uploadFilesEvent"] }] }); }
10990
11920
  }
10991
11921
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: SmartMultiFileEditorComponent, decorators: [{
10992
11922
  type: Component,
@@ -10996,7 +11926,14 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImpor
10996
11926
  useExisting: forwardRef(() => SmartMultiFileEditorComponent),
10997
11927
  multi: true,
10998
11928
  },
10999
- ], imports: [NgClass, NgStyle, UiActionToolbarComponent, SmartIconComponent, UploadWidgetComponent], template: "@if (widgetInstance && widgetInstance.multiFileUploaderProperties) {\n @if (widgetInstance.showLabel) {\n <h4\n class=\"labelContainer {{ widgetInstance.cssLabelClass ?? '' }}\"\n [ngClass]=\"getLabelNgClass()\"\n [ngStyle]=\"getLabelStyle()\"\n >\n {{ widgetInstance.label }}\n </h4>\n }\n <div class=\"fileEditorComponent\">\n @if (widgetInstance.value && !isTypeSupported(widgetInstance.value)) {\n <div class=\"errorMessage\">\n <smart-icon [icon]=\"errorIcon\" [color]=\"'var(--warn-color)'\"></smart-icon>\n <span> Unsupported widgetInstance.value type for this widget.</span>\n </div>\n } @else {\n <smart-upload-widget\n class=\"widgetContent {{ widgetInstance.cssClass ?? '' }}\"\n [ngClass]=\"getNgClass()\"\n [ngStyle]=\"getStyle()\"\n [uploadDescriptor]=\"widgetInstance.multiFileUploaderProperties.uploadDescriptor!\"\n [isMultiple]=\"true\"\n [isDisabled]=\"isDisabled\"\n (uploadFilesEvent)=\"upload($event)\"\n />\n }\n\n <div\n [ngClass]=\"{ disabledWidget: isDisabled }\"\n class=\"uploadedFileContainer\"\n [style.display]=\"\n widgetInstance.value && isTypeSupported(widgetInstance.value) ? 'block' : 'none'\n \"\n >\n <div class=\"scrollable\">\n @for (file of widgetInstance.value; track file; let i = $index) {\n <div class=\"uploadedFile\">\n <smart-icon class=\"uploadedFileIcon\" [icon]=\"fileIcon\"></smart-icon>\n <div class=\"fileData\">\n <div class=\"fileDataContainer\">\n <span class=\"fileName\">{{ file?.fileName }}</span>\n <span class=\"fileSize\">{{ formatSize($safeNavigationMigration(file?.size)) }}</span>\n </div>\n </div>\n <div class=\"fileActions\">\n <!--\n One toolbar per file, each resolving the widget's actions for itself and\n carrying its own file. The hidden sibling whose list these used to read is\n gone with the collection machinery (ADR-0006).\n -->\n <smart-ui-action-toolbar\n [id]=\"widgetInstance.toolbarId\"\n [actionParams]=\"{ _dataUri: file.dataUri }\"\n ></smart-ui-action-toolbar>\n <smart-icon\n class=\"downloadIcon\"\n icon=\"download\"\n (click)=\"downloadFile(file.dataUri)\"\n ></smart-icon>\n @if (!file.readOnly && !isDisabled) {\n <smart-icon\n class=\"removeIcon\"\n [icon]=\"removeIcon\"\n (click)=\"file.readOnly === false && deleteFile(file.dataUri)\"\n ></smart-icon>\n }\n </div>\n </div>\n }\n </div>\n </div>\n </div>\n}\n", styles: [":host{--border-color: #a6aabd60;--def-border-radius: .5rem;width:100%}.fileEditorComponent{border:2px dashed var(--border-color);border-radius:var(--def-border-radius);overflow:hidden}.fileEditorComponent ::ng-deep smartfileuploader ::ng-deep .container{border:unset;border-radius:unset}.uploadedFileContainer{display:flex;flex-direction:column;border-top:1px solid #e0e0e0}.uploadedFile{display:flex;flex-direction:row;justify-content:flex-start;align-items:center;padding:1rem;gap:1rem}.scrollable{max-height:30vh;overflow-y:auto}.scrollable>div:not(:last-child){border-bottom:1px solid #e0e0e0}.fileSize{display:flex;flex-direction:row;font-size:smaller}.fileData{flex:1;display:flex;flex-direction:column}.fileDataContainer{display:flex;flex-direction:column;justify-content:flex-end;width:fit-content}.fileActions{display:flex;flex-direction:row;justify-content:flex-end;gap:.5rem}.fileActions ::ng-deep button{border:unset}.fileActions ::ng-deep button:hover{border:unset}.removeIcon ::ng-deep i,.removeIcon ::ng-deep mat-icon{color:var(--warn-color)}.removeIcon ::ng-deep mat-icon,.downloadIcon ::ng-deep mat-icon{font-size:2rem}.removeIcon:hover,.downloadIcon:hover{cursor:pointer}smart-icon{display:flex;align-items:center}smart-icon ::ng-deep mat-icon{font-size:3rem;width:unset;height:unset}.uploadedFileIcon{width:3rem;display:flex;justify-content:center}.fileEditorComponent:has(::ng-deep .errorMessage){border-color:var(--warn-color)}.errorMessage{padding:1rem;font-size:1.5rem;color:var(--warn-color);display:flex;flex-direction:row;align-items:center;gap:1rem}:host ::ng-deep .disabledWidget ::ng-deep .fileData *{opacity:.7}:host ::ng-deep smartfileuploader ::ng-deep .uploadedFile{background:repeating-linear-gradient(45deg,#fafafa,#fafafa 6px,#f1f1f1 6px 12px)}\n"] }]
11929
+ ], imports: [
11930
+ NgClass,
11931
+ NgStyle,
11932
+ SmartTooltipDirective,
11933
+ UiActionToolbarComponent,
11934
+ SmartIconComponent,
11935
+ UploadWidgetComponent,
11936
+ ], template: "@if (widgetInstance && widgetInstance.multiFileUploaderProperties) {\n @if (widgetInstance.showLabel) {\n <h4\n class=\"labelContainer {{ widgetInstance.cssLabelClass ?? '' }}\"\n [ngClass]=\"getLabelNgClass()\"\n [ngStyle]=\"getLabelStyle()\"\n >\n {{ widgetInstance.label }}\n </h4>\n }\n <div class=\"fileEditorComponent\">\n @if (widgetInstance.value && !isTypeSupported(widgetInstance.value)) {\n <div class=\"errorMessage\">\n <smart-icon [icon]=\"errorIcon\" [color]=\"'var(--warn-color)'\"></smart-icon>\n <span> Unsupported widgetInstance.value type for this widget.</span>\n </div>\n } @else {\n <smart-upload-widget\n class=\"widgetContent {{ widgetInstance.cssClass ?? '' }}\"\n [ngClass]=\"getNgClass()\"\n [ngStyle]=\"getStyle()\"\n [uploadDescriptor]=\"widgetInstance.multiFileUploaderProperties.uploadDescriptor!\"\n [sources]=\"widgetInstance.multiFileUploaderProperties.sources\"\n [capture]=\"widgetInstance.multiFileUploaderProperties.capture\"\n [isMultiple]=\"true\"\n [isDisabled]=\"isDisabled\"\n (uploadFilesEvent)=\"upload($event)\"\n />\n }\n\n <div\n [ngClass]=\"{ disabledWidget: isDisabled }\"\n class=\"uploadedFileContainer\"\n [style.display]=\"\n widgetInstance.value && isTypeSupported(widgetInstance.value) ? 'block' : 'none'\n \"\n >\n <div class=\"scrollable\">\n @for (file of widgetInstance.value; track file; let i = $index) {\n <div class=\"uploadedFile\">\n @let previewKind = getPreviewKind(file);\n @let thumbnail = previewKind === 'image' ? getPreview(file.dataUri) : undefined;\n @if (thumbnail) {\n <img class=\"fileThumbnail\" [src]=\"thumbnail.url\" [alt]=\"file.fileName\" />\n } @else if (previewKind === 'video' || previewKind === 'audio') {\n <button\n type=\"button\"\n class=\"playTile\"\n [class.audioTile]=\"previewKind === 'audio'\"\n [class.open]=\"expandedDataUri === file.dataUri\"\n [smartTooltip]=\"playTooltip\"\n (click)=\"togglePlayer(file.dataUri)\"\n >\n <smart-icon\n [icon]=\"previewKind === 'audio' ? 'graphic_eq' : 'play_arrow'\"\n ></smart-icon>\n </button>\n } @else {\n <smart-icon class=\"uploadedFileIcon\" [icon]=\"fileIcon\"></smart-icon>\n }\n <div class=\"fileData\">\n <div class=\"fileDataContainer\">\n <span class=\"fileName\">{{ file?.fileName }}</span>\n <span class=\"fileSize\">{{ formatSize($safeNavigationMigration(file?.size)) }}</span>\n </div>\n </div>\n <div class=\"fileActions\">\n <!--\n One toolbar per file, each resolving the widget's actions for itself and\n carrying its own file. The hidden sibling whose list these used to read is\n gone with the collection machinery (ADR-0006).\n -->\n <smart-ui-action-toolbar\n [id]=\"widgetInstance.toolbarId\"\n [actionParams]=\"{ _dataUri: file.dataUri }\"\n ></smart-ui-action-toolbar>\n <smart-icon\n class=\"downloadIcon\"\n icon=\"download\"\n (click)=\"downloadFile(file.dataUri)\"\n ></smart-icon>\n @if (!file.readOnly && !isDisabled) {\n <smart-icon\n class=\"removeIcon\"\n [icon]=\"removeIcon\"\n (click)=\"file.readOnly === false && deleteFile(file.dataUri)\"\n ></smart-icon>\n }\n </div>\n </div>\n @if (expandedDataUri === file.dataUri) {\n @if (getPreview(file.dataUri); as player) {\n <div class=\"filePlayer\">\n @if (player.kind === 'audio') {\n <audio controls [src]=\"player.url\"></audio>\n } @else {\n <video\n controls\n [src]=\"player.url\"\n (loadedmetadata)=\"onPreviewMetadata(file.dataUri, $event)\"\n ></video>\n }\n </div>\n }\n }\n }\n </div>\n </div>\n </div>\n}\n", styles: [":host{--border-color: #a6aabd60;--def-border-radius: .5rem;width:100%}.fileEditorComponent{border:2px dashed var(--border-color);border-radius:var(--def-border-radius);overflow:hidden}.fileEditorComponent ::ng-deep smartfileuploader ::ng-deep .container{border:unset;border-radius:unset}.uploadedFileContainer{display:flex;flex-direction:column;border-top:1px solid #e0e0e0}.uploadedFile{display:flex;flex-direction:row;justify-content:flex-start;align-items:center;padding:1rem;gap:1rem}.scrollable{max-height:30vh;overflow-y:auto}.scrollable>div:not(:last-child){border-bottom:1px solid #e0e0e0}.fileSize{display:flex;flex-direction:row;font-size:smaller}.fileData{flex:1;display:flex;flex-direction:column}.fileDataContainer{display:flex;flex-direction:column;justify-content:flex-end;width:fit-content}.fileActions{display:flex;flex-direction:row;justify-content:flex-end;gap:.5rem}.fileActions ::ng-deep button{border:unset}.fileActions ::ng-deep button:hover{border:unset}.removeIcon ::ng-deep i,.removeIcon ::ng-deep mat-icon{color:var(--warn-color)}.removeIcon ::ng-deep mat-icon,.downloadIcon ::ng-deep mat-icon{font-size:2rem}.removeIcon:hover,.downloadIcon:hover{cursor:pointer}smart-icon{display:flex;align-items:center}smart-icon ::ng-deep mat-icon{font-size:3rem;width:unset;height:unset}.uploadedFileIcon{width:3rem;display:flex;justify-content:center}.fileThumbnail,.playTile{width:3rem;height:3rem;flex:none;border-radius:4px}.fileThumbnail{object-fit:cover;background:var(--color-surface-alt)}.playTile{display:flex;align-items:center;justify-content:center;padding:0;border:none;background:#2f3550;color:#fff;cursor:pointer}.playTile.audioTile{background:var(--color-surface-alt);color:var(--primary-color)}.playTile.open{outline:2px solid var(--primary-color);outline-offset:1px}.playTile smart-icon,.playTile smart-icon ::ng-deep mat-icon{font-size:2rem;color:inherit}.scrollable>.uploadedFile:has(+.filePlayer){border-bottom:none}.filePlayer{padding:0 1rem .75rem 5rem}.filePlayer video{display:block;width:100%;max-height:160px;background:#000;border-radius:4px}.filePlayer audio{display:block;width:100%}.fileEditorComponent:has(::ng-deep .errorMessage){border-color:var(--warn-color)}.errorMessage{padding:1rem;font-size:1.5rem;color:var(--warn-color);display:flex;flex-direction:row;align-items:center;gap:1rem}:host ::ng-deep .disabledWidget ::ng-deep .fileData *{opacity:.7}:host ::ng-deep smartfileuploader ::ng-deep .uploadedFile{background:repeating-linear-gradient(45deg,#fafafa,#fafafa 6px,#f1f1f1 6px 12px)}\n"] }]
11000
11937
  }], propDecorators: { widgetInstance: [{
11001
11938
  type: Input
11002
11939
  }], uploadFilesEvent: [{ type: i0.Output, args: ["uploadFilesEvent"] }], fileEvent: [{ type: i0.Output, args: ["fileEvent"] }] } });
@@ -11226,7 +12163,7 @@ class SmartVoiceRecorderComponent {
11226
12163
  useExisting: forwardRef(() => SmartVoiceRecorderComponent),
11227
12164
  multi: true,
11228
12165
  },
11229
- ], ngImport: i0, template: "<voice-record-widget [openDirection]=\"openDirection\" (recordingSaved)=\"uploadRecording($event)\">\n</voice-record-widget>\n", styles: [":host{display:block;width:100%;min-width:0}\n"], dependencies: [{ kind: "component", type: VoiceRecordWidgetComponent, selector: "voice-record-widget", inputs: ["openDirection"], outputs: ["recordingSaved"] }] }); }
12166
+ ], ngImport: i0, template: "<voice-record-widget [openDirection]=\"openDirection\" (recordingSaved)=\"uploadRecording($event)\">\n</voice-record-widget>\n", styles: [":host{display:block;width:100%;min-width:0}\n"], dependencies: [{ kind: "component", type: VoiceRecordWidgetComponent, selector: "voice-record-widget", inputs: ["openDirection", "texts", "showMicrophoneError", "deviceStart", "handover", "showSaveTitle", "actionsInHost"], outputs: ["recordingSaved"] }] }); }
11230
12167
  }
11231
12168
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: SmartVoiceRecorderComponent, decorators: [{
11232
12169
  type: Component,
@@ -15947,7 +16884,8 @@ class Table {
15947
16884
  renderRows() {
15948
16885
  this.createCellToActionMap();
15949
16886
  this.rowMenuActionModelArrayCache.clear();
15950
- this.myTableChild.renderRows();
16887
+ // The mobile template renders from its own loop and has no material table.
16888
+ this.myTableChild?.renderRows();
15951
16889
  }
15952
16890
  createCellToActionMap() {
15953
16891
  this.cellToActionMap = SmartGridToolbarActionsUtil.calculateCellToActionMap(this.smartTable.tableRows);
@@ -16781,11 +17719,11 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImpor
16781
17719
 
16782
17720
  class MobileTableComponent extends Table {
16783
17721
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: MobileTableComponent, deps: null, target: i0.ɵɵFactoryTarget.Component }); }
16784
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.8", type: MobileTableComponent, isStandalone: true, selector: "lib-mobile-table", usesInheritance: true, ngImport: i0, template: "<div class=\"mobileTable\">\n @for (row of smartTable.tableRows; track trackRow($index, row)) {\n <div\n class=\"mobileTableRow\"\n [ngClass]=\"getRowClasses(row)\"\n [ngStyle]=\"getRowStyles(row)\"\n [attr.data-testid]=\"row?.id ?? null\"\n >\n @if (showRowStateColumn) {\n <div class=\"mobileTableCell sm-grid-row-state-cell\" [attr.data-testid]=\"rowStateColumn\">\n @if (rowStateIcon(row); as icon) {\n <smart-icon\n class=\"sm-grid-row-state-icon\"\n [ngClass]=\"rowStateIconClass(row) ?? ''\"\n [icon]=\"icon\"\n color=\"\"\n role=\"img\"\n [attr.aria-label]=\"rowStateLabel(row)\"\n [matTooltip]=\"rowStateLabel(row) ?? ''\"\n ></smart-icon>\n }\n </div>\n }\n @for (header of smartTable.customSmartTableHeaders; track header; let i = $index) {\n <div\n class=\"mobileTableCell\"\n (click)=\"handleOnRowClick($event, row)\"\n (dblclick)=\"handleOnRowDoubleClick($event, row)\"\n >\n <div class=\"mobileTableCellHeader\" [attr.data-testid]=\"header.propertyName\">\n {{ header.label }}\n </div>\n @if (\n smartTable.customSmartTableHeaders &&\n smartTable.customSmartTableHeaders[i].propertyName === 'select'\n ) {\n <mat-checkbox\n (click)=\"$event.stopPropagation()\"\n (change)=\"\n $event\n ? setSelection(\n smartTable.selectionProperty\n ? smartTable.getValueDeeply(row, smartTable.selectionProperty)\n : row\n )\n : null\n \"\n [disabled]=\"isDisabled(row)\"\n [checked]=\"\n smartTable.selection!.isSelected(\n smartTable.selectionProperty\n ? smartTable.getValueDeeply(row, smartTable.selectionProperty)\n : row\n )\n \"\n [aria-label]=\"\n checkboxLabel(\n smartTable.selectionProperty\n ? smartTable.getValueDeeply(row, smartTable.selectionProperty)\n : row\n )\n \"\n >\n </mat-checkbox>\n }\n @if (\n smartTable.customSmartTableHeaders && smartTable.customSmartTableHeaders[i].properties &&\n !columnTemplate(i)\n ) {\n <div\n >\n @if (smartTable.customSmartTableHeaders[i].properties?.type === type().DATETIME) {\n <div class=\"smart-table-cell-text\">\n {{\n getValue(row, header.propertyName)\n | smartDateTime: $safeNavigationMigration(smartTable.customSmartTableHeaders[i].properties?.dateFormat)\n }}\n </div>\n }\n @if (smartTable.customSmartTableHeaders[i].properties?.type === type().DATE) {\n <div class=\"smart-table-cell-text\">\n {{\n getValue(row, header.propertyName)\n | smartDate: $safeNavigationMigration(smartTable.customSmartTableHeaders[i].properties?.dateFormat)\n }}\n </div>\n }\n @if (smartTable.customSmartTableHeaders[i].properties?.type === type().TIME) {\n <div class=\"smart-table-cell-text\">\n {{\n getValue(row, header.propertyName)\n | smartTime: $safeNavigationMigration(smartTable.customSmartTableHeaders[i].properties?.dateFormat)\n }}\n </div>\n }\n @if (smartTable.customSmartTableHeaders[i].properties?.type === type().CHECKBOX) {\n <div>\n <mat-checkbox\n [disabled]=\"true\"\n [checked]=\"getValue(row, header.propertyName)\"\n ></mat-checkbox>\n </div>\n }\n @if (\n smartTable.customSmartTableHeaders[i].properties?.type === type().ICON &&\n smartTable.customSmartTableHeaders[i].properties?.icons?.length\n ) {\n <div\n >\n <smart-icon\n [smartTooltip]=\"getToolTip(row, i)\"\n [icon]=\"getIcon(getValue(row, header.propertyName), i)!\"\n [color]=\"getColor(getValue(row, header.propertyName), i)\"\n >\n </smart-icon>\n </div>\n }\n </div>\n }\n @if (isImageResource(row, header.propertyName)) {\n <div>\n <smart-icon\n [icon]=\"getImageResourceIcon(row, header.propertyName)\"\n [color]=\"getImageResourceColor(row, header.propertyName)\"\n >\n </smart-icon>\n </div>\n }\n @if (smartTable.customSmartTableHeaders && smartTable.customSmartTableHeaders[i].buttons) {\n <div\n class=\"smart-table-buttons-col\"\n >\n @for (button of smartTable.customSmartTableHeaders[i].buttons; track button) {\n <div>\n @if (showButton(button, row)) {\n <div>\n @switch (button.type) {\n @case (smartTableButtonType.ICON) {\n <button\n (click)=\"customButtonClicked($event, button, row)\"\n mat-icon-button\n color=\"{{ button.color }}\"\n >\n <smart-icon title=\"{{ button.label }}\" [icon]=\"button.icon!\"></smart-icon>\n </button>\n }\n @case (smartTableButtonType.NORMAL) {\n <button\n (click)=\"customButtonClicked($event, button, row)\"\n mat-button\n color=\"{{ button.color }}\"\n >\n @if (button.icon) {\n <smart-icon [icon]=\"button.icon\"></smart-icon>\n }\n {{ button.label }}\n </button>\n }\n @case (smartTableButtonType.RAISED) {\n <button\n (click)=\"customButtonClicked($event, button, row)\"\n mat-raised-button\n color=\"{{ button.color }}\"\n >\n @if (button.icon) {\n <smart-icon [icon]=\"button.icon\"></smart-icon>\n }\n {{ button.label }}\n </button>\n }\n <!------ MENU ------>\n @case (smartTableButtonType.MENU) {\n <div class=\"menu-button\">\n <!------ DEFAULT_ACTION_COLUMN ID TOOLBAR ------>\n <smart-ui-action-toolbar\n [uiActionModels]=\"getRowColumnAction(element, defaultActionToolbarId)\"\n [widgetId]=\"smartTable.getGridId()\"\n [nodeId]=\"element.id\"\n [actionParams]=\"{ model: element }\"\n (actionFired)=\"onRowActionFired(element, $event)\"\n ></smart-ui-action-toolbar>\n <!------ GENERIC HAMBURGER ------>\n @if (shouldShowMenuButton(element)) {\n <smart-ui-action-toolbar\n [uiActionModels]=\"getMenuActions(element)\"\n [widgetId]=\"smartTable.getGridId()\"\n [nodeId]=\"element.id\"\n [actionParams]=\"{ model: element }\"\n (actionFired)=\"onRowActionFired(element, $event)\"\n ></smart-ui-action-toolbar>\n }\n <!------ MENU ------>\n </div>\n }\n }\n </div>\n }\n </div>\n }\n </div>\n }\n @if (smartTable.customSmartTableHeaders && smartTable.customSmartTableHeaders[i].icon) {\n <div>\n @if (smartTable.customSmartTableHeaders[i].icon?.icon) {\n <smart-icon\n [ngClass]=\"smartTable.customSmartTableHeaders[i].icon?.cssClass ?? ''\"\n [color]=\"$safeNavigationMigration(smartTable.customSmartTableHeaders[i].icon?.color)\"\n [icon]=\"smartTable.customSmartTableHeaders[i].icon!.icon\"\n >\n </smart-icon>\n }\n </div>\n }\n @if (\n smartTable.customSmartTableHeaders &&\n smartTable.customSmartTableHeaders[i].translator !== undefined\n ) {\n <div\n class=\"smart-table-cell-text\"\n >\n {{ smartTable.customSmartTableHeaders[i].translator!(getValue(row, header.propertyName)) }}\n </div>\n }\n @if (\n !smartTable.customSmartTableHeaders ||\n (smartTable.customSmartTableHeaders &&\n (!smartTable.customSmartTableHeaders[i].properties || columnTemplate(i)) &&\n !smartTable.customSmartTableHeaders[i].icon &&\n !smartTable.customSmartTableHeaders[i].buttons &&\n !smartTable.customSmartTableHeaders[i].translator &&\n !(smartTable.customSmartTableHeaders[i].propertyName === 'select') &&\n !(smartTable.customSmartTableHeaders[i].propertyName === 'expand'))\n ) {\n <div\n >\n @if (header.propertyName === 'icon') {\n <smart-icon\n [icon]=\"getValue(row, header.propertyName)!\"\n >\n </smart-icon>\n }\n @if (header.propertyName === 'img') {\n <img\n [src]=\"getValue(row, header.propertyName)\"\n alt=\"\"\n class=\"smarttableImg\"\n />\n }\n @if (\n header.propertyName !== 'icon' &&\n header.propertyName !== 'img' &&\n header.propertyName !== 'option' &&\n header.propertyName !== 'button'\n ) {\n @if (columnTemplate(i); as template) {\n <smart-html-cell\n [row]=\"row\"\n [template]=\"template\"\n [gridIdentifier]=\"smartTable.getGridId()\"\n ></smart-html-cell>\n } @else if (isTextContent(i)) {\n <div class=\"smart-table-cell-text\">{{ getValue(row, header.propertyName) }}</div>\n } @else {\n <div\n class=\"smart-table-cell-text\"\n [innerHtml]=\"getValue(row, header.propertyName)\"\n ></div>\n }\n }\n </div>\n }\n </div>\n }\n </div>\n }\n</div>\n", styles: [".mobileTable{display:flex;flex-direction:column}.mobileTableRow{display:flex;flex-direction:column;border-bottom:1px solid var(--color-border, #e4eaf2);padding:.5rem;gap:.5rem}.mobileTableRow.sm-grid-row-new{color:var(--success-color, #16a34a);font-style:italic}.mobileTableRow.sm-grid-row-modified{color:var(--accent-color, #20b6a8);font-weight:600}.mobileTableRow.sm-grid-row-deleted{color:var(--color-text-muted, #68778e)}.mobileTableRow.sm-grid-row-deleted .smart-table-cell-text{text-decoration:line-through}.mobileTableRow.sm-grid-row-moved{font-style:italic}.mobileTableRow.sm-grid-row-contains-change{color:var(--accent-color, #20b6a8)}.sm-grid-row-state-icon ::ng-deep .mat-icon{font-size:18px;width:18px;height:18px;line-height:18px;color:var(--accent-color, #20b6a8)}.sm-grid-row-state-icon-new ::ng-deep .mat-icon{color:var(--success-color, #16a34a)}.sm-grid-row-state-icon-deleted ::ng-deep .mat-icon{color:var(--color-text-muted, #68778e)}.sm-grid-row-state-icon-contains-change ::ng-deep .mat-icon{font-size:10px;width:18px;text-align:center;color:var(--color-text-muted, #68778e)}.mobileTableRow.selected{color:inherit}.mobileTableRow.sm-grid-row-open{--sm-grid-open-row-tint: var( --grid-open-row-background, color-mix(in srgb, var(--accent-color, #20b6a8) 10%, transparent) );background-image:linear-gradient(var(--sm-grid-open-row-tint),var(--sm-grid-open-row-tint));box-shadow:inset 3px 0 0 var(--grid-open-row-bar, var(--accent-color, #20b6a8))}.mobileTableCell{display:flex;flex-direction:column;gap:.25rem}.mobileTableCellHeader{font-weight:700;color:var(--primary-color, #384372)}.selected{background-color:var(--primary-light-color, var(--color-surface-alt, #f4f6fb))}.mat-mdc-menu-item[disabled]{cursor:default!important}\n"], dependencies: [{ kind: "directive", type: NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "component", type: MatCheckbox, selector: "mat-checkbox", inputs: ["aria-label", "aria-labelledby", "aria-describedby", "aria-expanded", "aria-controls", "aria-owns", "id", "required", "labelPosition", "name", "value", "disableRipple", "tabIndex", "color", "disabledInteractive", "checked", "disabled", "indeterminate"], outputs: ["change", "indeterminateChange"], exportAs: ["matCheckbox"] }, { kind: "component", type: SmartIconComponent, selector: "smart-icon", inputs: ["icon", "color", "imageResource"] }, { kind: "directive", type: SmartTooltipDirective, selector: "[smartTooltip]", inputs: ["smartTooltip", "smartTooltipClass"] }, { kind: "directive", type: MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }, { kind: "component", type: MatIconButton, selector: "button[mat-icon-button], a[mat-icon-button], button[matIconButton], a[matIconButton]", exportAs: ["matButton", "matAnchor"] }, { kind: "component", type: MatButton, selector: " button[matButton], a[matButton], button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button], a[mat-button], a[mat-raised-button], a[mat-flat-button], a[mat-stroked-button] ", inputs: ["matButton"], exportAs: ["matButton", "matAnchor"] }, { kind: "component", type: UiActionToolbarComponent, selector: "smart-ui-action-toolbar", inputs: ["uiActionModels", "uiActionDescriptorService", "id", "executor", "widgetId", "nodeId", "actionParams", "scrollOnWrap", "toolbarPropertes"], outputs: ["actionFired"] }, { kind: "component", type: SmartHtmlCellComponent, selector: "smart-html-cell", inputs: ["row", "template", "gridIdentifier"] }, { kind: "pipe", type: SmartDateTimePipe, name: "smartDateTime" }, { kind: "pipe", type: SmartDatePipe, name: "smartDate" }, { kind: "pipe", type: SmartTimePipe, name: "smartTime" }] }); }
17722
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.8", type: MobileTableComponent, isStandalone: true, selector: "lib-mobile-table", usesInheritance: true, ngImport: i0, template: "<div class=\"mobileTable\">\n @for (row of smartTable.tableRows; track trackRow($index, row)) {\n <div\n class=\"mobileTableRow\"\n [ngClass]=\"getRowClasses(row)\"\n [ngStyle]=\"getRowStyles(row)\"\n [attr.data-testid]=\"row?.id ?? null\"\n >\n @if (showRowStateColumn) {\n <div class=\"mobileTableCell sm-grid-row-state-cell\" [attr.data-testid]=\"rowStateColumn\">\n @if (rowStateIcon(row); as icon) {\n <smart-icon\n class=\"sm-grid-row-state-icon\"\n [ngClass]=\"rowStateIconClass(row) ?? ''\"\n [icon]=\"icon\"\n color=\"\"\n role=\"img\"\n [attr.aria-label]=\"rowStateLabel(row)\"\n [matTooltip]=\"rowStateLabel(row) ?? ''\"\n ></smart-icon>\n }\n </div>\n }\n @for (header of smartTable.customSmartTableHeaders; track header; let i = $index) {\n <div\n class=\"mobileTableCell\"\n (click)=\"handleOnRowClick($event, row)\"\n (dblclick)=\"handleOnRowDoubleClick($event, row)\"\n >\n <div class=\"mobileTableCellHeader\" [attr.data-testid]=\"header.propertyName\">\n {{ header.label }}\n </div>\n @if (\n smartTable.customSmartTableHeaders &&\n smartTable.customSmartTableHeaders[i].propertyName === 'select'\n ) {\n <mat-checkbox\n (click)=\"$event.stopPropagation()\"\n (change)=\"\n $event\n ? setSelection(\n smartTable.selectionProperty\n ? smartTable.getValueDeeply(row, smartTable.selectionProperty)\n : row\n )\n : null\n \"\n [disabled]=\"isDisabled(row)\"\n [checked]=\"\n smartTable.selection!.isSelected(\n smartTable.selectionProperty\n ? smartTable.getValueDeeply(row, smartTable.selectionProperty)\n : row\n )\n \"\n [aria-label]=\"\n checkboxLabel(\n smartTable.selectionProperty\n ? smartTable.getValueDeeply(row, smartTable.selectionProperty)\n : row\n )\n \"\n >\n </mat-checkbox>\n }\n @if (\n smartTable.customSmartTableHeaders && smartTable.customSmartTableHeaders[i].properties &&\n !columnTemplate(i)\n ) {\n <div\n >\n @if (smartTable.customSmartTableHeaders[i].properties?.type === type().DATETIME) {\n <div class=\"smart-table-cell-text\">\n {{\n getValue(row, header.propertyName)\n | smartDateTime: $safeNavigationMigration(smartTable.customSmartTableHeaders[i].properties?.dateFormat)\n }}\n </div>\n }\n @if (smartTable.customSmartTableHeaders[i].properties?.type === type().DATE) {\n <div class=\"smart-table-cell-text\">\n {{\n getValue(row, header.propertyName)\n | smartDate: $safeNavigationMigration(smartTable.customSmartTableHeaders[i].properties?.dateFormat)\n }}\n </div>\n }\n @if (smartTable.customSmartTableHeaders[i].properties?.type === type().TIME) {\n <div class=\"smart-table-cell-text\">\n {{\n getValue(row, header.propertyName)\n | smartTime: $safeNavigationMigration(smartTable.customSmartTableHeaders[i].properties?.dateFormat)\n }}\n </div>\n }\n @if (smartTable.customSmartTableHeaders[i].properties?.type === type().CHECKBOX) {\n <div>\n <mat-checkbox\n [disabled]=\"true\"\n [checked]=\"getValue(row, header.propertyName)\"\n ></mat-checkbox>\n </div>\n }\n @if (\n smartTable.customSmartTableHeaders[i].properties?.type === type().ICON &&\n smartTable.customSmartTableHeaders[i].properties?.icons?.length\n ) {\n <div\n >\n <smart-icon\n [smartTooltip]=\"getToolTip(row, i)\"\n [icon]=\"getIcon(getValue(row, header.propertyName), i)!\"\n [color]=\"getColor(getValue(row, header.propertyName), i)\"\n >\n </smart-icon>\n </div>\n }\n </div>\n }\n @if (isImageResource(row, header.propertyName)) {\n <div>\n <smart-icon\n [icon]=\"getImageResourceIcon(row, header.propertyName)\"\n [color]=\"getImageResourceColor(row, header.propertyName)\"\n >\n </smart-icon>\n </div>\n }\n @if (smartTable.customSmartTableHeaders && smartTable.customSmartTableHeaders[i].buttons) {\n <div\n class=\"smart-table-buttons-col\"\n >\n @for (button of smartTable.customSmartTableHeaders[i].buttons; track button) {\n <div>\n @if (showButton(button, row)) {\n <div>\n @switch (button.type) {\n @case (smartTableButtonType.ICON) {\n <button\n (click)=\"customButtonClicked($event, button, row)\"\n mat-icon-button\n color=\"{{ button.color }}\"\n >\n <smart-icon title=\"{{ button.label }}\" [icon]=\"button.icon!\"></smart-icon>\n </button>\n }\n @case (smartTableButtonType.NORMAL) {\n <button\n (click)=\"customButtonClicked($event, button, row)\"\n mat-button\n color=\"{{ button.color }}\"\n >\n @if (button.icon) {\n <smart-icon [icon]=\"button.icon\"></smart-icon>\n }\n {{ button.label }}\n </button>\n }\n @case (smartTableButtonType.RAISED) {\n <button\n (click)=\"customButtonClicked($event, button, row)\"\n mat-raised-button\n color=\"{{ button.color }}\"\n >\n @if (button.icon) {\n <smart-icon [icon]=\"button.icon\"></smart-icon>\n }\n {{ button.label }}\n </button>\n }\n <!------ MENU ------>\n @case (smartTableButtonType.MENU) {\n <div class=\"menu-button\">\n <!------ DEFAULT_ACTION_COLUMN ID TOOLBAR ------>\n <smart-ui-action-toolbar\n [uiActionModels]=\"getRowColumnAction(row, defaultActionToolbarId)\"\n [widgetId]=\"smartTable.getGridId()\"\n [nodeId]=\"row.id\"\n [actionParams]=\"{ model: row }\"\n (actionFired)=\"onRowActionFired(row, $event)\"\n ></smart-ui-action-toolbar>\n <!------ GENERIC HAMBURGER ------>\n @if (shouldShowMenuButton(row)) {\n <smart-ui-action-toolbar\n [uiActionModels]=\"getMenuActions(row)\"\n [widgetId]=\"smartTable.getGridId()\"\n [nodeId]=\"row.id\"\n [actionParams]=\"{ model: row }\"\n (actionFired)=\"onRowActionFired(row, $event)\"\n ></smart-ui-action-toolbar>\n }\n <!------ MENU ------>\n </div>\n }\n }\n </div>\n }\n </div>\n }\n </div>\n }\n @if (smartTable.customSmartTableHeaders && smartTable.customSmartTableHeaders[i].icon) {\n <div>\n @if (smartTable.customSmartTableHeaders[i].icon?.icon) {\n <smart-icon\n [ngClass]=\"smartTable.customSmartTableHeaders[i].icon?.cssClass ?? ''\"\n [color]=\"$safeNavigationMigration(smartTable.customSmartTableHeaders[i].icon?.color)\"\n [icon]=\"smartTable.customSmartTableHeaders[i].icon!.icon\"\n >\n </smart-icon>\n }\n </div>\n }\n @if (\n smartTable.customSmartTableHeaders &&\n smartTable.customSmartTableHeaders[i].translator !== undefined\n ) {\n <div\n class=\"smart-table-cell-text\"\n >\n {{ smartTable.customSmartTableHeaders[i].translator!(getValue(row, header.propertyName)) }}\n </div>\n }\n @if (\n !smartTable.customSmartTableHeaders ||\n (smartTable.customSmartTableHeaders &&\n (!smartTable.customSmartTableHeaders[i].properties || columnTemplate(i)) &&\n !smartTable.customSmartTableHeaders[i].icon &&\n !smartTable.customSmartTableHeaders[i].buttons &&\n !smartTable.customSmartTableHeaders[i].translator &&\n !(smartTable.customSmartTableHeaders[i].propertyName === 'select') &&\n !(smartTable.customSmartTableHeaders[i].propertyName === 'expand'))\n ) {\n <div\n >\n @if (header.propertyName === 'icon') {\n <smart-icon\n [icon]=\"getValue(row, header.propertyName)!\"\n >\n </smart-icon>\n }\n @if (header.propertyName === 'img') {\n <img\n [src]=\"getValue(row, header.propertyName)\"\n alt=\"\"\n class=\"smarttableImg\"\n />\n }\n @if (\n header.propertyName !== 'icon' &&\n header.propertyName !== 'img' &&\n header.propertyName !== 'option' &&\n header.propertyName !== 'button'\n ) {\n @if (columnTemplate(i); as template) {\n <smart-html-cell\n [row]=\"row\"\n [template]=\"template\"\n [gridIdentifier]=\"smartTable.getGridId()\"\n ></smart-html-cell>\n } @else if (isTextContent(i)) {\n <div class=\"smart-table-cell-text\">{{ getValue(row, header.propertyName) }}</div>\n } @else {\n <div\n class=\"smart-table-cell-text\"\n [innerHtml]=\"getValue(row, header.propertyName)\"\n ></div>\n }\n }\n </div>\n }\n </div>\n }\n </div>\n }\n</div>\n", styles: [".mobileTable{display:flex;flex-direction:column}.mobileTableRow{display:flex;flex-direction:column;border-bottom:1px solid var(--color-border, #e4eaf2);padding:.5rem;gap:.5rem}.mobileTableRow.sm-grid-row-new{color:var(--success-color, #16a34a);font-style:italic}.mobileTableRow.sm-grid-row-modified{color:var(--accent-color, #20b6a8);font-weight:600}.mobileTableRow.sm-grid-row-deleted{color:var(--color-text-muted, #68778e)}.mobileTableRow.sm-grid-row-deleted .smart-table-cell-text{text-decoration:line-through}.mobileTableRow.sm-grid-row-moved{font-style:italic}.mobileTableRow.sm-grid-row-contains-change{color:var(--accent-color, #20b6a8)}.sm-grid-row-state-icon ::ng-deep .mat-icon{font-size:18px;width:18px;height:18px;line-height:18px;color:var(--accent-color, #20b6a8)}.sm-grid-row-state-icon-new ::ng-deep .mat-icon{color:var(--success-color, #16a34a)}.sm-grid-row-state-icon-deleted ::ng-deep .mat-icon{color:var(--color-text-muted, #68778e)}.sm-grid-row-state-icon-contains-change ::ng-deep .mat-icon{font-size:10px;width:18px;text-align:center;color:var(--color-text-muted, #68778e)}.mobileTableRow.selected{color:inherit}.mobileTableRow.sm-grid-row-open{--sm-grid-open-row-tint: var( --grid-open-row-background, color-mix(in srgb, var(--accent-color, #20b6a8) 10%, transparent) );background-image:linear-gradient(var(--sm-grid-open-row-tint),var(--sm-grid-open-row-tint));box-shadow:inset 3px 0 0 var(--grid-open-row-bar, var(--accent-color, #20b6a8))}.mobileTableCell{display:flex;flex-direction:column;gap:.25rem}.mobileTableCellHeader{font-weight:700;color:var(--primary-color, #384372)}.selected{background-color:var(--primary-light-color, var(--color-surface-alt, #f4f6fb))}.mat-mdc-menu-item[disabled]{cursor:default!important}\n"], dependencies: [{ kind: "directive", type: NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "component", type: MatCheckbox, selector: "mat-checkbox", inputs: ["aria-label", "aria-labelledby", "aria-describedby", "aria-expanded", "aria-controls", "aria-owns", "id", "required", "labelPosition", "name", "value", "disableRipple", "tabIndex", "color", "disabledInteractive", "checked", "disabled", "indeterminate"], outputs: ["change", "indeterminateChange"], exportAs: ["matCheckbox"] }, { kind: "component", type: SmartIconComponent, selector: "smart-icon", inputs: ["icon", "color", "imageResource"] }, { kind: "directive", type: SmartTooltipDirective, selector: "[smartTooltip]", inputs: ["smartTooltip", "smartTooltipClass"] }, { kind: "directive", type: MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }, { kind: "component", type: MatIconButton, selector: "button[mat-icon-button], a[mat-icon-button], button[matIconButton], a[matIconButton]", exportAs: ["matButton", "matAnchor"] }, { kind: "component", type: MatButton, selector: " button[matButton], a[matButton], button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button], a[mat-button], a[mat-raised-button], a[mat-flat-button], a[mat-stroked-button] ", inputs: ["matButton"], exportAs: ["matButton", "matAnchor"] }, { kind: "component", type: UiActionToolbarComponent, selector: "smart-ui-action-toolbar", inputs: ["uiActionModels", "uiActionDescriptorService", "id", "executor", "widgetId", "nodeId", "actionParams", "scrollOnWrap", "toolbarPropertes"], outputs: ["actionFired"] }, { kind: "component", type: SmartHtmlCellComponent, selector: "smart-html-cell", inputs: ["row", "template", "gridIdentifier"] }, { kind: "pipe", type: SmartDateTimePipe, name: "smartDateTime" }, { kind: "pipe", type: SmartDatePipe, name: "smartDate" }, { kind: "pipe", type: SmartTimePipe, name: "smartTime" }] }); }
16785
17723
  }
16786
17724
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: MobileTableComponent, decorators: [{
16787
17725
  type: Component,
16788
- args: [{ selector: 'lib-mobile-table', imports: [NgClass, NgStyle, MatCheckbox, SmartIconComponent, SmartTooltipDirective, MatTooltip, MatIconButton, MatButton, UiActionToolbarComponent, SmartDateTimePipe, SmartDatePipe, SmartTimePipe, SmartHtmlCellComponent], template: "<div class=\"mobileTable\">\n @for (row of smartTable.tableRows; track trackRow($index, row)) {\n <div\n class=\"mobileTableRow\"\n [ngClass]=\"getRowClasses(row)\"\n [ngStyle]=\"getRowStyles(row)\"\n [attr.data-testid]=\"row?.id ?? null\"\n >\n @if (showRowStateColumn) {\n <div class=\"mobileTableCell sm-grid-row-state-cell\" [attr.data-testid]=\"rowStateColumn\">\n @if (rowStateIcon(row); as icon) {\n <smart-icon\n class=\"sm-grid-row-state-icon\"\n [ngClass]=\"rowStateIconClass(row) ?? ''\"\n [icon]=\"icon\"\n color=\"\"\n role=\"img\"\n [attr.aria-label]=\"rowStateLabel(row)\"\n [matTooltip]=\"rowStateLabel(row) ?? ''\"\n ></smart-icon>\n }\n </div>\n }\n @for (header of smartTable.customSmartTableHeaders; track header; let i = $index) {\n <div\n class=\"mobileTableCell\"\n (click)=\"handleOnRowClick($event, row)\"\n (dblclick)=\"handleOnRowDoubleClick($event, row)\"\n >\n <div class=\"mobileTableCellHeader\" [attr.data-testid]=\"header.propertyName\">\n {{ header.label }}\n </div>\n @if (\n smartTable.customSmartTableHeaders &&\n smartTable.customSmartTableHeaders[i].propertyName === 'select'\n ) {\n <mat-checkbox\n (click)=\"$event.stopPropagation()\"\n (change)=\"\n $event\n ? setSelection(\n smartTable.selectionProperty\n ? smartTable.getValueDeeply(row, smartTable.selectionProperty)\n : row\n )\n : null\n \"\n [disabled]=\"isDisabled(row)\"\n [checked]=\"\n smartTable.selection!.isSelected(\n smartTable.selectionProperty\n ? smartTable.getValueDeeply(row, smartTable.selectionProperty)\n : row\n )\n \"\n [aria-label]=\"\n checkboxLabel(\n smartTable.selectionProperty\n ? smartTable.getValueDeeply(row, smartTable.selectionProperty)\n : row\n )\n \"\n >\n </mat-checkbox>\n }\n @if (\n smartTable.customSmartTableHeaders && smartTable.customSmartTableHeaders[i].properties &&\n !columnTemplate(i)\n ) {\n <div\n >\n @if (smartTable.customSmartTableHeaders[i].properties?.type === type().DATETIME) {\n <div class=\"smart-table-cell-text\">\n {{\n getValue(row, header.propertyName)\n | smartDateTime: $safeNavigationMigration(smartTable.customSmartTableHeaders[i].properties?.dateFormat)\n }}\n </div>\n }\n @if (smartTable.customSmartTableHeaders[i].properties?.type === type().DATE) {\n <div class=\"smart-table-cell-text\">\n {{\n getValue(row, header.propertyName)\n | smartDate: $safeNavigationMigration(smartTable.customSmartTableHeaders[i].properties?.dateFormat)\n }}\n </div>\n }\n @if (smartTable.customSmartTableHeaders[i].properties?.type === type().TIME) {\n <div class=\"smart-table-cell-text\">\n {{\n getValue(row, header.propertyName)\n | smartTime: $safeNavigationMigration(smartTable.customSmartTableHeaders[i].properties?.dateFormat)\n }}\n </div>\n }\n @if (smartTable.customSmartTableHeaders[i].properties?.type === type().CHECKBOX) {\n <div>\n <mat-checkbox\n [disabled]=\"true\"\n [checked]=\"getValue(row, header.propertyName)\"\n ></mat-checkbox>\n </div>\n }\n @if (\n smartTable.customSmartTableHeaders[i].properties?.type === type().ICON &&\n smartTable.customSmartTableHeaders[i].properties?.icons?.length\n ) {\n <div\n >\n <smart-icon\n [smartTooltip]=\"getToolTip(row, i)\"\n [icon]=\"getIcon(getValue(row, header.propertyName), i)!\"\n [color]=\"getColor(getValue(row, header.propertyName), i)\"\n >\n </smart-icon>\n </div>\n }\n </div>\n }\n @if (isImageResource(row, header.propertyName)) {\n <div>\n <smart-icon\n [icon]=\"getImageResourceIcon(row, header.propertyName)\"\n [color]=\"getImageResourceColor(row, header.propertyName)\"\n >\n </smart-icon>\n </div>\n }\n @if (smartTable.customSmartTableHeaders && smartTable.customSmartTableHeaders[i].buttons) {\n <div\n class=\"smart-table-buttons-col\"\n >\n @for (button of smartTable.customSmartTableHeaders[i].buttons; track button) {\n <div>\n @if (showButton(button, row)) {\n <div>\n @switch (button.type) {\n @case (smartTableButtonType.ICON) {\n <button\n (click)=\"customButtonClicked($event, button, row)\"\n mat-icon-button\n color=\"{{ button.color }}\"\n >\n <smart-icon title=\"{{ button.label }}\" [icon]=\"button.icon!\"></smart-icon>\n </button>\n }\n @case (smartTableButtonType.NORMAL) {\n <button\n (click)=\"customButtonClicked($event, button, row)\"\n mat-button\n color=\"{{ button.color }}\"\n >\n @if (button.icon) {\n <smart-icon [icon]=\"button.icon\"></smart-icon>\n }\n {{ button.label }}\n </button>\n }\n @case (smartTableButtonType.RAISED) {\n <button\n (click)=\"customButtonClicked($event, button, row)\"\n mat-raised-button\n color=\"{{ button.color }}\"\n >\n @if (button.icon) {\n <smart-icon [icon]=\"button.icon\"></smart-icon>\n }\n {{ button.label }}\n </button>\n }\n <!------ MENU ------>\n @case (smartTableButtonType.MENU) {\n <div class=\"menu-button\">\n <!------ DEFAULT_ACTION_COLUMN ID TOOLBAR ------>\n <smart-ui-action-toolbar\n [uiActionModels]=\"getRowColumnAction(element, defaultActionToolbarId)\"\n [widgetId]=\"smartTable.getGridId()\"\n [nodeId]=\"element.id\"\n [actionParams]=\"{ model: element }\"\n (actionFired)=\"onRowActionFired(element, $event)\"\n ></smart-ui-action-toolbar>\n <!------ GENERIC HAMBURGER ------>\n @if (shouldShowMenuButton(element)) {\n <smart-ui-action-toolbar\n [uiActionModels]=\"getMenuActions(element)\"\n [widgetId]=\"smartTable.getGridId()\"\n [nodeId]=\"element.id\"\n [actionParams]=\"{ model: element }\"\n (actionFired)=\"onRowActionFired(element, $event)\"\n ></smart-ui-action-toolbar>\n }\n <!------ MENU ------>\n </div>\n }\n }\n </div>\n }\n </div>\n }\n </div>\n }\n @if (smartTable.customSmartTableHeaders && smartTable.customSmartTableHeaders[i].icon) {\n <div>\n @if (smartTable.customSmartTableHeaders[i].icon?.icon) {\n <smart-icon\n [ngClass]=\"smartTable.customSmartTableHeaders[i].icon?.cssClass ?? ''\"\n [color]=\"$safeNavigationMigration(smartTable.customSmartTableHeaders[i].icon?.color)\"\n [icon]=\"smartTable.customSmartTableHeaders[i].icon!.icon\"\n >\n </smart-icon>\n }\n </div>\n }\n @if (\n smartTable.customSmartTableHeaders &&\n smartTable.customSmartTableHeaders[i].translator !== undefined\n ) {\n <div\n class=\"smart-table-cell-text\"\n >\n {{ smartTable.customSmartTableHeaders[i].translator!(getValue(row, header.propertyName)) }}\n </div>\n }\n @if (\n !smartTable.customSmartTableHeaders ||\n (smartTable.customSmartTableHeaders &&\n (!smartTable.customSmartTableHeaders[i].properties || columnTemplate(i)) &&\n !smartTable.customSmartTableHeaders[i].icon &&\n !smartTable.customSmartTableHeaders[i].buttons &&\n !smartTable.customSmartTableHeaders[i].translator &&\n !(smartTable.customSmartTableHeaders[i].propertyName === 'select') &&\n !(smartTable.customSmartTableHeaders[i].propertyName === 'expand'))\n ) {\n <div\n >\n @if (header.propertyName === 'icon') {\n <smart-icon\n [icon]=\"getValue(row, header.propertyName)!\"\n >\n </smart-icon>\n }\n @if (header.propertyName === 'img') {\n <img\n [src]=\"getValue(row, header.propertyName)\"\n alt=\"\"\n class=\"smarttableImg\"\n />\n }\n @if (\n header.propertyName !== 'icon' &&\n header.propertyName !== 'img' &&\n header.propertyName !== 'option' &&\n header.propertyName !== 'button'\n ) {\n @if (columnTemplate(i); as template) {\n <smart-html-cell\n [row]=\"row\"\n [template]=\"template\"\n [gridIdentifier]=\"smartTable.getGridId()\"\n ></smart-html-cell>\n } @else if (isTextContent(i)) {\n <div class=\"smart-table-cell-text\">{{ getValue(row, header.propertyName) }}</div>\n } @else {\n <div\n class=\"smart-table-cell-text\"\n [innerHtml]=\"getValue(row, header.propertyName)\"\n ></div>\n }\n }\n </div>\n }\n </div>\n }\n </div>\n }\n</div>\n", styles: [".mobileTable{display:flex;flex-direction:column}.mobileTableRow{display:flex;flex-direction:column;border-bottom:1px solid var(--color-border, #e4eaf2);padding:.5rem;gap:.5rem}.mobileTableRow.sm-grid-row-new{color:var(--success-color, #16a34a);font-style:italic}.mobileTableRow.sm-grid-row-modified{color:var(--accent-color, #20b6a8);font-weight:600}.mobileTableRow.sm-grid-row-deleted{color:var(--color-text-muted, #68778e)}.mobileTableRow.sm-grid-row-deleted .smart-table-cell-text{text-decoration:line-through}.mobileTableRow.sm-grid-row-moved{font-style:italic}.mobileTableRow.sm-grid-row-contains-change{color:var(--accent-color, #20b6a8)}.sm-grid-row-state-icon ::ng-deep .mat-icon{font-size:18px;width:18px;height:18px;line-height:18px;color:var(--accent-color, #20b6a8)}.sm-grid-row-state-icon-new ::ng-deep .mat-icon{color:var(--success-color, #16a34a)}.sm-grid-row-state-icon-deleted ::ng-deep .mat-icon{color:var(--color-text-muted, #68778e)}.sm-grid-row-state-icon-contains-change ::ng-deep .mat-icon{font-size:10px;width:18px;text-align:center;color:var(--color-text-muted, #68778e)}.mobileTableRow.selected{color:inherit}.mobileTableRow.sm-grid-row-open{--sm-grid-open-row-tint: var( --grid-open-row-background, color-mix(in srgb, var(--accent-color, #20b6a8) 10%, transparent) );background-image:linear-gradient(var(--sm-grid-open-row-tint),var(--sm-grid-open-row-tint));box-shadow:inset 3px 0 0 var(--grid-open-row-bar, var(--accent-color, #20b6a8))}.mobileTableCell{display:flex;flex-direction:column;gap:.25rem}.mobileTableCellHeader{font-weight:700;color:var(--primary-color, #384372)}.selected{background-color:var(--primary-light-color, var(--color-surface-alt, #f4f6fb))}.mat-mdc-menu-item[disabled]{cursor:default!important}\n"] }]
17726
+ args: [{ selector: 'lib-mobile-table', imports: [NgClass, NgStyle, MatCheckbox, SmartIconComponent, SmartTooltipDirective, MatTooltip, MatIconButton, MatButton, UiActionToolbarComponent, SmartDateTimePipe, SmartDatePipe, SmartTimePipe, SmartHtmlCellComponent], template: "<div class=\"mobileTable\">\n @for (row of smartTable.tableRows; track trackRow($index, row)) {\n <div\n class=\"mobileTableRow\"\n [ngClass]=\"getRowClasses(row)\"\n [ngStyle]=\"getRowStyles(row)\"\n [attr.data-testid]=\"row?.id ?? null\"\n >\n @if (showRowStateColumn) {\n <div class=\"mobileTableCell sm-grid-row-state-cell\" [attr.data-testid]=\"rowStateColumn\">\n @if (rowStateIcon(row); as icon) {\n <smart-icon\n class=\"sm-grid-row-state-icon\"\n [ngClass]=\"rowStateIconClass(row) ?? ''\"\n [icon]=\"icon\"\n color=\"\"\n role=\"img\"\n [attr.aria-label]=\"rowStateLabel(row)\"\n [matTooltip]=\"rowStateLabel(row) ?? ''\"\n ></smart-icon>\n }\n </div>\n }\n @for (header of smartTable.customSmartTableHeaders; track header; let i = $index) {\n <div\n class=\"mobileTableCell\"\n (click)=\"handleOnRowClick($event, row)\"\n (dblclick)=\"handleOnRowDoubleClick($event, row)\"\n >\n <div class=\"mobileTableCellHeader\" [attr.data-testid]=\"header.propertyName\">\n {{ header.label }}\n </div>\n @if (\n smartTable.customSmartTableHeaders &&\n smartTable.customSmartTableHeaders[i].propertyName === 'select'\n ) {\n <mat-checkbox\n (click)=\"$event.stopPropagation()\"\n (change)=\"\n $event\n ? setSelection(\n smartTable.selectionProperty\n ? smartTable.getValueDeeply(row, smartTable.selectionProperty)\n : row\n )\n : null\n \"\n [disabled]=\"isDisabled(row)\"\n [checked]=\"\n smartTable.selection!.isSelected(\n smartTable.selectionProperty\n ? smartTable.getValueDeeply(row, smartTable.selectionProperty)\n : row\n )\n \"\n [aria-label]=\"\n checkboxLabel(\n smartTable.selectionProperty\n ? smartTable.getValueDeeply(row, smartTable.selectionProperty)\n : row\n )\n \"\n >\n </mat-checkbox>\n }\n @if (\n smartTable.customSmartTableHeaders && smartTable.customSmartTableHeaders[i].properties &&\n !columnTemplate(i)\n ) {\n <div\n >\n @if (smartTable.customSmartTableHeaders[i].properties?.type === type().DATETIME) {\n <div class=\"smart-table-cell-text\">\n {{\n getValue(row, header.propertyName)\n | smartDateTime: $safeNavigationMigration(smartTable.customSmartTableHeaders[i].properties?.dateFormat)\n }}\n </div>\n }\n @if (smartTable.customSmartTableHeaders[i].properties?.type === type().DATE) {\n <div class=\"smart-table-cell-text\">\n {{\n getValue(row, header.propertyName)\n | smartDate: $safeNavigationMigration(smartTable.customSmartTableHeaders[i].properties?.dateFormat)\n }}\n </div>\n }\n @if (smartTable.customSmartTableHeaders[i].properties?.type === type().TIME) {\n <div class=\"smart-table-cell-text\">\n {{\n getValue(row, header.propertyName)\n | smartTime: $safeNavigationMigration(smartTable.customSmartTableHeaders[i].properties?.dateFormat)\n }}\n </div>\n }\n @if (smartTable.customSmartTableHeaders[i].properties?.type === type().CHECKBOX) {\n <div>\n <mat-checkbox\n [disabled]=\"true\"\n [checked]=\"getValue(row, header.propertyName)\"\n ></mat-checkbox>\n </div>\n }\n @if (\n smartTable.customSmartTableHeaders[i].properties?.type === type().ICON &&\n smartTable.customSmartTableHeaders[i].properties?.icons?.length\n ) {\n <div\n >\n <smart-icon\n [smartTooltip]=\"getToolTip(row, i)\"\n [icon]=\"getIcon(getValue(row, header.propertyName), i)!\"\n [color]=\"getColor(getValue(row, header.propertyName), i)\"\n >\n </smart-icon>\n </div>\n }\n </div>\n }\n @if (isImageResource(row, header.propertyName)) {\n <div>\n <smart-icon\n [icon]=\"getImageResourceIcon(row, header.propertyName)\"\n [color]=\"getImageResourceColor(row, header.propertyName)\"\n >\n </smart-icon>\n </div>\n }\n @if (smartTable.customSmartTableHeaders && smartTable.customSmartTableHeaders[i].buttons) {\n <div\n class=\"smart-table-buttons-col\"\n >\n @for (button of smartTable.customSmartTableHeaders[i].buttons; track button) {\n <div>\n @if (showButton(button, row)) {\n <div>\n @switch (button.type) {\n @case (smartTableButtonType.ICON) {\n <button\n (click)=\"customButtonClicked($event, button, row)\"\n mat-icon-button\n color=\"{{ button.color }}\"\n >\n <smart-icon title=\"{{ button.label }}\" [icon]=\"button.icon!\"></smart-icon>\n </button>\n }\n @case (smartTableButtonType.NORMAL) {\n <button\n (click)=\"customButtonClicked($event, button, row)\"\n mat-button\n color=\"{{ button.color }}\"\n >\n @if (button.icon) {\n <smart-icon [icon]=\"button.icon\"></smart-icon>\n }\n {{ button.label }}\n </button>\n }\n @case (smartTableButtonType.RAISED) {\n <button\n (click)=\"customButtonClicked($event, button, row)\"\n mat-raised-button\n color=\"{{ button.color }}\"\n >\n @if (button.icon) {\n <smart-icon [icon]=\"button.icon\"></smart-icon>\n }\n {{ button.label }}\n </button>\n }\n <!------ MENU ------>\n @case (smartTableButtonType.MENU) {\n <div class=\"menu-button\">\n <!------ DEFAULT_ACTION_COLUMN ID TOOLBAR ------>\n <smart-ui-action-toolbar\n [uiActionModels]=\"getRowColumnAction(row, defaultActionToolbarId)\"\n [widgetId]=\"smartTable.getGridId()\"\n [nodeId]=\"row.id\"\n [actionParams]=\"{ model: row }\"\n (actionFired)=\"onRowActionFired(row, $event)\"\n ></smart-ui-action-toolbar>\n <!------ GENERIC HAMBURGER ------>\n @if (shouldShowMenuButton(row)) {\n <smart-ui-action-toolbar\n [uiActionModels]=\"getMenuActions(row)\"\n [widgetId]=\"smartTable.getGridId()\"\n [nodeId]=\"row.id\"\n [actionParams]=\"{ model: row }\"\n (actionFired)=\"onRowActionFired(row, $event)\"\n ></smart-ui-action-toolbar>\n }\n <!------ MENU ------>\n </div>\n }\n }\n </div>\n }\n </div>\n }\n </div>\n }\n @if (smartTable.customSmartTableHeaders && smartTable.customSmartTableHeaders[i].icon) {\n <div>\n @if (smartTable.customSmartTableHeaders[i].icon?.icon) {\n <smart-icon\n [ngClass]=\"smartTable.customSmartTableHeaders[i].icon?.cssClass ?? ''\"\n [color]=\"$safeNavigationMigration(smartTable.customSmartTableHeaders[i].icon?.color)\"\n [icon]=\"smartTable.customSmartTableHeaders[i].icon!.icon\"\n >\n </smart-icon>\n }\n </div>\n }\n @if (\n smartTable.customSmartTableHeaders &&\n smartTable.customSmartTableHeaders[i].translator !== undefined\n ) {\n <div\n class=\"smart-table-cell-text\"\n >\n {{ smartTable.customSmartTableHeaders[i].translator!(getValue(row, header.propertyName)) }}\n </div>\n }\n @if (\n !smartTable.customSmartTableHeaders ||\n (smartTable.customSmartTableHeaders &&\n (!smartTable.customSmartTableHeaders[i].properties || columnTemplate(i)) &&\n !smartTable.customSmartTableHeaders[i].icon &&\n !smartTable.customSmartTableHeaders[i].buttons &&\n !smartTable.customSmartTableHeaders[i].translator &&\n !(smartTable.customSmartTableHeaders[i].propertyName === 'select') &&\n !(smartTable.customSmartTableHeaders[i].propertyName === 'expand'))\n ) {\n <div\n >\n @if (header.propertyName === 'icon') {\n <smart-icon\n [icon]=\"getValue(row, header.propertyName)!\"\n >\n </smart-icon>\n }\n @if (header.propertyName === 'img') {\n <img\n [src]=\"getValue(row, header.propertyName)\"\n alt=\"\"\n class=\"smarttableImg\"\n />\n }\n @if (\n header.propertyName !== 'icon' &&\n header.propertyName !== 'img' &&\n header.propertyName !== 'option' &&\n header.propertyName !== 'button'\n ) {\n @if (columnTemplate(i); as template) {\n <smart-html-cell\n [row]=\"row\"\n [template]=\"template\"\n [gridIdentifier]=\"smartTable.getGridId()\"\n ></smart-html-cell>\n } @else if (isTextContent(i)) {\n <div class=\"smart-table-cell-text\">{{ getValue(row, header.propertyName) }}</div>\n } @else {\n <div\n class=\"smart-table-cell-text\"\n [innerHtml]=\"getValue(row, header.propertyName)\"\n ></div>\n }\n }\n </div>\n }\n </div>\n }\n </div>\n }\n</div>\n", styles: [".mobileTable{display:flex;flex-direction:column}.mobileTableRow{display:flex;flex-direction:column;border-bottom:1px solid var(--color-border, #e4eaf2);padding:.5rem;gap:.5rem}.mobileTableRow.sm-grid-row-new{color:var(--success-color, #16a34a);font-style:italic}.mobileTableRow.sm-grid-row-modified{color:var(--accent-color, #20b6a8);font-weight:600}.mobileTableRow.sm-grid-row-deleted{color:var(--color-text-muted, #68778e)}.mobileTableRow.sm-grid-row-deleted .smart-table-cell-text{text-decoration:line-through}.mobileTableRow.sm-grid-row-moved{font-style:italic}.mobileTableRow.sm-grid-row-contains-change{color:var(--accent-color, #20b6a8)}.sm-grid-row-state-icon ::ng-deep .mat-icon{font-size:18px;width:18px;height:18px;line-height:18px;color:var(--accent-color, #20b6a8)}.sm-grid-row-state-icon-new ::ng-deep .mat-icon{color:var(--success-color, #16a34a)}.sm-grid-row-state-icon-deleted ::ng-deep .mat-icon{color:var(--color-text-muted, #68778e)}.sm-grid-row-state-icon-contains-change ::ng-deep .mat-icon{font-size:10px;width:18px;text-align:center;color:var(--color-text-muted, #68778e)}.mobileTableRow.selected{color:inherit}.mobileTableRow.sm-grid-row-open{--sm-grid-open-row-tint: var( --grid-open-row-background, color-mix(in srgb, var(--accent-color, #20b6a8) 10%, transparent) );background-image:linear-gradient(var(--sm-grid-open-row-tint),var(--sm-grid-open-row-tint));box-shadow:inset 3px 0 0 var(--grid-open-row-bar, var(--accent-color, #20b6a8))}.mobileTableCell{display:flex;flex-direction:column;gap:.25rem}.mobileTableCellHeader{font-weight:700;color:var(--primary-color, #384372)}.selected{background-color:var(--primary-light-color, var(--color-surface-alt, #f4f6fb))}.mat-mdc-menu-item[disabled]{cursor:default!important}\n"] }]
16789
17727
  }] });
16790
17728
 
16791
17729
  class SmarttableComponent {
@@ -27959,7 +28897,7 @@ class UiActionFileUploadDialogComponent {
27959
28897
  this.service.cancel();
27960
28898
  }
27961
28899
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: UiActionFileUploadDialogComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
27962
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.8", type: UiActionFileUploadDialogComponent, isStandalone: true, selector: "lib-ui-action-file-upload-dialog", ngImport: i0, template: "<div class=\"folderNameDialogContainer\">\n <div class=\"headerContainer\">\n <h3 class=\"color-accent-700\">\n {{ getTitle() }}\n </h3>\n </div>\n\n @if(getText()){\n <p>\n {{ getText() }}\n </p>\n } @if(descriptor){\n <smart-upload-widget\n [uploadDescriptor]=\"descriptor.upload\"\n [isMultiple]=\"isMultiple ?? true\"\n (uploadFilesEvent)=\"upload($event)\"\n />\n }\n\n <div class=\"folderNameDialogButtonsContainer\">\n <ui-action-button [descriptor]=\"cancelButton\" (actionClick)=\"cancel()\"></ui-action-button>\n </div>\n</div>\n", styles: [".folderNameDialogContainer{display:flex;flex-direction:column;justify-content:center;gap:1rem;min-width:30vw}photo-capture-widget{width:100%;max-width:100%;overflow:hidden;display:flex;flex-direction:column;align-items:center}.headerContainer{display:flex;flex-direction:row;justify-content:space-between;align-items:center;padding-top:.5rem}.headerContainer ::ng-deep button{color:var(--primary-color);border:unset}.headerContainer h3{margin:0;font-size:1.5rem}.folderNameDialogButtonsContainer{display:flex;flex-direction:row;justify-content:flex-start;gap:.5rem}.message{text-align:center;color:#6b7280}.uploadField{display:flex;flex-direction:row;justify-content:center;align-items:center;gap:1rem;padding:1rem}.folderNameDialogButtonsContainer ::ng-deep button:hover{background-color:rgba(from var(--primary-color) r g b/.8)}.headerContainer ::ng-deep button:hover{background-color:rgba(from var(--primary-color) r g b/.1)}@media(max-width:900px){.folderNameDialogContainer{width:100%}}.fileSize{padding-left:1rem;display:flex;justify-content:flex-end}.fileData{display:flex;flex-direction:column;justify-content:flex-end;padding-right:1rem}\n"], dependencies: [{ kind: "component", type: UploadWidgetComponent, selector: "smart-upload-widget", inputs: ["uploadDescriptor", "isMultiple", "isDisabled"], outputs: ["uploadFilesEvent"] }, { kind: "component", type: UiActionButtonComponent, selector: "ui-action-button", inputs: ["disabled", "descriptor", "code", "identifier", "viewActivated", "tab", "rowState", "addedCssClass", "addBasicClasses", "iconPlaceholder", "chords"], outputs: ["actionClick", "actionDoubleClick"] }] }); }
28900
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.8", type: UiActionFileUploadDialogComponent, isStandalone: true, selector: "lib-ui-action-file-upload-dialog", ngImport: i0, template: "<div class=\"folderNameDialogContainer\">\n <div class=\"headerContainer\">\n <h3 class=\"color-accent-700\">\n {{ getTitle() }}\n </h3>\n </div>\n\n @if(getText()){\n <p>\n {{ getText() }}\n </p>\n } @if(descriptor){\n <smart-upload-widget\n [uploadDescriptor]=\"descriptor.upload\"\n [isMultiple]=\"isMultiple ?? true\"\n (uploadFilesEvent)=\"upload($event)\"\n />\n }\n\n <div class=\"folderNameDialogButtonsContainer\">\n <ui-action-button [descriptor]=\"cancelButton\" (actionClick)=\"cancel()\"></ui-action-button>\n </div>\n</div>\n", styles: [".folderNameDialogContainer{display:flex;flex-direction:column;justify-content:center;gap:1rem;min-width:30vw}photo-capture-widget{width:100%;max-width:100%;overflow:hidden;display:flex;flex-direction:column;align-items:center}.headerContainer{display:flex;flex-direction:row;justify-content:space-between;align-items:center;padding-top:.5rem}.headerContainer ::ng-deep button{color:var(--primary-color);border:unset}.headerContainer h3{margin:0;font-size:1.5rem}.folderNameDialogButtonsContainer{display:flex;flex-direction:row;justify-content:flex-start;gap:.5rem}.message{text-align:center;color:#6b7280}.uploadField{display:flex;flex-direction:row;justify-content:center;align-items:center;gap:1rem;padding:1rem}.folderNameDialogButtonsContainer ::ng-deep button:hover{background-color:rgba(from var(--primary-color) r g b/.8)}.headerContainer ::ng-deep button:hover{background-color:rgba(from var(--primary-color) r g b/.1)}@media(max-width:900px){.folderNameDialogContainer{width:100%}}.fileSize{padding-left:1rem;display:flex;justify-content:flex-end}.fileData{display:flex;flex-direction:column;justify-content:flex-end;padding-right:1rem}\n"], dependencies: [{ kind: "component", type: UploadWidgetComponent, selector: "smart-upload-widget", inputs: ["uploadDescriptor", "isMultiple", "isDisabled", "sources", "capture"], outputs: ["uploadFilesEvent"] }, { kind: "component", type: UiActionButtonComponent, selector: "ui-action-button", inputs: ["disabled", "descriptor", "code", "identifier", "viewActivated", "tab", "rowState", "addedCssClass", "addBasicClasses", "iconPlaceholder", "chords"], outputs: ["actionClick", "actionDoubleClick"] }] }); }
27963
28901
  }
27964
28902
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: UiActionFileUploadDialogComponent, decorators: [{
27965
28903
  type: Component,
@@ -28834,5 +29772,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImpor
28834
29772
  * Generated bundle index. Do not edit.
28835
29773
  */
28836
29774
 
28837
- export { ACLEntry, APIS$5 as APIS, AclEditingViewComponent, ApiModule$5 as ApiModule, ApiQueueService, BASE_PATH$7 as BASE_PATH, COLLECTION_FORMATS$7 as COLLECTION_FORMATS, CUSTOM_DIAGRAM_OPTIONS, CloseResult, CodeEditorLang, ComponentFactoryService, ComponentType, ComponentWidgetType, Configuration$7 as Configuration, DEFAULT_ROW_LAYOUT, DIALOG_DISABLE_CLOSE, DataChangeKind, DefaultChartOptionsProvider, DefaultUiActionCode_CLOSE, DrawTime, EMPTY_PARAMS, ExpandableSectionButtonIconPosition, ExpandableSectionButtonType, ExpandableSectionComponent, FilterExpressionBoolOperator$1 as FilterExpressionBoolOperator, FilterExpressionBuilderGroupBuilderGroupKindEnum, FilterExpressionBuilderUiModelTypeEnum, FilterExpressionDataType, FilterExpressionFieldWidgetType, FilterExpressionOperation, FilterExpressionOrderByOrderEnum, GenericPageComponent, GeoMapDataLoadingMode, GeoMapDataSourceType, GeoMapItemKind, GeoMapOperationMode, GeoMapSelectionMode, GeoMapTextType, GridColumnContentType, GridDataAccessConfigKindEnum, GridDefaultRowActionTrigger, GridRowStateDisplay, GridSelectionMode, GridSelectionType, GridUiActionType, GridViewDescriptorKindEnum, HighlightPipe, IS_ASYNC_PARAM_NAME, IconPosition, ImageResourceKindEnum, InvalidSmartlinkComponent, LayoutDirection, LinkTargetEnum, MAP_ENGINE, MapEngine, MenuOpenDirection, MenuTriggerType, MessageOptionType, MessageTextType, MessageType, NAMED_VALIDATOR, NamedValidatorOperationEnum, NamedValidatorService, NoPermissionPageComponent, PageExportFieldScope, PageExportSelectionKind, PageExportValueType, PageExportValuesSource, REUSE_ROUTER_SLOT_COMPONENT, ROOT_CONTAINER_UUID, ROUTER_SLOT_ID, RecordingUploaderPropertiesOpenDirectionEnum, SMART_DATE_FORMATS, SMART_DEFAULT_VIEW_COMPONENTS, SMART_MESSAGE_DEFAULTS, SMART_NG_CLIENT_CONFIG, SMART_ROW_LAYOUT_COMPONENT, SearchPageComponent, SelectAllMode, SelectionDefinitionTypeEnum, SelectionDefinitionValueSetValueTypeEnum, ServerRequestType, SessionAPIS, SessionErrorBehaviour, SessionService, SmartActionHost, SmartActionType, SmartBackendBootstrapService, SmartComponent, SmartComponentApiClient, SmartComponentDetachedDirective, SmartComponentHost, SmartComponentLayoutComponent, SmartDateFnsAdapter, SmartDatePipe, SmartDateTimePipe, SmartDefaultErrorUiService, SmartDiagramComponent, SmartDialog, SmartEmbeddedSlotDirective, SmartExpandableSectionService, SmartFileEditorComponent, SmartFilterComponent, SmartFilterEditorContentComponent, SmartFilterEditorService, SmartFilterExpressionFieldComponent, SmartFilterPosition, SmartFilterType, SmartFilterWidgetComponent, SmartFormInputMode, SmartFormTextFieldButtonIconPosition, SmartFormWidgetDirection, SmartFormWidgetType, SmartFormWidgetWidth, SmartGridButtonType, SmartGridComponent, SmartGridDataLayout, SmartGridService, SmartGridType, SmartIconComponent, SmartIconService, SmartKeybindingService, SmartLayoutDef, SmartLinkChannelVariableInPath, SmartLinkMigrationStatusStatusEnum, SmartLinkUuidVariableInPath, SmartMapComponent, SmartMultiFileEditorComponent, SmartNavbarComponent, SmartNavbarService, SmartNavigationService, SmartNgClientFeatureKind, SmartService, SmartSessionService, SmartSessionTimerComponent, SmartTable, SmartTableButtonType, SmartTableHeaderPropertyType, SmartTableInterfaceTypeEnum, SmartTableOptionButtonDirection, SmartTableOrder, SmartTableType, SmartTimePipe, SmartTooltipDirective, SmartTreeComponent, SmartTreeNodeButtonType, SmartUserSettinsIconPosition, SmartValidatorName, SmartValueSetStore, SmartViewContextService, SmartViewRedirect, SmartVoiceRecorderComponent, SmartWidgetHintPosition, SmartWidgetHintPositionEnum, SmartdialogService, SmartfileuploaderComponent, SmartformComponent, SmartformLayoutDefinitionService, SmarttreeGenericService, SmarttreeService, SubjectSelectorComponent, ThemeService, ToggleLabelPosition, ToolbarAlignment, ToolbarAppearance, ToolbarDirection, TreeArrowPosition, TreeGridControl, USE_ADVANCED_FILTER, UiActionButtonComponent, UiActionButtonType, UiActionConfirmDialogService, UiActionDescriptorService, UiActionDialogButtonComponent, UiActionDialogType, UiActionFeedbackType, UiActionInputDialogService, UiActionInputType, UiActionService, UiActionToolbarComponent, UiActionTooltipTooltipPositionEnum, UiBadgeComponent, UiBadgeDirective, UploadWidgetType, ValidationResultPageComponent, ValueChangeMode, ValueSetDependencyWhenParentEmptyEnum, ViewContextRebuiltError, ViewEventHandlerViewEventTypeEnum, ViewService, ViewState, ViewType, barChart, bubbleChart, createBasicOptions, doughnutChart, formatChord, formatMessage, hasGroupEditActions, horizontalBarChart, horizontalStackedBarChart, injectSmartComponent, isMacPlatform, isMergedExtra, matches, multiAxisLineChart, parseChord, pieChart, polarAreaChart, provideSmartNgClient, radarChart, registerSmartWidget, resolveDateLocale, resolveSlot, resolveSmartNgClientConfig, resolveViewHandler, scatterChart, stackedBarChart, transformDataToBarLikeDataSet, transformDataToBasicDataSets, transformDataToBubble, transformDataToScatter, withSmartDiagram, withSmartMap };
29775
+ export { ACLEntry, APIS$5 as APIS, AclEditingViewComponent, ApiModule$5 as ApiModule, ApiQueueService, BASE_PATH$7 as BASE_PATH, COLLECTION_FORMATS$7 as COLLECTION_FORMATS, CUSTOM_DIAGRAM_OPTIONS, CaptureDeviceStart, CaptureHandover, CaptureMode, CaptureOpenDirection, CloseResult, CodeEditorLang, ComponentFactoryService, ComponentType, ComponentWidgetType, Configuration$7 as Configuration, DEFAULT_ROW_LAYOUT, DIALOG_DISABLE_CLOSE, DataChangeKind, DefaultChartOptionsProvider, DefaultUiActionCode_CLOSE, DrawTime, EMPTY_PARAMS, ExpandableSectionButtonIconPosition, ExpandableSectionButtonType, ExpandableSectionComponent, FileUploaderSource, FileUploaderThumbnail, FilterExpressionBoolOperator$1 as FilterExpressionBoolOperator, FilterExpressionBuilderGroupBuilderGroupKindEnum, FilterExpressionBuilderUiModelTypeEnum, FilterExpressionDataType, FilterExpressionFieldWidgetType, FilterExpressionOperation, FilterExpressionOrderByOrderEnum, GenericPageComponent, GeoMapDataLoadingMode, GeoMapDataSourceType, GeoMapItemKind, GeoMapOperationMode, GeoMapSelectionMode, GeoMapTextType, GridColumnContentType, GridDataAccessConfigKindEnum, GridDefaultRowActionTrigger, GridRowStateDisplay, GridSelectionMode, GridSelectionType, GridUiActionType, GridViewDescriptorKindEnum, HighlightPipe, IS_ASYNC_PARAM_NAME, IconPosition, ImageResourceKindEnum, InvalidSmartlinkComponent, LayoutDirection, LinkTargetEnum, MAP_ENGINE, MapEngine, MenuOpenDirection, MenuTriggerType, MessageOptionType, MessageTextType, MessageType, NAMED_VALIDATOR, NamedValidatorOperationEnum, NamedValidatorService, NoPermissionPageComponent, PageExportFieldScope, PageExportSelectionKind, PageExportValueType, PageExportValuesSource, REUSE_ROUTER_SLOT_COMPONENT, ROOT_CONTAINER_UUID, ROUTER_SLOT_ID, RecordingUploaderPropertiesOpenDirectionEnum, SMART_DATE_FORMATS, SMART_DEFAULT_VIEW_COMPONENTS, SMART_MESSAGE_DEFAULTS, SMART_NG_CLIENT_CONFIG, SMART_ROW_LAYOUT_COMPONENT, SearchPageComponent, SelectAllMode, SelectionDefinitionTypeEnum, SelectionDefinitionValueSetValueTypeEnum, ServerRequestType, SessionAPIS, SessionErrorBehaviour, SessionService, SmartActionHost, SmartActionType, SmartBackendBootstrapService, SmartComponent, SmartComponentApiClient, SmartComponentDetachedDirective, SmartComponentHost, SmartComponentLayoutComponent, SmartDateFnsAdapter, SmartDatePipe, SmartDateTimePipe, SmartDefaultErrorUiService, SmartDiagramComponent, SmartDialog, SmartEmbeddedSlotDirective, SmartExpandableSectionService, SmartFileEditorComponent, SmartFilterComponent, SmartFilterEditorContentComponent, SmartFilterEditorService, SmartFilterExpressionFieldComponent, SmartFilterPosition, SmartFilterType, SmartFilterWidgetComponent, SmartFormInputMode, SmartFormTextFieldButtonIconPosition, SmartFormWidgetDirection, SmartFormWidgetType, SmartFormWidgetWidth, SmartGridButtonType, SmartGridComponent, SmartGridDataLayout, SmartGridService, SmartGridType, SmartIconComponent, SmartIconService, SmartKeybindingService, SmartLayoutDef, SmartLinkChannelVariableInPath, SmartLinkMigrationStatusStatusEnum, SmartLinkUuidVariableInPath, SmartMapComponent, SmartMultiFileEditorComponent, SmartNavbarComponent, SmartNavbarService, SmartNavigationService, SmartNgClientFeatureKind, SmartService, SmartSessionService, SmartSessionTimerComponent, SmartTable, SmartTableButtonType, SmartTableHeaderPropertyType, SmartTableInterfaceTypeEnum, SmartTableOptionButtonDirection, SmartTableOrder, SmartTableType, SmartTimePipe, SmartTooltipDirective, SmartTreeComponent, SmartTreeNodeButtonType, SmartUserSettinsIconPosition, SmartValidatorName, SmartValueSetStore, SmartViewContextService, SmartViewRedirect, SmartVoiceRecorderComponent, SmartWidgetHintPosition, SmartWidgetHintPositionEnum, SmartdialogService, SmartfileuploaderComponent, SmartformComponent, SmartformLayoutDefinitionService, SmarttreeGenericService, SmarttreeService, SubjectSelectorComponent, ThemeService, ToggleLabelPosition, ToolbarAlignment, ToolbarAppearance, ToolbarDirection, TreeArrowPosition, TreeGridControl, USE_ADVANCED_FILTER, UiActionButtonComponent, UiActionButtonType, UiActionConfirmDialogService, UiActionDescriptorService, UiActionDialogButtonComponent, UiActionDialogType, UiActionFeedbackType, UiActionInputDialogService, UiActionInputType, UiActionService, UiActionToolbarComponent, UiActionTooltipTooltipPositionEnum, UiBadgeComponent, UiBadgeDirective, UploadWidgetType, ValidationResultPageComponent, ValueChangeMode, ValueSetDependencyWhenParentEmptyEnum, ViewContextRebuiltError, ViewEventHandlerViewEventTypeEnum, ViewService, ViewState, ViewType, barChart, bubbleChart, createBasicOptions, doughnutChart, formatChord, formatMessage, hasGroupEditActions, horizontalBarChart, horizontalStackedBarChart, injectSmartComponent, isMacPlatform, isMergedExtra, matches, multiAxisLineChart, parseChord, pieChart, polarAreaChart, provideSmartNgClient, radarChart, registerSmartWidget, resolveDateLocale, resolveSlot, resolveSmartNgClientConfig, resolveViewHandler, scatterChart, stackedBarChart, transformDataToBarLikeDataSet, transformDataToBasicDataSets, transformDataToBubble, transformDataToScatter, withSmartDiagram, withSmartMap };
28838
29776
  //# sourceMappingURL=smartbit4all-ng-client.mjs.map