@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,484 @@
1
+ import {
2
+ Component,
3
+ signal,
4
+ computed,
5
+ ViewChild,
6
+ ElementRef,
7
+ AfterViewInit,
8
+ OnDestroy,
9
+ OnInit,
10
+ CUSTOM_ELEMENTS_SCHEMA,
11
+ HostListener
12
+ } from '@angular/core';
13
+ import { CommonModule } from '@angular/common';
14
+ import {
15
+ IonSpinner,
16
+ GestureController,
17
+ Gesture
18
+ } from '@ionic/angular/standalone';
19
+ import { Share } from '@capacitor/share';
20
+ import { Haptics, ImpactStyle } from '@capacitor/haptics';
21
+ import { DsMobileLightboxHeaderComponent } from './ds-mobile-lightbox-header';
22
+ import { DsMobileLightboxFooterComponent } from './ds-mobile-lightbox-footer';
23
+ import type { LightboxImage, LightboxAuthor } from './ds-mobile-lightbox.service';
24
+ import Swiper from 'swiper';
25
+ import type { SwiperOptions } from 'swiper/types';
26
+
27
+ /**
28
+ * DsMobileLightboxImageComponent
29
+ *
30
+ * Full-screen image lightbox component with Swiper.js navigation and pinch-zoom.
31
+ *
32
+ * This component is typically not used directly - use DsMobileLightboxService instead.
33
+ *
34
+ * Features:
35
+ * - Swiper.js for smooth image navigation
36
+ * - Pinch to zoom in/out
37
+ * - Double-tap to toggle zoom
38
+ * - Swipe down to close (when not zoomed)
39
+ * - Image counter and navigation controls
40
+ *
41
+ * @example
42
+ * ```typescript
43
+ * // Don't instantiate directly - use the service:
44
+ * constructor(private lightbox: DsMobileLightboxService) {}
45
+ *
46
+ * openImage() {
47
+ * this.lightbox.openImages({
48
+ * images: [{ type: 'image', src: 'image.jpg', title: 'My Image' }]
49
+ * });
50
+ * }
51
+ * ```
52
+ */
53
+ @Component({
54
+ selector: 'ds-mobile-lightbox-image',
55
+ standalone: true,
56
+ imports: [
57
+ CommonModule,
58
+ IonSpinner,
59
+ DsMobileLightboxHeaderComponent,
60
+ DsMobileLightboxFooterComponent
61
+ ],
62
+ schemas: [CUSTOM_ELEMENTS_SCHEMA],
63
+ template: `
64
+ <div class="lightbox-overlay"
65
+ [class.zoomed]="isZoomed()">
66
+
67
+ <div class="lightbox-wrapper">
68
+ <!-- Header with author info and action buttons -->
69
+ <ds-mobile-lightbox-header
70
+ [author]="author"
71
+ (closeClick)="close()"
72
+ (shareClick)="onShare()"
73
+ />
74
+
75
+ <!-- Swiper container -->
76
+ <div class="swiper-container" #swiperContainer>
77
+ <div class="swiper-wrapper">
78
+ @for (image of images; track image.src; let i = $index) {
79
+ <div class="swiper-slide">
80
+ <div class="image-zoom-container" [attr.data-index]="i">
81
+ <img
82
+ [src]="image.src"
83
+ [alt]="image.alt || 'Lightbox image'"
84
+ class="lightbox-image"
85
+ (load)="onImageLoad(i)"
86
+ (error)="onImageError(i)">
87
+ </div>
88
+ </div>
89
+ }
90
+ </div>
91
+ </div>
92
+
93
+ <!-- Loading indicator -->
94
+ @if (isLoading()) {
95
+ <div class="loading-spinner">
96
+ <ion-spinner name="crescent"></ion-spinner>
97
+ </div>
98
+ }
99
+
100
+ <!-- Footer with navigation and actions -->
101
+ <ds-mobile-lightbox-footer
102
+ [showNavigation]="showControls && images.length > 1"
103
+ [showActions]="showActions"
104
+ [currentIndex]="currentIndex()"
105
+ [totalImages]="images.length"
106
+ [isLiked]="isLiked()"
107
+ [likeCount]="likeCount()"
108
+ [commentCount]="commentCount()"
109
+ (prevClick)="previousImage()"
110
+ (nextClick)="nextImage()"
111
+ (likeClick)="onLikeToggle()"
112
+ (commentClick)="onReply()"
113
+ />
114
+ </div>
115
+ </div>
116
+ `,
117
+ styleUrl: './ds-mobile-lightbox.css'
118
+ })
119
+ export class DsMobileLightboxImageComponent implements OnInit, AfterViewInit, OnDestroy {
120
+ // Inputs (passed from service as regular properties, not signals)
121
+ images!: LightboxImage[];
122
+ author?: LightboxAuthor;
123
+ initialIndex: number = 0;
124
+ enableZoom: boolean = true;
125
+ showControls: boolean = true;
126
+ enableSwipe: boolean = true;
127
+ showInfo: boolean = true;
128
+ showActions: boolean = false;
129
+ animation: 'fade' | 'zoom' | 'slide' = 'fade';
130
+ onCloseRequested?: () => void;
131
+
132
+ // View children
133
+ @ViewChild('swiperContainer', { read: ElementRef }) swiperContainer!: ElementRef<HTMLDivElement>;
134
+
135
+ // State
136
+ currentIndex = signal(0);
137
+ scale = signal(1);
138
+ isZoomed = signal(false);
139
+ isLoading = signal(true);
140
+ hasError = signal(false);
141
+
142
+ // Action states
143
+ isLiked = signal(false);
144
+ likeCount = signal(0);
145
+ commentCount = signal(0);
146
+
147
+ // Computed
148
+ currentImage = computed(() => this.images[this.currentIndex()]);
149
+
150
+ // Swiper instance
151
+ private swiper?: Swiper;
152
+ private zoomData: Map<number, { scale: number; x: number; y: number }> = new Map();
153
+
154
+ constructor(
155
+ private gestureCtrl: GestureController
156
+ ) {}
157
+
158
+ ngOnInit(): void {
159
+ // Set initial index from the passed property
160
+ if (this.initialIndex !== undefined) {
161
+ this.currentIndex.set(this.initialIndex);
162
+ }
163
+
164
+ // Initialize action states from current image
165
+ const currentImg = this.images[this.currentIndex()];
166
+ if (currentImg) {
167
+ this.isLiked.set(currentImg.isLiked ?? false);
168
+ this.likeCount.set(currentImg.likeCount ?? 0);
169
+ this.commentCount.set(currentImg.commentCount ?? 0);
170
+ }
171
+ }
172
+
173
+ ngAfterViewInit(): void {
174
+ setTimeout(() => {
175
+ this.initializeSwiper();
176
+ this.initializeZoomGestures();
177
+ }, 100);
178
+ }
179
+
180
+ ngOnDestroy(): void {
181
+ // Clean up Swiper
182
+ if (this.swiper) {
183
+ this.swiper.destroy();
184
+ this.swiper = undefined;
185
+ }
186
+ }
187
+ /**
188
+ * Initialize Swiper for image navigation
189
+ */
190
+ private initializeSwiper(): void {
191
+ if (!this.swiperContainer) {
192
+ console.error('[Lightbox] Swiper container not found');
193
+ return;
194
+ }
195
+
196
+ const swiperOptions: SwiperOptions = {
197
+ initialSlide: this.initialIndex,
198
+ speed: 300,
199
+ resistance: true,
200
+ resistanceRatio: 0.85,
201
+ slidesPerView: 1,
202
+ spaceBetween: 0,
203
+ touchRatio: 1,
204
+ longSwipesRatio: 0.5,
205
+ threshold: 10,
206
+ on: {
207
+ slideChange: (swiper) => {
208
+ this.currentIndex.set(swiper.activeIndex);
209
+ this.updateActionStates();
210
+
211
+ // Check if the image is already loaded
212
+ const currentSlide = swiper.slides[swiper.activeIndex];
213
+ const img = currentSlide?.querySelector('img');
214
+ if (img && img.complete && img.naturalHeight !== 0) {
215
+ // Image is already loaded
216
+ this.isLoading.set(false);
217
+ }
218
+ },
219
+ slideChangeTransitionStart: () => {
220
+ // Don't show loading spinner if image is already loaded
221
+ const currentSlide = this.swiper?.slides[this.swiper.activeIndex];
222
+ const img = currentSlide?.querySelector('img');
223
+ if (!img || !img.complete || img.naturalHeight === 0) {
224
+ this.isLoading.set(true);
225
+ }
226
+ }
227
+ }
228
+ };
229
+
230
+ this.swiper = new Swiper(this.swiperContainer.nativeElement, swiperOptions);
231
+
232
+ // Check if the initial image is already loaded
233
+ setTimeout(() => {
234
+ const currentSlide = this.swiper?.slides[this.currentIndex()];
235
+ const img = currentSlide?.querySelector('img');
236
+ if (img && img.complete && img.naturalHeight !== 0) {
237
+ this.isLoading.set(false);
238
+ }
239
+ }, 0);
240
+ }
241
+
242
+ /**
243
+ * Initialize pinch-zoom gestures for all slides
244
+ */
245
+ private initializeZoomGestures(): void {
246
+ if (!this.enableZoom) return;
247
+
248
+ const slides = this.swiperContainer.nativeElement.querySelectorAll('.image-zoom-container');
249
+
250
+ slides.forEach((slide, index) => {
251
+ this.initializeZoomForSlide(slide as HTMLElement, index);
252
+ });
253
+ }
254
+
255
+ /**
256
+ * Initialize zoom gestures for a specific slide
257
+ */
258
+ private initializeZoomForSlide(container: HTMLElement, index: number): void {
259
+ let initialDistance = 0;
260
+ let initialScale = 1;
261
+ let currentScale = 1;
262
+ let lastTap = 0;
263
+
264
+ // Double-tap to zoom
265
+ container.addEventListener('click', (event: MouseEvent) => {
266
+ const now = Date.now();
267
+ const timeSinceLastTap = now - lastTap;
268
+
269
+ if (timeSinceLastTap < 300 && timeSinceLastTap > 0) {
270
+ event.preventDefault();
271
+ this.toggleZoom(container, index);
272
+ }
273
+
274
+ lastTap = now;
275
+ });
276
+
277
+ // Pinch to zoom
278
+ const getTouchDistance = (touches: TouchList) => {
279
+ const dx = touches[0].clientX - touches[1].clientX;
280
+ const dy = touches[0].clientY - touches[1].clientY;
281
+ return Math.sqrt(dx * dx + dy * dy);
282
+ };
283
+
284
+ container.addEventListener('touchstart', (event: TouchEvent) => {
285
+ if (event.touches.length === 2) {
286
+ event.preventDefault();
287
+ initialDistance = getTouchDistance(event.touches);
288
+ const zoomData = this.zoomData.get(index) || { scale: 1, x: 0, y: 0 };
289
+ initialScale = zoomData.scale;
290
+
291
+ // Disable Swiper when zooming
292
+ if (this.swiper) {
293
+ this.swiper.allowTouchMove = false;
294
+ }
295
+ }
296
+ }, { passive: false });
297
+
298
+ container.addEventListener('touchmove', (event: TouchEvent) => {
299
+ if (event.touches.length === 2) {
300
+ event.preventDefault();
301
+
302
+ const currentDistance = getTouchDistance(event.touches);
303
+ const pinchScale = currentDistance / initialDistance;
304
+
305
+ currentScale = Math.max(1, Math.min(initialScale * pinchScale, 4));
306
+
307
+ const img = container.querySelector('img');
308
+ if (img) {
309
+ img.style.transform = `scale(${currentScale})`;
310
+ }
311
+
312
+ if (currentScale > 1) {
313
+ this.isZoomed.set(true);
314
+ } else {
315
+ this.isZoomed.set(false);
316
+ }
317
+ }
318
+ }, { passive: false });
319
+
320
+ container.addEventListener('touchend', (event: TouchEvent) => {
321
+ if (event.touches.length < 2) {
322
+ // Save zoom state
323
+ this.zoomData.set(index, { scale: currentScale, x: 0, y: 0 });
324
+
325
+ // Re-enable Swiper if not zoomed
326
+ if (this.swiper && currentScale <= 1) {
327
+ this.swiper.allowTouchMove = true;
328
+ }
329
+ }
330
+ });
331
+ }
332
+
333
+ /**
334
+ * Toggle zoom on double-tap
335
+ */
336
+ private toggleZoom(container: HTMLElement, index: number): void {
337
+ const zoomData = this.zoomData.get(index) || { scale: 1, x: 0, y: 0 };
338
+ const img = container.querySelector('img');
339
+
340
+ if (!img) return;
341
+
342
+ if (zoomData.scale > 1) {
343
+ // Zoom out
344
+ img.style.transform = 'scale(1)';
345
+ this.zoomData.set(index, { scale: 1, x: 0, y: 0 });
346
+ this.isZoomed.set(false);
347
+ if (this.swiper) {
348
+ this.swiper.allowTouchMove = true;
349
+ }
350
+ } else {
351
+ // Zoom in
352
+ img.style.transform = 'scale(2)';
353
+ this.zoomData.set(index, { scale: 2, x: 0, y: 0 });
354
+ this.isZoomed.set(true);
355
+ if (this.swiper) {
356
+ this.swiper.allowTouchMove = false;
357
+ }
358
+ }
359
+ }
360
+
361
+ /**
362
+ * Update action states (like, comments) when slide changes
363
+ */
364
+ private updateActionStates(): void {
365
+ const currentImg = this.images[this.currentIndex()];
366
+ if (currentImg) {
367
+ this.isLiked.set(currentImg.isLiked ?? false);
368
+ this.likeCount.set(currentImg.likeCount ?? 0);
369
+ this.commentCount.set(currentImg.commentCount ?? 0);
370
+ }
371
+ }
372
+
373
+ /**
374
+ * Close the lightbox
375
+ */
376
+ close(): void {
377
+ if (this.onCloseRequested) {
378
+ this.onCloseRequested();
379
+ }
380
+ }
381
+
382
+ /**
383
+ * Handle share button click
384
+ */
385
+ async onShare(): Promise<void> {
386
+ console.log('[Lightbox] Share button clicked');
387
+ const currentImg = this.currentImage();
388
+
389
+ if (!currentImg?.src) {
390
+ console.warn('[Lightbox] No image to share');
391
+ return;
392
+ }
393
+
394
+ try {
395
+ // Check if Web Share API is available (for browser)
396
+ if (navigator.share) {
397
+ await navigator.share({
398
+ title: currentImg.title || 'Shared Image',
399
+ text: currentImg.description || '',
400
+ url: currentImg.src,
401
+ });
402
+ console.log('[Lightbox] Shared via Web Share API');
403
+ } else {
404
+ // Fallback to Capacitor Share API (for native apps)
405
+ await Share.share({
406
+ title: currentImg.title || 'Shared Image',
407
+ url: currentImg.src,
408
+ dialogTitle: 'Share Image',
409
+ });
410
+ console.log('[Lightbox] Shared via Capacitor Share API');
411
+ }
412
+ } catch (error: any) {
413
+ // User cancellation is expected and not an error
414
+ if (error?.message?.includes('cancel') || error?.code === 'USER_CANCELLED') {
415
+ console.log('[Lightbox] Share cancelled by user');
416
+ return;
417
+ }
418
+ console.error('[Lightbox] Share failed:', error);
419
+ }
420
+ }
421
+
422
+ /**
423
+ * Handle like button toggle
424
+ */
425
+ onLikeToggle(): void {
426
+ console.log('[Lightbox] Like button toggled');
427
+ this.isLiked.update(liked => !liked);
428
+
429
+ if (this.isLiked()) {
430
+ this.likeCount.update(count => count + 1);
431
+ } else {
432
+ this.likeCount.update(count => Math.max(0, count - 1));
433
+ }
434
+ }
435
+
436
+ /**
437
+ * Handle reply/comment button click
438
+ */
439
+ onReply(): void {
440
+ console.log('[Lightbox] Reply button clicked');
441
+ if (this.onCloseRequested) {
442
+ this.onCloseRequested();
443
+ }
444
+ }
445
+
446
+ /**
447
+ * Navigate to the next image
448
+ */
449
+ nextImage(): void {
450
+ if (this.swiper && this.currentIndex() < this.images.length - 1) {
451
+ this.swiper.slideNext();
452
+ }
453
+ }
454
+
455
+ /**
456
+ * Navigate to the previous image
457
+ */
458
+ previousImage(): void {
459
+ if (this.swiper && this.currentIndex() > 0) {
460
+ this.swiper.slidePrev();
461
+ }
462
+ }
463
+
464
+ /**
465
+ * Handle image load success
466
+ */
467
+ onImageLoad(index: number): void {
468
+ if (index === this.currentIndex()) {
469
+ this.isLoading.set(false);
470
+ }
471
+ }
472
+
473
+ /**
474
+ * Handle image load error
475
+ */
476
+ onImageError(index: number): void {
477
+ if (index === this.currentIndex()) {
478
+ console.error(`[Lightbox] Image ${index} failed to load`);
479
+ this.isLoading.set(false);
480
+ this.hasError.set(true);
481
+ }
482
+ }
483
+ }
484
+