@propbinder/mobile-design 0.2.3 → 0.2.4

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.
@@ -9073,6 +9073,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.15", ngImpo
9073
9073
  */
9074
9074
  class DsMobileChatModalComponent {
9075
9075
  modalController;
9076
+ lightbox;
9076
9077
  // Chat data passed from service
9077
9078
  chatData;
9078
9079
  /**
@@ -9087,15 +9088,16 @@ class DsMobileChatModalComponent {
9087
9088
  participant = signal({
9088
9089
  id: '',
9089
9090
  name: '',
9090
- avatarInitials: ''
9091
+ avatarInitials: '',
9091
9092
  }, ...(ngDevMode ? [{ debugName: "participant" }] : []));
9092
9093
  messages = signal([], ...(ngDevMode ? [{ debugName: "messages" }] : []));
9093
9094
  currentUserInitials = signal('', ...(ngDevMode ? [{ debugName: "currentUserInitials" }] : []));
9094
9095
  currentUserAvatarType = signal('initials', ...(ngDevMode ? [{ debugName: "currentUserAvatarType" }] : []));
9095
9096
  currentUserAvatarSrc = signal('', ...(ngDevMode ? [{ debugName: "currentUserAvatarSrc" }] : []));
9096
9097
  autoFocus = signal(false, ...(ngDevMode ? [{ debugName: "autoFocus" }] : []));
9097
- constructor(modalController) {
9098
+ constructor(modalController, lightbox) {
9098
9099
  this.modalController = modalController;
9100
+ this.lightbox = lightbox;
9099
9101
  }
9100
9102
  ngOnInit() {
9101
9103
  // Initialize chat data from input
@@ -9147,7 +9149,7 @@ class DsMobileChatModalComponent {
9147
9149
  isOwnMessage: true,
9148
9150
  avatarInitials: this.currentUserInitials(),
9149
9151
  avatarType: this.currentUserAvatarType(),
9150
- avatarSrc: this.currentUserAvatarSrc()
9152
+ avatarSrc: this.currentUserAvatarSrc(),
9151
9153
  };
9152
9154
  // Add message to list
9153
9155
  const updatedMessages = [...this.messages(), newMessage];
@@ -9163,8 +9165,50 @@ class DsMobileChatModalComponent {
9163
9165
  * Handle attachment click
9164
9166
  */
9165
9167
  handleAttachmentClick(attachment) {
9166
- console.log('[ChatModal] Attachment clicked:', attachment);
9167
- // In a real app, you would open the attachment (image viewer, PDF viewer, etc.)
9168
+ if (attachment.type !== 'image') {
9169
+ if (attachment.url) {
9170
+ window.open(attachment.url, '_blank', 'noopener,noreferrer');
9171
+ }
9172
+ return;
9173
+ }
9174
+ const msgs = this.messages();
9175
+ const allImages = [];
9176
+ for (const m of msgs) {
9177
+ for (const att of m.attachments ?? []) {
9178
+ if (att.type === 'image') {
9179
+ allImages.push({ msg: m, att });
9180
+ }
9181
+ }
9182
+ }
9183
+ const initialIndex = Math.max(0, allImages.findIndex((x) => x.att.id === attachment.id));
9184
+ const images = allImages.map((x) => ({
9185
+ type: 'image',
9186
+ src: x.att.url,
9187
+ title: x.att.name,
9188
+ alt: x.att.name,
9189
+ thumbnail: x.att.thumbnail,
9190
+ }));
9191
+ const ownerMessage = allImages[initialIndex]?.msg;
9192
+ const author = ownerMessage
9193
+ ? {
9194
+ name: ownerMessage.senderName,
9195
+ role: ownerMessage.senderRole,
9196
+ timestamp: ownerMessage.timestamp,
9197
+ avatarInitials: ownerMessage.avatarInitials,
9198
+ avatarSrc: ownerMessage.avatarSrc,
9199
+ avatarType: ownerMessage.avatarType === 'photo' || ownerMessage.avatarType === 'initials' ? ownerMessage.avatarType : undefined,
9200
+ }
9201
+ : undefined;
9202
+ this.lightbox.openImages({
9203
+ images,
9204
+ author,
9205
+ initialIndex,
9206
+ enableZoom: true,
9207
+ enableSwipe: true,
9208
+ showControls: true,
9209
+ showInfo: true,
9210
+ animation: 'fade',
9211
+ });
9168
9212
  }
9169
9213
  /**
9170
9214
  * Handle message long press
@@ -9173,7 +9217,7 @@ class DsMobileChatModalComponent {
9173
9217
  console.log('[ChatModal] Message long pressed:', message);
9174
9218
  // In a real app, you would show an action sheet with options (copy, delete, etc.)
9175
9219
  }
9176
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: DsMobileChatModalComponent, deps: [{ token: i1.ModalController }], target: i0.ɵɵFactoryTarget.Component });
9220
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: DsMobileChatModalComponent, deps: [{ token: i1.ModalController }, { token: DsMobileLightboxService }], target: i0.ɵɵFactoryTarget.Component });
9177
9221
  static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.15", type: DsMobileChatModalComponent, isStandalone: true, selector: "ds-mobile-chat-modal", inputs: { chatData: "chatData", loading: "loading", error: "error" }, ngImport: i0, template: `
9178
9222
  <ion-content [fullscreen]="true" [scrollY]="true" class="chat-modal-content">
9179
9223
  <div class="chat-modal-wrapper">
@@ -9186,77 +9230,67 @@ class DsMobileChatModalComponent {
9186
9230
  [initials]="participant().avatarInitials || ''"
9187
9231
  [type]="participant().avatarType || 'initials'"
9188
9232
  [src]="participant().avatarSrc || ''"
9189
- size="md"
9190
- />
9233
+ size="md" />
9191
9234
  <div class="participant-details">
9192
9235
  <div class="author-name">{{ participant().name }}</div>
9193
9236
  @if (participant().role) {
9194
- <div class="author-meta">{{ participant().role }}</div>
9237
+ <div class="author-meta">{{ participant().role }}</div>
9195
9238
  }
9196
9239
  </div>
9197
9240
  </div>
9198
-
9241
+
9199
9242
  <!-- Close button -->
9200
- <ds-icon-button
9201
- icon="remixCloseLine"
9202
- variant="secondary"
9203
- size="lg"
9204
- (clicked)="close()"
9205
- class="close-button"
9206
- aria-label="Close chat">
9207
- </ds-icon-button>
9243
+ <ds-icon-button icon="remixCloseLine" variant="secondary" size="lg" (clicked)="close()" class="close-button" aria-label="Close chat"> </ds-icon-button>
9208
9244
  </div>
9209
9245
  </div>
9210
9246
 
9211
9247
  <!-- Messages thread -->
9212
9248
  <div class="chat-messages-container">
9213
9249
  @if (loading) {
9214
- <!-- Loading State -->
9215
- <div class="chat-loading-state">
9216
- <div class="loading-spinner"></div>
9217
- <p class="loading-text">Loading messages...</p>
9218
- </div>
9250
+ <!-- Loading State -->
9251
+ <div class="chat-loading-state">
9252
+ <div class="loading-spinner"></div>
9253
+ <p class="loading-text">Loading messages...</p>
9254
+ </div>
9219
9255
  } @else if (error) {
9220
- <!-- Error State -->
9221
- <div class="chat-error-state">
9222
- <ds-icon name="remixErrorWarningLine" size="48px" [style.color]="'var(--color-destructive-base)'" />
9223
- <h3 class="error-state-title">Error loading messages</h3>
9224
- <p class="error-state-description">{{ error }}</p>
9225
- </div>
9256
+ <!-- Error State -->
9257
+ <div class="chat-error-state">
9258
+ <ds-icon name="remixErrorWarningLine" size="48px" [style.color]="'var(--color-destructive-base)'" />
9259
+ <h3 class="error-state-title">Error loading messages</h3>
9260
+ <p class="error-state-description">{{ error }}</p>
9261
+ </div>
9226
9262
  } @else {
9227
- <!-- Messages List -->
9228
- <div class="messages-list">
9229
- @if (messages().length === 0) {
9230
- <!-- Empty State -->
9231
- <div class="messages-empty-state">
9232
- <h3 class="empty-state-title">No messages yet</h3>
9233
- <p class="empty-state-description">Start the conversation by sending a message</p>
9234
- </div>
9235
- } @else {
9236
- @for (message of messages(); track message.id) {
9237
- <ds-mobile-message-bubble
9238
- [content]="message.content"
9239
- [isOwnMessage]="message.isOwnMessage"
9240
- [timestamp]="message.timestamp"
9241
- [avatarInitials]="message.avatarInitials || ''"
9242
- [avatarType]="message.avatarType || 'initials'"
9243
- [avatarSrc]="message.avatarSrc || ''"
9244
- [attachments]="message.attachments"
9245
- [clickable]="true"
9246
- (attachmentClick)="handleAttachmentClick($event)"
9247
- (longPress)="handleMessageLongPress(message)">
9248
- </ds-mobile-message-bubble>
9249
- }
9250
- }
9263
+ <!-- Messages List -->
9264
+ <div class="messages-list">
9265
+ @if (messages().length === 0) {
9266
+ <!-- Empty State -->
9267
+ <div class="messages-empty-state">
9268
+ <h3 class="empty-state-title">No messages yet</h3>
9269
+ <p class="empty-state-description">Start the conversation by sending a message</p>
9251
9270
  </div>
9252
-
9253
- <!-- Bottom spacer for fixed composer -->
9254
- <div class="composer-spacer"></div>
9271
+ } @else { @for (message of messages(); track message.id) {
9272
+ <ds-mobile-message-bubble
9273
+ [content]="message.content"
9274
+ [isOwnMessage]="message.isOwnMessage"
9275
+ [timestamp]="message.timestamp"
9276
+ [avatarInitials]="message.avatarInitials || ''"
9277
+ [avatarType]="message.avatarType || 'initials'"
9278
+ [avatarSrc]="message.avatarSrc || ''"
9279
+ [attachments]="message.attachments"
9280
+ [clickable]="true"
9281
+ (attachmentClick)="handleAttachmentClick($event)"
9282
+ (longPress)="handleMessageLongPress(message)">
9283
+ </ds-mobile-message-bubble>
9284
+ } }
9285
+ </div>
9286
+
9287
+ <!-- Bottom spacer for fixed composer -->
9288
+ <div class="composer-spacer"></div>
9255
9289
  }
9256
9290
  </div>
9257
9291
  </div>
9258
9292
  </ion-content>
9259
-
9293
+
9260
9294
  <!-- Fixed message composer -->
9261
9295
  @if (!loading && !error) {
9262
9296
  <div class="message-composer-fixed">
@@ -9274,15 +9308,7 @@ class DsMobileChatModalComponent {
9274
9308
  }
9275
9309
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: DsMobileChatModalComponent, decorators: [{
9276
9310
  type: Component,
9277
- args: [{ selector: 'ds-mobile-chat-modal', standalone: true, imports: [
9278
- CommonModule,
9279
- IonContent,
9280
- DsIconButtonComponent,
9281
- DsIconComponent,
9282
- DsAvatarWithBadgeComponent,
9283
- DsMobileMessageComposerComponent,
9284
- DsMobileMessageBubbleComponent
9285
- ], schemas: [CUSTOM_ELEMENTS_SCHEMA], template: `
9311
+ args: [{ selector: 'ds-mobile-chat-modal', standalone: true, imports: [CommonModule, IonContent, DsIconButtonComponent, DsIconComponent, DsAvatarWithBadgeComponent, DsMobileMessageComposerComponent, DsMobileMessageBubbleComponent], schemas: [CUSTOM_ELEMENTS_SCHEMA], template: `
9286
9312
  <ion-content [fullscreen]="true" [scrollY]="true" class="chat-modal-content">
9287
9313
  <div class="chat-modal-wrapper">
9288
9314
  <!-- Header with participant info -->
@@ -9294,77 +9320,67 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.15", ngImpo
9294
9320
  [initials]="participant().avatarInitials || ''"
9295
9321
  [type]="participant().avatarType || 'initials'"
9296
9322
  [src]="participant().avatarSrc || ''"
9297
- size="md"
9298
- />
9323
+ size="md" />
9299
9324
  <div class="participant-details">
9300
9325
  <div class="author-name">{{ participant().name }}</div>
9301
9326
  @if (participant().role) {
9302
- <div class="author-meta">{{ participant().role }}</div>
9327
+ <div class="author-meta">{{ participant().role }}</div>
9303
9328
  }
9304
9329
  </div>
9305
9330
  </div>
9306
-
9331
+
9307
9332
  <!-- Close button -->
9308
- <ds-icon-button
9309
- icon="remixCloseLine"
9310
- variant="secondary"
9311
- size="lg"
9312
- (clicked)="close()"
9313
- class="close-button"
9314
- aria-label="Close chat">
9315
- </ds-icon-button>
9333
+ <ds-icon-button icon="remixCloseLine" variant="secondary" size="lg" (clicked)="close()" class="close-button" aria-label="Close chat"> </ds-icon-button>
9316
9334
  </div>
9317
9335
  </div>
9318
9336
 
9319
9337
  <!-- Messages thread -->
9320
9338
  <div class="chat-messages-container">
9321
9339
  @if (loading) {
9322
- <!-- Loading State -->
9323
- <div class="chat-loading-state">
9324
- <div class="loading-spinner"></div>
9325
- <p class="loading-text">Loading messages...</p>
9326
- </div>
9340
+ <!-- Loading State -->
9341
+ <div class="chat-loading-state">
9342
+ <div class="loading-spinner"></div>
9343
+ <p class="loading-text">Loading messages...</p>
9344
+ </div>
9327
9345
  } @else if (error) {
9328
- <!-- Error State -->
9329
- <div class="chat-error-state">
9330
- <ds-icon name="remixErrorWarningLine" size="48px" [style.color]="'var(--color-destructive-base)'" />
9331
- <h3 class="error-state-title">Error loading messages</h3>
9332
- <p class="error-state-description">{{ error }}</p>
9333
- </div>
9346
+ <!-- Error State -->
9347
+ <div class="chat-error-state">
9348
+ <ds-icon name="remixErrorWarningLine" size="48px" [style.color]="'var(--color-destructive-base)'" />
9349
+ <h3 class="error-state-title">Error loading messages</h3>
9350
+ <p class="error-state-description">{{ error }}</p>
9351
+ </div>
9334
9352
  } @else {
9335
- <!-- Messages List -->
9336
- <div class="messages-list">
9337
- @if (messages().length === 0) {
9338
- <!-- Empty State -->
9339
- <div class="messages-empty-state">
9340
- <h3 class="empty-state-title">No messages yet</h3>
9341
- <p class="empty-state-description">Start the conversation by sending a message</p>
9342
- </div>
9343
- } @else {
9344
- @for (message of messages(); track message.id) {
9345
- <ds-mobile-message-bubble
9346
- [content]="message.content"
9347
- [isOwnMessage]="message.isOwnMessage"
9348
- [timestamp]="message.timestamp"
9349
- [avatarInitials]="message.avatarInitials || ''"
9350
- [avatarType]="message.avatarType || 'initials'"
9351
- [avatarSrc]="message.avatarSrc || ''"
9352
- [attachments]="message.attachments"
9353
- [clickable]="true"
9354
- (attachmentClick)="handleAttachmentClick($event)"
9355
- (longPress)="handleMessageLongPress(message)">
9356
- </ds-mobile-message-bubble>
9357
- }
9358
- }
9353
+ <!-- Messages List -->
9354
+ <div class="messages-list">
9355
+ @if (messages().length === 0) {
9356
+ <!-- Empty State -->
9357
+ <div class="messages-empty-state">
9358
+ <h3 class="empty-state-title">No messages yet</h3>
9359
+ <p class="empty-state-description">Start the conversation by sending a message</p>
9359
9360
  </div>
9360
-
9361
- <!-- Bottom spacer for fixed composer -->
9362
- <div class="composer-spacer"></div>
9361
+ } @else { @for (message of messages(); track message.id) {
9362
+ <ds-mobile-message-bubble
9363
+ [content]="message.content"
9364
+ [isOwnMessage]="message.isOwnMessage"
9365
+ [timestamp]="message.timestamp"
9366
+ [avatarInitials]="message.avatarInitials || ''"
9367
+ [avatarType]="message.avatarType || 'initials'"
9368
+ [avatarSrc]="message.avatarSrc || ''"
9369
+ [attachments]="message.attachments"
9370
+ [clickable]="true"
9371
+ (attachmentClick)="handleAttachmentClick($event)"
9372
+ (longPress)="handleMessageLongPress(message)">
9373
+ </ds-mobile-message-bubble>
9374
+ } }
9375
+ </div>
9376
+
9377
+ <!-- Bottom spacer for fixed composer -->
9378
+ <div class="composer-spacer"></div>
9363
9379
  }
9364
9380
  </div>
9365
9381
  </div>
9366
9382
  </ion-content>
9367
-
9383
+
9368
9384
  <!-- Fixed message composer -->
9369
9385
  @if (!loading && !error) {
9370
9386
  <div class="message-composer-fixed">
@@ -9379,7 +9395,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.15", ngImpo
9379
9395
  </div>
9380
9396
  }
9381
9397
  `, styles: [".author-details{display:flex;flex-direction:column;gap:2px;min-width:0;flex:1}.author-name{font-family:Brockmann,sans-serif;font-size:var(--font-size-sm);font-weight:600;line-height:20px;letter-spacing:-.3px;color:var(--color-text-primary, #1a1a1a);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.author-meta{font-family:Brockmann,sans-serif;font-size:var(--font-size-xs);font-weight:400;line-height:1.2;letter-spacing:-.26px;color:var(--color-text-tertiary, #737373);display:flex;align-items:center;gap:6px}.author-meta .separator{color:var(--color-text-tertiary, #a0a0a0)}.lightbox-context .author-name,.overlay-context .author-name{color:#fffffff2}.lightbox-context .author-meta,.overlay-context .author-meta{color:#ffffffb3}.lightbox-context .author-meta .separator,.overlay-context .author-meta .separator{color:#ffffff80}.section-headline{font-size:var(--font-size-sm);font-weight:600;color:var(--text-color-default-primary);padding:16px 0;margin:0;letter-spacing:-.2px;display:flex;align-items:center;gap:6px}.empty-state-title{font-family:Brockmann,sans-serif;font-size:var(--font-size-base);font-weight:600;line-height:1.3;color:var(--text-color-default-primary, #202227);margin:0 0 8px}.empty-state-description{font-family:Brockmann,sans-serif;font-size:var(--font-size-sm);font-weight:400;line-height:1.4;color:var(--text-color-default-secondary, #545B66);margin:0}\n", ":host{display:block;position:relative;height:100%;width:100%;max-width:640px;margin:0 auto}.chat-modal-content{--background: var(--color-background-neutral-primary, #ffffff)}.chat-modal-wrapper{display:flex;flex-direction:column;min-height:100%;min-height:100dvh;background:var(--color-background-neutral-primary, #ffffff);width:100%}.chat-modal-header{position:sticky;top:0;z-index:10;background:var(--color-background-neutral-primary, #ffffff);border-bottom:1px solid var(--border-color-default);padding:0 16px}.header-content{display:flex;align-items:center;justify-content:space-between;gap:12px;min-height:72px}.participant-info{display:flex;align-items:center;gap:12px;flex:1;min-width:0}.participant-details{display:flex;flex-direction:column;min-width:0;flex:1}.close-button{flex-shrink:0;border-radius:50%}.close-button::ng-deep button{border-radius:50%!important;width:36px!important;height:36px!important;min-width:36px!important;min-height:36px!important;padding:0!important;display:flex!important;align-items:center!important;justify-content:center!important}.chat-messages-container{background:var(--color-background-neutral-secondary, #f5f5f5);display:flex;flex-direction:column;gap:16px;width:100%;max-width:640px;margin:0 auto;padding:16px 0 20px;flex:1}.messages-list{display:flex;flex-direction:column;width:100%;padding:0 20px;align-items:stretch}.messages-list ds-mobile-message-bubble{width:100%;display:flex}.messages-empty-state{display:flex;flex-direction:column;align-items:center;justify-content:center;padding:60px 20px;text-align:center}.empty-state-title{font-family:Brockmann,sans-serif;font-size:var(--font-size-base);font-weight:600;line-height:1.3;color:var(--color-text-primary, #1a1a1a);margin:0 0 8px}.empty-state-description{font-family:Brockmann,sans-serif;font-size:var(--font-size-sm);font-weight:400;line-height:1.4;color:var(--color-text-secondary, #737373);margin:0}.composer-spacer{height:calc(81px + env(safe-area-inset-bottom,0px))}.message-composer-fixed{position:fixed;bottom:0;left:0;right:0;z-index:1000;pointer-events:none;transform:translateY(calc(-1 * var(--keyboard-height, 0px)));transition:transform .3s ease-out;max-width:100vw}.message-composer-fixed ds-mobile-message-composer{pointer-events:auto;box-shadow:100px 150px 0 150px var(--color-background-neutral-primary, #ffffff)}.chat-loading-state{display:flex;flex-direction:column;align-items:center;justify-content:center;padding:60px 20px;text-align:center}.loading-spinner{width:48px;height:48px;border:3px solid var(--color-background-neutral-secondary, #f0f0f0);border-top-color:var(--color-primary-base, #2563eb);border-radius:50%;animation:spin 1s linear infinite}@keyframes spin{to{transform:rotate(360deg)}}.loading-text{font-family:Brockmann,sans-serif;font-size:var(--font-size-sm);font-weight:400;line-height:1.4;color:var(--color-text-secondary, #737373);margin-top:16px}.chat-error-state{display:flex;flex-direction:column;align-items:center;justify-content:center;padding:60px 20px;text-align:center;gap:16px}.error-state-title{font-family:Brockmann,sans-serif;font-size:var(--font-size-base);font-weight:600;line-height:1.3;color:var(--color-text-primary, #1a1a1a);margin:0}.error-state-description{font-family:Brockmann,sans-serif;font-size:var(--font-size-sm);font-weight:400;line-height:1.4;color:var(--color-text-secondary, #737373);margin:0}@supports (padding: env(safe-area-inset-bottom)){.chat-messages-container{padding-bottom:calc(20px + env(safe-area-inset-bottom))}}\n"] }]
9382
- }], ctorParameters: () => [{ type: i1.ModalController }], propDecorators: { chatData: [{
9398
+ }], ctorParameters: () => [{ type: i1.ModalController }, { type: DsMobileLightboxService }], propDecorators: { chatData: [{
9383
9399
  type: Input
9384
9400
  }], loading: [{
9385
9401
  type: Input