@propbinder/mobile-design 0.2.36 → 0.2.40

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.
@@ -5,16 +5,17 @@ import { CommonModule, isPlatformBrowser } from '@angular/common';
5
5
  import * as i1$4 from '@angular/router';
6
6
  import { Router, NavigationEnd } from '@angular/router';
7
7
  import * as i1 from '@ionic/angular/standalone';
8
- import { ModalController, IonHeader, IonToolbar, IonTitle, IonButtons, IonContent, Platform, IonRefresher, IonRefresherContent, IonPopover, IonTabBar, IonTabButton, IonLabel, IonTabs, IonTab, IonSpinner, IonInfiniteScroll, IonInfiniteScrollContent } from '@ionic/angular/standalone';
8
+ import { ModalController, IonHeader, IonToolbar, IonTitle, IonButtons, IonContent, Platform, IonRefresher, IonRefresherContent, IonPopover, IonTabBar, IonTabButton, IonLabel, IonTabs, IonTab, IonSpinner, IonPicker, IonPickerColumn, IonPickerColumnOption, IonInfiniteScroll, IonInfiniteScrollContent } from '@ionic/angular/standalone';
9
9
  import { ImpactStyle, Haptics } from '@capacitor/haptics';
10
- import { DsIconButtonComponent, DsIconComponent, DsButtonComponent, DsAvatarComponent, DsShapeIndicatorComponent, DsTextareaComponent, DsCheckboxComponent, DsInputTimeComponent, DsLabelComponent, DsInputComponent, DsBadgeComponent } from '@propbinder/design-system';
10
+ import { DsIconButtonComponent, DsIconComponent, DsButtonComponent, DsAvatarComponent, DsShapeIndicatorComponent, DsTextareaComponent, DsDatepickerComponent, DsCheckboxComponent, DsInputTimeComponent, DsLabelComponent, DsInputComponent, DsBadgeComponent } from '@propbinder/design-system';
11
11
  import { StatusBar, Style } from '@capacitor/status-bar';
12
12
  import { Network } from '@capacitor/network';
13
13
  import { Keyboard } from '@capacitor/keyboard';
14
14
  import * as i1$1 from '@angular/platform-browser';
15
15
  import * as i1$3 from '@angular/forms';
16
16
  import { FormsModule, NG_VALUE_ACCESSOR } from '@angular/forms';
17
- import { Camera, CameraSource, CameraResultType } from '@capacitor/camera';
17
+ import { Capacitor } from '@capacitor/core';
18
+ import { FilePicker } from '@capawesome/capacitor-file-picker';
18
19
  import { Subject } from 'rxjs';
19
20
  import { createAnimation } from '@ionic/core';
20
21
  import { filter } from 'rxjs/operators';
@@ -23,7 +24,7 @@ import { Pagination } from 'swiper/modules';
23
24
  import { Share } from '@capacitor/share';
24
25
  import { Filesystem, Directory } from '@capacitor/filesystem';
25
26
  import { Browser } from '@capacitor/browser';
26
- import { Capacitor } from '@capacitor/core';
27
+ import { AppTrackingTransparency } from 'capacitor-plugin-app-tracking-transparency';
27
28
 
28
29
  const DEFAULT_CONFIG = {
29
30
  logoUrl: '/Assets/logos/propbinder-logomark.svg',
@@ -2498,17 +2499,18 @@ class DsMobilePostCreateBottomSheetComponent {
2498
2499
  this.applySafeAreaToToolbar();
2499
2500
  try {
2500
2501
  console.log('Requesting photo from library...');
2501
- const image = await Camera.getPhoto({
2502
- quality: 90,
2503
- allowEditing: false,
2504
- resultType: CameraResultType.Uri,
2505
- source: CameraSource.Photos, // Only show photo library, not camera
2502
+ const result = await FilePicker.pickImages({
2503
+ limit: 1,
2506
2504
  });
2505
+ const image = result.files?.[0];
2507
2506
  console.log('Photo selected successfully:', image);
2508
2507
  // Add the image path to the array
2509
- if (image.webPath) {
2510
- this.selectedImages.update(images => [...images, image.webPath]);
2511
- console.log('Image added to preview:', image.webPath);
2508
+ if (image) {
2509
+ const imageSrc = image.path ? Capacitor.convertFileSrc(image.path) : (image.blob ? URL.createObjectURL(image.blob) : '');
2510
+ if (imageSrc) {
2511
+ this.selectedImages.update(images => [...images, imageSrc]);
2512
+ console.log('Image added to preview:', imageSrc);
2513
+ }
2512
2514
  }
2513
2515
  // Re-apply safe area padding immediately after returning
2514
2516
  // Since we're using fixed values, this won't cause flickering
@@ -5726,6 +5728,7 @@ class DsMobileSectionComponent {
5726
5728
  <ds-icon name="remixArrowRightSLine" size="16px" />
5727
5729
  </a>
5728
5730
  }
5731
+ <ng-content select="[header-action]" />
5729
5732
  </header>
5730
5733
  }
5731
5734
 
@@ -5757,6 +5760,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImpo
5757
5760
  <ds-icon name="remixArrowRightSLine" size="16px" />
5758
5761
  </a>
5759
5762
  }
5763
+ <ng-content select="[header-action]" />
5760
5764
  </header>
5761
5765
  }
5762
5766
 
@@ -6862,9 +6866,15 @@ class DsMobileDropdownComponent {
6862
6866
  });
6863
6867
  }
6864
6868
  /**
6865
- * Content projection for custom item template
6869
+ * Content projection for custom item template (per-item renderer)
6866
6870
  */
6867
6871
  customItemTemplate;
6872
+ /**
6873
+ * Content projection for fully custom popover content.
6874
+ * When provided, the item list is bypassed entirely and this template
6875
+ * is rendered directly inside the popover — use for pickers, forms, etc.
6876
+ */
6877
+ customContent;
6868
6878
  /**
6869
6879
  * Optional trigger element ID for Ionic Popover positioning
6870
6880
  */
@@ -6875,9 +6885,10 @@ class DsMobileDropdownComponent {
6875
6885
  */
6876
6886
  keepFocusOn = input(...(ngDevMode ? [undefined, { debugName: "keepFocusOn" }] : []));
6877
6887
  /**
6878
- * Array of dropdown items to display
6888
+ * Array of dropdown items to display.
6889
+ * Not required when using the #customContent slot.
6879
6890
  */
6880
- items = input.required(...(ngDevMode ? [{ debugName: "items" }] : []));
6891
+ items = input([], ...(ngDevMode ? [{ debugName: "items" }] : []));
6881
6892
  /**
6882
6893
  * Whether the dropdown is open
6883
6894
  */
@@ -6906,6 +6917,12 @@ class DsMobileDropdownComponent {
6906
6917
  * ARIA label for the dropdown menu
6907
6918
  */
6908
6919
  ariaLabel = input('Dropdown menu', ...(ngDevMode ? [{ debugName: "ariaLabel" }] : []));
6920
+ /**
6921
+ * Maximum width of the popover.
6922
+ * Defaults to '192px' (standard dropdown width).
6923
+ * Override when using #customContent that requires more space (e.g. '280px' for a picker).
6924
+ */
6925
+ maxWidth = input('192px', ...(ngDevMode ? [{ debugName: "maxWidth" }] : []));
6909
6926
  /**
6910
6927
  * Emits when an item is selected
6911
6928
  */
@@ -6967,7 +6984,7 @@ class DsMobileDropdownComponent {
6967
6984
  this.itemSelected.emit(item);
6968
6985
  }
6969
6986
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: DsMobileDropdownComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
6970
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.16", type: DsMobileDropdownComponent, isStandalone: true, selector: "ds-mobile-dropdown", inputs: { trigger: { classPropertyName: "trigger", publicName: "trigger", isSignal: true, isRequired: false, transformFunction: null }, keepFocusOn: { classPropertyName: "keepFocusOn", publicName: "keepFocusOn", isSignal: true, isRequired: false, transformFunction: null }, items: { classPropertyName: "items", publicName: "items", isSignal: true, isRequired: true, transformFunction: null }, isOpen: { classPropertyName: "isOpen", publicName: "isOpen", isSignal: true, isRequired: false, transformFunction: null }, position: { classPropertyName: "position", publicName: "position", isSignal: true, isRequired: false, transformFunction: null }, align: { classPropertyName: "align", publicName: "align", isSignal: true, isRequired: false, transformFunction: null }, maxHeight: { classPropertyName: "maxHeight", publicName: "maxHeight", isSignal: true, isRequired: false, transformFunction: null }, emptyMessage: { classPropertyName: "emptyMessage", publicName: "emptyMessage", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { itemSelected: "itemSelected", closed: "closed" }, queries: [{ propertyName: "customItemTemplate", first: true, predicate: ["itemTemplate"], descendants: true }], ngImport: i0, template: `
6987
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.16", type: DsMobileDropdownComponent, isStandalone: true, selector: "ds-mobile-dropdown", inputs: { trigger: { classPropertyName: "trigger", publicName: "trigger", isSignal: true, isRequired: false, transformFunction: null }, keepFocusOn: { classPropertyName: "keepFocusOn", publicName: "keepFocusOn", isSignal: true, isRequired: false, transformFunction: null }, items: { classPropertyName: "items", publicName: "items", isSignal: true, isRequired: false, transformFunction: null }, isOpen: { classPropertyName: "isOpen", publicName: "isOpen", isSignal: true, isRequired: false, transformFunction: null }, position: { classPropertyName: "position", publicName: "position", isSignal: true, isRequired: false, transformFunction: null }, align: { classPropertyName: "align", publicName: "align", isSignal: true, isRequired: false, transformFunction: null }, maxHeight: { classPropertyName: "maxHeight", publicName: "maxHeight", isSignal: true, isRequired: false, transformFunction: null }, emptyMessage: { classPropertyName: "emptyMessage", publicName: "emptyMessage", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: false, transformFunction: null }, maxWidth: { classPropertyName: "maxWidth", publicName: "maxWidth", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { itemSelected: "itemSelected", closed: "closed" }, queries: [{ propertyName: "customItemTemplate", first: true, predicate: ["itemTemplate"], descendants: true }, { propertyName: "customContent", first: true, predicate: ["customContent"], descendants: true }], ngImport: i0, template: `
6971
6988
  <ion-popover
6972
6989
  [isOpen]="isOpen()"
6973
6990
  [trigger]="trigger()"
@@ -6981,71 +6998,77 @@ class DsMobileDropdownComponent {
6981
6998
  (didDismiss)="closed.emit()"
6982
6999
  [style.--offset-y]="offsetY()"
6983
7000
  [style.--offset-x]="offsetX()"
6984
- [style.--max-width]="'192px'"
7001
+ [style.--max-width]="maxWidth()"
6985
7002
  [style.--background]="'transparent'"
6986
7003
  [style.--box-shadow]="'none'"
6987
7004
  [style.--backdrop-opacity]="'0'">
6988
7005
 
6989
7006
  <ng-template>
6990
- <div
6991
- [class]="dropdownClasses()"
6992
- [style.max-height.px]="maxHeight()"
6993
- (mousedown)="$event.stopPropagation()"
6994
- (click)="$event.stopPropagation()"
6995
- role="menu"
6996
- [attr.aria-label]="ariaLabel()">
6997
-
6998
- @if (customItemTemplate) {
6999
- <!-- Custom template for each item -->
7000
- @for (item of items(); track item.id) {
7001
- <div
7002
- [class]="getItemClass(item)"
7003
- [attr.data-disabled]="item.disabled ? '' : null"
7004
- [attr.aria-disabled]="item.disabled"
7005
- (mousedown)="handleItemClick(item, $event)"
7006
- role="menuitem">
7007
- <ng-container
7008
- *ngTemplateOutlet="customItemTemplate; context: { $implicit: item }" />
7009
- </div>
7007
+ @if (customContent) {
7008
+ <!-- Fully custom popover content (e.g. picker, form) -->
7009
+ <ng-container *ngTemplateOutlet="customContent" />
7010
+ } @else {
7011
+ <!-- Standard item list -->
7012
+ <div
7013
+ [class]="dropdownClasses()"
7014
+ [style.max-height.px]="maxHeight()"
7015
+ (mousedown)="$event.stopPropagation()"
7016
+ (click)="$event.stopPropagation()"
7017
+ role="menu"
7018
+ [attr.aria-label]="ariaLabel()">
7019
+
7020
+ @if (customItemTemplate) {
7021
+ <!-- Custom template for each item -->
7022
+ @for (item of items(); track item.id) {
7023
+ <div
7024
+ [class]="getItemClass(item)"
7025
+ [attr.data-disabled]="item.disabled ? '' : null"
7026
+ [attr.aria-disabled]="item.disabled"
7027
+ (mousedown)="handleItemClick(item, $event)"
7028
+ role="menuitem">
7029
+ <ng-container
7030
+ *ngTemplateOutlet="customItemTemplate; context: { $implicit: item }" />
7031
+ </div>
7032
+ }
7033
+ } @else {
7034
+ <!-- Default three-slot template: leading - main - trailing -->
7035
+ @for (item of items(); track item.id) {
7036
+ <div
7037
+ [class]="getItemClass(item)"
7038
+ [attr.data-disabled]="item.disabled ? '' : null"
7039
+ [attr.aria-disabled]="item.disabled"
7040
+ (mousedown)="handleItemClick(item, $event)"
7041
+ role="menuitem">
7042
+
7043
+ <!-- Leading slot -->
7044
+ @if (item.leadingIcon) {
7045
+ <div class="ds-mobile-dropdown__slot ds-mobile-dropdown__slot--leading">
7046
+ <ds-icon [name]="item.leadingIcon" size="16px" />
7047
+ </div>
7048
+ }
7049
+
7050
+ <!-- Main slot -->
7051
+ @if (item.label) {
7052
+ <div class="ds-mobile-dropdown__slot ds-mobile-dropdown__slot--main">
7053
+ <span class="ds-mobile-dropdown__label">{{ item.label }}</span>
7054
+ </div>
7055
+ }
7056
+
7057
+ <!-- Trailing slot -->
7058
+ @if (item.trailingIcon) {
7059
+ <div class="ds-mobile-dropdown__slot ds-mobile-dropdown__slot--trailing">
7060
+ <ds-icon [name]="item.trailingIcon" size="20px" />
7061
+ </div>
7062
+ }
7063
+ </div>
7064
+ }
7010
7065
  }
7011
- } @else {
7012
- <!-- Default three-slot template: leading - main - trailing -->
7013
- @for (item of items(); track item.id) {
7014
- <div
7015
- [class]="getItemClass(item)"
7016
- [attr.data-disabled]="item.disabled ? '' : null"
7017
- [attr.aria-disabled]="item.disabled"
7018
- (mousedown)="handleItemClick(item, $event)"
7019
- role="menuitem">
7020
-
7021
- <!-- Leading slot -->
7022
- @if (item.leadingIcon) {
7023
- <div class="ds-mobile-dropdown__slot ds-mobile-dropdown__slot--leading">
7024
- <ds-icon [name]="item.leadingIcon" size="16px" />
7025
- </div>
7026
- }
7027
-
7028
- <!-- Main slot -->
7029
- @if (item.label) {
7030
- <div class="ds-mobile-dropdown__slot ds-mobile-dropdown__slot--main">
7031
- <span class="ds-mobile-dropdown__label">{{ item.label }}</span>
7032
- </div>
7033
- }
7034
-
7035
- <!-- Trailing slot -->
7036
- @if (item.trailingIcon) {
7037
- <div class="ds-mobile-dropdown__slot ds-mobile-dropdown__slot--trailing">
7038
- <ds-icon [name]="item.trailingIcon" size="20px" />
7039
- </div>
7040
- }
7041
- </div>
7066
+
7067
+ @if (items().length === 0 && emptyMessage()) {
7068
+ <div class="ds-mobile-dropdown__empty">{{ emptyMessage() }}</div>
7042
7069
  }
7043
- }
7044
-
7045
- @if (items().length === 0 && emptyMessage()) {
7046
- <div class="ds-mobile-dropdown__empty">{{ emptyMessage() }}</div>
7047
- }
7048
- </div>
7070
+ </div>
7071
+ }
7049
7072
  </ng-template>
7050
7073
  </ion-popover>
7051
7074
  `, isInline: true, styles: ["ion-popover{--background: transparent;--box-shadow: none;--backdrop-opacity: 0;--max-width: 192px}ion-popover::part(content){background:#ffffff80!important;backdrop-filter:blur(12px)!important;-webkit-backdrop-filter:blur(12px)!important;box-shadow:0 2px 8px #00000014,0 4px 16px #0000001f!important;border:1px solid rgba(255,255,255,1)!important;border-radius:16px!important;padding:4px!important;outline:none!important;max-width:192px!important}ion-popover::part(backdrop){background:transparent!important}.ds-mobile-dropdown{position:relative;width:100%;background:transparent!important;border:none!important;box-shadow:none!important;overflow-y:auto;animation:slideIn var(--spring-duration-medium) var(--spring-curve-bouncy);padding:0}.ds-mobile-dropdown__item{display:flex;align-items:center;gap:12px;padding:4px 12px;background:none;width:100%;text-align:left;cursor:pointer;transition:background .15s ease;min-height:40px;justify-content:flex-start;border-radius:14px}.ds-mobile-dropdown__item:last-child{border-bottom:none}.ds-mobile-dropdown__item:hover:not(.ds-mobile-dropdown__item--disabled){background:#0000000a}.ds-mobile-dropdown__item:active:not(.ds-mobile-dropdown__item--disabled){background:#00000014;transition:background 0s}.ds-mobile-dropdown__item--disabled{opacity:.5;cursor:not-allowed;pointer-events:none}.ds-mobile-dropdown__slot{display:flex;align-items:center}.ds-mobile-dropdown__slot--leading{flex-shrink:0;justify-content:center}.ds-mobile-dropdown__slot--main{flex:1;min-width:0;display:flex;flex-direction:column;gap:2px;text-align:left;align-items:flex-start}.ds-mobile-dropdown__slot--trailing{flex-shrink:0;margin-left:auto}.ds-mobile-dropdown__label{font-family:Brockmann,sans-serif;font-size:var(--font-size-sm);font-weight:600;line-height:20px;color:var(--color-text-primary, #1a1a1a);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;text-align:left}.ds-mobile-dropdown__empty{padding:24px 16px;text-align:center;font-family:Brockmann,sans-serif;font-size:var(--font-size-sm);line-height:18px;color:var(--color-text-secondary, #737373)}@keyframes slideIn{0%{opacity:0;transform:translateY(8px) scale(.75)}to{opacity:1;transform:translateY(0) scale(1)}}.ds-mobile-dropdown--above{animation:slideUp var(--spring-duration-medium) var(--spring-curve-bouncy)}@keyframes slideOut{0%{opacity:1;transform:translateY(0) scale(1)}to{opacity:0;transform:translateY(8px) scale(.75)}}@keyframes slideDown{0%{opacity:1;transform:translateY(0) scale(1)}to{opacity:0;transform:translateY(-8px) scale(.75)}}@keyframes slideUp{0%{opacity:0;transform:translateY(-8px) scale(.75)}to{opacity:1;transform:translateY(0) scale(1)}}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$2.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: DsIconComponent, selector: "ds-icon", inputs: ["name", "size", "color", "interactive"] }, { kind: "component", type: IonPopover, selector: "ion-popover" }] });
@@ -7066,78 +7089,87 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImpo
7066
7089
  (didDismiss)="closed.emit()"
7067
7090
  [style.--offset-y]="offsetY()"
7068
7091
  [style.--offset-x]="offsetX()"
7069
- [style.--max-width]="'192px'"
7092
+ [style.--max-width]="maxWidth()"
7070
7093
  [style.--background]="'transparent'"
7071
7094
  [style.--box-shadow]="'none'"
7072
7095
  [style.--backdrop-opacity]="'0'">
7073
7096
 
7074
7097
  <ng-template>
7075
- <div
7076
- [class]="dropdownClasses()"
7077
- [style.max-height.px]="maxHeight()"
7078
- (mousedown)="$event.stopPropagation()"
7079
- (click)="$event.stopPropagation()"
7080
- role="menu"
7081
- [attr.aria-label]="ariaLabel()">
7082
-
7083
- @if (customItemTemplate) {
7084
- <!-- Custom template for each item -->
7085
- @for (item of items(); track item.id) {
7086
- <div
7087
- [class]="getItemClass(item)"
7088
- [attr.data-disabled]="item.disabled ? '' : null"
7089
- [attr.aria-disabled]="item.disabled"
7090
- (mousedown)="handleItemClick(item, $event)"
7091
- role="menuitem">
7092
- <ng-container
7093
- *ngTemplateOutlet="customItemTemplate; context: { $implicit: item }" />
7094
- </div>
7098
+ @if (customContent) {
7099
+ <!-- Fully custom popover content (e.g. picker, form) -->
7100
+ <ng-container *ngTemplateOutlet="customContent" />
7101
+ } @else {
7102
+ <!-- Standard item list -->
7103
+ <div
7104
+ [class]="dropdownClasses()"
7105
+ [style.max-height.px]="maxHeight()"
7106
+ (mousedown)="$event.stopPropagation()"
7107
+ (click)="$event.stopPropagation()"
7108
+ role="menu"
7109
+ [attr.aria-label]="ariaLabel()">
7110
+
7111
+ @if (customItemTemplate) {
7112
+ <!-- Custom template for each item -->
7113
+ @for (item of items(); track item.id) {
7114
+ <div
7115
+ [class]="getItemClass(item)"
7116
+ [attr.data-disabled]="item.disabled ? '' : null"
7117
+ [attr.aria-disabled]="item.disabled"
7118
+ (mousedown)="handleItemClick(item, $event)"
7119
+ role="menuitem">
7120
+ <ng-container
7121
+ *ngTemplateOutlet="customItemTemplate; context: { $implicit: item }" />
7122
+ </div>
7123
+ }
7124
+ } @else {
7125
+ <!-- Default three-slot template: leading - main - trailing -->
7126
+ @for (item of items(); track item.id) {
7127
+ <div
7128
+ [class]="getItemClass(item)"
7129
+ [attr.data-disabled]="item.disabled ? '' : null"
7130
+ [attr.aria-disabled]="item.disabled"
7131
+ (mousedown)="handleItemClick(item, $event)"
7132
+ role="menuitem">
7133
+
7134
+ <!-- Leading slot -->
7135
+ @if (item.leadingIcon) {
7136
+ <div class="ds-mobile-dropdown__slot ds-mobile-dropdown__slot--leading">
7137
+ <ds-icon [name]="item.leadingIcon" size="16px" />
7138
+ </div>
7139
+ }
7140
+
7141
+ <!-- Main slot -->
7142
+ @if (item.label) {
7143
+ <div class="ds-mobile-dropdown__slot ds-mobile-dropdown__slot--main">
7144
+ <span class="ds-mobile-dropdown__label">{{ item.label }}</span>
7145
+ </div>
7146
+ }
7147
+
7148
+ <!-- Trailing slot -->
7149
+ @if (item.trailingIcon) {
7150
+ <div class="ds-mobile-dropdown__slot ds-mobile-dropdown__slot--trailing">
7151
+ <ds-icon [name]="item.trailingIcon" size="20px" />
7152
+ </div>
7153
+ }
7154
+ </div>
7155
+ }
7095
7156
  }
7096
- } @else {
7097
- <!-- Default three-slot template: leading - main - trailing -->
7098
- @for (item of items(); track item.id) {
7099
- <div
7100
- [class]="getItemClass(item)"
7101
- [attr.data-disabled]="item.disabled ? '' : null"
7102
- [attr.aria-disabled]="item.disabled"
7103
- (mousedown)="handleItemClick(item, $event)"
7104
- role="menuitem">
7105
-
7106
- <!-- Leading slot -->
7107
- @if (item.leadingIcon) {
7108
- <div class="ds-mobile-dropdown__slot ds-mobile-dropdown__slot--leading">
7109
- <ds-icon [name]="item.leadingIcon" size="16px" />
7110
- </div>
7111
- }
7112
-
7113
- <!-- Main slot -->
7114
- @if (item.label) {
7115
- <div class="ds-mobile-dropdown__slot ds-mobile-dropdown__slot--main">
7116
- <span class="ds-mobile-dropdown__label">{{ item.label }}</span>
7117
- </div>
7118
- }
7119
-
7120
- <!-- Trailing slot -->
7121
- @if (item.trailingIcon) {
7122
- <div class="ds-mobile-dropdown__slot ds-mobile-dropdown__slot--trailing">
7123
- <ds-icon [name]="item.trailingIcon" size="20px" />
7124
- </div>
7125
- }
7126
- </div>
7157
+
7158
+ @if (items().length === 0 && emptyMessage()) {
7159
+ <div class="ds-mobile-dropdown__empty">{{ emptyMessage() }}</div>
7127
7160
  }
7128
- }
7129
-
7130
- @if (items().length === 0 && emptyMessage()) {
7131
- <div class="ds-mobile-dropdown__empty">{{ emptyMessage() }}</div>
7132
- }
7133
- </div>
7161
+ </div>
7162
+ }
7134
7163
  </ng-template>
7135
7164
  </ion-popover>
7136
7165
  `, styles: ["ion-popover{--background: transparent;--box-shadow: none;--backdrop-opacity: 0;--max-width: 192px}ion-popover::part(content){background:#ffffff80!important;backdrop-filter:blur(12px)!important;-webkit-backdrop-filter:blur(12px)!important;box-shadow:0 2px 8px #00000014,0 4px 16px #0000001f!important;border:1px solid rgba(255,255,255,1)!important;border-radius:16px!important;padding:4px!important;outline:none!important;max-width:192px!important}ion-popover::part(backdrop){background:transparent!important}.ds-mobile-dropdown{position:relative;width:100%;background:transparent!important;border:none!important;box-shadow:none!important;overflow-y:auto;animation:slideIn var(--spring-duration-medium) var(--spring-curve-bouncy);padding:0}.ds-mobile-dropdown__item{display:flex;align-items:center;gap:12px;padding:4px 12px;background:none;width:100%;text-align:left;cursor:pointer;transition:background .15s ease;min-height:40px;justify-content:flex-start;border-radius:14px}.ds-mobile-dropdown__item:last-child{border-bottom:none}.ds-mobile-dropdown__item:hover:not(.ds-mobile-dropdown__item--disabled){background:#0000000a}.ds-mobile-dropdown__item:active:not(.ds-mobile-dropdown__item--disabled){background:#00000014;transition:background 0s}.ds-mobile-dropdown__item--disabled{opacity:.5;cursor:not-allowed;pointer-events:none}.ds-mobile-dropdown__slot{display:flex;align-items:center}.ds-mobile-dropdown__slot--leading{flex-shrink:0;justify-content:center}.ds-mobile-dropdown__slot--main{flex:1;min-width:0;display:flex;flex-direction:column;gap:2px;text-align:left;align-items:flex-start}.ds-mobile-dropdown__slot--trailing{flex-shrink:0;margin-left:auto}.ds-mobile-dropdown__label{font-family:Brockmann,sans-serif;font-size:var(--font-size-sm);font-weight:600;line-height:20px;color:var(--color-text-primary, #1a1a1a);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;text-align:left}.ds-mobile-dropdown__empty{padding:24px 16px;text-align:center;font-family:Brockmann,sans-serif;font-size:var(--font-size-sm);line-height:18px;color:var(--color-text-secondary, #737373)}@keyframes slideIn{0%{opacity:0;transform:translateY(8px) scale(.75)}to{opacity:1;transform:translateY(0) scale(1)}}.ds-mobile-dropdown--above{animation:slideUp var(--spring-duration-medium) var(--spring-curve-bouncy)}@keyframes slideOut{0%{opacity:1;transform:translateY(0) scale(1)}to{opacity:0;transform:translateY(8px) scale(.75)}}@keyframes slideDown{0%{opacity:1;transform:translateY(0) scale(1)}to{opacity:0;transform:translateY(-8px) scale(.75)}}@keyframes slideUp{0%{opacity:0;transform:translateY(-8px) scale(.75)}to{opacity:1;transform:translateY(0) scale(1)}}\n"] }]
7137
7166
  }], ctorParameters: () => [], propDecorators: { customItemTemplate: [{
7138
7167
  type: ContentChild,
7139
7168
  args: ['itemTemplate']
7140
- }], trigger: [{ type: i0.Input, args: [{ isSignal: true, alias: "trigger", required: false }] }], keepFocusOn: [{ type: i0.Input, args: [{ isSignal: true, alias: "keepFocusOn", required: false }] }], items: [{ type: i0.Input, args: [{ isSignal: true, alias: "items", required: true }] }], isOpen: [{ type: i0.Input, args: [{ isSignal: true, alias: "isOpen", required: false }] }], position: [{ type: i0.Input, args: [{ isSignal: true, alias: "position", required: false }] }], align: [{ type: i0.Input, args: [{ isSignal: true, alias: "align", required: false }] }], maxHeight: [{ type: i0.Input, args: [{ isSignal: true, alias: "maxHeight", required: false }] }], emptyMessage: [{ type: i0.Input, args: [{ isSignal: true, alias: "emptyMessage", required: false }] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabel", required: false }] }], itemSelected: [{ type: i0.Output, args: ["itemSelected"] }], closed: [{ type: i0.Output, args: ["closed"] }] } });
7169
+ }], customContent: [{
7170
+ type: ContentChild,
7171
+ args: ['customContent']
7172
+ }], trigger: [{ type: i0.Input, args: [{ isSignal: true, alias: "trigger", required: false }] }], keepFocusOn: [{ type: i0.Input, args: [{ isSignal: true, alias: "keepFocusOn", required: false }] }], items: [{ type: i0.Input, args: [{ isSignal: true, alias: "items", required: false }] }], isOpen: [{ type: i0.Input, args: [{ isSignal: true, alias: "isOpen", required: false }] }], position: [{ type: i0.Input, args: [{ isSignal: true, alias: "position", required: false }] }], align: [{ type: i0.Input, args: [{ isSignal: true, alias: "align", required: false }] }], maxHeight: [{ type: i0.Input, args: [{ isSignal: true, alias: "maxHeight", required: false }] }], emptyMessage: [{ type: i0.Input, args: [{ isSignal: true, alias: "emptyMessage", required: false }] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabel", required: false }] }], maxWidth: [{ type: i0.Input, args: [{ isSignal: true, alias: "maxWidth", required: false }] }], itemSelected: [{ type: i0.Output, args: ["itemSelected"] }], closed: [{ type: i0.Output, args: ["closed"] }] } });
7141
7173
 
7142
7174
  /**
7143
7175
  * DsMobileMessageComposerComponent
@@ -7164,6 +7196,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImpo
7164
7196
  * ```
7165
7197
  */
7166
7198
  class DsMobileMessageComposerComponent {
7199
+ cdr;
7200
+ constructor(cdr) {
7201
+ this.cdr = cdr;
7202
+ }
7167
7203
  /**
7168
7204
  * Avatar initials
7169
7205
  */
@@ -7258,41 +7294,40 @@ class DsMobileMessageComposerComponent {
7258
7294
  const users = this.mentionUsers();
7259
7295
  if (!query)
7260
7296
  return users;
7261
- return users.filter(user => user.name.toLowerCase().includes(query));
7297
+ return users.filter((user) => user.name.toLowerCase().includes(query));
7262
7298
  }, ...(ngDevMode ? [{ debugName: "filteredUsers" }] : []));
7263
7299
  /**
7264
7300
  * Convert filtered users to dropdown items
7265
7301
  */
7266
7302
  mentionDropdownItems = computed(() => {
7267
- return this.filteredUsers().map(user => ({
7303
+ return this.filteredUsers().map((user) => ({
7268
7304
  id: user.name,
7269
7305
  label: user.name,
7270
7306
  data: {
7271
7307
  name: user.name,
7272
7308
  initials: user.initials,
7273
- role: user.role
7274
- }
7309
+ role: user.role,
7310
+ },
7275
7311
  }));
7276
7312
  }, ...(ngDevMode ? [{ debugName: "mentionDropdownItems" }] : []));
7277
7313
  /**
7278
7314
  * Attachment menu items
7315
+ * Static list to prevent change detection loops
7279
7316
  */
7280
- attachmentMenuItems = computed(() => {
7281
- return [
7282
- {
7283
- id: 'photo',
7284
- leadingIcon: 'remixImageLine',
7285
- label: 'Photo',
7286
- action: () => this.handleAddPhoto()
7287
- },
7288
- {
7289
- id: 'file',
7290
- leadingIcon: 'remixFile3Line',
7291
- label: 'File',
7292
- action: () => this.handleAddFile()
7293
- }
7294
- ];
7295
- }, ...(ngDevMode ? [{ debugName: "attachmentMenuItems" }] : []));
7317
+ attachmentMenuItems = [
7318
+ {
7319
+ id: 'photo',
7320
+ leadingIcon: 'remixImageLine',
7321
+ label: 'Photo',
7322
+ action: () => this.handleAddPhoto(),
7323
+ },
7324
+ {
7325
+ id: 'file',
7326
+ leadingIcon: 'remixFile3Line',
7327
+ label: 'File',
7328
+ action: () => this.handleAddFile(),
7329
+ },
7330
+ ];
7296
7331
  /**
7297
7332
  * Emits when a message is sent
7298
7333
  */
@@ -7328,6 +7363,8 @@ class DsMobileMessageComposerComponent {
7328
7363
  }
7329
7364
  // Set up keyboard listeners
7330
7365
  this.setupKeyboardListeners();
7366
+ // Explicitly trigger change detection to avoid NG0100 with ViewChild bindings
7367
+ this.cdr.detectChanges();
7331
7368
  }
7332
7369
  ngOnDestroy() {
7333
7370
  // Clean up keyboard listeners
@@ -7339,22 +7376,22 @@ class DsMobileMessageComposerComponent {
7339
7376
  setupKeyboardListeners() {
7340
7377
  Keyboard.addListener('keyboardWillShow', (info) => {
7341
7378
  document.documentElement.style.setProperty('--keyboard-height', `${info.keyboardHeight}px`);
7342
- }).catch(e => console.log('Keyboard listeners not available:', e));
7379
+ }).catch(() => { });
7343
7380
  Keyboard.addListener('keyboardWillHide', () => {
7344
7381
  document.documentElement.style.setProperty('--keyboard-height', '0px');
7345
- }).catch(e => console.log('Keyboard listeners not available:', e));
7382
+ }).catch(() => { });
7346
7383
  }
7347
7384
  /**
7348
7385
  * Clean up keyboard event listeners
7349
7386
  */
7350
7387
  cleanupKeyboardListeners() {
7351
- Keyboard.removeAllListeners().catch(e => console.log('Keyboard cleanup not available:', e));
7388
+ Keyboard.removeAllListeners().catch(() => { });
7352
7389
  }
7353
7390
  /**
7354
7391
  * Show the keyboard when user interacts with input
7355
7392
  */
7356
7393
  showKeyboard() {
7357
- Keyboard.show().catch(e => console.log('Keyboard.show() not available'));
7394
+ Keyboard.show().catch(() => { });
7358
7395
  }
7359
7396
  /**
7360
7397
  * Handle keyboard shortcuts (Shift+Enter to send)
@@ -7527,7 +7564,7 @@ class DsMobileMessageComposerComponent {
7527
7564
  event.preventDefault();
7528
7565
  event.stopPropagation();
7529
7566
  }
7530
- this.isAttachmentMenuOpen.update(open => !open);
7567
+ this.isAttachmentMenuOpen.update((open) => !open);
7531
7568
  }
7532
7569
  /**
7533
7570
  * Close attachment menu
@@ -7541,7 +7578,7 @@ class DsMobileMessageComposerComponent {
7541
7578
  }
7542
7579
  /**
7543
7580
  * Handle add photo button click from menu
7544
- * Uses Capacitor Camera API to open photo library directly
7581
+ * Uses Capawesome File Picker API to open photo library directly
7545
7582
  * Allows multiple photo selection
7546
7583
  */
7547
7584
  async handleAddPhoto(event) {
@@ -7553,38 +7590,35 @@ class DsMobileMessageComposerComponent {
7553
7590
  return;
7554
7591
  }
7555
7592
  try {
7556
- console.log('[MessageComposer] Opening photo library');
7593
+ //console.log('[MessageComposer] Opening photo library');
7557
7594
  // Calculate remaining slots
7558
7595
  const remainingSlots = 6 - this.attachments().length;
7559
7596
  // Open photo library with multiple selection using pickImages
7560
- const result = await Camera.pickImages({
7561
- quality: 90,
7597
+ const result = await FilePicker.pickImages({
7562
7598
  limit: remainingSlots, // Limit to remaining slots
7563
7599
  });
7564
- if (result.photos && result.photos.length > 0) {
7565
- console.log(`[MessageComposer] ${result.photos.length} photo(s) selected`);
7600
+ if (result.files && result.files.length > 0) {
7601
+ //console.log(`[MessageComposer] ${result.files.length} photo(s) selected`);
7566
7602
  // Process each selected photo
7567
- for (const photo of result.photos) {
7603
+ for (const photo of result.files) {
7568
7604
  const attachmentId = `photo-${Date.now()}-${Math.random()}`;
7569
7605
  // Add attachment with loading state
7570
7606
  const loadingAttachment = {
7571
7607
  id: attachmentId,
7572
- src: photo.webPath || '',
7608
+ src: photo.path ? Capacitor.convertFileSrc(photo.path) : (photo.blob ? URL.createObjectURL(photo.blob) : ''),
7573
7609
  type: 'image',
7574
- name: `Photo ${this.attachments().length + 1}`,
7575
- size: '',
7576
- isLoading: true
7610
+ name: photo.name,
7611
+ size: this.formatFileSize(photo.size ?? 0),
7612
+ isLoading: true,
7577
7613
  };
7578
- this.attachments.update(attachments => [...attachments, loadingAttachment]);
7614
+ this.attachments.update((attachments) => [...attachments, loadingAttachment]);
7579
7615
  // Simulate processing time (in real app, this would be actual image processing)
7580
7616
  // TODO: Reduce to 300ms or remove in production
7581
7617
  setTimeout(() => {
7582
- this.attachments.update(attachments => attachments.map(a => a.id === attachmentId
7583
- ? { ...a, isLoading: false }
7584
- : a));
7618
+ this.attachments.update((attachments) => attachments.map((a) => (a.id === attachmentId ? { ...a, isLoading: false } : a)));
7585
7619
  }, 1500); // 1.5s for testing - shows loading overlay clearly
7586
7620
  }
7587
- console.log('[MessageComposer] All photos added successfully');
7621
+ //console.log('[MessageComposer] All photos added successfully');
7588
7622
  // Notify parent that attachments changed so it can scroll
7589
7623
  this.attachmentsChanged.emit();
7590
7624
  // ResizeObserver in MobileModalBase automatically handles layout adjustments
@@ -7592,7 +7626,7 @@ class DsMobileMessageComposerComponent {
7592
7626
  }
7593
7627
  catch (error) {
7594
7628
  if (error.message && !error.message.includes('cancel')) {
7595
- console.error('[MessageComposer] Error adding photo:', error);
7629
+ //console.error('[MessageComposer] Error adding photo:', error);
7596
7630
  }
7597
7631
  // User cancelled - that's fine
7598
7632
  }
@@ -7653,7 +7687,7 @@ class DsMobileMessageComposerComponent {
7653
7687
  const k = 1024;
7654
7688
  const sizes = ['B', 'KB', 'MB', 'GB'];
7655
7689
  const i = Math.floor(Math.log(bytes) / Math.log(k));
7656
- return Math.round(bytes / Math.pow(k, i) * 100) / 100 + ' ' + sizes[i];
7690
+ return Math.round((bytes / Math.pow(k, i)) * 100) / 100 + ' ' + sizes[i];
7657
7691
  }
7658
7692
  /**
7659
7693
  * Handle file selection from file input
@@ -7668,7 +7702,7 @@ class DsMobileMessageComposerComponent {
7668
7702
  // Process each selected file (up to 6 total)
7669
7703
  const remainingSlots = 6 - this.attachments().length;
7670
7704
  const filesToProcess = Array.from(files).slice(0, remainingSlots);
7671
- filesToProcess.forEach(file => {
7705
+ filesToProcess.forEach((file) => {
7672
7706
  const fileType = this.detectFileType(file);
7673
7707
  const attachmentId = `file-${Date.now()}-${Math.random()}`;
7674
7708
  // Add attachment with loading state immediately
@@ -7678,9 +7712,9 @@ class DsMobileMessageComposerComponent {
7678
7712
  type: fileType,
7679
7713
  name: file.name,
7680
7714
  size: this.formatFileSize(file.size),
7681
- isLoading: true
7715
+ isLoading: true,
7682
7716
  };
7683
- this.attachments.update(attachments => [...attachments, loadingAttachment]);
7717
+ this.attachments.update((attachments) => [...attachments, loadingAttachment]);
7684
7718
  // Create a data URL for preview
7685
7719
  const reader = new FileReader();
7686
7720
  reader.onload = (e) => {
@@ -7690,9 +7724,7 @@ class DsMobileMessageComposerComponent {
7690
7724
  // TODO: Remove setTimeout in production (use actual FileReader timing)
7691
7725
  setTimeout(() => {
7692
7726
  // Update attachment with actual data and remove loading state
7693
- this.attachments.update(attachments => attachments.map(a => a.id === attachmentId
7694
- ? { ...a, src: result, isLoading: false }
7695
- : a));
7727
+ this.attachments.update((attachments) => attachments.map((a) => (a.id === attachmentId ? { ...a, src: result, isLoading: false } : a)));
7696
7728
  // Notify parent that attachments changed so it can scroll
7697
7729
  setTimeout(() => {
7698
7730
  this.attachmentsChanged.emit();
@@ -7711,12 +7743,12 @@ class DsMobileMessageComposerComponent {
7711
7743
  * Keeps keyboard open by maintaining focus
7712
7744
  */
7713
7745
  removeAttachment(attachmentId) {
7714
- this.attachments.update(attachments => attachments.filter(a => a.id !== attachmentId));
7746
+ this.attachments.update((attachments) => attachments.filter((a) => a.id !== attachmentId));
7715
7747
  // Immediately refocus input to prevent keyboard from closing
7716
7748
  setTimeout(() => {
7717
7749
  if (this.messageInputRef?.nativeElement) {
7718
7750
  this.messageInputRef.nativeElement.focus();
7719
- Keyboard.show().catch(e => console.log('Keyboard.show() not available:', e));
7751
+ Keyboard.show().catch((e) => console.log('Keyboard.show() not available:', e));
7720
7752
  }
7721
7753
  }, 0);
7722
7754
  // Notify parent that attachments changed so it can scroll
@@ -7736,13 +7768,11 @@ class DsMobileMessageComposerComponent {
7736
7768
  const isReply = !!this.replyingTo();
7737
7769
  // Emit message sent event
7738
7770
  this.messageSent.emit({
7739
- content: isReply && this.replyingTo()
7740
- ? `@${this.replyingTo().authorName} ${text}`
7741
- : text,
7771
+ content: isReply && this.replyingTo() ? `@${this.replyingTo().authorName} ${text}` : text,
7742
7772
  isReply,
7743
7773
  replyTo: this.replyingTo()?.authorName,
7744
7774
  isEdit,
7745
- attachments: hasAttachments ? [...this.attachments()] : undefined
7775
+ attachments: hasAttachments ? [...this.attachments()] : undefined,
7746
7776
  });
7747
7777
  // Keep keyboard open by explicitly showing it before clearing
7748
7778
  // This prevents the keyboard from starting to close during the clear operation
@@ -7757,7 +7787,7 @@ class DsMobileMessageComposerComponent {
7757
7787
  this.messageInputRef.nativeElement.focus();
7758
7788
  }
7759
7789
  }
7760
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: DsMobileMessageComposerComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
7790
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: DsMobileMessageComposerComponent, deps: [{ token: i0.ChangeDetectorRef }], target: i0.ɵɵFactoryTarget.Component });
7761
7791
  static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.16", type: DsMobileMessageComposerComponent, isStandalone: true, selector: "ds-mobile-message-composer", inputs: { avatarInitials: { classPropertyName: "avatarInitials", publicName: "avatarInitials", isSignal: true, isRequired: false, transformFunction: null }, avatarType: { classPropertyName: "avatarType", publicName: "avatarType", isSignal: true, isRequired: false, transformFunction: null }, avatarSrc: { classPropertyName: "avatarSrc", publicName: "avatarSrc", isSignal: true, isRequired: false, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, sendButtonLabel: { classPropertyName: "sendButtonLabel", publicName: "sendButtonLabel", isSignal: true, isRequired: false, transformFunction: null }, attachmentButtonLabel: { classPropertyName: "attachmentButtonLabel", publicName: "attachmentButtonLabel", isSignal: true, isRequired: false, transformFunction: null }, showAttachmentButton: { classPropertyName: "showAttachmentButton", publicName: "showAttachmentButton", isSignal: true, isRequired: false, transformFunction: null }, editIndicatorText: { classPropertyName: "editIndicatorText", publicName: "editIndicatorText", isSignal: true, isRequired: false, transformFunction: null }, replyIndicatorText: { classPropertyName: "replyIndicatorText", publicName: "replyIndicatorText", isSignal: true, isRequired: false, transformFunction: null }, enableMentions: { classPropertyName: "enableMentions", publicName: "enableMentions", isSignal: true, isRequired: false, transformFunction: null }, mentionUsers: { classPropertyName: "mentionUsers", publicName: "mentionUsers", isSignal: true, isRequired: false, transformFunction: null }, autoFocus: { classPropertyName: "autoFocus", publicName: "autoFocus", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { messageSent: "messageSent", editCancelled: "editCancelled", replyCancelled: "replyCancelled", mentionSelected: "mentionSelected", attachmentClicked: "attachmentClicked", attachmentsChanged: "attachmentsChanged" }, viewQueries: [{ propertyName: "messageInputRef", first: true, predicate: ["messageInputEl"], descendants: true }, { propertyName: "fileInput", first: true, predicate: ["fileInput"], descendants: true }], ngImport: i0, template: `
7762
7792
  <div class="message-composer">
7763
7793
  <!-- Edit indicator (optional) -->
@@ -7772,7 +7802,7 @@ class DsMobileMessageComposerComponent {
7772
7802
  </button>
7773
7803
  </div>
7774
7804
  }
7775
-
7805
+
7776
7806
  <!-- Reply indicator (optional) -->
7777
7807
  @if (replyingTo() && !editingMessage()) {
7778
7808
  <div class="reply-indicator">
@@ -7787,21 +7817,18 @@ class DsMobileMessageComposerComponent {
7787
7817
  </button>
7788
7818
  </div>
7789
7819
  }
7790
-
7820
+
7791
7821
  <!-- Attachment Previews (if any) -->
7792
7822
  @if (attachments().length > 0) {
7793
7823
  <div class="attachment-previews-section">
7794
7824
  <div class="attachment-previews">
7795
7825
  @for (attachment of attachments(); track attachment.id) {
7796
- <ds-mobile-attachment-preview
7797
- [attachment]="attachment"
7798
- (remove)="removeAttachment($event)"
7799
- />
7826
+ <ds-mobile-attachment-preview [attachment]="attachment" (remove)="removeAttachment($event)" />
7800
7827
  }
7801
7828
  </div>
7802
7829
  </div>
7803
7830
  }
7804
-
7831
+
7805
7832
  <div class="composer-content">
7806
7833
  <!-- Attachment button replacing avatar (left side) -->
7807
7834
  @if (showAttachmentButton()) {
@@ -7812,34 +7839,30 @@ class DsMobileMessageComposerComponent {
7812
7839
  icon="remixAddLine"
7813
7840
  variant="secondary"
7814
7841
  size="lg"
7815
- (touchstart)="toggleAttachmentMenu($event)"
7816
- (mousedown)="toggleAttachmentMenu($event)"
7842
+ (clicked)="toggleAttachmentMenu($event)"
7817
7843
  [attr.aria-label]="attachmentButtonLabel()"
7818
- [attr.aria-expanded]="isAttachmentMenuOpen()">
7844
+ [attr.aria-expanded]="isAttachmentMenuOpen()"
7845
+ >
7819
7846
  </ds-icon-button>
7820
-
7847
+
7821
7848
  <!-- Attachment menu using dropdown -->
7822
7849
  <ds-mobile-dropdown
7823
- [items]="attachmentMenuItems()"
7850
+ [items]="attachmentMenuItems"
7824
7851
  [isOpen]="isAttachmentMenuOpen()"
7825
7852
  [trigger]="'attachment-trigger'"
7826
7853
  [keepFocusOn]="messageInputRef"
7827
7854
  position="above"
7828
7855
  align="start"
7829
7856
  (itemSelected)="handleAttachmentMenuSelect($event)"
7830
- (closed)="closeAttachmentMenu()">
7857
+ (closed)="closeAttachmentMenu()"
7858
+ >
7831
7859
  </ds-mobile-dropdown>
7832
7860
  </div>
7833
7861
  } @else {
7834
7862
  <!-- Avatar (only shown when attachment button is hidden) -->
7835
- <ds-avatar
7836
- [initials]="avatarInitials()"
7837
- [type]="avatarType()"
7838
- [src]="avatarSrc()"
7839
- size="lg"
7840
- />
7863
+ <ds-avatar [initials]="avatarInitials()" [type]="avatarType()" [src]="avatarSrc()" size="lg" />
7841
7864
  }
7842
-
7865
+
7843
7866
  <div class="composer-input-wrapper">
7844
7867
  <textarea
7845
7868
  #messageInputEl
@@ -7852,8 +7875,9 @@ class DsMobileMessageComposerComponent {
7852
7875
  (focus)="showKeyboard()"
7853
7876
  (click)="showKeyboard()"
7854
7877
  rows="1"
7855
- > </textarea>
7856
-
7878
+ >
7879
+ </textarea>
7880
+
7857
7881
  <!-- Mention menu using dropdown (only render if mentions are enabled) -->
7858
7882
  @if (enableMentions()) {
7859
7883
  <ds-mobile-dropdown
@@ -7864,12 +7888,10 @@ class DsMobileMessageComposerComponent {
7864
7888
  align="start"
7865
7889
  [maxHeight]="200"
7866
7890
  (itemSelected)="handleMentionSelect($event)"
7867
- (closed)="closeMentionMenu()">
7891
+ (closed)="closeMentionMenu()"
7892
+ >
7868
7893
  <ng-template #itemTemplate let-item>
7869
- <ds-avatar
7870
- [initials]="item.data.initials"
7871
- [type]="'initials'"
7872
- size="sm" />
7894
+ <ds-avatar [initials]="item.data.initials" [type]="'initials'" size="sm" />
7873
7895
  <div class="mention-user-info">
7874
7896
  <span class="mention-user-name">{{ item.data.name }}</span>
7875
7897
  <span class="mention-user-role">{{ item.data.role }}</span>
@@ -7877,7 +7899,7 @@ class DsMobileMessageComposerComponent {
7877
7899
  </ng-template>
7878
7900
  </ds-mobile-dropdown>
7879
7901
  }
7880
-
7902
+
7881
7903
  <!-- Send button (absolute positioned in top right, always rendered) -->
7882
7904
  <ds-icon-button
7883
7905
  icon="remixCheckLine"
@@ -7886,11 +7908,12 @@ class DsMobileMessageComposerComponent {
7886
7908
  (clicked)="sendMessage()"
7887
7909
  [attr.aria-label]="sendButtonLabel()"
7888
7910
  [class.send-button-inline]="true"
7889
- [class.show]="messageText().trim().length > 0 || attachments().length > 0">
7911
+ [class.show]="messageText().trim().length > 0 || attachments().length > 0"
7912
+ >
7890
7913
  </ds-icon-button>
7891
7914
  </div>
7892
7915
  </div>
7893
-
7916
+
7894
7917
  <!-- Hidden file input -->
7895
7918
  <input
7896
7919
  #fileInput
@@ -7902,19 +7925,11 @@ class DsMobileMessageComposerComponent {
7902
7925
  (change)="handleFileSelect($event)"
7903
7926
  />
7904
7927
  </div>
7905
- `, isInline: true, styles: [":host{display:block}.message-composer{background:var(--color-background-neutral-primary, #ffffff);border-top:1px solid var(--border-color-default);border-bottom-left-radius:0;border-bottom-right-radius:0;padding:12px 16px;width:100%;display:flex;flex-direction:column;gap:8px}.edit-indicator{display:flex;align-items:center;justify-content:space-between;padding:8px 12px;background:var(--color-background-brand-subtle, #f0edfe);border-radius:8px;animation:slideDown .2s ease-out}.edit-indicator-content{display:flex;align-items:center;gap:8px;color:var(--color-accent, #6B5FF5);flex:1;min-width:0}.edit-text{font-family:Brockmann,sans-serif;font-size:var(--font-size-sm);font-weight:500;line-height:18px;color:var(--color-accent, #6B5FF5)}.cancel-edit{background:none;border:none;padding:4px;cursor:pointer;display:flex;align-items:center;justify-content:center;color:var(--color-accent, #6B5FF5);border-radius:4px;transition:background .2s ease;flex-shrink:0}.cancel-edit:active{background:var(--color-brand-subtle, #e0dbfe)}.reply-indicator{display:flex;align-items:center;justify-content:space-between;padding:8px 12px;background:var(--color-background-neutral-secondary, #f5f5f5);border-radius:8px;animation:slideDown .2s ease-out}.reply-indicator-content{display:flex;align-items:center;gap:4px;color:var(--color-text-secondary, #737373);flex:1;min-width:0}.reply-to-text{font-family:Brockmann,sans-serif;font-size:var(--font-size-sm);line-height:18px;color:var(--color-text-secondary, #737373);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.reply-author{color:var(--color-accent, #6B5FF5);font-weight:600}.cancel-reply{background:none;border:none;padding:4px;cursor:pointer;display:flex;align-items:center;justify-content:center;color:var(--color-text-secondary, #737373);border-radius:4px;transition:background .2s ease;flex-shrink:0}.cancel-reply:active{background:var(--color-background-neutral-secondary, #f5f5f5)}@keyframes slideDown{0%{opacity:0;transform:translateY(-10px)}to{opacity:1;transform:translateY(0)}}.attachment-previews-section{padding:0 0 8px;animation:slideDown .2s ease-out}.attachment-previews{display:flex;flex-wrap:wrap;gap:8px}.composer-content{display:flex;align-items:center;gap:12px;width:100%;position:relative}.composer-leading-button{flex-shrink:0}.composer-leading-button::ng-deep button{width:40px!important;height:40px!important;min-width:40px!important;min-height:40px!important;padding:0!important;border-radius:50%!important;transition:transform .3s ease}.composer-leading-button--open::ng-deep button{transform:rotate(45deg)}.composer-input-wrapper{flex:1;display:flex;align-items:flex-start;gap:8px;background:var(--color-background-neutral-secondary, #f5f5f5);border-radius:24px;padding:12px 16px;min-height:44px;position:relative}.mention-user-info{display:flex;flex-direction:column;gap:2px;flex:1;min-width:0}.mention-user-name{font-family:Brockmann,sans-serif;font-size:var(--font-size-base);font-weight:600;line-height:20px;color:var(--color-text-primary, #1a1a1a)}.mention-user-role{font-family:Brockmann,sans-serif;font-size:var(--font-size-sm);line-height:18px;color:var(--color-text-secondary, #737373)}.composer-input{flex:1;border:none;background:transparent;font-family:Brockmann,sans-serif;font-size:var(--font-size-sm);line-height:20px;color:var(--color-text-primary, #1a1a1a);outline:none;resize:none;min-height:20px;max-height:120px;overflow-y:auto;padding:0;margin:0}.composer-input::placeholder{color:var(--color-text-tertiary, #a0a0a0);font-size:var(--font-size-sm)}.send-button-inline{position:absolute;top:6px;right:6px;z-index:10;flex-shrink:0;opacity:0;transform:translate(20px) scale(.8);pointer-events:none;transition:opacity .15s ease-in,transform .15s ease-in}.send-button-inline.show{opacity:1;transform:translate(0) scale(1);pointer-events:auto;animation:slideInFromRight var(--spring-bouncy)}.send-button-inline::ng-deep button{width:32px!important;height:32px!important;min-width:32px!important;min-height:32px!important;padding:0!important;border-radius:50%!important}@keyframes slideInFromRight{0%{opacity:0;transform:translate(20px) scale(.8)}to{opacity:1;transform:translate(0) scale(1)}}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$3.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1$3.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$3.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "component", type: DsAvatarComponent, selector: "ds-avatar", inputs: ["type", "size", "initials", "src", "alt", "iconName", "iconColor"] }, { kind: "component", type: DsIconButtonComponent, selector: "ds-icon-button", inputs: ["variant", "size", "icon", "disabled", "loading", "pressed", "expanded", "ariaLabel", "tooltip", "tooltipDisabled", "tooltipPlacement"], outputs: ["clicked", "focused", "blurred"] }, { kind: "component", type: DsIconComponent, selector: "ds-icon", inputs: ["name", "size", "color", "interactive"] }, { kind: "component", type: DsMobileAttachmentPreviewComponent, selector: "ds-mobile-attachment-preview", inputs: ["attachment"], outputs: ["remove"] }, { kind: "component", type: DsMobileDropdownComponent, selector: "ds-mobile-dropdown", inputs: ["trigger", "keepFocusOn", "items", "isOpen", "position", "align", "maxHeight", "emptyMessage", "ariaLabel"], outputs: ["itemSelected", "closed"] }] });
7928
+ `, isInline: true, styles: [":host{display:block}.message-composer{background:var(--color-background-neutral-primary, #ffffff);border-top:1px solid var(--border-color-default);border-bottom-left-radius:0;border-bottom-right-radius:0;padding:12px 16px;width:100%;display:flex;flex-direction:column;gap:8px}.edit-indicator{display:flex;align-items:center;justify-content:space-between;padding:8px 12px;background:var(--color-background-brand-subtle, #f0edfe);border-radius:8px;animation:slideDown .2s ease-out}.edit-indicator-content{display:flex;align-items:center;gap:8px;color:var(--color-accent, #6b5ff5);flex:1;min-width:0}.edit-text{font-family:Brockmann,sans-serif;font-size:var(--font-size-sm);font-weight:500;line-height:18px;color:var(--color-accent, #6b5ff5)}.cancel-edit{background:none;border:none;padding:4px;cursor:pointer;display:flex;align-items:center;justify-content:center;color:var(--color-accent, #6b5ff5);border-radius:4px;transition:background .2s ease;flex-shrink:0}.cancel-edit:active{background:var(--color-brand-subtle, #e0dbfe)}.reply-indicator{display:flex;align-items:center;justify-content:space-between;padding:8px 12px;background:var(--color-background-neutral-secondary, #f5f5f5);border-radius:8px;animation:slideDown .2s ease-out}.reply-indicator-content{display:flex;align-items:center;gap:4px;color:var(--color-text-secondary, #737373);flex:1;min-width:0}.reply-to-text{font-family:Brockmann,sans-serif;font-size:var(--font-size-sm);line-height:18px;color:var(--color-text-secondary, #737373);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.reply-author{color:var(--color-accent, #6b5ff5);font-weight:600}.cancel-reply{background:none;border:none;padding:4px;cursor:pointer;display:flex;align-items:center;justify-content:center;color:var(--color-text-secondary, #737373);border-radius:4px;transition:background .2s ease;flex-shrink:0}.cancel-reply:active{background:var(--color-background-neutral-secondary, #f5f5f5)}@keyframes slideDown{0%{opacity:0;transform:translateY(-10px)}to{opacity:1;transform:translateY(0)}}.attachment-previews-section{padding:0 0 8px;animation:slideDown .2s ease-out}.attachment-previews{display:flex;flex-wrap:wrap;gap:8px}.composer-content{display:flex;align-items:center;gap:12px;width:100%;position:relative}.composer-leading-button{flex-shrink:0}.composer-leading-button::ng-deep button{width:40px!important;height:40px!important;min-width:40px!important;min-height:40px!important;padding:0!important;border-radius:50%!important;transition:transform .3s ease}.composer-leading-button--open::ng-deep button{transform:rotate(45deg)}.composer-input-wrapper{flex:1;display:flex;align-items:flex-start;gap:8px;background:var(--color-background-neutral-secondary, #f5f5f5);border-radius:24px;padding:12px 16px;min-height:44px;position:relative}.mention-user-info{display:flex;flex-direction:column;gap:2px;flex:1;min-width:0}.mention-user-name{font-family:Brockmann,sans-serif;font-size:var(--font-size-base);font-weight:600;line-height:20px;color:var(--color-text-primary, #1a1a1a)}.mention-user-role{font-family:Brockmann,sans-serif;font-size:var(--font-size-sm);line-height:18px;color:var(--color-text-secondary, #737373)}.composer-input{flex:1;border:none;background:transparent;font-family:Brockmann,sans-serif;font-size:var(--font-size-sm);line-height:20px;color:var(--color-text-primary, #1a1a1a);outline:none;resize:none;min-height:20px;max-height:120px;overflow-y:auto;padding:0;margin:0}.composer-input::placeholder{color:var(--color-text-tertiary, #a0a0a0);font-size:var(--font-size-sm)}.send-button-inline{position:absolute;top:6px;right:6px;z-index:10;flex-shrink:0;opacity:0;transform:translate(20px) scale(.8);pointer-events:none;transition:opacity .15s ease-in,transform .15s ease-in}.send-button-inline.show{opacity:1;transform:translate(0) scale(1);pointer-events:auto;animation:slideInFromRight var(--spring-bouncy)}.send-button-inline::ng-deep button{width:32px!important;height:32px!important;min-width:32px!important;min-height:32px!important;padding:0!important;border-radius:50%!important}@keyframes slideInFromRight{0%{opacity:0;transform:translate(20px) scale(.8)}to{opacity:1;transform:translate(0) scale(1)}}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$3.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1$3.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$3.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "component", type: DsAvatarComponent, selector: "ds-avatar", inputs: ["type", "size", "initials", "src", "alt", "iconName", "iconColor"] }, { kind: "component", type: DsIconButtonComponent, selector: "ds-icon-button", inputs: ["variant", "size", "icon", "disabled", "loading", "pressed", "expanded", "ariaLabel", "tooltip", "tooltipDisabled", "tooltipPlacement"], outputs: ["clicked", "focused", "blurred"] }, { kind: "component", type: DsIconComponent, selector: "ds-icon", inputs: ["name", "size", "color", "interactive"] }, { kind: "component", type: DsMobileAttachmentPreviewComponent, selector: "ds-mobile-attachment-preview", inputs: ["attachment"], outputs: ["remove"] }, { kind: "component", type: DsMobileDropdownComponent, selector: "ds-mobile-dropdown", inputs: ["trigger", "keepFocusOn", "items", "isOpen", "position", "align", "maxHeight", "emptyMessage", "ariaLabel", "maxWidth"], outputs: ["itemSelected", "closed"] }] });
7906
7929
  }
7907
7930
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: DsMobileMessageComposerComponent, decorators: [{
7908
7931
  type: Component,
7909
- args: [{ selector: 'ds-mobile-message-composer', standalone: true, imports: [
7910
- CommonModule,
7911
- FormsModule,
7912
- DsAvatarComponent,
7913
- DsIconButtonComponent,
7914
- DsIconComponent,
7915
- DsMobileAttachmentPreviewComponent,
7916
- DsMobileDropdownComponent
7917
- ], template: `
7932
+ args: [{ selector: 'ds-mobile-message-composer', standalone: true, imports: [CommonModule, FormsModule, DsAvatarComponent, DsIconButtonComponent, DsIconComponent, DsMobileAttachmentPreviewComponent, DsMobileDropdownComponent], template: `
7918
7933
  <div class="message-composer">
7919
7934
  <!-- Edit indicator (optional) -->
7920
7935
  @if (editingMessage()) {
@@ -7928,7 +7943,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImpo
7928
7943
  </button>
7929
7944
  </div>
7930
7945
  }
7931
-
7946
+
7932
7947
  <!-- Reply indicator (optional) -->
7933
7948
  @if (replyingTo() && !editingMessage()) {
7934
7949
  <div class="reply-indicator">
@@ -7943,21 +7958,18 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImpo
7943
7958
  </button>
7944
7959
  </div>
7945
7960
  }
7946
-
7961
+
7947
7962
  <!-- Attachment Previews (if any) -->
7948
7963
  @if (attachments().length > 0) {
7949
7964
  <div class="attachment-previews-section">
7950
7965
  <div class="attachment-previews">
7951
7966
  @for (attachment of attachments(); track attachment.id) {
7952
- <ds-mobile-attachment-preview
7953
- [attachment]="attachment"
7954
- (remove)="removeAttachment($event)"
7955
- />
7967
+ <ds-mobile-attachment-preview [attachment]="attachment" (remove)="removeAttachment($event)" />
7956
7968
  }
7957
7969
  </div>
7958
7970
  </div>
7959
7971
  }
7960
-
7972
+
7961
7973
  <div class="composer-content">
7962
7974
  <!-- Attachment button replacing avatar (left side) -->
7963
7975
  @if (showAttachmentButton()) {
@@ -7968,34 +7980,30 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImpo
7968
7980
  icon="remixAddLine"
7969
7981
  variant="secondary"
7970
7982
  size="lg"
7971
- (touchstart)="toggleAttachmentMenu($event)"
7972
- (mousedown)="toggleAttachmentMenu($event)"
7983
+ (clicked)="toggleAttachmentMenu($event)"
7973
7984
  [attr.aria-label]="attachmentButtonLabel()"
7974
- [attr.aria-expanded]="isAttachmentMenuOpen()">
7985
+ [attr.aria-expanded]="isAttachmentMenuOpen()"
7986
+ >
7975
7987
  </ds-icon-button>
7976
-
7988
+
7977
7989
  <!-- Attachment menu using dropdown -->
7978
7990
  <ds-mobile-dropdown
7979
- [items]="attachmentMenuItems()"
7991
+ [items]="attachmentMenuItems"
7980
7992
  [isOpen]="isAttachmentMenuOpen()"
7981
7993
  [trigger]="'attachment-trigger'"
7982
7994
  [keepFocusOn]="messageInputRef"
7983
7995
  position="above"
7984
7996
  align="start"
7985
7997
  (itemSelected)="handleAttachmentMenuSelect($event)"
7986
- (closed)="closeAttachmentMenu()">
7998
+ (closed)="closeAttachmentMenu()"
7999
+ >
7987
8000
  </ds-mobile-dropdown>
7988
8001
  </div>
7989
8002
  } @else {
7990
8003
  <!-- Avatar (only shown when attachment button is hidden) -->
7991
- <ds-avatar
7992
- [initials]="avatarInitials()"
7993
- [type]="avatarType()"
7994
- [src]="avatarSrc()"
7995
- size="lg"
7996
- />
8004
+ <ds-avatar [initials]="avatarInitials()" [type]="avatarType()" [src]="avatarSrc()" size="lg" />
7997
8005
  }
7998
-
8006
+
7999
8007
  <div class="composer-input-wrapper">
8000
8008
  <textarea
8001
8009
  #messageInputEl
@@ -8008,8 +8016,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImpo
8008
8016
  (focus)="showKeyboard()"
8009
8017
  (click)="showKeyboard()"
8010
8018
  rows="1"
8011
- > </textarea>
8012
-
8019
+ >
8020
+ </textarea>
8021
+
8013
8022
  <!-- Mention menu using dropdown (only render if mentions are enabled) -->
8014
8023
  @if (enableMentions()) {
8015
8024
  <ds-mobile-dropdown
@@ -8020,12 +8029,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImpo
8020
8029
  align="start"
8021
8030
  [maxHeight]="200"
8022
8031
  (itemSelected)="handleMentionSelect($event)"
8023
- (closed)="closeMentionMenu()">
8032
+ (closed)="closeMentionMenu()"
8033
+ >
8024
8034
  <ng-template #itemTemplate let-item>
8025
- <ds-avatar
8026
- [initials]="item.data.initials"
8027
- [type]="'initials'"
8028
- size="sm" />
8035
+ <ds-avatar [initials]="item.data.initials" [type]="'initials'" size="sm" />
8029
8036
  <div class="mention-user-info">
8030
8037
  <span class="mention-user-name">{{ item.data.name }}</span>
8031
8038
  <span class="mention-user-role">{{ item.data.role }}</span>
@@ -8033,7 +8040,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImpo
8033
8040
  </ng-template>
8034
8041
  </ds-mobile-dropdown>
8035
8042
  }
8036
-
8043
+
8037
8044
  <!-- Send button (absolute positioned in top right, always rendered) -->
8038
8045
  <ds-icon-button
8039
8046
  icon="remixCheckLine"
@@ -8042,11 +8049,12 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImpo
8042
8049
  (clicked)="sendMessage()"
8043
8050
  [attr.aria-label]="sendButtonLabel()"
8044
8051
  [class.send-button-inline]="true"
8045
- [class.show]="messageText().trim().length > 0 || attachments().length > 0">
8052
+ [class.show]="messageText().trim().length > 0 || attachments().length > 0"
8053
+ >
8046
8054
  </ds-icon-button>
8047
8055
  </div>
8048
8056
  </div>
8049
-
8057
+
8050
8058
  <!-- Hidden file input -->
8051
8059
  <input
8052
8060
  #fileInput
@@ -8058,8 +8066,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImpo
8058
8066
  (change)="handleFileSelect($event)"
8059
8067
  />
8060
8068
  </div>
8061
- `, styles: [":host{display:block}.message-composer{background:var(--color-background-neutral-primary, #ffffff);border-top:1px solid var(--border-color-default);border-bottom-left-radius:0;border-bottom-right-radius:0;padding:12px 16px;width:100%;display:flex;flex-direction:column;gap:8px}.edit-indicator{display:flex;align-items:center;justify-content:space-between;padding:8px 12px;background:var(--color-background-brand-subtle, #f0edfe);border-radius:8px;animation:slideDown .2s ease-out}.edit-indicator-content{display:flex;align-items:center;gap:8px;color:var(--color-accent, #6B5FF5);flex:1;min-width:0}.edit-text{font-family:Brockmann,sans-serif;font-size:var(--font-size-sm);font-weight:500;line-height:18px;color:var(--color-accent, #6B5FF5)}.cancel-edit{background:none;border:none;padding:4px;cursor:pointer;display:flex;align-items:center;justify-content:center;color:var(--color-accent, #6B5FF5);border-radius:4px;transition:background .2s ease;flex-shrink:0}.cancel-edit:active{background:var(--color-brand-subtle, #e0dbfe)}.reply-indicator{display:flex;align-items:center;justify-content:space-between;padding:8px 12px;background:var(--color-background-neutral-secondary, #f5f5f5);border-radius:8px;animation:slideDown .2s ease-out}.reply-indicator-content{display:flex;align-items:center;gap:4px;color:var(--color-text-secondary, #737373);flex:1;min-width:0}.reply-to-text{font-family:Brockmann,sans-serif;font-size:var(--font-size-sm);line-height:18px;color:var(--color-text-secondary, #737373);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.reply-author{color:var(--color-accent, #6B5FF5);font-weight:600}.cancel-reply{background:none;border:none;padding:4px;cursor:pointer;display:flex;align-items:center;justify-content:center;color:var(--color-text-secondary, #737373);border-radius:4px;transition:background .2s ease;flex-shrink:0}.cancel-reply:active{background:var(--color-background-neutral-secondary, #f5f5f5)}@keyframes slideDown{0%{opacity:0;transform:translateY(-10px)}to{opacity:1;transform:translateY(0)}}.attachment-previews-section{padding:0 0 8px;animation:slideDown .2s ease-out}.attachment-previews{display:flex;flex-wrap:wrap;gap:8px}.composer-content{display:flex;align-items:center;gap:12px;width:100%;position:relative}.composer-leading-button{flex-shrink:0}.composer-leading-button::ng-deep button{width:40px!important;height:40px!important;min-width:40px!important;min-height:40px!important;padding:0!important;border-radius:50%!important;transition:transform .3s ease}.composer-leading-button--open::ng-deep button{transform:rotate(45deg)}.composer-input-wrapper{flex:1;display:flex;align-items:flex-start;gap:8px;background:var(--color-background-neutral-secondary, #f5f5f5);border-radius:24px;padding:12px 16px;min-height:44px;position:relative}.mention-user-info{display:flex;flex-direction:column;gap:2px;flex:1;min-width:0}.mention-user-name{font-family:Brockmann,sans-serif;font-size:var(--font-size-base);font-weight:600;line-height:20px;color:var(--color-text-primary, #1a1a1a)}.mention-user-role{font-family:Brockmann,sans-serif;font-size:var(--font-size-sm);line-height:18px;color:var(--color-text-secondary, #737373)}.composer-input{flex:1;border:none;background:transparent;font-family:Brockmann,sans-serif;font-size:var(--font-size-sm);line-height:20px;color:var(--color-text-primary, #1a1a1a);outline:none;resize:none;min-height:20px;max-height:120px;overflow-y:auto;padding:0;margin:0}.composer-input::placeholder{color:var(--color-text-tertiary, #a0a0a0);font-size:var(--font-size-sm)}.send-button-inline{position:absolute;top:6px;right:6px;z-index:10;flex-shrink:0;opacity:0;transform:translate(20px) scale(.8);pointer-events:none;transition:opacity .15s ease-in,transform .15s ease-in}.send-button-inline.show{opacity:1;transform:translate(0) scale(1);pointer-events:auto;animation:slideInFromRight var(--spring-bouncy)}.send-button-inline::ng-deep button{width:32px!important;height:32px!important;min-width:32px!important;min-height:32px!important;padding:0!important;border-radius:50%!important}@keyframes slideInFromRight{0%{opacity:0;transform:translate(20px) scale(.8)}to{opacity:1;transform:translate(0) scale(1)}}\n"] }]
8062
- }], propDecorators: { avatarInitials: [{ type: i0.Input, args: [{ isSignal: true, alias: "avatarInitials", required: false }] }], avatarType: [{ type: i0.Input, args: [{ isSignal: true, alias: "avatarType", required: false }] }], avatarSrc: [{ type: i0.Input, args: [{ isSignal: true, alias: "avatarSrc", required: false }] }], placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], sendButtonLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "sendButtonLabel", required: false }] }], attachmentButtonLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "attachmentButtonLabel", required: false }] }], showAttachmentButton: [{ type: i0.Input, args: [{ isSignal: true, alias: "showAttachmentButton", required: false }] }], editIndicatorText: [{ type: i0.Input, args: [{ isSignal: true, alias: "editIndicatorText", required: false }] }], replyIndicatorText: [{ type: i0.Input, args: [{ isSignal: true, alias: "replyIndicatorText", required: false }] }], enableMentions: [{ type: i0.Input, args: [{ isSignal: true, alias: "enableMentions", required: false }] }], mentionUsers: [{ type: i0.Input, args: [{ isSignal: true, alias: "mentionUsers", required: false }] }], autoFocus: [{ type: i0.Input, args: [{ isSignal: true, alias: "autoFocus", required: false }] }], messageInputRef: [{
8069
+ `, styles: [":host{display:block}.message-composer{background:var(--color-background-neutral-primary, #ffffff);border-top:1px solid var(--border-color-default);border-bottom-left-radius:0;border-bottom-right-radius:0;padding:12px 16px;width:100%;display:flex;flex-direction:column;gap:8px}.edit-indicator{display:flex;align-items:center;justify-content:space-between;padding:8px 12px;background:var(--color-background-brand-subtle, #f0edfe);border-radius:8px;animation:slideDown .2s ease-out}.edit-indicator-content{display:flex;align-items:center;gap:8px;color:var(--color-accent, #6b5ff5);flex:1;min-width:0}.edit-text{font-family:Brockmann,sans-serif;font-size:var(--font-size-sm);font-weight:500;line-height:18px;color:var(--color-accent, #6b5ff5)}.cancel-edit{background:none;border:none;padding:4px;cursor:pointer;display:flex;align-items:center;justify-content:center;color:var(--color-accent, #6b5ff5);border-radius:4px;transition:background .2s ease;flex-shrink:0}.cancel-edit:active{background:var(--color-brand-subtle, #e0dbfe)}.reply-indicator{display:flex;align-items:center;justify-content:space-between;padding:8px 12px;background:var(--color-background-neutral-secondary, #f5f5f5);border-radius:8px;animation:slideDown .2s ease-out}.reply-indicator-content{display:flex;align-items:center;gap:4px;color:var(--color-text-secondary, #737373);flex:1;min-width:0}.reply-to-text{font-family:Brockmann,sans-serif;font-size:var(--font-size-sm);line-height:18px;color:var(--color-text-secondary, #737373);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.reply-author{color:var(--color-accent, #6b5ff5);font-weight:600}.cancel-reply{background:none;border:none;padding:4px;cursor:pointer;display:flex;align-items:center;justify-content:center;color:var(--color-text-secondary, #737373);border-radius:4px;transition:background .2s ease;flex-shrink:0}.cancel-reply:active{background:var(--color-background-neutral-secondary, #f5f5f5)}@keyframes slideDown{0%{opacity:0;transform:translateY(-10px)}to{opacity:1;transform:translateY(0)}}.attachment-previews-section{padding:0 0 8px;animation:slideDown .2s ease-out}.attachment-previews{display:flex;flex-wrap:wrap;gap:8px}.composer-content{display:flex;align-items:center;gap:12px;width:100%;position:relative}.composer-leading-button{flex-shrink:0}.composer-leading-button::ng-deep button{width:40px!important;height:40px!important;min-width:40px!important;min-height:40px!important;padding:0!important;border-radius:50%!important;transition:transform .3s ease}.composer-leading-button--open::ng-deep button{transform:rotate(45deg)}.composer-input-wrapper{flex:1;display:flex;align-items:flex-start;gap:8px;background:var(--color-background-neutral-secondary, #f5f5f5);border-radius:24px;padding:12px 16px;min-height:44px;position:relative}.mention-user-info{display:flex;flex-direction:column;gap:2px;flex:1;min-width:0}.mention-user-name{font-family:Brockmann,sans-serif;font-size:var(--font-size-base);font-weight:600;line-height:20px;color:var(--color-text-primary, #1a1a1a)}.mention-user-role{font-family:Brockmann,sans-serif;font-size:var(--font-size-sm);line-height:18px;color:var(--color-text-secondary, #737373)}.composer-input{flex:1;border:none;background:transparent;font-family:Brockmann,sans-serif;font-size:var(--font-size-sm);line-height:20px;color:var(--color-text-primary, #1a1a1a);outline:none;resize:none;min-height:20px;max-height:120px;overflow-y:auto;padding:0;margin:0}.composer-input::placeholder{color:var(--color-text-tertiary, #a0a0a0);font-size:var(--font-size-sm)}.send-button-inline{position:absolute;top:6px;right:6px;z-index:10;flex-shrink:0;opacity:0;transform:translate(20px) scale(.8);pointer-events:none;transition:opacity .15s ease-in,transform .15s ease-in}.send-button-inline.show{opacity:1;transform:translate(0) scale(1);pointer-events:auto;animation:slideInFromRight var(--spring-bouncy)}.send-button-inline::ng-deep button{width:32px!important;height:32px!important;min-width:32px!important;min-height:32px!important;padding:0!important;border-radius:50%!important}@keyframes slideInFromRight{0%{opacity:0;transform:translate(20px) scale(.8)}to{opacity:1;transform:translate(0) scale(1)}}\n"] }]
8070
+ }], ctorParameters: () => [{ type: i0.ChangeDetectorRef }], propDecorators: { avatarInitials: [{ type: i0.Input, args: [{ isSignal: true, alias: "avatarInitials", required: false }] }], avatarType: [{ type: i0.Input, args: [{ isSignal: true, alias: "avatarType", required: false }] }], avatarSrc: [{ type: i0.Input, args: [{ isSignal: true, alias: "avatarSrc", required: false }] }], placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], sendButtonLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "sendButtonLabel", required: false }] }], attachmentButtonLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "attachmentButtonLabel", required: false }] }], showAttachmentButton: [{ type: i0.Input, args: [{ isSignal: true, alias: "showAttachmentButton", required: false }] }], editIndicatorText: [{ type: i0.Input, args: [{ isSignal: true, alias: "editIndicatorText", required: false }] }], replyIndicatorText: [{ type: i0.Input, args: [{ isSignal: true, alias: "replyIndicatorText", required: false }] }], enableMentions: [{ type: i0.Input, args: [{ isSignal: true, alias: "enableMentions", required: false }] }], mentionUsers: [{ type: i0.Input, args: [{ isSignal: true, alias: "mentionUsers", required: false }] }], autoFocus: [{ type: i0.Input, args: [{ isSignal: true, alias: "autoFocus", required: false }] }], messageInputRef: [{
8063
8071
  type: ViewChild,
8064
8072
  args: ['messageInputEl']
8065
8073
  }], fileInput: [{
@@ -10389,7 +10397,7 @@ class DsMobileTabBarComponent {
10389
10397
  <ds-avatar [size]="'md'" [type]="avatarType" [initials]="avatarInitials" [src]="avatarSrc" [iconName]="avatarIconName" (click)="handleAvatarClick()" />
10390
10398
  </div>
10391
10399
  </ion-tab-bar>
10392
- `, isInline: true, styles: [":host{--ds-tab-bar-height: 64px}@media (min-width: 768px){:host{display:block;--ds-tab-bar-height: 64px}}@media (max-width: 767px){:host{display:contents}}ion-tabs.ds-tabs-wrapper{height:100%;background:var(--color-header-surface)}ion-tab-button:before,ion-tab-button:after{content:none!important;display:none!important}ion-tab-button[title]:before,ion-tab-button[title]:after{display:none!important}ion-tab-button::part(native):before,ion-tab-button::part(native):after{display:none!important}.ds-tab-bar{--background: var(--color-background-neutral-primary);transition:transform .2s ease-in-out}ion-tab-bar[slot=bottom]{border-top:1px solid var(--border-color-default);padding-top:8px;padding-bottom:max(8px,calc(var(--ion-safe-area-bottom, 0px) - 16px));padding-left:12px;padding-right:12px}@media (max-width: 767px){ion-tab-bar[slot=bottom]{position:fixed;bottom:0;left:0;right:0;z-index:100}}@media (max-width: 767px){:host-context(ion-tabs:has(ds-mobile-page-details)) .ds-tab-bar{transform:translateY(100%);transition:transform .3s ease}}.ds-tab-bar__logo,.ds-tab-bar__actions{display:none}.ds-tab-bar__tabs{display:flex;width:100%;justify-content:space-around;align-items:center}@media (min-width: 769px){ion-tab-bar[slot=bottom]{padding-bottom:env(safe-area-inset-bottom,0px)}}@media (display-mode: standalone){ion-tab-bar[slot=bottom]{padding-bottom:env(safe-area-inset-bottom,0px)!important}}@media (min-width: 768px){:host[slot=top]{order:-1!important}:host ion-tab-bar{position:relative!important;bottom:auto!important;top:0!important}ion-tab-bar[slot=top]{--background: var(--color-header-surface);position:relative!important;display:flex!important;align-items:center;padding:12px 24px;height:64px;max-width:none;bottom:auto!important;top:0!important}ion-tab-bar[slot=bottom]{position:relative!important;bottom:auto!important}ion-tabs>div:not([slot]){order:1!important}.ds-tab-bar__logo{display:flex;position:absolute;left:24px;align-items:center;color:var(--header-content-color, white)}.ds-tab-bar__actions{display:flex;position:absolute;right:24px;align-items:center;gap:12px}.ds-tab-bar__tabs{display:flex;gap:8px;align-items:center;max-width:640px;width:100%;margin:0 auto;justify-content:center;padding-left:var(--content-padding-md);padding-right:var(--content-padding-md)}.logomark{height:28px;width:auto;flex-shrink:0}}@media (min-width: 992px){.ds-tab-bar__tabs{max-width:640px;padding-left:var(--content-padding-lg);padding-right:var(--content-padding-lg);justify-content:center}}@media (min-width: 1440px){.ds-tab-bar__tabs{max-width:640px;padding-left:var(--content-padding-xl);padding-right:var(--content-padding-xl)}}@media (min-width: 1768px){.ds-tab-bar__tabs{max-width:640px;padding-left:var(--content-padding-2xl);padding-right:var(--content-padding-2xl)}}@media (min-width: 1920px){.ds-tab-bar__tabs{max-width:640px;padding-left:var(--content-padding-3xl);padding-right:var(--content-padding-3xl)}}ion-tab-button{--color: var(--text-color-default-tertiary);--color-selected: var(--color-accent);display:flex;flex-direction:column;align-items:center;justify-content:center;position:relative;overflow:visible;pointer-events:auto}ion-tab-button[title]:before{content:attr(title);position:absolute;opacity:0;pointer-events:none}.tab-icon-ripple{position:absolute;left:50%;top:50%;width:40px;height:40px;border-radius:50%;background:var(--color-accent);transform:translate(-50%,-50%) scale(0);opacity:0;pointer-events:none;transition:all .6s cubic-bezier(.36,1.2,.04,1.4);z-index:0}.tab-selected .tab-icon-ripple{transform:translate(-50%,-50%) scale(2);opacity:.05;animation:ripple-fade .6s cubic-bezier(.36,.5,.04,1.8) forwards}@keyframes ripple-fade{0%{opacity:0;transform:translate(-50%,-50%) scale(0)}30%{opacity:.1}to{opacity:0;transform:translate(-50%,-50%) scale(2)}}ion-tab-button::part(native){overflow:visible}ion-tab-button ion-ripple-effect{color:var(--color-accent);border-radius:1000px}ion-tab-button ion-label{font-size:var(--font-size-xs);font-weight:400;letter-spacing:-.3px;margin-top:0}.tab-icon-wrapper{position:relative;width:24px;height:24px;display:flex;align-items:center;justify-content:center;z-index:1;margin-bottom:4px}.tab-icon-inactive,.tab-icon-active{position:absolute;left:50%;top:50%;transform:translate(-50%,-50%);transition:all .8s cubic-bezier(.36,1,.04,1)}.tab-icon-inactive{opacity:1;transform:translate(-50%,-50%) scale(1)}.tab-icon-active{opacity:0;transform:translate(-50%,calc(-50% - 12px)) scale(.5)}.tab-selected .tab-icon-inactive{opacity:0;transform:translate(-50%,-50%) scale(.5)}.tab-selected .tab-icon-active{opacity:1;transform:translate(-50%,-50%) scale(1)}@media (min-width: 768px){.ds-tab-button{flex-direction:row;height:40px;padding:0 16px!important;border-radius:40px;transition:all .2s ease;width:-moz-fit-content;width:fit-content;min-width:-moz-fit-content;min-width:fit-content;flex:0 0 auto;--color: rgba(var(--color-header-content-rgb, 255, 255, 255), .7);--color-selected: var(--color-header-content, white);color:rgba(var(--color-header-content-rgb, 255, 255, 255),.7);background:transparent;position:relative;overflow:hidden}.tab-icon-wrapper,.tab-icon-ripple{width:20px;height:20px}.ds-tab-button::part(native){border-radius:40px}.ds-tab-button:hover:not(.tab-selected){--color: var(--color-header-content, white);--color-selected: var(--color-header-content, white);color:var(--color-header-content, white);background:rgba(var(--color-header-content-rgb, 255, 255, 255),.1)}.ds-tab-button:hover:not(.tab-selected) ion-label{color:var(--color-header-content, white)}.ds-tab-button:hover:not(.tab-selected) ds-icon{--icon-color: var(--color-header-content, white);color:var(--color-header-content, white)}.ds-tab-button:hover:not(.tab-selected) ds-icon svg{fill:var(--color-header-content, white)}.ds-tab-button.tab-selected,.ds-tab-button.tab-selected:hover{background:var(--color-header-accent);--color-selected: var(--color-on-header-accent, white);--color: var(--color-on-header-accent, white);color:var(--color-on-header-accent, white)}.ds-tab-button.tab-selected:hover ion-label{color:var(--color-on-header-accent, white)}.ds-tab-button.tab-selected:hover ds-icon{--icon-color: var(--color-on-header-accent, white);color:var(--color-on-header-accent, white)}.ds-tab-button.tab-selected:hover ds-icon svg{fill:var(--color-on-header-accent, white)}.ds-tab-button.tab-selected:hover .tab-icon-active{opacity:1!important;visibility:visible!important}.ds-tab-button.tab-selected:hover .tab-icon-inactive{opacity:0!important}.ds-tab-button.tab-selected ion-label{color:var(--color-on-header-accent, white)}.ds-tab-button.tab-selected ds-icon{--icon-color: var(--color-on-header-accent, white);color:var(--color-on-header-accent, white)}.ds-tab-button.tab-selected ds-icon svg{fill:var(--color-on-header-accent, white)}.ds-tab-button .button-native{width:auto;padding:0}.ds-tab-button ion-label{font-size:var(--font-size-sm);font-weight:500;margin:0;color:inherit}.ds-tab-button .tab-icon-wrapper{margin-right:4px;margin-bottom:0}.ds-tab-button ion-ripple-effect{color:rgba(var(--header-content-color-rgb, 255, 255, 255),.3);border-radius:1000px;transform:scale(1.5)}}@media (min-width: 768px){.ds-tab-bar__actions ds-avatar{cursor:pointer;transition:transform .2s ease}.ds-tab-bar__actions ds-avatar:hover{transform:scale(1.05)}}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$2.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1$2.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "component", type: IonTabBar, selector: "ion-tab-bar", inputs: ["color", "mode", "selectedTab", "translucent"] }, { kind: "component", type: IonTabButton, selector: "ion-tab-button", inputs: ["disabled", "download", "href", "layout", "mode", "rel", "selected", "tab", "target"] }, { kind: "component", type: IonLabel, selector: "ion-label", inputs: ["color", "mode", "position"] }, { kind: "component", type: DsIconComponent, selector: "ds-icon", inputs: ["name", "size", "color", "interactive"] }, { kind: "component", type: DsAvatarComponent, selector: "ds-avatar", inputs: ["type", "size", "initials", "src", "alt", "iconName", "iconColor"] }, { kind: "component", type: DsLogoComponent, selector: "ds-logo", inputs: ["variant", "size", "customHeight", "customWidth"] }] });
10400
+ `, isInline: true, styles: [":host{--ds-tab-bar-height: 64px}@media (min-width: 768px){:host{display:block;--ds-tab-bar-height: 64px}}@media (max-width: 767px){:host{display:contents}}ion-tabs.ds-tabs-wrapper{height:100%;background:var(--color-header-surface)}ion-tab-button:before,ion-tab-button:after{content:none!important;display:none!important}ion-tab-button[title]:before,ion-tab-button[title]:after{display:none!important}ion-tab-button::part(native):before,ion-tab-button::part(native):after{display:none!important}.ds-tab-bar{--background: var(--color-background-neutral-primary);transition:transform .2s ease-in-out}ion-tab-bar[slot=bottom]{border-top:1px solid var(--border-color-default);padding-top:8px;padding-bottom:max(8px,calc(var(--ion-safe-area-bottom, 0px) - 16px));padding-left:12px;padding-right:12px}@media (max-width: 767px){ion-tab-bar[slot=bottom]{position:fixed;bottom:0;left:0;right:0;z-index:100}}@media (max-width: 767px){:host-context(ion-tabs:has(ds-mobile-page-details)) .ds-tab-bar{transform:translateY(100%);transition:transform .3s ease}}.ds-tab-bar__logo,.ds-tab-bar__actions{display:none}.ds-tab-bar__tabs{display:flex;width:100%;justify-content:space-around;align-items:center}@media (min-width: 769px){ion-tab-bar[slot=bottom]{padding-bottom:env(safe-area-inset-bottom,0px)}}@media (display-mode: standalone){ion-tab-bar[slot=bottom]{padding-bottom:env(safe-area-inset-bottom,0px)!important}}@media (min-width: 768px){:host[slot=top]{order:-1!important}:host ion-tab-bar{position:relative!important;bottom:auto!important;top:0!important}ion-tab-bar[slot=top]{--background: var(--color-header-surface);position:relative!important;display:flex!important;align-items:center;padding:12px 24px;height:64px;max-width:none;bottom:auto!important;top:0!important}ion-tab-bar[slot=bottom]{position:relative!important;bottom:auto!important}ion-tabs>div:not([slot]){order:1!important}.ds-tab-bar__logo{display:flex;position:absolute;left:24px;align-items:center;color:var(--header-content-color, white)}.ds-tab-bar__actions{display:flex;position:absolute;right:24px;align-items:center;gap:12px}.ds-tab-bar__tabs{display:flex;gap:8px;align-items:center;max-width:640px;width:100%;margin:0 auto;justify-content:center;padding-left:var(--content-padding-md);padding-right:var(--content-padding-md)}.logomark{height:28px;width:auto;flex-shrink:0}}@media (min-width: 992px){.ds-tab-bar__tabs{max-width:640px;padding-left:var(--content-padding-lg);padding-right:var(--content-padding-lg);justify-content:center}}@media (min-width: 1440px){.ds-tab-bar__tabs{max-width:640px;padding-left:var(--content-padding-xl);padding-right:var(--content-padding-xl)}}@media (min-width: 1768px){.ds-tab-bar__tabs{max-width:640px;padding-left:var(--content-padding-2xl);padding-right:var(--content-padding-2xl)}}@media (min-width: 1920px){.ds-tab-bar__tabs{max-width:640px;padding-left:var(--content-padding-3xl);padding-right:var(--content-padding-3xl)}}ion-tab-button{--color: var(--text-color-default-tertiary);--color-selected: var(--color-accent);display:flex;flex-direction:column;align-items:center;justify-content:center;position:relative;overflow:visible;pointer-events:auto}ion-tab-button[title]:before{content:attr(title);position:absolute;opacity:0;pointer-events:none}.tab-icon-ripple{position:absolute;left:50%;top:50%;width:40px;height:40px;border-radius:50%;background:var(--color-accent);transform:translate(-50%,-50%) scale(0);opacity:0;pointer-events:none;transition:all .6s cubic-bezier(.36,1.2,.04,1.4);z-index:0}.tab-selected .tab-icon-ripple{transform:translate(-50%,-50%) scale(2);opacity:.05;animation:ripple-fade .6s cubic-bezier(.36,.5,.04,1.8) forwards}@keyframes ripple-fade{0%{opacity:0;transform:translate(-50%,-50%) scale(0)}30%{opacity:.1}to{opacity:0;transform:translate(-50%,-50%) scale(2)}}ion-tab-button::part(native){overflow:visible}ion-tab-button ion-ripple-effect{color:var(--color-accent);border-radius:1000px}ion-tab-button ion-label{font-size:11px;font-weight:500;letter-spacing:-.5px;margin-top:0}.tab-icon-wrapper{position:relative;width:24px;height:24px;display:flex;align-items:center;justify-content:center;z-index:1;margin-bottom:4px}.tab-icon-inactive,.tab-icon-active{position:absolute;left:50%;top:50%;transform:translate(-50%,-50%);transition:all .8s cubic-bezier(.36,1,.04,1)}.tab-icon-inactive{opacity:1;transform:translate(-50%,-50%) scale(1)}.tab-icon-active{opacity:0;transform:translate(-50%,calc(-50% - 12px)) scale(.5)}.tab-selected .tab-icon-inactive{opacity:0;transform:translate(-50%,-50%) scale(.5)}.tab-selected .tab-icon-active{opacity:1;transform:translate(-50%,-50%) scale(1)}@media (min-width: 768px){.ds-tab-button{flex-direction:row;height:40px;padding:0 16px!important;border-radius:40px;transition:all .2s ease;width:-moz-fit-content;width:fit-content;min-width:-moz-fit-content;min-width:fit-content;flex:0 0 auto;--color: rgba(var(--color-header-content-rgb, 255, 255, 255), .7);--color-selected: var(--color-header-content, white);color:rgba(var(--color-header-content-rgb, 255, 255, 255),.7);background:transparent;position:relative;overflow:hidden}.tab-icon-wrapper,.tab-icon-ripple{width:20px;height:20px}.ds-tab-button::part(native){border-radius:40px}.ds-tab-button:hover:not(.tab-selected){--color: var(--color-header-content, white);--color-selected: var(--color-header-content, white);color:var(--color-header-content, white);background:rgba(var(--color-header-content-rgb, 255, 255, 255),.1)}.ds-tab-button:hover:not(.tab-selected) ion-label{color:var(--color-header-content, white)}.ds-tab-button:hover:not(.tab-selected) ds-icon{--icon-color: var(--color-header-content, white);color:var(--color-header-content, white)}.ds-tab-button:hover:not(.tab-selected) ds-icon svg{fill:var(--color-header-content, white)}.ds-tab-button.tab-selected,.ds-tab-button.tab-selected:hover{background:var(--color-header-accent);--color-selected: var(--color-on-header-accent, white);--color: var(--color-on-header-accent, white);color:var(--color-on-header-accent, white)}.ds-tab-button.tab-selected:hover ion-label{color:var(--color-on-header-accent, white)}.ds-tab-button.tab-selected:hover ds-icon{--icon-color: var(--color-on-header-accent, white);color:var(--color-on-header-accent, white)}.ds-tab-button.tab-selected:hover ds-icon svg{fill:var(--color-on-header-accent, white)}.ds-tab-button.tab-selected:hover .tab-icon-active{opacity:1!important;visibility:visible!important}.ds-tab-button.tab-selected:hover .tab-icon-inactive{opacity:0!important}.ds-tab-button.tab-selected ion-label{color:var(--color-on-header-accent, white)}.ds-tab-button.tab-selected ds-icon{--icon-color: var(--color-on-header-accent, white);color:var(--color-on-header-accent, white)}.ds-tab-button.tab-selected ds-icon svg{fill:var(--color-on-header-accent, white)}.ds-tab-button .button-native{width:auto;padding:0}.ds-tab-button ion-label{font-size:var(--font-size-sm);font-weight:500;margin:0;color:inherit}.ds-tab-button .tab-icon-wrapper{margin-right:4px;margin-bottom:0}.ds-tab-button ion-ripple-effect{color:rgba(var(--header-content-color-rgb, 255, 255, 255),.3);border-radius:1000px;transform:scale(1.5)}}@media (min-width: 768px){.ds-tab-bar__actions ds-avatar{cursor:pointer;transition:transform .2s ease}.ds-tab-bar__actions ds-avatar:hover{transform:scale(1.05)}}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$2.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1$2.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "component", type: IonTabBar, selector: "ion-tab-bar", inputs: ["color", "mode", "selectedTab", "translucent"] }, { kind: "component", type: IonTabButton, selector: "ion-tab-button", inputs: ["disabled", "download", "href", "layout", "mode", "rel", "selected", "tab", "target"] }, { kind: "component", type: IonLabel, selector: "ion-label", inputs: ["color", "mode", "position"] }, { kind: "component", type: DsIconComponent, selector: "ds-icon", inputs: ["name", "size", "color", "interactive"] }, { kind: "component", type: DsAvatarComponent, selector: "ds-avatar", inputs: ["type", "size", "initials", "src", "alt", "iconName", "iconColor"] }, { kind: "component", type: DsLogoComponent, selector: "ds-logo", inputs: ["variant", "size", "customHeight", "customWidth"] }] });
10393
10401
  }
10394
10402
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: DsMobileTabBarComponent, decorators: [{
10395
10403
  type: Component,
@@ -10425,7 +10433,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImpo
10425
10433
  <ds-avatar [size]="'md'" [type]="avatarType" [initials]="avatarInitials" [src]="avatarSrc" [iconName]="avatarIconName" (click)="handleAvatarClick()" />
10426
10434
  </div>
10427
10435
  </ion-tab-bar>
10428
- `, styles: [":host{--ds-tab-bar-height: 64px}@media (min-width: 768px){:host{display:block;--ds-tab-bar-height: 64px}}@media (max-width: 767px){:host{display:contents}}ion-tabs.ds-tabs-wrapper{height:100%;background:var(--color-header-surface)}ion-tab-button:before,ion-tab-button:after{content:none!important;display:none!important}ion-tab-button[title]:before,ion-tab-button[title]:after{display:none!important}ion-tab-button::part(native):before,ion-tab-button::part(native):after{display:none!important}.ds-tab-bar{--background: var(--color-background-neutral-primary);transition:transform .2s ease-in-out}ion-tab-bar[slot=bottom]{border-top:1px solid var(--border-color-default);padding-top:8px;padding-bottom:max(8px,calc(var(--ion-safe-area-bottom, 0px) - 16px));padding-left:12px;padding-right:12px}@media (max-width: 767px){ion-tab-bar[slot=bottom]{position:fixed;bottom:0;left:0;right:0;z-index:100}}@media (max-width: 767px){:host-context(ion-tabs:has(ds-mobile-page-details)) .ds-tab-bar{transform:translateY(100%);transition:transform .3s ease}}.ds-tab-bar__logo,.ds-tab-bar__actions{display:none}.ds-tab-bar__tabs{display:flex;width:100%;justify-content:space-around;align-items:center}@media (min-width: 769px){ion-tab-bar[slot=bottom]{padding-bottom:env(safe-area-inset-bottom,0px)}}@media (display-mode: standalone){ion-tab-bar[slot=bottom]{padding-bottom:env(safe-area-inset-bottom,0px)!important}}@media (min-width: 768px){:host[slot=top]{order:-1!important}:host ion-tab-bar{position:relative!important;bottom:auto!important;top:0!important}ion-tab-bar[slot=top]{--background: var(--color-header-surface);position:relative!important;display:flex!important;align-items:center;padding:12px 24px;height:64px;max-width:none;bottom:auto!important;top:0!important}ion-tab-bar[slot=bottom]{position:relative!important;bottom:auto!important}ion-tabs>div:not([slot]){order:1!important}.ds-tab-bar__logo{display:flex;position:absolute;left:24px;align-items:center;color:var(--header-content-color, white)}.ds-tab-bar__actions{display:flex;position:absolute;right:24px;align-items:center;gap:12px}.ds-tab-bar__tabs{display:flex;gap:8px;align-items:center;max-width:640px;width:100%;margin:0 auto;justify-content:center;padding-left:var(--content-padding-md);padding-right:var(--content-padding-md)}.logomark{height:28px;width:auto;flex-shrink:0}}@media (min-width: 992px){.ds-tab-bar__tabs{max-width:640px;padding-left:var(--content-padding-lg);padding-right:var(--content-padding-lg);justify-content:center}}@media (min-width: 1440px){.ds-tab-bar__tabs{max-width:640px;padding-left:var(--content-padding-xl);padding-right:var(--content-padding-xl)}}@media (min-width: 1768px){.ds-tab-bar__tabs{max-width:640px;padding-left:var(--content-padding-2xl);padding-right:var(--content-padding-2xl)}}@media (min-width: 1920px){.ds-tab-bar__tabs{max-width:640px;padding-left:var(--content-padding-3xl);padding-right:var(--content-padding-3xl)}}ion-tab-button{--color: var(--text-color-default-tertiary);--color-selected: var(--color-accent);display:flex;flex-direction:column;align-items:center;justify-content:center;position:relative;overflow:visible;pointer-events:auto}ion-tab-button[title]:before{content:attr(title);position:absolute;opacity:0;pointer-events:none}.tab-icon-ripple{position:absolute;left:50%;top:50%;width:40px;height:40px;border-radius:50%;background:var(--color-accent);transform:translate(-50%,-50%) scale(0);opacity:0;pointer-events:none;transition:all .6s cubic-bezier(.36,1.2,.04,1.4);z-index:0}.tab-selected .tab-icon-ripple{transform:translate(-50%,-50%) scale(2);opacity:.05;animation:ripple-fade .6s cubic-bezier(.36,.5,.04,1.8) forwards}@keyframes ripple-fade{0%{opacity:0;transform:translate(-50%,-50%) scale(0)}30%{opacity:.1}to{opacity:0;transform:translate(-50%,-50%) scale(2)}}ion-tab-button::part(native){overflow:visible}ion-tab-button ion-ripple-effect{color:var(--color-accent);border-radius:1000px}ion-tab-button ion-label{font-size:var(--font-size-xs);font-weight:400;letter-spacing:-.3px;margin-top:0}.tab-icon-wrapper{position:relative;width:24px;height:24px;display:flex;align-items:center;justify-content:center;z-index:1;margin-bottom:4px}.tab-icon-inactive,.tab-icon-active{position:absolute;left:50%;top:50%;transform:translate(-50%,-50%);transition:all .8s cubic-bezier(.36,1,.04,1)}.tab-icon-inactive{opacity:1;transform:translate(-50%,-50%) scale(1)}.tab-icon-active{opacity:0;transform:translate(-50%,calc(-50% - 12px)) scale(.5)}.tab-selected .tab-icon-inactive{opacity:0;transform:translate(-50%,-50%) scale(.5)}.tab-selected .tab-icon-active{opacity:1;transform:translate(-50%,-50%) scale(1)}@media (min-width: 768px){.ds-tab-button{flex-direction:row;height:40px;padding:0 16px!important;border-radius:40px;transition:all .2s ease;width:-moz-fit-content;width:fit-content;min-width:-moz-fit-content;min-width:fit-content;flex:0 0 auto;--color: rgba(var(--color-header-content-rgb, 255, 255, 255), .7);--color-selected: var(--color-header-content, white);color:rgba(var(--color-header-content-rgb, 255, 255, 255),.7);background:transparent;position:relative;overflow:hidden}.tab-icon-wrapper,.tab-icon-ripple{width:20px;height:20px}.ds-tab-button::part(native){border-radius:40px}.ds-tab-button:hover:not(.tab-selected){--color: var(--color-header-content, white);--color-selected: var(--color-header-content, white);color:var(--color-header-content, white);background:rgba(var(--color-header-content-rgb, 255, 255, 255),.1)}.ds-tab-button:hover:not(.tab-selected) ion-label{color:var(--color-header-content, white)}.ds-tab-button:hover:not(.tab-selected) ds-icon{--icon-color: var(--color-header-content, white);color:var(--color-header-content, white)}.ds-tab-button:hover:not(.tab-selected) ds-icon svg{fill:var(--color-header-content, white)}.ds-tab-button.tab-selected,.ds-tab-button.tab-selected:hover{background:var(--color-header-accent);--color-selected: var(--color-on-header-accent, white);--color: var(--color-on-header-accent, white);color:var(--color-on-header-accent, white)}.ds-tab-button.tab-selected:hover ion-label{color:var(--color-on-header-accent, white)}.ds-tab-button.tab-selected:hover ds-icon{--icon-color: var(--color-on-header-accent, white);color:var(--color-on-header-accent, white)}.ds-tab-button.tab-selected:hover ds-icon svg{fill:var(--color-on-header-accent, white)}.ds-tab-button.tab-selected:hover .tab-icon-active{opacity:1!important;visibility:visible!important}.ds-tab-button.tab-selected:hover .tab-icon-inactive{opacity:0!important}.ds-tab-button.tab-selected ion-label{color:var(--color-on-header-accent, white)}.ds-tab-button.tab-selected ds-icon{--icon-color: var(--color-on-header-accent, white);color:var(--color-on-header-accent, white)}.ds-tab-button.tab-selected ds-icon svg{fill:var(--color-on-header-accent, white)}.ds-tab-button .button-native{width:auto;padding:0}.ds-tab-button ion-label{font-size:var(--font-size-sm);font-weight:500;margin:0;color:inherit}.ds-tab-button .tab-icon-wrapper{margin-right:4px;margin-bottom:0}.ds-tab-button ion-ripple-effect{color:rgba(var(--header-content-color-rgb, 255, 255, 255),.3);border-radius:1000px;transform:scale(1.5)}}@media (min-width: 768px){.ds-tab-bar__actions ds-avatar{cursor:pointer;transition:transform .2s ease}.ds-tab-bar__actions ds-avatar:hover{transform:scale(1.05)}}\n"] }]
10436
+ `, styles: [":host{--ds-tab-bar-height: 64px}@media (min-width: 768px){:host{display:block;--ds-tab-bar-height: 64px}}@media (max-width: 767px){:host{display:contents}}ion-tabs.ds-tabs-wrapper{height:100%;background:var(--color-header-surface)}ion-tab-button:before,ion-tab-button:after{content:none!important;display:none!important}ion-tab-button[title]:before,ion-tab-button[title]:after{display:none!important}ion-tab-button::part(native):before,ion-tab-button::part(native):after{display:none!important}.ds-tab-bar{--background: var(--color-background-neutral-primary);transition:transform .2s ease-in-out}ion-tab-bar[slot=bottom]{border-top:1px solid var(--border-color-default);padding-top:8px;padding-bottom:max(8px,calc(var(--ion-safe-area-bottom, 0px) - 16px));padding-left:12px;padding-right:12px}@media (max-width: 767px){ion-tab-bar[slot=bottom]{position:fixed;bottom:0;left:0;right:0;z-index:100}}@media (max-width: 767px){:host-context(ion-tabs:has(ds-mobile-page-details)) .ds-tab-bar{transform:translateY(100%);transition:transform .3s ease}}.ds-tab-bar__logo,.ds-tab-bar__actions{display:none}.ds-tab-bar__tabs{display:flex;width:100%;justify-content:space-around;align-items:center}@media (min-width: 769px){ion-tab-bar[slot=bottom]{padding-bottom:env(safe-area-inset-bottom,0px)}}@media (display-mode: standalone){ion-tab-bar[slot=bottom]{padding-bottom:env(safe-area-inset-bottom,0px)!important}}@media (min-width: 768px){:host[slot=top]{order:-1!important}:host ion-tab-bar{position:relative!important;bottom:auto!important;top:0!important}ion-tab-bar[slot=top]{--background: var(--color-header-surface);position:relative!important;display:flex!important;align-items:center;padding:12px 24px;height:64px;max-width:none;bottom:auto!important;top:0!important}ion-tab-bar[slot=bottom]{position:relative!important;bottom:auto!important}ion-tabs>div:not([slot]){order:1!important}.ds-tab-bar__logo{display:flex;position:absolute;left:24px;align-items:center;color:var(--header-content-color, white)}.ds-tab-bar__actions{display:flex;position:absolute;right:24px;align-items:center;gap:12px}.ds-tab-bar__tabs{display:flex;gap:8px;align-items:center;max-width:640px;width:100%;margin:0 auto;justify-content:center;padding-left:var(--content-padding-md);padding-right:var(--content-padding-md)}.logomark{height:28px;width:auto;flex-shrink:0}}@media (min-width: 992px){.ds-tab-bar__tabs{max-width:640px;padding-left:var(--content-padding-lg);padding-right:var(--content-padding-lg);justify-content:center}}@media (min-width: 1440px){.ds-tab-bar__tabs{max-width:640px;padding-left:var(--content-padding-xl);padding-right:var(--content-padding-xl)}}@media (min-width: 1768px){.ds-tab-bar__tabs{max-width:640px;padding-left:var(--content-padding-2xl);padding-right:var(--content-padding-2xl)}}@media (min-width: 1920px){.ds-tab-bar__tabs{max-width:640px;padding-left:var(--content-padding-3xl);padding-right:var(--content-padding-3xl)}}ion-tab-button{--color: var(--text-color-default-tertiary);--color-selected: var(--color-accent);display:flex;flex-direction:column;align-items:center;justify-content:center;position:relative;overflow:visible;pointer-events:auto}ion-tab-button[title]:before{content:attr(title);position:absolute;opacity:0;pointer-events:none}.tab-icon-ripple{position:absolute;left:50%;top:50%;width:40px;height:40px;border-radius:50%;background:var(--color-accent);transform:translate(-50%,-50%) scale(0);opacity:0;pointer-events:none;transition:all .6s cubic-bezier(.36,1.2,.04,1.4);z-index:0}.tab-selected .tab-icon-ripple{transform:translate(-50%,-50%) scale(2);opacity:.05;animation:ripple-fade .6s cubic-bezier(.36,.5,.04,1.8) forwards}@keyframes ripple-fade{0%{opacity:0;transform:translate(-50%,-50%) scale(0)}30%{opacity:.1}to{opacity:0;transform:translate(-50%,-50%) scale(2)}}ion-tab-button::part(native){overflow:visible}ion-tab-button ion-ripple-effect{color:var(--color-accent);border-radius:1000px}ion-tab-button ion-label{font-size:11px;font-weight:500;letter-spacing:-.5px;margin-top:0}.tab-icon-wrapper{position:relative;width:24px;height:24px;display:flex;align-items:center;justify-content:center;z-index:1;margin-bottom:4px}.tab-icon-inactive,.tab-icon-active{position:absolute;left:50%;top:50%;transform:translate(-50%,-50%);transition:all .8s cubic-bezier(.36,1,.04,1)}.tab-icon-inactive{opacity:1;transform:translate(-50%,-50%) scale(1)}.tab-icon-active{opacity:0;transform:translate(-50%,calc(-50% - 12px)) scale(.5)}.tab-selected .tab-icon-inactive{opacity:0;transform:translate(-50%,-50%) scale(.5)}.tab-selected .tab-icon-active{opacity:1;transform:translate(-50%,-50%) scale(1)}@media (min-width: 768px){.ds-tab-button{flex-direction:row;height:40px;padding:0 16px!important;border-radius:40px;transition:all .2s ease;width:-moz-fit-content;width:fit-content;min-width:-moz-fit-content;min-width:fit-content;flex:0 0 auto;--color: rgba(var(--color-header-content-rgb, 255, 255, 255), .7);--color-selected: var(--color-header-content, white);color:rgba(var(--color-header-content-rgb, 255, 255, 255),.7);background:transparent;position:relative;overflow:hidden}.tab-icon-wrapper,.tab-icon-ripple{width:20px;height:20px}.ds-tab-button::part(native){border-radius:40px}.ds-tab-button:hover:not(.tab-selected){--color: var(--color-header-content, white);--color-selected: var(--color-header-content, white);color:var(--color-header-content, white);background:rgba(var(--color-header-content-rgb, 255, 255, 255),.1)}.ds-tab-button:hover:not(.tab-selected) ion-label{color:var(--color-header-content, white)}.ds-tab-button:hover:not(.tab-selected) ds-icon{--icon-color: var(--color-header-content, white);color:var(--color-header-content, white)}.ds-tab-button:hover:not(.tab-selected) ds-icon svg{fill:var(--color-header-content, white)}.ds-tab-button.tab-selected,.ds-tab-button.tab-selected:hover{background:var(--color-header-accent);--color-selected: var(--color-on-header-accent, white);--color: var(--color-on-header-accent, white);color:var(--color-on-header-accent, white)}.ds-tab-button.tab-selected:hover ion-label{color:var(--color-on-header-accent, white)}.ds-tab-button.tab-selected:hover ds-icon{--icon-color: var(--color-on-header-accent, white);color:var(--color-on-header-accent, white)}.ds-tab-button.tab-selected:hover ds-icon svg{fill:var(--color-on-header-accent, white)}.ds-tab-button.tab-selected:hover .tab-icon-active{opacity:1!important;visibility:visible!important}.ds-tab-button.tab-selected:hover .tab-icon-inactive{opacity:0!important}.ds-tab-button.tab-selected ion-label{color:var(--color-on-header-accent, white)}.ds-tab-button.tab-selected ds-icon{--icon-color: var(--color-on-header-accent, white);color:var(--color-on-header-accent, white)}.ds-tab-button.tab-selected ds-icon svg{fill:var(--color-on-header-accent, white)}.ds-tab-button .button-native{width:auto;padding:0}.ds-tab-button ion-label{font-size:var(--font-size-sm);font-weight:500;margin:0;color:inherit}.ds-tab-button .tab-icon-wrapper{margin-right:4px;margin-bottom:0}.ds-tab-button ion-ripple-effect{color:rgba(var(--header-content-color-rgb, 255, 255, 255),.3);border-radius:1000px;transform:scale(1.5)}}@media (min-width: 768px){.ds-tab-bar__actions ds-avatar{cursor:pointer;transition:transform .2s ease}.ds-tab-bar__actions ds-avatar:hover{transform:scale(1.05)}}\n"] }]
10429
10437
  }], ctorParameters: () => [{ type: i0.ElementRef }], propDecorators: { tabs: [{
10430
10438
  type: Input
10431
10439
  }], avatarType: [{
@@ -10720,6 +10728,12 @@ class DsMobileSwiperComponent {
10720
10728
  slideNext() {
10721
10729
  this.swiperInstance?.slideNext();
10722
10730
  }
10731
+ /**
10732
+ * Navigate to a specific slide by index
10733
+ */
10734
+ slideTo(index, speed = 300) {
10735
+ this.swiperInstance?.slideTo(index, speed);
10736
+ }
10723
10737
  /**
10724
10738
  * Check if at the beginning
10725
10739
  */
@@ -16007,18 +16021,17 @@ class DsMobileNewInquiryModalComponent {
16007
16021
  return;
16008
16022
  }
16009
16023
  try {
16010
- const image = await Camera.getPhoto({
16011
- quality: 90,
16012
- allowEditing: false,
16013
- resultType: CameraResultType.Uri,
16014
- source: CameraSource.Photos, // Only show photo library
16024
+ const result = await FilePicker.pickImages({
16025
+ limit: 1,
16015
16026
  });
16016
- if (image.webPath) {
16027
+ const image = result.files?.[0];
16028
+ if (image) {
16017
16029
  const newAttachment = {
16018
16030
  id: `photo-${Date.now()}`,
16019
- src: image.webPath,
16031
+ src: image.path ? Capacitor.convertFileSrc(image.path) : (image.blob ? URL.createObjectURL(image.blob) : ''),
16020
16032
  type: 'image',
16021
- name: `Photo ${this.attachments().length + 1}`,
16033
+ name: image.name || `Photo ${this.attachments().length + 1}`,
16034
+ size: this.formatFileSize(image.size ?? 0),
16022
16035
  };
16023
16036
  this.attachments.update((attachments) => [...attachments, newAttachment]);
16024
16037
  }
@@ -16412,6 +16425,11 @@ class DsMobileBookingModalComponent {
16412
16425
  modalController;
16413
16426
  facilityId;
16414
16427
  facilityTitle;
16428
+ /**
16429
+ * Number of days ahead available for booking selection.
16430
+ * Defaults to 60 (2 months). Override via componentProps when opening the modal.
16431
+ */
16432
+ daysAhead = 60;
16415
16433
  swiperComponent;
16416
16434
  // Signals for reactive state management
16417
16435
  dateOptions = signal([], ...(ngDevMode ? [{ debugName: "dateOptions" }] : []));
@@ -16442,22 +16460,50 @@ class DsMobileBookingModalComponent {
16442
16460
  }
16443
16461
  }, 150);
16444
16462
  }
16463
+ /**
16464
+ * Returns true if the given date should be disabled in both the swiper and the datepicker.
16465
+ * Weekends are disabled. Index-based mock disabling (i===3, i===7) is swiper-only and
16466
+ * not representable as a date rule, so it is intentionally excluded here.
16467
+ */
16468
+ isDateUnavailable(date) {
16469
+ return date.getDay() === 0 || date.getDay() === 6;
16470
+ }
16471
+ /**
16472
+ * Computed signal that returns a fresh disabled-date function whenever dateOptions()
16473
+ * changes. Returning a new function reference causes the datepicker's own isDateDisabled
16474
+ * input to update, which triggers its internal computed to re-run and re-render all
16475
+ * calendar cells with the correct disabled state.
16476
+ * The disabledSet is pre-built once per computation for O(1) per-cell lookups.
16477
+ */
16478
+ dateDisabledFn = computed(() => {
16479
+ const disabledSet = new Set(this.dateOptions()
16480
+ .filter(opt => opt.state === 'disabled')
16481
+ .map(opt => opt.fullDate.toDateString()));
16482
+ return (date) => {
16483
+ const today = new Date();
16484
+ today.setHours(0, 0, 0, 0);
16485
+ const max = new Date(today);
16486
+ max.setDate(today.getDate() + this.daysAhead - 1);
16487
+ const d = new Date(date);
16488
+ d.setHours(0, 0, 0, 0);
16489
+ if (d < today || d > max)
16490
+ return true;
16491
+ return disabledSet.has(date.toDateString());
16492
+ };
16493
+ }, ...(ngDevMode ? [{ debugName: "dateDisabledFn" }] : []));
16445
16494
  /**
16446
16495
  * Generate mock date and time data
16447
16496
  */
16448
16497
  generateMockData() {
16449
- // Generate 14 days starting from today
16450
16498
  const dates = [];
16451
16499
  const today = new Date();
16452
16500
  const dayNames = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'];
16453
16501
  const monthNames = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
16454
16502
  let firstAvailableDate = null;
16455
- for (let i = 0; i < 14; i++) {
16503
+ for (let i = 0; i < this.daysAhead; i++) {
16456
16504
  const date = new Date(today);
16457
16505
  date.setDate(today.getDate() + i);
16458
- // Mark some dates as disabled (e.g., weekends or specific dates)
16459
- const isWeekend = date.getDay() === 0 || date.getDay() === 6;
16460
- const isDisabled = isWeekend || (i === 3) || (i === 7); // Example: disable some dates
16506
+ const isDisabled = this.isDateUnavailable(date) || (i === 3) || (i === 7);
16461
16507
  const dateOption = {
16462
16508
  id: `date-${i}`,
16463
16509
  dayName: dayNames[date.getDay()],
@@ -16545,6 +16591,23 @@ class DsMobileBookingModalComponent {
16545
16591
  this.timeSlots.set(updatedSlots);
16546
16592
  this.selectedTimeSlot.set(selectedSlot);
16547
16593
  }
16594
+ /**
16595
+ * Called when the datepicker overlay emits a date selection.
16596
+ * Finds the matching DateOption in the swiper, selects it, and auto-scrolls to it.
16597
+ */
16598
+ jumpToDate(date) {
16599
+ if (!date)
16600
+ return;
16601
+ const options = this.dateOptions();
16602
+ const idx = options.findIndex(opt => opt.fullDate.toDateString() === date.toDateString());
16603
+ if (idx === -1)
16604
+ return;
16605
+ const option = options[idx];
16606
+ if (option.state === 'disabled')
16607
+ return;
16608
+ this.selectDate(option);
16609
+ this.swiperComponent?.slideTo(idx);
16610
+ }
16548
16611
  /**
16549
16612
  * Handle confirm button click
16550
16613
  */
@@ -16571,7 +16634,7 @@ class DsMobileBookingModalComponent {
16571
16634
  await this.modalController.dismiss(null, 'cancel');
16572
16635
  }
16573
16636
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: DsMobileBookingModalComponent, deps: [{ token: i1.ModalController }], target: i0.ɵɵFactoryTarget.Component });
16574
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.16", type: DsMobileBookingModalComponent, isStandalone: true, selector: "ds-mobile-booking-modal", inputs: { facilityId: "facilityId", facilityTitle: "facilityTitle" }, viewQueries: [{ propertyName: "swiperComponent", first: true, predicate: DsMobileSwiperComponent, descendants: true }], ngImport: i0, template: `
16637
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.16", type: DsMobileBookingModalComponent, isStandalone: true, selector: "ds-mobile-booking-modal", inputs: { facilityId: "facilityId", facilityTitle: "facilityTitle", daysAhead: "daysAhead" }, viewQueries: [{ propertyName: "swiperComponent", first: true, predicate: DsMobileSwiperComponent, descendants: true }], ngImport: i0, template: `
16575
16638
  <ds-mobile-modal-base
16576
16639
  headerTitle="Hvornår skal det være?"
16577
16640
  [showCloseButton]="true"
@@ -16580,6 +16643,16 @@ class DsMobileBookingModalComponent {
16580
16643
 
16581
16644
  <!-- Date Section -->
16582
16645
  <ds-mobile-section headline="Vælg dato">
16646
+ <ds-datepicker
16647
+ header-action
16648
+ [isDateDisabled]="dateDisabledFn()"
16649
+ (dateChange)="jumpToDate($event)">
16650
+ <span class="calendar-link">
16651
+ Vælg fra kalender
16652
+ <ds-icon name="remixArrowRightSLine" size="16px" />
16653
+ </span>
16654
+ </ds-datepicker>
16655
+
16583
16656
  <ds-mobile-swiper
16584
16657
  class="date-swiper"
16585
16658
  [slideWidth]="'auto'"
@@ -16633,7 +16706,7 @@ class DsMobileBookingModalComponent {
16633
16706
  </ds-button>
16634
16707
  </div>
16635
16708
  </ds-mobile-modal-base>
16636
- `, isInline: true, styles: [".date-item{display:flex;flex-direction:column;align-items:center;justify-content:center;width:80px;padding:12px 8px;border-radius:12px;border:1px solid var(--color-border, #e5e5e5);background:var(--color-surface-primary, #ffffff);cursor:pointer;transition:all .2s ease;-webkit-tap-highlight-color:transparent;gap:4px}.date-item .day-name{font-family:Brockmann,sans-serif;font-size:var(--font-size-xs, 12px);font-weight:400;color:var(--text-color-default-secondary, #71727a);text-transform:uppercase}.date-item .date-number{font-family:Brockmann,sans-serif;font-size:var(--font-size-xl, 20px);font-weight:600;color:var(--text-color-default-primary, #202227)}.date-item .month-name{font-family:Brockmann,sans-serif;font-size:var(--font-size-xs, 12px);font-weight:400;color:var(--text-color-default-secondary, #71727a);text-transform:capitalize}.date-item.selected{background:var(--color-accent, #5d5fef);border-color:var(--color-accent, #5d5fef)}.date-item.selected .day-name,.date-item.selected .date-number,.date-item.selected .month-name{color:#fff}.date-item.disabled{background:var(--color-background-neutral-secondary, #f5f5f5);border-color:var(--color-border, #e5e5e5);cursor:not-allowed;opacity:.6}.date-item.disabled .day-name,.date-item.disabled .date-number,.date-item.disabled .month-name{color:var(--text-color-default-tertiary, #9a9aa2)}.date-item:not(.disabled):not(.selected):hover{border-color:var(--color-accent, #5d5fef);background:var(--color-background-neutral-secondary, #f5f5f5)}.time-slots-list{display:flex;flex-direction:column;gap:8px}.time-slot-item{display:flex;align-items:center;justify-content:space-between;width:100%;padding:16px 20px;border-radius:12px;border:1px solid var(--color-border, #e5e5e5);background:var(--color-surface-primary, #ffffff);cursor:pointer;transition:all .2s ease;-webkit-tap-highlight-color:transparent}.time-slot-item span{font-family:Brockmann,sans-serif;font-size:var(--font-size-base, 16px);font-weight:400;color:var(--text-color-default-primary, #202227)}.time-slot-item.selected{background:var(--color-accent, #5d5fef);border-color:var(--color-accent, #5d5fef)}.time-slot-item.selected span{color:#fff;font-weight:500}.time-slot-item.disabled{background:var(--color-background-neutral-secondary, #f5f5f5);border-color:var(--color-border, #e5e5e5);cursor:not-allowed;opacity:.6}.time-slot-item.disabled span{color:var(--text-color-default-tertiary, #9a9aa2)}.time-slot-item:not(.disabled):not(.selected):hover{border-color:var(--color-accent, #5d5fef);background:var(--color-background-neutral-secondary, #f5f5f5)}.booking-confirm-action{width:100%;padding:16px 20px;background:var(--color-surface-primary, #ffffff);border-top:1px solid var(--color-border, #e5e5e5);box-sizing:border-box}.booking-confirm-action ::ng-deep ds-button{display:block;width:100%}.booking-confirm-action ::ng-deep ds-button button{width:100%;border-radius:100px;height:48px}::ng-deep .date-swiper .swiper-slide{width:auto!important}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: DsMobileModalBaseComponent, selector: "ds-mobile-modal-base", inputs: ["showHeader"] }, { kind: "component", type: DsMobileSectionComponent, selector: "ds-mobile-section", inputs: ["headline", "icon", "linkText", "padding", "gap", "contentGap", "showBorder", "overflow"], outputs: ["linkClick"] }, { kind: "component", type: DsMobileSwiperComponent, selector: "ds-mobile-swiper", inputs: ["slideWidth", "gap", "pagination", "autoHeight", "progressiveOpacity", "progressiveScale"] }, { kind: "component", type: DsIconComponent, selector: "ds-icon", inputs: ["name", "size", "color", "interactive"] }, { kind: "component", type: DsButtonComponent, selector: "ds-button", inputs: ["variant", "size", "disabled", "loading", "pressed", "expanded", "leadingIcon", "trailingIcon", "ariaLabel", "iconOnly"], outputs: ["clicked", "focused", "blurred"] }] });
16709
+ `, isInline: true, styles: [".date-item{display:flex;flex-direction:column;align-items:center;justify-content:center;width:80px;padding:12px 8px;border-radius:12px;border:1px solid var(--color-border, #e5e5e5);background:var(--color-surface-primary, #ffffff);cursor:pointer;transition:all .2s ease;-webkit-tap-highlight-color:transparent;gap:4px}.date-item .day-name{font-family:Brockmann,sans-serif;font-size:var(--font-size-xs, 12px);font-weight:400;color:var(--text-color-default-secondary, #71727a);text-transform:uppercase}.date-item .date-number{font-family:Brockmann,sans-serif;font-size:var(--font-size-xl, 20px);font-weight:600;color:var(--text-color-default-primary, #202227)}.date-item .month-name{font-family:Brockmann,sans-serif;font-size:var(--font-size-xs, 12px);font-weight:400;color:var(--text-color-default-secondary, #71727a);text-transform:capitalize}.date-item.selected{background:var(--color-accent, #5d5fef);border-color:var(--color-accent, #5d5fef)}.date-item.selected .day-name,.date-item.selected .date-number,.date-item.selected .month-name{color:#fff}.date-item.disabled{background:var(--color-background-neutral-secondary, #f5f5f5);border-color:var(--color-border, #e5e5e5);cursor:not-allowed;opacity:.6}.date-item.disabled .day-name,.date-item.disabled .date-number,.date-item.disabled .month-name{color:var(--text-color-default-tertiary, #9a9aa2)}.date-item:not(.disabled):not(.selected):hover{border-color:var(--color-accent, #5d5fef);background:var(--color-background-neutral-secondary, #f5f5f5)}.time-slots-list{display:flex;flex-direction:column;gap:8px}.time-slot-item{display:flex;align-items:center;justify-content:space-between;width:100%;padding:16px 20px;border-radius:12px;border:1px solid var(--color-border, #e5e5e5);background:var(--color-surface-primary, #ffffff);cursor:pointer;transition:all .2s ease;-webkit-tap-highlight-color:transparent}.time-slot-item span{font-family:Brockmann,sans-serif;font-size:var(--font-size-base, 16px);font-weight:400;color:var(--text-color-default-primary, #202227)}.time-slot-item.selected{background:var(--color-accent, #5d5fef);border-color:var(--color-accent, #5d5fef)}.time-slot-item.selected span{color:#fff;font-weight:500}.time-slot-item.disabled{background:var(--color-background-neutral-secondary, #f5f5f5);border-color:var(--color-border, #e5e5e5);cursor:not-allowed;opacity:.6}.time-slot-item.disabled span{color:var(--text-color-default-tertiary, #9a9aa2)}.time-slot-item:not(.disabled):not(.selected):hover{border-color:var(--color-accent, #5d5fef);background:var(--color-background-neutral-secondary, #f5f5f5)}.booking-confirm-action{width:100%;padding:16px 20px;background:var(--color-surface-primary, #ffffff);border-top:1px solid var(--color-border, #e5e5e5);box-sizing:border-box}.booking-confirm-action ::ng-deep ds-button{display:block;width:100%}.booking-confirm-action ::ng-deep ds-button button{width:100%;border-radius:100px;height:48px}::ng-deep .date-swiper .swiper-slide{width:auto!important}.calendar-link{display:flex;align-items:center;gap:2px;font-family:Brockmann,sans-serif;font-size:var(--font-size-sm);font-weight:500;color:var(--color-accent, #5d5fef);cursor:pointer;white-space:nowrap;-webkit-user-select:none;user-select:none;line-height:1}::ng-deep .cdk-overlay-container{z-index:20001}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: DsMobileModalBaseComponent, selector: "ds-mobile-modal-base", inputs: ["showHeader"] }, { kind: "component", type: DsMobileSectionComponent, selector: "ds-mobile-section", inputs: ["headline", "icon", "linkText", "padding", "gap", "contentGap", "showBorder", "overflow"], outputs: ["linkClick"] }, { kind: "component", type: DsMobileSwiperComponent, selector: "ds-mobile-swiper", inputs: ["slideWidth", "gap", "pagination", "autoHeight", "progressiveOpacity", "progressiveScale"] }, { kind: "component", type: DsIconComponent, selector: "ds-icon", inputs: ["name", "size", "color", "interactive"] }, { kind: "component", type: DsButtonComponent, selector: "ds-button", inputs: ["variant", "size", "disabled", "loading", "pressed", "expanded", "leadingIcon", "trailingIcon", "ariaLabel", "iconOnly"], outputs: ["clicked", "focused", "blurred"] }, { kind: "component", type: DsDatepickerComponent, selector: "ds-datepicker", inputs: ["variant", "disabled", "placeholder", "ariaLabel", "ariaDescribedBy", "disableFutureDates", "disablePastDates", "isDateDisabled", "locale"], outputs: ["dateChange", "opened", "closed"] }] });
16637
16710
  }
16638
16711
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: DsMobileBookingModalComponent, decorators: [{
16639
16712
  type: Component,
@@ -16643,7 +16716,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImpo
16643
16716
  DsMobileSectionComponent,
16644
16717
  DsMobileSwiperComponent,
16645
16718
  DsIconComponent,
16646
- DsButtonComponent
16719
+ DsButtonComponent,
16720
+ DsDatepickerComponent
16647
16721
  ], schemas: [CUSTOM_ELEMENTS_SCHEMA], template: `
16648
16722
  <ds-mobile-modal-base
16649
16723
  headerTitle="Hvornår skal det være?"
@@ -16653,6 +16727,16 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImpo
16653
16727
 
16654
16728
  <!-- Date Section -->
16655
16729
  <ds-mobile-section headline="Vælg dato">
16730
+ <ds-datepicker
16731
+ header-action
16732
+ [isDateDisabled]="dateDisabledFn()"
16733
+ (dateChange)="jumpToDate($event)">
16734
+ <span class="calendar-link">
16735
+ Vælg fra kalender
16736
+ <ds-icon name="remixArrowRightSLine" size="16px" />
16737
+ </span>
16738
+ </ds-datepicker>
16739
+
16656
16740
  <ds-mobile-swiper
16657
16741
  class="date-swiper"
16658
16742
  [slideWidth]="'auto'"
@@ -16706,11 +16790,13 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImpo
16706
16790
  </ds-button>
16707
16791
  </div>
16708
16792
  </ds-mobile-modal-base>
16709
- `, styles: [".date-item{display:flex;flex-direction:column;align-items:center;justify-content:center;width:80px;padding:12px 8px;border-radius:12px;border:1px solid var(--color-border, #e5e5e5);background:var(--color-surface-primary, #ffffff);cursor:pointer;transition:all .2s ease;-webkit-tap-highlight-color:transparent;gap:4px}.date-item .day-name{font-family:Brockmann,sans-serif;font-size:var(--font-size-xs, 12px);font-weight:400;color:var(--text-color-default-secondary, #71727a);text-transform:uppercase}.date-item .date-number{font-family:Brockmann,sans-serif;font-size:var(--font-size-xl, 20px);font-weight:600;color:var(--text-color-default-primary, #202227)}.date-item .month-name{font-family:Brockmann,sans-serif;font-size:var(--font-size-xs, 12px);font-weight:400;color:var(--text-color-default-secondary, #71727a);text-transform:capitalize}.date-item.selected{background:var(--color-accent, #5d5fef);border-color:var(--color-accent, #5d5fef)}.date-item.selected .day-name,.date-item.selected .date-number,.date-item.selected .month-name{color:#fff}.date-item.disabled{background:var(--color-background-neutral-secondary, #f5f5f5);border-color:var(--color-border, #e5e5e5);cursor:not-allowed;opacity:.6}.date-item.disabled .day-name,.date-item.disabled .date-number,.date-item.disabled .month-name{color:var(--text-color-default-tertiary, #9a9aa2)}.date-item:not(.disabled):not(.selected):hover{border-color:var(--color-accent, #5d5fef);background:var(--color-background-neutral-secondary, #f5f5f5)}.time-slots-list{display:flex;flex-direction:column;gap:8px}.time-slot-item{display:flex;align-items:center;justify-content:space-between;width:100%;padding:16px 20px;border-radius:12px;border:1px solid var(--color-border, #e5e5e5);background:var(--color-surface-primary, #ffffff);cursor:pointer;transition:all .2s ease;-webkit-tap-highlight-color:transparent}.time-slot-item span{font-family:Brockmann,sans-serif;font-size:var(--font-size-base, 16px);font-weight:400;color:var(--text-color-default-primary, #202227)}.time-slot-item.selected{background:var(--color-accent, #5d5fef);border-color:var(--color-accent, #5d5fef)}.time-slot-item.selected span{color:#fff;font-weight:500}.time-slot-item.disabled{background:var(--color-background-neutral-secondary, #f5f5f5);border-color:var(--color-border, #e5e5e5);cursor:not-allowed;opacity:.6}.time-slot-item.disabled span{color:var(--text-color-default-tertiary, #9a9aa2)}.time-slot-item:not(.disabled):not(.selected):hover{border-color:var(--color-accent, #5d5fef);background:var(--color-background-neutral-secondary, #f5f5f5)}.booking-confirm-action{width:100%;padding:16px 20px;background:var(--color-surface-primary, #ffffff);border-top:1px solid var(--color-border, #e5e5e5);box-sizing:border-box}.booking-confirm-action ::ng-deep ds-button{display:block;width:100%}.booking-confirm-action ::ng-deep ds-button button{width:100%;border-radius:100px;height:48px}::ng-deep .date-swiper .swiper-slide{width:auto!important}\n"] }]
16793
+ `, styles: [".date-item{display:flex;flex-direction:column;align-items:center;justify-content:center;width:80px;padding:12px 8px;border-radius:12px;border:1px solid var(--color-border, #e5e5e5);background:var(--color-surface-primary, #ffffff);cursor:pointer;transition:all .2s ease;-webkit-tap-highlight-color:transparent;gap:4px}.date-item .day-name{font-family:Brockmann,sans-serif;font-size:var(--font-size-xs, 12px);font-weight:400;color:var(--text-color-default-secondary, #71727a);text-transform:uppercase}.date-item .date-number{font-family:Brockmann,sans-serif;font-size:var(--font-size-xl, 20px);font-weight:600;color:var(--text-color-default-primary, #202227)}.date-item .month-name{font-family:Brockmann,sans-serif;font-size:var(--font-size-xs, 12px);font-weight:400;color:var(--text-color-default-secondary, #71727a);text-transform:capitalize}.date-item.selected{background:var(--color-accent, #5d5fef);border-color:var(--color-accent, #5d5fef)}.date-item.selected .day-name,.date-item.selected .date-number,.date-item.selected .month-name{color:#fff}.date-item.disabled{background:var(--color-background-neutral-secondary, #f5f5f5);border-color:var(--color-border, #e5e5e5);cursor:not-allowed;opacity:.6}.date-item.disabled .day-name,.date-item.disabled .date-number,.date-item.disabled .month-name{color:var(--text-color-default-tertiary, #9a9aa2)}.date-item:not(.disabled):not(.selected):hover{border-color:var(--color-accent, #5d5fef);background:var(--color-background-neutral-secondary, #f5f5f5)}.time-slots-list{display:flex;flex-direction:column;gap:8px}.time-slot-item{display:flex;align-items:center;justify-content:space-between;width:100%;padding:16px 20px;border-radius:12px;border:1px solid var(--color-border, #e5e5e5);background:var(--color-surface-primary, #ffffff);cursor:pointer;transition:all .2s ease;-webkit-tap-highlight-color:transparent}.time-slot-item span{font-family:Brockmann,sans-serif;font-size:var(--font-size-base, 16px);font-weight:400;color:var(--text-color-default-primary, #202227)}.time-slot-item.selected{background:var(--color-accent, #5d5fef);border-color:var(--color-accent, #5d5fef)}.time-slot-item.selected span{color:#fff;font-weight:500}.time-slot-item.disabled{background:var(--color-background-neutral-secondary, #f5f5f5);border-color:var(--color-border, #e5e5e5);cursor:not-allowed;opacity:.6}.time-slot-item.disabled span{color:var(--text-color-default-tertiary, #9a9aa2)}.time-slot-item:not(.disabled):not(.selected):hover{border-color:var(--color-accent, #5d5fef);background:var(--color-background-neutral-secondary, #f5f5f5)}.booking-confirm-action{width:100%;padding:16px 20px;background:var(--color-surface-primary, #ffffff);border-top:1px solid var(--color-border, #e5e5e5);box-sizing:border-box}.booking-confirm-action ::ng-deep ds-button{display:block;width:100%}.booking-confirm-action ::ng-deep ds-button button{width:100%;border-radius:100px;height:48px}::ng-deep .date-swiper .swiper-slide{width:auto!important}.calendar-link{display:flex;align-items:center;gap:2px;font-family:Brockmann,sans-serif;font-size:var(--font-size-sm);font-weight:500;color:var(--color-accent, #5d5fef);cursor:pointer;white-space:nowrap;-webkit-user-select:none;user-select:none;line-height:1}::ng-deep .cdk-overlay-container{z-index:20001}\n"] }]
16710
16794
  }], ctorParameters: () => [{ type: i1.ModalController }], propDecorators: { facilityId: [{
16711
16795
  type: Input
16712
16796
  }], facilityTitle: [{
16713
16797
  type: Input
16798
+ }], daysAhead: [{
16799
+ type: Input
16714
16800
  }], swiperComponent: [{
16715
16801
  type: ViewChild,
16716
16802
  args: [DsMobileSwiperComponent]
@@ -16960,12 +17046,14 @@ class DsMobileBookingModalService extends BaseModalService {
16960
17046
  * @param facilityId The ID of the facility to book
16961
17047
  * @param facilityTitle The display name of the facility
16962
17048
  * @param facilityThumbnail Optional thumbnail image URL
17049
+ * @param daysAhead Number of days ahead available for selection (defaults to 14)
16963
17050
  * @returns Promise that resolves when the booking flow is complete
16964
17051
  */
16965
- async open(facilityId, facilityTitle, facilityThumbnail) {
17052
+ async open(facilityId, facilityTitle, facilityThumbnail, daysAhead) {
16966
17053
  const modal = await this.createModal(DsMobileBookingModalComponent, {
16967
17054
  facilityId,
16968
- facilityTitle
17055
+ facilityTitle,
17056
+ ...(daysAhead !== undefined && { daysAhead })
16969
17057
  }, { keyboardClose: true });
16970
17058
  await modal.present();
16971
17059
  const result = await modal.onWillDismiss();
@@ -17272,18 +17360,17 @@ class DsMobileFacilityCreationModalComponent {
17272
17360
  return;
17273
17361
  }
17274
17362
  try {
17275
- const image = await Camera.getPhoto({
17276
- quality: 90,
17277
- allowEditing: false,
17278
- resultType: CameraResultType.Uri,
17279
- source: CameraSource.Photos, // Only show photo library
17363
+ const result = await FilePicker.pickImages({
17364
+ limit: 1,
17280
17365
  });
17281
- if (image.webPath) {
17366
+ const image = result.files?.[0];
17367
+ if (image) {
17282
17368
  const newAttachment = {
17283
17369
  id: `photo-${Date.now()}`,
17284
- src: image.webPath,
17370
+ src: image.path ? Capacitor.convertFileSrc(image.path) : (image.blob ? URL.createObjectURL(image.blob) : ''),
17285
17371
  type: 'image',
17286
- name: `Photo ${this.attachments().length + 1}`,
17372
+ name: image.name || `Photo ${this.attachments().length + 1}`,
17373
+ size: this.formatFileSize(image.size ?? 0),
17287
17374
  };
17288
17375
  this.attachments.update((attachments) => [...attachments, newAttachment]);
17289
17376
  }
@@ -17454,8 +17541,8 @@ class DsMobileFacilityCreationModalComponent {
17454
17541
 
17455
17542
  <!-- List Items Section -->
17456
17543
  <ds-mobile-section [contentGap]="'0'">
17457
- <!-- Who can book -->
17458
- <ds-mobile-list-item
17544
+ <!-- Who can book - hidden until Propbinder API supports this field -->
17545
+ <!-- <ds-mobile-list-item
17459
17546
  [leadingSize]="'32px'"
17460
17547
  [showDivider]="true"
17461
17548
  [interactive]="true"
@@ -17468,7 +17555,7 @@ class DsMobileFacilityCreationModalComponent {
17468
17555
  <div class="detail-value">{{ whoCanBook() }}</div>
17469
17556
  </div>
17470
17557
  <ds-icon content-trailing name="remixArrowRightSLine" size="20px" color="tertiary" />
17471
- </ds-mobile-list-item>
17558
+ </ds-mobile-list-item> -->
17472
17559
 
17473
17560
  <!-- When can it be booked -->
17474
17561
  <ds-mobile-list-item
@@ -17489,7 +17576,7 @@ class DsMobileFacilityCreationModalComponent {
17489
17576
  <!-- Price -->
17490
17577
  <ds-mobile-list-item
17491
17578
  [leadingSize]="'32px'"
17492
- [showDivider]="true"
17579
+ [showDivider]="false"
17493
17580
  [interactive]="true"
17494
17581
  [showDesktopMoreButton]="false"
17495
17582
  [align]="'center'"
@@ -17502,8 +17589,8 @@ class DsMobileFacilityCreationModalComponent {
17502
17589
  <ds-icon content-trailing name="remixArrowRightSLine" size="20px" color="tertiary" />
17503
17590
  </ds-mobile-list-item>
17504
17591
 
17505
- <!-- Access requirements -->
17506
- <ds-mobile-list-item
17592
+ <!-- Access requirements - hidden until Propbinder API supports this field -->
17593
+ <!-- <ds-mobile-list-item
17507
17594
  [leadingSize]="'32px'"
17508
17595
  [showDivider]="false"
17509
17596
  [interactive]="true"
@@ -17516,7 +17603,7 @@ class DsMobileFacilityCreationModalComponent {
17516
17603
  <div class="detail-value">{{ accessRequirements() }}</div>
17517
17604
  </div>
17518
17605
  <ds-icon content-trailing name="remixArrowRightSLine" size="20px" color="tertiary" />
17519
- </ds-mobile-list-item>
17606
+ </ds-mobile-list-item> -->
17520
17607
  </ds-mobile-section>
17521
17608
 
17522
17609
  <!-- Fixed Bottom Container (Slides with keyboard) -->
@@ -17610,8 +17697,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImpo
17610
17697
 
17611
17698
  <!-- List Items Section -->
17612
17699
  <ds-mobile-section [contentGap]="'0'">
17613
- <!-- Who can book -->
17614
- <ds-mobile-list-item
17700
+ <!-- Who can book - hidden until Propbinder API supports this field -->
17701
+ <!-- <ds-mobile-list-item
17615
17702
  [leadingSize]="'32px'"
17616
17703
  [showDivider]="true"
17617
17704
  [interactive]="true"
@@ -17624,7 +17711,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImpo
17624
17711
  <div class="detail-value">{{ whoCanBook() }}</div>
17625
17712
  </div>
17626
17713
  <ds-icon content-trailing name="remixArrowRightSLine" size="20px" color="tertiary" />
17627
- </ds-mobile-list-item>
17714
+ </ds-mobile-list-item> -->
17628
17715
 
17629
17716
  <!-- When can it be booked -->
17630
17717
  <ds-mobile-list-item
@@ -17645,7 +17732,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImpo
17645
17732
  <!-- Price -->
17646
17733
  <ds-mobile-list-item
17647
17734
  [leadingSize]="'32px'"
17648
- [showDivider]="true"
17735
+ [showDivider]="false"
17649
17736
  [interactive]="true"
17650
17737
  [showDesktopMoreButton]="false"
17651
17738
  [align]="'center'"
@@ -17658,8 +17745,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImpo
17658
17745
  <ds-icon content-trailing name="remixArrowRightSLine" size="20px" color="tertiary" />
17659
17746
  </ds-mobile-list-item>
17660
17747
 
17661
- <!-- Access requirements -->
17662
- <ds-mobile-list-item
17748
+ <!-- Access requirements - hidden until Propbinder API supports this field -->
17749
+ <!-- <ds-mobile-list-item
17663
17750
  [leadingSize]="'32px'"
17664
17751
  [showDivider]="false"
17665
17752
  [interactive]="true"
@@ -17672,7 +17759,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImpo
17672
17759
  <div class="detail-value">{{ accessRequirements() }}</div>
17673
17760
  </div>
17674
17761
  <ds-icon content-trailing name="remixArrowRightSLine" size="20px" color="tertiary" />
17675
- </ds-mobile-list-item>
17762
+ </ds-mobile-list-item> -->
17676
17763
  </ds-mobile-section>
17677
17764
 
17678
17765
  <!-- Fixed Bottom Container (Slides with keyboard) -->
@@ -18091,24 +18178,59 @@ var dsMobileWhoCanBookSheet = /*#__PURE__*/Object.freeze({
18091
18178
  * DsMobileWhenCanBookSheetComponent
18092
18179
  *
18093
18180
  * Bottom sheet for selecting when a facility can be booked (days, time range, duration).
18181
+ * "Vælg selv" opens a drum-roll IonPopover on mobile, or inline number inputs on desktop.
18094
18182
  */
18095
18183
  class DsMobileWhenCanBookSheetComponent {
18096
18184
  modalController;
18185
+ platformId = inject(PLATFORM_ID);
18186
+ // Platform
18187
+ isDesktop = signal(false, ...(ngDevMode ? [{ debugName: "isDesktop" }] : []));
18097
18188
  // State
18098
- selectedDays = signal(new Set(['Fre', 'Lør', 'Søn']), ...(ngDevMode ? [{ debugName: "selectedDays" }] : [])); // default to weekend
18189
+ selectedDays = signal(new Set(['Fre', 'Lør', 'Søn']), ...(ngDevMode ? [{ debugName: "selectedDays" }] : []));
18099
18190
  startTime = signal('09:00', ...(ngDevMode ? [{ debugName: "startTime" }] : []));
18100
18191
  endTime = signal('17:30', ...(ngDevMode ? [{ debugName: "endTime" }] : []));
18101
18192
  selectedDuration = signal('1 time', ...(ngDevMode ? [{ debugName: "selectedDuration" }] : []));
18193
+ /**
18194
+ * Maximum number of days available in the "Vælg selv" picker/inputs.
18195
+ * Defaults to 30 (one full month). Override via componentProps when opening the sheet.
18196
+ */
18197
+ maxDays = input(30, ...(ngDevMode ? [{ debugName: "maxDays" }] : []));
18198
+ // Custom duration state
18199
+ pickerOpen = signal(false, ...(ngDevMode ? [{ debugName: "pickerOpen" }] : []));
18200
+ customDays = signal(0, ...(ngDevMode ? [{ debugName: "customDays" }] : []));
18201
+ customHours = signal(1, ...(ngDevMode ? [{ debugName: "customHours" }] : []));
18202
+ customMinutes = signal(0, ...(ngDevMode ? [{ debugName: "customMinutes" }] : []));
18203
+ customDurationLabel = computed(() => {
18204
+ const d = this.customDays();
18205
+ const h = this.customHours();
18206
+ const m = this.customMinutes();
18207
+ const parts = [];
18208
+ if (d > 0)
18209
+ parts.push(`${d} dage`);
18210
+ if (h > 0)
18211
+ parts.push(`${h}t`);
18212
+ if (m > 0)
18213
+ parts.push(`${m}min`);
18214
+ return parts.join(' ');
18215
+ }, ...(ngDevMode ? [{ debugName: "customDurationLabel" }] : []));
18216
+ showInlineInputs = computed(() => this.selectedDuration() === 'Vælg selv' && this.isDesktop(), ...(ngDevMode ? [{ debugName: "showInlineInputs" }] : []));
18102
18217
  // Options
18103
18218
  days = ['Man', 'Tir', 'Ons', 'Tor', 'Fre', 'Lør', 'Søn'];
18104
18219
  durations = [
18105
18220
  { value: '30 min', label: '30 min' },
18106
18221
  { value: '1 time', label: '1 time' },
18107
18222
  { value: '2 timer', label: '2 timer' },
18223
+ { value: 'Hele dagen', label: 'Hele dagen' },
18108
18224
  { value: 'Vælg selv', label: 'Vælg selv' }
18109
18225
  ];
18226
+ daysOptions = computed(() => Array.from({ length: this.maxDays() + 1 }, (_, i) => i), ...(ngDevMode ? [{ debugName: "daysOptions" }] : []));
18227
+ hours = Array.from({ length: 24 }, (_, i) => i);
18228
+ minuteOptions = [0, 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55];
18110
18229
  // Validation
18111
18230
  isValid = computed(() => {
18231
+ if (this.selectedDuration() === 'Vælg selv' && this.customDurationLabel() === '') {
18232
+ return false;
18233
+ }
18112
18234
  return this.selectedDays().size > 0 &&
18113
18235
  this.startTime() &&
18114
18236
  this.endTime();
@@ -18116,6 +18238,50 @@ class DsMobileWhenCanBookSheetComponent {
18116
18238
  constructor(modalController) {
18117
18239
  this.modalController = modalController;
18118
18240
  }
18241
+ ngOnInit() {
18242
+ if (isPlatformBrowser(this.platformId)) {
18243
+ this.isDesktop.set(window.innerWidth >= 768);
18244
+ window.addEventListener('resize', () => {
18245
+ this.isDesktop.set(window.innerWidth >= 768);
18246
+ });
18247
+ }
18248
+ }
18249
+ /**
18250
+ * Handle duration chip selection.
18251
+ * On mobile, opening "Vælg selv" triggers the IonPopover via its trigger id.
18252
+ * On desktop, it shows the inline inputs.
18253
+ */
18254
+ selectDuration(value) {
18255
+ this.selectedDuration.set(value);
18256
+ if (value === 'Vælg selv' && !this.isDesktop()) {
18257
+ this.pickerOpen.set(true);
18258
+ }
18259
+ }
18260
+ /**
18261
+ * Normalizes days/hours/minutes so values never overflow their units.
18262
+ * e.g. 0d 48t 0min → 2d 0t 0min; 0d 0t 75min → 0d 1t 15min.
18263
+ * Caps days at maxDays().
18264
+ */
18265
+ normalize() {
18266
+ const totalMinutes = this.customDays() * 24 * 60
18267
+ + this.customHours() * 60
18268
+ + this.customMinutes();
18269
+ const maxTotalMinutes = this.maxDays() * 24 * 60 + 23 * 60 + 59;
18270
+ const clamped = Math.max(0, Math.min(totalMinutes, maxTotalMinutes));
18271
+ const d = Math.floor(clamped / (24 * 60));
18272
+ const h = Math.floor((clamped % (24 * 60)) / 60);
18273
+ const m = clamped % 60;
18274
+ this.customDays.set(d);
18275
+ this.customHours.set(h);
18276
+ this.customMinutes.set(m);
18277
+ }
18278
+ /**
18279
+ * Called when the mobile picker popover is dismissed — normalize and commit.
18280
+ */
18281
+ onPickerDismiss() {
18282
+ this.normalize();
18283
+ this.pickerOpen.set(false);
18284
+ }
18119
18285
  /**
18120
18286
  * Toggle day selection
18121
18287
  */
@@ -18128,10 +18294,13 @@ class DsMobileWhenCanBookSheetComponent {
18128
18294
  * Confirm selection and dismiss with data
18129
18295
  */
18130
18296
  confirmSelection() {
18297
+ const duration = this.selectedDuration() === 'Vælg selv'
18298
+ ? this.customDurationLabel()
18299
+ : this.selectedDuration();
18131
18300
  const data = {
18132
18301
  days: Array.from(this.selectedDays()),
18133
18302
  timeRange: { start: this.startTime(), end: this.endTime() },
18134
- duration: this.selectedDuration()
18303
+ duration
18135
18304
  };
18136
18305
  this.modalController.dismiss({ value: data }, 'select');
18137
18306
  }
@@ -18142,7 +18311,7 @@ class DsMobileWhenCanBookSheetComponent {
18142
18311
  this.modalController.dismiss(null, 'cancel');
18143
18312
  }
18144
18313
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: DsMobileWhenCanBookSheetComponent, deps: [{ token: i1.ModalController }], target: i0.ɵɵFactoryTarget.Component });
18145
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.16", type: DsMobileWhenCanBookSheetComponent, isStandalone: true, selector: "ds-mobile-when-can-book-sheet", ngImport: i0, template: `
18314
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.16", type: DsMobileWhenCanBookSheetComponent, isStandalone: true, selector: "ds-mobile-when-can-book-sheet", inputs: { maxDays: { classPropertyName: "maxDays", publicName: "maxDays", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: `
18146
18315
  <ds-mobile-bottom-sheet-wrapper>
18147
18316
  <!-- Header with back and done buttons -->
18148
18317
  <ds-mobile-bottom-sheet-header
@@ -18175,9 +18344,9 @@ class DsMobileWhenCanBookSheetComponent {
18175
18344
  <div class="section">
18176
18345
  <ds-label size="md" className="form-section-label">I tidsrummet</ds-label>
18177
18346
  <div class="time-inputs">
18178
- <ds-input-time [(ngModel)]="startTime"></ds-input-time>
18347
+ <ds-input-time size="lg" [(ngModel)]="startTime"></ds-input-time>
18179
18348
  <span class="separator">til</span>
18180
- <ds-input-time [(ngModel)]="endTime"></ds-input-time>
18349
+ <ds-input-time size="lg" [(ngModel)]="endTime"></ds-input-time>
18181
18350
  </div>
18182
18351
  </div>
18183
18352
 
@@ -18189,16 +18358,79 @@ class DsMobileWhenCanBookSheetComponent {
18189
18358
  <button
18190
18359
  type="button"
18191
18360
  class="duration-chip"
18361
+ [id]="duration.value === 'Vælg selv' ? 'vaelg-selv-chip' : null"
18192
18362
  [class.active]="selectedDuration() === duration.value"
18193
- (click)="selectedDuration.set(duration.value)">
18194
- {{ duration.label }}
18363
+ (click)="selectDuration(duration.value)">
18364
+ @if (duration.value === 'Vælg selv' && selectedDuration() === 'Vælg selv') {
18365
+ {{ customDurationLabel() || 'Vælg selv' }}
18366
+ } @else {
18367
+ {{ duration.label }}
18368
+ }
18195
18369
  </button>
18196
18370
  }
18197
18371
  </div>
18372
+
18373
+ <!-- Desktop: inline inputs shown when "Vælg selv" is active -->
18374
+ @if (showInlineInputs()) {
18375
+ <div class="custom-duration-inputs">
18376
+ <input
18377
+ type="number"
18378
+ class="duration-number-input"
18379
+ min="0"
18380
+ [ngModel]="customHours()"
18381
+ (ngModelChange)="customHours.set(+$event)"
18382
+ (blur)="normalize()"
18383
+ />
18384
+ <span class="duration-unit">timer</span>
18385
+ <input
18386
+ type="number"
18387
+ class="duration-number-input"
18388
+ min="0"
18389
+ max="59"
18390
+ [ngModel]="customMinutes()"
18391
+ (ngModelChange)="customMinutes.set(+$event)"
18392
+ (blur)="normalize()"
18393
+ />
18394
+ <span class="duration-unit">min</span>
18395
+ </div>
18396
+ }
18397
+
18398
+ <!-- Mobile: drum-roll popover anchored to the "Vælg selv" chip -->
18399
+ @if (!isDesktop()) {
18400
+ <ds-mobile-dropdown
18401
+ trigger="vaelg-selv-chip"
18402
+ [isOpen]="pickerOpen()"
18403
+ maxWidth="240px"
18404
+ position="above"
18405
+ (closed)="onPickerDismiss()">
18406
+ <ng-template #customContent>
18407
+ <ion-picker style="--background: transparent; --fade-background-rgb: transparent; --highlight-background: rgba(0, 0, 0, 0.05); --highlight-border-radius: 9999px;">
18408
+ <ion-picker-column
18409
+ [value]="customHours()"
18410
+ style="--padding-start: 4px; --padding-end: 4px;"
18411
+ (ionChange)="customHours.set(+($event.detail.value ?? customHours()))">
18412
+ <div slot="suffix" class="picker-suffix">t</div>
18413
+ @for (h of hours; track h) {
18414
+ <ion-picker-column-option [value]="h">{{ h }}</ion-picker-column-option>
18415
+ }
18416
+ </ion-picker-column>
18417
+ <ion-picker-column
18418
+ [value]="customMinutes()"
18419
+ style="--padding-start: 4px; --padding-end: 4px;"
18420
+ (ionChange)="customMinutes.set(+($event.detail.value ?? customMinutes()))">
18421
+ <div slot="suffix" class="picker-suffix">min</div>
18422
+ @for (m of minuteOptions; track m) {
18423
+ <ion-picker-column-option [value]="m">{{ m }}</ion-picker-column-option>
18424
+ }
18425
+ </ion-picker-column>
18426
+ </ion-picker>
18427
+ </ng-template>
18428
+ </ds-mobile-dropdown>
18429
+ }
18198
18430
  </div>
18199
18431
  </div>
18200
18432
  </ds-mobile-bottom-sheet-wrapper>
18201
- `, isInline: true, styles: [".form-content{padding:16px}.section{margin-bottom:24px}.section:last-child{margin-bottom:0}::ng-deep ds-label.form-section-label{display:block;margin-bottom:12px}::ng-deep .form-section-label{font-weight:500}.day-chips{display:flex;gap:8px;flex-wrap:wrap;width:100%}.day-chip{flex:1;min-width:0;padding:10px 16px;border:1px solid var(--border-color-default, #d1d5db);border-radius:8px;background:transparent;color:var(--color-text-primary);font-size:14px;font-weight:500;cursor:pointer;transition:all .2s ease;outline:none}.day-chip:hover{background:var(--color-bg-secondary)}.day-chip.active{background:var(--color-accent);color:#fff;border-color:var(--color-accent)}.time-inputs{display:flex;align-items:center;gap:12px}.separator{color:var(--color-text-secondary);font-size:14px}.duration-chips{display:flex;gap:8px;flex-wrap:wrap}.duration-chip{padding:10px 16px;border:1px solid var(--border-color-default, #d1d5db);border-radius:8px;background:transparent;color:var(--color-text-primary);font-size:14px;font-weight:500;cursor:pointer;transition:all .2s ease;outline:none}.duration-chip:hover{background:var(--color-bg-secondary)}.duration-chip.active{background:var(--color-accent);color:#fff;border-color:var(--color-accent)}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$3.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$3.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "component", type: DsInputTimeComponent, selector: "ds-input-time", inputs: ["variant", "disabled", "readonly", "required", "clearable", "ghost", "ariaLabel", "ariaDescribedBy", "ariaLabelledBy"], outputs: ["valueChange"] }, { kind: "component", type: DsLabelComponent, selector: "ds-label", inputs: ["className", "for", "id", "size"] }, { kind: "component", type: DsMobileBottomSheetWrapperComponent, selector: "ds-mobile-bottom-sheet-wrapper" }, { kind: "component", type: DsMobileBottomSheetHeaderComponent, selector: "ds-mobile-bottom-sheet-header", inputs: ["title", "leftButtonLabel", "rightButtonLabel", "rightButtonDisabled"], outputs: ["leftButtonClick", "rightButtonClick"] }] });
18433
+ `, isInline: true, styles: [".form-content{padding:16px}.section{display:flex;flex-direction:column;gap:8px;margin-bottom:24px}.section:last-child{margin-bottom:0}::ng-deep ds-label.form-section-label{display:block;margin-bottom:12px}::ng-deep .form-section-label{font-weight:500}.day-chips{display:flex;gap:8px;flex-wrap:wrap;width:100%}.day-chip{flex:none;width:64px;padding:10px 16px;border:1px solid var(--border-color-default, #d1d5db);border-radius:8px;background:transparent;color:var(--color-text-primary);font-size:14px;font-weight:500;cursor:pointer;transition:all .2s ease;outline:none}.day-chip:hover{background:var(--color-bg-secondary)}.day-chip.active{background:var(--color-accent);color:#fff;border-color:var(--color-accent)}.time-inputs{display:flex;align-items:center;gap:12px}.separator{color:var(--color-text-secondary);font-size:14px}.duration-chips{display:flex;gap:8px;flex-wrap:wrap}.duration-chip{padding:10px 16px;border:1px solid var(--border-color-default, #d1d5db);border-radius:8px;background:transparent;color:var(--color-text-primary);font-size:14px;font-weight:500;cursor:pointer;transition:all .2s ease;outline:none}.duration-chip:hover{background:var(--color-bg-secondary)}.duration-chip.active{background:var(--color-accent);color:#fff;border-color:var(--color-accent)}.custom-duration-inputs{display:flex;align-items:center;gap:8px;margin-top:12px;animation:fadeSlideIn .15s ease}@keyframes fadeSlideIn{0%{opacity:0;transform:translateY(-4px)}to{opacity:1;transform:translateY(0)}}.duration-number-input{width:64px;padding:8px 10px;border:1px solid var(--border-color-default, #d1d5db);border-radius:8px;background:transparent;color:var(--color-text-primary);font-size:14px;font-weight:500;text-align:center;outline:none;transition:border-color .2s ease}.duration-number-input:focus{border-color:var(--color-accent)}.duration-number-input::-webkit-inner-spin-button,.duration-number-input::-webkit-outer-spin-button{-webkit-appearance:none;margin:0}.duration-number-input[type=number]{-moz-appearance:textfield}.duration-unit{color:var(--color-text-secondary);font-size:14px}.picker-suffix{margin-left:-24px;font-size:13px;color:var(--color-text-secondary)}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$3.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1$3.NumberValueAccessor, selector: "input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]" }, { kind: "directive", type: i1$3.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$3.MinValidator, selector: "input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]", inputs: ["min"] }, { kind: "directive", type: i1$3.MaxValidator, selector: "input[type=number][max][formControlName],input[type=number][max][formControl],input[type=number][max][ngModel]", inputs: ["max"] }, { kind: "directive", type: i1$3.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "component", type: DsInputTimeComponent, selector: "ds-input-time", inputs: ["variant", "disabled", "readonly", "required", "clearable", "ghost", "size", "ariaLabel", "ariaDescribedBy", "ariaLabelledBy"], outputs: ["valueChange"] }, { kind: "component", type: DsLabelComponent, selector: "ds-label", inputs: ["className", "for", "id", "size"] }, { kind: "component", type: DsMobileBottomSheetWrapperComponent, selector: "ds-mobile-bottom-sheet-wrapper" }, { kind: "component", type: DsMobileBottomSheetHeaderComponent, selector: "ds-mobile-bottom-sheet-header", inputs: ["title", "leftButtonLabel", "rightButtonLabel", "rightButtonDisabled"], outputs: ["leftButtonClick", "rightButtonClick"] }, { kind: "component", type: DsMobileDropdownComponent, selector: "ds-mobile-dropdown", inputs: ["trigger", "keepFocusOn", "items", "isOpen", "position", "align", "maxHeight", "emptyMessage", "ariaLabel", "maxWidth"], outputs: ["itemSelected", "closed"] }, { kind: "component", type: IonPicker, selector: "ion-picker", inputs: ["mode"] }, { kind: "component", type: IonPickerColumn, selector: "ion-picker-column", inputs: ["color", "disabled", "mode", "value"] }, { kind: "component", type: IonPickerColumnOption, selector: "ion-picker-column-option", inputs: ["color", "disabled", "value"] }] });
18202
18434
  }
18203
18435
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: DsMobileWhenCanBookSheetComponent, decorators: [{
18204
18436
  type: Component,
@@ -18208,7 +18440,11 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImpo
18208
18440
  DsInputTimeComponent,
18209
18441
  DsLabelComponent,
18210
18442
  DsMobileBottomSheetWrapperComponent,
18211
- DsMobileBottomSheetHeaderComponent
18443
+ DsMobileBottomSheetHeaderComponent,
18444
+ DsMobileDropdownComponent,
18445
+ IonPicker,
18446
+ IonPickerColumn,
18447
+ IonPickerColumnOption,
18212
18448
  ], template: `
18213
18449
  <ds-mobile-bottom-sheet-wrapper>
18214
18450
  <!-- Header with back and done buttons -->
@@ -18242,9 +18478,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImpo
18242
18478
  <div class="section">
18243
18479
  <ds-label size="md" className="form-section-label">I tidsrummet</ds-label>
18244
18480
  <div class="time-inputs">
18245
- <ds-input-time [(ngModel)]="startTime"></ds-input-time>
18481
+ <ds-input-time size="lg" [(ngModel)]="startTime"></ds-input-time>
18246
18482
  <span class="separator">til</span>
18247
- <ds-input-time [(ngModel)]="endTime"></ds-input-time>
18483
+ <ds-input-time size="lg" [(ngModel)]="endTime"></ds-input-time>
18248
18484
  </div>
18249
18485
  </div>
18250
18486
 
@@ -18256,17 +18492,80 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImpo
18256
18492
  <button
18257
18493
  type="button"
18258
18494
  class="duration-chip"
18495
+ [id]="duration.value === 'Vælg selv' ? 'vaelg-selv-chip' : null"
18259
18496
  [class.active]="selectedDuration() === duration.value"
18260
- (click)="selectedDuration.set(duration.value)">
18261
- {{ duration.label }}
18497
+ (click)="selectDuration(duration.value)">
18498
+ @if (duration.value === 'Vælg selv' && selectedDuration() === 'Vælg selv') {
18499
+ {{ customDurationLabel() || 'Vælg selv' }}
18500
+ } @else {
18501
+ {{ duration.label }}
18502
+ }
18262
18503
  </button>
18263
18504
  }
18264
18505
  </div>
18506
+
18507
+ <!-- Desktop: inline inputs shown when "Vælg selv" is active -->
18508
+ @if (showInlineInputs()) {
18509
+ <div class="custom-duration-inputs">
18510
+ <input
18511
+ type="number"
18512
+ class="duration-number-input"
18513
+ min="0"
18514
+ [ngModel]="customHours()"
18515
+ (ngModelChange)="customHours.set(+$event)"
18516
+ (blur)="normalize()"
18517
+ />
18518
+ <span class="duration-unit">timer</span>
18519
+ <input
18520
+ type="number"
18521
+ class="duration-number-input"
18522
+ min="0"
18523
+ max="59"
18524
+ [ngModel]="customMinutes()"
18525
+ (ngModelChange)="customMinutes.set(+$event)"
18526
+ (blur)="normalize()"
18527
+ />
18528
+ <span class="duration-unit">min</span>
18529
+ </div>
18530
+ }
18531
+
18532
+ <!-- Mobile: drum-roll popover anchored to the "Vælg selv" chip -->
18533
+ @if (!isDesktop()) {
18534
+ <ds-mobile-dropdown
18535
+ trigger="vaelg-selv-chip"
18536
+ [isOpen]="pickerOpen()"
18537
+ maxWidth="240px"
18538
+ position="above"
18539
+ (closed)="onPickerDismiss()">
18540
+ <ng-template #customContent>
18541
+ <ion-picker style="--background: transparent; --fade-background-rgb: transparent; --highlight-background: rgba(0, 0, 0, 0.05); --highlight-border-radius: 9999px;">
18542
+ <ion-picker-column
18543
+ [value]="customHours()"
18544
+ style="--padding-start: 4px; --padding-end: 4px;"
18545
+ (ionChange)="customHours.set(+($event.detail.value ?? customHours()))">
18546
+ <div slot="suffix" class="picker-suffix">t</div>
18547
+ @for (h of hours; track h) {
18548
+ <ion-picker-column-option [value]="h">{{ h }}</ion-picker-column-option>
18549
+ }
18550
+ </ion-picker-column>
18551
+ <ion-picker-column
18552
+ [value]="customMinutes()"
18553
+ style="--padding-start: 4px; --padding-end: 4px;"
18554
+ (ionChange)="customMinutes.set(+($event.detail.value ?? customMinutes()))">
18555
+ <div slot="suffix" class="picker-suffix">min</div>
18556
+ @for (m of minuteOptions; track m) {
18557
+ <ion-picker-column-option [value]="m">{{ m }}</ion-picker-column-option>
18558
+ }
18559
+ </ion-picker-column>
18560
+ </ion-picker>
18561
+ </ng-template>
18562
+ </ds-mobile-dropdown>
18563
+ }
18265
18564
  </div>
18266
18565
  </div>
18267
18566
  </ds-mobile-bottom-sheet-wrapper>
18268
- `, styles: [".form-content{padding:16px}.section{margin-bottom:24px}.section:last-child{margin-bottom:0}::ng-deep ds-label.form-section-label{display:block;margin-bottom:12px}::ng-deep .form-section-label{font-weight:500}.day-chips{display:flex;gap:8px;flex-wrap:wrap;width:100%}.day-chip{flex:1;min-width:0;padding:10px 16px;border:1px solid var(--border-color-default, #d1d5db);border-radius:8px;background:transparent;color:var(--color-text-primary);font-size:14px;font-weight:500;cursor:pointer;transition:all .2s ease;outline:none}.day-chip:hover{background:var(--color-bg-secondary)}.day-chip.active{background:var(--color-accent);color:#fff;border-color:var(--color-accent)}.time-inputs{display:flex;align-items:center;gap:12px}.separator{color:var(--color-text-secondary);font-size:14px}.duration-chips{display:flex;gap:8px;flex-wrap:wrap}.duration-chip{padding:10px 16px;border:1px solid var(--border-color-default, #d1d5db);border-radius:8px;background:transparent;color:var(--color-text-primary);font-size:14px;font-weight:500;cursor:pointer;transition:all .2s ease;outline:none}.duration-chip:hover{background:var(--color-bg-secondary)}.duration-chip.active{background:var(--color-accent);color:#fff;border-color:var(--color-accent)}\n"] }]
18269
- }], ctorParameters: () => [{ type: i1.ModalController }] });
18567
+ `, styles: [".form-content{padding:16px}.section{display:flex;flex-direction:column;gap:8px;margin-bottom:24px}.section:last-child{margin-bottom:0}::ng-deep ds-label.form-section-label{display:block;margin-bottom:12px}::ng-deep .form-section-label{font-weight:500}.day-chips{display:flex;gap:8px;flex-wrap:wrap;width:100%}.day-chip{flex:none;width:64px;padding:10px 16px;border:1px solid var(--border-color-default, #d1d5db);border-radius:8px;background:transparent;color:var(--color-text-primary);font-size:14px;font-weight:500;cursor:pointer;transition:all .2s ease;outline:none}.day-chip:hover{background:var(--color-bg-secondary)}.day-chip.active{background:var(--color-accent);color:#fff;border-color:var(--color-accent)}.time-inputs{display:flex;align-items:center;gap:12px}.separator{color:var(--color-text-secondary);font-size:14px}.duration-chips{display:flex;gap:8px;flex-wrap:wrap}.duration-chip{padding:10px 16px;border:1px solid var(--border-color-default, #d1d5db);border-radius:8px;background:transparent;color:var(--color-text-primary);font-size:14px;font-weight:500;cursor:pointer;transition:all .2s ease;outline:none}.duration-chip:hover{background:var(--color-bg-secondary)}.duration-chip.active{background:var(--color-accent);color:#fff;border-color:var(--color-accent)}.custom-duration-inputs{display:flex;align-items:center;gap:8px;margin-top:12px;animation:fadeSlideIn .15s ease}@keyframes fadeSlideIn{0%{opacity:0;transform:translateY(-4px)}to{opacity:1;transform:translateY(0)}}.duration-number-input{width:64px;padding:8px 10px;border:1px solid var(--border-color-default, #d1d5db);border-radius:8px;background:transparent;color:var(--color-text-primary);font-size:14px;font-weight:500;text-align:center;outline:none;transition:border-color .2s ease}.duration-number-input:focus{border-color:var(--color-accent)}.duration-number-input::-webkit-inner-spin-button,.duration-number-input::-webkit-outer-spin-button{-webkit-appearance:none;margin:0}.duration-number-input[type=number]{-moz-appearance:textfield}.duration-unit{color:var(--color-text-secondary);font-size:14px}.picker-suffix{margin-left:-24px;font-size:13px;color:var(--color-text-secondary)}\n"] }]
18568
+ }], ctorParameters: () => [{ type: i1.ModalController }], propDecorators: { maxDays: [{ type: i0.Input, args: [{ isSignal: true, alias: "maxDays", required: false }] }] } });
18270
18569
 
18271
18570
  var dsMobileWhenCanBookSheet = /*#__PURE__*/Object.freeze({
18272
18571
  __proto__: null,
@@ -18317,7 +18616,7 @@ class DsMobilePriceSheetComponent {
18317
18616
  this.modalController.dismiss(null, 'cancel');
18318
18617
  }
18319
18618
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: DsMobilePriceSheetComponent, deps: [{ token: i1.ModalController }], target: i0.ɵɵFactoryTarget.Component });
18320
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.16", type: DsMobilePriceSheetComponent, isStandalone: true, selector: "ds-mobile-price-sheet", ngImport: i0, template: `
18619
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.16", type: DsMobilePriceSheetComponent, isStandalone: true, selector: "ds-mobile-price-sheet", ngImport: i0, template: `
18321
18620
  <ds-mobile-bottom-sheet-wrapper>
18322
18621
  <!-- Header with back and done buttons -->
18323
18622
  <ds-mobile-bottom-sheet-header
@@ -18330,29 +18629,33 @@ class DsMobilePriceSheetComponent {
18330
18629
  </ds-mobile-bottom-sheet-header>
18331
18630
 
18332
18631
  <div class="form-content">
18333
- <!-- Toggle for Gratis -->
18334
- <div class="toggle-row" (click)="toggleFree()">
18335
- <ds-label size="md" className="form-section-label">Gratis</ds-label>
18336
- <div class="toggle-switch" [class.active]="isFree()">
18337
- <div class="toggle-knob"></div>
18632
+ <div class="price-toggle-container">
18633
+ <!-- Toggle for Gratis -->
18634
+ <div class="toggle-row" (click)="toggleFree()">
18635
+ <ds-label size="md" className="form-section-label">Gratis</ds-label>
18636
+ <div class="toggle-switch" [class.active]="isFree()">
18637
+ <div class="toggle-knob"></div>
18638
+ </div>
18338
18639
  </div>
18339
- </div>
18340
18640
 
18341
- <!-- Custom price input -->
18342
- <div class="section">
18343
- <ds-label size="md" className="form-section-label">Hvad skal det koste?</ds-label>
18344
- <ds-input
18345
- type="number"
18346
- [(ngModel)]="customPrice"
18347
- [disabled]="isFree()"
18348
- placeholder="75"
18349
- [suffix]="'kr. per booking'"
18350
- class="price-input">
18351
- </ds-input>
18641
+ <!-- Custom price input inside same container -->
18642
+ @if (!isFree()) {
18643
+ <div class="price-input-row" (click)="$event.stopPropagation()">
18644
+ <ds-label size="md" className="form-section-label">Hvad skal det koste?</ds-label>
18645
+ <ds-input
18646
+ type="number"
18647
+ size="lg"
18648
+ [(ngModel)]="customPrice"
18649
+ placeholder="75"
18650
+ [suffix]="'kr. per booking'"
18651
+ class="price-input">
18652
+ </ds-input>
18653
+ </div>
18654
+ }
18352
18655
  </div>
18353
18656
  </div>
18354
18657
  </ds-mobile-bottom-sheet-wrapper>
18355
- `, isInline: true, styles: [".form-content{padding:16px}.toggle-row{display:flex;justify-content:space-between;align-items:center;padding:16px;background:var(--color-background-neutral-secondary, #f5f5f5);border-radius:12px;margin-bottom:24px;cursor:pointer;transition:background .2s ease}.toggle-row:hover{background:var(--color-background-neutral-secondary-hover, #ebebeb)}::ng-deep ds-label.form-section-label{display:block;margin-bottom:8px}::ng-deep .form-section-label{font-weight:500}.toggle-switch{width:51px;height:31px;background:var(--color-border-default);border-radius:16px;position:relative;transition:background .3s ease;cursor:pointer}.toggle-switch.active{background:var(--color-accent)}.toggle-knob{width:27px;height:27px;background:#fff;border-radius:50%;position:absolute;top:2px;left:2px;transition:transform .3s ease;box-shadow:0 2px 4px #0000001a}.toggle-switch.active .toggle-knob{transform:translate(20px)}.section{margin-bottom:24px}::ng-deep .price-input .ds-input__field{height:48px!important;min-height:48px!important}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$3.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$3.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "component", type: DsInputComponent, selector: "ds-input", inputs: ["variant", "type", "placeholder", "disabled", "readonly", "required", "clearable", "ghost", "leadingIcon", "trailingIcon", "prefix", "suffix", "format", "align", "ariaLabel", "ariaDescribedBy", "ariaLabelledBy"], outputs: ["valueChange", "focused", "blurred"] }, { kind: "component", type: DsLabelComponent, selector: "ds-label", inputs: ["className", "for", "id", "size"] }, { kind: "component", type: DsMobileBottomSheetWrapperComponent, selector: "ds-mobile-bottom-sheet-wrapper" }, { kind: "component", type: DsMobileBottomSheetHeaderComponent, selector: "ds-mobile-bottom-sheet-header", inputs: ["title", "leftButtonLabel", "rightButtonLabel", "rightButtonDisabled"], outputs: ["leftButtonClick", "rightButtonClick"] }] });
18658
+ `, isInline: true, styles: [".form-content{padding:16px}.price-toggle-container{display:flex;flex-direction:column;background:var(--color-background-neutral-primary, #f5f5f5);border:1px solid var(--border-color-default);border-radius:12px;margin-bottom:24px;overflow:hidden}.toggle-row{display:flex;justify-content:space-between;align-items:center;padding:16px;cursor:pointer;transition:background .2s ease}.toggle-row:hover{background:var(--color-background-neutral-primary-hover, #ebebeb)}::ng-deep ds-label.form-section-label{display:block;margin-bottom:8px}::ng-deep .form-section-label{font-weight:500}.toggle-switch{width:51px;height:32px;background:var(--color-background-neutral-tertiary);border-radius:16px;position:relative;transition:background .3s ease;cursor:pointer}.toggle-switch.active{background:var(--color-accent)}.toggle-knob{width:27px;height:27px;background:#fff;border-radius:50%;position:absolute;top:2px;left:2px;transition:transform .3s ease;box-shadow:0 2px 4px #0000001a}.toggle-switch.active .toggle-knob{transform:translate(20px)}.price-input-row{display:flex;flex-direction:column;gap:8px;padding:16px;border-top:1px solid var(--border-color-default)}::ng-deep .price-input .ds-input__field{height:48px!important;min-height:48px!important}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$3.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$3.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "component", type: DsInputComponent, selector: "ds-input", inputs: ["variant", "type", "placeholder", "disabled", "readonly", "required", "clearable", "ghost", "size", "leadingIcon", "trailingIcon", "prefix", "suffix", "format", "align", "ariaLabel", "ariaDescribedBy", "ariaLabelledBy"], outputs: ["valueChange", "focused", "blurred"] }, { kind: "component", type: DsLabelComponent, selector: "ds-label", inputs: ["className", "for", "id", "size"] }, { kind: "component", type: DsMobileBottomSheetWrapperComponent, selector: "ds-mobile-bottom-sheet-wrapper" }, { kind: "component", type: DsMobileBottomSheetHeaderComponent, selector: "ds-mobile-bottom-sheet-header", inputs: ["title", "leftButtonLabel", "rightButtonLabel", "rightButtonDisabled"], outputs: ["leftButtonClick", "rightButtonClick"] }] });
18356
18659
  }
18357
18660
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: DsMobilePriceSheetComponent, decorators: [{
18358
18661
  type: Component,
@@ -18376,29 +18679,33 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImpo
18376
18679
  </ds-mobile-bottom-sheet-header>
18377
18680
 
18378
18681
  <div class="form-content">
18379
- <!-- Toggle for Gratis -->
18380
- <div class="toggle-row" (click)="toggleFree()">
18381
- <ds-label size="md" className="form-section-label">Gratis</ds-label>
18382
- <div class="toggle-switch" [class.active]="isFree()">
18383
- <div class="toggle-knob"></div>
18682
+ <div class="price-toggle-container">
18683
+ <!-- Toggle for Gratis -->
18684
+ <div class="toggle-row" (click)="toggleFree()">
18685
+ <ds-label size="md" className="form-section-label">Gratis</ds-label>
18686
+ <div class="toggle-switch" [class.active]="isFree()">
18687
+ <div class="toggle-knob"></div>
18688
+ </div>
18384
18689
  </div>
18385
- </div>
18386
18690
 
18387
- <!-- Custom price input -->
18388
- <div class="section">
18389
- <ds-label size="md" className="form-section-label">Hvad skal det koste?</ds-label>
18390
- <ds-input
18391
- type="number"
18392
- [(ngModel)]="customPrice"
18393
- [disabled]="isFree()"
18394
- placeholder="75"
18395
- [suffix]="'kr. per booking'"
18396
- class="price-input">
18397
- </ds-input>
18691
+ <!-- Custom price input inside same container -->
18692
+ @if (!isFree()) {
18693
+ <div class="price-input-row" (click)="$event.stopPropagation()">
18694
+ <ds-label size="md" className="form-section-label">Hvad skal det koste?</ds-label>
18695
+ <ds-input
18696
+ type="number"
18697
+ size="lg"
18698
+ [(ngModel)]="customPrice"
18699
+ placeholder="75"
18700
+ [suffix]="'kr. per booking'"
18701
+ class="price-input">
18702
+ </ds-input>
18703
+ </div>
18704
+ }
18398
18705
  </div>
18399
18706
  </div>
18400
18707
  </ds-mobile-bottom-sheet-wrapper>
18401
- `, styles: [".form-content{padding:16px}.toggle-row{display:flex;justify-content:space-between;align-items:center;padding:16px;background:var(--color-background-neutral-secondary, #f5f5f5);border-radius:12px;margin-bottom:24px;cursor:pointer;transition:background .2s ease}.toggle-row:hover{background:var(--color-background-neutral-secondary-hover, #ebebeb)}::ng-deep ds-label.form-section-label{display:block;margin-bottom:8px}::ng-deep .form-section-label{font-weight:500}.toggle-switch{width:51px;height:31px;background:var(--color-border-default);border-radius:16px;position:relative;transition:background .3s ease;cursor:pointer}.toggle-switch.active{background:var(--color-accent)}.toggle-knob{width:27px;height:27px;background:#fff;border-radius:50%;position:absolute;top:2px;left:2px;transition:transform .3s ease;box-shadow:0 2px 4px #0000001a}.toggle-switch.active .toggle-knob{transform:translate(20px)}.section{margin-bottom:24px}::ng-deep .price-input .ds-input__field{height:48px!important;min-height:48px!important}\n"] }]
18708
+ `, styles: [".form-content{padding:16px}.price-toggle-container{display:flex;flex-direction:column;background:var(--color-background-neutral-primary, #f5f5f5);border:1px solid var(--border-color-default);border-radius:12px;margin-bottom:24px;overflow:hidden}.toggle-row{display:flex;justify-content:space-between;align-items:center;padding:16px;cursor:pointer;transition:background .2s ease}.toggle-row:hover{background:var(--color-background-neutral-primary-hover, #ebebeb)}::ng-deep ds-label.form-section-label{display:block;margin-bottom:8px}::ng-deep .form-section-label{font-weight:500}.toggle-switch{width:51px;height:32px;background:var(--color-background-neutral-tertiary);border-radius:16px;position:relative;transition:background .3s ease;cursor:pointer}.toggle-switch.active{background:var(--color-accent)}.toggle-knob{width:27px;height:27px;background:#fff;border-radius:50%;position:absolute;top:2px;left:2px;transition:transform .3s ease;box-shadow:0 2px 4px #0000001a}.toggle-switch.active .toggle-knob{transform:translate(20px)}.price-input-row{display:flex;flex-direction:column;gap:8px;padding:16px;border-top:1px solid var(--border-color-default)}::ng-deep .price-input .ds-input__field{height:48px!important;min-height:48px!important}\n"] }]
18402
18709
  }], ctorParameters: () => [{ type: i1.ModalController }] });
18403
18710
 
18404
18711
  var dsMobilePriceSheet = /*#__PURE__*/Object.freeze({
@@ -18599,25 +18906,6 @@ class DsMobileFacilityDetailModalComponent {
18599
18906
  </div>
18600
18907
  </ds-mobile-swiper>
18601
18908
 
18602
- @if (facilityData.requirements || facilityData.bookingType) {
18603
- <div class="info-items-container">
18604
- <h2 class="section-headline">Ting, du skal vide</h2>
18605
- @if (facilityData.requirements) {
18606
- @for (requirement of facilityData.requirements; track requirement) {
18607
- <div class="info-item">
18608
- <ds-icon name="remixKeyLine" size="16px" color="--text-color-default-secondary" />
18609
- <span>{{ requirement }}</span>
18610
- </div>
18611
- }
18612
- }
18613
- @if (facilityData.bookingType) {
18614
- <div class="info-item">
18615
- <ds-icon name="remixCheckboxCircleLine" size="16px" color="--text-color-default-secondary" />
18616
- <span>{{ facilityData.bookingType }}</span>
18617
- </div>
18618
- }
18619
- </div>
18620
- }
18621
18909
  </ds-mobile-section>
18622
18910
  }
18623
18911
 
@@ -18681,25 +18969,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImpo
18681
18969
  </div>
18682
18970
  </ds-mobile-swiper>
18683
18971
 
18684
- @if (facilityData.requirements || facilityData.bookingType) {
18685
- <div class="info-items-container">
18686
- <h2 class="section-headline">Ting, du skal vide</h2>
18687
- @if (facilityData.requirements) {
18688
- @for (requirement of facilityData.requirements; track requirement) {
18689
- <div class="info-item">
18690
- <ds-icon name="remixKeyLine" size="16px" color="--text-color-default-secondary" />
18691
- <span>{{ requirement }}</span>
18692
- </div>
18693
- }
18694
- }
18695
- @if (facilityData.bookingType) {
18696
- <div class="info-item">
18697
- <ds-icon name="remixCheckboxCircleLine" size="16px" color="--text-color-default-secondary" />
18698
- <span>{{ facilityData.bookingType }}</span>
18699
- </div>
18700
- }
18701
- </div>
18702
- }
18703
18972
  </ds-mobile-section>
18704
18973
  }
18705
18974
 
@@ -21863,11 +22132,91 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImpo
21863
22132
  args: ['pageComponent']
21864
22133
  }] } });
21865
22134
 
22135
+ class TrackingPermissionService {
22136
+ trackingPromptRequestedKey = 'tracking_prompt_requested_v1';
22137
+ platform = inject(Platform);
22138
+ trackingSettingsReminder = signal(false, ...(ngDevMode ? [{ debugName: "trackingSettingsReminder" }] : []));
22139
+ showTrackingSettingsReminder = this.trackingSettingsReminder.asReadonly();
22140
+ async requestOnFirstHomeEntry() {
22141
+ if (!this.isNativeIos()) {
22142
+ this.trackingSettingsReminder.set(false);
22143
+ return;
22144
+ }
22145
+ if (this.hasRequestedTrackingPrompt()) {
22146
+ await this.refreshTrackingStatus();
22147
+ return;
22148
+ }
22149
+ try {
22150
+ const { status } = await AppTrackingTransparency.getStatus();
22151
+ if (status === 'notDetermined') {
22152
+ await AppTrackingTransparency.requestPermission();
22153
+ }
22154
+ }
22155
+ catch (error) {
22156
+ console.log('Unable to request app tracking permission:', error);
22157
+ }
22158
+ finally {
22159
+ this.setTrackingPromptRequested();
22160
+ await this.refreshTrackingStatus();
22161
+ }
22162
+ }
22163
+ async getTrackingStatus() {
22164
+ if (!this.isNativeIos()) {
22165
+ return null;
22166
+ }
22167
+ try {
22168
+ const { status } = await AppTrackingTransparency.getStatus();
22169
+ return status;
22170
+ }
22171
+ catch (error) {
22172
+ console.log('Unable to read app tracking status:', error);
22173
+ return null;
22174
+ }
22175
+ }
22176
+ async openAppSettings() {
22177
+ if (!this.isNativeIos()) {
22178
+ return;
22179
+ }
22180
+ // iOS deep link for opening this app's system settings page.
22181
+ window.location.href = 'app-settings:';
22182
+ }
22183
+ shouldShowSettingsReminder() {
22184
+ return this.trackingSettingsReminder();
22185
+ }
22186
+ async refreshTrackingStatus() {
22187
+ if (!this.isNativeIos()) {
22188
+ this.trackingSettingsReminder.set(false);
22189
+ return;
22190
+ }
22191
+ const status = await this.getTrackingStatus();
22192
+ const hasRequested = this.hasRequestedTrackingPrompt();
22193
+ this.trackingSettingsReminder.set(Boolean(hasRequested && status && status !== 'authorized'));
22194
+ }
22195
+ isNativeIos() {
22196
+ return this.platform.is('ios') && this.platform.is('capacitor');
22197
+ }
22198
+ hasRequestedTrackingPrompt() {
22199
+ return localStorage.getItem(this.trackingPromptRequestedKey) === '1';
22200
+ }
22201
+ setTrackingPromptRequested() {
22202
+ localStorage.setItem(this.trackingPromptRequestedKey, '1');
22203
+ }
22204
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: TrackingPermissionService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
22205
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: TrackingPermissionService, providedIn: 'root' });
22206
+ }
22207
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: TrackingPermissionService, decorators: [{
22208
+ type: Injectable,
22209
+ args: [{
22210
+ providedIn: 'root',
22211
+ }]
22212
+ }] });
22213
+
21866
22214
  class MobileHomePageComponent {
21867
22215
  router;
21868
22216
  userService;
21869
22217
  postsService;
21870
22218
  postModal;
22219
+ trackingPermissionService;
21871
22220
  pageComponent;
21872
22221
  // Get recent posts from PostsService - exclude pinned post (post-4) and limit to 3
21873
22222
  recentPosts = computed(() => this.postsService.posts()
@@ -21901,13 +22250,17 @@ class MobileHomePageComponent {
21901
22250
  openInquiries = computed(() => this.allInquiries()
21902
22251
  .filter(inquiry => inquiry.status === 'open')
21903
22252
  .slice(0, 3), ...(ngDevMode ? [{ debugName: "openInquiries" }] : []));
21904
- constructor(router, userService, postsService, postModal) {
22253
+ constructor(router, userService, postsService, postModal, trackingPermissionService) {
21905
22254
  this.router = router;
21906
22255
  this.userService = userService;
21907
22256
  this.postsService = postsService;
21908
22257
  this.postModal = postModal;
22258
+ this.trackingPermissionService = trackingPermissionService;
21909
22259
  console.log('MobileHomePageComponent constructor');
21910
22260
  }
22261
+ ngOnInit() {
22262
+ void this.trackingPermissionService.requestOnFirstHomeEntry();
22263
+ }
21911
22264
  handleRefresh(event) {
21912
22265
  console.log('Pull-to-refresh triggered');
21913
22266
  setTimeout(() => {
@@ -21944,7 +22297,7 @@ class MobileHomePageComponent {
21944
22297
  console.log('Navigating to inquiries page');
21945
22298
  this.router.navigate(['/inquiries']);
21946
22299
  }
21947
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: MobileHomePageComponent, deps: [{ token: i1$4.Router }, { token: UserService }, { token: PostsService }, { token: DsMobilePostDetailModalService }], target: i0.ɵɵFactoryTarget.Component });
22300
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: MobileHomePageComponent, deps: [{ token: i1$4.Router }, { token: UserService }, { token: PostsService }, { token: DsMobilePostDetailModalService }, { token: TrackingPermissionService }], target: i0.ɵɵFactoryTarget.Component });
21948
22301
  static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.16", type: MobileHomePageComponent, isStandalone: true, selector: "app-home-page", viewQueries: [{ propertyName: "pageComponent", first: true, predicate: ["pageComponent"], descendants: true }], ngImport: i0, template: `
21949
22302
  <ds-mobile-page-main
21950
22303
  #pageComponent
@@ -22196,7 +22549,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImpo
22196
22549
  </ds-mobile-section>
22197
22550
  </ds-mobile-page-main>
22198
22551
  `, styles: [".posts-list,.inquiries-list{display:flex;flex-direction:column}.empty-state{display:flex;flex-direction:column;align-items:center;justify-content:center;padding:20px;text-align:center}.empty-state ds-button{display:block;margin-top:16px}.empty-state ds-button::ng-deep .btn{width:100%;border-radius:9999px}.empty-state-title{font-family:Brockmann,sans-serif;font-size:var(--font-size-base);font-weight:600;color:var(--color-text-primary);margin-top:-16px;z-index:4}.empty-state-description{font-family:Brockmann,sans-serif;font-size:var(--font-size-sm);color:var(--color-text-secondary);margin:0}\n"] }]
22199
- }], ctorParameters: () => [{ type: i1$4.Router }, { type: UserService }, { type: PostsService }, { type: DsMobilePostDetailModalService }], propDecorators: { pageComponent: [{
22552
+ }], ctorParameters: () => [{ type: i1$4.Router }, { type: UserService }, { type: PostsService }, { type: DsMobilePostDetailModalService }, { type: TrackingPermissionService }], propDecorators: { pageComponent: [{
22200
22553
  type: ViewChild,
22201
22554
  args: ['pageComponent']
22202
22555
  }] } });
@@ -24089,18 +24442,80 @@ class MobileTabsExampleComponent {
24089
24442
  userService;
24090
24443
  router;
24091
24444
  whitelabelDemoModal = inject(WhitelabelDemoModalService);
24445
+ trackingPermissionService = inject(TrackingPermissionService);
24446
+ trackedProfileMenuItems = computed(() => {
24447
+ const accountActions = [
24448
+ {
24449
+ action: 'profile',
24450
+ title: 'Min profil',
24451
+ icon: 'remixUser3Line',
24452
+ destructive: false,
24453
+ },
24454
+ {
24455
+ action: 'settings',
24456
+ title: 'Indstillinger',
24457
+ icon: 'remixSettings3Line',
24458
+ destructive: false,
24459
+ },
24460
+ {
24461
+ action: 'appearance',
24462
+ title: 'Udseende',
24463
+ icon: 'remixPaletteLine',
24464
+ destructive: false,
24465
+ },
24466
+ ];
24467
+ if (this.trackingPermissionService.shouldShowSettingsReminder()) {
24468
+ accountActions.push({
24469
+ action: 'tracking-settings',
24470
+ title: 'Sporingsindstillinger',
24471
+ icon: 'remixSettings3Line',
24472
+ destructive: false,
24473
+ });
24474
+ }
24475
+ return [
24476
+ {
24477
+ actions: accountActions,
24478
+ },
24479
+ {
24480
+ actions: [
24481
+ {
24482
+ action: 'language',
24483
+ title: 'Sprog',
24484
+ subtitle: 'Dansk',
24485
+ flagIcon: '/Assets/country-flags/denmark.svg',
24486
+ icon: 'remixGlobalLine',
24487
+ destructive: false,
24488
+ showChevron: true,
24489
+ },
24490
+ ],
24491
+ },
24492
+ {
24493
+ actions: [
24494
+ {
24495
+ action: 'logout',
24496
+ title: 'Log ud',
24497
+ icon: 'remixLogoutBoxLine',
24498
+ destructive: true,
24499
+ },
24500
+ ],
24501
+ },
24502
+ ];
24503
+ }, ...(ngDevMode ? [{ debugName: "trackedProfileMenuItems" }] : []));
24092
24504
  constructor(userService, router) {
24093
24505
  this.userService = userService;
24094
24506
  this.router = router;
24095
24507
  console.log('MobileTabsExampleComponent constructor');
24508
+ effect(() => {
24509
+ this.userService.setProfileMenuItems(this.trackedProfileMenuItems());
24510
+ });
24096
24511
  }
24097
24512
  ngOnInit() {
24098
24513
  console.log('MobileTabsExampleComponent ngOnInit');
24099
24514
  // Configure user avatar globally - this is now the single source of truth
24100
24515
  this.userService.setAvatarInitials('LM');
24101
24516
  this.userService.setAvatarType('initials');
24102
- // Set profile menu items globally - this will be used by both tab bar and page-main components
24103
- this.userService.setProfileMenuItems(this.profileMenuItems);
24517
+ // Initial status refresh ensures menu reflects past ATT choice.
24518
+ void this.trackingPermissionService.refreshTrackingStatus();
24104
24519
  }
24105
24520
  tabs = [
24106
24521
  {
@@ -24146,53 +24561,7 @@ class MobileTabsExampleComponent {
24146
24561
  * throughout the entire application.
24147
24562
  */
24148
24563
  get profileMenuItems() {
24149
- return [
24150
- {
24151
- actions: [
24152
- {
24153
- action: 'profile',
24154
- title: 'Min profil',
24155
- icon: 'remixUser3Line',
24156
- destructive: false,
24157
- },
24158
- {
24159
- action: 'settings',
24160
- title: 'Indstillinger',
24161
- icon: 'remixSettings3Line',
24162
- destructive: false,
24163
- },
24164
- {
24165
- action: 'appearance',
24166
- title: 'Udseende',
24167
- icon: 'remixPaletteLine',
24168
- destructive: false,
24169
- },
24170
- ],
24171
- },
24172
- {
24173
- actions: [
24174
- {
24175
- action: 'language',
24176
- title: 'Sprog',
24177
- subtitle: 'Dansk',
24178
- flagIcon: '/Assets/country-flags/denmark.svg',
24179
- icon: 'remixGlobalLine',
24180
- destructive: false,
24181
- showChevron: true,
24182
- },
24183
- ],
24184
- },
24185
- {
24186
- actions: [
24187
- {
24188
- action: 'logout',
24189
- title: 'Log ud',
24190
- icon: 'remixLogoutBoxLine',
24191
- destructive: true,
24192
- },
24193
- ],
24194
- },
24195
- ];
24564
+ return this.trackedProfileMenuItems();
24196
24565
  }
24197
24566
  /**
24198
24567
  * Handle profile menu action selection.
@@ -24504,6 +24873,7 @@ class MobileBookingPageComponent {
24504
24873
  [bookingTime]="booking.bookingTime || ''"
24505
24874
  [align]="'center'"
24506
24875
  [clickable]="true"
24876
+ [enableLongPress]="false"
24507
24877
  [showChevron]="false"
24508
24878
  (bookingClick)="openFacilityDetail(booking.id)"
24509
24879
  />
@@ -24611,6 +24981,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImpo
24611
24981
  [bookingTime]="booking.bookingTime || ''"
24612
24982
  [align]="'center'"
24613
24983
  [clickable]="true"
24984
+ [enableLongPress]="false"
24614
24985
  [showChevron]="false"
24615
24986
  (bookingClick)="openFacilityDetail(booking.id)"
24616
24987
  />