ugcinc 3.84.6 → 3.85.0

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 (120) hide show
  1. package/dist/index.d.ts +2 -1
  2. package/dist/index.js +6 -1
  3. package/dist/render/Root.d.ts +12 -0
  4. package/dist/render/Root.js +508 -0
  5. package/dist/render/components/CaptionOverlay.d.ts +21 -0
  6. package/dist/render/components/CaptionOverlay.js +210 -0
  7. package/dist/render/components/ImageElement.d.ts +26 -0
  8. package/dist/render/components/ImageElement.js +88 -0
  9. package/dist/render/components/TextElement.d.ts +30 -0
  10. package/dist/render/components/TextElement.js +390 -0
  11. package/dist/render/components/VideoElement.d.ts +30 -0
  12. package/dist/render/components/VideoElement.js +108 -0
  13. package/dist/render/components/index.d.ts +7 -0
  14. package/dist/render/components/index.js +14 -0
  15. package/dist/render/compositions/AutoCaptionComposition.d.ts +42 -0
  16. package/dist/render/compositions/AutoCaptionComposition.js +29 -0
  17. package/dist/render/compositions/DmComposition/BaseDmComposition.d.ts +112 -0
  18. package/dist/render/compositions/DmComposition/BaseDmComposition.js +212 -0
  19. package/dist/render/compositions/DmComposition/DebugOverlay.d.ts +20 -0
  20. package/dist/render/compositions/DmComposition/DebugOverlay.js +258 -0
  21. package/dist/render/compositions/DmComposition/index.d.ts +6 -0
  22. package/dist/render/compositions/DmComposition/index.js +10 -0
  23. package/dist/render/compositions/IMessageDmComposition/convertPropsToElements.d.ts +27 -0
  24. package/dist/render/compositions/IMessageDmComposition/convertPropsToElements.js +629 -0
  25. package/dist/render/compositions/IMessageDmComposition/index.d.ts +20 -0
  26. package/dist/render/compositions/IMessageDmComposition/index.js +485 -0
  27. package/dist/render/compositions/IMessageDmComposition/types.d.ts +756 -0
  28. package/dist/render/compositions/IMessageDmComposition/types.js +225 -0
  29. package/dist/render/compositions/ImageEditorComposition.d.ts +74 -0
  30. package/dist/render/compositions/ImageEditorComposition.js +351 -0
  31. package/dist/render/compositions/InstagramDmComposition/convertPropsToElements.d.ts +60 -0
  32. package/dist/render/compositions/InstagramDmComposition/convertPropsToElements.js +1318 -0
  33. package/dist/render/compositions/InstagramDmComposition/convertPublicToProps.d.ts +30 -0
  34. package/dist/render/compositions/InstagramDmComposition/convertPublicToProps.js +131 -0
  35. package/dist/render/compositions/InstagramDmComposition/index.d.ts +16 -0
  36. package/dist/render/compositions/InstagramDmComposition/index.js +374 -0
  37. package/dist/render/compositions/InstagramDmComposition/theme.d.ts +42 -0
  38. package/dist/render/compositions/InstagramDmComposition/theme.js +55 -0
  39. package/dist/render/compositions/InstagramDmComposition/types.d.ts +215 -0
  40. package/dist/render/compositions/InstagramDmComposition/types.js +7 -0
  41. package/dist/render/compositions/ScreenshotAnimation.d.ts +14 -0
  42. package/dist/render/compositions/ScreenshotAnimation.js +268 -0
  43. package/dist/render/compositions/VideoEditorComposition.d.ts +45 -0
  44. package/dist/render/compositions/VideoEditorComposition.js +307 -0
  45. package/dist/render/compositions/index.d.ts +12 -0
  46. package/dist/render/compositions/index.js +43 -0
  47. package/dist/render/compositions/messaging/components/MediaBubble.d.ts +22 -0
  48. package/dist/render/compositions/messaging/components/MediaBubble.js +25 -0
  49. package/dist/render/compositions/messaging/components/MessageBubble.d.ts +35 -0
  50. package/dist/render/compositions/messaging/components/MessageBubble.js +34 -0
  51. package/dist/render/compositions/messaging/components/ProfilePic.d.ts +23 -0
  52. package/dist/render/compositions/messaging/components/ProfilePic.js +37 -0
  53. package/dist/render/compositions/messaging/components/Reaction.d.ts +23 -0
  54. package/dist/render/compositions/messaging/components/Reaction.js +19 -0
  55. package/dist/render/compositions/messaging/components/TypingIndicator.d.ts +25 -0
  56. package/dist/render/compositions/messaging/components/TypingIndicator.js +66 -0
  57. package/dist/render/compositions/messaging/index.d.ts +14 -0
  58. package/dist/render/compositions/messaging/index.js +43 -0
  59. package/dist/render/compositions/messaging/types.d.ts +148 -0
  60. package/dist/render/compositions/messaging/types.js +21 -0
  61. package/dist/render/compositions/messaging/utils/bubbleRadius.d.ts +45 -0
  62. package/dist/render/compositions/messaging/utils/bubbleRadius.js +84 -0
  63. package/dist/render/compositions/messaging/utils/groupMessages.d.ts +41 -0
  64. package/dist/render/compositions/messaging/utils/groupMessages.js +110 -0
  65. package/dist/render/data/phone-top-nav.d.ts +1 -0
  66. package/dist/render/data/phone-top-nav.js +4 -0
  67. package/dist/render/data/screenshot.d.ts +164 -0
  68. package/dist/render/data/screenshot.js +63 -0
  69. package/dist/render/hooks/index.d.ts +54 -0
  70. package/dist/render/hooks/index.js +132 -0
  71. package/dist/render/index.d.ts +12 -0
  72. package/dist/render/index.js +36 -0
  73. package/dist/render/types/base.d.ts +148 -0
  74. package/dist/render/types/base.js +5 -0
  75. package/dist/render/types/caption.d.ts +105 -0
  76. package/dist/render/types/caption.js +8 -0
  77. package/dist/render/types/crop.d.ts +60 -0
  78. package/dist/render/types/crop.js +8 -0
  79. package/dist/render/types/deduplication.d.ts +284 -0
  80. package/dist/render/types/deduplication.js +240 -0
  81. package/dist/render/types/editor.d.ts +97 -0
  82. package/dist/render/types/editor.js +10 -0
  83. package/dist/render/types/element.d.ts +139 -0
  84. package/dist/render/types/element.js +19 -0
  85. package/dist/render/types/index.d.ts +20 -0
  86. package/dist/render/types/index.js +24 -0
  87. package/dist/render/types/instagram-dm-public.d.ts +60 -0
  88. package/dist/render/types/instagram-dm-public.js +8 -0
  89. package/dist/render/types/position.d.ts +59 -0
  90. package/dist/render/types/position.js +5 -0
  91. package/dist/render/types/screenshot.d.ts +57 -0
  92. package/dist/render/types/screenshot.js +34 -0
  93. package/dist/render/types/segment.d.ts +163 -0
  94. package/dist/render/types/segment.js +8 -0
  95. package/dist/render/types/video.d.ts +192 -0
  96. package/dist/render/types/video.js +14 -0
  97. package/dist/render/utils/captionPresets.d.ts +38 -0
  98. package/dist/render/utils/captionPresets.js +168 -0
  99. package/dist/render/utils/cropBounds.d.ts +20 -0
  100. package/dist/render/utils/cropBounds.js +166 -0
  101. package/dist/render/utils/defaults.d.ts +74 -0
  102. package/dist/render/utils/defaults.js +91 -0
  103. package/dist/render/utils/emoji.d.ts +40 -0
  104. package/dist/render/utils/emoji.js +105 -0
  105. package/dist/render/utils/fit.d.ts +35 -0
  106. package/dist/render/utils/fit.js +63 -0
  107. package/dist/render/utils/fonts.d.ts +55 -0
  108. package/dist/render/utils/fonts.js +191 -0
  109. package/dist/render/utils/index.d.ts +14 -0
  110. package/dist/render/utils/index.js +73 -0
  111. package/dist/render/utils/positionResolver.d.ts +64 -0
  112. package/dist/render/utils/positionResolver.js +508 -0
  113. package/dist/render/utils/text.d.ts +50 -0
  114. package/dist/render/utils/text.js +177 -0
  115. package/dist/render/utils/timeline.d.ts +62 -0
  116. package/dist/render/utils/timeline.js +172 -0
  117. package/dist/render.d.ts +1 -1
  118. package/dist/types.d.ts +136 -17
  119. package/dist/types.js +58 -0
  120. package/package.json +20 -6
@@ -0,0 +1,629 @@
1
+ "use strict";
2
+ /**
3
+ * Convert iMessage DM Composition props to ImageEditorElements
4
+ *
5
+ * This module transforms the composition props into renderable message elements.
6
+ * Header, footer, and status bar elements are rendered directly in the composition JSX.
7
+ */
8
+ Object.defineProperty(exports, "__esModule", { value: true });
9
+ exports.SENDER_IMAGE_INPUT_ID_PREFIX = void 0;
10
+ exports.convertPropsToElements = convertPropsToElements;
11
+ const fonts_1 = require("../../utils/fonts");
12
+ /** Input ID prefix for sender images */
13
+ exports.SENDER_IMAGE_INPUT_ID_PREFIX = 'sender-image-';
14
+ // ============================================================================
15
+ // DEFAULT VALUES
16
+ // ============================================================================
17
+ const MESSAGE_DEFAULTS = {
18
+ // Message area bounds
19
+ messageAreaTop: 450,
20
+ messageAreaBottom: 2310,
21
+ // Message colors
22
+ senderBubbleColor: '#1a42d5',
23
+ recipientBubbleColor: '#303032',
24
+ bubbleTextColor: '#ffffff',
25
+ recipientTextColor: '#ffffff',
26
+ // Message typography
27
+ messageFontSize: 53,
28
+ messageLineHeight: 60,
29
+ messageLetterSpacing: 1.2,
30
+ // Message padding (inside bubble)
31
+ messagePaddingTop: 23,
32
+ messagePaddingBottom: 22,
33
+ // Sender padding
34
+ senderPaddingLeft: 34,
35
+ senderPaddingRight: 48,
36
+ multiLineSenderPaddingLeft: 34,
37
+ // Recipient padding
38
+ recipientPaddingLeft: 37,
39
+ recipientPaddingRight: 36,
40
+ multiLineRecipientPaddingRight: 48,
41
+ // Bubble corner radii
42
+ bubbleRadiusNormal: 51,
43
+ bubbleRadiusGrouped: 51,
44
+ // Message spacing
45
+ messageGapInGroup: 3,
46
+ messageGapSameUser: 8,
47
+ messageGapDifferentUser: 70,
48
+ // Bubble positioning
49
+ recipientBubbleLeft: 48,
50
+ senderBubbleRight: 1158,
51
+ bubbleMaxWidth: 900,
52
+ // Sender image attachment
53
+ senderImageLeft: 336,
54
+ senderImageHeight: 1120,
55
+ senderImageGapToText: 3,
56
+ senderImageBorderRadius: 51,
57
+ // Message area header text (above first message)
58
+ messageHeaderImessageText: 'iMessage',
59
+ messageHeaderTimestampText: 'Today 12:16 PM',
60
+ messageHeaderTimestampGap: 30,
61
+ messageHeaderImessageGap: 5,
62
+ messageHeaderTextColor: '#626167',
63
+ messageHeaderFontSize: 34,
64
+ messageHeaderLetterSpacing: 1.25,
65
+ messageHeaderLeft: 0,
66
+ messageHeaderRight: 1206,
67
+ // Message tails (on last message in group)
68
+ showMessageTails: true,
69
+ // Read receipt (under last sender message)
70
+ showReadReceipt: true,
71
+ readReceiptText: 'Read 12:16 PM',
72
+ readReceiptGap: 52,
73
+ readReceiptFontSize: 35,
74
+ readReceiptLetterSpacing: 0.9,
75
+ readReceiptColor: '#626167',
76
+ readReceiptLeft: 0,
77
+ readReceiptRight: 1158,
78
+ };
79
+ // Use the shared font family to ensure consistency with TextElement rendering
80
+ const FONT_FAMILY = (0, fonts_1.getFontFamily)('apple');
81
+ /**
82
+ * Calculate the actual width for auto-width text AND the line breaks.
83
+ * Uses DOM-based measurement to ensure the same fonts are used as CSS rendering.
84
+ */
85
+ function calculateAutoWidthAndLines({ text, maxWidth, paddingLeft, paddingRight, fontSize, letterSpacing, }) {
86
+ // Check if we're in a browser environment
87
+ if (typeof document === 'undefined') {
88
+ return { width: maxWidth, lines: [text] };
89
+ }
90
+ // Available width for text content (excluding padding)
91
+ const availableWidth = maxWidth - paddingLeft - paddingRight;
92
+ // Handle edge case of very small or zero available width
93
+ if (availableWidth <= 0) {
94
+ return { width: maxWidth, lines: [text] };
95
+ }
96
+ // Create a measurement span with exact same styling as render
97
+ const measureSpan = document.createElement('span');
98
+ measureSpan.style.cssText = `
99
+ position: absolute;
100
+ visibility: hidden;
101
+ pointer-events: none;
102
+ font-family: ${FONT_FAMILY};
103
+ font-size: ${fontSize}px;
104
+ font-weight: normal;
105
+ letter-spacing: ${letterSpacing}px;
106
+ white-space: nowrap;
107
+ `;
108
+ document.body.appendChild(measureSpan);
109
+ const measureText = (textToMeasure) => {
110
+ measureSpan.textContent = textToMeasure;
111
+ return measureSpan.getBoundingClientRect().width;
112
+ };
113
+ // Split text into words, preserving spaces for accurate measurement
114
+ const words = text.split(/(\s+)/);
115
+ const lines = [];
116
+ let currentLine = '';
117
+ for (let i = 0; i < words.length; i++) {
118
+ const word = words[i];
119
+ if (word === '\n' || word === '\r\n') {
120
+ if (currentLine)
121
+ lines.push(currentLine);
122
+ currentLine = '';
123
+ continue;
124
+ }
125
+ if (!word)
126
+ continue;
127
+ if (/^\s+$/.test(word)) {
128
+ if (currentLine)
129
+ currentLine += word;
130
+ continue;
131
+ }
132
+ const testLine = currentLine + word;
133
+ const testWidth = measureText(testLine);
134
+ const WRAP_TOLERANCE = 0.5;
135
+ if (testWidth > availableWidth + WRAP_TOLERANCE) {
136
+ // Push current line if it has content
137
+ if (currentLine.trim()) {
138
+ lines.push(currentLine.trimEnd());
139
+ currentLine = '';
140
+ }
141
+ const wordWidth = measureText(word);
142
+ // Handle words longer than available width (break character by character)
143
+ if (wordWidth > availableWidth) {
144
+ let remainingWord = word;
145
+ while (remainingWord) {
146
+ let breakPoint = 1;
147
+ for (let j = 1; j <= remainingWord.length; j++) {
148
+ measureSpan.textContent = remainingWord.substring(0, j);
149
+ if (measureSpan.getBoundingClientRect().width > availableWidth) {
150
+ breakPoint = Math.max(1, j - 1);
151
+ break;
152
+ }
153
+ breakPoint = j;
154
+ }
155
+ if (breakPoint >= remainingWord.length) {
156
+ currentLine = remainingWord;
157
+ break;
158
+ }
159
+ else {
160
+ lines.push(remainingWord.substring(0, breakPoint));
161
+ remainingWord = remainingWord.substring(breakPoint);
162
+ }
163
+ }
164
+ }
165
+ else {
166
+ // Word fits on its own line
167
+ currentLine = word;
168
+ }
169
+ }
170
+ else {
171
+ currentLine = testLine;
172
+ }
173
+ }
174
+ if (currentLine.trim())
175
+ lines.push(currentLine.trimEnd());
176
+ if (lines.length === 0)
177
+ lines.push('');
178
+ // Find the widest line
179
+ let widestLineWidth = 0;
180
+ for (const line of lines) {
181
+ const lineWidth = measureText(line);
182
+ widestLineWidth = Math.max(widestLineWidth, lineWidth);
183
+ }
184
+ document.body.removeChild(measureSpan);
185
+ const calculatedWidth = Math.min(widestLineWidth + paddingLeft + paddingRight, maxWidth);
186
+ return { width: calculatedWidth, lines };
187
+ }
188
+ /**
189
+ * Calculate message positions (bottom-anchored)
190
+ */
191
+ function calculateMessagePositions(props) {
192
+ const messages = props.messages ?? [];
193
+ if (messages.length === 0)
194
+ return [];
195
+ // Get values with defaults
196
+ const messageAreaBottom = props.messageAreaBottom ?? MESSAGE_DEFAULTS.messageAreaBottom;
197
+ const messageFontSize = props.messageFontSize ?? MESSAGE_DEFAULTS.messageFontSize;
198
+ const messageLineHeight = props.messageLineHeight ?? MESSAGE_DEFAULTS.messageLineHeight;
199
+ const messageLetterSpacing = props.messageLetterSpacing ?? MESSAGE_DEFAULTS.messageLetterSpacing;
200
+ const messagePaddingTop = props.messagePaddingTop ?? MESSAGE_DEFAULTS.messagePaddingTop;
201
+ const messagePaddingBottom = props.messagePaddingBottom ?? MESSAGE_DEFAULTS.messagePaddingBottom;
202
+ const senderPaddingLeft = props.senderPaddingLeft ?? MESSAGE_DEFAULTS.senderPaddingLeft;
203
+ const senderPaddingRight = props.senderPaddingRight ?? MESSAGE_DEFAULTS.senderPaddingRight;
204
+ const multiLineSenderPaddingLeft = props.multiLineSenderPaddingLeft ?? MESSAGE_DEFAULTS.multiLineSenderPaddingLeft;
205
+ const recipientPaddingLeft = props.recipientPaddingLeft ?? MESSAGE_DEFAULTS.recipientPaddingLeft;
206
+ const recipientPaddingRight = props.recipientPaddingRight ?? MESSAGE_DEFAULTS.recipientPaddingRight;
207
+ const multiLineRecipientPaddingRight = props.multiLineRecipientPaddingRight ?? MESSAGE_DEFAULTS.multiLineRecipientPaddingRight;
208
+ const messageGapInGroup = props.messageGapInGroup ?? MESSAGE_DEFAULTS.messageGapInGroup;
209
+ const messageGapSameUser = props.messageGapSameUser ?? MESSAGE_DEFAULTS.messageGapSameUser;
210
+ const messageGapDifferentUser = props.messageGapDifferentUser ?? MESSAGE_DEFAULTS.messageGapDifferentUser;
211
+ const recipientBubbleLeft = props.recipientBubbleLeft ?? MESSAGE_DEFAULTS.recipientBubbleLeft;
212
+ const senderBubbleRight = props.senderBubbleRight ?? MESSAGE_DEFAULTS.senderBubbleRight;
213
+ const bubbleMaxWidth = props.bubbleMaxWidth ?? MESSAGE_DEFAULTS.bubbleMaxWidth;
214
+ const senderImageLeft = props.senderImageLeft ?? MESSAGE_DEFAULTS.senderImageLeft;
215
+ const senderImageHeight = props.senderImageHeight ?? MESSAGE_DEFAULTS.senderImageHeight;
216
+ const senderImageGapToText = props.senderImageGapToText ?? MESSAGE_DEFAULTS.senderImageGapToText;
217
+ const calculatedMessages = [];
218
+ let currentBottom = messageAreaBottom;
219
+ // Process messages in reverse order (last message at bottom)
220
+ for (let i = messages.length - 1; i >= 0; i--) {
221
+ const message = messages[i];
222
+ if (!message)
223
+ continue;
224
+ const prevMessage = i > 0 ? messages[i - 1] : null;
225
+ const nextMessage = i < messages.length - 1 ? messages[i + 1] : null;
226
+ // Determine grouping
227
+ const isGroupedWithPrev = message.groupWithPrevious && prevMessage?.sender === message.sender;
228
+ const isGroupedWithNext = nextMessage?.groupWithPrevious && nextMessage?.sender === message.sender;
229
+ const isFirstInGroup = !isGroupedWithPrev;
230
+ const isLastInGroup = !isGroupedWithNext;
231
+ const isSingleMessage = isFirstInGroup && isLastInGroup;
232
+ // Calculate gap above this message
233
+ let gap = 0;
234
+ if (i < messages.length - 1) {
235
+ const belowMessage = messages[i + 1];
236
+ if (belowMessage?.groupWithPrevious && belowMessage.sender === message.sender) {
237
+ gap = messageGapInGroup;
238
+ }
239
+ else if (belowMessage?.sender === message.sender) {
240
+ gap = messageGapSameUser;
241
+ }
242
+ else {
243
+ gap = messageGapDifferentUser;
244
+ }
245
+ }
246
+ // Get sender-specific padding (use base padding for initial calculation)
247
+ const basePaddingLeft = message.sender === 'user' ? senderPaddingLeft : recipientPaddingLeft;
248
+ const basePaddingRight = message.sender === 'user' ? senderPaddingRight : recipientPaddingRight;
249
+ // Calculate width and line breaks with base padding
250
+ const { width: bubbleWidth, lines } = calculateAutoWidthAndLines({
251
+ text: message.text,
252
+ maxWidth: bubbleMaxWidth,
253
+ paddingLeft: basePaddingLeft,
254
+ paddingRight: basePaddingRight,
255
+ fontSize: messageFontSize,
256
+ letterSpacing: messageLetterSpacing,
257
+ });
258
+ // Determine if multiline and get final padding
259
+ const isMultiLine = lines.length > 1;
260
+ const finalPaddingLeft = message.sender === 'user' && isMultiLine
261
+ ? multiLineSenderPaddingLeft
262
+ : basePaddingLeft;
263
+ const finalPaddingRight = message.sender === 'recipient' && isMultiLine
264
+ ? multiLineRecipientPaddingRight
265
+ : basePaddingRight;
266
+ // Calculate bubble height based on actual line count
267
+ const lineCount = lines.length;
268
+ const textHeight = lineCount * messageLineHeight;
269
+ const bubbleHeight = textHeight + messagePaddingTop + messagePaddingBottom;
270
+ // Calculate position
271
+ const bubbleBottom = currentBottom - gap;
272
+ const bubbleTop = bubbleBottom - bubbleHeight;
273
+ // Calculate left position based on sender
274
+ let bubbleLeft;
275
+ if (message.sender === 'recipient') {
276
+ bubbleLeft = recipientBubbleLeft;
277
+ }
278
+ else {
279
+ bubbleLeft = senderBubbleRight - bubbleWidth;
280
+ }
281
+ // Calculate image dimensions if present (sender only for now)
282
+ let imageWidth;
283
+ let imageHeight;
284
+ let imageY;
285
+ if (message.imageUrl && message.sender === 'user') {
286
+ imageWidth = senderBubbleRight - senderImageLeft;
287
+ imageHeight = senderImageHeight;
288
+ imageY = bubbleTop - senderImageGapToText - imageHeight;
289
+ }
290
+ calculatedMessages.unshift({
291
+ message,
292
+ top: bubbleTop,
293
+ bottom: bubbleBottom,
294
+ left: bubbleLeft,
295
+ width: bubbleWidth,
296
+ height: bubbleHeight,
297
+ lines,
298
+ isFirstInGroup,
299
+ isLastInGroup,
300
+ isSingleMessage,
301
+ isMultiLine,
302
+ paddingLeft: finalPaddingLeft,
303
+ paddingRight: finalPaddingRight,
304
+ imageWidth,
305
+ imageHeight,
306
+ imageY,
307
+ });
308
+ // Move currentBottom up - account for image if present
309
+ if (message.imageUrl && imageY !== undefined) {
310
+ currentBottom = imageY;
311
+ }
312
+ else {
313
+ currentBottom = bubbleTop;
314
+ }
315
+ }
316
+ return calculatedMessages;
317
+ }
318
+ /**
319
+ * Get corner radii for a message based on grouping
320
+ */
321
+ function getCornerRadii(cm, bubbleRadiusNormal, bubbleRadiusGrouped) {
322
+ const isUser = cm.message.sender === 'user';
323
+ if (cm.isSingleMessage) {
324
+ // All corners normal
325
+ return {
326
+ topLeft: bubbleRadiusNormal,
327
+ topRight: bubbleRadiusNormal,
328
+ bottomLeft: bubbleRadiusNormal,
329
+ bottomRight: bubbleRadiusNormal,
330
+ };
331
+ }
332
+ if (isUser) {
333
+ // User messages: right side has grouped corners when connected
334
+ return {
335
+ topLeft: bubbleRadiusNormal,
336
+ topRight: cm.isFirstInGroup ? bubbleRadiusNormal : bubbleRadiusGrouped,
337
+ bottomLeft: bubbleRadiusNormal,
338
+ bottomRight: cm.isLastInGroup ? bubbleRadiusNormal : bubbleRadiusGrouped,
339
+ };
340
+ }
341
+ else {
342
+ // Recipient messages: left side has grouped corners when connected
343
+ return {
344
+ topLeft: cm.isFirstInGroup ? bubbleRadiusNormal : bubbleRadiusGrouped,
345
+ topRight: bubbleRadiusNormal,
346
+ bottomLeft: cm.isLastInGroup ? bubbleRadiusNormal : bubbleRadiusGrouped,
347
+ bottomRight: bubbleRadiusNormal,
348
+ };
349
+ }
350
+ }
351
+ /**
352
+ * Generate message bubble elements (single element with autoWidth)
353
+ */
354
+ function generateMessageElements(props, calculatedMessages) {
355
+ if (calculatedMessages.length === 0) {
356
+ return [];
357
+ }
358
+ const elements = [];
359
+ // Get values with defaults
360
+ const senderBubbleColor = props.senderBubbleColor ?? MESSAGE_DEFAULTS.senderBubbleColor;
361
+ const recipientBubbleColor = props.recipientBubbleColor ?? MESSAGE_DEFAULTS.recipientBubbleColor;
362
+ const bubbleTextColor = props.bubbleTextColor ?? MESSAGE_DEFAULTS.bubbleTextColor;
363
+ const recipientTextColor = props.recipientTextColor ?? MESSAGE_DEFAULTS.recipientTextColor;
364
+ const messageFontSize = props.messageFontSize ?? MESSAGE_DEFAULTS.messageFontSize;
365
+ const messageLineHeight = props.messageLineHeight ?? MESSAGE_DEFAULTS.messageLineHeight;
366
+ const messageLetterSpacing = props.messageLetterSpacing ?? MESSAGE_DEFAULTS.messageLetterSpacing;
367
+ const messagePaddingTop = props.messagePaddingTop ?? MESSAGE_DEFAULTS.messagePaddingTop;
368
+ const messagePaddingBottom = props.messagePaddingBottom ?? MESSAGE_DEFAULTS.messagePaddingBottom;
369
+ const bubbleRadiusNormal = props.bubbleRadiusNormal ?? MESSAGE_DEFAULTS.bubbleRadiusNormal;
370
+ const bubbleRadiusGrouped = props.bubbleRadiusGrouped ?? MESSAGE_DEFAULTS.bubbleRadiusGrouped;
371
+ const bubbleMaxWidth = props.bubbleMaxWidth ?? MESSAGE_DEFAULTS.bubbleMaxWidth;
372
+ const recipientBubbleLeft = props.recipientBubbleLeft ?? MESSAGE_DEFAULTS.recipientBubbleLeft;
373
+ const senderBubbleRight = props.senderBubbleRight ?? MESSAGE_DEFAULTS.senderBubbleRight;
374
+ const senderImageLeft = props.senderImageLeft ?? MESSAGE_DEFAULTS.senderImageLeft;
375
+ const senderImageBorderRadius = props.senderImageBorderRadius ?? MESSAGE_DEFAULTS.senderImageBorderRadius;
376
+ // Calculate lineHeight as a multiplier (TextElement expects a multiplier)
377
+ const lineHeightMultiplier = messageLineHeight / messageFontSize;
378
+ for (let i = 0; i < calculatedMessages.length; i++) {
379
+ const cm = calculatedMessages[i];
380
+ const isUser = cm.message.sender === 'user';
381
+ const bubbleColor = isUser ? senderBubbleColor : recipientBubbleColor;
382
+ const textColor = isUser ? bubbleTextColor : recipientTextColor;
383
+ // Image element (sender only for now)
384
+ if (cm.message.imageUrl && cm.imageWidth && cm.imageHeight && cm.imageY !== undefined && isUser) {
385
+ elements.push({
386
+ id: `image-${cm.message.id}`,
387
+ type: 'image',
388
+ x: senderImageLeft,
389
+ y: cm.imageY,
390
+ width: cm.imageWidth,
391
+ height: cm.imageHeight,
392
+ zIndex: 5 + i,
393
+ inputId: `${exports.SENDER_IMAGE_INPUT_ID_PREFIX}${cm.message.id}`,
394
+ borderRadius: senderImageBorderRadius,
395
+ fit: 'contain',
396
+ });
397
+ }
398
+ // Get corner radii based on grouping
399
+ const radii = getCornerRadii(cm, bubbleRadiusNormal, bubbleRadiusGrouped);
400
+ // Calculate element x position for autoWidth alignment
401
+ // For sender: x is at (senderBubbleRight - bubbleMaxWidth), boxAlign: 'right'
402
+ // For recipient: x is at recipientBubbleLeft, boxAlign: 'left'
403
+ const elementX = isUser
404
+ ? senderBubbleRight - bubbleMaxWidth
405
+ : recipientBubbleLeft;
406
+ // Join lines with newlines for the text content
407
+ const textContent = cm.lines.join('\n');
408
+ // Single element with text + background (autoWidth handles sizing)
409
+ elements.push({
410
+ id: `message-${cm.message.id}`,
411
+ type: 'text',
412
+ x: elementX,
413
+ y: cm.top,
414
+ width: bubbleMaxWidth,
415
+ height: cm.height,
416
+ zIndex: 5 + i,
417
+ text: textContent,
418
+ font: 'apple',
419
+ fontWeight: 400,
420
+ fontSize: messageFontSize,
421
+ letterSpacing: messageLetterSpacing,
422
+ lineHeight: lineHeightMultiplier,
423
+ color: textColor,
424
+ textAlign: 'left',
425
+ verticalAlign: 'middle',
426
+ backgroundColor: bubbleColor,
427
+ backgroundOpacity: 100,
428
+ backgroundBorderRadius: radii,
429
+ paddingTop: messagePaddingTop,
430
+ paddingRight: cm.paddingRight,
431
+ paddingBottom: messagePaddingBottom,
432
+ paddingLeft: cm.paddingLeft,
433
+ autoWidth: true,
434
+ boxAlign: isUser ? 'right' : 'left',
435
+ });
436
+ }
437
+ return elements;
438
+ }
439
+ /**
440
+ * Generate tail render data for last messages in groups
441
+ */
442
+ function generateTailData(props, calculatedMessages) {
443
+ const showMessageTails = props.showMessageTails ?? MESSAGE_DEFAULTS.showMessageTails;
444
+ if (!showMessageTails) {
445
+ return [];
446
+ }
447
+ const tails = [];
448
+ for (let i = 0; i < calculatedMessages.length; i++) {
449
+ const cm = calculatedMessages[i];
450
+ // Only add tail for last message in group
451
+ if (!cm.isLastInGroup) {
452
+ continue;
453
+ }
454
+ tails.push({
455
+ id: `tail-${cm.message.id}`,
456
+ isUser: cm.message.sender === 'user',
457
+ messageLeft: cm.left,
458
+ messageRight: cm.left + cm.width,
459
+ messageBottom: cm.bottom,
460
+ });
461
+ }
462
+ return tails;
463
+ }
464
+ // ============================================================================
465
+ // MAIN CONVERSION FUNCTION
466
+ // ============================================================================
467
+ /**
468
+ * Convert iMessage DM Composition props to ImageEditorElements
469
+ *
470
+ * Only generates message bubble elements. Header, footer, and status bar
471
+ * elements are rendered directly in the composition JSX.
472
+ */
473
+ /**
474
+ * Generate message area header text elements (iMessage + timestamp above first message)
475
+ */
476
+ function generateHeaderTextElements(props, calculatedMessages) {
477
+ if (calculatedMessages.length === 0) {
478
+ return [];
479
+ }
480
+ const elements = [];
481
+ // Get props with defaults
482
+ const messageHeaderImessageText = props.messageHeaderImessageText ?? MESSAGE_DEFAULTS.messageHeaderImessageText;
483
+ const messageHeaderTimestampText = props.messageHeaderTimestampText ?? MESSAGE_DEFAULTS.messageHeaderTimestampText;
484
+ const messageHeaderTimestampGap = props.messageHeaderTimestampGap ?? MESSAGE_DEFAULTS.messageHeaderTimestampGap;
485
+ const messageHeaderImessageGap = props.messageHeaderImessageGap ?? MESSAGE_DEFAULTS.messageHeaderImessageGap;
486
+ const messageHeaderTextColor = props.messageHeaderTextColor ?? MESSAGE_DEFAULTS.messageHeaderTextColor;
487
+ const messageHeaderFontSize = props.messageHeaderFontSize ?? MESSAGE_DEFAULTS.messageHeaderFontSize;
488
+ const messageHeaderLetterSpacing = props.messageHeaderLetterSpacing ?? MESSAGE_DEFAULTS.messageHeaderLetterSpacing;
489
+ const messageHeaderLeft = props.messageHeaderLeft ?? MESSAGE_DEFAULTS.messageHeaderLeft;
490
+ const messageHeaderRight = props.messageHeaderRight ?? MESSAGE_DEFAULTS.messageHeaderRight;
491
+ const messageHeaderWidth = messageHeaderRight - messageHeaderLeft;
492
+ // Find the top of the first message (accounting for image if present)
493
+ const firstMessage = calculatedMessages[0];
494
+ let firstMessageTop = firstMessage.top;
495
+ // If first message has an image, use the image top instead
496
+ if (firstMessage.imageY !== undefined) {
497
+ firstMessageTop = firstMessage.imageY;
498
+ }
499
+ // Timestamp text - positioned messageHeaderTimestampGap pixels above first message/image
500
+ const timestampBottom = firstMessageTop - messageHeaderTimestampGap;
501
+ const timestampTop = timestampBottom - messageHeaderFontSize;
502
+ elements.push({
503
+ id: 'message-header-timestamp',
504
+ type: 'text',
505
+ x: messageHeaderLeft,
506
+ y: timestampTop,
507
+ width: messageHeaderWidth,
508
+ height: messageHeaderFontSize,
509
+ zIndex: 4,
510
+ text: messageHeaderTimestampText,
511
+ font: 'apple',
512
+ fontWeight: 400,
513
+ fontSize: messageHeaderFontSize,
514
+ letterSpacing: messageHeaderLetterSpacing,
515
+ color: messageHeaderTextColor,
516
+ textAlign: 'center',
517
+ verticalAlign: 'middle',
518
+ });
519
+ // iMessage text - positioned messageHeaderImessageGap pixels above timestamp
520
+ const imessageBottom = timestampTop - messageHeaderImessageGap;
521
+ const imessageTop = imessageBottom - messageHeaderFontSize;
522
+ elements.push({
523
+ id: 'message-header-imessage',
524
+ type: 'text',
525
+ x: messageHeaderLeft,
526
+ y: imessageTop,
527
+ width: messageHeaderWidth,
528
+ height: messageHeaderFontSize,
529
+ zIndex: 4,
530
+ text: messageHeaderImessageText,
531
+ font: 'apple',
532
+ fontWeight: 400,
533
+ fontSize: messageHeaderFontSize,
534
+ letterSpacing: messageHeaderLetterSpacing,
535
+ color: messageHeaderTextColor,
536
+ textAlign: 'center',
537
+ verticalAlign: 'middle',
538
+ });
539
+ return elements;
540
+ }
541
+ /**
542
+ * Generate read receipt element (under last sender message)
543
+ */
544
+ function generateReadReceiptElement(props, calculatedMessages) {
545
+ // Check if read receipt is enabled
546
+ const showReadReceipt = props.showReadReceipt ?? MESSAGE_DEFAULTS.showReadReceipt;
547
+ if (!showReadReceipt) {
548
+ return [];
549
+ }
550
+ // Find the last sender message
551
+ let lastSenderMessage = null;
552
+ for (let i = calculatedMessages.length - 1; i >= 0; i--) {
553
+ if (calculatedMessages[i].message.sender === 'user') {
554
+ lastSenderMessage = calculatedMessages[i];
555
+ break;
556
+ }
557
+ }
558
+ if (!lastSenderMessage) {
559
+ return [];
560
+ }
561
+ // Get props with defaults
562
+ const readReceiptText = props.readReceiptText ?? MESSAGE_DEFAULTS.readReceiptText;
563
+ const readReceiptGap = props.readReceiptGap ?? MESSAGE_DEFAULTS.readReceiptGap;
564
+ const readReceiptFontSize = props.readReceiptFontSize ?? MESSAGE_DEFAULTS.readReceiptFontSize;
565
+ const readReceiptLetterSpacing = props.readReceiptLetterSpacing ?? MESSAGE_DEFAULTS.readReceiptLetterSpacing;
566
+ const readReceiptColor = props.readReceiptColor ?? MESSAGE_DEFAULTS.readReceiptColor;
567
+ const readReceiptLeft = props.readReceiptLeft ?? MESSAGE_DEFAULTS.readReceiptLeft;
568
+ const readReceiptRight = props.readReceiptRight ?? MESSAGE_DEFAULTS.readReceiptRight;
569
+ const readReceiptWidth = readReceiptRight - readReceiptLeft;
570
+ // Position: bottom of read text is readReceiptGap pixels below the bottom of the last sender message
571
+ const readReceiptTop = lastSenderMessage.bottom + readReceiptGap - readReceiptFontSize;
572
+ return [{
573
+ id: 'read-receipt',
574
+ type: 'text',
575
+ x: readReceiptLeft,
576
+ y: readReceiptTop,
577
+ width: readReceiptWidth,
578
+ height: readReceiptFontSize,
579
+ zIndex: 4,
580
+ text: readReceiptText,
581
+ font: 'apple',
582
+ fontWeight: 600,
583
+ fontSize: readReceiptFontSize,
584
+ letterSpacing: readReceiptLetterSpacing,
585
+ color: readReceiptColor,
586
+ textAlign: 'right',
587
+ verticalAlign: 'middle',
588
+ }];
589
+ }
590
+ /**
591
+ * Build the imageUrls map for all image elements
592
+ */
593
+ function buildImageUrls(props) {
594
+ const imageUrls = {};
595
+ const messages = props.messages ?? [];
596
+ for (const message of messages) {
597
+ // Only include sender images for now
598
+ if (message.imageUrl && message.sender === 'user') {
599
+ imageUrls[`${exports.SENDER_IMAGE_INPUT_ID_PREFIX}${message.id}`] = message.imageUrl;
600
+ }
601
+ }
602
+ return imageUrls;
603
+ }
604
+ function convertPropsToElements(props) {
605
+ const width = props.width ?? 1206;
606
+ const height = props.height ?? 2622;
607
+ // Calculate message positions
608
+ const calculatedMessages = calculateMessagePositions(props);
609
+ // Generate message elements
610
+ const messageElements = generateMessageElements(props, calculatedMessages);
611
+ // Generate tail data (for direct JSX rendering)
612
+ const tails = generateTailData(props, calculatedMessages);
613
+ // Generate header text elements (iMessage + timestamp above first message)
614
+ const headerTextElements = generateHeaderTextElements(props, calculatedMessages);
615
+ // Generate read receipt element (under last sender message)
616
+ const readReceiptElements = generateReadReceiptElement(props, calculatedMessages);
617
+ // Combine all elements and sort by zIndex
618
+ const allElements = [...messageElements, ...headerTextElements, ...readReceiptElements];
619
+ allElements.sort((a, b) => (a.zIndex ?? 0) - (b.zIndex ?? 0));
620
+ // Build image URLs map
621
+ const imageUrls = buildImageUrls(props);
622
+ return {
623
+ elements: allElements,
624
+ imageUrls,
625
+ tails,
626
+ width,
627
+ height,
628
+ };
629
+ }
@@ -0,0 +1,20 @@
1
+ /**
2
+ * iMessage DM Composition
3
+ *
4
+ * A fully parameterized iMessage DM conversation renderer.
5
+ * Uses BaseDmComposition for shared iOS chrome (status bar, home indicator)
6
+ * and zoom/pan/crosshair functionality.
7
+ */
8
+ import React from 'react';
9
+ import type { IMessageDmCompositionProps } from './types';
10
+ export type { IMessageDmCompositionProps } from './types';
11
+ export type { ImMessage, ImMessageSender } from './types';
12
+ /**
13
+ * Default composition props for registration
14
+ */
15
+ export declare const defaultIMessageDmProps: IMessageDmCompositionProps;
16
+ /**
17
+ * iMessage DM Composition Component
18
+ */
19
+ export declare const IMessageDmComposition: React.FC<IMessageDmCompositionProps>;
20
+ export default IMessageDmComposition;