@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,603 @@
1
+ import {
2
+ Component,
3
+ input,
4
+ output,
5
+ computed,
6
+ signal,
7
+ PLATFORM_ID,
8
+ inject,
9
+ ElementRef,
10
+ AfterViewInit,
11
+ } from '@angular/core';
12
+ import { CommonModule, isPlatformBrowser } from '@angular/common';
13
+ import { DsMobileLongPressDirective } from '../shared/directives/long-press.directive';
14
+ import { DsIconButtonComponent } from '@propbinder/design-system';
15
+
16
+ /**
17
+ * DsMobileListItemComponent
18
+ *
19
+ * A versatile, reusable list item component for mobile applications.
20
+ * Supports both interactive and non-interactive modes with flexible content projection.
21
+ *
22
+ * Features:
23
+ * - Interactive mode with click and long-press support
24
+ * - Pseudo-element background extends 8px beyond bounds (no negative margins needed)
25
+ * - Flexible content slots (leading, main, trailing)
26
+ * - Optional structured inputs for common use cases (title, subtitle)
27
+ * - Accessibility features (focus states, ARIA attributes)
28
+ * - Disabled and loading states
29
+ *
30
+ * This component serves as the foundation for specialized list item types like posts,
31
+ * notifications, messages, contacts, and other list content.
32
+ *
33
+ * @example
34
+ * ```html
35
+ * <!-- Simple structured usage -->
36
+ * <ds-mobile-list-item
37
+ * title="Document Title"
38
+ * subtitle="Supporting text"
39
+ * [interactive]="true"
40
+ * (itemClick)="handleClick()">
41
+ *
42
+ * <ds-icon content-leading name="document" />
43
+ * </ds-mobile-list-item>
44
+ *
45
+ * <!-- Flexible custom usage -->
46
+ * <ds-mobile-list-item
47
+ * [interactive]="true"
48
+ * (itemClick)="handleClick()"
49
+ * (longPress)="showContextMenu()">
50
+ *
51
+ * <div content-leading>
52
+ * <ds-avatar initials="JD" />
53
+ * </div>
54
+ *
55
+ * <div content-main>
56
+ * <h3>Custom Content</h3>
57
+ * <p>Full control over layout and styling</p>
58
+ * </div>
59
+ *
60
+ * <button content-trailing (click)="handleAction($event)">
61
+ * Action
62
+ * </button>
63
+ * </ds-mobile-list-item>
64
+ *
65
+ * <!-- Non-interactive read-only -->
66
+ * <ds-mobile-list-item
67
+ * title="Read-only Item"
68
+ * subtitle="No interaction">
69
+ * <ds-icon content-leading name="info" />
70
+ * </ds-mobile-list-item>
71
+ * ```
72
+ */
73
+ @Component({
74
+ selector: 'ds-mobile-list-item',
75
+ standalone: true,
76
+ imports: [CommonModule, DsIconButtonComponent],
77
+ hostDirectives: [
78
+ {
79
+ directive: DsMobileLongPressDirective,
80
+ outputs: ['longPress'],
81
+ },
82
+ ],
83
+ host: {
84
+ '[class.interactive]': 'interactive() && !disabled()',
85
+ '[class.disabled]': 'disabled()',
86
+ '[class.loading]': 'loading()',
87
+ '[class.no-divider]': '!showDivider()',
88
+ '[class.no-leading-content]': '!hasLeadingContent()',
89
+ '[class.variant-compact]': 'variant() === "compact"',
90
+ '[class.align-top]': 'align() === "top"',
91
+ '[class.align-center]': 'align() === "center"',
92
+ '[class.align-bottom]': 'align() === "bottom"',
93
+ '[attr.role]': 'interactive() ? "button" : null',
94
+ '[attr.tabindex]': 'interactive() && !disabled() ? "0" : null',
95
+ '[attr.aria-disabled]': 'disabled() ? "true" : null',
96
+ '[style.--leading-size]': 'leadingSize()',
97
+ '[style.--interactive-offset]': 'interactiveOffset()',
98
+ '[style.--divider-spacing]': 'dividerSpacing()',
99
+ '(click)': 'handleClick($event)',
100
+ '(keydown.enter)': 'handleKeyDown($event)',
101
+ '(keydown.space)': 'handleKeyDown($event)',
102
+ '(longPress)': 'handleLongPress()',
103
+ },
104
+ styles: [
105
+ `
106
+ :host {
107
+ display: block;
108
+ position: relative;
109
+ padding: var(--item-padding-top, 12px) 0
110
+ var(--item-padding-bottom, 12px) 0;
111
+ box-sizing: border-box;
112
+ /* CSS variables defined at host level for use by children and pseudo-elements */
113
+ --leading-size: 32px;
114
+ --content-gap: 12px;
115
+ --interactive-offset: 8px;
116
+ }
117
+
118
+ /* Divider line on host */
119
+ :host::after {
120
+ content: '';
121
+ position: absolute;
122
+ bottom: 0;
123
+ left: calc(var(--leading-size) + var(--content-gap));
124
+ right: 0;
125
+ height: 1px;
126
+ background: var(--border-color-default, #e5e5e5);
127
+ z-index: 1;
128
+ display: var(--divider-display, block);
129
+ }
130
+
131
+ /* Hide divider when no-divider class is present */
132
+ :host(.no-divider)::after {
133
+ display: none;
134
+ }
135
+
136
+ /* Adjust divider when no leading content */
137
+ :host(.no-leading-content)::after {
138
+ left: 0;
139
+ }
140
+
141
+ .list-item-inner {
142
+ display: flex;
143
+ flex-direction: row;
144
+ align-items: flex-start;
145
+ gap: var(--content-gap);
146
+ position: relative;
147
+ }
148
+
149
+ :host(.align-center) .list-item-inner {
150
+ align-items: center;
151
+ }
152
+
153
+ :host(.align-bottom) .list-item-inner {
154
+ align-items: flex-end;
155
+ }
156
+
157
+ /* Pseudo-element for interactive background */
158
+ :host(.interactive) .list-item-inner::before {
159
+ content: '';
160
+ position: absolute;
161
+ top: calc(-1 * var(--interactive-offset));
162
+ left: calc(-1 * var(--interactive-offset));
163
+ right: calc(-1 * var(--interactive-offset));
164
+ bottom: calc(-1 * var(--interactive-offset));
165
+ background: var(--color-background-neutral-primary, #ffffff);
166
+ border-radius: 16px;
167
+ z-index: -1;
168
+ pointer-events: none;
169
+ }
170
+
171
+ /* Interactive states */
172
+ :host(.interactive) {
173
+ cursor: pointer;
174
+ }
175
+
176
+ /* Hover state (desktop only) */
177
+ @media (hover: hover) and (pointer: fine) {
178
+ :host(.interactive):hover .list-item-inner::before {
179
+ background: var(--color-background-neutral-primary-hover, #f5f5f5);
180
+ }
181
+ }
182
+
183
+ /* Active state */
184
+ :host(.interactive):active .list-item-inner::before {
185
+ background: var(--color-background-neutral-primary-hover, #f5f5f5);
186
+ }
187
+
188
+ /* Focus visible for keyboard navigation */
189
+ :host(.interactive):focus-visible {
190
+ outline: none;
191
+ }
192
+
193
+ :host(.interactive):focus-visible .list-item-inner::before {
194
+ outline: 2px solid var(--color-brand-primary, #5d5fef);
195
+ outline-offset: 2px;
196
+ }
197
+
198
+ /* Disabled state */
199
+ :host(.disabled) {
200
+ opacity: 0.5;
201
+ pointer-events: none;
202
+ }
203
+
204
+ /* Loading state */
205
+ :host(.loading) {
206
+ pointer-events: none;
207
+ }
208
+
209
+ /* Variants */
210
+ :host(.variant-compact) .list-item-inner {
211
+ gap: 8px;
212
+ }
213
+
214
+ /* Content slots */
215
+ .content-leading {
216
+ flex-shrink: 0;
217
+ width: var(--leading-size);
218
+ height: var(--leading-size);
219
+ display: flex;
220
+ align-items: flex-start;
221
+ justify-content: center;
222
+ position: relative;
223
+ z-index: 1;
224
+ }
225
+
226
+ :host(.align-center) .content-leading {
227
+ align-items: center;
228
+ }
229
+
230
+ :host(.align-bottom) .content-leading {
231
+ align-items: flex-end;
232
+ }
233
+
234
+ .content-main {
235
+ flex: 1;
236
+ min-width: 0;
237
+ display: flex;
238
+ flex-direction: column;
239
+ gap: 8px;
240
+ position: relative;
241
+ z-index: 1;
242
+ justify-content: flex-start;
243
+ }
244
+
245
+ :host(.align-center) .content-main {
246
+ justify-content: center;
247
+ }
248
+
249
+ :host(.align-bottom) .content-main {
250
+ justify-content: flex-end;
251
+ }
252
+
253
+ .content-trailing {
254
+ flex-shrink: 0;
255
+ display: flex;
256
+ align-items: flex-start;
257
+ position: relative;
258
+ z-index: 1;
259
+ }
260
+
261
+ /* Structured content styles */
262
+ .structured-title {
263
+ font-family: 'Brockmann', sans-serif;
264
+ font-size: var(--font-size-sm, 14px);
265
+ font-weight: 600;
266
+ line-height: 20px;
267
+ letter-spacing: -0.3px;
268
+ color: var(--text-color-default-primary, #202227);
269
+ margin: 0;
270
+ white-space: nowrap;
271
+ overflow: hidden;
272
+ text-overflow: ellipsis;
273
+ }
274
+
275
+ .structured-subtitle {
276
+ font-family: 'Brockmann', sans-serif;
277
+ font-size: var(--font-size-sm, 14px);
278
+ font-weight: 400;
279
+ line-height: 20px;
280
+ letter-spacing: -0.3px;
281
+ color: var(--text-color-default-secondary, #545b66);
282
+ margin: 0;
283
+ white-space: nowrap;
284
+ overflow: hidden;
285
+ text-overflow: ellipsis;
286
+ }
287
+
288
+ /* Desktop more actions button - using ds-icon-button */
289
+ .desktop-more-button::ng-deep button {
290
+ border-radius: 50% !important;
291
+ }
292
+ `,
293
+ ],
294
+ template: `
295
+ <div class="list-item-inner">
296
+ @if (hasLeadingContent()) {
297
+ <div class="content-leading">
298
+ <ng-content select="[content-leading]" />
299
+ </div>
300
+ }
301
+
302
+ <div class="content-main">
303
+ @if (title()) {
304
+ <h3 class="structured-title">{{ title() }}</h3>
305
+ } @if (subtitle()) {
306
+ <p class="structured-subtitle">{{ subtitle() }}</p>
307
+ }
308
+
309
+ <ng-content select="[content-main]" />
310
+ <ng-content />
311
+ </div>
312
+
313
+ <div class="content-trailing">
314
+ @if (interactive() && shouldShowMoreButton()) {
315
+ <ds-icon-button
316
+ class="desktop-more-button"
317
+ icon="remixMoreFill"
318
+ variant="secondary"
319
+ size="sm"
320
+ (clicked)="handleMoreButtonClick($event)"
321
+ aria-label="More options"
322
+ >
323
+ </ds-icon-button>
324
+ }
325
+ <ng-content select="[content-trailing]" />
326
+ </div>
327
+ </div>
328
+ `,
329
+ })
330
+ export class DsMobileListItemComponent implements AfterViewInit {
331
+ private platformId = inject(PLATFORM_ID);
332
+ private elementRef = inject(ElementRef);
333
+
334
+ /**
335
+ * Detect if viewport is desktop size
336
+ * Use viewport width for breakpoint detection (show button on tablet and above)
337
+ */
338
+ isDesktop = signal<boolean>(false);
339
+
340
+ constructor() {
341
+ if (isPlatformBrowser(this.platformId)) {
342
+ // Show button on tablet breakpoint and above (768px+)
343
+ const isDesktopViewport = window.innerWidth >= 768;
344
+
345
+ // console.log('[ListItem] Desktop detection:', {
346
+ // innerWidth: window.innerWidth,
347
+ // isDesktopViewport
348
+ // });
349
+
350
+ this.isDesktop.set(isDesktopViewport);
351
+
352
+ // Listen for window resize to update detection
353
+ window.addEventListener('resize', () => {
354
+ const newIsDesktop = window.innerWidth >= 768;
355
+ if (newIsDesktop !== this.isDesktop()) {
356
+ // console.log('[ListItem] Viewport changed, updating desktop detection:', newIsDesktop);
357
+ this.isDesktop.set(newIsDesktop);
358
+ }
359
+ });
360
+ }
361
+ }
362
+
363
+ ngAfterViewInit(): void {
364
+ // Check if there's actual content in the leading slot
365
+ this.checkLeadingContent();
366
+ }
367
+
368
+ /**
369
+ * Check if leading content slot has actual content projected
370
+ */
371
+ private checkLeadingContent(): void {
372
+ if (!isPlatformBrowser(this.platformId)) {
373
+ return;
374
+ }
375
+
376
+ const hostElement = this.elementRef.nativeElement;
377
+ const leadingElements = hostElement.querySelectorAll('[content-leading]');
378
+
379
+ // Check if any element with content-leading attribute exists and has content
380
+ const hasContent = Array.from(leadingElements).some((el: any) => {
381
+ return el.childNodes.length > 0 || el.textContent?.trim().length > 0;
382
+ });
383
+
384
+ this.hasLeadingContent.set(hasContent);
385
+ }
386
+
387
+ /**
388
+ * CSS size value for the leading content area (e.g., '32px', '40px', '48px')
389
+ * Defaults to '32px' for standard list item avatars/icons
390
+ */
391
+ leadingSize = input<string>('32px');
392
+
393
+ /**
394
+ * Display variant
395
+ * - undefined (default) - Standard display
396
+ * - 'compact' - Compact display for nested/related items
397
+ */
398
+ variant = input<'compact' | undefined>(undefined);
399
+
400
+ /**
401
+ * Vertical alignment of leading and main content slots
402
+ * - 'top' - Align to top (default)
403
+ * - 'center' - Align to center
404
+ * - 'bottom' - Align to bottom
405
+ */
406
+ align = input<'top' | 'center' | 'bottom'>('top');
407
+
408
+ /**
409
+ * Whether the list item is interactive (clickable and long-pressable)
410
+ * When true, adds interactive background, cursor pointer, and touch handlers
411
+ */
412
+ interactive = input<boolean>(false);
413
+
414
+ /**
415
+ * Whether the list item is disabled
416
+ * Disables all interactions and reduces opacity
417
+ */
418
+ disabled = input<boolean>(false);
419
+
420
+ /**
421
+ * Whether the list item is in a loading state
422
+ * Disables interactions but maintains full opacity
423
+ */
424
+ loading = input<boolean>(false);
425
+
426
+ /**
427
+ * Enable long-press interaction when interactive is true
428
+ * Set to false to disable long-press but keep click
429
+ */
430
+ enableLongPress = input<boolean>(true);
431
+
432
+ /**
433
+ * Show "more actions" button on desktop for items with long-press enabled
434
+ * @deprecated Use `moreActions` instead. Kept for backwards compatibility.
435
+ * @default true
436
+ */
437
+ showDesktopMoreButton = input<boolean>(true);
438
+
439
+ /**
440
+ * Unified toggle for contextual actions (more button + long press).
441
+ * - `true` — more button visible on **all** breakpoints, long press enabled
442
+ * - `false` — more button hidden, long press suppressed
443
+ * - `undefined` (default) — falls back to legacy `enableLongPress` + `showDesktopMoreButton` + desktop check
444
+ */
445
+ moreActions = input<boolean | undefined>(undefined);
446
+
447
+ /**
448
+ * Resolved visibility of the more-actions button.
449
+ * When `moreActions` is set it takes precedence; otherwise legacy inputs + breakpoint apply.
450
+ */
451
+ shouldShowMoreButton = computed(() => {
452
+ const ma = this.moreActions();
453
+ if (ma !== undefined) return ma;
454
+ return this.enableLongPress() && this.showDesktopMoreButton() && this.isDesktop();
455
+ });
456
+
457
+ /**
458
+ * Offset distance for the interactive background pseudo-element
459
+ * Extends the background beyond the content bounds
460
+ * @default '8px'
461
+ */
462
+ interactiveOffset = input<string>('8px');
463
+
464
+ /**
465
+ * Optional structured title text
466
+ * Provides a simple way to add title without custom markup
467
+ */
468
+ title = input<string>();
469
+
470
+ /**
471
+ * Optional structured subtitle text
472
+ * Provides a simple way to add subtitle without custom markup
473
+ */
474
+ subtitle = input<string>();
475
+
476
+ /**
477
+ * Whether to show the divider line below the list item
478
+ * Automatically hidden on last-child and detail variant
479
+ * @default true
480
+ */
481
+ showDivider = input<boolean>(true);
482
+
483
+ /**
484
+ * Spacing around the divider (top and bottom padding)
485
+ * @default '4px'
486
+ */
487
+ dividerSpacing = input<string>('4px');
488
+
489
+ /**
490
+ * Emits when the list item is clicked (if interactive and not disabled)
491
+ */
492
+ itemClick = output<void>();
493
+
494
+ /**
495
+ * Emits when the desktop more actions button is clicked
496
+ * This is separate from longPress to give more control to parent components
497
+ * Typically, you can use (longPress) for both mobile and desktop actions
498
+ */
499
+ moreButtonClick = output<Event>();
500
+
501
+ /**
502
+ * Track if long press was triggered to prevent click
503
+ */
504
+ private longPressTriggered = false;
505
+
506
+ /**
507
+ * Check if leading content slot has content
508
+ * Dynamically updated after view initialization
509
+ */
510
+ hasLeadingContent = signal<boolean>(true);
511
+
512
+ /**
513
+ * Check if trailing content slot has content
514
+ * Always true to maintain consistent layout
515
+ */
516
+ hasTrailingContent = computed(() => true);
517
+
518
+ /**
519
+ * Handle click events
520
+ */
521
+ handleClick(event: Event): void {
522
+ // console.log('[ListItem] Click event fired', {
523
+ // interactive: this.interactive(),
524
+ // disabled: this.disabled(),
525
+ // loading: this.loading(),
526
+ // longPressTriggered: this.longPressTriggered,
527
+ // target: event.target
528
+ // });
529
+
530
+ if (!this.interactive() || this.disabled() || this.loading()) {
531
+ // console.log('[ListItem] Click ignored - not interactive or disabled/loading');
532
+ return;
533
+ }
534
+
535
+ // Don't emit click if it came from an interactive child element
536
+ // (but not the host element itself)
537
+ const target = event.target as HTMLElement;
538
+ const closestInteractive = target.closest(
539
+ 'button, a, input, select, textarea, [role="button"]'
540
+ );
541
+
542
+ // Only block if the interactive element is a REAL child action (like a button in trailing slot),
543
+ // but NOT if it's just the host itself or something inside leading/main that shouldn't block.
544
+ if (closestInteractive && closestInteractive !== event.currentTarget) {
545
+ // If the target is inside content-leading or content-main, we generally want to allow the click
546
+ const isInsidePrimaryContent = !!target.closest('[content-leading], [content-main], .structured-title, .structured-subtitle');
547
+
548
+ if (!isInsidePrimaryContent) {
549
+ // console.log('[ListItem] Click ignored - came from interactive child in trailing/unknown slot:', closestInteractive);
550
+ return;
551
+ }
552
+ }
553
+
554
+ if (!this.longPressTriggered) {
555
+ // console.log('[ListItem] Emitting itemClick');
556
+ this.itemClick.emit();
557
+ } else {
558
+ // console.log('[ListItem] Click ignored - long press was triggered');
559
+ }
560
+
561
+ this.longPressTriggered = false;
562
+ }
563
+
564
+ /**
565
+ * Handle keyboard events (Enter/Space)
566
+ */
567
+ handleKeyDown(event: Event): void {
568
+ if (!this.interactive() || this.disabled() || this.loading()) {
569
+ return;
570
+ }
571
+
572
+ event.preventDefault();
573
+ this.itemClick.emit();
574
+ }
575
+
576
+ /**
577
+ * Handle long press events from the directive
578
+ * Set the flag to prevent the subsequent click event
579
+ */
580
+ handleLongPress(): void {
581
+ if (this.moreActions() === false) return;
582
+ this.longPressTriggered = true;
583
+ setTimeout(() => {
584
+ this.longPressTriggered = false;
585
+ }, 100);
586
+ }
587
+
588
+ /**
589
+ * Handle desktop more button click
590
+ * Stops propagation to prevent triggering itemClick
591
+ * Emits moreButtonClick for parent components to handle
592
+ */
593
+ handleMoreButtonClick(event: Event): void {
594
+ // console.log('[ListItem] Desktop more button clicked');
595
+
596
+ // Stop propagation to prevent triggering itemClick
597
+ event.stopPropagation();
598
+ event.preventDefault();
599
+
600
+ // Emit the more button click event
601
+ this.moreButtonClick.emit(event);
602
+ }
603
+ }
@@ -0,0 +1,2 @@
1
+ export * from './ds-mobile-list-item';
2
+