@smartbit4all/ng-client 7.3.41 → 7.3.43

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, DestroyRef, forwardRef, createComponent, EnvironmentInjector, ApplicationRef, 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.
@@ -9099,6 +9185,18 @@ class UploadWidgetUtils {
9099
9185
  const value = parseFloat((bytes / Math.pow(k, i)).toFixed(1));
9100
9186
  return `${value} ${sizes[i]}`;
9101
9187
  }
9188
+ /** The size of a capture for the Capture Dialog's captions: a decimal point in English, a
9189
+ * decimal comma in Hungarian and without a known locale. */
9190
+ static formatCapturedSize(bytes, locale) {
9191
+ const size = UploadWidgetUtils.formatSize(bytes);
9192
+ return locale?.startsWith('en') ? size : size.replace('.', ',');
9193
+ }
9194
+ /** A length of time as mm:ss. */
9195
+ static formatDuration(milliseconds) {
9196
+ return [Math.floor((milliseconds % 3600000) / 60000), Math.floor((milliseconds % 60000) / 1000)]
9197
+ .map((v) => (v < 10 ? '0' + v : v))
9198
+ .join(':');
9199
+ }
9102
9200
  /** The recording mime of a media kind: webm where the browser records it, else mp4 (Safari). */
9103
9201
  static chooseRecorderMimeType(kind) {
9104
9202
  return MediaRecorder.isTypeSupported(`${kind}/webm`) ? `${kind}/webm` : `${kind}/mp4`;
@@ -9110,6 +9208,89 @@ class UploadWidgetUtils {
9110
9208
  const ext = mimeType.includes('webm') ? 'webm' : 'mp4';
9111
9209
  return new File([blob], `${prefix}_${formattedDate}.${ext}`, { type: mimeType });
9112
9210
  }
9211
+ /** An image re-encoded as a jpeg, smaller step by step until it fits; the file itself when it
9212
+ * does not fit at the floor of the quality and the size, or is no image. */
9213
+ static async compressImageFile(file, maxFileSize) {
9214
+ if (!file.type.startsWith('image/'))
9215
+ return file;
9216
+ const img = await new Promise((resolve, reject) => {
9217
+ const url = URL.createObjectURL(file);
9218
+ const image = new Image();
9219
+ image.onload = () => {
9220
+ URL.revokeObjectURL(url);
9221
+ resolve(image);
9222
+ };
9223
+ image.onerror = (e) => {
9224
+ URL.revokeObjectURL(url);
9225
+ reject(e);
9226
+ };
9227
+ image.src = url;
9228
+ });
9229
+ const canvas = document.createElement('canvas');
9230
+ const ctx = canvas.getContext('2d');
9231
+ if (!ctx)
9232
+ return file;
9233
+ let width = img.naturalWidth;
9234
+ let height = img.naturalHeight;
9235
+ let quality = 0.9;
9236
+ const minQuality = 0.4;
9237
+ const minDimension = 640;
9238
+ const getBlob = (q) => new Promise((resolve) => {
9239
+ canvas.width = width;
9240
+ canvas.height = height;
9241
+ ctx.clearRect(0, 0, width, height);
9242
+ ctx.drawImage(img, 0, 0, width, height);
9243
+ canvas.toBlob((blob) => {
9244
+ if (blob)
9245
+ resolve(blob);
9246
+ }, 'image/jpeg', q);
9247
+ });
9248
+ let blob = await getBlob(quality);
9249
+ while (blob.size > maxFileSize) {
9250
+ if (quality > minQuality) {
9251
+ quality -= 0.1;
9252
+ }
9253
+ else if (width > minDimension && height > minDimension) {
9254
+ width = Math.floor(width * 0.8);
9255
+ height = Math.floor(height * 0.8);
9256
+ quality = 0.9;
9257
+ }
9258
+ else {
9259
+ break;
9260
+ }
9261
+ blob = await getBlob(quality);
9262
+ }
9263
+ if (blob.size <= maxFileSize) {
9264
+ return new File([blob], file.name, { type: 'image/jpeg' });
9265
+ }
9266
+ else {
9267
+ return file;
9268
+ }
9269
+ }
9270
+ /** The descriptor of a capture button: raised, in the primary colour unless told otherwise. */
9271
+ static createCaptureButton(title, icon, color = 'primary', type = UiActionButtonType.RAISED) {
9272
+ return { type, title, icon, iconPosition: IconPosition.PRE, color };
9273
+ }
9274
+ /** The button that takes a picture. */
9275
+ static createTakePictureButton(texts) {
9276
+ return UploadWidgetUtils.createCaptureButton(texts.takePicture, 'camera');
9277
+ }
9278
+ /** The button that starts a video recording. */
9279
+ static createStartRecordingButton(texts) {
9280
+ return UploadWidgetUtils.createCaptureButton(texts.startRecording, 'fiber_manual_record');
9281
+ }
9282
+ /** The button that stops a recording, in the colour of its host. */
9283
+ static createStopRecordingButton(texts, color) {
9284
+ return UploadWidgetUtils.createCaptureButton(texts.stopRecording, 'stop', color);
9285
+ }
9286
+ /** The button that discards the kept capture for a new one. */
9287
+ static createRetakeButton(texts, type = UiActionButtonType.RAISED) {
9288
+ return UploadWidgetUtils.createCaptureButton(texts.retake, 'replay', 'primary', type);
9289
+ }
9290
+ /** The button that accepts the kept capture. */
9291
+ static createUseButton(texts) {
9292
+ return UploadWidgetUtils.createCaptureButton(texts.use, 'check');
9293
+ }
9113
9294
  /** The zone's texts: a descriptor's text per key, the client's default for a missing one. */
9114
9295
  static resolveTexts(texts) {
9115
9296
  return { ...UPLOAD_ZONE_DEFAULT_TEXTS, ...texts };
@@ -9147,11 +9328,23 @@ const UPLOAD_ZONE_DEFAULT_TEXTS = {
9147
9328
  cameraError: 'A kamera nem érhető el.',
9148
9329
  microphoneError: 'A mikrofon nem érhető el.',
9149
9330
  play: 'Lejátszás',
9331
+ photoDialogTitle: 'Fénykép készítése',
9332
+ videoDialogTitle: 'Videó felvétele',
9333
+ audioDialogTitle: 'Hangfelvétel',
9334
+ cancel: 'Mégse',
9335
+ retake: 'Újra',
9336
+ use: 'Használ',
9337
+ save: 'Mentés',
9338
+ capturedPhoto: 'Elkészült kép',
9339
+ capturedVideo: 'Felvétel',
9340
+ recordingHint: 'A lejátszás ikon visszajátssza, a mikrofon gomb új felvételt indít (felülírja ezt).',
9341
+ microphoneRetryHint: 'A mikrofon gomb újra kéri az engedélyt.',
9150
9342
  };
9151
9343
 
9152
9344
  class SmartfileuploaderComponent {
9153
9345
  constructor() {
9154
9346
  this._snackBar = inject(MatSnackBar);
9347
+ this.changeDetectorRef = inject(ChangeDetectorRef);
9155
9348
  this.files = [];
9156
9349
  this.uploadCallback = input.required(/* @ts-ignore */
9157
9350
  ...(ngDevMode ? [{ debugName: "uploadCallback" }] : /* istanbul ignore next */ []));
@@ -9225,6 +9418,8 @@ class SmartfileuploaderComponent {
9225
9418
  if (this.autoUpload() && this.errors.length == 0) {
9226
9419
  this.uploadFile();
9227
9420
  }
9421
+ // A Capture Dialog hands its file over outside any template event of this view.
9422
+ this.changeDetectorRef.markForCheck();
9228
9423
  }
9229
9424
  getFile(event) {
9230
9425
  if (event.target.files && event.target.files.length) {
@@ -9334,11 +9529,11 @@ class SmartfileuploaderComponent {
9334
9529
  };
9335
9530
  }
9336
9531
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: SmartfileuploaderComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
9337
- 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{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"] }] }); }
9532
+ 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"] }] }); }
9338
9533
  }
9339
9534
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: SmartfileuploaderComponent, decorators: [{
9340
9535
  type: Component,
9341
- 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{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"] }]
9536
+ 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"] }]
9342
9537
  }], propDecorators: { fileInput: [{
9343
9538
  type: ViewChild,
9344
9539
  args: ['fileInput']
@@ -9346,8 +9541,658 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImpor
9346
9541
  type: Input
9347
9542
  }], 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 }] }] } });
9348
9543
 
9544
+ /** The camera of one capture view: the stream, the recorder and the capture kept for a decision.
9545
+ * The view calls release() when it closes. */
9546
+ class CameraDevice {
9547
+ constructor(options) {
9548
+ this.options = options;
9549
+ /** A signal, because it is set after an `await`, outside the change detection of the click. */
9550
+ this.isCameraActive = signal(false, /* @ts-ignore */
9551
+ ...(ngDevMode ? [{ debugName: "isCameraActive" }] : /* istanbul ignore next */ []));
9552
+ /** Set when the browser gives no camera. */
9553
+ this.cameraError = signal(false, /* @ts-ignore */
9554
+ ...(ngDevMode ? [{ debugName: "cameraError" }] : /* istanbul ignore next */ []));
9555
+ /** True while a camera request runs. */
9556
+ this.isCameraStarting = signal(false, /* @ts-ignore */
9557
+ ...(ngDevMode ? [{ debugName: "isCameraStarting" }] : /* istanbul ignore next */ []));
9558
+ this.isRecording = signal(false, /* @ts-ignore */
9559
+ ...(ngDevMode ? [{ debugName: "isRecording" }] : /* istanbul ignore next */ []));
9560
+ this.isCaptured = signal(false, /* @ts-ignore */
9561
+ ...(ngDevMode ? [{ debugName: "isCaptured" }] : /* istanbul ignore next */ []));
9562
+ /** A capture kept for retake or use under the CONFIRM handover. */
9563
+ this.keptFiles = signal(undefined, /* @ts-ignore */
9564
+ ...(ngDevMode ? [{ debugName: "keptFiles" }] : /* istanbul ignore next */ []));
9565
+ /** The playback address of a kept video. */
9566
+ this.keptVideoUrl = signal(undefined, /* @ts-ignore */
9567
+ ...(ngDevMode ? [{ debugName: "keptVideoUrl" }] : /* istanbul ignore next */ []));
9568
+ /** The milliseconds of the running video, kept as the length of the stopped one. */
9569
+ this.recordingTime = signal(0, /* @ts-ignore */
9570
+ ...(ngDevMode ? [{ debugName: "recordingTime" }] : /* istanbul ignore next */ []));
9571
+ /** True while a host may not lose the capture by accident: a device is asked for or runs, or
9572
+ * a capture waits for confirmation. */
9573
+ this.isCaptureUnderWay = computed(() => this.isCameraStarting() ||
9574
+ this.isCameraActive() ||
9575
+ this.isRecording() ||
9576
+ !!this.keptFiles(), /* @ts-ignore */
9577
+ ...(ngDevMode ? [{ debugName: "isCaptureUnderWay" }] : /* istanbul ignore next */ []));
9578
+ this.recordingStartTime = 0;
9579
+ /** Advanced by stopCamera(), so a stream the browser gives after a close is stopped at once. */
9580
+ this.cameraRequest = 0;
9581
+ }
9582
+ async startCamera() {
9583
+ this.cameraError.set(false);
9584
+ this.isCameraStarting.set(true);
9585
+ const request = ++this.cameraRequest;
9586
+ let stream;
9587
+ try {
9588
+ stream = await this.openCameraStream();
9589
+ }
9590
+ catch {
9591
+ if (request === this.cameraRequest) {
9592
+ this.isCameraStarting.set(false);
9593
+ this.cameraError.set(true);
9594
+ }
9595
+ return;
9596
+ }
9597
+ if (request !== this.cameraRequest) {
9598
+ stream.getTracks().forEach((track) => track.stop());
9599
+ return;
9600
+ }
9601
+ this.stream = stream;
9602
+ const video = this.options.getVideoElement();
9603
+ video.srcObject = this.stream;
9604
+ video.play();
9605
+ this.isCameraStarting.set(false);
9606
+ this.isCameraActive.set(true);
9607
+ this.isCaptured.set(false);
9608
+ }
9609
+ /** A video asks for the sound too; a desktop without a microphone records a silent one. */
9610
+ async openCameraStream() {
9611
+ if (this.options.mode() === 'VIDEO') {
9612
+ try {
9613
+ return await navigator.mediaDevices.getUserMedia({ video: true, audio: true });
9614
+ }
9615
+ catch {
9616
+ // No microphone answers: the camera alone is asked below.
9617
+ }
9618
+ }
9619
+ return navigator.mediaDevices.getUserMedia({ video: true });
9620
+ }
9621
+ /** Closing the camera mid-recording discards the recording. */
9622
+ stopCamera() {
9623
+ this.cameraRequest++;
9624
+ this.isCameraStarting.set(false);
9625
+ clearInterval(this.recordingClock);
9626
+ if (this.recorder) {
9627
+ this.recorder.onstop = null;
9628
+ this.recorder.stop();
9629
+ this.recorder = undefined;
9630
+ }
9631
+ this.isRecording.set(false);
9632
+ this.stream?.getTracks().forEach((track) => track.stop());
9633
+ this.stream = undefined;
9634
+ this.isCameraActive.set(false);
9635
+ }
9636
+ toggleCamera() {
9637
+ if (this.isCameraActive()) {
9638
+ this.stopCamera();
9639
+ }
9640
+ else {
9641
+ this.startCamera();
9642
+ }
9643
+ }
9644
+ async capturePhoto() {
9645
+ const video = this.options.getVideoElement();
9646
+ const canvas = this.options.getCanvasElement();
9647
+ const context = canvas.getContext('2d');
9648
+ if (!context)
9649
+ return;
9650
+ canvas.width = video.videoWidth;
9651
+ canvas.height = video.videoHeight;
9652
+ context.drawImage(video, 0, 0);
9653
+ const blob = await new Promise((resolve) => canvas.toBlob(resolve, 'image/jpeg', 0.9));
9654
+ if (!blob)
9655
+ return;
9656
+ let file = new File([blob], `photo_${Date.now()}.jpg`, { type: 'image/jpeg' });
9657
+ const maxFileSize = this.options.maxFileSize();
9658
+ if (maxFileSize && file.size > maxFileSize) {
9659
+ file = await UploadWidgetUtils.compressImageFile(file, maxFileSize);
9660
+ }
9661
+ this.handOverCapture([file]);
9662
+ this.isCaptured.set(true);
9663
+ this.stopCamera();
9664
+ }
9665
+ /** A browser that records neither webm nor mp4 refuses the recorder: the camera error shows. */
9666
+ startRecording() {
9667
+ let recorder;
9668
+ try {
9669
+ recorder = new MediaRecorder(this.stream, {
9670
+ mimeType: UploadWidgetUtils.chooseRecorderMimeType('video'),
9671
+ });
9672
+ }
9673
+ catch {
9674
+ this.cameraError.set(true);
9675
+ return;
9676
+ }
9677
+ const chunks = [];
9678
+ recorder.ondataavailable = (event) => chunks.push(event.data);
9679
+ recorder.onstop = () => {
9680
+ const blob = new Blob(chunks, { type: recorder.mimeType });
9681
+ this.recorder = undefined;
9682
+ this.updateRecordingTime();
9683
+ this.handOverCapture([UploadWidgetUtils.createRecordingFile(blob, 'video', 'video/mp4')]);
9684
+ this.stopCamera();
9685
+ };
9686
+ recorder.start();
9687
+ this.recorder = recorder;
9688
+ this.isRecording.set(true);
9689
+ this.recordingStartTime = Date.now();
9690
+ this.recordingTime.set(0);
9691
+ this.recordingClock = setInterval(() => this.updateRecordingTime(), 1000);
9692
+ }
9693
+ updateRecordingTime() {
9694
+ this.recordingTime.set(Date.now() - this.recordingStartTime);
9695
+ }
9696
+ /** The recording state holds until onstop hands the video over and stops the camera. */
9697
+ stopRecording() {
9698
+ this.recorder?.stop();
9699
+ }
9700
+ handOverCapture(files) {
9701
+ if (this.options.handover() === CaptureHandover.IMMEDIATE) {
9702
+ this.options.handOver(files);
9703
+ return;
9704
+ }
9705
+ this.keptFiles.set(files);
9706
+ if (this.options.mode() === 'VIDEO') {
9707
+ this.keptVideoUrl.set(URL.createObjectURL(files[0]));
9708
+ }
9709
+ }
9710
+ retakeCapture() {
9711
+ this.discardKeptCapture();
9712
+ if (this.options.deviceStart() === CaptureDeviceStart.AUTO) {
9713
+ this.startCamera();
9714
+ }
9715
+ }
9716
+ useCapture() {
9717
+ this.options.handOver(this.keptFiles());
9718
+ this.discardKeptCapture();
9719
+ }
9720
+ /** Stops the camera and drops what it kept: the close of the view. */
9721
+ release() {
9722
+ this.stopCamera();
9723
+ this.discardKeptCapture();
9724
+ }
9725
+ discardKeptCapture() {
9726
+ const videoUrl = this.keptVideoUrl();
9727
+ if (videoUrl) {
9728
+ URL.revokeObjectURL(videoUrl);
9729
+ }
9730
+ this.keptVideoUrl.set(undefined);
9731
+ this.keptFiles.set(undefined);
9732
+ this.isCaptured.set(false);
9733
+ }
9734
+ }
9735
+
9736
+ /** The camera view of the Capture Dialog: a fixed stage with the switch and the captions inside
9737
+ * it, and the buttons in actionButtonsTemplate for the dialog's footer. */
9738
+ class CaptureCameraViewComponent {
9739
+ constructor() {
9740
+ this.injector = inject(Injector);
9741
+ this.session = inject(SmartSessionService);
9742
+ this.video = viewChild.required('video', /* @ts-ignore */
9743
+ ...(ngDevMode ? [{ debugName: "video" }] : /* istanbul ignore next */ []));
9744
+ this.canvas = viewChild.required('canvas', /* @ts-ignore */
9745
+ ...(ngDevMode ? [{ debugName: "canvas" }] : /* istanbul ignore next */ []));
9746
+ this.actionButtonsTemplate = viewChild('actionButtons', /* @ts-ignore */
9747
+ ...(ngDevMode ? [{ debugName: "actionButtonsTemplate" }] : /* istanbul ignore next */ []));
9748
+ this.mode = input.required(/* @ts-ignore */
9749
+ ...(ngDevMode ? [{ debugName: "mode" }] : /* istanbul ignore next */ []));
9750
+ this.deviceStart = input.required(/* @ts-ignore */
9751
+ ...(ngDevMode ? [{ debugName: "deviceStart" }] : /* istanbul ignore next */ []));
9752
+ this.handover = input.required(/* @ts-ignore */
9753
+ ...(ngDevMode ? [{ debugName: "handover" }] : /* istanbul ignore next */ []));
9754
+ this.texts = input.required(/* @ts-ignore */
9755
+ ...(ngDevMode ? [{ debugName: "texts" }] : /* istanbul ignore next */ []));
9756
+ this.maxFileSize = input(/* @ts-ignore */
9757
+ ...(ngDevMode ? [undefined, { debugName: "maxFileSize" }] : /* istanbul ignore next */ []));
9758
+ this.captured = output();
9759
+ this.device = new CameraDevice({
9760
+ mode: this.mode,
9761
+ deviceStart: this.deviceStart,
9762
+ handover: this.handover,
9763
+ maxFileSize: this.maxFileSize,
9764
+ getVideoElement: () => this.video().nativeElement,
9765
+ getCanvasElement: () => this.canvas().nativeElement,
9766
+ handOver: (files) => this.captured.emit(files),
9767
+ });
9768
+ /** The timer on the stage of a running video. */
9769
+ this.recordingCaption = computed(() => UploadWidgetUtils.formatDuration(this.device.recordingTime()), /* @ts-ignore */
9770
+ ...(ngDevMode ? [{ debugName: "recordingCaption" }] : /* istanbul ignore next */ []));
9771
+ /** The kept capture's line on the stage. */
9772
+ this.capturedCaption = computed(() => {
9773
+ const files = this.device.keptFiles();
9774
+ if (!files) {
9775
+ return undefined;
9776
+ }
9777
+ const size = UploadWidgetUtils.formatCapturedSize(files[0].size, this.session.sessionInfoData?.locale);
9778
+ if (this.mode() === 'PHOTO') {
9779
+ return `${this.texts().capturedPhoto} · ${size}`;
9780
+ }
9781
+ const duration = UploadWidgetUtils.formatDuration(this.device.recordingTime());
9782
+ return `${this.texts().capturedVideo} · ${duration} · ${size}`;
9783
+ }, /* @ts-ignore */
9784
+ ...(ngDevMode ? [{ debugName: "capturedCaption" }] : /* istanbul ignore next */ []));
9785
+ /** The switch shows while the camera is off, so a second shot stays possible. */
9786
+ this.isCameraSwitchShown = computed(() => !this.device.keptFiles() &&
9787
+ !(this.device.isCameraActive() || this.device.isCameraStarting()), /* @ts-ignore */
9788
+ ...(ngDevMode ? [{ debugName: "isCameraSwitchShown" }] : /* istanbul ignore next */ []));
9789
+ this.isSwitchOnHintShown = computed(() => this.isCameraSwitchShown() && !this.device.isCaptured(), /* @ts-ignore */
9790
+ ...(ngDevMode ? [{ debugName: "isSwitchOnHintShown" }] : /* istanbul ignore next */ []));
9791
+ this.switchOnButton = computed(() => UploadWidgetUtils.createCaptureButton(this.texts().cameraOn, 'power_settings_new', 'primary', UiActionButtonType.NORMAL), /* @ts-ignore */
9792
+ ...(ngDevMode ? [{ debugName: "switchOnButton" }] : /* istanbul ignore next */ []));
9793
+ this.captureButton = computed(() => UploadWidgetUtils.createTakePictureButton(this.texts()), /* @ts-ignore */
9794
+ ...(ngDevMode ? [{ debugName: "captureButton" }] : /* istanbul ignore next */ []));
9795
+ this.startRecordingButton = computed(() => UploadWidgetUtils.createStartRecordingButton(this.texts()), /* @ts-ignore */
9796
+ ...(ngDevMode ? [{ debugName: "startRecordingButton" }] : /* istanbul ignore next */ []));
9797
+ this.stopRecordingButton = computed(() => UploadWidgetUtils.createStopRecordingButton(this.texts(), 'warn'), /* @ts-ignore */
9798
+ ...(ngDevMode ? [{ debugName: "stopRecordingButton" }] : /* istanbul ignore next */ []));
9799
+ this.retakeButton = computed(() => UploadWidgetUtils.createRetakeButton(this.texts(), UiActionButtonType.NORMAL), /* @ts-ignore */
9800
+ ...(ngDevMode ? [{ debugName: "retakeButton" }] : /* istanbul ignore next */ []));
9801
+ this.useButton = computed(() => UploadWidgetUtils.createUseButton(this.texts()), /* @ts-ignore */
9802
+ ...(ngDevMode ? [{ debugName: "useButton" }] : /* istanbul ignore next */ []));
9803
+ }
9804
+ ngAfterViewInit() {
9805
+ if (this.deviceStart() === CaptureDeviceStart.AUTO) {
9806
+ afterNextRender(() => this.device.startCamera(), { injector: this.injector });
9807
+ }
9808
+ }
9809
+ ngOnDestroy() {
9810
+ this.device.release();
9811
+ }
9812
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: CaptureCameraViewComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
9813
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.8", type: CaptureCameraViewComponent, isStandalone: true, selector: "smart-capture-camera-view", inputs: { mode: { classPropertyName: "mode", publicName: "mode", isSignal: true, isRequired: true, transformFunction: null }, deviceStart: { classPropertyName: "deviceStart", publicName: "deviceStart", isSignal: true, isRequired: true, transformFunction: null }, handover: { classPropertyName: "handover", publicName: "handover", isSignal: true, isRequired: true, transformFunction: null }, texts: { classPropertyName: "texts", publicName: "texts", isSignal: true, isRequired: true, transformFunction: null }, maxFileSize: { classPropertyName: "maxFileSize", publicName: "maxFileSize", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { captured: "captured" }, viewQueries: [{ propertyName: "video", first: true, predicate: ["video"], descendants: true, isSignal: true }, { propertyName: "canvas", first: true, predicate: ["canvas"], descendants: true, isSignal: true }, { propertyName: "actionButtonsTemplate", first: true, predicate: ["actionButtons"], descendants: true, isSignal: true }], ngImport: i0, template: "<div class=\"cameraStage\">\n <video #video autoplay [muted]=\"true\" [hidden]=\"device.isCaptured() || !device.isCameraActive()\"></video>\n <canvas #canvas [hidden]=\"!device.isCaptured() || device.isCameraActive()\"></canvas>\n @if (device.keptVideoUrl(); as videoUrl) {\n <video controls [src]=\"videoUrl\"></video>\n }\n @if (device.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 (device.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 (isCameraSwitchShown()) {\n <ui-action-button\n class=\"switchOnButton\"\n [descriptor]=\"switchOnButton()\"\n (actionClick)=\"device.startCamera()\"\n ></ui-action-button>\n }\n</div>\n\n<ng-template #actionButtons>\n @if (device.keptFiles()) {\n <ui-action-button\n class=\"retakeButton\"\n [descriptor]=\"retakeButton()\"\n (actionClick)=\"device.retakeCapture()\"\n ></ui-action-button>\n <ui-action-button\n class=\"useButton\"\n [descriptor]=\"useButton()\"\n (actionClick)=\"device.useCapture()\"\n ></ui-action-button>\n } @else if (mode() === 'VIDEO') {\n @if (device.isRecording()) {\n <ui-action-button\n class=\"stopRecordingButton\"\n [descriptor]=\"stopRecordingButton()\"\n (actionClick)=\"device.stopRecording()\"\n ></ui-action-button>\n } @else {\n <ui-action-button\n class=\"startRecordingButton\"\n [disabled]=\"!device.isCameraActive()\"\n [descriptor]=\"startRecordingButton()\"\n (actionClick)=\"device.startRecording()\"\n ></ui-action-button>\n }\n } @else {\n <ui-action-button\n class=\"capturePhotoButton\"\n [disabled]=\"!device.isCameraActive()\"\n [descriptor]=\"captureButton()\"\n (actionClick)=\"device.capturePhoto()\"\n ></ui-action-button>\n }\n</ng-template>\n", styles: [":host{display:block;width:100%}.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))}.cameraStage video,.cameraStage canvas{position:absolute;inset:0;width:100%;height:100%;object-fit:contain}.cameraStage .switchOnButton ::ng-deep button{background:var(--color-surface)}.cameraStage .message{margin:0;text-align:center;color:inherit}.cameraStage .cameraErrorMessage{position:relative;color:color-mix(in srgb,var(--warn-color) 65%,white)}.cameraStage .recordingMessage,.cameraStage .capturedCaption{position:absolute;left:.5rem;top:.5rem;padding:.125rem .5rem;border-radius:.5rem;font-size:.75rem;font-weight:500}.cameraStage .recordingMessage{background:color-mix(in srgb,var(--stage-background) 67%,transparent);font-family:monospace}.cameraStage .capturedCaption{background:color-mix(in srgb,var(--success-color) 50%,black)}.cameraStage .recordingMessage:before{content:\"\";display:inline-block;width:.5rem;height:.5rem;margin-right:.3125rem;border-radius:50%;background:var(--warn-color)}\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"] }] }); }
9814
+ }
9815
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: CaptureCameraViewComponent, decorators: [{
9816
+ type: Component,
9817
+ args: [{ selector: 'smart-capture-camera-view', imports: [UiActionButtonComponent], template: "<div class=\"cameraStage\">\n <video #video autoplay [muted]=\"true\" [hidden]=\"device.isCaptured() || !device.isCameraActive()\"></video>\n <canvas #canvas [hidden]=\"!device.isCaptured() || device.isCameraActive()\"></canvas>\n @if (device.keptVideoUrl(); as videoUrl) {\n <video controls [src]=\"videoUrl\"></video>\n }\n @if (device.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 (device.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 (isCameraSwitchShown()) {\n <ui-action-button\n class=\"switchOnButton\"\n [descriptor]=\"switchOnButton()\"\n (actionClick)=\"device.startCamera()\"\n ></ui-action-button>\n }\n</div>\n\n<ng-template #actionButtons>\n @if (device.keptFiles()) {\n <ui-action-button\n class=\"retakeButton\"\n [descriptor]=\"retakeButton()\"\n (actionClick)=\"device.retakeCapture()\"\n ></ui-action-button>\n <ui-action-button\n class=\"useButton\"\n [descriptor]=\"useButton()\"\n (actionClick)=\"device.useCapture()\"\n ></ui-action-button>\n } @else if (mode() === 'VIDEO') {\n @if (device.isRecording()) {\n <ui-action-button\n class=\"stopRecordingButton\"\n [descriptor]=\"stopRecordingButton()\"\n (actionClick)=\"device.stopRecording()\"\n ></ui-action-button>\n } @else {\n <ui-action-button\n class=\"startRecordingButton\"\n [disabled]=\"!device.isCameraActive()\"\n [descriptor]=\"startRecordingButton()\"\n (actionClick)=\"device.startRecording()\"\n ></ui-action-button>\n }\n } @else {\n <ui-action-button\n class=\"capturePhotoButton\"\n [disabled]=\"!device.isCameraActive()\"\n [descriptor]=\"captureButton()\"\n (actionClick)=\"device.capturePhoto()\"\n ></ui-action-button>\n }\n</ng-template>\n", styles: [":host{display:block;width:100%}.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))}.cameraStage video,.cameraStage canvas{position:absolute;inset:0;width:100%;height:100%;object-fit:contain}.cameraStage .switchOnButton ::ng-deep button{background:var(--color-surface)}.cameraStage .message{margin:0;text-align:center;color:inherit}.cameraStage .cameraErrorMessage{position:relative;color:color-mix(in srgb,var(--warn-color) 65%,white)}.cameraStage .recordingMessage,.cameraStage .capturedCaption{position:absolute;left:.5rem;top:.5rem;padding:.125rem .5rem;border-radius:.5rem;font-size:.75rem;font-weight:500}.cameraStage .recordingMessage{background:color-mix(in srgb,var(--stage-background) 67%,transparent);font-family:monospace}.cameraStage .capturedCaption{background:color-mix(in srgb,var(--success-color) 50%,black)}.cameraStage .recordingMessage:before{content:\"\";display:inline-block;width:.5rem;height:.5rem;margin-right:.3125rem;border-radius:50%;background:var(--warn-color)}\n"] }]
9818
+ }], propDecorators: { video: [{ type: i0.ViewChild, args: ['video', { isSignal: true }] }], canvas: [{ type: i0.ViewChild, args: ['canvas', { isSignal: true }] }], actionButtonsTemplate: [{ type: i0.ViewChild, args: ['actionButtons', { isSignal: true }] }], mode: [{ type: i0.Input, args: [{ isSignal: true, alias: "mode", required: true }] }], deviceStart: [{ type: i0.Input, args: [{ isSignal: true, alias: "deviceStart", required: true }] }], handover: [{ type: i0.Input, args: [{ isSignal: true, alias: "handover", required: true }] }], texts: [{ type: i0.Input, args: [{ isSignal: true, alias: "texts", required: true }] }], maxFileSize: [{ type: i0.Input, args: [{ isSignal: true, alias: "maxFileSize", required: false }] }], captured: [{ type: i0.Output, args: ["captured"] }] } });
9819
+
9820
+ /** The microphone of one capture view: the record plugin, its start and the stopped recording.
9821
+ * The view calls release() when it closes. */
9822
+ class MicrophoneDevice {
9823
+ constructor(options) {
9824
+ this.options = options;
9825
+ this.isRecording = signal(false, /* @ts-ignore */
9826
+ ...(ngDevMode ? [{ debugName: "isRecording" }] : /* istanbul ignore next */ []));
9827
+ this.recordedBlob = signal(null, /* @ts-ignore */
9828
+ ...(ngDevMode ? [{ debugName: "recordedBlob" }] : /* istanbul ignore next */ []));
9829
+ this.time = signal('00:00', /* @ts-ignore */
9830
+ ...(ngDevMode ? [{ debugName: "time" }] : /* istanbul ignore next */ []));
9831
+ this.isPlaying = signal(false, /* @ts-ignore */
9832
+ ...(ngDevMode ? [{ debugName: "isPlaying" }] : /* istanbul ignore next */ []));
9833
+ /** Set when the browser gives no microphone, until the next start. */
9834
+ this.microphoneError = signal(false, /* @ts-ignore */
9835
+ ...(ngDevMode ? [{ debugName: "microphoneError" }] : /* istanbul ignore next */ []));
9836
+ this.isWaveformOpen = signal(false, /* @ts-ignore */
9837
+ ...(ngDevMode ? [{ debugName: "isWaveformOpen" }] : /* istanbul ignore next */ []));
9838
+ /** True while a host may not lose the capture by accident: the recording runs, its microphone
9839
+ * request included, or waits for the save. */
9840
+ this.isCaptureUnderWay = computed(() => this.isRecording() || !!this.recordedBlob(), /* @ts-ignore */
9841
+ ...(ngDevMode ? [{ debugName: "isCaptureUnderWay" }] : /* istanbul ignore next */ []));
9842
+ /** True while the view shows the waveform band. */
9843
+ this.isWaveformShown = computed(() => this.isWaveformOpen() || this.isRecording() || !!this.recordedBlob(), /* @ts-ignore */
9844
+ ...(ngDevMode ? [{ debugName: "isWaveformShown" }] : /* istanbul ignore next */ []));
9845
+ }
9846
+ async initializeWaveSurferIfNeeded() {
9847
+ const waveform = this.options.getWaveformElement();
9848
+ if (this.wavesurfer || !waveform) {
9849
+ return;
9850
+ }
9851
+ await new Promise((resolve) => requestAnimationFrame(() => resolve()));
9852
+ if (!this.pendingStart) {
9853
+ return;
9854
+ }
9855
+ waveform.style.padding = '0.1rem';
9856
+ this.initWaveSurfer(waveform);
9857
+ }
9858
+ initWaveSurfer(waveform) {
9859
+ const primaryColor = getComputedStyle(this.options.getThemeElement())
9860
+ .getPropertyValue('--primary-color')
9861
+ .trim();
9862
+ const el = this.options.getButtonElement().querySelector('.sb4-primary');
9863
+ const remInPx = parseFloat(getComputedStyle(document.documentElement).fontSize);
9864
+ const height = (el?.getBoundingClientRect().height ?? 0) - remInPx * 0.1;
9865
+ this.wavesurfer = WaveSurfer.create({
9866
+ container: waveform,
9867
+ waveColor: primaryColor,
9868
+ progressColor: this.addAlphaToColor(primaryColor, 0.5),
9869
+ height: height,
9870
+ barWidth: 5,
9871
+ barGap: 1,
9872
+ barRadius: 5,
9873
+ });
9874
+ this.wavesurfer.setVolume(1);
9875
+ const mimeType = UploadWidgetUtils.chooseRecorderMimeType('audio');
9876
+ const record = this.wavesurfer.registerPlugin(RecordPlugin.create({
9877
+ renderRecordedAudio: true,
9878
+ scrollingWaveform: true,
9879
+ continuousWaveform: false,
9880
+ continuousWaveformDuration: 15,
9881
+ mimeType,
9882
+ }));
9883
+ this.record = record;
9884
+ this.wavesurfer.on('dblclick', () => {
9885
+ this.options.ngZone.run(() => {
9886
+ this.wavesurfer?.play();
9887
+ this.isPlaying.set(true);
9888
+ });
9889
+ });
9890
+ this.record.on('record-end', (blob) => {
9891
+ if (record !== this.record) {
9892
+ return;
9893
+ }
9894
+ this.options.ngZone.run(() => {
9895
+ this.recordedBlob.set(blob);
9896
+ this.isRecording.set(false);
9897
+ });
9898
+ if (this.options.handover() === CaptureHandover.IMMEDIATE) {
9899
+ this.saveRecording();
9900
+ }
9901
+ });
9902
+ this.record.on('record-progress', (time) => {
9903
+ this.options.ngZone.run(() => {
9904
+ this.time.set(UploadWidgetUtils.formatDuration(time));
9905
+ });
9906
+ });
9907
+ this.wavesurfer.on('finish', () => {
9908
+ this.options.ngZone.run(() => {
9909
+ this.isPlaying.set(false);
9910
+ });
9911
+ });
9912
+ }
9913
+ /** Starts from a click or on open: a refused microphone shows its error line, no rejection. */
9914
+ requestRecording() {
9915
+ this.startRecording().catch(() => { });
9916
+ }
9917
+ async startRecording() {
9918
+ if (this.isRecording())
9919
+ return;
9920
+ const start = {};
9921
+ this.pendingStart = start;
9922
+ // Initialize first (still within the gesture tick on most browsers)
9923
+ await this.initializeWaveSurferIfNeeded();
9924
+ if (this.pendingStart !== start) {
9925
+ return;
9926
+ }
9927
+ const record = this.record;
9928
+ if (!record)
9929
+ throw new Error('Voice recorder plugin is not initialized.');
9930
+ this.options.ngZone.run(() => {
9931
+ this.microphoneError.set(false);
9932
+ this.isWaveformOpen.set(true);
9933
+ this.isRecording.set(true);
9934
+ this.isPlaying.set(false);
9935
+ this.recordedBlob.set(null);
9936
+ this.time.set('00:00');
9937
+ });
9938
+ try {
9939
+ // Force native mic permission first so it works with WKWebView
9940
+ const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
9941
+ stream.getTracks().forEach((track) => track.stop());
9942
+ if (this.pendingStart !== start) {
9943
+ return;
9944
+ }
9945
+ await record.startRecording();
9946
+ if (this.pendingStart !== start) {
9947
+ record.stopRecording();
9948
+ record.stopMic();
9949
+ return;
9950
+ }
9951
+ this.pendingStart = undefined;
9952
+ }
9953
+ catch (error) {
9954
+ if (this.pendingStart !== start) {
9955
+ return;
9956
+ }
9957
+ console.error('Mic failed', error);
9958
+ this.pendingStart = undefined;
9959
+ this.options.ngZone.run(() => {
9960
+ this.isRecording.set(false);
9961
+ this.isWaveformOpen.set(!!this.recordedBlob());
9962
+ this.microphoneError.set(true);
9963
+ });
9964
+ throw error;
9965
+ }
9966
+ }
9967
+ stopRecording() {
9968
+ if (this.pendingStart) {
9969
+ this.cancelPendingStart();
9970
+ }
9971
+ else if (this.record && this.isRecording()) {
9972
+ this.record.stopRecording();
9973
+ this.options.ngZone.run(() => {
9974
+ this.isRecording.set(false);
9975
+ });
9976
+ }
9977
+ }
9978
+ /** A stop before the recording runs has nothing to keep: the strip goes back to idle. */
9979
+ cancelPendingStart() {
9980
+ this.pendingStart = undefined;
9981
+ this.destroyRecorder();
9982
+ this.options.ngZone.run(() => {
9983
+ this.isRecording.set(false);
9984
+ this.isWaveformOpen.set(false);
9985
+ });
9986
+ }
9987
+ /** The plugin's destroy alone closes a running microphone's AudioContext twice, which rejects;
9988
+ * after the stop and the stopMic it has nothing left to close. */
9989
+ destroyRecorder() {
9990
+ const wavesurfer = this.wavesurfer;
9991
+ const record = this.record;
9992
+ this.wavesurfer = undefined;
9993
+ this.record = undefined;
9994
+ record?.stopRecording();
9995
+ record?.stopMic();
9996
+ wavesurfer?.destroy();
9997
+ }
9998
+ saveRecording() {
9999
+ const recordedBlob = this.recordedBlob();
10000
+ if (recordedBlob) {
10001
+ const file = UploadWidgetUtils.createRecordingFile(recordedBlob, 'recording', 'audio/mp4');
10002
+ this.options.handOver(file);
10003
+ // Destroy completely so next recording initializes fresh
10004
+ this.destroyRecorder();
10005
+ this.options.ngZone.run(() => {
10006
+ this.recordedBlob.set(null);
10007
+ this.time.set('00:00');
10008
+ this.isPlaying.set(false);
10009
+ this.isWaveformOpen.set(false);
10010
+ });
10011
+ }
10012
+ }
10013
+ replayRecording() {
10014
+ if (this.recordedBlob()) {
10015
+ this.options.ngZone.run(() => {
10016
+ this.isPlaying.update((isPlaying) => !isPlaying);
10017
+ this.wavesurfer?.playPause();
10018
+ });
10019
+ }
10020
+ }
10021
+ /** Cancels a start under way and closes the microphone: the close of the view. */
10022
+ release() {
10023
+ this.pendingStart = undefined;
10024
+ this.destroyRecorder();
10025
+ }
10026
+ addAlphaToColor(hexColor, alpha) {
10027
+ if (/^#([a-f\d])([a-f\d])([a-f\d])$/i.test(hexColor)) {
10028
+ hexColor = hexColor.replace(/^#([a-f\d])([a-f\d])([a-f\d])$/i, (_, r, g, b) => `#${r}${r}${g}${g}${b}${b}`);
10029
+ }
10030
+ const hexMatch = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hexColor);
10031
+ if (hexMatch) {
10032
+ const [, r, g, b] = hexMatch;
10033
+ return `rgba(${parseInt(r, 16)}, ${parseInt(g, 16)}, ${parseInt(b, 16)}, ${alpha})`;
10034
+ }
10035
+ const ctx = document.createElement('canvas').getContext('2d');
10036
+ if (ctx) {
10037
+ ctx.fillStyle = hexColor;
10038
+ const parsed = ctx.fillStyle;
10039
+ const rgbMatch = /^rgb\((\d+),\s*(\d+),\s*(\d+)\)$/.exec(parsed);
10040
+ if (rgbMatch) {
10041
+ const [, r, g, b] = rgbMatch;
10042
+ return `rgba(${r}, ${g}, ${b}, ${alpha})`;
10043
+ }
10044
+ }
10045
+ return hexColor;
10046
+ }
10047
+ }
10048
+
10049
+ /** The recorder view of the Capture Dialog: the strip in the theme's colours, with its hints, and
10050
+ * the stop and save buttons in actionButtonsTemplate for the dialog's footer. */
10051
+ class CaptureRecorderViewComponent {
10052
+ constructor() {
10053
+ this.actionButtonsTemplate = viewChild('actionButtons', /* @ts-ignore */
10054
+ ...(ngDevMode ? [{ debugName: "actionButtonsTemplate" }] : /* istanbul ignore next */ []));
10055
+ this.deviceStart = input.required(/* @ts-ignore */
10056
+ ...(ngDevMode ? [{ debugName: "deviceStart" }] : /* istanbul ignore next */ []));
10057
+ this.handover = input.required(/* @ts-ignore */
10058
+ ...(ngDevMode ? [{ debugName: "handover" }] : /* istanbul ignore next */ []));
10059
+ this.texts = input.required(/* @ts-ignore */
10060
+ ...(ngDevMode ? [{ debugName: "texts" }] : /* istanbul ignore next */ []));
10061
+ this.recordingSaved = output();
10062
+ this.device = new MicrophoneDevice({
10063
+ handover: this.handover,
10064
+ ngZone: inject(NgZone),
10065
+ getWaveformElement: () => this.waveformRef?.nativeElement,
10066
+ getButtonElement: () => this.mainButton.nativeElement,
10067
+ // A host theme sets the token on the body, which the element inherits and html does not see.
10068
+ getThemeElement: () => this.waveformRef.nativeElement,
10069
+ handOver: (file) => this.recordingSaved.emit(file),
10070
+ });
10071
+ this.micButton = this.createIconButton('microphone', 'primary');
10072
+ /** Only a theme colour's name paints the icon. */
10073
+ this.stopButton = {
10074
+ ...this.createIconButton('stop-circle', 'warn'),
10075
+ iconColor: 'warn',
10076
+ };
10077
+ this.playButton = this.createIconButton('play_circle', 'primary');
10078
+ this.stopPlaybackButton = this.createIconButton('stop_circle', 'warn');
10079
+ this.saveButton = computed(() => UploadWidgetUtils.createCaptureButton(this.texts().save, 'save'), /* @ts-ignore */
10080
+ ...(ngDevMode ? [{ debugName: "saveButton" }] : /* istanbul ignore next */ []));
10081
+ this.stopRecordingButton = computed(() => UploadWidgetUtils.createStopRecordingButton(this.texts(), 'warn'), /* @ts-ignore */
10082
+ ...(ngDevMode ? [{ debugName: "stopRecordingButton" }] : /* istanbul ignore next */ []));
10083
+ }
10084
+ ngAfterViewInit() {
10085
+ if (this.deviceStart() === CaptureDeviceStart.AUTO) {
10086
+ this.device.requestRecording();
10087
+ }
10088
+ }
10089
+ ngOnDestroy() {
10090
+ this.device.release();
10091
+ }
10092
+ createIconButton(icon, color) {
10093
+ return UploadWidgetUtils.createCaptureButton('', icon, color, UiActionButtonType.ICON);
10094
+ }
10095
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: CaptureRecorderViewComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
10096
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.8", type: CaptureRecorderViewComponent, isStandalone: true, selector: "smart-capture-recorder-view", inputs: { deviceStart: { classPropertyName: "deviceStart", publicName: "deviceStart", isSignal: true, isRequired: true, transformFunction: null }, handover: { classPropertyName: "handover", publicName: "handover", isSignal: true, isRequired: true, transformFunction: null }, texts: { classPropertyName: "texts", publicName: "texts", isSignal: true, isRequired: true, transformFunction: null } }, outputs: { recordingSaved: "recordingSaved" }, 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 direction-right\"\n [class.active]=\"device.isWaveformShown()\"\n [class.isRecording]=\"device.isRecording()\"\n [class.hasRecording]=\"device.recordedBlob()\"\n>\n <div class=\"recorderMain\">\n <div class=\"recorderToolbar\">\n @if (device.isRecording()) {\n <ui-action-button\n class=\"mainButton\"\n #mainButton\n [descriptor]=\"stopButton\"\n (actionClick)=\"device.stopRecording()\"\n ></ui-action-button>\n <div class=\"progress\">{{ device.time() }}</div>\n } @else {\n <ui-action-button\n class=\"mainButton\"\n #mainButton\n [descriptor]=\"micButton\"\n (actionClick)=\"device.requestRecording()\"\n ></ui-action-button>\n }\n\n @if (device.recordedBlob()) {\n <ui-action-button\n class=\"mainButton\"\n [descriptor]=\"device.isPlaying() ? stopPlaybackButton : playButton\"\n (actionClick)=\"device.replayRecording()\"\n ></ui-action-button>\n }\n </div>\n\n <div class=\"waveformBand\" [class.open]=\"device.isWaveformShown()\">\n <div class=\"waveformGrid\"></div>\n <div class=\"waveform\" #waveform></div>\n </div>\n </div>\n</div>\n@if (device.microphoneError()) {\n <p class=\"message\">{{ texts().microphoneError }}</p>\n <p class=\"hint\">{{ texts().microphoneRetryHint }}</p>\n}\n@if (device.recordedBlob()) {\n <p class=\"hint\">{{ texts().recordingHint }}</p>\n}\n\n<ng-template #actionButtons>\n @if (device.isRecording()) {\n <ui-action-button\n class=\"stopRecordingButton\"\n [descriptor]=\"stopRecordingButton()\"\n (actionClick)=\"device.stopRecording()\"\n ></ui-action-button>\n }\n @if (device.recordedBlob()) {\n <ui-action-button\n class=\"saveRecordingButton\"\n [descriptor]=\"saveButton()\"\n (actionClick)=\"device.saveRecording()\"\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}\n", ":host .message{color:var(--warn-color)}:host .hint{margin:.5rem 0 0;text-align:center;font-size:.75rem;color:var(--color-text-muted)}:host .progress{color:var(--color-text-muted)}:host .isRecording .progress:before{content:\"\";display:inline-block;width:.5rem;height:.5rem;margin-right:.3125rem;border-radius:50%;background:var(--warn-color)}:host .active ::ng-deep .recorderToolbar{border-color:var(--color-border)}:host .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 .recorderToolbar ::ng-deep .sb4-warn,:host .recorderToolbar ::ng-deep .sb4-primary,:host .recorderToolbar ::ng-deep ui-action-button{border:unset}:host .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"] }] }); }
10097
+ }
10098
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: CaptureRecorderViewComponent, decorators: [{
10099
+ type: Component,
10100
+ args: [{ selector: 'smart-capture-recorder-view', imports: [UiActionButtonComponent], template: "<div\n class=\"recorderContainer direction-right\"\n [class.active]=\"device.isWaveformShown()\"\n [class.isRecording]=\"device.isRecording()\"\n [class.hasRecording]=\"device.recordedBlob()\"\n>\n <div class=\"recorderMain\">\n <div class=\"recorderToolbar\">\n @if (device.isRecording()) {\n <ui-action-button\n class=\"mainButton\"\n #mainButton\n [descriptor]=\"stopButton\"\n (actionClick)=\"device.stopRecording()\"\n ></ui-action-button>\n <div class=\"progress\">{{ device.time() }}</div>\n } @else {\n <ui-action-button\n class=\"mainButton\"\n #mainButton\n [descriptor]=\"micButton\"\n (actionClick)=\"device.requestRecording()\"\n ></ui-action-button>\n }\n\n @if (device.recordedBlob()) {\n <ui-action-button\n class=\"mainButton\"\n [descriptor]=\"device.isPlaying() ? stopPlaybackButton : playButton\"\n (actionClick)=\"device.replayRecording()\"\n ></ui-action-button>\n }\n </div>\n\n <div class=\"waveformBand\" [class.open]=\"device.isWaveformShown()\">\n <div class=\"waveformGrid\"></div>\n <div class=\"waveform\" #waveform></div>\n </div>\n </div>\n</div>\n@if (device.microphoneError()) {\n <p class=\"message\">{{ texts().microphoneError }}</p>\n <p class=\"hint\">{{ texts().microphoneRetryHint }}</p>\n}\n@if (device.recordedBlob()) {\n <p class=\"hint\">{{ texts().recordingHint }}</p>\n}\n\n<ng-template #actionButtons>\n @if (device.isRecording()) {\n <ui-action-button\n class=\"stopRecordingButton\"\n [descriptor]=\"stopRecordingButton()\"\n (actionClick)=\"device.stopRecording()\"\n ></ui-action-button>\n }\n @if (device.recordedBlob()) {\n <ui-action-button\n class=\"saveRecordingButton\"\n [descriptor]=\"saveButton()\"\n (actionClick)=\"device.saveRecording()\"\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}\n", ":host .message{color:var(--warn-color)}:host .hint{margin:.5rem 0 0;text-align:center;font-size:.75rem;color:var(--color-text-muted)}:host .progress{color:var(--color-text-muted)}:host .isRecording .progress:before{content:\"\";display:inline-block;width:.5rem;height:.5rem;margin-right:.3125rem;border-radius:50%;background:var(--warn-color)}:host .active ::ng-deep .recorderToolbar{border-color:var(--color-border)}:host .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 .recorderToolbar ::ng-deep .sb4-warn,:host .recorderToolbar ::ng-deep .sb4-primary,:host .recorderToolbar ::ng-deep ui-action-button{border:unset}:host .mainButton ::ng-deep .sb4-warn{padding:.5rem .75rem}\n"] }]
10101
+ }], propDecorators: { waveformRef: [{
10102
+ type: ViewChild,
10103
+ args: ['waveform']
10104
+ }], mainButton: [{
10105
+ type: ViewChild,
10106
+ args: ['mainButton', { read: ElementRef }]
10107
+ }], actionButtonsTemplate: [{ type: i0.ViewChild, args: ['actionButtons', { isSignal: true }] }], deviceStart: [{ type: i0.Input, args: [{ isSignal: true, alias: "deviceStart", required: true }] }], handover: [{ type: i0.Input, args: [{ isSignal: true, alias: "handover", required: true }] }], texts: [{ type: i0.Input, args: [{ isSignal: true, alias: "texts", required: true }] }], recordingSaved: [{ type: i0.Output, args: ["recordingSaved"] }] } });
10108
+
10109
+ /** The Capture Dialog of a File Field: closes with the files of a use or a save, else empty. */
10110
+ class CaptureDialogComponent {
10111
+ constructor() {
10112
+ this.dialogRef = inject(MatDialogRef);
10113
+ this.data = inject(MAT_DIALOG_DATA);
10114
+ this.isMicrophone = this.data.source === FileUploaderSource.MICROPHONE;
10115
+ this.cameraMode = this.data.source === FileUploaderSource.CAMERA_VIDEO ? 'VIDEO' : 'PHOTO';
10116
+ this.cameraView = viewChild(CaptureCameraViewComponent, /* @ts-ignore */
10117
+ ...(ngDevMode ? [{ debugName: "cameraView" }] : /* istanbul ignore next */ []));
10118
+ this.recorderView = viewChild(CaptureRecorderViewComponent, /* @ts-ignore */
10119
+ ...(ngDevMode ? [{ debugName: "recorderView" }] : /* istanbul ignore next */ []));
10120
+ this.captureView = computed(() => this.cameraView() ?? this.recorderView(), /* @ts-ignore */
10121
+ ...(ngDevMode ? [{ debugName: "captureView" }] : /* istanbul ignore next */ []));
10122
+ /** The view's buttons go to the footer of the dialog. */
10123
+ this.actionButtons = computed(() => this.captureView()?.actionButtonsTemplate() ?? null, /* @ts-ignore */
10124
+ ...(ngDevMode ? [{ debugName: "actionButtons" }] : /* istanbul ignore next */ []));
10125
+ /** The recorder waits for the open: it reads the waveform's height from its rendered button. */
10126
+ this.isOpened = signal(false, /* @ts-ignore */
10127
+ ...(ngDevMode ? [{ debugName: "isOpened" }] : /* istanbul ignore next */ []));
10128
+ this.closeButton = {
10129
+ title: '',
10130
+ type: UiActionButtonType.ICON,
10131
+ icon: 'close',
10132
+ iconPosition: IconPosition.PRE,
10133
+ color: 'primary',
10134
+ };
10135
+ this.cancelButton = {
10136
+ title: this.data.texts.cancel,
10137
+ type: UiActionButtonType.FLAT,
10138
+ color: 'primary',
10139
+ };
10140
+ effect(() => {
10141
+ this.dialogRef.disableClose = this.captureView()?.device.isCaptureUnderWay() ?? false;
10142
+ });
10143
+ this.dialogRef.afterOpened().subscribe(() => this.isOpened.set(true));
10144
+ // disableClose stops the keyboard too, but only the backdrop is meant to wait for the capture.
10145
+ this.dialogRef.keydownEvents().subscribe((event) => {
10146
+ if (event.key === 'Escape' && this.dialogRef.disableClose) {
10147
+ this.discardCapture();
10148
+ }
10149
+ });
10150
+ }
10151
+ /** The close destroys the capture view, which stops the device. */
10152
+ discardCapture() {
10153
+ this.dialogRef.close();
10154
+ }
10155
+ handOverCapture(files) {
10156
+ this.dialogRef.close(files);
10157
+ }
10158
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: CaptureDialogComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
10159
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.8", type: CaptureDialogComponent, isStandalone: true, selector: "smart-capture-dialog", viewQueries: [{ propertyName: "cameraView", first: true, predicate: CaptureCameraViewComponent, descendants: true, isSignal: true }, { propertyName: "recorderView", first: true, predicate: CaptureRecorderViewComponent, 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 <smart-capture-recorder-view\n [deviceStart]=\"data.settings.deviceStart\"\n [handover]=\"data.settings.handover\"\n [texts]=\"data.texts\"\n (recordingSaved)=\"handOverCapture([$event])\"\n ></smart-capture-recorder-view>\n }\n </div>\n } @else {\n <smart-capture-camera-view\n [mode]=\"cameraMode\"\n [deviceStart]=\"data.settings.deviceStart\"\n [handover]=\"data.settings.handover\"\n [texts]=\"data.texts\"\n [maxFileSize]=\"data.maxFileSize\"\n (captured)=\"handOverCapture($event)\"\n ></smart-capture-camera-view>\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;border-radius:50%}.captureDialog ::ng-deep .stopRecordingButton{--text-color: color-mix(in srgb, var(--warn-color) 5%, white)}.recorderSlot{min-height:3.25rem}\n"], dependencies: [{ kind: "component", type: CaptureCameraViewComponent, selector: "smart-capture-camera-view", inputs: ["mode", "deviceStart", "handover", "texts", "maxFileSize"], outputs: ["captured"] }, { kind: "component", type: CaptureRecorderViewComponent, selector: "smart-capture-recorder-view", inputs: ["deviceStart", "handover", "texts"], outputs: ["recordingSaved"] }, { 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: UiActionButtonComponent, selector: "ui-action-button", inputs: ["disabled", "descriptor", "code", "identifier", "viewActivated", "tab", "rowState", "addedCssClass", "addBasicClasses", "iconPlaceholder", "chords"], outputs: ["actionClick", "actionDoubleClick"] }] }); }
10160
+ }
10161
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: CaptureDialogComponent, decorators: [{
10162
+ type: Component,
10163
+ args: [{ selector: 'smart-capture-dialog', imports: [
10164
+ CaptureCameraViewComponent,
10165
+ CaptureRecorderViewComponent,
10166
+ MatDialogContent,
10167
+ NgTemplateOutlet,
10168
+ UiActionButtonComponent,
10169
+ ], 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 <smart-capture-recorder-view\n [deviceStart]=\"data.settings.deviceStart\"\n [handover]=\"data.settings.handover\"\n [texts]=\"data.texts\"\n (recordingSaved)=\"handOverCapture([$event])\"\n ></smart-capture-recorder-view>\n }\n </div>\n } @else {\n <smart-capture-camera-view\n [mode]=\"cameraMode\"\n [deviceStart]=\"data.settings.deviceStart\"\n [handover]=\"data.settings.handover\"\n [texts]=\"data.texts\"\n [maxFileSize]=\"data.maxFileSize\"\n (captured)=\"handOverCapture($event)\"\n ></smart-capture-camera-view>\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;border-radius:50%}.captureDialog ::ng-deep .stopRecordingButton{--text-color: color-mix(in srgb, var(--warn-color) 5%, white)}.recorderSlot{min-height:3.25rem}\n"] }]
10170
+ }], ctorParameters: () => [], propDecorators: { cameraView: [{ type: i0.ViewChild, args: [i0.forwardRef(() => CaptureCameraViewComponent), { isSignal: true }] }], recorderView: [{ type: i0.ViewChild, args: [i0.forwardRef(() => CaptureRecorderViewComponent), { isSignal: true }] }] } });
10171
+
10172
+ class CaptureDialogService extends SmartdialogService {
10173
+ constructor() {
10174
+ super(inject(MatDialog), inject(Injector));
10175
+ }
10176
+ /** The returned dialog closes with the handed over files, or empty at a discard. */
10177
+ openCaptureDialog(data) {
10178
+ return this.createDialogRef(data, CaptureDialogComponent, {
10179
+ width: '560px',
10180
+ maxWidth: '90vw',
10181
+ autoFocus: 'dialog',
10182
+ });
10183
+ }
10184
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: CaptureDialogService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
10185
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: CaptureDialogService, providedIn: 'root' }); }
10186
+ }
10187
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: CaptureDialogService, decorators: [{
10188
+ type: Injectable,
10189
+ args: [{ providedIn: 'root' }]
10190
+ }], ctorParameters: () => [] });
10191
+
10192
+ /** The inline camera panel of the upload zone; a phone opens its own camera instead. */
9349
10193
  class PhotoCaptureWidgetComponent {
9350
10194
  constructor() {
10195
+ this.injector = inject(Injector);
9351
10196
  this.maxFileSize = input(/* @ts-ignore */
9352
10197
  ...(ngDevMode ? [undefined, { debugName: "maxFileSize" }] : /* istanbul ignore next */ []));
9353
10198
  /** PHOTO takes a picture; VIDEO records a video with the webcam or picks one on a desktop. */
@@ -9360,53 +10205,46 @@ class PhotoCaptureWidgetComponent {
9360
10205
  /** The zone's texts; the buttons follow them. */
9361
10206
  this.texts = input(UPLOAD_ZONE_DEFAULT_TEXTS, /* @ts-ignore */
9362
10207
  ...(ngDevMode ? [{ debugName: "texts" }] : /* istanbul ignore next */ []));
10208
+ /** AUTO asks for the camera when the widget opens, MANUAL at the switch-on button. */
10209
+ this.deviceStart = input(CaptureDeviceStart.MANUAL, /* @ts-ignore */
10210
+ ...(ngDevMode ? [{ debugName: "deviceStart" }] : /* istanbul ignore next */ []));
10211
+ /** CONFIRM keeps a capture for retake or use, IMMEDIATE hands it over at the take or the stop. */
10212
+ this.handover = input(CaptureHandover.IMMEDIATE, /* @ts-ignore */
10213
+ ...(ngDevMode ? [{ debugName: "handover" }] : /* istanbul ignore next */ []));
9363
10214
  this.photoCaptured = output();
10215
+ this.device = new CameraDevice({
10216
+ mode: this.mode,
10217
+ deviceStart: this.deviceStart,
10218
+ handover: this.handover,
10219
+ maxFileSize: this.maxFileSize,
10220
+ getVideoElement: () => this.videoRef.nativeElement,
10221
+ getCanvasElement: () => this.canvasRef.nativeElement,
10222
+ handOver: (files) => this.photoCaptured.emit(files),
10223
+ });
9364
10224
  /** The desktop webcam panel of an externally triggered photo widget, opened by openCapture(). */
9365
10225
  this.isPanelOpen = signal(false, /* @ts-ignore */
9366
10226
  ...(ngDevMode ? [{ debugName: "isPanelOpen" }] : /* istanbul ignore next */ []));
9367
- /**
9368
- * Both are written after an `await` — the camera stream, the compression — so a signal
9369
- * each: the click that started it has long been checked by then.
9370
- */
9371
- this.cameraActiveState = signal(false, /* @ts-ignore */
9372
- ...(ngDevMode ? [{ debugName: "cameraActiveState" }] : /* istanbul ignore next */ []));
9373
- /** Set when the browser gives no camera, shown in place of the switch-on hint. */
9374
- this.cameraError = signal(false, /* @ts-ignore */
9375
- ...(ngDevMode ? [{ debugName: "cameraError" }] : /* istanbul ignore next */ []));
9376
- this.isRecordingState = signal(false, /* @ts-ignore */
9377
- ...(ngDevMode ? [{ debugName: "isRecordingState" }] : /* istanbul ignore next */ []));
9378
- this.isCapturedState = signal(false, /* @ts-ignore */
9379
- ...(ngDevMode ? [{ debugName: "isCapturedState" }] : /* istanbul ignore next */ []));
9380
- /** Advanced by stopCamera(), so a stream the browser gives after a close is stopped at once. */
9381
- this.cameraRequest = 0;
9382
- this.toggleCameraButton = computed(() => this.createButton(this.cameraActive ? this.texts().cameraOff : this.texts().cameraOn, 'power_settings_new', this.cameraActive ? 'red' : 'primary'), /* @ts-ignore */
10227
+ /** Under AUTO the switch returns when the camera is off, so a second shot stays possible. */
10228
+ this.isCameraSwitchShown = computed(() => !this.device.keptFiles() &&
10229
+ (this.deviceStart() === CaptureDeviceStart.MANUAL ||
10230
+ !(this.device.isCameraActive() || this.device.isCameraStarting())), /* @ts-ignore */
10231
+ ...(ngDevMode ? [{ debugName: "isCameraSwitchShown" }] : /* istanbul ignore next */ []));
10232
+ this.isSwitchOnHintShown = computed(() => this.isCameraSwitchShown() && !this.device.isCameraActive() && !this.device.isCaptured(), /* @ts-ignore */
10233
+ ...(ngDevMode ? [{ debugName: "isSwitchOnHintShown" }] : /* istanbul ignore next */ []));
10234
+ this.toggleCameraButton = computed(() => UploadWidgetUtils.createCaptureButton(this.device.isCameraActive() ? this.texts().cameraOff : this.texts().cameraOn, 'power_settings_new', this.device.isCameraActive() ? 'red' : 'primary'), /* @ts-ignore */
9383
10235
  ...(ngDevMode ? [{ debugName: "toggleCameraButton" }] : /* istanbul ignore next */ []));
9384
- this.captureButton = computed(() => this.createButton(this.texts().takePicture, 'camera'), /* @ts-ignore */
10236
+ this.captureButton = computed(() => UploadWidgetUtils.createTakePictureButton(this.texts()), /* @ts-ignore */
9385
10237
  ...(ngDevMode ? [{ debugName: "captureButton" }] : /* istanbul ignore next */ []));
9386
- this.startRecordingButton = computed(() => this.createButton(this.texts().startRecording, 'fiber_manual_record'), /* @ts-ignore */
10238
+ this.startRecordingButton = computed(() => UploadWidgetUtils.createStartRecordingButton(this.texts()), /* @ts-ignore */
9387
10239
  ...(ngDevMode ? [{ debugName: "startRecordingButton" }] : /* istanbul ignore next */ []));
9388
- this.stopRecordingButton = computed(() => this.createButton(this.texts().stopRecording, 'stop', 'red'), /* @ts-ignore */
10240
+ this.stopRecordingButton = computed(() => UploadWidgetUtils.createStopRecordingButton(this.texts(), 'red'), /* @ts-ignore */
9389
10241
  ...(ngDevMode ? [{ debugName: "stopRecordingButton" }] : /* istanbul ignore next */ []));
9390
- this.pickVideoButton = computed(() => this.createButton(this.texts().pickVideo, 'video_library'), /* @ts-ignore */
10242
+ this.pickVideoButton = computed(() => UploadWidgetUtils.createCaptureButton(this.texts().pickVideo, 'video_library'), /* @ts-ignore */
9391
10243
  ...(ngDevMode ? [{ debugName: "pickVideoButton" }] : /* istanbul ignore next */ []));
9392
- }
9393
- get cameraActive() {
9394
- return this.cameraActiveState();
9395
- }
9396
- set cameraActive(active) {
9397
- this.cameraActiveState.set(active);
9398
- }
9399
- get isRecording() {
9400
- return this.isRecordingState();
9401
- }
9402
- set isRecording(recording) {
9403
- this.isRecordingState.set(recording);
9404
- }
9405
- get isCaptured() {
9406
- return this.isCapturedState();
9407
- }
9408
- set isCaptured(captured) {
9409
- this.isCapturedState.set(captured);
10244
+ this.retakeButton = computed(() => UploadWidgetUtils.createRetakeButton(this.texts()), /* @ts-ignore */
10245
+ ...(ngDevMode ? [{ debugName: "retakeButton" }] : /* istanbul ignore next */ []));
10246
+ this.useButton = computed(() => UploadWidgetUtils.createUseButton(this.texts()), /* @ts-ignore */
10247
+ ...(ngDevMode ? [{ debugName: "useButton" }] : /* istanbul ignore next */ []));
9410
10248
  }
9411
10249
  isMobile() {
9412
10250
  return /iPhone|iPad|iPod|Android/i.test(navigator.userAgent);
@@ -9417,110 +10255,32 @@ class PhotoCaptureWidgetComponent {
9417
10255
  this.fileInputRef.nativeElement.click();
9418
10256
  return;
9419
10257
  }
9420
- if (this.isPanelOpen()) {
9421
- this.stopCamera();
9422
- }
9423
- this.isPanelOpen.update((open) => !open);
9424
- }
9425
- ngOnDestroy() {
9426
- this.stopCamera();
9427
- }
9428
- async startCamera() {
9429
- this.cameraError.set(false);
9430
- const request = ++this.cameraRequest;
9431
- let stream;
9432
- try {
9433
- stream = await this.openCameraStream();
9434
- }
9435
- catch {
9436
- if (request === this.cameraRequest) {
9437
- this.cameraError.set(true);
9438
- }
9439
- return;
9440
- }
9441
- if (request !== this.cameraRequest) {
9442
- stream.getTracks().forEach((track) => track.stop());
9443
- return;
9444
- }
9445
- this.stream = stream;
9446
- this.videoRef.nativeElement.srcObject = this.stream;
9447
- this.videoRef.nativeElement.play();
9448
- this.cameraActive = true;
9449
- this.isCaptured = false;
9450
- }
9451
- /** A video asks for the sound too; a desktop without a microphone records a silent one. */
9452
- async openCameraStream() {
9453
- if (this.mode() === 'VIDEO') {
9454
- try {
9455
- return await navigator.mediaDevices.getUserMedia({ video: true, audio: true });
9456
- }
9457
- catch {
9458
- // No microphone answers: the camera alone is asked below.
9459
- }
10258
+ if (this.isPanelOpen()) {
10259
+ this.device.release();
9460
10260
  }
9461
- return navigator.mediaDevices.getUserMedia({ video: true });
9462
- }
9463
- /** Closing the camera mid-recording discards the recording. */
9464
- stopCamera() {
9465
- this.cameraRequest++;
9466
- if (this.recorder) {
9467
- this.recorder.onstop = null;
9468
- this.recorder.stop();
9469
- this.recorder = undefined;
10261
+ else {
10262
+ this.startCameraAfterRender();
9470
10263
  }
9471
- this.isRecording = false;
9472
- this.stream?.getTracks().forEach((track) => track.stop());
9473
- this.stream = undefined;
9474
- this.cameraActive = false;
10264
+ this.isPanelOpen.update((open) => !open);
9475
10265
  }
9476
- async capturePhoto() {
9477
- const video = this.videoRef.nativeElement;
9478
- const canvas = this.canvasRef.nativeElement;
9479
- const context = canvas.getContext('2d');
9480
- if (!context)
9481
- return;
9482
- canvas.width = video.videoWidth;
9483
- canvas.height = video.videoHeight;
9484
- context.drawImage(video, 0, 0);
9485
- const blob = await new Promise((resolve) => canvas.toBlob(resolve, 'image/jpeg', 0.9));
9486
- if (!blob)
9487
- return;
9488
- let file = new File([blob], `photo_${Date.now()}.jpg`, { type: 'image/jpeg' });
9489
- const maxFileSize = this.maxFileSize();
9490
- if (maxFileSize && file.size > maxFileSize) {
9491
- file = await this.compressImageFile(file, maxFileSize);
10266
+ ngAfterViewInit() {
10267
+ if (!this.externalTrigger()) {
10268
+ this.startCameraAfterRender();
9492
10269
  }
9493
- this.photoCaptured.emit([file]);
9494
- this.isCaptured = true;
9495
- this.stopCamera();
9496
10270
  }
9497
- /** A browser that records neither webm nor mp4 refuses the recorder: the camera error shows. */
9498
- startRecording() {
9499
- let recorder;
9500
- try {
9501
- recorder = new MediaRecorder(this.stream, {
9502
- mimeType: UploadWidgetUtils.chooseRecorderMimeType('video'),
9503
- });
9504
- }
9505
- catch {
9506
- this.cameraError.set(true);
10271
+ ngOnDestroy() {
10272
+ this.device.release();
10273
+ }
10274
+ /** AUTO waits for the render: the stream goes into the panel's video element. */
10275
+ startCameraAfterRender() {
10276
+ if (this.deviceStart() !== CaptureDeviceStart.AUTO || this.isMobile()) {
9507
10277
  return;
9508
10278
  }
9509
- const chunks = [];
9510
- recorder.ondataavailable = (event) => chunks.push(event.data);
9511
- recorder.onstop = () => {
9512
- const blob = new Blob(chunks, { type: recorder.mimeType });
9513
- this.recorder = undefined;
9514
- this.photoCaptured.emit([UploadWidgetUtils.createRecordingFile(blob, 'video', 'video/mp4')]);
9515
- this.stopCamera();
9516
- };
9517
- recorder.start();
9518
- this.recorder = recorder;
9519
- this.isRecording = true;
9520
- }
9521
- /** The recording state holds until onstop hands the video over and stops the camera. */
9522
- stopRecording() {
9523
- this.recorder?.stop();
10279
+ afterNextRender(() => {
10280
+ if (!this.externalTrigger() || this.isPanelOpen()) {
10281
+ this.device.startCamera();
10282
+ }
10283
+ }, { injector: this.injector });
9524
10284
  }
9525
10285
  getPhotoDataURL() {
9526
10286
  return this.canvasRef.nativeElement.toDataURL('image/png');
@@ -9535,7 +10295,7 @@ class PhotoCaptureWidgetComponent {
9535
10295
  for (const file of filesArray) {
9536
10296
  if (maxSize && file.size > maxSize && file.type.startsWith('image/')) {
9537
10297
  try {
9538
- const compressed = await this.compressImageFile(file, maxSize);
10298
+ const compressed = await UploadWidgetUtils.compressImageFile(file, maxSize);
9539
10299
  processedFiles.push(compressed);
9540
10300
  }
9541
10301
  catch {
@@ -9548,80 +10308,12 @@ class PhotoCaptureWidgetComponent {
9548
10308
  }
9549
10309
  this.photoCaptured.emit(processedFiles);
9550
10310
  }
9551
- toggleCamera() {
9552
- if (this.cameraActive) {
9553
- this.stopCamera();
9554
- }
9555
- else {
9556
- this.startCamera();
9557
- }
9558
- }
9559
- async compressImageFile(file, maxFileSize) {
9560
- if (!file.type.startsWith('image/'))
9561
- return file;
9562
- const img = await new Promise((resolve, reject) => {
9563
- const url = URL.createObjectURL(file);
9564
- const image = new Image();
9565
- image.onload = () => {
9566
- URL.revokeObjectURL(url);
9567
- resolve(image);
9568
- };
9569
- image.onerror = (e) => {
9570
- URL.revokeObjectURL(url);
9571
- reject(e);
9572
- };
9573
- image.src = url;
9574
- });
9575
- const canvas = document.createElement('canvas');
9576
- const ctx = canvas.getContext('2d');
9577
- if (!ctx)
9578
- return file;
9579
- let width = img.naturalWidth;
9580
- let height = img.naturalHeight;
9581
- let quality = 0.9;
9582
- const minQuality = 0.4;
9583
- const minDimension = 640;
9584
- const getBlob = (q) => new Promise((resolve) => {
9585
- canvas.width = width;
9586
- canvas.height = height;
9587
- ctx.clearRect(0, 0, width, height);
9588
- ctx.drawImage(img, 0, 0, width, height);
9589
- canvas.toBlob((blob) => {
9590
- if (blob)
9591
- resolve(blob);
9592
- }, 'image/jpeg', q);
9593
- });
9594
- let blob = await getBlob(quality);
9595
- while (blob.size > maxFileSize) {
9596
- if (quality > minQuality) {
9597
- quality -= 0.1;
9598
- }
9599
- else if (width > minDimension && height > minDimension) {
9600
- width = Math.floor(width * 0.8);
9601
- height = Math.floor(height * 0.8);
9602
- quality = 0.9;
9603
- }
9604
- else {
9605
- break;
9606
- }
9607
- blob = await getBlob(quality);
9608
- }
9609
- if (blob.size <= maxFileSize) {
9610
- return new File([blob], file.name, { type: 'image/jpeg' });
9611
- }
9612
- else {
9613
- return file;
9614
- }
9615
- }
9616
- createButton(title, icon, color = 'primary') {
9617
- return { type: UiActionButtonType.RAISED, title, icon, iconPosition: IconPosition.PRE, color };
9618
- }
9619
10311
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: PhotoCaptureWidgetComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
9620
- 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 } }, 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: "@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\">\n <video #video autoplay [muted]=\"true\" [hidden]=\"isCaptured || !cameraActive\"></video>\n <canvas #canvas [hidden]=\"!isCaptured || cameraActive\"></canvas>\n @if (cameraError()) {\n <p class=\"message\">{{ texts().cameraError }}</p>\n } @else if (isRecording) {\n <p class=\"message\">{{ texts().recording }}</p>\n } @else if (!cameraActive && !isCaptured) {\n @if (mode() === 'VIDEO') {\n <p class=\"message\">{{ texts().switchOnForVideo }}</p>\n } @else {\n <p class=\"message\">{{ texts().switchOnCameraHint }}</p>\n }\n }\n <div class=\"controls\">\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 @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 <!-- 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 } @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 </div>\n </div>\n}\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"] }] }); }
10312
+ 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 } }, 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: "@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\">\n <video #video autoplay [muted]=\"true\" [hidden]=\"device.isCaptured() || !device.isCameraActive()\"></video>\n <canvas #canvas [hidden]=\"!device.isCaptured() || device.isCameraActive()\"></canvas>\n @if (device.keptVideoUrl(); as videoUrl) {\n <video controls [src]=\"videoUrl\"></video>\n }\n @if (device.cameraError()) {\n <p class=\"message\">{{ texts().cameraError }}</p>\n } @else if (device.isRecording()) {\n <p class=\"message\">{{ texts().recording }}</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 <div class=\"controls\">\n @if (isCameraSwitchShown()) {\n <!-- START CAMERA -->\n <ui-action-button\n class=\"toggleCameraButton\"\n [disabled]=\"device.isRecording()\"\n [descriptor]=\"toggleCameraButton()\"\n (actionClick)=\"device.toggleCamera()\"\n >\n </ui-action-button>\n }\n @if (device.keptFiles()) {\n <ui-action-button\n class=\"retakeButton\"\n [descriptor]=\"retakeButton()\"\n (actionClick)=\"device.retakeCapture()\"\n >\n </ui-action-button>\n <ui-action-button\n class=\"useButton\"\n [descriptor]=\"useButton()\"\n (actionClick)=\"device.useCapture()\"\n >\n </ui-action-button>\n } @else if (mode() === 'VIDEO') {\n <!-- RECORD / STOP VIDEO -->\n @if (device.isRecording()) {\n <ui-action-button\n class=\"stopRecordingButton\"\n [descriptor]=\"stopRecordingButton()\"\n (actionClick)=\"device.stopRecording()\"\n >\n </ui-action-button>\n } @else {\n <ui-action-button\n class=\"startRecordingButton\"\n [disabled]=\"!device.isCameraActive()\"\n [descriptor]=\"startRecordingButton()\"\n (actionClick)=\"device.startRecording()\"\n >\n </ui-action-button>\n }\n } @else {\n <!-- CAPTURE CAMERA -->\n <ui-action-button\n class=\"capturePhotoButton\"\n [disabled]=\"!device.isCameraActive()\"\n [descriptor]=\"captureButton()\"\n (actionClick)=\"device.capturePhoto()\"\n >\n </ui-action-button>\n }\n @if (mode() === 'VIDEO' && !device.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]=\"device.isRecording()\"\n [descriptor]=\"pickVideoButton()\"\n (actionClick)=\"fileInput.click()\"\n >\n </ui-action-button>\n }\n </div>\n </div>\n}\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"] }] }); }
9621
10313
  }
9622
10314
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: PhotoCaptureWidgetComponent, decorators: [{
9623
10315
  type: Component,
9624
- args: [{ selector: 'photo-capture-widget', imports: [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\">\n <video #video autoplay [muted]=\"true\" [hidden]=\"isCaptured || !cameraActive\"></video>\n <canvas #canvas [hidden]=\"!isCaptured || cameraActive\"></canvas>\n @if (cameraError()) {\n <p class=\"message\">{{ texts().cameraError }}</p>\n } @else if (isRecording) {\n <p class=\"message\">{{ texts().recording }}</p>\n } @else if (!cameraActive && !isCaptured) {\n @if (mode() === 'VIDEO') {\n <p class=\"message\">{{ texts().switchOnForVideo }}</p>\n } @else {\n <p class=\"message\">{{ texts().switchOnCameraHint }}</p>\n }\n }\n <div class=\"controls\">\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 @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 <!-- 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 } @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 </div>\n </div>\n}\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"] }]
10316
+ args: [{ selector: 'photo-capture-widget', imports: [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\">\n <video #video autoplay [muted]=\"true\" [hidden]=\"device.isCaptured() || !device.isCameraActive()\"></video>\n <canvas #canvas [hidden]=\"!device.isCaptured() || device.isCameraActive()\"></canvas>\n @if (device.keptVideoUrl(); as videoUrl) {\n <video controls [src]=\"videoUrl\"></video>\n }\n @if (device.cameraError()) {\n <p class=\"message\">{{ texts().cameraError }}</p>\n } @else if (device.isRecording()) {\n <p class=\"message\">{{ texts().recording }}</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 <div class=\"controls\">\n @if (isCameraSwitchShown()) {\n <!-- START CAMERA -->\n <ui-action-button\n class=\"toggleCameraButton\"\n [disabled]=\"device.isRecording()\"\n [descriptor]=\"toggleCameraButton()\"\n (actionClick)=\"device.toggleCamera()\"\n >\n </ui-action-button>\n }\n @if (device.keptFiles()) {\n <ui-action-button\n class=\"retakeButton\"\n [descriptor]=\"retakeButton()\"\n (actionClick)=\"device.retakeCapture()\"\n >\n </ui-action-button>\n <ui-action-button\n class=\"useButton\"\n [descriptor]=\"useButton()\"\n (actionClick)=\"device.useCapture()\"\n >\n </ui-action-button>\n } @else if (mode() === 'VIDEO') {\n <!-- RECORD / STOP VIDEO -->\n @if (device.isRecording()) {\n <ui-action-button\n class=\"stopRecordingButton\"\n [descriptor]=\"stopRecordingButton()\"\n (actionClick)=\"device.stopRecording()\"\n >\n </ui-action-button>\n } @else {\n <ui-action-button\n class=\"startRecordingButton\"\n [disabled]=\"!device.isCameraActive()\"\n [descriptor]=\"startRecordingButton()\"\n (actionClick)=\"device.startRecording()\"\n >\n </ui-action-button>\n }\n } @else {\n <!-- CAPTURE CAMERA -->\n <ui-action-button\n class=\"capturePhotoButton\"\n [disabled]=\"!device.isCameraActive()\"\n [descriptor]=\"captureButton()\"\n (actionClick)=\"device.capturePhoto()\"\n >\n </ui-action-button>\n }\n @if (mode() === 'VIDEO' && !device.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]=\"device.isRecording()\"\n [descriptor]=\"pickVideoButton()\"\n (actionClick)=\"fileInput.click()\"\n >\n </ui-action-button>\n }\n </div>\n </div>\n}\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"] }]
9625
10317
  }], propDecorators: { videoRef: [{
9626
10318
  type: ViewChild,
9627
10319
  args: ['video']
@@ -9631,12 +10323,11 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImpor
9631
10323
  }], fileInputRef: [{
9632
10324
  type: ViewChild,
9633
10325
  args: ['fileInput']
9634
- }], 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 }] }], photoCaptured: [{ type: i0.Output, args: ["photoCaptured"] }] } });
10326
+ }], 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 }] }], photoCaptured: [{ type: i0.Output, args: ["photoCaptured"] }] } });
9635
10327
 
10328
+ /** The inline recorder strip of the upload zone and of the deprecated recordingUploader. */
9636
10329
  class VoiceRecordWidgetComponent {
9637
10330
  constructor() {
9638
- this.ngZone = inject(NgZone);
9639
- this.changeDetectorRef = inject(ChangeDetectorRef);
9640
10331
  this.openDirection = input('RIGHT', /* @ts-ignore */
9641
10332
  ...(ngDevMode ? [{ debugName: "openDirection" }] : /* istanbul ignore next */ []));
9642
10333
  /** The zone's texts, the microphone error line's among them. */
@@ -9645,15 +10336,24 @@ class VoiceRecordWidgetComponent {
9645
10336
  /** True in the upload zone only; the deprecated recordingUploader shows no error line. */
9646
10337
  this.showMicrophoneError = input(false, /* @ts-ignore */
9647
10338
  ...(ngDevMode ? [{ debugName: "showMicrophoneError" }] : /* istanbul ignore next */ []));
10339
+ /** AUTO starts the recording when the widget opens, MANUAL at the microphone button. */
10340
+ this.deviceStart = input(CaptureDeviceStart.MANUAL, /* @ts-ignore */
10341
+ ...(ngDevMode ? [{ debugName: "deviceStart" }] : /* istanbul ignore next */ []));
10342
+ /** CONFIRM keeps a stopped recording for playback and save, IMMEDIATE hands it over. */
10343
+ this.handover = input(CaptureHandover.CONFIRM, /* @ts-ignore */
10344
+ ...(ngDevMode ? [{ debugName: "handover" }] : /* istanbul ignore next */ []));
10345
+ /** True under the zone's Capture Settings; everywhere else the save stays an icon. */
10346
+ this.showSaveTitle = input(false, /* @ts-ignore */
10347
+ ...(ngDevMode ? [{ debugName: "showSaveTitle" }] : /* istanbul ignore next */ []));
9648
10348
  this.recordingSaved = output();
9649
- this.isRecording = false;
9650
- this.recordedBlob = null;
9651
- this.time = '00:00';
9652
- this.isPlaying = false;
9653
- this.isWaveformOpen = false;
9654
- /** Set when the browser gives no microphone, until the next start. */
9655
- this.microphoneError = signal(false, /* @ts-ignore */
9656
- ...(ngDevMode ? [{ debugName: "microphoneError" }] : /* istanbul ignore next */ []));
10349
+ this.device = new MicrophoneDevice({
10350
+ handover: this.handover,
10351
+ ngZone: inject(NgZone),
10352
+ getWaveformElement: () => this.waveformRef?.nativeElement,
10353
+ getButtonElement: () => this.mainButton.nativeElement,
10354
+ getThemeElement: () => document.documentElement,
10355
+ handOver: (file) => this.recordingSaved.emit(file),
10356
+ });
9657
10357
  this.micButton = {
9658
10358
  title: '',
9659
10359
  type: UiActionButtonType.ICON,
@@ -9676,6 +10376,10 @@ class VoiceRecordWidgetComponent {
9676
10376
  iconPosition: IconPosition.PRE,
9677
10377
  color: 'primary',
9678
10378
  };
10379
+ this.effectiveSaveButton = computed(() => this.showSaveTitle()
10380
+ ? { ...this.saveButton, title: this.texts().save, type: UiActionButtonType.RAISED }
10381
+ : this.saveButton, /* @ts-ignore */
10382
+ ...(ngDevMode ? [{ debugName: "effectiveSaveButton" }] : /* istanbul ignore next */ []));
9679
10383
  }
9680
10384
  ngOnInit() {
9681
10385
  this.playButton = {
@@ -9693,195 +10397,34 @@ class VoiceRecordWidgetComponent {
9693
10397
  color: 'red',
9694
10398
  };
9695
10399
  }
9696
- ngAfterViewInit() { }
9697
- get isLeftDirection() {
9698
- return this.openDirection() === 'LEFT';
9699
- }
9700
- async initializeWaveSurferIfNeeded() {
9701
- if (this.wavesurfer || !this.waveformRef?.nativeElement) {
9702
- return;
9703
- }
9704
- await new Promise((resolve) => requestAnimationFrame(() => resolve()));
9705
- this.waveformRef.nativeElement.style.padding = '0.1rem';
9706
- this.initWaveSurfer();
9707
- }
9708
- initWaveSurfer() {
9709
- let body = document.querySelector('body');
9710
- let primaryColor = getComputedStyle(document.documentElement)
9711
- .getPropertyValue('--primary-color')
9712
- .trim();
9713
- let el = this.mainButton.nativeElement.querySelector('.sb4-primary');
9714
- let remInPx = parseFloat(getComputedStyle(document.documentElement).fontSize);
9715
- let height = (el?.getBoundingClientRect().height ?? 0) - remInPx * 0.1;
9716
- this.wavesurfer = WaveSurfer.create({
9717
- container: this.waveformRef.nativeElement,
9718
- waveColor: primaryColor,
9719
- progressColor: this.addAlphaToColor(primaryColor, 0.5),
9720
- height: height,
9721
- barWidth: 5,
9722
- barGap: 1,
9723
- barRadius: 5,
9724
- });
9725
- this.wavesurfer.setVolume(1);
9726
- const mimeType = UploadWidgetUtils.chooseRecorderMimeType('audio');
9727
- this.record = this.wavesurfer.registerPlugin(RecordPlugin.create({
9728
- renderRecordedAudio: true,
9729
- scrollingWaveform: true,
9730
- continuousWaveform: false,
9731
- continuousWaveformDuration: 15,
9732
- mimeType,
9733
- }));
9734
- this.wavesurfer.on('dblclick', () => {
9735
- this.updateViewState(() => {
9736
- this.wavesurfer?.play();
9737
- this.isPlaying = true;
9738
- });
9739
- });
9740
- this.record.on('record-end', (blob) => {
9741
- this.updateViewState(() => {
9742
- this.recordedBlob = blob;
9743
- this.isRecording = false;
9744
- });
9745
- });
9746
- this.record.on('record-progress', (time) => {
9747
- this.updateViewState(() => {
9748
- this.updateProgress(time);
9749
- });
9750
- });
9751
- this.wavesurfer.on('finish', () => {
9752
- this.updateViewState(() => {
9753
- this.isPlaying = false;
9754
- });
9755
- });
9756
- }
9757
- async startRecording() {
9758
- const confirmed = this.checkHasRecorded();
9759
- if (!confirmed || this.isRecording)
9760
- return;
9761
- // Initialize first (still within the gesture tick on most browsers)
9762
- await this.initializeWaveSurferIfNeeded();
9763
- if (!this.record)
9764
- throw new Error('Voice recorder plugin is not initialized.');
9765
- this.updateViewState(() => {
9766
- this.microphoneError.set(false);
9767
- this.isWaveformOpen = true;
9768
- this.isRecording = true;
9769
- this.isPlaying = false;
9770
- this.recordedBlob = null;
9771
- this.time = '00:00';
9772
- });
9773
- try {
9774
- // Force native mic permission first so it works with WKWebView
9775
- const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
9776
- stream.getTracks().forEach((track) => track.stop());
9777
- await this.record.startRecording();
9778
- }
9779
- catch (error) {
9780
- console.error('Mic failed', error);
9781
- this.updateViewState(() => {
9782
- this.isRecording = false;
9783
- this.isWaveformOpen = !!this.recordedBlob;
9784
- this.microphoneError.set(true);
9785
- });
9786
- throw error;
9787
- }
9788
- }
9789
- /**
9790
- * Whether recording may start. Always `true` today: the branch below is where a "you
9791
- * already have a recording — overwrite it?" confirmation was meant to go, and never did.
9792
- * Kept, rather than inlined away, so the place stays visible; see A37.
9793
- */
9794
- checkHasRecorded() {
9795
- if (this.recordedBlob) {
9796
- }
9797
- return true;
9798
- }
9799
- updateProgress(time) {
9800
- let formattedTime = [Math.floor((time % 3600000) / 60000), Math.floor((time % 60000) / 1000)]
9801
- .map((v) => (v < 10 ? '0' + v : v))
9802
- .join(':');
9803
- this.time = formattedTime;
9804
- }
9805
- stopRecording() {
9806
- if (this.record && this.isRecording) {
9807
- this.record.stopRecording();
9808
- this.updateViewState(() => {
9809
- this.isRecording = false;
9810
- });
9811
- }
9812
- }
9813
- saveRecording() {
9814
- if (this.recordedBlob) {
9815
- const file = UploadWidgetUtils.createRecordingFile(this.recordedBlob, 'recording', 'audio/mp4');
9816
- this.recordingSaved.emit(file);
9817
- // Destroy completely so next recording initializes fresh
9818
- this.wavesurfer?.destroy();
9819
- this.wavesurfer = undefined;
9820
- this.record = undefined;
9821
- this.updateViewState(() => {
9822
- this.recordedBlob = null;
9823
- this.time = '00:00';
9824
- this.isPlaying = false;
9825
- this.isWaveformOpen = false;
9826
- });
10400
+ ngAfterViewInit() {
10401
+ if (this.deviceStart() === CaptureDeviceStart.AUTO) {
10402
+ this.device.requestRecording();
9827
10403
  }
9828
10404
  }
9829
- replayRecording() {
9830
- if (this.recordedBlob) {
9831
- this.updateViewState(() => {
9832
- this.isPlaying = !this.isPlaying;
9833
- this.wavesurfer?.playPause();
9834
- });
9835
- }
10405
+ get isLeftDirection() {
10406
+ return this.openDirection() === 'LEFT';
9836
10407
  }
9837
10408
  ngOnDestroy() {
9838
- if (this.wavesurfer) {
9839
- this.wavesurfer.destroy();
9840
- }
9841
- }
9842
- addAlphaToColor(hexColor, alpha) {
9843
- if (/^#([a-f\d])([a-f\d])([a-f\d])$/i.test(hexColor)) {
9844
- hexColor = hexColor.replace(/^#([a-f\d])([a-f\d])([a-f\d])$/i, (_, r, g, b) => `#${r}${r}${g}${g}${b}${b}`);
9845
- }
9846
- const hexMatch = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hexColor);
9847
- if (hexMatch) {
9848
- const [, r, g, b] = hexMatch;
9849
- return `rgba(${parseInt(r, 16)}, ${parseInt(g, 16)}, ${parseInt(b, 16)}, ${alpha})`;
9850
- }
9851
- const ctx = document.createElement('canvas').getContext('2d');
9852
- if (ctx) {
9853
- ctx.fillStyle = hexColor;
9854
- const parsed = ctx.fillStyle;
9855
- const rgbMatch = /^rgb\((\d+),\s*(\d+),\s*(\d+)\)$/.exec(parsed);
9856
- if (rgbMatch) {
9857
- const [, r, g, b] = rgbMatch;
9858
- return `rgba(${r}, ${g}, ${b}, ${alpha})`;
9859
- }
9860
- }
9861
- return hexColor;
9862
- }
9863
- updateViewState(update) {
9864
- this.ngZone.run(() => {
9865
- update();
9866
- this.changeDetectorRef.markForCheck();
9867
- });
10409
+ this.device.release();
9868
10410
  }
9869
10411
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: VoiceRecordWidgetComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
9870
- 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 } }, 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@if (showMicrophoneError() && microphoneError()) {\n <p class=\"message\">{{ texts().microphoneError }}</p>\n}\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}\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"] }] }); }
10412
+ 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 } }, 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]=\"device.isWaveformShown()\"\n [class.isRecording]=\"device.isRecording()\"\n [class.hasRecording]=\"device.recordedBlob()\"\n >\n <div class=\"recorderMain\">\n <div class=\"recorderToolbar\">\n <!-- Start Recording -->\n @if (!device.isRecording()) {\n <ui-action-button\n class=\"mainButton\"\n #mainButton\n [descriptor]=\"micButton\"\n (actionClick)=\"device.requestRecording()\"\n >\n </ui-action-button>\n }\n\n <!-- Stop Recording -->\n @if (device.isRecording()) {\n <ui-action-button\n class=\"mainButton\"\n #mainButton\n [descriptor]=\"stopButton\"\n (actionClick)=\"device.stopRecording()\"\n >\n </ui-action-button>\n }\n\n <!-- Save Recording -->\n @if (device.recordedBlob()) {\n <ui-action-button\n class=\"mainButton\"\n [descriptor]=\"effectiveSaveButton()\"\n (actionClick)=\"device.saveRecording()\"\n >\n </ui-action-button>\n }\n\n <!-- Start Play -->\n @if (device.recordedBlob() && !device.isPlaying()) {\n <ui-action-button\n class=\"mainButton\"\n [descriptor]=\"playButton\"\n (actionClick)=\"device.replayRecording()\"\n ></ui-action-button>\n }\n\n <!-- Stop Play -->\n @if (device.recordedBlob() && device.isPlaying()) {\n <ui-action-button\n class=\"mainButton\"\n [descriptor]=\"stopPlaybackButton\"\n (actionClick)=\"device.replayRecording()\"\n ></ui-action-button>\n }\n\n @if (device.isRecording()) {\n <div class=\"progress\">{{ device.time() }}</div>\n }\n </div>\n\n <div class=\"waveformBand\" [class.open]=\"device.isWaveformShown()\">\n <div class=\"waveformGrid\"></div>\n <div class=\"waveform\" #waveform></div>\n </div>\n </div>\n</div>\n@if (showMicrophoneError() && device.microphoneError()) {\n <p class=\"message\">{{ texts().microphoneError }}</p>\n}\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}\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"] }] }); }
9871
10413
  }
9872
10414
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: VoiceRecordWidgetComponent, decorators: [{
9873
10415
  type: Component,
9874
- 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@if (showMicrophoneError() && microphoneError()) {\n <p class=\"message\">{{ texts().microphoneError }}</p>\n}\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}\n"] }]
10416
+ 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]=\"device.isWaveformShown()\"\n [class.isRecording]=\"device.isRecording()\"\n [class.hasRecording]=\"device.recordedBlob()\"\n >\n <div class=\"recorderMain\">\n <div class=\"recorderToolbar\">\n <!-- Start Recording -->\n @if (!device.isRecording()) {\n <ui-action-button\n class=\"mainButton\"\n #mainButton\n [descriptor]=\"micButton\"\n (actionClick)=\"device.requestRecording()\"\n >\n </ui-action-button>\n }\n\n <!-- Stop Recording -->\n @if (device.isRecording()) {\n <ui-action-button\n class=\"mainButton\"\n #mainButton\n [descriptor]=\"stopButton\"\n (actionClick)=\"device.stopRecording()\"\n >\n </ui-action-button>\n }\n\n <!-- Save Recording -->\n @if (device.recordedBlob()) {\n <ui-action-button\n class=\"mainButton\"\n [descriptor]=\"effectiveSaveButton()\"\n (actionClick)=\"device.saveRecording()\"\n >\n </ui-action-button>\n }\n\n <!-- Start Play -->\n @if (device.recordedBlob() && !device.isPlaying()) {\n <ui-action-button\n class=\"mainButton\"\n [descriptor]=\"playButton\"\n (actionClick)=\"device.replayRecording()\"\n ></ui-action-button>\n }\n\n <!-- Stop Play -->\n @if (device.recordedBlob() && device.isPlaying()) {\n <ui-action-button\n class=\"mainButton\"\n [descriptor]=\"stopPlaybackButton\"\n (actionClick)=\"device.replayRecording()\"\n ></ui-action-button>\n }\n\n @if (device.isRecording()) {\n <div class=\"progress\">{{ device.time() }}</div>\n }\n </div>\n\n <div class=\"waveformBand\" [class.open]=\"device.isWaveformShown()\">\n <div class=\"waveformGrid\"></div>\n <div class=\"waveform\" #waveform></div>\n </div>\n </div>\n</div>\n@if (showMicrophoneError() && device.microphoneError()) {\n <p class=\"message\">{{ texts().microphoneError }}</p>\n}\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}\n"] }]
9875
10417
  }], propDecorators: { waveformRef: [{
9876
10418
  type: ViewChild,
9877
10419
  args: ['waveform']
9878
10420
  }], mainButton: [{
9879
10421
  type: ViewChild,
9880
10422
  args: ['mainButton', { read: ElementRef }]
9881
- }], 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 }] }], recordingSaved: [{ type: i0.Output, args: ["recordingSaved"] }] } });
10423
+ }], 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 }] }], recordingSaved: [{ type: i0.Output, args: ["recordingSaved"] }] } });
9882
10424
 
9883
10425
  class UploadWidgetComponent {
9884
10426
  constructor() {
10427
+ this.captureDialogService = inject(CaptureDialogService);
9885
10428
  this.uploadDescriptor = input(this.defaultUploadDescriptor, /* @ts-ignore */
9886
10429
  ...(ngDevMode ? [{ debugName: "uploadDescriptor" }] : /* istanbul ignore next */ []));
9887
10430
  this.isMultiple = input.required(/* @ts-ignore */
@@ -9890,7 +10433,20 @@ class UploadWidgetComponent {
9890
10433
  ...(ngDevMode ? [{ debugName: "isDisabled" }] : /* istanbul ignore next */ []));
9891
10434
  this.sources = input(undefined, /* @ts-ignore */
9892
10435
  ...(ngDevMode ? [{ debugName: "sources" }] : /* istanbul ignore next */ []));
10436
+ this.capture = input(undefined, /* @ts-ignore */
10437
+ ...(ngDevMode ? [{ debugName: "capture" }] : /* istanbul ignore next */ []));
9893
10438
  this.uploadFilesEvent = output();
10439
+ /** The Capture Settings in force: a missing bean or field takes its default here only. */
10440
+ this.captureSettings = computed(() => {
10441
+ const capture = this.capture();
10442
+ return {
10443
+ mode: capture?.mode ?? CaptureMode.DIALOG,
10444
+ openDirection: capture?.openDirection ?? CaptureOpenDirection.RIGHT,
10445
+ deviceStart: capture?.deviceStart ?? CaptureDeviceStart.AUTO,
10446
+ handover: capture?.handover ?? CaptureHandover.CONFIRM,
10447
+ };
10448
+ }, /* @ts-ignore */
10449
+ ...(ngDevMode ? [{ debugName: "captureSettings" }] : /* istanbul ignore next */ []));
9894
10450
  this.uploadWidgetType = UploadWidgetType;
9895
10451
  this.fileUploaderSource = FileUploaderSource;
9896
10452
  /** The recorder opens below the capture row at a click on the microphone button. */
@@ -9965,17 +10521,45 @@ class UploadWidgetComponent {
9965
10521
  getCaptureSources() {
9966
10522
  return (this.sources() ?? []).filter((source) => source !== FileUploaderSource.FILE);
9967
10523
  }
10524
+ /** On a phone a camera button stays the device's own camera, whatever the Capture Mode says. */
9968
10525
  startCapture(source) {
9969
- if (source === FileUploaderSource.CAMERA_PHOTO) {
9970
- this.photoCapture?.openCapture();
10526
+ const camera = this.getCameraWidget(source);
10527
+ if (this.captureSettings().mode === CaptureMode.DIALOG && !camera?.isMobile()) {
10528
+ this.openCaptureDialog(source);
9971
10529
  }
9972
- else if (source === FileUploaderSource.CAMERA_VIDEO) {
9973
- this.videoCapture?.openCapture();
10530
+ else if (camera) {
10531
+ camera.openCapture();
9974
10532
  }
9975
10533
  else if (source === FileUploaderSource.MICROPHONE) {
9976
10534
  this.isRecorderOpen.update((open) => !open);
9977
10535
  }
9978
10536
  }
10537
+ getCameraWidget(source) {
10538
+ if (source === FileUploaderSource.MICROPHONE) {
10539
+ return undefined;
10540
+ }
10541
+ return source === FileUploaderSource.CAMERA_PHOTO ? this.photoCapture : this.videoCapture;
10542
+ }
10543
+ openCaptureDialog(source) {
10544
+ this.captureDialogService
10545
+ .openCaptureDialog({
10546
+ size: {},
10547
+ content: { title: this.texts[CAPTURE_BUTTONS[source].dialogTitle] },
10548
+ source,
10549
+ settings: this.captureSettings(),
10550
+ texts: this.texts,
10551
+ maxFileSize: source === FileUploaderSource.CAMERA_PHOTO ? this.getMaxFileSize() : undefined,
10552
+ })
10553
+ .afterClosed()
10554
+ .subscribe((files) => {
10555
+ if (files) {
10556
+ this.uploadImage(files);
10557
+ }
10558
+ });
10559
+ }
10560
+ getMaxFileSize() {
10561
+ return this.maxSizeMb * 1024 * 1024;
10562
+ }
9979
10563
  getCaptureButtonClass(source) {
9980
10564
  return CAPTURE_BUTTONS[source].cssClass;
9981
10565
  }
@@ -10019,7 +10603,7 @@ class UploadWidgetComponent {
10019
10603
  };
10020
10604
  }
10021
10605
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: UploadWidgetComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
10022
- 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 } }, 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 [texts]=\"texts\"\n [maxFileSize]=\"maxSizeMb * 1024 * 1024\"\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 [texts]=\"texts\"\n (photoCaptured)=\"uploadImage($event)\"\n ></photo-capture-widget>\n }\n @if (isRecorderOpen()) {\n <voice-record-widget\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]=\"maxSizeMb * 1024 * 1024\"\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{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"], outputs: ["photoCaptured"] }, { kind: "component", type: VoiceRecordWidgetComponent, selector: "voice-record-widget", inputs: ["openDirection", "texts", "showMicrophoneError"], 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"] }] }); }
10606
+ 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"], outputs: ["photoCaptured"] }, { kind: "component", type: VoiceRecordWidgetComponent, selector: "voice-record-widget", inputs: ["openDirection", "texts", "showMicrophoneError", "deviceStart", "handover", "showSaveTitle"], 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"] }] }); }
10023
10607
  }
10024
10608
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: UploadWidgetComponent, decorators: [{
10025
10609
  type: Component,
@@ -10028,7 +10612,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImpor
10028
10612
  VoiceRecordWidgetComponent,
10029
10613
  SmartfileuploaderComponent,
10030
10614
  UiActionButtonComponent,
10031
- ], 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 [texts]=\"texts\"\n [maxFileSize]=\"maxSizeMb * 1024 * 1024\"\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 [texts]=\"texts\"\n (photoCaptured)=\"uploadImage($event)\"\n ></photo-capture-widget>\n }\n @if (isRecorderOpen()) {\n <voice-record-widget\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]=\"maxSizeMb * 1024 * 1024\"\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{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"] }]
10615
+ ], 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"] }]
10032
10616
  }], propDecorators: { fileUploadMaterial: [{
10033
10617
  type: ViewChild,
10034
10618
  args: ['fileUploadMaterial']
@@ -10038,25 +10622,28 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImpor
10038
10622
  }], videoCapture: [{
10039
10623
  type: ViewChild,
10040
10624
  args: ['videoCapture']
10041
- }], 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 }] }], uploadFilesEvent: [{ type: i0.Output, args: ["uploadFilesEvent"] }] } });
10625
+ }], 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"] }] } });
10042
10626
  const CAPTURE_BUTTONS = {
10043
10627
  [FileUploaderSource.CAMERA_PHOTO]: {
10044
10628
  icon: 'photo_camera',
10045
10629
  cssClass: 'capturePhotoButton',
10046
10630
  tooltip: 'capturePhoto',
10047
10631
  noun: 'photo',
10632
+ dialogTitle: 'photoDialogTitle',
10048
10633
  },
10049
10634
  [FileUploaderSource.CAMERA_VIDEO]: {
10050
10635
  icon: 'videocam',
10051
10636
  cssClass: 'captureVideoButton',
10052
10637
  tooltip: 'captureVideo',
10053
10638
  noun: 'video',
10639
+ dialogTitle: 'videoDialogTitle',
10054
10640
  },
10055
10641
  [FileUploaderSource.MICROPHONE]: {
10056
10642
  icon: 'mic',
10057
10643
  cssClass: 'recordAudioButton',
10058
10644
  tooltip: 'recordAudio',
10059
10645
  noun: 'audio',
10646
+ dialogTitle: 'audioDialogTitle',
10060
10647
  },
10061
10648
  };
10062
10649
 
@@ -10376,7 +10963,7 @@ class SmartFileEditorComponent {
10376
10963
  useExisting: forwardRef(() => SmartFileEditorComponent),
10377
10964
  multi: true,
10378
10965
  },
10379
- ], 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 [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:#f1f1f1;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"], 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"] }] }); }
10966
+ ], 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"] }] }); }
10380
10967
  }
10381
10968
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: SmartFileEditorComponent, decorators: [{
10382
10969
  type: Component,
@@ -10386,7 +10973,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImpor
10386
10973
  useExisting: forwardRef(() => SmartFileEditorComponent),
10387
10974
  multi: true,
10388
10975
  },
10389
- ], 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 [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:#f1f1f1;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"] }]
10976
+ ], 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"] }]
10390
10977
  }], propDecorators: { widgetInstance: [{
10391
10978
  type: Input
10392
10979
  }], uploadFilesEvent: [{ type: i0.Output, args: ["uploadFilesEvent"] }] } });
@@ -11457,7 +12044,7 @@ class SmartMultiFileEditorComponent {
11457
12044
  useExisting: forwardRef(() => SmartMultiFileEditorComponent),
11458
12045
  multi: true,
11459
12046
  },
11460
- ], 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 [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:#f1f1f1}.playTile{display:flex;align-items:center;justify-content:center;padding:0;border:none;background:#2f3550;color:#fff;cursor:pointer}.playTile.audioTile{background:#e8eaf6;color:var(--primary-color)}.playTile.open{outline:2px solid var(--primary-color);outline-offset:1px}.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"], outputs: ["uploadFilesEvent"] }] }); }
12047
+ ], 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"] }] }); }
11461
12048
  }
11462
12049
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: SmartMultiFileEditorComponent, decorators: [{
11463
12050
  type: Component,
@@ -11474,7 +12061,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImpor
11474
12061
  UiActionToolbarComponent,
11475
12062
  SmartIconComponent,
11476
12063
  UploadWidgetComponent,
11477
- ], 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 [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:#f1f1f1}.playTile{display:flex;align-items:center;justify-content:center;padding:0;border:none;background:#2f3550;color:#fff;cursor:pointer}.playTile.audioTile{background:#e8eaf6;color:var(--primary-color)}.playTile.open{outline:2px solid var(--primary-color);outline-offset:1px}.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"] }]
12064
+ ], 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"] }]
11478
12065
  }], propDecorators: { widgetInstance: [{
11479
12066
  type: Input
11480
12067
  }], uploadFilesEvent: [{ type: i0.Output, args: ["uploadFilesEvent"] }], fileEvent: [{ type: i0.Output, args: ["fileEvent"] }] } });
@@ -11704,7 +12291,7 @@ class SmartVoiceRecorderComponent {
11704
12291
  useExisting: forwardRef(() => SmartVoiceRecorderComponent),
11705
12292
  multi: true,
11706
12293
  },
11707
- ], 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"], outputs: ["recordingSaved"] }] }); }
12294
+ ], 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"], outputs: ["recordingSaved"] }] }); }
11708
12295
  }
11709
12296
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: SmartVoiceRecorderComponent, decorators: [{
11710
12297
  type: Component,
@@ -16425,7 +17012,8 @@ class Table {
16425
17012
  renderRows() {
16426
17013
  this.createCellToActionMap();
16427
17014
  this.rowMenuActionModelArrayCache.clear();
16428
- this.myTableChild.renderRows();
17015
+ // The mobile template renders from its own loop and has no material table.
17016
+ this.myTableChild?.renderRows();
16429
17017
  }
16430
17018
  createCellToActionMap() {
16431
17019
  this.cellToActionMap = SmartGridToolbarActionsUtil.calculateCellToActionMap(this.smartTable.tableRows);
@@ -16782,6 +17370,10 @@ class Table {
16782
17370
  }
16783
17371
  }
16784
17372
  async onToggle(element, event) {
17373
+ // The mobile table has no material table, so no detail row to open.
17374
+ if (!this.myTableChild) {
17375
+ return;
17376
+ }
16785
17377
  if (!this.smartTable.rowExpander) {
16786
17378
  throw new Error('SmartTableRowExpander not found!. Please provide a SmartTableRowExpander implementation.');
16787
17379
  }
@@ -17259,11 +17851,11 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImpor
17259
17851
 
17260
17852
  class MobileTableComponent extends Table {
17261
17853
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: MobileTableComponent, deps: null, target: i0.ɵɵFactoryTarget.Component }); }
17262
- 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" }] }); }
17854
+ 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" }] }); }
17263
17855
  }
17264
17856
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: MobileTableComponent, decorators: [{
17265
17857
  type: Component,
17266
- 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"] }]
17858
+ 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"] }]
17267
17859
  }] });
17268
17860
 
17269
17861
  class SmarttableComponent {
@@ -28437,7 +29029,7 @@ class UiActionFileUploadDialogComponent {
28437
29029
  this.service.cancel();
28438
29030
  }
28439
29031
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: UiActionFileUploadDialogComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
28440
- 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"], 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"] }] }); }
29032
+ 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"] }] }); }
28441
29033
  }
28442
29034
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: UiActionFileUploadDialogComponent, decorators: [{
28443
29035
  type: Component,
@@ -29312,5 +29904,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImpor
29312
29904
  * Generated bundle index. Do not edit.
29313
29905
  */
29314
29906
 
29315
- 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, 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 };
29907
+ 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 };
29316
29908
  //# sourceMappingURL=smartbit4all-ng-client.mjs.map