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.
- package/dist/index.d.ts +2 -1
- package/dist/index.js +6 -1
- package/dist/render/Root.d.ts +12 -0
- package/dist/render/Root.js +508 -0
- package/dist/render/components/CaptionOverlay.d.ts +21 -0
- package/dist/render/components/CaptionOverlay.js +210 -0
- package/dist/render/components/ImageElement.d.ts +26 -0
- package/dist/render/components/ImageElement.js +88 -0
- package/dist/render/components/TextElement.d.ts +30 -0
- package/dist/render/components/TextElement.js +390 -0
- package/dist/render/components/VideoElement.d.ts +30 -0
- package/dist/render/components/VideoElement.js +108 -0
- package/dist/render/components/index.d.ts +7 -0
- package/dist/render/components/index.js +14 -0
- package/dist/render/compositions/AutoCaptionComposition.d.ts +42 -0
- package/dist/render/compositions/AutoCaptionComposition.js +29 -0
- package/dist/render/compositions/DmComposition/BaseDmComposition.d.ts +112 -0
- package/dist/render/compositions/DmComposition/BaseDmComposition.js +212 -0
- package/dist/render/compositions/DmComposition/DebugOverlay.d.ts +20 -0
- package/dist/render/compositions/DmComposition/DebugOverlay.js +258 -0
- package/dist/render/compositions/DmComposition/index.d.ts +6 -0
- package/dist/render/compositions/DmComposition/index.js +10 -0
- package/dist/render/compositions/IMessageDmComposition/convertPropsToElements.d.ts +27 -0
- package/dist/render/compositions/IMessageDmComposition/convertPropsToElements.js +629 -0
- package/dist/render/compositions/IMessageDmComposition/index.d.ts +20 -0
- package/dist/render/compositions/IMessageDmComposition/index.js +485 -0
- package/dist/render/compositions/IMessageDmComposition/types.d.ts +756 -0
- package/dist/render/compositions/IMessageDmComposition/types.js +225 -0
- package/dist/render/compositions/ImageEditorComposition.d.ts +74 -0
- package/dist/render/compositions/ImageEditorComposition.js +351 -0
- package/dist/render/compositions/InstagramDmComposition/convertPropsToElements.d.ts +60 -0
- package/dist/render/compositions/InstagramDmComposition/convertPropsToElements.js +1318 -0
- package/dist/render/compositions/InstagramDmComposition/convertPublicToProps.d.ts +30 -0
- package/dist/render/compositions/InstagramDmComposition/convertPublicToProps.js +131 -0
- package/dist/render/compositions/InstagramDmComposition/index.d.ts +16 -0
- package/dist/render/compositions/InstagramDmComposition/index.js +374 -0
- package/dist/render/compositions/InstagramDmComposition/theme.d.ts +42 -0
- package/dist/render/compositions/InstagramDmComposition/theme.js +55 -0
- package/dist/render/compositions/InstagramDmComposition/types.d.ts +215 -0
- package/dist/render/compositions/InstagramDmComposition/types.js +7 -0
- package/dist/render/compositions/ScreenshotAnimation.d.ts +14 -0
- package/dist/render/compositions/ScreenshotAnimation.js +268 -0
- package/dist/render/compositions/VideoEditorComposition.d.ts +45 -0
- package/dist/render/compositions/VideoEditorComposition.js +307 -0
- package/dist/render/compositions/index.d.ts +12 -0
- package/dist/render/compositions/index.js +43 -0
- package/dist/render/compositions/messaging/components/MediaBubble.d.ts +22 -0
- package/dist/render/compositions/messaging/components/MediaBubble.js +25 -0
- package/dist/render/compositions/messaging/components/MessageBubble.d.ts +35 -0
- package/dist/render/compositions/messaging/components/MessageBubble.js +34 -0
- package/dist/render/compositions/messaging/components/ProfilePic.d.ts +23 -0
- package/dist/render/compositions/messaging/components/ProfilePic.js +37 -0
- package/dist/render/compositions/messaging/components/Reaction.d.ts +23 -0
- package/dist/render/compositions/messaging/components/Reaction.js +19 -0
- package/dist/render/compositions/messaging/components/TypingIndicator.d.ts +25 -0
- package/dist/render/compositions/messaging/components/TypingIndicator.js +66 -0
- package/dist/render/compositions/messaging/index.d.ts +14 -0
- package/dist/render/compositions/messaging/index.js +43 -0
- package/dist/render/compositions/messaging/types.d.ts +148 -0
- package/dist/render/compositions/messaging/types.js +21 -0
- package/dist/render/compositions/messaging/utils/bubbleRadius.d.ts +45 -0
- package/dist/render/compositions/messaging/utils/bubbleRadius.js +84 -0
- package/dist/render/compositions/messaging/utils/groupMessages.d.ts +41 -0
- package/dist/render/compositions/messaging/utils/groupMessages.js +110 -0
- package/dist/render/data/phone-top-nav.d.ts +1 -0
- package/dist/render/data/phone-top-nav.js +4 -0
- package/dist/render/data/screenshot.d.ts +164 -0
- package/dist/render/data/screenshot.js +63 -0
- package/dist/render/hooks/index.d.ts +54 -0
- package/dist/render/hooks/index.js +132 -0
- package/dist/render/index.d.ts +12 -0
- package/dist/render/index.js +36 -0
- package/dist/render/types/base.d.ts +148 -0
- package/dist/render/types/base.js +5 -0
- package/dist/render/types/caption.d.ts +105 -0
- package/dist/render/types/caption.js +8 -0
- package/dist/render/types/crop.d.ts +60 -0
- package/dist/render/types/crop.js +8 -0
- package/dist/render/types/deduplication.d.ts +284 -0
- package/dist/render/types/deduplication.js +240 -0
- package/dist/render/types/editor.d.ts +97 -0
- package/dist/render/types/editor.js +10 -0
- package/dist/render/types/element.d.ts +139 -0
- package/dist/render/types/element.js +19 -0
- package/dist/render/types/index.d.ts +20 -0
- package/dist/render/types/index.js +24 -0
- package/dist/render/types/instagram-dm-public.d.ts +60 -0
- package/dist/render/types/instagram-dm-public.js +8 -0
- package/dist/render/types/position.d.ts +59 -0
- package/dist/render/types/position.js +5 -0
- package/dist/render/types/screenshot.d.ts +57 -0
- package/dist/render/types/screenshot.js +34 -0
- package/dist/render/types/segment.d.ts +163 -0
- package/dist/render/types/segment.js +8 -0
- package/dist/render/types/video.d.ts +192 -0
- package/dist/render/types/video.js +14 -0
- package/dist/render/utils/captionPresets.d.ts +38 -0
- package/dist/render/utils/captionPresets.js +168 -0
- package/dist/render/utils/cropBounds.d.ts +20 -0
- package/dist/render/utils/cropBounds.js +166 -0
- package/dist/render/utils/defaults.d.ts +74 -0
- package/dist/render/utils/defaults.js +91 -0
- package/dist/render/utils/emoji.d.ts +40 -0
- package/dist/render/utils/emoji.js +105 -0
- package/dist/render/utils/fit.d.ts +35 -0
- package/dist/render/utils/fit.js +63 -0
- package/dist/render/utils/fonts.d.ts +55 -0
- package/dist/render/utils/fonts.js +191 -0
- package/dist/render/utils/index.d.ts +14 -0
- package/dist/render/utils/index.js +73 -0
- package/dist/render/utils/positionResolver.d.ts +64 -0
- package/dist/render/utils/positionResolver.js +508 -0
- package/dist/render/utils/text.d.ts +50 -0
- package/dist/render/utils/text.js +177 -0
- package/dist/render/utils/timeline.d.ts +62 -0
- package/dist/render/utils/timeline.js +172 -0
- package/dist/render.d.ts +1 -1
- package/dist/types.d.ts +136 -17
- package/dist/types.js +58 -0
- 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;
|