@propbinder/mobile-design 0.2.46 → 0.2.48

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
@@ -43,6 +43,13 @@ type NetworkStatus = 'online' | 'offline' | 'unknown';
43
43
  * @internal This is a base class and should not be used directly.
44
44
  */
45
45
  declare abstract class MobilePageBase implements OnDestroy {
46
+ /**
47
+ * Shows a loading overlay above page content area.
48
+ *
49
+ * Non-breaking: defaults to false, so existing pages are unchanged
50
+ * until they explicitly opt in.
51
+ */
52
+ contentLoading: _angular_core.InputSignal<boolean>;
46
53
  /**
47
54
  * Maximum content width (desktop only)
48
55
  *
@@ -136,7 +143,7 @@ declare abstract class MobilePageBase implements OnDestroy {
136
143
  */
137
144
  ngOnDestroy(): void;
138
145
  static ɵfac: _angular_core.ɵɵFactoryDeclaration<MobilePageBase, never>;
139
- static ɵdir: _angular_core.ɵɵDirectiveDeclaration<MobilePageBase, never, never, { "contentWidth": { "alias": "contentWidth"; "required": false; "isSignal": true; }; }, {}, never, never, true, never>;
146
+ static ɵdir: _angular_core.ɵɵDirectiveDeclaration<MobilePageBase, never, never, { "contentLoading": { "alias": "contentLoading"; "required": false; "isSignal": true; }; "contentWidth": { "alias": "contentWidth"; "required": false; "isSignal": true; }; }, {}, never, never, true, never>;
140
147
  }
141
148
 
142
149
  interface ActionResult {
@@ -731,7 +738,7 @@ declare class DsMobilePageMainComponent extends MobilePageBase implements AfterV
731
738
  title: _angular_core.InputSignal<string>;
732
739
  headerTitle: _angular_core.InputSignal<string>;
733
740
  headerSubtitle: _angular_core.InputSignal<string>;
734
- avatarType: _angular_core.InputSignal<"initials" | "photo" | "icon">;
741
+ avatarType: _angular_core.InputSignal<"photo" | "initials" | "icon">;
735
742
  avatarInitials: _angular_core.InputSignal<string>;
736
743
  avatarSrc: _angular_core.InputSignal<string>;
737
744
  avatarIconName: _angular_core.InputSignal<string>;
@@ -1577,11 +1584,19 @@ declare class DsMobileCommentComponent {
1577
1584
  /**
1578
1585
  * Avatar type
1579
1586
  */
1580
- avatarType: _angular_core.InputSignal<"initials" | "photo" | "icon">;
1587
+ avatarType: _angular_core.InputSignal<"photo" | "initials" | "icon">;
1581
1588
  /**
1582
1589
  * Whether the comment is clickable
1583
1590
  */
1584
1591
  clickable: _angular_core.InputSignal<boolean>;
1592
+ /**
1593
+ * Unified toggle for contextual actions (more button + long press).
1594
+ * - `true` — more button visible on **all** breakpoints, long press enabled
1595
+ * - `false` — more button hidden, long press suppressed
1596
+ * - `undefined` (default) — falls back to desktop-only button
1597
+ */
1598
+ moreActions: _angular_core.InputSignal<boolean | undefined>;
1599
+ shouldShowMoreButton: _angular_core.Signal<boolean>;
1585
1600
  /**
1586
1601
  * Whether this comment belongs to the current user
1587
1602
  */
@@ -1668,7 +1683,7 @@ declare class DsMobileCommentComponent {
1668
1683
  */
1669
1684
  handleMoreButtonClick(event: Event): void;
1670
1685
  static ɵfac: _angular_core.ɵɵFactoryDeclaration<DsMobileCommentComponent, never>;
1671
- static ɵcmp: _angular_core.ɵɵComponentDeclaration<DsMobileCommentComponent, "ds-mobile-comment", never, { "authorName": { "alias": "authorName"; "required": true; "isSignal": true; }; "authorRole": { "alias": "authorRole"; "required": true; "isSignal": true; }; "timestamp": { "alias": "timestamp"; "required": true; "isSignal": true; }; "content": { "alias": "content"; "required": true; "isSignal": true; }; "avatarInitials": { "alias": "avatarInitials"; "required": false; "isSignal": true; }; "avatarType": { "alias": "avatarType"; "required": false; "isSignal": true; }; "clickable": { "alias": "clickable"; "required": false; "isSignal": true; }; "isOwnComment": { "alias": "isOwnComment"; "required": false; "isSignal": true; }; "isLiked": { "alias": "isLiked"; "required": false; "isSignal": true; }; "likeCount": { "alias": "likeCount"; "required": false; "isSignal": true; }; }, { "likeToggled": "likeToggled"; "commentClick": "commentClick"; "replyClick": "replyClick"; "editClick": "editClick"; "longPress": "longPress"; }, never, never, true, never>;
1686
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<DsMobileCommentComponent, "ds-mobile-comment", never, { "authorName": { "alias": "authorName"; "required": true; "isSignal": true; }; "authorRole": { "alias": "authorRole"; "required": true; "isSignal": true; }; "timestamp": { "alias": "timestamp"; "required": true; "isSignal": true; }; "content": { "alias": "content"; "required": true; "isSignal": true; }; "avatarInitials": { "alias": "avatarInitials"; "required": false; "isSignal": true; }; "avatarType": { "alias": "avatarType"; "required": false; "isSignal": true; }; "clickable": { "alias": "clickable"; "required": false; "isSignal": true; }; "moreActions": { "alias": "moreActions"; "required": false; "isSignal": true; }; "isOwnComment": { "alias": "isOwnComment"; "required": false; "isSignal": true; }; "isLiked": { "alias": "isLiked"; "required": false; "isSignal": true; }; "likeCount": { "alias": "likeCount"; "required": false; "isSignal": true; }; }, { "likeToggled": "likeToggled"; "commentClick": "commentClick"; "replyClick": "replyClick"; "editClick": "editClick"; "longPress": "longPress"; }, never, never, true, never>;
1672
1687
  }
1673
1688
 
1674
1689
  /**
@@ -1696,7 +1711,7 @@ declare class DsMobilePostComposerComponent {
1696
1711
  /**
1697
1712
  * Avatar type
1698
1713
  */
1699
- avatarType: _angular_core.InputSignal<"initials" | "photo" | "icon">;
1714
+ avatarType: _angular_core.InputSignal<"photo" | "initials" | "icon">;
1700
1715
  /**
1701
1716
  * Avatar photo source (for photo type)
1702
1717
  */
@@ -1988,7 +2003,7 @@ declare class DsMobileMessageComposerComponent implements AfterViewInit, OnDestr
1988
2003
  /**
1989
2004
  * Avatar type
1990
2005
  */
1991
- avatarType: _angular_core.InputSignal<"initials" | "photo" | "icon">;
2006
+ avatarType: _angular_core.InputSignal<"photo" | "initials" | "icon">;
1992
2007
  /**
1993
2008
  * Avatar photo source (for photo type)
1994
2009
  */
@@ -2309,7 +2324,7 @@ declare class DsMobileMessageBubbleComponent {
2309
2324
  /**
2310
2325
  * Avatar type
2311
2326
  */
2312
- avatarType: _angular_core.InputSignal<"initials" | "photo" | "icon">;
2327
+ avatarType: _angular_core.InputSignal<"photo" | "initials" | "icon">;
2313
2328
  /**
2314
2329
  * Avatar photo source (for photo type)
2315
2330
  */
@@ -2603,11 +2618,22 @@ declare class DsMobileListItemComponent implements AfterViewInit {
2603
2618
  enableLongPress: _angular_core.InputSignal<boolean>;
2604
2619
  /**
2605
2620
  * Show "more actions" button on desktop for items with long-press enabled
2606
- * Only visible on desktop (hover: hover) and when enableLongPress is true
2607
- * Clicking this button triggers the same handler as long-press on mobile
2621
+ * @deprecated Use `moreActions` instead. Kept for backwards compatibility.
2608
2622
  * @default true
2609
2623
  */
2610
2624
  showDesktopMoreButton: _angular_core.InputSignal<boolean>;
2625
+ /**
2626
+ * Unified toggle for contextual actions (more button + long press).
2627
+ * - `true` — more button visible on **all** breakpoints, long press enabled
2628
+ * - `false` — more button hidden, long press suppressed
2629
+ * - `undefined` (default) — falls back to legacy `enableLongPress` + `showDesktopMoreButton` + desktop check
2630
+ */
2631
+ moreActions: _angular_core.InputSignal<boolean | undefined>;
2632
+ /**
2633
+ * Resolved visibility of the more-actions button.
2634
+ * When `moreActions` is set it takes precedence; otherwise legacy inputs + breakpoint apply.
2635
+ */
2636
+ shouldShowMoreButton: _angular_core.Signal<boolean>;
2611
2637
  /**
2612
2638
  * Offset distance for the interactive background pseudo-element
2613
2639
  * Extends the background beyond the content bounds
@@ -2679,7 +2705,7 @@ declare class DsMobileListItemComponent implements AfterViewInit {
2679
2705
  */
2680
2706
  handleMoreButtonClick(event: Event): void;
2681
2707
  static ɵfac: _angular_core.ɵɵFactoryDeclaration<DsMobileListItemComponent, never>;
2682
- static ɵcmp: _angular_core.ɵɵComponentDeclaration<DsMobileListItemComponent, "ds-mobile-list-item", never, { "leadingSize": { "alias": "leadingSize"; "required": false; "isSignal": true; }; "variant": { "alias": "variant"; "required": false; "isSignal": true; }; "align": { "alias": "align"; "required": false; "isSignal": true; }; "interactive": { "alias": "interactive"; "required": false; "isSignal": true; }; "disabled": { "alias": "disabled"; "required": false; "isSignal": true; }; "loading": { "alias": "loading"; "required": false; "isSignal": true; }; "enableLongPress": { "alias": "enableLongPress"; "required": false; "isSignal": true; }; "showDesktopMoreButton": { "alias": "showDesktopMoreButton"; "required": false; "isSignal": true; }; "interactiveOffset": { "alias": "interactiveOffset"; "required": false; "isSignal": true; }; "title": { "alias": "title"; "required": false; "isSignal": true; }; "subtitle": { "alias": "subtitle"; "required": false; "isSignal": true; }; "showDivider": { "alias": "showDivider"; "required": false; "isSignal": true; }; "dividerSpacing": { "alias": "dividerSpacing"; "required": false; "isSignal": true; }; }, { "itemClick": "itemClick"; "moreButtonClick": "moreButtonClick"; }, never, ["[content-leading]", "[content-main]", "*", "[content-trailing]"], true, [{ directive: typeof DsMobileLongPressDirective; inputs: {}; outputs: { "longPress": "longPress"; }; }]>;
2708
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<DsMobileListItemComponent, "ds-mobile-list-item", never, { "leadingSize": { "alias": "leadingSize"; "required": false; "isSignal": true; }; "variant": { "alias": "variant"; "required": false; "isSignal": true; }; "align": { "alias": "align"; "required": false; "isSignal": true; }; "interactive": { "alias": "interactive"; "required": false; "isSignal": true; }; "disabled": { "alias": "disabled"; "required": false; "isSignal": true; }; "loading": { "alias": "loading"; "required": false; "isSignal": true; }; "enableLongPress": { "alias": "enableLongPress"; "required": false; "isSignal": true; }; "showDesktopMoreButton": { "alias": "showDesktopMoreButton"; "required": false; "isSignal": true; }; "moreActions": { "alias": "moreActions"; "required": false; "isSignal": true; }; "interactiveOffset": { "alias": "interactiveOffset"; "required": false; "isSignal": true; }; "title": { "alias": "title"; "required": false; "isSignal": true; }; "subtitle": { "alias": "subtitle"; "required": false; "isSignal": true; }; "showDivider": { "alias": "showDivider"; "required": false; "isSignal": true; }; "dividerSpacing": { "alias": "dividerSpacing"; "required": false; "isSignal": true; }; }, { "itemClick": "itemClick"; "moreButtonClick": "moreButtonClick"; }, never, ["[content-leading]", "[content-main]", "*", "[content-trailing]"], true, [{ directive: typeof DsMobileLongPressDirective; inputs: {}; outputs: { "longPress": "longPress"; }; }]>;
2683
2709
  }
2684
2710
 
2685
2711
  /**
@@ -2825,7 +2851,7 @@ declare class DsMobileInteractiveListItemPostComponent {
2825
2851
  /**
2826
2852
  * Avatar type
2827
2853
  */
2828
- avatarType: _angular_core.InputSignal<"initials" | "photo" | "icon">;
2854
+ avatarType: _angular_core.InputSignal<"photo" | "initials" | "icon">;
2829
2855
  /**
2830
2856
  * Avatar photo source (for photo type)
2831
2857
  */
@@ -2862,6 +2888,11 @@ declare class DsMobileInteractiveListItemPostComponent {
2862
2888
  * @default true
2863
2889
  */
2864
2890
  enableLongPress: _angular_core.InputSignal<boolean>;
2891
+ /**
2892
+ * Unified toggle for contextual actions (more button + long press).
2893
+ * When set, takes precedence over `enableLongPress`.
2894
+ */
2895
+ moreActions: _angular_core.InputSignal<boolean | undefined>;
2865
2896
  /**
2866
2897
  * Emits when the post card is clicked (if clickable)
2867
2898
  */
@@ -2879,7 +2910,7 @@ declare class DsMobileInteractiveListItemPostComponent {
2879
2910
  handleLongPress(): void;
2880
2911
  handleMoreButtonClick(event: Event): void;
2881
2912
  static ɵfac: _angular_core.ɵɵFactoryDeclaration<DsMobileInteractiveListItemPostComponent, never>;
2882
- static ɵcmp: _angular_core.ɵɵComponentDeclaration<DsMobileInteractiveListItemPostComponent, "ds-mobile-interactive-list-item-post", never, { "authorName": { "alias": "authorName"; "required": true; "isSignal": true; }; "authorRole": { "alias": "authorRole"; "required": true; "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; }; "avatarIconName": { "alias": "avatarIconName"; "required": false; "isSignal": true; }; "showBadge": { "alias": "showBadge"; "required": false; "isSignal": true; }; "variant": { "alias": "variant"; "required": false; "isSignal": true; }; "align": { "alias": "align"; "required": false; "isSignal": true; }; "clickable": { "alias": "clickable"; "required": false; "isSignal": true; }; "enableLongPress": { "alias": "enableLongPress"; "required": false; "isSignal": true; }; }, { "postClick": "postClick"; "commentClick": "commentClick"; "longPress": "longPress"; }, never, ["post-menu", "post-content", "post-actions"], true, never>;
2913
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<DsMobileInteractiveListItemPostComponent, "ds-mobile-interactive-list-item-post", never, { "authorName": { "alias": "authorName"; "required": true; "isSignal": true; }; "authorRole": { "alias": "authorRole"; "required": true; "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; }; "avatarIconName": { "alias": "avatarIconName"; "required": false; "isSignal": true; }; "showBadge": { "alias": "showBadge"; "required": false; "isSignal": true; }; "variant": { "alias": "variant"; "required": false; "isSignal": true; }; "align": { "alias": "align"; "required": false; "isSignal": true; }; "clickable": { "alias": "clickable"; "required": false; "isSignal": true; }; "enableLongPress": { "alias": "enableLongPress"; "required": false; "isSignal": true; }; "moreActions": { "alias": "moreActions"; "required": false; "isSignal": true; }; }, { "postClick": "postClick"; "commentClick": "commentClick"; "longPress": "longPress"; }, never, ["post-menu", "post-content", "post-actions"], true, never>;
2883
2914
  }
2884
2915
  /**
2885
2916
  * PostContentComponent
@@ -3089,6 +3120,11 @@ declare class DsMobileInteractiveListItemInquiryComponent {
3089
3120
  * @default true
3090
3121
  */
3091
3122
  enableLongPress: _angular_core.InputSignal<boolean>;
3123
+ /**
3124
+ * Unified toggle for contextual actions (more button + long press).
3125
+ * When set, takes precedence over `enableLongPress`.
3126
+ */
3127
+ moreActions: _angular_core.InputSignal<boolean | undefined>;
3092
3128
  /**
3093
3129
  * Emits when the inquiry item is clicked (if clickable)
3094
3130
  */
@@ -3105,7 +3141,7 @@ declare class DsMobileInteractiveListItemInquiryComponent {
3105
3141
  handleLongPress(): void;
3106
3142
  handleMoreButtonClick(event: Event): void;
3107
3143
  static ɵfac: _angular_core.ɵɵFactoryDeclaration<DsMobileInteractiveListItemInquiryComponent, never>;
3108
- static ɵcmp: _angular_core.ɵɵComponentDeclaration<DsMobileInteractiveListItemInquiryComponent, "ds-mobile-interactive-list-item-inquiry", never, { "title": { "alias": "title"; "required": true; "isSignal": true; }; "description": { "alias": "description"; "required": false; "isSignal": true; }; "status": { "alias": "status"; "required": false; "isSignal": true; }; "statusLabel": { "alias": "statusLabel"; "required": false; "isSignal": true; }; "timestamp": { "alias": "timestamp"; "required": true; "isSignal": true; }; "iconName": { "alias": "iconName"; "required": false; "isSignal": true; }; "iconColor": { "alias": "iconColor"; "required": false; "isSignal": true; }; "variant": { "alias": "variant"; "required": false; "isSignal": true; }; "align": { "alias": "align"; "required": false; "isSignal": true; }; "clickable": { "alias": "clickable"; "required": false; "isSignal": true; }; "showChevron": { "alias": "showChevron"; "required": false; "isSignal": true; }; "enableLongPress": { "alias": "enableLongPress"; "required": false; "isSignal": true; }; }, { "inquiryClick": "inquiryClick"; "longPress": "longPress"; }, never, never, true, never>;
3144
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<DsMobileInteractiveListItemInquiryComponent, "ds-mobile-interactive-list-item-inquiry", never, { "title": { "alias": "title"; "required": true; "isSignal": true; }; "description": { "alias": "description"; "required": false; "isSignal": true; }; "status": { "alias": "status"; "required": false; "isSignal": true; }; "statusLabel": { "alias": "statusLabel"; "required": false; "isSignal": true; }; "timestamp": { "alias": "timestamp"; "required": true; "isSignal": true; }; "iconName": { "alias": "iconName"; "required": false; "isSignal": true; }; "iconColor": { "alias": "iconColor"; "required": false; "isSignal": true; }; "variant": { "alias": "variant"; "required": false; "isSignal": true; }; "align": { "alias": "align"; "required": false; "isSignal": true; }; "clickable": { "alias": "clickable"; "required": false; "isSignal": true; }; "showChevron": { "alias": "showChevron"; "required": false; "isSignal": true; }; "enableLongPress": { "alias": "enableLongPress"; "required": false; "isSignal": true; }; "moreActions": { "alias": "moreActions"; "required": false; "isSignal": true; }; }, { "inquiryClick": "inquiryClick"; "longPress": "longPress"; }, never, never, true, never>;
3109
3145
  }
3110
3146
 
3111
3147
  /**
@@ -3153,7 +3189,7 @@ declare class DsMobileInteractiveListItemMessageComponent {
3153
3189
  /**
3154
3190
  * Avatar type
3155
3191
  */
3156
- avatarType: _angular_core.InputSignal<"initials" | "photo" | "icon">;
3192
+ avatarType: _angular_core.InputSignal<"photo" | "initials" | "icon">;
3157
3193
  /**
3158
3194
  * Avatar photo source (for photo type)
3159
3195
  */
@@ -3328,6 +3364,11 @@ declare class DsMobileInteractiveListItemBookingComponent {
3328
3364
  * @default true
3329
3365
  */
3330
3366
  enableLongPress: _angular_core.InputSignal<boolean>;
3367
+ /**
3368
+ * Unified toggle for contextual actions (more button + long press).
3369
+ * When set, takes precedence over `enableLongPress`.
3370
+ */
3371
+ moreActions: _angular_core.InputSignal<boolean | undefined>;
3331
3372
  /**
3332
3373
  * Emits when the booking item is clicked (if clickable)
3333
3374
  */
@@ -3344,7 +3385,7 @@ declare class DsMobileInteractiveListItemBookingComponent {
3344
3385
  handleLongPress(): void;
3345
3386
  handleMoreButtonClick(event: Event): void;
3346
3387
  static ɵfac: _angular_core.ɵɵFactoryDeclaration<DsMobileInteractiveListItemBookingComponent, never>;
3347
- static ɵcmp: _angular_core.ɵɵComponentDeclaration<DsMobileInteractiveListItemBookingComponent, "ds-mobile-interactive-list-item-booking", never, { "thumbnail": { "alias": "thumbnail"; "required": false; "isSignal": true; }; "facilityTitle": { "alias": "facilityTitle"; "required": true; "isSignal": true; }; "description": { "alias": "description"; "required": false; "isSignal": true; }; "bookingDate": { "alias": "bookingDate"; "required": false; "isSignal": true; }; "bookingTime": { "alias": "bookingTime"; "required": false; "isSignal": true; }; "availabilityStatus": { "alias": "availabilityStatus"; "required": false; "isSignal": true; }; "statusLabel": { "alias": "statusLabel"; "required": false; "isSignal": true; }; "variant": { "alias": "variant"; "required": false; "isSignal": true; }; "align": { "alias": "align"; "required": false; "isSignal": true; }; "clickable": { "alias": "clickable"; "required": false; "isSignal": true; }; "showChevron": { "alias": "showChevron"; "required": false; "isSignal": true; }; "enableLongPress": { "alias": "enableLongPress"; "required": false; "isSignal": true; }; }, { "bookingClick": "bookingClick"; "longPress": "longPress"; }, never, never, true, never>;
3388
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<DsMobileInteractiveListItemBookingComponent, "ds-mobile-interactive-list-item-booking", never, { "thumbnail": { "alias": "thumbnail"; "required": false; "isSignal": true; }; "facilityTitle": { "alias": "facilityTitle"; "required": true; "isSignal": true; }; "description": { "alias": "description"; "required": false; "isSignal": true; }; "bookingDate": { "alias": "bookingDate"; "required": false; "isSignal": true; }; "bookingTime": { "alias": "bookingTime"; "required": false; "isSignal": true; }; "availabilityStatus": { "alias": "availabilityStatus"; "required": false; "isSignal": true; }; "statusLabel": { "alias": "statusLabel"; "required": false; "isSignal": true; }; "variant": { "alias": "variant"; "required": false; "isSignal": true; }; "align": { "alias": "align"; "required": false; "isSignal": true; }; "clickable": { "alias": "clickable"; "required": false; "isSignal": true; }; "showChevron": { "alias": "showChevron"; "required": false; "isSignal": true; }; "enableLongPress": { "alias": "enableLongPress"; "required": false; "isSignal": true; }; "moreActions": { "alias": "moreActions"; "required": false; "isSignal": true; }; }, { "bookingClick": "bookingClick"; "longPress": "longPress"; }, never, never, true, never>;
3348
3389
  }
3349
3390
 
3350
3391
  interface TabConfig {
@@ -4535,6 +4576,13 @@ declare abstract class MobileModalBase implements OnInit, OnDestroy {
4535
4576
  * @default false
4536
4577
  */
4537
4578
  isAutoHeight: _angular_core.InputSignal<boolean>;
4579
+ /**
4580
+ * Controls how modal content behaves when the keyboard opens.
4581
+ * - 'follow': content is pushed to follow keyboard movement
4582
+ * - 'overlay': keyboard/footer overlays lower content (no auto scroll push)
4583
+ * @default 'follow'
4584
+ */
4585
+ keyboardContentBehavior: _angular_core.InputSignal<"overlay" | "follow">;
4538
4586
  /**
4539
4587
  * Emitted when modal is closed
4540
4588
  */
@@ -4581,8 +4629,16 @@ declare abstract class MobileModalBase implements OnInit, OnDestroy {
4581
4629
  * @protected
4582
4630
  */
4583
4631
  protected isThisModal(modalElement: HTMLIonModalElement): boolean;
4632
+ /**
4633
+ * Returns true when keyboard should overlay content without push-scrolling.
4634
+ */
4635
+ private isOverlayBehavior;
4636
+ /**
4637
+ * Computes scroll bottom inset for current keyboard behavior.
4638
+ */
4639
+ private getScrollPadding;
4584
4640
  static ɵfac: _angular_core.ɵɵFactoryDeclaration<MobileModalBase, never>;
4585
- 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; }; "contentPadding": { "alias": "contentPadding"; "required": false; "isSignal": true; }; "isAutoHeight": { "alias": "isAutoHeight"; "required": false; "isSignal": true; }; }, { "closed": "closed"; "keyboardWillShow": "keyboardWillShow"; "keyboardWillHide": "keyboardWillHide"; }, never, never, true, never>;
4641
+ 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; }; "contentPadding": { "alias": "contentPadding"; "required": false; "isSignal": true; }; "isAutoHeight": { "alias": "isAutoHeight"; "required": false; "isSignal": true; }; "keyboardContentBehavior": { "alias": "keyboardContentBehavior"; "required": false; "isSignal": true; }; }, { "closed": "closed"; "keyboardWillShow": "keyboardWillShow"; "keyboardWillHide": "keyboardWillHide"; }, never, never, true, never>;
4586
4642
  }
4587
4643
 
4588
4644
  /**
@@ -4597,6 +4653,7 @@ declare abstract class MobileModalBase implements OnInit, OnDestroy {
4597
4653
  * - Default loading and error state templates (with override capability)
4598
4654
  * - Fixed bottom component support (e.g., message composer)
4599
4655
  * - Automatic keyboard handling
4656
+ * - Configurable keyboard content behavior (`follow` or `overlay`)
4600
4657
  * - Safe area support
4601
4658
  *
4602
4659
  * **Slot Structure:**
@@ -4639,7 +4696,8 @@ declare abstract class MobileModalBase implements OnInit, OnDestroy {
4639
4696
  * <ds-mobile-modal-base
4640
4697
  * headerTitle="Create Inquiry"
4641
4698
  * [hasFixedBottom]="true"
4642
- * [enableKeyboardHandling]="true">
4699
+ * [enableKeyboardHandling]="true"
4700
+ * [keyboardContentBehavior]="'overlay'">
4643
4701
  *
4644
4702
  * <div class="content">
4645
4703
  * <input type="text" placeholder="Type something..." />
@@ -4694,6 +4752,10 @@ declare class DsMobileModalBaseComponent extends MobileModalBase implements OnIn
4694
4752
  * Determine if header should be shown based on showHeader input and content detection
4695
4753
  */
4696
4754
  shouldShowHeader(): boolean;
4755
+ /**
4756
+ * Check whether header-leading slot has actual projected content.
4757
+ */
4758
+ hasHeaderLeadingContent(): boolean;
4697
4759
  /**
4698
4760
  * Check if a content child slot has actual content
4699
4761
  */
@@ -4831,6 +4893,10 @@ declare class DsMobilePostDetailModalComponent implements OnInit, AfterViewInit
4831
4893
  currentUserInitialsInput: string;
4832
4894
  loading: boolean;
4833
4895
  error: string | undefined;
4896
+ onTogglePostLike?: (payload: {
4897
+ postId: string;
4898
+ active: boolean;
4899
+ }) => void;
4834
4900
  onSubmitComment?: (payload: {
4835
4901
  postId: string;
4836
4902
  text: string;
@@ -4891,6 +4957,13 @@ declare class DsMobilePostDetailModalComponent implements OnInit, AfterViewInit
4891
4957
  * Handle edit comment
4892
4958
  */
4893
4959
  handleEditComment(comment: CommentData): void;
4960
+ /**
4961
+ * Handle post like/unlike toggle
4962
+ */
4963
+ handlePostLikeToggle(ev: {
4964
+ active: boolean;
4965
+ count: number;
4966
+ }): void;
4894
4967
  /**
4895
4968
  * Handle comment like/unlike toggle
4896
4969
  * @param comment The comment being liked/unliked
@@ -4913,7 +4986,7 @@ declare class DsMobilePostDetailModalComponent implements OnInit, AfterViewInit
4913
4986
  */
4914
4987
  handleCommentLongPress(authorName: string, content: string, isOwnComment: boolean): Promise<void>;
4915
4988
  static ɵfac: _angular_core.ɵɵFactoryDeclaration<DsMobilePostDetailModalComponent, never>;
4916
- 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>;
4989
+ 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; }; "onTogglePostLike": { "alias": "onTogglePostLike"; "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>;
4917
4990
  }
4918
4991
 
4919
4992
  /**
@@ -5047,6 +5120,10 @@ declare class DsMobilePostDetailModalService extends BaseModalService {
5047
5120
  open(postData: PostDetailData, options?: {
5048
5121
  loading?: boolean;
5049
5122
  error?: string;
5123
+ onTogglePostLike?: (payload: {
5124
+ postId: string;
5125
+ active: boolean;
5126
+ }) => void;
5050
5127
  onSubmitComment?: (payload: {
5051
5128
  postId: string;
5052
5129
  text: string;
@@ -5505,7 +5582,7 @@ declare class DsMobileChatModalComponent implements OnInit, AfterViewInit {
5505
5582
  participant: _angular_core.WritableSignal<ChatParticipant>;
5506
5583
  messages: _angular_core.WritableSignal<ChatMessage[]>;
5507
5584
  currentUserInitials: _angular_core.WritableSignal<string>;
5508
- currentUserAvatarType: _angular_core.WritableSignal<"initials" | "photo" | "icon">;
5585
+ currentUserAvatarType: _angular_core.WritableSignal<"photo" | "initials" | "icon">;
5509
5586
  currentUserAvatarSrc: _angular_core.WritableSignal<string>;
5510
5587
  autoFocus: _angular_core.WritableSignal<boolean>;
5511
5588
  /**
@@ -5763,6 +5840,7 @@ interface NewInquiryData {
5763
5840
  declare class DsMobileNewInquiryModalComponent implements OnInit, AfterViewInit {
5764
5841
  private modalController;
5765
5842
  titleInputRef?: ElementRef<HTMLElement>;
5843
+ descriptionInputRef?: ElementRef<HTMLElement>;
5766
5844
  titleInput?: DsTextareaComponent;
5767
5845
  fileInput?: ElementRef<HTMLInputElement>;
5768
5846
  /**
@@ -5815,10 +5893,18 @@ declare class DsMobileNewInquiryModalComponent implements OnInit, AfterViewInit
5815
5893
  * Auto-resize the title textarea based on content
5816
5894
  */
5817
5895
  private autoResizeTitleTextarea;
5896
+ /**
5897
+ * Auto-resize the description textarea based on content
5898
+ */
5899
+ private autoResizeDescriptionTextarea;
5818
5900
  /**
5819
5901
  * Handle title change with auto-resize
5820
5902
  */
5821
5903
  handleTitleChange(value: string): void;
5904
+ /**
5905
+ * Handle description change with auto-resize
5906
+ */
5907
+ handleDescriptionChange(value: string): void;
5822
5908
  /**
5823
5909
  * Validate form fields
5824
5910
  */
@@ -6167,6 +6253,7 @@ declare class DsMobileFacilityCreationModalComponent implements OnInit, AfterVie
6167
6253
  private modalController;
6168
6254
  private bottomSheetService;
6169
6255
  titleInputRef?: ElementRef<HTMLElement>;
6256
+ descriptionInputRef?: ElementRef<HTMLElement>;
6170
6257
  titleInput?: DsTextareaComponent;
6171
6258
  fileInput?: ElementRef<HTMLInputElement>;
6172
6259
  /**
@@ -6235,10 +6322,18 @@ declare class DsMobileFacilityCreationModalComponent implements OnInit, AfterVie
6235
6322
  * Auto-resize the title textarea based on content
6236
6323
  */
6237
6324
  private autoResizeTitleTextarea;
6325
+ /**
6326
+ * Auto-resize the description textarea based on content
6327
+ */
6328
+ private autoResizeDescriptionTextarea;
6238
6329
  /**
6239
6330
  * Handle title change with auto-resize
6240
6331
  */
6241
6332
  handleTitleChange(value: string): void;
6333
+ /**
6334
+ * Handle description change with auto-resize
6335
+ */
6336
+ handleDescriptionChange(value: string): void;
6242
6337
  /**
6243
6338
  * Validate form fields
6244
6339
  */
@@ -7096,7 +7191,7 @@ declare class DsMobileHandbookFolderMiniComponent {
7096
7191
  * ```
7097
7192
  */
7098
7193
  declare class DsTextInputComponent implements ControlValueAccessor {
7099
- type: _angular_core.InputSignal<"search" | "text" | "url" | "email" | "tel" | "password">;
7194
+ type: _angular_core.InputSignal<"search" | "text" | "email" | "tel" | "url" | "password">;
7100
7195
  placeholder: _angular_core.InputSignal<string>;
7101
7196
  disabled: _angular_core.InputSignal<boolean>;
7102
7197
  readonly: _angular_core.InputSignal<boolean>;
@@ -7104,7 +7199,7 @@ declare class DsTextInputComponent implements ControlValueAccessor {
7104
7199
  hasError: _angular_core.InputSignal<boolean>;
7105
7200
  errorMessage: _angular_core.InputSignal<string>;
7106
7201
  autocomplete: _angular_core.InputSignal<string>;
7107
- inputmode: _angular_core.InputSignal<"search" | "text" | "url" | "numeric" | "email" | "tel" | undefined>;
7202
+ inputmode: _angular_core.InputSignal<"search" | "text" | "email" | "tel" | "url" | "numeric" | undefined>;
7108
7203
  autoClearError: _angular_core.InputSignal<boolean>;
7109
7204
  validator: _angular_core.InputSignal<((value: string) => boolean) | null>;
7110
7205
  valueChange: _angular_core.OutputEmitterRef<string>;
@@ -7469,7 +7564,7 @@ declare class UserService {
7469
7564
  private _avatarSrc;
7470
7565
  private _profileMenuItems;
7471
7566
  readonly avatarInitials: _angular_core.Signal<string>;
7472
- readonly avatarType: _angular_core.Signal<"initials" | "photo" | "icon">;
7567
+ readonly avatarType: _angular_core.Signal<"photo" | "initials" | "icon">;
7473
7568
  readonly avatarSrc: _angular_core.Signal<string>;
7474
7569
  readonly profileMenuItems: _angular_core.Signal<ActionGroup[] | undefined>;
7475
7570
  private profileActionSelectedSubject;
@@ -7687,6 +7782,7 @@ declare class MobileHomePageComponent implements OnInit {
7687
7782
  private postsService;
7688
7783
  private postModal;
7689
7784
  private trackingPermissionService;
7785
+ private bottomSheet;
7690
7786
  pageComponent: DsMobilePageMainComponent;
7691
7787
  recentPosts: _angular_core.Signal<_propbinder_mobile_design.Post[]>;
7692
7788
  private allInquiries;
@@ -7697,13 +7793,14 @@ declare class MobileHomePageComponent implements OnInit {
7697
7793
  status: "open";
7698
7794
  timestamp: string;
7699
7795
  }[]>;
7700
- constructor(router: Router, userService: UserService, postsService: PostsService, postModal: DsMobilePostDetailModalService, trackingPermissionService: TrackingPermissionService);
7796
+ constructor(router: Router, userService: UserService, postsService: PostsService, postModal: DsMobilePostDetailModalService, trackingPermissionService: TrackingPermissionService, bottomSheet: DsMobileBottomSheetService);
7701
7797
  ngOnInit(): void;
7702
7798
  handleRefresh(event: any): void;
7703
7799
  openPost(postId: string, focusComment?: boolean): Promise<void>;
7704
7800
  openInquiryDetail(inquiryId: string): void;
7705
7801
  navigateToCommunity(): void;
7706
7802
  navigateToInquiries(): void;
7803
+ handlePostLongPress(postId: string): Promise<void>;
7707
7804
  static ɵfac: _angular_core.ɵɵFactoryDeclaration<MobileHomePageComponent, never>;
7708
7805
  static ɵcmp: _angular_core.ɵɵComponentDeclaration<MobileHomePageComponent, "app-home-page", never, {}, {}, never, never, true, never>;
7709
7806
  }
@@ -7834,9 +7931,10 @@ interface FacilityDetail {
7834
7931
  declare class MobileBookingPageComponent {
7835
7932
  private facilityModal;
7836
7933
  private facilityCreationModal;
7934
+ userService: UserService;
7837
7935
  pageComponent: DsMobilePageMainComponent;
7838
7936
  bookingsSwiper?: DsMobileSwiperComponent;
7839
- constructor(facilityModal: DsMobileFacilityDetailModalService, facilityCreationModal: DsMobileFacilityCreationModalService);
7937
+ constructor(facilityModal: DsMobileFacilityDetailModalService, facilityCreationModal: DsMobileFacilityCreationModalService, userService: UserService);
7840
7938
  activeBookings: _angular_core.WritableSignal<FacilityDetail[]>;
7841
7939
  availableFacilities: _angular_core.WritableSignal<FacilityDetail[]>;
7842
7940
  handleRefresh(event: any): void;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@propbinder/mobile-design",
3
- "version": "0.2.46",
3
+ "version": "0.2.48",
4
4
  "peerDependencies": {
5
5
  "@angular/common": "^20.3.0 || ^21.0.0",
6
6
  "@angular/core": "^20.3.0 || ^21.0.0"
package/styles/ionic.css CHANGED
@@ -76,14 +76,28 @@
76
76
  2. These CSS variables below
77
77
  ============================================ */
78
78
 
79
+ /* Normalized safe-area variables consumed by app layout styles.
80
+ Defaults to env() and can be overridden at runtime from SafeArea plugin. */
81
+ --app-safe-top: env(safe-area-inset-top, 0px);
82
+ --app-safe-right: env(safe-area-inset-right, 0px);
83
+ --app-safe-bottom: env(safe-area-inset-bottom, 0px);
84
+ --app-safe-left: env(safe-area-inset-left, 0px);
85
+
79
86
  /* Sheet/modal top offset - positions content below status bar area */
80
87
  /* Uses max() to ensure at least 32px offset even on devices without notch */
81
88
  /* +12px adds breathing room below the status bar */
82
- --app-sheet-top-offset: calc(max(32px, env(safe-area-inset-top, 32px)) + 12px);
89
+ --app-sheet-top-offset: calc(max(32px, var(--app-safe-top, 32px)) + 12px);
83
90
 
84
91
  /* Header top offset - used to fine-tune header position on iOS with overlay: true */
85
92
  /* On web (no safe area), this should be 0px. On iOS, it compensates for status bar overlap */
86
- --app-header-top-offset: max(0px, calc(env(safe-area-inset-top, 0px) - 16px));
93
+ --app-header-top-offset: max(0px, calc(var(--app-safe-top, 0px) - 16px));
94
+ }
95
+
96
+ /* Android-only top safe-area tuning:
97
+ add 8px extra breathing room above header/sheet content. */
98
+ .plt-android {
99
+ --app-sheet-top-offset: calc(max(32px, var(--app-safe-top, 32px)) + 20px);
100
+ --app-header-top-offset: max(0px, calc(var(--app-safe-top, 0px) - 8px));
87
101
  }
88
102
 
89
103
  /* Global Styles for Mobile App */
@@ -197,14 +211,14 @@ ion-content::part(scroll)::-webkit-scrollbar {
197
211
 
198
212
  /* iOS-specific: Enable native scroll overshoot/bounce effect */
199
213
  .plt-ios ion-content::part(scroll) {
200
- overscroll-behavior-y: contain;
214
+ overscroll-behavior-y: auto;
201
215
  /* iOS: Set scroll container background to match brand secondary */
202
216
  /* This prevents white background from showing when header fades on scroll */
203
217
  background: var(--color-header-surface) !important;
204
218
  }
205
219
 
206
220
  .plt-ios .ion-page {
207
- overscroll-behavior-y: contain;
221
+ overscroll-behavior-y: auto;
208
222
  /* Set page background to brand secondary to match header on iOS */
209
223
  background: var(--color-header-surface) !important;
210
224
  }
@@ -278,7 +292,7 @@ ion-spinner {
278
292
  ion-modal:not(.ds-bottom-sheet) {
279
293
  --background: var(--color-background-neutral-primary);
280
294
  --border-radius: 16px;
281
- --box-shadow: var(--box-shadow-lg);
295
+ --box-shadow: none;
282
296
  /* Prevent modal from resizing when keyboard appears */
283
297
  height: 100% !important;
284
298
  max-height: 100vh !important;
@@ -297,6 +311,11 @@ ion-modal ion-content {
297
311
  height: 100% !important;
298
312
  }
299
313
 
314
+ /* Keep neutral cursor over dismissable backdrop areas */
315
+ ion-modal::part(backdrop) {
316
+ cursor: default !important;
317
+ }
318
+
300
319
  ion-action-sheet {
301
320
  --background: var(--color-background-neutral-primary);
302
321
  --color: var(--text-color-default-primary);
@@ -325,7 +344,7 @@ ion-toast {
325
344
  /* Base bottom sheet styling */
326
345
  .ds-bottom-sheet {
327
346
  --border-radius: 16px;
328
- --box-shadow: 0px -2px 24px rgba(0, 0, 0, 0.12);
347
+ --box-shadow: none;
329
348
  --backdrop-opacity: 0.4;
330
349
  transition: --backdrop-opacity 0.3s ease;
331
350
  /* Modal at top:0 so backdrop covers full screen including status bar */
@@ -461,7 +480,7 @@ ion-toast {
461
480
  .ds-mobile-modal {
462
481
  --background: var(--color-background-neutral-primary, #ffffff);
463
482
  --border-radius: 16px;
464
- --box-shadow: 0 10px 40px rgba(0, 0, 0, 0.15);
483
+ --box-shadow: none;
465
484
  --max-width: 640px;
466
485
  --width: 100%;
467
486
  }
@@ -156,6 +156,7 @@
156
156
  :host ion-content::part(scroll) {
157
157
  display: flex;
158
158
  flex-direction: column;
159
+ min-height: 100%;
159
160
  -webkit-overflow-scrolling: touch;
160
161
  /* Note: Do NOT set overscroll-behavior-y here as it prevents ion-refresher from working */
161
162
  /* overscroll-behavior on ion-app is sufficient to block native browser pull-to-refresh */
@@ -207,13 +208,13 @@
207
208
  width: 100%;
208
209
  position: relative;
209
210
  z-index: 20;
210
- /* Flex child that grows to fill available space */
211
+ /* Fill remaining viewport height while keeping ion-content as scroller */
211
212
  flex: 1;
212
213
  display: flex;
213
214
  flex-direction: column;
214
215
  background: var(--color-background-neutral-primary);
215
216
  border-radius: 24px 24px 0 0;
216
- overflow: scroll;
217
+ overflow: visible;
217
218
 
218
219
  /* Visual styling for iOS overshoot - extend background below using box-shadow */
219
220
  transform: translateZ(0);
@@ -226,13 +227,10 @@
226
227
  padding-left: var(--content-wrapper-padding, 20px);
227
228
  padding-right: var(--content-wrapper-padding, 20px);
228
229
  /* Bottom padding ALWAYS preserved for safe area and tab bar */
229
- padding-bottom: calc(var(--mobile-content-spacing) + var(--mobile-tab-bar-height) + env(safe-area-inset-bottom, 0px));
230
+ padding-bottom: calc(var(--mobile-content-spacing) + var(--mobile-tab-bar-height) + var(--app-safe-bottom, 0px));
230
231
  }
231
232
 
232
233
  :host .content-inner {
233
- /* Grow to fill parent flex container */
234
- flex: 1;
235
-
236
234
  /* Mobile-first: max-width 640px, centered */
237
235
  max-width: 640px;
238
236
  margin: 0 auto;