@propbinder/mobile-design 0.2.46 → 0.2.47

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 {
@@ -911,7 +918,7 @@ declare class WhitelabelService {
911
918
  readonly logoUrl: _angular_core.Signal<string>;
912
919
  readonly logoMarkUrl: _angular_core.Signal<string>;
913
920
  readonly logoAlt: _angular_core.Signal<string>;
914
- readonly logoSize: _angular_core.Signal<"sm" | "md" | "lg" | "xl">;
921
+ readonly logoSize: _angular_core.Signal<"md" | "sm" | "lg" | "xl">;
915
922
  readonly logoHeight: _angular_core.Signal<number>;
916
923
  readonly appIconSurface: _angular_core.Signal<string>;
917
924
  readonly appIconContent: _angular_core.Signal<string>;
@@ -1582,6 +1589,14 @@ declare class DsMobileCommentComponent {
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
  /**
@@ -2580,7 +2595,7 @@ declare class DsMobileListItemComponent implements AfterViewInit {
2580
2595
  * - 'center' - Align to center
2581
2596
  * - 'bottom' - Align to bottom
2582
2597
  */
2583
- align: _angular_core.InputSignal<"center" | "top" | "bottom">;
2598
+ align: _angular_core.InputSignal<"top" | "center" | "bottom">;
2584
2599
  /**
2585
2600
  * Whether the list item is interactive (clickable and long-pressable)
2586
2601
  * When true, adds interactive background, cursor pointer, and touch handlers
@@ -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
  /**
@@ -2850,7 +2876,7 @@ declare class DsMobileInteractiveListItemPostComponent {
2850
2876
  * - 'center' - Align to center
2851
2877
  * - 'bottom' - Align to bottom
2852
2878
  */
2853
- align: _angular_core.InputSignal<"center" | "top" | "bottom">;
2879
+ align: _angular_core.InputSignal<"top" | "center" | "bottom">;
2854
2880
  /**
2855
2881
  * Whether the post card is clickable
2856
2882
  */
@@ -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
@@ -3073,7 +3104,7 @@ declare class DsMobileInteractiveListItemInquiryComponent {
3073
3104
  * - 'center' - Align to center
3074
3105
  * - 'bottom' - Align to bottom
3075
3106
  */
3076
- align: _angular_core.InputSignal<"center" | "top" | "bottom">;
3107
+ align: _angular_core.InputSignal<"top" | "center" | "bottom">;
3077
3108
  /**
3078
3109
  * Whether the inquiry item is clickable
3079
3110
  */
@@ -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
  /**
@@ -3172,7 +3208,7 @@ declare class DsMobileInteractiveListItemMessageComponent {
3172
3208
  * - 'center' - Align to center
3173
3209
  * - 'bottom' - Align to bottom
3174
3210
  */
3175
- align: _angular_core.InputSignal<"center" | "top" | "bottom">;
3211
+ align: _angular_core.InputSignal<"top" | "center" | "bottom">;
3176
3212
  /**
3177
3213
  * Emits when the message item is clicked
3178
3214
  */
@@ -3312,7 +3348,7 @@ declare class DsMobileInteractiveListItemBookingComponent {
3312
3348
  * - 'center' - Align to center
3313
3349
  * - 'bottom' - Align to bottom
3314
3350
  */
3315
- align: _angular_core.InputSignal<"center" | "top" | "bottom">;
3351
+ align: _angular_core.InputSignal<"top" | "center" | "bottom">;
3316
3352
  /**
3317
3353
  * Whether the booking item is clickable
3318
3354
  */
@@ -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<"follow" | "overlay">;
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
  */
@@ -5130,7 +5192,7 @@ declare class DsMobileCardInlineComponent {
5130
5192
  * - 'default' - Column layout with standard padding (gap: 12px, padding: 10px 12px)
5131
5193
  * - 'compact' - Row layout with reduced padding (gap: 8px, padding: 10px)
5132
5194
  */
5133
- variant: _angular_core.InputSignal<"default" | "compact">;
5195
+ variant: _angular_core.InputSignal<"compact" | "default">;
5134
5196
  /**
5135
5197
  * Whether the card is disabled
5136
5198
  * Disables all interactions and reduces opacity
@@ -5193,7 +5255,7 @@ declare class DsMobileCardInlineBannerComponent {
5193
5255
  * - 'default' - Standard padding and column layout
5194
5256
  * - 'compact' - Reduced padding and row layout
5195
5257
  */
5196
- layout: _angular_core.InputSignal<"default" | "compact">;
5258
+ layout: _angular_core.InputSignal<"compact" | "default">;
5197
5259
  /**
5198
5260
  * Emits when the banner is clicked
5199
5261
  */
@@ -5244,7 +5306,7 @@ declare class DsMobileCardInlineContactComponent {
5244
5306
  * - 'default' - Standard padding and column layout
5245
5307
  * - 'compact' - Reduced padding and row layout
5246
5308
  */
5247
- layout: _angular_core.InputSignal<"default" | "compact">;
5309
+ layout: _angular_core.InputSignal<"compact" | "default">;
5248
5310
  /**
5249
5311
  * Whether the contact item is clickable
5250
5312
  */
@@ -5301,7 +5363,7 @@ declare class DsMobileCardInlineFileComponent {
5301
5363
  * - 'default' - Standard padding and column layout
5302
5364
  * - 'compact' - Reduced padding and row layout
5303
5365
  */
5304
- layout: _angular_core.InputSignal<"default" | "compact">;
5366
+ layout: _angular_core.InputSignal<"compact" | "default">;
5305
5367
  /**
5306
5368
  * Optional URL to open when clicked
5307
5369
  * If provided, clicking the card will open this URL in a new tab
@@ -5763,6 +5825,7 @@ interface NewInquiryData {
5763
5825
  declare class DsMobileNewInquiryModalComponent implements OnInit, AfterViewInit {
5764
5826
  private modalController;
5765
5827
  titleInputRef?: ElementRef<HTMLElement>;
5828
+ descriptionInputRef?: ElementRef<HTMLElement>;
5766
5829
  titleInput?: DsTextareaComponent;
5767
5830
  fileInput?: ElementRef<HTMLInputElement>;
5768
5831
  /**
@@ -5815,10 +5878,18 @@ declare class DsMobileNewInquiryModalComponent implements OnInit, AfterViewInit
5815
5878
  * Auto-resize the title textarea based on content
5816
5879
  */
5817
5880
  private autoResizeTitleTextarea;
5881
+ /**
5882
+ * Auto-resize the description textarea based on content
5883
+ */
5884
+ private autoResizeDescriptionTextarea;
5818
5885
  /**
5819
5886
  * Handle title change with auto-resize
5820
5887
  */
5821
5888
  handleTitleChange(value: string): void;
5889
+ /**
5890
+ * Handle description change with auto-resize
5891
+ */
5892
+ handleDescriptionChange(value: string): void;
5822
5893
  /**
5823
5894
  * Validate form fields
5824
5895
  */
@@ -6167,6 +6238,7 @@ declare class DsMobileFacilityCreationModalComponent implements OnInit, AfterVie
6167
6238
  private modalController;
6168
6239
  private bottomSheetService;
6169
6240
  titleInputRef?: ElementRef<HTMLElement>;
6241
+ descriptionInputRef?: ElementRef<HTMLElement>;
6170
6242
  titleInput?: DsTextareaComponent;
6171
6243
  fileInput?: ElementRef<HTMLInputElement>;
6172
6244
  /**
@@ -6235,10 +6307,18 @@ declare class DsMobileFacilityCreationModalComponent implements OnInit, AfterVie
6235
6307
  * Auto-resize the title textarea based on content
6236
6308
  */
6237
6309
  private autoResizeTitleTextarea;
6310
+ /**
6311
+ * Auto-resize the description textarea based on content
6312
+ */
6313
+ private autoResizeDescriptionTextarea;
6238
6314
  /**
6239
6315
  * Handle title change with auto-resize
6240
6316
  */
6241
6317
  handleTitleChange(value: string): void;
6318
+ /**
6319
+ * Handle description change with auto-resize
6320
+ */
6321
+ handleDescriptionChange(value: string): void;
6242
6322
  /**
6243
6323
  * Validate form fields
6244
6324
  */
@@ -7175,7 +7255,7 @@ declare class DsMobileFabComponent implements AfterViewInit, OnDestroy {
7175
7255
  * Note: FAB is always 56px circular, but this affects the icon size
7176
7256
  * @default 'md'
7177
7257
  */
7178
- size: _angular_core.InputSignal<"sm" | "md" | "lg">;
7258
+ size: _angular_core.InputSignal<"md" | "sm" | "lg">;
7179
7259
  /**
7180
7260
  * ARIA label for accessibility
7181
7261
  * @required - Always provide a descriptive label
@@ -7687,6 +7767,7 @@ declare class MobileHomePageComponent implements OnInit {
7687
7767
  private postsService;
7688
7768
  private postModal;
7689
7769
  private trackingPermissionService;
7770
+ private bottomSheet;
7690
7771
  pageComponent: DsMobilePageMainComponent;
7691
7772
  recentPosts: _angular_core.Signal<_propbinder_mobile_design.Post[]>;
7692
7773
  private allInquiries;
@@ -7697,13 +7778,14 @@ declare class MobileHomePageComponent implements OnInit {
7697
7778
  status: "open";
7698
7779
  timestamp: string;
7699
7780
  }[]>;
7700
- constructor(router: Router, userService: UserService, postsService: PostsService, postModal: DsMobilePostDetailModalService, trackingPermissionService: TrackingPermissionService);
7781
+ constructor(router: Router, userService: UserService, postsService: PostsService, postModal: DsMobilePostDetailModalService, trackingPermissionService: TrackingPermissionService, bottomSheet: DsMobileBottomSheetService);
7701
7782
  ngOnInit(): void;
7702
7783
  handleRefresh(event: any): void;
7703
7784
  openPost(postId: string, focusComment?: boolean): Promise<void>;
7704
7785
  openInquiryDetail(inquiryId: string): void;
7705
7786
  navigateToCommunity(): void;
7706
7787
  navigateToInquiries(): void;
7788
+ handlePostLongPress(postId: string): Promise<void>;
7707
7789
  static ɵfac: _angular_core.ɵɵFactoryDeclaration<MobileHomePageComponent, never>;
7708
7790
  static ɵcmp: _angular_core.ɵɵComponentDeclaration<MobileHomePageComponent, "app-home-page", never, {}, {}, never, never, true, never>;
7709
7791
  }
@@ -7834,9 +7916,10 @@ interface FacilityDetail {
7834
7916
  declare class MobileBookingPageComponent {
7835
7917
  private facilityModal;
7836
7918
  private facilityCreationModal;
7919
+ userService: UserService;
7837
7920
  pageComponent: DsMobilePageMainComponent;
7838
7921
  bookingsSwiper?: DsMobileSwiperComponent;
7839
- constructor(facilityModal: DsMobileFacilityDetailModalService, facilityCreationModal: DsMobileFacilityCreationModalService);
7922
+ constructor(facilityModal: DsMobileFacilityDetailModalService, facilityCreationModal: DsMobileFacilityCreationModalService, userService: UserService);
7840
7923
  activeBookings: _angular_core.WritableSignal<FacilityDetail[]>;
7841
7924
  availableFacilities: _angular_core.WritableSignal<FacilityDetail[]>;
7842
7925
  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.47",
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;