@propbinder/mobile-design 0.2.3 → 0.2.5

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.
package/index.d.ts CHANGED
@@ -1,7 +1,8 @@
1
1
  import * as _angular_core from '@angular/core';
2
- import { AfterViewInit, OnInit, ElementRef, OnDestroy, EventEmitter, ApplicationRef, EnvironmentInjector, Type } from '@angular/core';
3
- import { ModalController, IonContent, NavController, GestureController } from '@ionic/angular/standalone';
2
+ import { AfterViewInit, OnInit, ElementRef, OnDestroy, EventEmitter, ApplicationRef, EnvironmentInjector, Type, AfterContentInit, ChangeDetectorRef } from '@angular/core';
3
+ import { ModalController, IonContent, NavController, GestureController, ModalOptions as ModalOptions$1 } from '@ionic/angular/standalone';
4
4
  import { ImpactStyle } from '@capacitor/haptics';
5
+ import { DsTextareaComponent } from '@propbinder/design-system';
5
6
  import { ControlValueAccessor } from '@angular/forms';
6
7
  import { Router, ActivatedRoute } from '@angular/router';
7
8
  import { Animation } from '@ionic/angular';
@@ -403,13 +404,58 @@ declare class DsMobilePageMainComponent extends MobilePageBase implements AfterV
403
404
  static ɵcmp: _angular_core.ɵɵComponentDeclaration<DsMobilePageMainComponent, "ds-mobile-page-main", never, { "title": { "alias": "title"; "required": true; "isSignal": true; }; "headerTitle": { "alias": "headerTitle"; "required": false; "isSignal": true; }; "headerSubtitle": { "alias": "headerSubtitle"; "required": false; "isSignal": true; }; "avatarType": { "alias": "avatarType"; "required": false; "isSignal": true; }; "avatarInitials": { "alias": "avatarInitials"; "required": false; "isSignal": true; }; "avatarSrc": { "alias": "avatarSrc"; "required": false; "isSignal": true; }; "avatarIconName": { "alias": "avatarIconName"; "required": false; "isSignal": true; }; "showRefresh": { "alias": "showRefresh"; "required": false; "isSignal": true; }; "showCondensedHeader": { "alias": "showCondensedHeader"; "required": false; "isSignal": true; }; "scrollThreshold": { "alias": "scrollThreshold"; "required": false; "isSignal": true; }; "headerFadeDistance": { "alias": "headerFadeDistance"; "required": false; "isSignal": true; }; "profileMenuItems": { "alias": "profileMenuItems"; "required": false; "isSignal": true; }; }, { "avatarClick": "avatarClick"; "profileActionSelected": "profileActionSelected"; "refresh": "refresh"; "scroll": "scroll"; }, never, ["[header-content]", "*"], true, never>;
404
405
  }
405
406
 
407
+ interface InlineTabItem {
408
+ id: string;
409
+ label: string;
410
+ badge?: number;
411
+ }
412
+ /**
413
+ * DsMobileInlineTabsComponent
414
+ *
415
+ * Pill-style inline tabs for filtering/switching views
416
+ * Used in the purple header section of pages
417
+ *
418
+ * @example
419
+ * ```html
420
+ * <ds-mobile-inline-tabs
421
+ * [tabs]="[
422
+ * { id: 'all', label: 'All' },
423
+ * { id: 'open', label: 'Open' },
424
+ * { id: 'closed', label: 'Closed' }
425
+ * ]"
426
+ * [activeTab]="'all'"
427
+ * (tabChange)="handleTabChange($event)">
428
+ * </ds-mobile-inline-tabs>
429
+ * ```
430
+ */
431
+ declare class DsMobileInlineTabsComponent {
432
+ /**
433
+ * Array of tab items to display
434
+ */
435
+ tabs: _angular_core.InputSignal<InlineTabItem[]>;
436
+ /**
437
+ * Currently active tab ID
438
+ */
439
+ activeTab: _angular_core.InputSignal<string>;
440
+ /**
441
+ * Emitted when a tab is clicked
442
+ */
443
+ tabChange: _angular_core.OutputEmitterRef<string>;
444
+ handleTabClick(tabId: string): void;
445
+ static ɵfac: _angular_core.ɵɵFactoryDeclaration<DsMobileInlineTabsComponent, never>;
446
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<DsMobileInlineTabsComponent, "ds-mobile-inline-tabs", never, { "tabs": { "alias": "tabs"; "required": true; "isSignal": true; }; "activeTab": { "alias": "activeTab"; "required": true; "isSignal": true; }; }, { "tabChange": "tabChange"; }, never, never, true, never>;
447
+ }
448
+
406
449
  /**
407
450
  * DsMobilePageDetailsComponent
408
451
  *
409
- * A mobile page layout for detail/drill-down pages with:
410
- * - Back button header (mobile + desktop variants)
411
- * - White background content area
412
- * - Responsive padding
452
+ * A complete mobile page layout for detail/drill-down pages with:
453
+ * - Fixed header with back button + title (fades in on scroll)
454
+ * - Purple expandable header section (scrolls with content)
455
+ * - Optional inline tabs in expandable header
456
+ * - White rounded content wrapper
457
+ * - Pull-to-refresh support (native platforms only)
458
+ * - Auto scroll title fade-in and header fade-out
413
459
  *
414
460
  * @example
415
461
  * ```html
@@ -422,47 +468,63 @@ declare class DsMobilePageMainComponent extends MobilePageBase implements AfterV
422
468
  * </div>
423
469
  * </ds-mobile-page-details>
424
470
  *
425
- * <!-- With default back route -->
471
+ * <!-- With tabs -->
426
472
  * <ds-mobile-page-details
427
- * title="Invoice Details"
428
- * backRoute="/invoices">
473
+ * title="Inquiry Details"
474
+ * [tabs]="tabItems"
475
+ * [activeTab]="activeTab()"
476
+ * (tabChange)="setActiveTab($event)"
477
+ * (back)="goBack()">
429
478
  * <div class="page-content">
430
479
  * <!-- Your content -->
431
480
  * </div>
432
481
  * </ds-mobile-page-details>
433
482
  * ```
434
483
  */
435
- declare class DsMobilePageDetailsComponent extends MobilePageBase {
484
+ declare class DsMobilePageDetailsComponent extends MobilePageBase implements AfterViewInit {
436
485
  private navCtrl;
437
486
  private elementRef;
487
+ ionContent?: IonContent;
488
+ private platform;
489
+ isNativePlatform: _angular_core.Signal<boolean>;
438
490
  title: _angular_core.InputSignal<string>;
439
491
  backRoute: _angular_core.InputSignal<string>;
492
+ tabs: _angular_core.InputSignal<InlineTabItem[] | undefined>;
493
+ activeTab: _angular_core.InputSignal<string>;
494
+ showRefresh: _angular_core.InputSignal<boolean>;
495
+ scrollThreshold: _angular_core.InputSignal<number>;
496
+ headerFadeDistance: _angular_core.InputSignal<number>;
440
497
  back: _angular_core.OutputEmitterRef<void>;
498
+ tabChange: _angular_core.OutputEmitterRef<string>;
499
+ refresh: _angular_core.OutputEmitterRef<any>;
500
+ scroll: _angular_core.OutputEmitterRef<any>;
441
501
  constructor(navCtrl: NavController, elementRef: ElementRef);
502
+ ngAfterViewInit(): void;
442
503
  /**
443
504
  * Handle back navigation
444
505
  *
445
506
  * By default, navigates using the provided backRoute or browser back.
446
507
  * Parent components can listen to the (back) event to override this behavior.
447
- *
448
- * @example
449
- * ```html
450
- * <!-- Default behavior: uses backRoute or browser back -->
451
- * <ds-mobile-page-details
452
- * title="Details"
453
- * backRoute="/home">
454
- * </ds-mobile-page-details>
455
- *
456
- * <!-- Custom behavior: parent handles navigation -->
457
- * <ds-mobile-page-details
458
- * title="Details"
459
- * (back)="customBackHandler()">
460
- * </ds-mobile-page-details>
461
- * ```
462
508
  */
463
509
  handleBack(): void;
510
+ /**
511
+ * Handle tab change
512
+ */
513
+ handleTabChange(tabId: string): void;
514
+ /**
515
+ * Handle scroll events
516
+ * - Shows title in fixed header when scrolled past threshold
517
+ * - Fades out expandable header content based on scroll position
518
+ * - Emits scroll event for custom handling
519
+ */
520
+ handleScroll(event: any): void;
521
+ /**
522
+ * Handle pull-to-refresh
523
+ * Emits refresh event - parent should call event.target.complete()
524
+ */
525
+ handleRefresh(event: any): Promise<void>;
464
526
  static ɵfac: _angular_core.ɵɵFactoryDeclaration<DsMobilePageDetailsComponent, never>;
465
- static ɵcmp: _angular_core.ɵɵComponentDeclaration<DsMobilePageDetailsComponent, "ds-mobile-page-details", never, { "title": { "alias": "title"; "required": true; "isSignal": true; }; "backRoute": { "alias": "backRoute"; "required": false; "isSignal": true; }; }, { "back": "back"; }, never, ["*"], true, never>;
527
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<DsMobilePageDetailsComponent, "ds-mobile-page-details", never, { "title": { "alias": "title"; "required": true; "isSignal": true; }; "backRoute": { "alias": "backRoute"; "required": false; "isSignal": true; }; "tabs": { "alias": "tabs"; "required": false; "isSignal": true; }; "activeTab": { "alias": "activeTab"; "required": false; "isSignal": true; }; "showRefresh": { "alias": "showRefresh"; "required": false; "isSignal": true; }; "scrollThreshold": { "alias": "scrollThreshold"; "required": false; "isSignal": true; }; "headerFadeDistance": { "alias": "headerFadeDistance"; "required": false; "isSignal": true; }; }, { "back": "back"; "tabChange": "tabChange"; "refresh": "refresh"; "scroll": "scroll"; }, never, ["*"], true, never>;
466
528
  }
467
529
 
468
530
  /**
@@ -814,6 +876,72 @@ declare class DsMobilePostComposerComponent {
814
876
  static ɵcmp: _angular_core.ɵɵComponentDeclaration<DsMobilePostComposerComponent, "ds-mobile-post-composer", never, { "avatarInitials": { "alias": "avatarInitials"; "required": false; "isSignal": true; }; "avatarType": { "alias": "avatarType"; "required": false; "isSignal": true; }; "avatarSrc": { "alias": "avatarSrc"; "required": false; "isSignal": true; }; "avatarIconName": { "alias": "avatarIconName"; "required": false; "isSignal": true; }; "placeholder": { "alias": "placeholder"; "required": false; "isSignal": true; }; "buttonText": { "alias": "buttonText"; "required": false; "isSignal": true; }; }, { "composerClick": "composerClick"; }, never, never, true, never>;
815
877
  }
816
878
 
879
+ /**
880
+ * File type for attachment preview
881
+ */
882
+ type AttachmentFileType = 'image' | 'pdf' | 'doc' | 'docx' | 'xls' | 'xlsx' | 'other';
883
+ /**
884
+ * Attachment data interface
885
+ */
886
+ interface AttachmentData {
887
+ id: string;
888
+ src: string;
889
+ type: AttachmentFileType;
890
+ name?: string;
891
+ size?: string;
892
+ }
893
+ /**
894
+ * DsMobileAttachmentPreviewComponent
895
+ *
896
+ * Reusable component for displaying attachment previews.
897
+ * Supports both image previews and file type indicators (PDF, DOCX, etc.).
898
+ *
899
+ * Features:
900
+ * - Image preview for photos
901
+ * - File type indicator box for documents
902
+ * - Remove button overlay
903
+ * - Consistent 96x96 size
904
+ *
905
+ * @example
906
+ * ```html
907
+ * <!-- Image attachment -->
908
+ * <ds-mobile-attachment-preview
909
+ * [attachment]="{ id: '1', src: 'photo.jpg', type: 'image' }"
910
+ * (remove)="handleRemove($event)">
911
+ * </ds-mobile-attachment-preview>
912
+ *
913
+ * <!-- PDF attachment -->
914
+ * <ds-mobile-attachment-preview
915
+ * [attachment]="{ id: '2', src: 'doc.pdf', type: 'pdf', name: 'Document.pdf' }"
916
+ * (remove)="handleRemove($event)">
917
+ * </ds-mobile-attachment-preview>
918
+ * ```
919
+ */
920
+ declare class DsMobileAttachmentPreviewComponent {
921
+ /**
922
+ * Attachment data to display
923
+ */
924
+ attachment: _angular_core.InputSignal<AttachmentData>;
925
+ /**
926
+ * Emits when the remove button is clicked
927
+ */
928
+ remove: _angular_core.OutputEmitterRef<string>;
929
+ /**
930
+ * Get the file type label (PDF, DOCX, etc.)
931
+ */
932
+ getFileTypeLabel(): string;
933
+ /**
934
+ * Get the appropriate icon name based on file type
935
+ */
936
+ getIconName(): string;
937
+ /**
938
+ * Handle remove button click
939
+ */
940
+ handleRemove(): void;
941
+ static ɵfac: _angular_core.ɵɵFactoryDeclaration<DsMobileAttachmentPreviewComponent, never>;
942
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<DsMobileAttachmentPreviewComponent, "ds-mobile-attachment-preview", never, { "attachment": { "alias": "attachment"; "required": true; "isSignal": true; }; }, { "remove": "remove"; }, never, never, true, never>;
943
+ }
944
+
817
945
  /**
818
946
  * DsMobileMessageComposerComponent
819
947
  *
@@ -859,6 +987,14 @@ declare class DsMobileMessageComposerComponent implements AfterViewInit, OnDestr
859
987
  * Send button aria label
860
988
  */
861
989
  sendButtonLabel: _angular_core.InputSignal<string>;
990
+ /**
991
+ * Attachment button aria label
992
+ */
993
+ attachmentButtonLabel: _angular_core.InputSignal<string>;
994
+ /**
995
+ * Whether to show the attachment button
996
+ */
997
+ showAttachmentButton: _angular_core.InputSignal<boolean>;
862
998
  /**
863
999
  * Edit indicator text (when editing)
864
1000
  */
@@ -887,10 +1023,18 @@ declare class DsMobileMessageComposerComponent implements AfterViewInit, OnDestr
887
1023
  * ViewChild for message input
888
1024
  */
889
1025
  messageInput?: ElementRef<HTMLTextAreaElement>;
1026
+ /**
1027
+ * ViewChild for file input
1028
+ */
1029
+ fileInput?: ElementRef<HTMLInputElement>;
890
1030
  /**
891
1031
  * Message text signal
892
1032
  */
893
1033
  messageText: _angular_core.WritableSignal<string>;
1034
+ /**
1035
+ * Attachments signal
1036
+ */
1037
+ attachments: _angular_core.WritableSignal<AttachmentData[]>;
894
1038
  /**
895
1039
  * Editing message state (optional)
896
1040
  */
@@ -930,6 +1074,7 @@ declare class DsMobileMessageComposerComponent implements AfterViewInit, OnDestr
930
1074
  isReply?: boolean;
931
1075
  replyTo?: string;
932
1076
  isEdit?: boolean;
1077
+ attachments?: AttachmentData[];
933
1078
  }>;
934
1079
  /**
935
1080
  * Emits when edit is cancelled
@@ -945,6 +1090,10 @@ declare class DsMobileMessageComposerComponent implements AfterViewInit, OnDestr
945
1090
  mentionSelected: _angular_core.OutputEmitterRef<{
946
1091
  userName: string;
947
1092
  }>;
1093
+ /**
1094
+ * Emits when attachment button is clicked
1095
+ */
1096
+ attachmentClicked: _angular_core.OutputEmitterRef<void>;
948
1097
  ngAfterViewInit(): void;
949
1098
  ngOnDestroy(): void;
950
1099
  /**
@@ -959,6 +1108,10 @@ declare class DsMobileMessageComposerComponent implements AfterViewInit, OnDestr
959
1108
  * Show the keyboard when user interacts with input
960
1109
  */
961
1110
  showKeyboard(): void;
1111
+ /**
1112
+ * Handle keyboard shortcuts (Shift+Enter to send)
1113
+ */
1114
+ handleKeyDown(event: KeyboardEvent): void;
962
1115
  /**
963
1116
  * Handle input changes and detect @ mentions
964
1117
  */
@@ -991,12 +1144,32 @@ declare class DsMobileMessageComposerComponent implements AfterViewInit, OnDestr
991
1144
  * Focus the input
992
1145
  */
993
1146
  focus(): void;
1147
+ /**
1148
+ * Handle attachment button click
1149
+ */
1150
+ handleAttachmentClick(): void;
1151
+ /**
1152
+ * Detect file type from file name or mime type
1153
+ */
1154
+ private detectFileType;
1155
+ /**
1156
+ * Format file size for display
1157
+ */
1158
+ private formatFileSize;
1159
+ /**
1160
+ * Handle file selection from file input
1161
+ */
1162
+ handleFileSelect(event: Event): void;
1163
+ /**
1164
+ * Remove an attachment from the list
1165
+ */
1166
+ removeAttachment(attachmentId: string): void;
994
1167
  /**
995
1168
  * Send message
996
1169
  */
997
1170
  sendMessage(): void;
998
1171
  static ɵfac: _angular_core.ɵɵFactoryDeclaration<DsMobileMessageComposerComponent, never>;
999
- static ɵcmp: _angular_core.ɵɵComponentDeclaration<DsMobileMessageComposerComponent, "ds-mobile-message-composer", never, { "avatarInitials": { "alias": "avatarInitials"; "required": false; "isSignal": true; }; "avatarType": { "alias": "avatarType"; "required": false; "isSignal": true; }; "avatarSrc": { "alias": "avatarSrc"; "required": false; "isSignal": true; }; "placeholder": { "alias": "placeholder"; "required": false; "isSignal": true; }; "sendButtonLabel": { "alias": "sendButtonLabel"; "required": false; "isSignal": true; }; "editIndicatorText": { "alias": "editIndicatorText"; "required": false; "isSignal": true; }; "replyIndicatorText": { "alias": "replyIndicatorText"; "required": false; "isSignal": true; }; "enableMentions": { "alias": "enableMentions"; "required": false; "isSignal": true; }; "mentionUsers": { "alias": "mentionUsers"; "required": false; "isSignal": true; }; "autoFocus": { "alias": "autoFocus"; "required": false; "isSignal": true; }; }, { "messageSent": "messageSent"; "editCancelled": "editCancelled"; "replyCancelled": "replyCancelled"; "mentionSelected": "mentionSelected"; }, never, never, true, never>;
1172
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<DsMobileMessageComposerComponent, "ds-mobile-message-composer", never, { "avatarInitials": { "alias": "avatarInitials"; "required": false; "isSignal": true; }; "avatarType": { "alias": "avatarType"; "required": false; "isSignal": true; }; "avatarSrc": { "alias": "avatarSrc"; "required": false; "isSignal": true; }; "placeholder": { "alias": "placeholder"; "required": false; "isSignal": true; }; "sendButtonLabel": { "alias": "sendButtonLabel"; "required": false; "isSignal": true; }; "attachmentButtonLabel": { "alias": "attachmentButtonLabel"; "required": false; "isSignal": true; }; "showAttachmentButton": { "alias": "showAttachmentButton"; "required": false; "isSignal": true; }; "editIndicatorText": { "alias": "editIndicatorText"; "required": false; "isSignal": true; }; "replyIndicatorText": { "alias": "replyIndicatorText"; "required": false; "isSignal": true; }; "enableMentions": { "alias": "enableMentions"; "required": false; "isSignal": true; }; "mentionUsers": { "alias": "mentionUsers"; "required": false; "isSignal": true; }; "autoFocus": { "alias": "autoFocus"; "required": false; "isSignal": true; }; }, { "messageSent": "messageSent"; "editCancelled": "editCancelled"; "replyCancelled": "replyCancelled"; "mentionSelected": "mentionSelected"; "attachmentClicked": "attachmentClicked"; }, never, never, true, never>;
1000
1173
  }
1001
1174
 
1002
1175
  /**
@@ -1064,6 +1237,10 @@ declare class DsMobileMessageBubbleComponent {
1064
1237
  * Timestamp text (e.g., "12:34", "08-12-2025 13:18")
1065
1238
  */
1066
1239
  timestamp: _angular_core.InputSignal<string>;
1240
+ /**
1241
+ * Whether to show the timestamp below the bubble
1242
+ */
1243
+ showTimestamp: _angular_core.InputSignal<boolean>;
1067
1244
  /**
1068
1245
  * Avatar initials
1069
1246
  */
@@ -1095,6 +1272,10 @@ declare class DsMobileMessageBubbleComponent {
1095
1272
  * Emits when the message is long-pressed
1096
1273
  */
1097
1274
  longPress: _angular_core.OutputEmitterRef<void>;
1275
+ /**
1276
+ * Emits when the message is clicked (to toggle timestamp)
1277
+ */
1278
+ messageClick: _angular_core.OutputEmitterRef<void>;
1098
1279
  /**
1099
1280
  * Long press tracking
1100
1281
  */
@@ -1104,16 +1285,21 @@ declare class DsMobileMessageBubbleComponent {
1104
1285
  private touchStartY;
1105
1286
  private readonly LONG_PRESS_DURATION;
1106
1287
  private readonly MOVE_THRESHOLD;
1288
+ private clickStartTime;
1107
1289
  /**
1108
1290
  * Handle attachment click
1109
1291
  */
1110
1292
  handleAttachmentClick(attachment: ChatAttachment): void;
1293
+ /**
1294
+ * Handle click (for web/mouse support)
1295
+ */
1296
+ handleClick(event: MouseEvent): void;
1111
1297
  /**
1112
1298
  * Handle touch start for long press detection
1113
1299
  */
1114
1300
  handleTouchStart(event: TouchEvent): void;
1115
1301
  /**
1116
- * Handle touch end to clear long press timer
1302
+ * Handle touch end to clear long press timer and detect short tap
1117
1303
  */
1118
1304
  handleTouchEnd(event: TouchEvent): void;
1119
1305
  /**
@@ -1125,7 +1311,7 @@ declare class DsMobileMessageBubbleComponent {
1125
1311
  */
1126
1312
  handleContextMenu(event: Event): void;
1127
1313
  static ɵfac: _angular_core.ɵɵFactoryDeclaration<DsMobileMessageBubbleComponent, never>;
1128
- static ɵcmp: _angular_core.ɵɵComponentDeclaration<DsMobileMessageBubbleComponent, "ds-mobile-message-bubble", never, { "content": { "alias": "content"; "required": true; "isSignal": true; }; "isOwnMessage": { "alias": "isOwnMessage"; "required": false; "isSignal": true; }; "senderName": { "alias": "senderName"; "required": false; "isSignal": true; }; "timestamp": { "alias": "timestamp"; "required": true; "isSignal": true; }; "avatarInitials": { "alias": "avatarInitials"; "required": false; "isSignal": true; }; "avatarType": { "alias": "avatarType"; "required": false; "isSignal": true; }; "avatarSrc": { "alias": "avatarSrc"; "required": false; "isSignal": true; }; "attachments": { "alias": "attachments"; "required": false; "isSignal": true; }; "clickable": { "alias": "clickable"; "required": false; "isSignal": true; }; }, { "attachmentClick": "attachmentClick"; "longPress": "longPress"; }, never, never, true, never>;
1314
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<DsMobileMessageBubbleComponent, "ds-mobile-message-bubble", never, { "content": { "alias": "content"; "required": true; "isSignal": true; }; "isOwnMessage": { "alias": "isOwnMessage"; "required": false; "isSignal": true; }; "senderName": { "alias": "senderName"; "required": false; "isSignal": true; }; "timestamp": { "alias": "timestamp"; "required": true; "isSignal": true; }; "showTimestamp": { "alias": "showTimestamp"; "required": false; "isSignal": true; }; "avatarInitials": { "alias": "avatarInitials"; "required": false; "isSignal": true; }; "avatarType": { "alias": "avatarType"; "required": false; "isSignal": true; }; "avatarSrc": { "alias": "avatarSrc"; "required": false; "isSignal": true; }; "attachments": { "alias": "attachments"; "required": false; "isSignal": true; }; "clickable": { "alias": "clickable"; "required": false; "isSignal": true; }; }, { "attachmentClick": "attachmentClick"; "longPress": "longPress"; "messageClick": "messageClick"; }, never, never, true, never>;
1129
1315
  }
1130
1316
 
1131
1317
  /**
@@ -1311,7 +1497,7 @@ declare class DsMobileListItemComponent {
1311
1497
  * - 'center' - Align to center
1312
1498
  * - 'bottom' - Align to bottom
1313
1499
  */
1314
- align: _angular_core.InputSignal<"top" | "center" | "bottom">;
1500
+ align: _angular_core.InputSignal<"center" | "top" | "bottom">;
1315
1501
  /**
1316
1502
  * Whether the list item is interactive (clickable and long-pressable)
1317
1503
  * When true, adds interactive background, cursor pointer, and touch handlers
@@ -2058,48 +2244,6 @@ declare class DsMobileTabsComponent implements OnInit {
2058
2244
  static ɵcmp: _angular_core.ɵɵComponentDeclaration<DsMobileTabsComponent, "ds-mobile-tabs", never, { "tabs": { "alias": "tabs"; "required": false; }; "avatarType": { "alias": "avatarType"; "required": false; }; "avatarInitials": { "alias": "avatarInitials"; "required": false; }; "avatarSrc": { "alias": "avatarSrc"; "required": false; }; "avatarIconName": { "alias": "avatarIconName"; "required": false; }; }, { "avatarClick": "avatarClick"; }, never, never, true, never>;
2059
2245
  }
2060
2246
 
2061
- interface InlineTabItem {
2062
- id: string;
2063
- label: string;
2064
- badge?: number;
2065
- }
2066
- /**
2067
- * DsMobileInlineTabsComponent
2068
- *
2069
- * Pill-style inline tabs for filtering/switching views
2070
- * Used in the purple header section of pages
2071
- *
2072
- * @example
2073
- * ```html
2074
- * <ds-mobile-inline-tabs
2075
- * [tabs]="[
2076
- * { id: 'all', label: 'All' },
2077
- * { id: 'open', label: 'Open' },
2078
- * { id: 'closed', label: 'Closed' }
2079
- * ]"
2080
- * [activeTab]="'all'"
2081
- * (tabChange)="handleTabChange($event)">
2082
- * </ds-mobile-inline-tabs>
2083
- * ```
2084
- */
2085
- declare class DsMobileInlineTabsComponent {
2086
- /**
2087
- * Array of tab items to display
2088
- */
2089
- tabs: _angular_core.InputSignal<InlineTabItem[]>;
2090
- /**
2091
- * Currently active tab ID
2092
- */
2093
- activeTab: _angular_core.InputSignal<string>;
2094
- /**
2095
- * Emitted when a tab is clicked
2096
- */
2097
- tabChange: _angular_core.OutputEmitterRef<string>;
2098
- handleTabClick(tabId: string): void;
2099
- static ɵfac: _angular_core.ɵɵFactoryDeclaration<DsMobileInlineTabsComponent, never>;
2100
- static ɵcmp: _angular_core.ɵɵComponentDeclaration<DsMobileInlineTabsComponent, "ds-mobile-inline-tabs", never, { "tabs": { "alias": "tabs"; "required": true; "isSignal": true; }; "activeTab": { "alias": "activeTab"; "required": true; "isSignal": true; }; }, { "tabChange": "tabChange"; }, never, never, true, never>;
2101
- }
2102
-
2103
2247
  /**
2104
2248
  * Media file types supported by the lightbox
2105
2249
  */
@@ -2809,86 +2953,304 @@ declare class DsMobileModalService {
2809
2953
  }
2810
2954
 
2811
2955
  /**
2812
- * Post data interface for the modal
2813
- *
2814
- * Represents a post with its content, author info, and comments.
2815
- * Use this interface to map your API response data to the component.
2956
+ * MobileModalBase
2816
2957
  *
2817
- * @example
2818
- * ```typescript
2819
- * const postData: PostDetailData = {
2820
- * postId: '123',
2821
- * authorName: 'John Doe',
2822
- * authorRole: 'Tenant',
2823
- * timestamp: '2h ago',
2824
- * avatarInitials: 'JD',
2825
- * content: 'Post content here...',
2826
- * likeCount: 42,
2827
- * commentCount: 12,
2828
- * comments: [...]
2829
- * };
2830
- * ```
2831
- */
2832
- interface PostDetailData {
2833
- /** Unique post identifier */
2834
- postId: string;
2835
- /** Post author name */
2836
- authorName: string;
2837
- /** Author role (e.g., 'Tenant', 'Manager') */
2838
- authorRole: string;
2839
- /** Post timestamp (e.g., '2h ago', 'Yesterday') */
2840
- timestamp: string;
2841
- /** Author avatar initials (1-2 letters) */
2842
- avatarInitials?: string;
2843
- /** Avatar display type */
2844
- avatarType?: 'photo' | 'initials';
2845
- /** Author avatar image URL */
2846
- avatarSrc?: string;
2847
- /** Post text content */
2848
- content: string;
2849
- /** Optional post image URL */
2850
- imageSrc?: string;
2851
- /** Image alt text */
2852
- imageAlt?: string;
2853
- /** Whether the current user has liked this post */
2854
- isLiked?: boolean;
2855
- /** Number of likes */
2856
- likeCount?: number;
2857
- /** Number of comments */
2858
- commentCount?: number;
2859
- /** Array of comments */
2860
- comments?: CommentData[];
2861
- /** Auto-focus comment input when modal opens */
2862
- focusComment?: boolean;
2863
- }
2864
- /**
2865
- * Comment data interface
2958
+ * Shared base class for mobile modal components.
2959
+ * Provides consistent modal behavior, state management, and keyboard handling.
2866
2960
  *
2867
- * Represents a single comment on a post.
2961
+ * **Key Features:**
2962
+ * - Loading and error state management
2963
+ * - Header configuration (title, meta)
2964
+ * - Automatic keyboard height tracking (iOS/Android)
2965
+ * - Fixed bottom component support
2966
+ * - Consistent close behavior
2868
2967
  *
2869
- * @example
2968
+ * **Usage:**
2870
2969
  * ```typescript
2871
- * const comment: CommentData = {
2872
- * authorName: 'Jane Smith',
2873
- * authorRole: 'Tenant',
2874
- * timestamp: '1h ago',
2875
- * avatarInitials: 'JS',
2876
- * content: 'Great post!',
2877
- * isLiked: false,
2878
- * likeCount: 5,
2879
- * isOwnComment: false
2880
- * };
2970
+ * export class MyModalComponent extends MobileModalBase {
2971
+ * constructor() {
2972
+ * super();
2973
+ * }
2974
+ * }
2881
2975
  * ```
2976
+ *
2977
+ * @internal This is a base class and should not be used directly.
2882
2978
  */
2883
- interface CommentData {
2884
- /** Unique comment identifier */
2885
- id?: string;
2886
- /** Comment author name */
2887
- authorName: string;
2888
- /** Author role */
2889
- authorRole: string;
2890
- /** Comment timestamp */
2891
- timestamp: string;
2979
+ declare abstract class MobileModalBase implements OnInit, OnDestroy {
2980
+ protected modalController: ModalController;
2981
+ /**
2982
+ * Reference to IonContent for accessing scroll element
2983
+ */
2984
+ protected ionContent?: IonContent;
2985
+ /**
2986
+ * ResizeObserver for tracking fixed bottom height
2987
+ */
2988
+ private fixedBottomObserver?;
2989
+ /**
2990
+ * Loading state - when true, shows loading indicator
2991
+ * @default false
2992
+ */
2993
+ loading: _angular_core.InputSignal<boolean>;
2994
+ /**
2995
+ * Error state - when set, shows error message
2996
+ * @default undefined
2997
+ */
2998
+ error: _angular_core.InputSignal<string | undefined>;
2999
+ /**
3000
+ * Modal header title
3001
+ * @default ''
3002
+ */
3003
+ headerTitle: _angular_core.InputSignal<string>;
3004
+ /**
3005
+ * Modal header metadata (subtitle/secondary text)
3006
+ * @default ''
3007
+ */
3008
+ headerMeta: _angular_core.InputSignal<string>;
3009
+ /**
3010
+ * Accessibility label for close button
3011
+ * @default 'Close'
3012
+ */
3013
+ closeButtonLabel: _angular_core.InputSignal<string>;
3014
+ /**
3015
+ * Enable automatic keyboard height tracking
3016
+ * When enabled, sets --keyboard-height CSS variable for sliding content
3017
+ * @default false
3018
+ */
3019
+ enableKeyboardHandling: _angular_core.InputSignal<boolean>;
3020
+ /**
3021
+ * Whether modal has a fixed bottom component
3022
+ * Used to manage spacing and keyboard interactions
3023
+ * @default false
3024
+ */
3025
+ hasFixedBottom: _angular_core.InputSignal<boolean>;
3026
+ /**
3027
+ * Emitted when modal is closed
3028
+ */
3029
+ closed: _angular_core.OutputEmitterRef<void>;
3030
+ ngOnInit(): void;
3031
+ ngOnDestroy(): void;
3032
+ /**
3033
+ * Close the modal
3034
+ * Emits closed event and dismisses the modal
3035
+ */
3036
+ close(): void;
3037
+ /**
3038
+ * Set up keyboard event listeners to adjust component position
3039
+ * Uses --keyboard-height for fixed bottom composer and adds padding to scroll area
3040
+ * @protected
3041
+ */
3042
+ protected setupKeyboardListeners(): void;
3043
+ /**
3044
+ * Set up ResizeObserver to track fixed bottom height and apply as CSS variable
3045
+ * This allows dynamic bottom padding that adjusts to content
3046
+ * @protected
3047
+ */
3048
+ protected setupFixedBottomObserver(): Promise<void>;
3049
+ /**
3050
+ * Clean up keyboard event listeners
3051
+ * @protected
3052
+ */
3053
+ protected cleanupKeyboardListeners(): void;
3054
+ static ɵfac: _angular_core.ɵɵFactoryDeclaration<MobileModalBase, never>;
3055
+ static ɵdir: _angular_core.ɵɵDirectiveDeclaration<MobileModalBase, never, never, { "loading": { "alias": "loading"; "required": false; "isSignal": true; }; "error": { "alias": "error"; "required": false; "isSignal": true; }; "headerTitle": { "alias": "headerTitle"; "required": false; "isSignal": true; }; "headerMeta": { "alias": "headerMeta"; "required": false; "isSignal": true; }; "closeButtonLabel": { "alias": "closeButtonLabel"; "required": false; "isSignal": true; }; "enableKeyboardHandling": { "alias": "enableKeyboardHandling"; "required": false; "isSignal": true; }; "hasFixedBottom": { "alias": "hasFixedBottom"; "required": false; "isSignal": true; }; }, { "closed": "closed"; }, never, never, true, never>;
3056
+ }
3057
+
3058
+ /**
3059
+ * DsMobileModalBaseComponent
3060
+ *
3061
+ * Base modal component providing consistent layout and behavior for all modals.
3062
+ *
3063
+ * **Features:**
3064
+ * - Optional header with auto-detection of content
3065
+ * - Flexible header with slots for leading content (avatar, icon)
3066
+ * - Title and metadata inputs or custom header slot
3067
+ * - Default loading and error state templates (with override capability)
3068
+ * - Fixed bottom component support (e.g., message composer)
3069
+ * - Automatic keyboard handling
3070
+ * - Safe area support
3071
+ *
3072
+ * **Slot Structure:**
3073
+ * - `[header-leading]` - Left side of header (avatar, icon)
3074
+ * - `[header-main]` - Custom header content (replaces title/meta)
3075
+ * - `[loading-state]` - Custom loading template
3076
+ * - `[error-state]` - Custom error template
3077
+ * - `[footer]` or `[fixed-bottom]` - Fixed bottom component (slides with keyboard)
3078
+ * - Default slot - Main modal content
3079
+ *
3080
+ * @example
3081
+ * ```html
3082
+ * <!-- Simple title modal -->
3083
+ * <ds-mobile-modal-base
3084
+ * headerTitle="Settings"
3085
+ * closeButtonLabel="Close">
3086
+ * <div class="content">...</div>
3087
+ * </ds-mobile-modal-base>
3088
+ *
3089
+ * <!-- Avatar header modal -->
3090
+ * <ds-mobile-modal-base
3091
+ * headerTitle="John Doe"
3092
+ * headerMeta="Tenant · 2h ago"
3093
+ * [hasFixedBottom]="true"
3094
+ * [enableKeyboardHandling]="true">
3095
+ *
3096
+ * <ds-avatar header-leading [initials]="'JD'" size="md" />
3097
+ *
3098
+ * <div class="content">...</div>
3099
+ *
3100
+ * <div fixed-bottom class="composer">...</div>
3101
+ * </ds-mobile-modal-base>
3102
+ *
3103
+ * <!-- Headerless modal (close button positioned absolutely) -->
3104
+ * <ds-mobile-modal-base [showHeader]="false">
3105
+ * <div class="full-width-content">...</div>
3106
+ * </ds-mobile-modal-base>
3107
+ *
3108
+ * <!-- Modal with footer actions (slides with keyboard) -->
3109
+ * <ds-mobile-modal-base
3110
+ * headerTitle="Create Inquiry"
3111
+ * [hasFixedBottom]="true"
3112
+ * [enableKeyboardHandling]="true">
3113
+ *
3114
+ * <div class="content">
3115
+ * <input type="text" placeholder="Type something..." />
3116
+ * </div>
3117
+ *
3118
+ * <div footer class="action-bar">
3119
+ * <button>Cancel</button>
3120
+ * <button>Submit</button>
3121
+ * </div>
3122
+ * </ds-mobile-modal-base>
3123
+ * ```
3124
+ */
3125
+ declare class DsMobileModalBaseComponent extends MobileModalBase implements AfterContentInit {
3126
+ private cdr;
3127
+ /**
3128
+ * Reference to ion-content for keyboard handling
3129
+ */
3130
+ ionContent?: IonContent;
3131
+ /**
3132
+ * Control header visibility
3133
+ * - true: Always show header
3134
+ * - false: Never show header (close button becomes absolute)
3135
+ * - 'auto' (default): Auto-detect based on header content
3136
+ */
3137
+ showHeader: _angular_core.InputSignal<boolean | "auto">;
3138
+ /**
3139
+ * Detect if custom loading state is provided
3140
+ */
3141
+ customLoadingState?: ElementRef;
3142
+ /**
3143
+ * Detect if custom error state is provided
3144
+ */
3145
+ customErrorState?: ElementRef;
3146
+ /**
3147
+ * Detect if header leading content is provided
3148
+ */
3149
+ headerLeading?: ElementRef;
3150
+ /**
3151
+ * Detect if header main content is provided
3152
+ */
3153
+ headerMain?: ElementRef;
3154
+ /**
3155
+ * Flag to track if content has been initialized
3156
+ */
3157
+ hasCustomLoadingState: boolean;
3158
+ hasCustomErrorState: boolean;
3159
+ constructor(cdr: ChangeDetectorRef);
3160
+ ngAfterContentInit(): void;
3161
+ /**
3162
+ * Determine if header should be shown based on showHeader input and content detection
3163
+ */
3164
+ shouldShowHeader(): boolean;
3165
+ /**
3166
+ * Check if a content child slot has actual content
3167
+ */
3168
+ private hasContentInSlot;
3169
+ static ɵfac: _angular_core.ɵɵFactoryDeclaration<DsMobileModalBaseComponent, never>;
3170
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<DsMobileModalBaseComponent, "ds-mobile-modal-base", never, { "showHeader": { "alias": "showHeader"; "required": false; "isSignal": true; }; }, {}, ["customLoadingState", "customErrorState", "headerLeading", "headerMain"], ["[header-leading]", "[header-main]", "[loading-state]", "[error-state]", "*", "[fixed-bottom]", "[footer]"], true, never>;
3171
+ }
3172
+
3173
+ /**
3174
+ * Post data interface for the modal
3175
+ *
3176
+ * Represents a post with its content, author info, and comments.
3177
+ * Use this interface to map your API response data to the component.
3178
+ *
3179
+ * @example
3180
+ * ```typescript
3181
+ * const postData: PostDetailData = {
3182
+ * postId: '123',
3183
+ * authorName: 'John Doe',
3184
+ * authorRole: 'Tenant',
3185
+ * timestamp: '2h ago',
3186
+ * avatarInitials: 'JD',
3187
+ * content: 'Post content here...',
3188
+ * likeCount: 42,
3189
+ * commentCount: 12,
3190
+ * comments: [...]
3191
+ * };
3192
+ * ```
3193
+ */
3194
+ interface PostDetailData {
3195
+ /** Unique post identifier */
3196
+ postId: string;
3197
+ /** Post author name */
3198
+ authorName: string;
3199
+ /** Author role (e.g., 'Tenant', 'Manager') */
3200
+ authorRole: string;
3201
+ /** Post timestamp (e.g., '2h ago', 'Yesterday') */
3202
+ timestamp: string;
3203
+ /** Author avatar initials (1-2 letters) */
3204
+ avatarInitials?: string;
3205
+ /** Avatar display type */
3206
+ avatarType?: 'photo' | 'initials';
3207
+ /** Author avatar image URL */
3208
+ avatarSrc?: string;
3209
+ /** Post text content */
3210
+ content: string;
3211
+ /** Optional post image URL */
3212
+ imageSrc?: string;
3213
+ /** Image alt text */
3214
+ imageAlt?: string;
3215
+ /** Whether the current user has liked this post */
3216
+ isLiked?: boolean;
3217
+ /** Number of likes */
3218
+ likeCount?: number;
3219
+ /** Number of comments */
3220
+ commentCount?: number;
3221
+ /** Array of comments */
3222
+ comments?: CommentData[];
3223
+ /** Auto-focus comment input when modal opens */
3224
+ focusComment?: boolean;
3225
+ }
3226
+ /**
3227
+ * Comment data interface
3228
+ *
3229
+ * Represents a single comment on a post.
3230
+ *
3231
+ * @example
3232
+ * ```typescript
3233
+ * const comment: CommentData = {
3234
+ * authorName: 'Jane Smith',
3235
+ * authorRole: 'Tenant',
3236
+ * timestamp: '1h ago',
3237
+ * avatarInitials: 'JS',
3238
+ * content: 'Great post!',
3239
+ * isLiked: false,
3240
+ * likeCount: 5,
3241
+ * isOwnComment: false
3242
+ * };
3243
+ * ```
3244
+ */
3245
+ interface CommentData {
3246
+ /** Unique comment identifier */
3247
+ id?: string;
3248
+ /** Comment author name */
3249
+ authorName: string;
3250
+ /** Author role */
3251
+ authorRole: string;
3252
+ /** Comment timestamp */
3253
+ timestamp: string;
2892
3254
  /** Author avatar initials */
2893
3255
  avatarInitials: string;
2894
3256
  /** Comment text content */
@@ -2929,13 +3291,14 @@ interface CommentData {
2929
3291
  * }
2930
3292
  * ```
2931
3293
  */
2932
- declare class DsMobilePostDetailModalComponent implements AfterViewInit, OnDestroy {
2933
- private modalController;
3294
+ declare class DsMobilePostDetailModalComponent implements OnInit, AfterViewInit {
2934
3295
  private lightbox;
2935
3296
  private bottomSheet;
2936
3297
  postData: PostDetailData;
2937
3298
  currentUserName: string;
2938
3299
  currentUserInitialsInput: string;
3300
+ loading: boolean;
3301
+ error: string | undefined;
2939
3302
  onSubmitComment?: (payload: {
2940
3303
  postId: string;
2941
3304
  text: string;
@@ -2951,16 +3314,6 @@ declare class DsMobilePostDetailModalComponent implements AfterViewInit, OnDestr
2951
3314
  onDeleteComment?: (payload: {
2952
3315
  commentId: string;
2953
3316
  }) => void;
2954
- /**
2955
- * Loading state - when true, shows loading indicator
2956
- * Set this to true while fetching post data from your API
2957
- */
2958
- loading: boolean;
2959
- /**
2960
- * Error state - when set, shows error message
2961
- * Set this to an error message string if API call fails
2962
- */
2963
- error?: string;
2964
3317
  commentInput?: ElementRef<HTMLTextAreaElement>;
2965
3318
  post: _angular_core.WritableSignal<PostDetailData>;
2966
3319
  commentText: _angular_core.WritableSignal<string>;
@@ -2987,19 +3340,9 @@ declare class DsMobilePostDetailModalComponent implements AfterViewInit, OnDestr
2987
3340
  initials: string;
2988
3341
  role: string;
2989
3342
  }[]>;
2990
- constructor(modalController: ModalController, lightbox: DsMobileLightboxService, bottomSheet: DsMobileBottomSheetService);
3343
+ constructor(lightbox: DsMobileLightboxService, bottomSheet: DsMobileBottomSheetService);
2991
3344
  ngOnInit(): void;
2992
3345
  ngAfterViewInit(): void;
2993
- ngOnDestroy(): void;
2994
- /**
2995
- * Set up keyboard event listeners to adjust composer position
2996
- * The CSS uses --keyboard-height variable to translate the composer up
2997
- */
2998
- private setupKeyboardListeners;
2999
- /**
3000
- * Clean up keyboard event listeners
3001
- */
3002
- private cleanupKeyboardListeners;
3003
3346
  /**
3004
3347
  * Show the keyboard when user interacts with input
3005
3348
  */
@@ -3041,10 +3384,6 @@ declare class DsMobilePostDetailModalComponent implements AfterViewInit, OnDestr
3041
3384
  active: boolean;
3042
3385
  count: number;
3043
3386
  }): void;
3044
- /**
3045
- * Close the modal
3046
- */
3047
- close(): void;
3048
3387
  /**
3049
3388
  * Submit a comment
3050
3389
  */
@@ -3058,7 +3397,83 @@ declare class DsMobilePostDetailModalComponent implements AfterViewInit, OnDestr
3058
3397
  */
3059
3398
  handleCommentLongPress(authorName: string, content: string, isOwnComment: boolean): Promise<void>;
3060
3399
  static ɵfac: _angular_core.ɵɵFactoryDeclaration<DsMobilePostDetailModalComponent, never>;
3061
- static ɵcmp: _angular_core.ɵɵComponentDeclaration<DsMobilePostDetailModalComponent, "ds-mobile-post-detail-modal", never, { "postData": { "alias": "postData"; "required": false; }; "currentUserName": { "alias": "currentUserName"; "required": false; }; "currentUserInitialsInput": { "alias": "currentUserInitialsInput"; "required": false; }; "onSubmitComment": { "alias": "onSubmitComment"; "required": false; }; "onToggleCommentLike": { "alias": "onToggleCommentLike"; "required": false; }; "onEditComment": { "alias": "onEditComment"; "required": false; }; "onDeleteComment": { "alias": "onDeleteComment"; "required": false; }; "loading": { "alias": "loading"; "required": false; }; "error": { "alias": "error"; "required": false; }; }, {}, never, never, true, never>;
3400
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<DsMobilePostDetailModalComponent, "ds-mobile-post-detail-modal", never, { "postData": { "alias": "postData"; "required": false; }; "currentUserName": { "alias": "currentUserName"; "required": false; }; "currentUserInitialsInput": { "alias": "currentUserInitialsInput"; "required": false; }; "loading": { "alias": "loading"; "required": false; }; "error": { "alias": "error"; "required": false; }; "onSubmitComment": { "alias": "onSubmitComment"; "required": false; }; "onToggleCommentLike": { "alias": "onToggleCommentLike"; "required": false; }; "onEditComment": { "alias": "onEditComment"; "required": false; }; "onDeleteComment": { "alias": "onDeleteComment"; "required": false; }; }, {}, never, never, true, never>;
3401
+ }
3402
+
3403
+ /**
3404
+ * BaseModalService
3405
+ *
3406
+ * Abstract base class for all modal services in the application.
3407
+ * Enforces consistent modal configuration and presentation across all modals.
3408
+ *
3409
+ * **Features:**
3410
+ * - Standardized modal presentation (stacked effect with gap at top)
3411
+ * - Consistent backdrop and dismissal behavior
3412
+ * - Automatic safe area handling
3413
+ * - Native iOS-style animations
3414
+ *
3415
+ * **Why use this:**
3416
+ * - Ensures all modals have the same look and feel
3417
+ * - Prevents inconsistencies in modal configuration
3418
+ * - Single source of truth for modal settings
3419
+ * - Makes it impossible to forget critical configuration
3420
+ *
3421
+ * @example
3422
+ * ```typescript
3423
+ * @Injectable({ providedIn: 'root' })
3424
+ * export class MyModalService extends BaseModalService {
3425
+ * async open(data: MyData) {
3426
+ * const modal = await this.createModal(
3427
+ * MyModalComponent,
3428
+ * { data }
3429
+ * );
3430
+ * await modal.present();
3431
+ * }
3432
+ * }
3433
+ * ```
3434
+ */
3435
+ declare abstract class BaseModalService {
3436
+ protected modalController: ModalController;
3437
+ constructor(modalController: ModalController);
3438
+ /**
3439
+ * Create a modal with standardized configuration
3440
+ *
3441
+ * This method enforces consistent modal behavior across the app:
3442
+ * - Uses 'ds-modal-base' CSS class for consistent styling
3443
+ * - iOS mode for native feel
3444
+ * - presentingElement for stacked modal effect
3445
+ * - Standard backdrop and dismissal settings
3446
+ *
3447
+ * @param component The component to display in the modal
3448
+ * @param componentProps Props to pass to the component
3449
+ * @param customOptions Optional overrides for specific modal needs
3450
+ * @returns Promise resolving to the created modal
3451
+ *
3452
+ * @example
3453
+ * ```typescript
3454
+ * const modal = await this.createModal(
3455
+ * PostDetailComponent,
3456
+ * { postId: '123', loading: false }
3457
+ * );
3458
+ * await modal.present();
3459
+ * ```
3460
+ */
3461
+ protected createModal<T>(component: Type<T>, componentProps?: Record<string, any>, customOptions?: Partial<ModalOptions$1>): Promise<HTMLIonModalElement>;
3462
+ /**
3463
+ * Close the currently open modal
3464
+ *
3465
+ * @param data Optional data to pass back when dismissing
3466
+ * @returns Promise that resolves when the modal is dismissed
3467
+ */
3468
+ close(data?: any): Promise<boolean>;
3469
+ /**
3470
+ * Get the top-most modal if one exists
3471
+ *
3472
+ * @returns Promise that resolves to the modal element or undefined
3473
+ */
3474
+ getTop(): Promise<HTMLIonModalElement | undefined>;
3475
+ static ɵfac: _angular_core.ɵɵFactoryDeclaration<BaseModalService, never>;
3476
+ static ɵprov: _angular_core.ɵɵInjectableDeclaration<BaseModalService>;
3062
3477
  }
3063
3478
 
3064
3479
  /**
@@ -3104,8 +3519,7 @@ declare class DsMobilePostDetailModalComponent implements AfterViewInit, OnDestr
3104
3519
  * }
3105
3520
  * ```
3106
3521
  */
3107
- declare class DsMobilePostDetailModalService {
3108
- private modalController;
3522
+ declare class DsMobilePostDetailModalService extends BaseModalService {
3109
3523
  constructor(modalController: ModalController);
3110
3524
  /**
3111
3525
  * Open the post detail modal
@@ -3135,19 +3549,6 @@ declare class DsMobilePostDetailModalService {
3135
3549
  currentUserName?: string;
3136
3550
  currentUserInitials?: string;
3137
3551
  }): Promise<void>;
3138
- /**
3139
- * Close the currently open post detail modal
3140
- *
3141
- * @param data Optional data to pass back when dismissing
3142
- * @returns Promise that resolves when the modal is dismissed
3143
- */
3144
- close(data?: any): Promise<boolean>;
3145
- /**
3146
- * Get the top-most modal if one exists
3147
- *
3148
- * @returns Promise that resolves to the modal element or undefined
3149
- */
3150
- getTop(): Promise<HTMLIonModalElement | undefined>;
3151
3552
  static ɵfac: _angular_core.ɵɵFactoryDeclaration<DsMobilePostDetailModalService, never>;
3152
3553
  static ɵprov: _angular_core.ɵɵInjectableDeclaration<DsMobilePostDetailModalService>;
3153
3554
  }
@@ -3161,12 +3562,21 @@ interface ChatMessage {
3161
3562
  senderId: string;
3162
3563
  senderName: string;
3163
3564
  senderRole?: string;
3164
- timestamp: string;
3565
+ timestamp: Date;
3165
3566
  avatarInitials?: string;
3166
3567
  avatarType?: 'initials' | 'photo' | 'icon';
3167
3568
  avatarSrc?: string;
3168
3569
  isOwnMessage: boolean;
3169
3570
  attachments?: ChatAttachment[];
3571
+ fileAttachments?: AttachmentData[];
3572
+ }
3573
+ /**
3574
+ * Message group interface for grouped timestamp display
3575
+ */
3576
+ interface MessageGroup {
3577
+ timestamp: Date;
3578
+ displayTimestamp: string;
3579
+ messages: ChatMessage[];
3170
3580
  }
3171
3581
  /**
3172
3582
  * Chat participant interface
@@ -3184,27 +3594,58 @@ interface ChatParticipant {
3184
3594
  *
3185
3595
  * Represents the data needed to display a chat conversation.
3186
3596
  *
3187
- * @example
3597
+ * @example
3188
3598
  * ```typescript
3189
3599
  * const chatData: ChatModalData = {
3190
3600
  * participant: {
3191
- * id: '123',
3601
+ * id: 'u-123',
3192
3602
  * name: 'Ricki Meihlen',
3193
3603
  * role: 'Inquiry assignee',
3194
- * avatarInitials: 'RM'
3604
+ * avatarInitials: 'RM',
3605
+ * avatarType: 'initials'
3195
3606
  * },
3607
+ * currentUserId: 'u-456',
3608
+ * currentUserInitials: 'SD',
3609
+ * currentUserAvatarType: 'initials',
3610
+ * autoFocus: true,
3196
3611
  * messages: [
3197
3612
  * {
3198
- * id: '1',
3199
- * content: 'We have received your case...',
3200
- * senderId: '123',
3613
+ * id: 'm-1',
3614
+ * content: 'We have received your case. Please see the attached photo.',
3615
+ * senderId: 'u-123',
3201
3616
  * senderName: 'Ricki Meihlen',
3617
+ * senderRole: 'Case worker',
3202
3618
  * timestamp: '12:34',
3203
3619
  * isOwnMessage: false,
3204
- * avatarInitials: 'RM'
3620
+ * avatarInitials: 'RM',
3621
+ * avatarType: 'initials',
3622
+ * attachments: [
3623
+ * {
3624
+ * id: 'a-1',
3625
+ * type: 'image',
3626
+ * url: 'https://example.com/photo.jpg',
3627
+ * name: 'photo.jpg',
3628
+ * thumbnail: 'https://example.com/photo_thumb.jpg'
3629
+ * },
3630
+ * {
3631
+ * id: 'a-2',
3632
+ * type: 'pdf',
3633
+ * url: 'https://example.com/report.pdf',
3634
+ * name: 'report.pdf'
3635
+ * }
3636
+ * ]
3637
+ * },
3638
+ * {
3639
+ * id: 'm-2',
3640
+ * content: 'Thanks — I will take a look.',
3641
+ * senderId: 'u-456',
3642
+ * senderName: 'You',
3643
+ * timestamp: '12:36',
3644
+ * isOwnMessage: true,
3645
+ * avatarInitials: 'SD',
3646
+ * avatarType: 'initials'
3205
3647
  * }
3206
- * ],
3207
- * currentUserId: '456'
3648
+ * ]
3208
3649
  * };
3209
3650
  * ```
3210
3651
  */
@@ -3253,8 +3694,8 @@ interface ChatModalData {
3253
3694
  * }
3254
3695
  * ```
3255
3696
  */
3256
- declare class DsMobileChatModalComponent implements OnInit, AfterViewInit, OnDestroy {
3257
- private modalController;
3697
+ declare class DsMobileChatModalComponent implements OnInit, AfterViewInit {
3698
+ private lightboxService;
3258
3699
  chatData: ChatModalData;
3259
3700
  /**
3260
3701
  * Loading state - when true, shows loading indicator
@@ -3270,18 +3711,25 @@ declare class DsMobileChatModalComponent implements OnInit, AfterViewInit, OnDes
3270
3711
  currentUserAvatarType: _angular_core.WritableSignal<"initials" | "photo" | "icon">;
3271
3712
  currentUserAvatarSrc: _angular_core.WritableSignal<string>;
3272
3713
  autoFocus: _angular_core.WritableSignal<boolean>;
3273
- constructor(modalController: ModalController);
3714
+ /**
3715
+ * Selected message ID for showing timestamp
3716
+ */
3717
+ selectedMessageId: _angular_core.WritableSignal<string | null>;
3718
+ /**
3719
+ * Timeout for auto-hiding timestamp
3720
+ */
3721
+ private timestampTimeout?;
3722
+ /**
3723
+ * Computed signal for grouped messages with timestamp headers
3724
+ */
3725
+ messageGroups: _angular_core.Signal<MessageGroup[]>;
3726
+ constructor(lightboxService: DsMobileLightboxService);
3274
3727
  ngOnInit(): void;
3275
3728
  ngAfterViewInit(): void;
3276
- ngOnDestroy(): void;
3277
3729
  /**
3278
3730
  * Scroll to bottom of messages
3279
3731
  */
3280
3732
  private scrollToBottom;
3281
- /**
3282
- * Close the modal
3283
- */
3284
- close(): void;
3285
3733
  /**
3286
3734
  * Handle message sent from composer
3287
3735
  */
@@ -3290,15 +3738,54 @@ declare class DsMobileChatModalComponent implements OnInit, AfterViewInit, OnDes
3290
3738
  isReply?: boolean;
3291
3739
  replyTo?: string;
3292
3740
  isEdit?: boolean;
3741
+ attachments?: AttachmentData[];
3293
3742
  }): void;
3294
3743
  /**
3295
3744
  * Handle attachment click
3296
3745
  */
3297
3746
  handleAttachmentClick(attachment: ChatAttachment): void;
3747
+ /**
3748
+ * Handle composer attachment button click
3749
+ */
3750
+ handleComposerAttachmentClick(): void;
3751
+ /**
3752
+ * Get file variant for card-inline-file component
3753
+ */
3754
+ getFileVariant(type: string): 'pdf' | 'doc';
3755
+ /**
3756
+ * Handle file attachment click
3757
+ */
3758
+ handleFileAttachmentClick(fileAttachment: AttachmentData): void;
3759
+ /**
3760
+ * Handle image attachment click - opens lightbox
3761
+ */
3762
+ handleImageClick(attachment: AttachmentData, message: ChatMessage): Promise<void>;
3298
3763
  /**
3299
3764
  * Handle message long press
3300
3765
  */
3301
3766
  handleMessageLongPress(message: ChatMessage): void;
3767
+ /**
3768
+ * Handle message click to show/hide timestamp
3769
+ */
3770
+ handleMessageClick(messageId: string): void;
3771
+ /**
3772
+ * Format message timestamp for display (EU 24-hour format, Danish)
3773
+ */
3774
+ formatMessageTimestamp(date: Date): string;
3775
+ /**
3776
+ * Group messages by time threshold
3777
+ * Messages within the threshold and on the same day are grouped together
3778
+ */
3779
+ private groupMessagesByTime;
3780
+ /**
3781
+ * Determine if a new message group should be started
3782
+ */
3783
+ private shouldStartNewGroup;
3784
+ /**
3785
+ * Format group timestamp header with smart date display
3786
+ * Uses 24-hour EU time format with Danish locale
3787
+ */
3788
+ private formatGroupTimestamp;
3302
3789
  static ɵfac: _angular_core.ɵɵFactoryDeclaration<DsMobileChatModalComponent, never>;
3303
3790
  static ɵcmp: _angular_core.ɵɵComponentDeclaration<DsMobileChatModalComponent, "ds-mobile-chat-modal", never, { "chatData": { "alias": "chatData"; "required": false; }; "loading": { "alias": "loading"; "required": false; }; "error": { "alias": "error"; "required": false; }; }, {}, never, never, true, never>;
3304
3791
  }
@@ -3358,8 +3845,7 @@ declare class DsMobileChatModalComponent implements OnInit, AfterViewInit, OnDes
3358
3845
  * }
3359
3846
  * ```
3360
3847
  */
3361
- declare class DsMobileChatModalService {
3362
- private modalController;
3848
+ declare class DsMobileChatModalService extends BaseModalService {
3363
3849
  constructor(modalController: ModalController);
3364
3850
  /**
3365
3851
  * Open the chat modal
@@ -3372,21 +3858,190 @@ declare class DsMobileChatModalService {
3372
3858
  loading?: boolean;
3373
3859
  error?: string;
3374
3860
  }): Promise<void>;
3861
+ static ɵfac: _angular_core.ɵɵFactoryDeclaration<DsMobileChatModalService, never>;
3862
+ static ɵprov: _angular_core.ɵɵInjectableDeclaration<DsMobileChatModalService>;
3863
+ }
3864
+
3865
+ /**
3866
+ * Photo interface for inquiry (deprecated - use AttachmentData)
3867
+ * @deprecated Use AttachmentData from attachment-preview component instead
3868
+ */
3869
+ interface InquiryPhoto {
3870
+ id: string;
3871
+ src: string;
3872
+ alt?: string;
3873
+ }
3874
+ /**
3875
+ * New inquiry form data
3876
+ */
3877
+ interface NewInquiryData {
3878
+ title: string;
3879
+ description: string;
3880
+ attachments: AttachmentData[];
3881
+ category?: string;
3882
+ }
3883
+ /**
3884
+ * DsMobileNewInquiryModalComponent
3885
+ *
3886
+ * Modal component for creating new inquiries.
3887
+ * Uses ds-mobile-modal-base for consistent layout and behavior.
3888
+ *
3889
+ * Features:
3890
+ * - Title and description fields
3891
+ * - Photo upload with preview
3892
+ * - Submit button at bottom
3893
+ * - Form validation
3894
+ * - Camera/photo picker integration
3895
+ *
3896
+ * This component is typically not used directly - use DsMobileNewInquiryModalService instead.
3897
+ *
3898
+ * @example
3899
+ * ```typescript
3900
+ * // Don't instantiate directly - use the service:
3901
+ * constructor(private inquiryModal: DsMobileNewInquiryModalService) {}
3902
+ *
3903
+ * createInquiry() {
3904
+ * this.inquiryModal.open({
3905
+ * onSubmit: (data) => console.log('Inquiry created:', data)
3906
+ * });
3907
+ * }
3908
+ * ```
3909
+ */
3910
+ declare class DsMobileNewInquiryModalComponent implements OnInit, AfterViewInit {
3911
+ titleInputRef?: ElementRef<HTMLElement>;
3912
+ titleInput?: DsTextareaComponent;
3913
+ fileInput?: ElementRef<HTMLInputElement>;
3375
3914
  /**
3376
- * Close the currently open chat modal
3377
- *
3378
- * @param data Optional data to pass back when dismissing
3379
- * @returns Promise that resolves when the modal is dismissed
3915
+ * Loading state for the modal
3380
3916
  */
3381
- close(data?: any): Promise<boolean>;
3917
+ loading: boolean;
3382
3918
  /**
3383
- * Get the top-most modal if one exists
3919
+ * Error message to display
3920
+ */
3921
+ error?: string;
3922
+ /**
3923
+ * Callback function when form is submitted
3924
+ */
3925
+ onSubmit?: (data: NewInquiryData) => void | Promise<void>;
3926
+ /**
3927
+ * Form title field
3928
+ */
3929
+ title: string;
3930
+ /**
3931
+ * Form description field
3932
+ */
3933
+ description: string;
3934
+ /**
3935
+ * Attachments array (replaces photos)
3936
+ */
3937
+ attachments: _angular_core.WritableSignal<AttachmentData[]>;
3938
+ /**
3939
+ * Form validation state
3940
+ */
3941
+ isFormValid: _angular_core.WritableSignal<boolean>;
3942
+ /**
3943
+ * Submitting state
3944
+ */
3945
+ isSubmitting: _angular_core.WritableSignal<boolean>;
3946
+ ngOnInit(): void;
3947
+ ngAfterViewInit(): void;
3948
+ /**
3949
+ * Auto-resize the title textarea based on content
3950
+ */
3951
+ private autoResizeTitleTextarea;
3952
+ /**
3953
+ * Handle title change with auto-resize
3954
+ */
3955
+ handleTitleChange(value: string): void;
3956
+ /**
3957
+ * Validate form fields
3958
+ */
3959
+ validateForm(): void;
3960
+ /**
3961
+ * Add a new photo from camera/library
3962
+ */
3963
+ addPhoto(): Promise<void>;
3964
+ /**
3965
+ * Remove an attachment
3966
+ */
3967
+ removeAttachment(attachmentId: string): void;
3968
+ /**
3969
+ * Handle attachment button click
3970
+ */
3971
+ handleAddAttachment(): void;
3972
+ /**
3973
+ * Detect file type from file name or mime type
3974
+ */
3975
+ private detectFileType;
3976
+ /**
3977
+ * Format file size for display
3978
+ */
3979
+ private formatFileSize;
3980
+ /**
3981
+ * Handle file selection from file input
3982
+ */
3983
+ handleFileSelect(event: Event): void;
3984
+ /**
3985
+ * Handle form submission
3986
+ */
3987
+ handleSubmit(): Promise<void>;
3988
+ static ɵfac: _angular_core.ɵɵFactoryDeclaration<DsMobileNewInquiryModalComponent, never>;
3989
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<DsMobileNewInquiryModalComponent, "ds-mobile-new-inquiry-modal", never, { "loading": { "alias": "loading"; "required": false; }; "error": { "alias": "error"; "required": false; }; "onSubmit": { "alias": "onSubmit"; "required": false; }; }, {}, never, never, true, never>;
3990
+ }
3991
+
3992
+ /**
3993
+ * Options for opening the new inquiry modal
3994
+ */
3995
+ interface NewInquiryModalOptions {
3996
+ /** Callback function when inquiry is submitted */
3997
+ onSubmit?: (data: NewInquiryData) => void | Promise<void>;
3998
+ /** Initial loading state */
3999
+ loading?: boolean;
4000
+ /** Initial error message */
4001
+ error?: string;
4002
+ }
4003
+ /**
4004
+ * DsMobileNewInquiryModalService
4005
+ *
4006
+ * Service for displaying the new inquiry creation modal.
4007
+ * Built on Ionic's modal system with native gestures and animations.
4008
+ *
4009
+ * Features:
4010
+ * - Full-screen modal with form
4011
+ * - Title and description inputs
4012
+ * - Photo upload with camera/gallery
4013
+ * - Form validation
4014
+ * - Submit handling
4015
+ * - Loading and error states
4016
+ *
4017
+ * @example
4018
+ * ```typescript
4019
+ * constructor(private inquiryModal: DsMobileNewInquiryModalService) {}
4020
+ *
4021
+ * async createNewInquiry() {
4022
+ * await this.inquiryModal.open({
4023
+ * onSubmit: async (data) => {
4024
+ * console.log('Creating inquiry:', data);
4025
+ * // Call your API to create the inquiry
4026
+ * await this.apiService.createInquiry(data);
4027
+ * // Close the modal
4028
+ * await this.inquiryModal.close();
4029
+ * }
4030
+ * });
4031
+ * }
4032
+ * ```
4033
+ */
4034
+ declare class DsMobileNewInquiryModalService extends BaseModalService {
4035
+ constructor(modalController: ModalController);
4036
+ /**
4037
+ * Open the new inquiry modal
3384
4038
  *
3385
- * @returns Promise that resolves to the modal element or undefined
4039
+ * @param options Modal options including onSubmit callback
4040
+ * @returns Promise that resolves when the modal is presented
3386
4041
  */
3387
- getTop(): Promise<HTMLIonModalElement | undefined>;
3388
- static ɵfac: _angular_core.ɵɵFactoryDeclaration<DsMobileChatModalService, never>;
3389
- static ɵprov: _angular_core.ɵɵInjectableDeclaration<DsMobileChatModalService>;
4042
+ open(options?: NewInquiryModalOptions): Promise<void>;
4043
+ static ɵfac: _angular_core.ɵɵFactoryDeclaration<DsMobileNewInquiryModalService, never>;
4044
+ static ɵprov: _angular_core.ɵɵInjectableDeclaration<DsMobileNewInquiryModalService>;
3390
4045
  }
3391
4046
 
3392
4047
  /**
@@ -3512,8 +4167,7 @@ interface ContactItem {
3512
4167
  *
3513
4168
  * This component is typically not used directly - use DsMobileHandbookDetailModalService instead.
3514
4169
  */
3515
- declare class DsMobileHandbookDetailModalComponent {
3516
- private modalController;
4170
+ declare class DsMobileHandbookDetailModalComponent implements OnInit {
3517
4171
  handbookData: HandbookDetailData;
3518
4172
  /**
3519
4173
  * Loading state - when true, shows loading indicator
@@ -3524,7 +4178,6 @@ declare class DsMobileHandbookDetailModalComponent {
3524
4178
  */
3525
4179
  error?: string;
3526
4180
  handbook: _angular_core.WritableSignal<HandbookDetailData>;
3527
- constructor(modalController: ModalController);
3528
4181
  ngOnInit(): void;
3529
4182
  /**
3530
4183
  * Split handbook items to enforce content structure rules:
@@ -3539,10 +4192,6 @@ declare class DsMobileHandbookDetailModalComponent {
3539
4192
  * Get all display items with enforced content structure rules applied
3540
4193
  */
3541
4194
  getDisplayItems(): HandbookItem[];
3542
- /**
3543
- * Close the modal
3544
- */
3545
- close(): void;
3546
4195
  /**
3547
4196
  * Handle contact click
3548
4197
  */
@@ -3593,8 +4242,7 @@ declare class DsMobileHandbookDetailModalComponent {
3593
4242
  * }
3594
4243
  * ```
3595
4244
  */
3596
- declare class DsMobileHandbookDetailModalService {
3597
- private modalController;
4245
+ declare class DsMobileHandbookDetailModalService extends BaseModalService {
3598
4246
  constructor(modalController: ModalController);
3599
4247
  /**
3600
4248
  * Open the handbook detail modal
@@ -3607,19 +4255,6 @@ declare class DsMobileHandbookDetailModalService {
3607
4255
  loading?: boolean;
3608
4256
  error?: string;
3609
4257
  }): Promise<void>;
3610
- /**
3611
- * Close the currently open handbook detail modal
3612
- *
3613
- * @param data Optional data to pass back when dismissing
3614
- * @returns Promise that resolves when the modal is dismissed
3615
- */
3616
- close(data?: any): Promise<boolean>;
3617
- /**
3618
- * Get the top-most modal if one exists
3619
- *
3620
- * @returns Promise that resolves to the modal element or undefined
3621
- */
3622
- getTop(): Promise<HTMLIonModalElement | undefined>;
3623
4258
  static ɵfac: _angular_core.ɵɵFactoryDeclaration<DsMobileHandbookDetailModalService, never>;
3624
4259
  static ɵprov: _angular_core.ɵɵInjectableDeclaration<DsMobileHandbookDetailModalService>;
3625
4260
  }
@@ -3841,6 +4476,68 @@ declare class DsTextInputComponent implements ControlValueAccessor {
3841
4476
  static ɵcmp: _angular_core.ɵɵComponentDeclaration<DsTextInputComponent, "ds-text-input", never, { "type": { "alias": "type"; "required": false; "isSignal": true; }; "placeholder": { "alias": "placeholder"; "required": false; "isSignal": true; }; "disabled": { "alias": "disabled"; "required": false; "isSignal": true; }; "readonly": { "alias": "readonly"; "required": false; "isSignal": true; }; "required": { "alias": "required"; "required": false; "isSignal": true; }; "hasError": { "alias": "hasError"; "required": false; "isSignal": true; }; "errorMessage": { "alias": "errorMessage"; "required": false; "isSignal": true; }; "autocomplete": { "alias": "autocomplete"; "required": false; "isSignal": true; }; "inputmode": { "alias": "inputmode"; "required": false; "isSignal": true; }; "autoClearError": { "alias": "autoClearError"; "required": false; "isSignal": true; }; "validator": { "alias": "validator"; "required": false; "isSignal": true; }; }, { "valueChange": "valueChange"; "blur": "blur"; "focus": "focus"; "errorCleared": "errorCleared"; }, never, never, true, never>;
3842
4477
  }
3843
4478
 
4479
+ /**
4480
+ * DsMobileFabComponent
4481
+ *
4482
+ * Floating Action Button component for mobile interfaces.
4483
+ * A prominent circular button that floats above the content.
4484
+ *
4485
+ * Features:
4486
+ * - Configurable positioning (bottom-right, bottom-left, bottom-center)
4487
+ * - Uses design system theming variables
4488
+ * - Handles iOS safe areas
4489
+ * - Accessible with ARIA labels
4490
+ * - Smooth entrance animation
4491
+ *
4492
+ * @example
4493
+ * ```html
4494
+ * <ds-mobile-fab
4495
+ * icon="remixAddLine"
4496
+ * [position]="'bottom-right'"
4497
+ * (fabClick)="handleAddClick()"
4498
+ * ariaLabel="Add new inquiry">
4499
+ * </ds-mobile-fab>
4500
+ * ```
4501
+ */
4502
+ declare class DsMobileFabComponent {
4503
+ /**
4504
+ * Icon name from the design system icon library
4505
+ * @default 'remixAddLine'
4506
+ */
4507
+ icon: _angular_core.InputSignal<string>;
4508
+ /**
4509
+ * Position of the FAB on the screen
4510
+ * @default 'bottom-right'
4511
+ */
4512
+ position: _angular_core.InputSignal<"bottom-right" | "bottom-left" | "bottom-center">;
4513
+ /**
4514
+ * Size of the FAB button
4515
+ * Note: FAB is always 56px circular, but this affects the icon size
4516
+ * @default 'md'
4517
+ */
4518
+ size: _angular_core.InputSignal<"sm" | "md" | "lg">;
4519
+ /**
4520
+ * ARIA label for accessibility
4521
+ * @required - Always provide a descriptive label
4522
+ */
4523
+ ariaLabel: _angular_core.InputSignal<string>;
4524
+ /**
4525
+ * Whether the FAB is disabled
4526
+ * @default false
4527
+ */
4528
+ disabled: _angular_core.InputSignal<boolean>;
4529
+ /**
4530
+ * Emitted when the FAB is clicked
4531
+ */
4532
+ fabClick: _angular_core.OutputEmitterRef<void>;
4533
+ /**
4534
+ * Handle button click
4535
+ */
4536
+ handleClick(): void;
4537
+ static ɵfac: _angular_core.ɵɵFactoryDeclaration<DsMobileFabComponent, never>;
4538
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<DsMobileFabComponent, "ds-mobile-fab", never, { "icon": { "alias": "icon"; "required": false; "isSignal": true; }; "position": { "alias": "position"; "required": false; "isSignal": true; }; "size": { "alias": "size"; "required": false; "isSignal": true; }; "ariaLabel": { "alias": "ariaLabel"; "required": true; "isSignal": true; }; "disabled": { "alias": "disabled"; "required": false; "isSignal": true; }; }, { "fabClick": "fabClick"; }, never, never, true, never>;
4539
+ }
4540
+
3844
4541
  interface WhitelabelConfig {
3845
4542
  logoUrl: string;
3846
4543
  logoMarkUrl: string;
@@ -4003,6 +4700,105 @@ declare class UserService {
4003
4700
  static ɵprov: _angular_core.ɵɵInjectableDeclaration<UserService>;
4004
4701
  }
4005
4702
 
4703
+ /**
4704
+ * Post and Comment Models
4705
+ * Unified data structures for community posts
4706
+ */
4707
+ interface Comment {
4708
+ authorName: string;
4709
+ authorRole: string;
4710
+ timestamp: string;
4711
+ avatarInitials: string;
4712
+ avatarSrc?: string;
4713
+ avatarType?: 'photo' | 'initials' | 'icon';
4714
+ content: string;
4715
+ likeCount: number;
4716
+ isLiked?: boolean;
4717
+ isOwnComment: boolean;
4718
+ }
4719
+ interface Post {
4720
+ id: string;
4721
+ authorName: string;
4722
+ authorRole: string;
4723
+ timestamp: string;
4724
+ avatarType: 'photo' | 'initials' | 'icon';
4725
+ avatarSrc?: string;
4726
+ avatarInitials?: string;
4727
+ avatarIconName?: string;
4728
+ content: string;
4729
+ imageSrc?: string;
4730
+ imageAlt?: string;
4731
+ isLiked: boolean;
4732
+ likeCount: number;
4733
+ commentCount: number;
4734
+ comments?: Comment[];
4735
+ isPinned?: boolean;
4736
+ showBadge?: boolean;
4737
+ hasInlinePhotos?: boolean;
4738
+ inlinePhotos?: string[];
4739
+ inlinePhotoCount?: number;
4740
+ }
4741
+
4742
+ /**
4743
+ * PostsService
4744
+ *
4745
+ * Centralized service for managing community posts.
4746
+ * Provides a single source of truth for post data, including comments.
4747
+ *
4748
+ * Features:
4749
+ * - CRUD operations for posts
4750
+ * - Infinite scroll pagination
4751
+ * - Post search/filter (future)
4752
+ * - Comment management (future)
4753
+ */
4754
+ declare class PostsService {
4755
+ private postsState;
4756
+ posts: _angular_core.Signal<Post[]>;
4757
+ private currentPage;
4758
+ private hasMore;
4759
+ constructor();
4760
+ /**
4761
+ * Get a post by ID
4762
+ */
4763
+ getPostById(id: string): Post | undefined;
4764
+ /**
4765
+ * Add a new post (e.g., from post creator)
4766
+ * Adds to the beginning of the list
4767
+ */
4768
+ addPost(post: Post): void;
4769
+ /**
4770
+ * Update an existing post
4771
+ */
4772
+ updatePost(id: string, updates: Partial<Post>): void;
4773
+ /**
4774
+ * Delete a post by ID
4775
+ */
4776
+ deletePost(id: string): void;
4777
+ /**
4778
+ * Load more posts for infinite scroll
4779
+ * Returns true if more posts were loaded, false if no more posts
4780
+ */
4781
+ loadMorePosts(): Promise<boolean>;
4782
+ /**
4783
+ * Check if there are more posts to load
4784
+ */
4785
+ hasMorePosts(): boolean;
4786
+ /**
4787
+ * Reset pagination (e.g., on pull-to-refresh)
4788
+ */
4789
+ resetPagination(): void;
4790
+ /**
4791
+ * Get initial posts (shown on page load)
4792
+ */
4793
+ private getInitialPosts;
4794
+ /**
4795
+ * Get additional posts for infinite scroll
4796
+ */
4797
+ private getAdditionalPosts;
4798
+ static ɵfac: _angular_core.ɵɵFactoryDeclaration<PostsService, never>;
4799
+ static ɵprov: _angular_core.ɵɵInjectableDeclaration<PostsService>;
4800
+ }
4801
+
4006
4802
  declare class MobileCommunityPageComponent {
4007
4803
  private router;
4008
4804
  private route;
@@ -4010,23 +4806,22 @@ declare class MobileCommunityPageComponent {
4010
4806
  private lightbox;
4011
4807
  private postModal;
4012
4808
  userService: UserService;
4013
- userPosts: _angular_core.WritableSignal<any[]>;
4014
- showStaticPosts: _angular_core.WritableSignal<boolean>;
4809
+ private postsService;
4810
+ allPosts: _angular_core.Signal<Post[]>;
4015
4811
  hasAnyPosts: _angular_core.Signal<boolean>;
4016
- constructor(router: Router, route: ActivatedRoute, bottomSheet: DsMobileBottomSheetService, lightbox: DsMobileLightboxService, postModal: DsMobilePostDetailModalService, userService: UserService);
4812
+ hasMorePosts: _angular_core.Signal<boolean>;
4813
+ constructor(router: Router, route: ActivatedRoute, bottomSheet: DsMobileBottomSheetService, lightbox: DsMobileLightboxService, postModal: DsMobilePostDetailModalService, userService: UserService, postsService: PostsService);
4814
+ /**
4815
+ * Handle infinite scroll event
4816
+ * Loads more posts when user scrolls to bottom
4817
+ */
4818
+ onInfiniteScroll(event: any): Promise<void>;
4017
4819
  handleRefresh(event: any): void;
4018
4820
  /**
4019
4821
  * Open post detail modal
4020
- * This provides a better UX than route navigation:
4021
- * - Maintains scroll position
4022
- * - Native iOS-style modal feel
4023
- * - Proper close button that works
4822
+ * Gets post data from service and opens modal
4024
4823
  */
4025
4824
  openPost(postId: string, focusComment?: boolean): Promise<void>;
4026
- /**
4027
- * Open user-created post detail modal
4028
- */
4029
- openUserPost(index: number, focusComment?: boolean): Promise<void>;
4030
4825
  openPostCreator(): Promise<void>;
4031
4826
  /**
4032
4827
  * Open an image in the lightbox viewer
@@ -4037,7 +4832,7 @@ declare class MobileCommunityPageComponent {
4037
4832
  /**
4038
4833
  * Handle long press on a post to show action sheet
4039
4834
  */
4040
- handlePostLongPress(postIdOrIndex: string | number, isOwnPost: boolean): Promise<void>;
4835
+ handlePostLongPress(postId: string, isOwnPost: boolean): Promise<void>;
4041
4836
  static ɵfac: _angular_core.ɵɵFactoryDeclaration<MobileCommunityPageComponent, never>;
4042
4837
  static ɵcmp: _angular_core.ɵɵComponentDeclaration<MobileCommunityPageComponent, "app-mobile-community-page", never, {}, {}, never, never, true, never>;
4043
4838
  }
@@ -4075,7 +4870,8 @@ interface Inquiry {
4075
4870
  declare class MobileInquiriesPageComponent {
4076
4871
  userService: UserService;
4077
4872
  private navCtrl;
4078
- constructor(userService: UserService, navCtrl: NavController);
4873
+ private newInquiryModal;
4874
+ constructor(userService: UserService, navCtrl: NavController, newInquiryModal: DsMobileNewInquiryModalService);
4079
4875
  filterStatus: _angular_core.WritableSignal<"open" | "closed" | "all">;
4080
4876
  tabItems: InlineTabItem[];
4081
4877
  inquiries: _angular_core.WritableSignal<Inquiry[]>;
@@ -4086,22 +4882,11 @@ declare class MobileInquiriesPageComponent {
4086
4882
  getInquiryIcon(category: string): string;
4087
4883
  openInquiryDetail(inquiryId: string): void;
4088
4884
  showInquiryActions(inquiryId: string): void;
4885
+ createNewInquiry(): Promise<void>;
4089
4886
  static ɵfac: _angular_core.ɵɵFactoryDeclaration<MobileInquiriesPageComponent, never>;
4090
4887
  static ɵcmp: _angular_core.ɵɵComponentDeclaration<MobileInquiriesPageComponent, "app-mobile-inquiries-page", never, {}, {}, never, never, true, never>;
4091
4888
  }
4092
4889
 
4093
- interface ActivityItem {
4094
- id: string;
4095
- type: 'assignment' | 'status_change' | 'creation';
4096
- actor?: string;
4097
- actorInitials?: string;
4098
- title: string;
4099
- description?: string;
4100
- timestamp: string;
4101
- date: string;
4102
- iconName: string;
4103
- iconBgColor?: string;
4104
- }
4105
4890
  interface MessageThread {
4106
4891
  id: string;
4107
4892
  senderName: string;
@@ -4112,28 +4897,28 @@ interface MessageThread {
4112
4897
  timestamp: string;
4113
4898
  unread: boolean;
4114
4899
  }
4115
- declare class MobileInquiryDetailPageComponent extends MobilePageBase implements AfterViewInit {
4900
+ declare class MobileInquiryDetailPageComponent {
4116
4901
  userService: UserService;
4117
- private navCtrl;
4118
- private elementRef;
4119
4902
  private lightbox;
4120
4903
  private chatModal;
4121
- ionContent?: IonContent;
4122
- private platform;
4123
- isNativePlatform: _angular_core.Signal<boolean>;
4124
4904
  inquiryTitle: string;
4125
4905
  activeTab: _angular_core.WritableSignal<string>;
4126
4906
  tabItems: InlineTabItem[];
4127
- activities: ActivityItem[];
4128
4907
  messageThreads: MessageThread[];
4129
4908
  unreadMessagesCount: _angular_core.Signal<number>;
4130
4909
  photos: LightboxImage[];
4131
- constructor(userService: UserService, navCtrl: NavController, elementRef: ElementRef, lightbox: DsMobileLightboxService, chatModal: DsMobileChatModalService);
4132
- ngAfterViewInit(): void;
4910
+ constructor(userService: UserService, lightbox: DsMobileLightboxService, chatModal: DsMobileChatModalService);
4133
4911
  setActiveTab(tabId: string): void;
4134
4912
  goBack(): void;
4135
- handleScroll(event: any): void;
4136
4913
  handleRefresh(event: any): void;
4914
+ /**
4915
+ * Check if there are unread messages
4916
+ */
4917
+ hasUnreadMessages(): boolean;
4918
+ /**
4919
+ * Navigate to messages tab when banner is clicked
4920
+ */
4921
+ navigateToMessagesTab(): void;
4137
4922
  openMessage(messageId: string): Promise<void>;
4138
4923
  openPhotoLightbox(index: number): Promise<void>;
4139
4924
  static ɵfac: _angular_core.ɵɵFactoryDeclaration<MobileInquiryDetailPageComponent, never>;
@@ -4221,6 +5006,23 @@ declare class MobilePostDetailPageComponent {
4221
5006
  static ɵcmp: _angular_core.ɵɵComponentDeclaration<MobilePostDetailPageComponent, "app-mobile-post-detail-page", never, {}, {}, never, never, true, never>;
4222
5007
  }
4223
5008
 
5009
+ /**
5010
+ * SignInPageComponent
5011
+ *
5012
+ * Sign-in page with email authentication.
5013
+ * Features logomark, decorative city background, and form validation.
5014
+ */
5015
+ declare class SignInPageComponent {
5016
+ email: string;
5017
+ emailError: _angular_core.WritableSignal<boolean>;
5018
+ isSubmitting: _angular_core.WritableSignal<boolean>;
5019
+ emailSent: _angular_core.WritableSignal<boolean>;
5020
+ handleSubmit(): void;
5021
+ handleBackToLogin(): void;
5022
+ static ɵfac: _angular_core.ɵɵFactoryDeclaration<SignInPageComponent, never>;
5023
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<SignInPageComponent, "app-sign-in", never, {}, {}, never, never, true, never>;
5024
+ }
5025
+
4224
5026
  /**
4225
5027
  * Whitelabel Demo Page
4226
5028
  *
@@ -4246,23 +5048,26 @@ declare class WhitelabelDemoPage implements OnInit {
4246
5048
  }
4247
5049
 
4248
5050
  /**
4249
- * Custom page transition - iOS-style push/pop
5051
+ * Custom page transition - iOS-style push/pop with dark overlay
4250
5052
  *
4251
5053
  * FORWARD (navigating TO detail):
4252
5054
  * - Entering page (detail): slides in from RIGHT, z-index 10 (on top)
4253
5055
  * - Leaving page (list): slides LEFT slightly, z-index 9 (underneath)
5056
+ * - Dark overlay: fades in on leaving page (opacity 0 to 0.70)
4254
5057
  *
4255
5058
  * REVERSE (swipe back FROM detail):
4256
5059
  * - Entering page (list): slides in from LEFT, z-index 9 (underneath)
4257
5060
  * - Leaving page (detail): slides out to RIGHT, z-index 10 (on top)
5061
+ * - Dark overlay: fades out on entering page (main page underneath, opacity 0.70 to 0)
4258
5062
  */
4259
5063
  declare const customPageTransition: (_: HTMLElement, opts: any) => Animation;
4260
5064
  /**
4261
5065
  * Custom back transition - iOS style where page slides out to reveal page underneath
4262
5066
  * The entering page (inquiries) slides in from the LEFT underneath
4263
5067
  * The leaving page (detail) slides out to the RIGHT on top
5068
+ * Dark overlay: fades out on entering page (main page underneath, opacity 0.70 to 0)
4264
5069
  */
4265
5070
  declare const customBackTransition: (_: HTMLElement, opts: any) => Animation;
4266
5071
 
4267
- export { ActionCommentComponent, ActionLikeComponent, ContentRowComponent, DsAvatarWithBadgeComponent, DsMobileActionsBottomSheetComponent, DsMobileBottomSheetService, DsMobileChatModalComponent, DsMobileChatModalService, DsMobileActionsBottomSheetComponent as DsMobileCommentActionsBottomSheetComponent, DsMobileCommentComponent, DsMobileContactListItemComponent, DsMobileContentComponent, DsMobileContentSectionComponent, DsMobileHandbookDetailModalComponent, DsMobileHandbookDetailModalService, DsMobileHandbookFolderComponent, DsMobileHandbookFolderMiniComponent, DsMobileHeaderContentComponent, DsMobileHeaderContentTileComponent, DsMobileInlinePhotoComponent, DsMobileInlineTabsComponent, DsMobileInteractiveListItemInquiryComponent, DsMobileInteractiveListItemMessageComponent, DsMobileInteractiveListItemPostComponent, DsMobileLightboxImageComponent as DsMobileLightboxComponent, DsMobileLightboxFooterComponent, DsMobileLightboxHeaderComponent, DsMobileLightboxImageComponent, DsMobileLightboxPdfComponent, DsMobileLightboxService, DsMobileListItemComponent, DsMobileListItemStaticComponent, DsMobileLongPressDirective, DsMobileMessageBubbleComponent, DsMobileMessageComposerComponent, DsMobileModalService, DsMobilePageDetailsComponent, DsMobilePageMainComponent, DsMobileActionsBottomSheetComponent as DsMobilePostActionsBottomSheetComponent, DsMobilePostComposerComponent, DsMobilePostCreateBottomSheetComponent, DsMobilePostDetailModalComponent, DsMobilePostDetailModalService, DsMobileTabBarComponent, DsMobileTabsComponent, DsTextInputComponent, MobileCommunityPageComponent, MobileHandbookPageComponent, MobileHomePageComponent, MobileInquiriesPageComponent, MobileInquiryDetailPageComponent, MobilePageBase, MobilePostDetailPageComponent, MobileTabsExampleComponent, PostActionsComponent, PostAttachmentsComponent, PostContentComponent, PostCreatePageComponent, PostMediaComponent, PostPdfAttachmentComponent, PostTextComponent, SectionHeaderComponent, TileContentComponent, TileIconComponent, TileLabelComponent, TileValueComponent, UserService, WhitelabelDemoPage, WhitelabelService, customBackTransition, customPageTransition };
4268
- export type { ActionGroup, ActionItem, ActionResult, AttachmentItem, AvatarSize, AvatarType, BadgePosition, BottomSheetOptions, ChatAttachment, ChatMessage, ChatModalData, ChatParticipant, ActionResult as CommentActionResult, CommentData, ContactItem, ContentWidth, HandbookDetailData, HandbookItem, InlineTabItem, LightboxAuthor, LightboxImage, LightboxImageOptions, LightboxMediaFile, LightboxMediaType, LightboxOptions, LightboxPdf, LightboxPdfOptions, ModalOptions, ActionResult as PostActionResult, PostDetailData, TabConfig, WhitelabelConfig };
5072
+ export { ActionCommentComponent, ActionLikeComponent, ContentRowComponent, DsAvatarWithBadgeComponent, DsMobileActionsBottomSheetComponent, DsMobileAttachmentPreviewComponent, DsMobileBottomSheetService, DsMobileChatModalComponent, DsMobileChatModalService, DsMobileActionsBottomSheetComponent as DsMobileCommentActionsBottomSheetComponent, DsMobileCommentComponent, DsMobileContactListItemComponent, DsMobileContentComponent, DsMobileContentSectionComponent, DsMobileFabComponent, DsMobileHandbookDetailModalComponent, DsMobileHandbookDetailModalService, DsMobileHandbookFolderComponent, DsMobileHandbookFolderMiniComponent, DsMobileHeaderContentComponent, DsMobileHeaderContentTileComponent, DsMobileInlinePhotoComponent, DsMobileInlineTabsComponent, DsMobileInteractiveListItemInquiryComponent, DsMobileInteractiveListItemMessageComponent, DsMobileInteractiveListItemPostComponent, DsMobileLightboxImageComponent as DsMobileLightboxComponent, DsMobileLightboxFooterComponent, DsMobileLightboxHeaderComponent, DsMobileLightboxImageComponent, DsMobileLightboxPdfComponent, DsMobileLightboxService, DsMobileListItemComponent, DsMobileListItemStaticComponent, DsMobileLongPressDirective, DsMobileMessageBubbleComponent, DsMobileMessageComposerComponent, DsMobileModalBaseComponent, DsMobileModalService, DsMobileNewInquiryModalComponent, DsMobileNewInquiryModalService, DsMobilePageDetailsComponent, DsMobilePageMainComponent, DsMobileActionsBottomSheetComponent as DsMobilePostActionsBottomSheetComponent, DsMobilePostComposerComponent, DsMobilePostCreateBottomSheetComponent, DsMobilePostDetailModalComponent, DsMobilePostDetailModalService, DsMobileTabBarComponent, DsMobileTabsComponent, DsTextInputComponent, MobileCommunityPageComponent, MobileHandbookPageComponent, MobileHomePageComponent, MobileInquiriesPageComponent, MobileInquiryDetailPageComponent, MobileModalBase, MobilePageBase, MobilePostDetailPageComponent, MobileTabsExampleComponent, PostActionsComponent, PostAttachmentsComponent, PostContentComponent, PostCreatePageComponent, PostMediaComponent, PostPdfAttachmentComponent, PostTextComponent, SectionHeaderComponent, SignInPageComponent, TileContentComponent, TileIconComponent, TileLabelComponent, TileValueComponent, UserService, WhitelabelDemoPage, WhitelabelService, customBackTransition, customPageTransition };
5073
+ export type { ActionGroup, ActionItem, ActionResult, AttachmentData, AttachmentFileType, AttachmentItem, AvatarSize, AvatarType, BadgePosition, BottomSheetOptions, ChatAttachment, ChatMessage, ChatModalData, ChatParticipant, Comment, ActionResult as CommentActionResult, CommentData, ContactItem, ContentWidth, HandbookDetailData, HandbookItem, InlineTabItem, InquiryPhoto, LightboxAuthor, LightboxImage, LightboxImageOptions, LightboxMediaFile, LightboxMediaType, LightboxOptions, LightboxPdf, LightboxPdfOptions, ModalOptions, NewInquiryData, NewInquiryModalOptions, Post, ActionResult as PostActionResult, PostDetailData, TabConfig, WhitelabelConfig };