@propbinder/mobile-design 0.2.47 → 0.2.50

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.
Files changed (221) hide show
  1. package/ng-package.json +24 -0
  2. package/package.json +3 -39
  3. package/src/animations/page-transitions.ts +165 -0
  4. package/src/assets/fonts/brockmann-mediumitalic-webfont.woff2 +0 -0
  5. package/src/assets/fonts/brockmann-regularitalic-webfont.woff2 +0 -0
  6. package/src/assets/fonts/brockmann-semibolditalic-webfont.woff2 +0 -0
  7. package/src/components/action-list-item/ds-mobile-action-list-item.ts +102 -0
  8. package/src/components/action-list-item/index.ts +2 -0
  9. package/src/components/app-icon/ds-app-icon.ts +133 -0
  10. package/src/components/app-icon/index.ts +2 -0
  11. package/src/components/attachment-preview/ds-mobile-attachment-preview.css +139 -0
  12. package/src/components/attachment-preview/ds-mobile-attachment-preview.ts +164 -0
  13. package/src/components/attachment-preview/index.ts +1 -0
  14. package/src/components/avatar-with-badge/ds-avatar-with-badge.ts +142 -0
  15. package/src/components/avatar-with-badge/index.ts +2 -0
  16. package/src/components/booking-modal/ds-mobile-booking-confirmation-wrapper.ts +71 -0
  17. package/src/components/booking-modal/ds-mobile-booking-modal.service.ts +121 -0
  18. package/src/components/booking-modal/ds-mobile-booking-modal.ts +598 -0
  19. package/src/components/booking-modal/ds-mobile-booking-summary.ts +161 -0
  20. package/src/components/booking-modal/index.ts +4 -0
  21. package/src/components/bottom-sheet/ds-mobile-actions-bottom-sheet.ts +266 -0
  22. package/src/components/bottom-sheet/ds-mobile-bottom-sheet-header.ts +146 -0
  23. package/src/components/bottom-sheet/ds-mobile-bottom-sheet-wrapper.ts +156 -0
  24. package/src/components/bottom-sheet/ds-mobile-bottom-sheet.css +101 -0
  25. package/src/components/bottom-sheet/ds-mobile-bottom-sheet.service.ts +169 -0
  26. package/src/components/bottom-sheet/ds-mobile-confirmation-sheet.ts +211 -0
  27. package/src/components/bottom-sheet/ds-mobile-post-create-bottom-sheet.ts +578 -0
  28. package/src/components/bottom-sheet/ds-mobile-profile-actions-sheet.ts +614 -0
  29. package/src/components/bottom-sheet/index.ts +8 -0
  30. package/src/components/bottom-sheet/modal-shadow-fix.ts +42 -0
  31. package/src/components/card-inline/ds-mobile-card-inline.ts +301 -0
  32. package/src/components/card-inline/index.ts +2 -0
  33. package/src/components/card-inline-banner/ds-mobile-card-inline-banner.ts +118 -0
  34. package/src/components/card-inline-banner/index.ts +1 -0
  35. package/src/components/card-inline-contact/ds-mobile-card-inline-contact.ts +120 -0
  36. package/src/components/card-inline-contact/index.ts +1 -0
  37. package/src/components/card-inline-file/ds-mobile-card-inline-file.ts +141 -0
  38. package/src/components/card-inline-file/index.ts +1 -0
  39. package/src/components/chat-modal/ds-mobile-chat-modal.css +159 -0
  40. package/src/components/chat-modal/ds-mobile-chat-modal.service.ts +105 -0
  41. package/src/components/chat-modal/ds-mobile-chat-modal.ts +918 -0
  42. package/src/components/chat-modal/index.ts +8 -0
  43. package/src/components/comment/ds-mobile-comment.ts +568 -0
  44. package/src/components/comment/index.ts +2 -0
  45. package/src/components/contact-list-item/ds-mobile-contact-list-item.ts +182 -0
  46. package/src/components/contact-list-item/index.ts +2 -0
  47. package/src/components/content/ds-mobile-content.ts +139 -0
  48. package/src/components/content/index.ts +2 -0
  49. package/src/components/dropdown/ds-mobile-dropdown.css +199 -0
  50. package/src/components/dropdown/ds-mobile-dropdown.ts +340 -0
  51. package/src/components/dropdown/index.ts +2 -0
  52. package/src/components/ds-mobile-tabs.css +407 -0
  53. package/src/components/ds-mobile-tabs.ts +216 -0
  54. package/src/components/empty-state/ds-mobile-empty-state.ts +120 -0
  55. package/src/components/empty-state/index.ts +2 -0
  56. package/src/components/fab/ds-mobile-fab.ts +315 -0
  57. package/src/components/fab/index.ts +1 -0
  58. package/src/components/facility-creation-modal/ds-mobile-facility-creation-confirmation-wrapper.ts +121 -0
  59. package/src/components/facility-creation-modal/ds-mobile-facility-creation-modal.css +189 -0
  60. package/src/components/facility-creation-modal/ds-mobile-facility-creation-modal.service.ts +135 -0
  61. package/src/components/facility-creation-modal/ds-mobile-facility-creation-modal.ts +656 -0
  62. package/src/components/facility-creation-modal/index.ts +9 -0
  63. package/src/components/facility-creation-modal/sheets/ds-mobile-access-sheet.ts +105 -0
  64. package/src/components/facility-creation-modal/sheets/ds-mobile-price-sheet.ts +188 -0
  65. package/src/components/facility-creation-modal/sheets/ds-mobile-when-can-book-sheet.ts +460 -0
  66. package/src/components/facility-creation-modal/sheets/ds-mobile-who-can-book-sheet.ts +134 -0
  67. package/src/components/facility-detail-modal/ds-mobile-facility-detail-modal.service.ts +69 -0
  68. package/src/components/facility-detail-modal/ds-mobile-facility-detail-modal.ts +379 -0
  69. package/src/components/facility-detail-modal/index.ts +2 -0
  70. package/src/components/file-attachment/ds-mobile-file-attachment.ts +164 -0
  71. package/src/components/file-attachment/index.ts +2 -0
  72. package/src/components/handbook-detail-modal/ds-mobile-handbook-detail-modal.css +214 -0
  73. package/src/components/handbook-detail-modal/ds-mobile-handbook-detail-modal.service.ts +84 -0
  74. package/src/components/handbook-detail-modal/ds-mobile-handbook-detail-modal.ts +424 -0
  75. package/src/components/handbook-detail-modal/index.ts +3 -0
  76. package/src/components/handbook-folder/ds-mobile-handbook-folder-mini.ts +175 -0
  77. package/src/components/handbook-folder/ds-mobile-handbook-folder.ts +533 -0
  78. package/src/components/handbook-folder/index.ts +4 -0
  79. package/src/components/header-content/ds-mobile-header-content.ts +222 -0
  80. package/src/components/header-content/index.ts +2 -0
  81. package/src/components/illustration/ds-mobile-illustration.ts +124 -0
  82. package/src/components/illustration/index.ts +2 -0
  83. package/src/components/index.ts +124 -0
  84. package/src/components/inline-photo/ds-mobile-inline-photo.ts +361 -0
  85. package/src/components/inline-photo/index.ts +1 -0
  86. package/src/components/inline-tabs/ds-mobile-inline-tabs.ts +132 -0
  87. package/src/components/inline-tabs/index.ts +2 -0
  88. package/src/components/interactive-list-item-booking/ds-mobile-interactive-list-item-booking.ts +350 -0
  89. package/src/components/interactive-list-item-booking/index.ts +1 -0
  90. package/src/components/interactive-list-item-inquiry/ds-mobile-interactive-list-item-inquiry.ts +321 -0
  91. package/src/components/interactive-list-item-inquiry/index.ts +2 -0
  92. package/src/components/interactive-list-item-message/ds-mobile-interactive-list-item-message.ts +237 -0
  93. package/src/components/interactive-list-item-message/index.ts +2 -0
  94. package/src/components/interactive-list-item-post/ds-mobile-interactive-list-item-post.ts +549 -0
  95. package/src/components/interactive-list-item-post/ds-mobile-post-pdf-attachment.ts +124 -0
  96. package/src/components/interactive-list-item-post/index.ts +13 -0
  97. package/src/components/lightbox/ds-mobile-lightbox-footer.ts +315 -0
  98. package/src/components/lightbox/ds-mobile-lightbox-header.ts +202 -0
  99. package/src/components/lightbox/ds-mobile-lightbox-image.ts +484 -0
  100. package/src/components/lightbox/ds-mobile-lightbox-pdf.css +377 -0
  101. package/src/components/lightbox/ds-mobile-lightbox-pdf.ts +374 -0
  102. package/src/components/lightbox/ds-mobile-lightbox.css +587 -0
  103. package/src/components/lightbox/ds-mobile-lightbox.service.ts +296 -0
  104. package/src/components/lightbox/ds-mobile-lightbox.ts +529 -0
  105. package/src/components/lightbox/index.ts +22 -0
  106. package/src/components/list-item/ds-mobile-list-item.ts +603 -0
  107. package/src/components/list-item/index.ts +2 -0
  108. package/src/components/list-item-static/ds-mobile-list-item-static.ts +133 -0
  109. package/src/components/list-item-static/index.ts +2 -0
  110. package/src/components/loader-overlay/ds-mobile-loader-overlay.css +49 -0
  111. package/src/components/loader-overlay/ds-mobile-loader-overlay.ts +77 -0
  112. package/src/components/loader-overlay/index.ts +1 -0
  113. package/src/components/logo/ds-logo.ts +95 -0
  114. package/src/components/logo/index.ts +2 -0
  115. package/src/components/message-bubble/ds-mobile-message-bubble.ts +633 -0
  116. package/src/components/message-bubble/index.ts +7 -0
  117. package/src/components/message-composer/ds-mobile-message-composer.ts +1146 -0
  118. package/src/components/message-composer/index.ts +7 -0
  119. package/src/components/modal/ds-mobile-modal.css +163 -0
  120. package/src/components/modal/ds-mobile-modal.service.ts +329 -0
  121. package/src/components/modal/index.ts +8 -0
  122. package/src/components/modal-base/ds-mobile-modal-base.css +378 -0
  123. package/src/components/modal-base/ds-mobile-modal-base.ts +261 -0
  124. package/src/components/modal-base/index.ts +2 -0
  125. package/src/components/new-inquiry-modal/ds-mobile-new-inquiry-modal.css +112 -0
  126. package/src/components/new-inquiry-modal/ds-mobile-new-inquiry-modal.service.ts +93 -0
  127. package/src/components/new-inquiry-modal/ds-mobile-new-inquiry-modal.ts +442 -0
  128. package/src/components/new-inquiry-modal/index.ts +4 -0
  129. package/src/components/offline-banner/ds-mobile-offline-banner.ts +135 -0
  130. package/src/components/offline-banner/index.ts +1 -0
  131. package/src/components/page-details/ds-mobile-page-details.css +83 -0
  132. package/src/components/page-details/ds-mobile-page-details.ts +282 -0
  133. package/src/components/page-details/index.ts +2 -0
  134. package/src/components/page-main/ds-mobile-page-main.css +68 -0
  135. package/src/components/page-main/ds-mobile-page-main.ts +421 -0
  136. package/src/components/page-main/index.ts +2 -0
  137. package/src/components/post-composer/ds-mobile-post-composer.ts +140 -0
  138. package/src/components/post-composer/index.ts +2 -0
  139. package/src/components/post-detail-modal/ds-mobile-post-detail-modal.css +390 -0
  140. package/src/components/post-detail-modal/ds-mobile-post-detail-modal.service.ts +108 -0
  141. package/src/components/post-detail-modal/ds-mobile-post-detail-modal.ts +722 -0
  142. package/src/components/post-detail-modal/index.ts +9 -0
  143. package/src/components/property-banner/ds-mobile-property-banner.ts +95 -0
  144. package/src/components/property-banner/index.ts +2 -0
  145. package/src/components/section/ds-mobile-section.ts +263 -0
  146. package/src/components/section/index.ts +2 -0
  147. package/src/components/shared/directives/index.ts +2 -0
  148. package/src/components/shared/directives/long-press.directive.ts +212 -0
  149. package/src/components/shared/index.ts +3 -0
  150. package/src/components/shared/mobile-modal-base.ts +457 -0
  151. package/src/components/shared/mobile-page-base.ts +204 -0
  152. package/src/components/swiper/ds-mobile-swiper-with-nav.ts +160 -0
  153. package/src/components/swiper/ds-mobile-swiper.ts +327 -0
  154. package/src/components/swiper/index.ts +3 -0
  155. package/src/components/system-message-banner/ds-mobile-system-message-banner.ts +129 -0
  156. package/src/components/system-message-banner/index.ts +2 -0
  157. package/src/components/tab-bar/ds-mobile-tab-bar.css +533 -0
  158. package/src/components/tab-bar/ds-mobile-tab-bar.ts +735 -0
  159. package/src/components/tab-bar/index.ts +2 -0
  160. package/src/components/tabs/ds-mobile-tabs.css +25 -0
  161. package/src/components/tabs/ds-mobile-tabs.ts +89 -0
  162. package/src/components/tabs/index.ts +2 -0
  163. package/src/components/text-input/ds-text-input.ts +287 -0
  164. package/src/components/text-input/index.ts +2 -0
  165. package/src/examples/booking.page.ts +434 -0
  166. package/src/examples/community.page.ts +776 -0
  167. package/src/examples/handbook.page.ts +324 -0
  168. package/src/examples/home.page.ts +347 -0
  169. package/src/examples/index.ts +12 -0
  170. package/src/examples/inquiries.example.ts +273 -0
  171. package/src/examples/inquiry-detail.example.css +189 -0
  172. package/src/examples/inquiry-detail.example.ts +415 -0
  173. package/src/examples/mobile-tabs-example.component.ts +208 -0
  174. package/src/examples/post-create.page.ts +311 -0
  175. package/src/examples/post-detail.page.ts +296 -0
  176. package/src/examples/sign-in.page.ts +291 -0
  177. package/src/examples/whitelabel-demo-modal.component.ts +1094 -0
  178. package/src/examples/whitelabel-demo-modal.service.ts +77 -0
  179. package/src/models/index.ts +7 -0
  180. package/src/models/post.model.ts +41 -0
  181. package/src/pages/community.page.ts +769 -0
  182. package/src/pages/handbook.page.ts +388 -0
  183. package/src/pages/home.page.ts +303 -0
  184. package/src/pages/index.ts +11 -0
  185. package/src/pages/inquiries.example.ts +273 -0
  186. package/src/pages/inquiry-detail.example.css +189 -0
  187. package/src/pages/inquiry-detail.example.ts +415 -0
  188. package/src/pages/mobile-tabs-example.component.ts +179 -0
  189. package/src/pages/post-create.page.ts +311 -0
  190. package/src/pages/post-detail.page.ts +296 -0
  191. package/src/pages/sign-in.page.ts +291 -0
  192. package/src/pages/whitelabel-demo-modal.component.ts +1094 -0
  193. package/src/pages/whitelabel-demo-modal.service.ts +77 -0
  194. package/src/public-api.ts +6 -0
  195. package/src/services/base-modal.service.ts +101 -0
  196. package/src/services/index.ts +11 -0
  197. package/src/services/posts.service.ts +542 -0
  198. package/src/services/tracking-permission.service.ts +88 -0
  199. package/src/services/user.service.ts +60 -0
  200. package/src/services/whitelabel.service.ts +675 -0
  201. package/{styles → src/styles}/ionic.css +25 -0
  202. package/tsconfig.lib.json +17 -0
  203. package/tsconfig.lib.prod.json +9 -0
  204. package/tsconfig.spec.json +13 -0
  205. package/fesm2022/propbinder-mobile-design.mjs +0 -26136
  206. package/fesm2022/propbinder-mobile-design.mjs.map +0 -1
  207. package/index.d.ts +0 -8154
  208. /package/{assets → src/assets}/fonts/Brockmann-Bold.otf +0 -0
  209. /package/{assets → src/assets}/fonts/Brockmann-BoldItalic.otf +0 -0
  210. /package/{assets → src/assets}/fonts/Brockmann-Medium.otf +0 -0
  211. /package/{assets → src/assets}/fonts/Brockmann-MediumItalic.otf +0 -0
  212. /package/{assets → src/assets}/fonts/Brockmann-Regular.otf +0 -0
  213. /package/{assets → src/assets}/fonts/Brockmann-RegularItalic.otf +0 -0
  214. /package/{assets → src/assets}/fonts/Brockmann-SemiBold.otf +0 -0
  215. /package/{assets → src/assets}/fonts/Brockmann-SemiBoldItalic.otf +0 -0
  216. /package/{assets → src/assets}/fonts/Brockmann_desktop_license.pdf +0 -0
  217. /package/{assets → src/assets}/fonts/brockmann-medium-webfont.woff2 +0 -0
  218. /package/{assets → src/assets}/fonts/brockmann-regular-webfont.woff2 +0 -0
  219. /package/{assets → src/assets}/fonts/brockmann-semibold-webfont.woff2 +0 -0
  220. /package/{styles → src/components/shared}/mobile-common.css +0 -0
  221. /package/{styles → src/components/shared}/mobile-page-base.css +0 -0
@@ -0,0 +1,529 @@
1
+ import {
2
+ Component,
3
+ signal,
4
+ computed,
5
+ ViewChild,
6
+ ElementRef,
7
+ AfterViewInit,
8
+ OnDestroy,
9
+ OnInit,
10
+ CUSTOM_ELEMENTS_SCHEMA
11
+ } from '@angular/core';
12
+ import { CommonModule } from '@angular/common';
13
+ import {
14
+ IonContent,
15
+ IonSpinner,
16
+ ModalController,
17
+ GestureController,
18
+ Gesture
19
+ } from '@ionic/angular/standalone';
20
+ import { Share } from '@capacitor/share';
21
+ import { DsIconButtonComponent } from '@propbinder/design-system';
22
+ import { DsAvatarComponent } from '@propbinder/design-system';
23
+ import type { LightboxImage, LightboxAuthor } from './ds-mobile-lightbox.service';
24
+
25
+ /**
26
+ * DsMobileLightboxComponent
27
+ *
28
+ * Full-screen image lightbox component with native mobile gestures.
29
+ * Supports swipe navigation, pinch-to-zoom, and double-tap zoom.
30
+ *
31
+ * This component is typically not used directly - use DsMobileLightboxService instead.
32
+ *
33
+ * Features:
34
+ * - Swipe left/right to navigate between images
35
+ * - Pinch to zoom in/out
36
+ * - Double-tap to toggle zoom
37
+ * - Swipe down to close (when not zoomed)
38
+ * - Image counter and navigation controls
39
+ * - Optional title and description display
40
+ *
41
+ * @example
42
+ * ```typescript
43
+ * // Don't instantiate directly - use the service:
44
+ * constructor(private lightbox: DsMobileLightboxService) {}
45
+ *
46
+ * openImage() {
47
+ * this.lightbox.open({
48
+ * images: [{ src: 'image.jpg', title: 'My Image' }]
49
+ * });
50
+ * }
51
+ * ```
52
+ */
53
+ @Component({
54
+ selector: 'ds-mobile-lightbox',
55
+ standalone: true,
56
+ imports: [
57
+ CommonModule,
58
+ IonContent,
59
+ IonSpinner,
60
+ DsIconButtonComponent,
61
+ DsAvatarComponent
62
+ ],
63
+ styleUrls: ['../shared/mobile-common.css', './ds-mobile-lightbox.css'],
64
+ schemas: [CUSTOM_ELEMENTS_SCHEMA],
65
+ template: `
66
+ <ion-content
67
+ [fullscreen]="true"
68
+ [scrollY]="false"
69
+ [scrollX]="false"
70
+ class="lightbox-content"
71
+ [class.zoomed]="isZoomed()">
72
+
73
+ <div class="lightbox-wrapper">
74
+ <!-- Header with action buttons -->
75
+ <div class="lightbox-header">
76
+ <!-- Action buttons row -->
77
+ <div class="header-actions">
78
+ <ds-icon-button
79
+ icon="remixCloseLine"
80
+ variant="ghost"
81
+ size="md"
82
+ (click)="close()"
83
+ class="close-button"
84
+ aria-label="Luk lyskasse">
85
+ </ds-icon-button>
86
+
87
+ <ds-icon-button
88
+ icon="remixShare2Line"
89
+ variant="ghost"
90
+ size="md"
91
+ (click)="onShare()"
92
+ class="share-button"
93
+ aria-label="Del billede">
94
+ </ds-icon-button>
95
+ </div>
96
+ </div>
97
+
98
+ <!-- Image container with gesture support -->
99
+ <div class="lightbox-container" #container>
100
+ <div
101
+ class="image-wrapper"
102
+ #imageWrapper
103
+ [style.transform]="transform()">
104
+ <img
105
+ #image
106
+ [src]="currentImage().src"
107
+ [alt]="currentImage().alt || 'Lightbox image'"
108
+ class="lightbox-image"
109
+ (load)="onImageLoad()"
110
+ (error)="onImageError()">
111
+ </div>
112
+
113
+ <!-- Loading indicator -->
114
+ @if (isLoading()) {
115
+ <div class="loading-spinner">
116
+ <ion-spinner name="crescent"></ion-spinner>
117
+ </div>
118
+ }
119
+
120
+ <!-- Error message -->
121
+ @if (hasError()) {
122
+ <div class="error-message">
123
+ <p>Failed to load image</p>
124
+ </div>
125
+ }
126
+ </div>
127
+
128
+ <!-- Navigation controls -->
129
+ @if (showControls && images.length > 1) {
130
+ <div class="lightbox-controls">
131
+ <button
132
+ class="nav-button prev"
133
+ (click)="previousImage()"
134
+ [disabled]="currentIndex() === 0"
135
+ aria-label="Forrige billede">
136
+ <svg width="24" height="24" viewBox="0 0 24 24" fill="none">
137
+ <path d="M15 18L9 12L15 6" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
138
+ </svg>
139
+ </button>
140
+
141
+ <div class="counter">
142
+ {{ currentIndex() + 1 }} / {{ images.length }}
143
+ </div>
144
+
145
+ <button
146
+ class="nav-button next"
147
+ (click)="nextImage()"
148
+ [disabled]="currentIndex() === images.length - 1"
149
+ aria-label="Næste billede">
150
+ <svg width="24" height="24" viewBox="0 0 24 24" fill="none">
151
+ <path d="M9 18L15 12L9 6" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
152
+ </svg>
153
+ </button>
154
+ </div>
155
+ }
156
+
157
+ <!-- Caption at bottom -->
158
+ @if (showInfo && author) {
159
+ <div class="lightbox-caption" [class.with-controls]="showControls && images.length > 1">
160
+ <!-- Author meta -->
161
+ <div class="author-details">
162
+ <ds-avatar
163
+ [initials]="author.avatarInitials ?? ''"
164
+ [type]="author.avatarType ?? 'initials'"
165
+ [src]="author.avatarSrc ?? ''"
166
+ size="md"
167
+ />
168
+ <div class="author-details">
169
+ <div class="author-name">{{ author.name }}</div>
170
+ @if (author.role || author.timestamp) {
171
+ <div class="author-meta">
172
+ @if (author.role) {
173
+ <span>{{ author.role }}</span>
174
+ }
175
+ @if (author.role && author.timestamp) {
176
+ <span class="separator">·</span>
177
+ }
178
+ @if (author.timestamp) {
179
+ <span>{{ author.timestamp }}</span>
180
+ }
181
+ </div>
182
+ }
183
+ </div>
184
+ </div>
185
+ </div>
186
+ }
187
+ </div>
188
+ </ion-content>
189
+ `
190
+ })
191
+ export class DsMobileLightboxComponent implements OnInit, AfterViewInit, OnDestroy {
192
+ // Inputs (passed from service as regular properties, not signals)
193
+ images!: LightboxImage[];
194
+ author?: LightboxAuthor;
195
+ initialIndex: number = 0;
196
+ enableZoom: boolean = true;
197
+ showControls: boolean = true;
198
+ enableSwipe: boolean = true;
199
+ showInfo: boolean = true;
200
+ animation: 'fade' | 'zoom' | 'slide' = 'fade';
201
+
202
+ // View children
203
+ @ViewChild('container', { read: ElementRef }) containerRef!: ElementRef<HTMLDivElement>;
204
+ @ViewChild('imageWrapper', { read: ElementRef }) imageWrapperRef!: ElementRef<HTMLDivElement>;
205
+ @ViewChild('image', { read: ElementRef }) imageRef!: ElementRef<HTMLImageElement>;
206
+
207
+ // State
208
+ currentIndex = signal(0);
209
+ scale = signal(1);
210
+ translateX = signal(0);
211
+ translateY = signal(0);
212
+ isZoomed = signal(false);
213
+ isLoading = signal(true);
214
+ hasError = signal(false);
215
+
216
+ // Action states
217
+ isLiked = signal(false);
218
+ likeCount = signal(0);
219
+ commentCount = signal(0);
220
+
221
+ // Computed
222
+ currentImage = computed(() => this.images[this.currentIndex()]);
223
+ transform = computed(() => {
224
+ const s = this.scale();
225
+ const x = this.translateX();
226
+ const y = this.translateY();
227
+ return `scale(${s}) translate(${x}px, ${y}px)`;
228
+ });
229
+
230
+ // Gesture tracking
231
+ private gesture?: Gesture;
232
+ private swipeGesture?: Gesture;
233
+ private pinchGesture?: Gesture;
234
+ private initialScale = 1;
235
+ private initialTranslateX = 0;
236
+ private initialTranslateY = 0;
237
+ private lastPinchScale = 1;
238
+
239
+ constructor(
240
+ private modalController: ModalController,
241
+ private gestureCtrl: GestureController
242
+ ) {}
243
+
244
+ ngOnInit(): void {
245
+ // Set initial index from the passed property
246
+ if (this.initialIndex !== undefined) {
247
+ this.currentIndex.set(this.initialIndex);
248
+ }
249
+
250
+ // Initialize action states from current image
251
+ const currentImg = this.images[this.currentIndex()];
252
+ if (currentImg) {
253
+ this.isLiked.set(currentImg.isLiked ?? false);
254
+ this.likeCount.set(currentImg.likeCount ?? 0);
255
+ this.commentCount.set(currentImg.commentCount ?? 0);
256
+ }
257
+ }
258
+
259
+ ngAfterViewInit(): void {
260
+ console.log('[Lightbox] ngAfterViewInit called');
261
+ console.log('[Lightbox] Current image:', this.currentImage());
262
+ console.log('[Lightbox] Images array:', this.images);
263
+ console.log('[Lightbox] Container ref:', this.containerRef?.nativeElement);
264
+ console.log('[Lightbox] Image wrapper ref:', this.imageWrapperRef?.nativeElement);
265
+ console.log('[Lightbox] Image ref:', this.imageRef?.nativeElement);
266
+
267
+ // Initialize gestures after view is ready
268
+ if (this.enableZoom) {
269
+ this.initializeZoomGesture();
270
+ this.initializePinchZoom();
271
+ }
272
+ if (this.enableSwipe) {
273
+ this.initializeSwipeGesture();
274
+ }
275
+ }
276
+
277
+ ngOnDestroy(): void {
278
+ // Clean up gestures
279
+ this.gesture?.destroy();
280
+ this.swipeGesture?.destroy();
281
+ this.pinchGesture?.destroy();
282
+ }
283
+
284
+ /**
285
+ * Initialize double-tap zoom gesture
286
+ */
287
+ private initializeZoomGesture(): void {
288
+ if (!this.imageWrapperRef) return;
289
+
290
+ let lastTap = 0;
291
+ const element = this.imageWrapperRef.nativeElement;
292
+
293
+ element.addEventListener('click', (event: MouseEvent) => {
294
+ const now = Date.now();
295
+ const timeSinceLastTap = now - lastTap;
296
+
297
+ if (timeSinceLastTap < 300 && timeSinceLastTap > 0) {
298
+ // Double tap detected
299
+ event.preventDefault();
300
+ this.onDoubleTap();
301
+ }
302
+
303
+ lastTap = now;
304
+ });
305
+ }
306
+
307
+ /**
308
+ * Initialize pinch-to-zoom gesture
309
+ */
310
+ private initializePinchZoom(): void {
311
+ if (!this.imageWrapperRef) return;
312
+
313
+ const element = this.imageWrapperRef.nativeElement;
314
+ let initialDistance = 0;
315
+ let initialCenterX = 0;
316
+ let initialCenterY = 0;
317
+
318
+ const getTouchDistance = (touches: TouchList) => {
319
+ const dx = touches[0].clientX - touches[1].clientX;
320
+ const dy = touches[0].clientY - touches[1].clientY;
321
+ return Math.sqrt(dx * dx + dy * dy);
322
+ };
323
+
324
+ const getTouchCenter = (touches: TouchList) => {
325
+ return {
326
+ x: (touches[0].clientX + touches[1].clientX) / 2,
327
+ y: (touches[0].clientY + touches[1].clientY) / 2
328
+ };
329
+ };
330
+
331
+ element.addEventListener('touchstart', (event: TouchEvent) => {
332
+ if (event.touches.length === 2) {
333
+ event.preventDefault();
334
+ initialDistance = getTouchDistance(event.touches);
335
+ const center = getTouchCenter(event.touches);
336
+ initialCenterX = center.x;
337
+ initialCenterY = center.y;
338
+ this.initialScale = this.scale();
339
+ this.lastPinchScale = 1;
340
+ }
341
+ }, { passive: false });
342
+
343
+ element.addEventListener('touchmove', (event: TouchEvent) => {
344
+ if (event.touches.length === 2) {
345
+ event.preventDefault();
346
+
347
+ const currentDistance = getTouchDistance(event.touches);
348
+ const pinchScale = currentDistance / initialDistance;
349
+
350
+ // Calculate new scale
351
+ let newScale = this.initialScale * pinchScale;
352
+ newScale = Math.max(1, Math.min(newScale, 4)); // Clamp between 1x and 4x
353
+
354
+ this.scale.set(newScale);
355
+
356
+ if (newScale > 1) {
357
+ this.isZoomed.set(true);
358
+ } else {
359
+ this.isZoomed.set(false);
360
+ this.translateX.set(0);
361
+ this.translateY.set(0);
362
+ }
363
+
364
+ this.lastPinchScale = pinchScale;
365
+ }
366
+ }, { passive: false });
367
+
368
+ element.addEventListener('touchend', (event: TouchEvent) => {
369
+ if (event.touches.length < 2) {
370
+ // Reset to 1x if we're close to it
371
+ if (this.scale() < 1.1) {
372
+ this.resetZoom();
373
+ }
374
+ }
375
+ });
376
+ }
377
+
378
+ /**
379
+ * Initialize swipe gesture for navigation
380
+ */
381
+ private initializeSwipeGesture(): void {
382
+ if (!this.containerRef) return;
383
+
384
+ this.swipeGesture = this.gestureCtrl.create({
385
+ el: this.containerRef.nativeElement,
386
+ gestureName: 'swipe-navigate',
387
+ direction: 'x',
388
+ threshold: 50,
389
+ onStart: () => {
390
+ this.initialTranslateX = this.translateX();
391
+ },
392
+ onMove: (detail) => {
393
+ // Only allow swipe if not zoomed
394
+ if (!this.isZoomed() && detail.deltaX !== undefined) {
395
+ this.translateX.set(this.initialTranslateX + detail.deltaX / 2);
396
+ }
397
+ },
398
+ onEnd: (detail) => {
399
+ if (!this.isZoomed() && detail.deltaX !== undefined) {
400
+ const threshold = 100;
401
+
402
+ if (detail.deltaX > threshold) {
403
+ // Swipe right - previous image
404
+ this.previousImage();
405
+ } else if (detail.deltaX < -threshold) {
406
+ // Swipe left - next image
407
+ this.nextImage();
408
+ } else {
409
+ // Reset position
410
+ this.translateX.set(0);
411
+ }
412
+ }
413
+ }
414
+ }, true);
415
+
416
+ this.swipeGesture.enable();
417
+ }
418
+
419
+ /**
420
+ * Close the lightbox
421
+ */
422
+ close(): void {
423
+ this.modalController.dismiss();
424
+ }
425
+
426
+ async onShare(): Promise<void> {
427
+ console.log('[Lightbox] Share button clicked');
428
+ const currentImg = this.currentImage();
429
+
430
+ if (!currentImg?.src) return;
431
+
432
+ try {
433
+ // Use Capacitor Share API for native share sheet
434
+ await Share.share({
435
+ title: currentImg.title || 'Image',
436
+ text: currentImg.description || '',
437
+ url: currentImg.src,
438
+ dialogTitle: 'Share Image'
439
+ });
440
+
441
+ console.log('[Lightbox] Image shared successfully');
442
+ } catch (error) {
443
+ console.error('[Lightbox] Error sharing image:', error);
444
+ // If share fails or is cancelled, do nothing
445
+ // Note: User canceling the share dialog will throw an error, which is expected
446
+ }
447
+ }
448
+
449
+ onLike(event: { active: boolean; count: number }): void {
450
+ console.log('[Lightbox] Like toggled:', event);
451
+ this.isLiked.set(event.active);
452
+ this.likeCount.set(event.count);
453
+ // You can emit an event or call a service here to persist the like
454
+ }
455
+
456
+ onReply(): void {
457
+ console.log('[Lightbox] Reply button clicked');
458
+ // Close the lightbox and navigate to reply/comment interface
459
+ // Or open a bottom sheet for quick reply
460
+ this.modalController.dismiss({ action: 'reply' });
461
+ }
462
+
463
+ /**
464
+ * Navigate to the next image
465
+ */
466
+ nextImage(): void {
467
+ if (this.currentIndex() < this.images.length - 1) {
468
+ this.resetZoom();
469
+ this.currentIndex.update(i => i + 1);
470
+ this.isLoading.set(true);
471
+ this.hasError.set(false);
472
+ }
473
+ }
474
+
475
+ /**
476
+ * Navigate to the previous image
477
+ */
478
+ previousImage(): void {
479
+ if (this.currentIndex() > 0) {
480
+ this.resetZoom();
481
+ this.currentIndex.update(i => i - 1);
482
+ this.isLoading.set(true);
483
+ this.hasError.set(false);
484
+ }
485
+ }
486
+
487
+ /**
488
+ * Handle double-tap to toggle zoom
489
+ */
490
+ onDoubleTap(): void {
491
+ if (!this.enableZoom) return;
492
+
493
+ if (this.isZoomed()) {
494
+ this.resetZoom();
495
+ } else {
496
+ this.scale.set(2);
497
+ this.isZoomed.set(true);
498
+ }
499
+ }
500
+
501
+ /**
502
+ * Reset zoom and pan to default state
503
+ */
504
+ resetZoom(): void {
505
+ this.scale.set(1);
506
+ this.translateX.set(0);
507
+ this.translateY.set(0);
508
+ this.isZoomed.set(false);
509
+ }
510
+
511
+ /**
512
+ * Handle image load success
513
+ */
514
+ onImageLoad(): void {
515
+ console.log('[Lightbox] Image loaded successfully');
516
+ this.isLoading.set(false);
517
+ this.hasError.set(false);
518
+ }
519
+
520
+ /**
521
+ * Handle image load error
522
+ */
523
+ onImageError(): void {
524
+ console.error('[Lightbox] Image failed to load');
525
+ this.isLoading.set(false);
526
+ this.hasError.set(true);
527
+ }
528
+ }
529
+
@@ -0,0 +1,22 @@
1
+ // Components
2
+ export { DsMobileLightboxImageComponent } from './ds-mobile-lightbox-image';
3
+ export { DsMobileLightboxPdfComponent } from './ds-mobile-lightbox-pdf';
4
+ export { DsMobileLightboxHeaderComponent } from './ds-mobile-lightbox-header';
5
+ export { DsMobileLightboxFooterComponent } from './ds-mobile-lightbox-footer';
6
+
7
+ // Service and Types
8
+ export {
9
+ DsMobileLightboxService,
10
+ type LightboxImage,
11
+ type LightboxPdf,
12
+ type LightboxMediaFile,
13
+ type LightboxMediaType,
14
+ type LightboxAuthor,
15
+ type LightboxOptions,
16
+ type LightboxImageOptions,
17
+ type LightboxPdfOptions
18
+ } from './ds-mobile-lightbox.service';
19
+
20
+ // Legacy export for backward compatibility
21
+ export { DsMobileLightboxImageComponent as DsMobileLightboxComponent } from './ds-mobile-lightbox-image';
22
+