@adatechnology/conversations-ui 0.8.0 → 0.10.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/README.md +106 -0
- package/dist/{ConversationSimulatorPanel-q-wVgBCN.d.ts → ConversationSimulatorPanel-P6eqTSAO.d.ts} +1 -16
- package/dist/{chunk-HMCOTZAX.js → chunk-NSOAUYNE.js} +33 -28
- package/dist/{chunk-O6457XHK.js → chunk-WCZNIJ3M.js} +16 -1
- package/dist/{chunk-L3OFAQPS.js → chunk-Y326LLEP.js} +4 -14
- package/dist/chunk-ZSOD5AQT.js +19 -0
- package/dist/flows/index.js +4 -2
- package/dist/index.d.ts +10 -3
- package/dist/index.js +8 -6
- package/dist/participant/index.d.ts +54 -3
- package/dist/participant/index.js +1722 -418
- package/dist/preview/index.d.ts +2 -2
- package/dist/preview/index.js +2 -2
- package/dist/styles.css +281 -23
- package/package.json +3 -2
- package/src/AudioRecorderButton.tsx +4 -28
- package/src/Tooltip.tsx +5 -12
- package/src/TooltipDom.test.tsx +78 -0
- package/src/audioRecorderFormat.ts +27 -0
- package/src/domHarness.test-helper.ts +53 -0
- package/src/participant/ConversationThread.tsx +4 -0
- package/src/participant/ParticipantAttachmentItem.tsx +7 -10
- package/src/participant/ParticipantAudioAttachment.tsx +34 -0
- package/src/participant/ParticipantAudioPlayer.tsx +68 -0
- package/src/participant/ParticipantAudioPlayerControls.tsx +68 -0
- package/src/participant/ParticipantAudioRecorderView.tsx +55 -0
- package/src/participant/ParticipantAudioTranscription.tsx +90 -0
- package/src/participant/ParticipantBackButton.tsx +1 -1
- package/src/participant/ParticipantBubbleBody.tsx +54 -0
- package/src/participant/ParticipantBubbleStatus.tsx +22 -28
- package/src/participant/ParticipantChannelBadges.tsx +10 -2
- package/src/participant/ParticipantComposer.tsx +51 -16
- package/src/participant/ParticipantComposerAudio.tsx +95 -0
- package/src/participant/ParticipantComposerControls.tsx +2 -0
- package/src/participant/ParticipantConversations.tsx +3 -0
- package/src/participant/ParticipantCopyableToken.tsx +1 -0
- package/src/participant/ParticipantFailedMenu.tsx +40 -0
- package/src/participant/ParticipantFailedMenuList.tsx +41 -0
- package/src/participant/ParticipantMessageBubble.test.tsx +6 -6
- package/src/participant/ParticipantMessageBubble.tsx +20 -52
- package/src/participant/ParticipantMessageText.test.tsx +1 -1
- package/src/participant/ParticipantMessageText.tsx +4 -2
- package/src/participant/ParticipantProtocolBadge.tsx +1 -0
- package/src/participant/ParticipantRecorderParts.tsx +118 -0
- package/src/participant/ParticipantScrollToLatest.tsx +1 -1
- package/src/participant/ParticipantThread.tsx +30 -12
- package/src/participant/ParticipantThreadHeader.test.tsx +1 -1
- package/src/participant/ParticipantThreadScreen.tsx +1 -0
- package/src/participant/failedMenuKeyboard.ts +29 -0
- package/src/participant/index.ts +3 -0
- package/src/participant/participantAudio.types.ts +10 -0
- package/src/participant/participantAudioCapture.test.ts +15 -0
- package/src/participant/participantAudioCapture.ts +85 -0
- package/src/participant/participantAudioCoordinator.ts +13 -0
- package/src/participant/participantAudioFormat.test.ts +93 -0
- package/src/participant/participantAudioFormat.ts +72 -0
- package/src/participant/participantAudioGolden.test.tsx +168 -0
- package/src/participant/participantAudioLabels.test.ts +22 -0
- package/src/participant/participantAudioLabels.ts +76 -0
- package/src/participant/participantAudioPlayerControlsDom.test.tsx +123 -0
- package/src/participant/participantAudioPlayerCss.test.ts +63 -0
- package/src/participant/participantAudioPlayerDom.test.tsx +154 -0
- package/src/participant/participantAudioPlayerDomKit.test-helper.tsx +84 -0
- package/src/participant/participantAudioPlayerState.ts +35 -0
- package/src/participant/participantAudioRecorderDom.test.tsx +127 -0
- package/src/participant/participantAudioRecorderInterruptionDom.test.tsx +135 -0
- package/src/participant/participantAudioRecorderSpecDom.test.tsx +110 -0
- package/src/participant/participantAudioRecorderState.test.ts +144 -0
- package/src/participant/participantAudioRecorderState.ts +93 -0
- package/src/participant/participantAudioRecorderView.test.tsx +98 -0
- package/src/participant/participantAudioSupport.ts +36 -0
- package/src/participant/participantAudioTranscriptionDom.test.tsx +198 -0
- package/src/participant/participantComposerAudioDom.test.tsx +185 -0
- package/src/participant/participantComposerAudioGolden.test.tsx +68 -0
- package/src/participant/participantComposerAudioSsr.test.tsx +74 -0
- package/src/participant/participantFailedMenuDom.test.tsx +154 -0
- package/src/participant/participantFailedMenuState.test.ts +61 -0
- package/src/participant/participantFailedMenuState.ts +57 -0
- package/src/participant/participantFailedMessage.test.tsx +83 -0
- package/src/participant/participantFileValidation.ts +1 -1
- package/src/participant/participantGolden.test.tsx +2 -0
- package/src/participant/participantGoldenScenarios.test-helper.tsx +14 -1
- package/src/participant/participantLabels.ts +10 -1
- package/src/participant/participantMediaFakes.test-helper.ts +82 -0
- package/src/participant/participantNoMicrophoneDom.test.tsx +44 -0
- package/src/participant/participantPublicTypes.test.ts +11 -0
- package/src/participant/participantRecorderDomKit.test-helper.tsx +131 -0
- package/src/participant/participantRecorderFakes.test-helper.ts +47 -0
- package/src/participant/participantSpecRecorderFake.test-helper.ts +52 -0
- package/src/participant/participantThemeContrast.test.ts +17 -0
- package/src/participant/participantTooltips.test.tsx +74 -0
- package/src/participant/test-fixtures/composer-audio-denied.golden.html +1 -0
- package/src/participant/test-fixtures/composer-audio-idle.golden.html +1 -0
- package/src/participant/test-fixtures/composer-audio-recording.golden.html +1 -0
- package/src/participant/test-fixtures/composer-audio-review.golden.html +1 -0
- package/src/participant/test-fixtures/thread-audio-native.golden.html +1 -0
- package/src/participant/test-fixtures/thread-audio-player.golden.html +1 -0
- package/src/participant/test-fixtures/thread-audio-transcription-empty.golden.html +1 -0
- package/src/participant/test-fixtures/thread-audio-transcription-pending.golden.html +1 -0
- package/src/participant/test-fixtures/thread-audio-transcription-ready.golden.html +1 -0
- package/src/participant/test-fixtures/thread-audio-transcription-too_long.golden.html +1 -0
- package/src/participant/test-fixtures/thread-avatars-initials.golden.html +1 -1
- package/src/participant/test-fixtures/thread-closed.golden.html +1 -1
- package/src/participant/test-fixtures/thread-composer-microphone-idle.golden.html +1 -0
- package/src/participant/test-fixtures/thread-default.golden.html +1 -1
- package/src/participant/test-fixtures/thread-empty-draft.golden.html +1 -0
- package/src/participant/test-fixtures/thread-error.golden.html +1 -1
- package/src/participant/test-fixtures/thread-loading.golden.html +1 -1
- package/src/participant/test-fixtures/thread-plain.golden.html +1 -1
- package/src/participant/test-fixtures/thread-tail-off.golden.html +1 -1
- package/src/participant/useFailedMenuEffects.ts +59 -0
- package/src/participant/useParticipantAudioMediaEvents.ts +46 -0
- package/src/participant/useParticipantAudioPlayer.ts +52 -0
- package/src/participant/useParticipantAudioPlayerActions.ts +47 -0
- package/src/participant/useParticipantAudioRecorder.ts +153 -0
- package/src/participant/useParticipantAudioRecorderCore.ts +55 -0
- package/src/participant/useParticipantAudioRecorderEffects.ts +70 -0
- package/src/participant/useParticipantAudioSourceRetry.ts +42 -0
- package/src/participant/useParticipantFailedMenu.ts +60 -0
- package/src/participant/useParticipantSendAudio.test.tsx +85 -0
- package/src/participant/useParticipantSendAudio.ts +28 -0
- package/src/participant/useParticipantThreadController.ts +20 -7
- package/src/participant/useStableAttachmentUrl.ts +71 -0
- package/src/participant/useSuppressTouchTooltips.ts +38 -0
- package/src/participant/useTranscriptionOverflow.ts +25 -0
- package/src/participant/useTranscriptionReadyAnnouncement.ts +24 -0
- package/src/styles.css +48 -5
- package/src/tooltipLayerRegistry.test.ts +56 -0
- package/src/tooltipLayerRegistry.ts +34 -0
package/dist/preview/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { M as MessagePayload } from '../quickReply.types-DzWH3NYV.js';
|
|
2
|
-
import { C as ConversationSummary, a as ConversationEventSource, b as ConversationsApi, L as ListConversationsParams, c as ConversationPage, S as SSEProvider, d as ConversationDocument, e as SendPreviewMediaParams, P as PreviewUploadedMedia, f as PreviewWebhookClient, R as ResolveMediaUrl } from '../ConversationSimulatorPanel-
|
|
3
|
-
export { A as AudioRecorderButton, g as AudioRecorderButtonLabels, h as AudioRecorderButtonProps, i as ConversationPreview, j as ConversationPreviewProps, k as ConversationSimulatorClient, l as ConversationSimulatorPanel, m as ConversationSimulatorPanelLabels, n as ConversationSimulatorPanelProps, o as CreatePreviewMediaUploaderParams, p as CreatePreviewWebhookClientParams, D as DEFAULT_AUDIO_RECORDER_BUTTON_LABELS, q as DEFAULT_CONVERSATION_SIMULATOR_PANEL_LABELS, r as DEFAULT_MEDIA_UPLOAD_PATH, s as PreviewInProductionError, t as PreviewMediaUploadRejectedError, u as PreviewMediaUploadRequest, v as PreviewUploadedMedia, w as PreviewWebhookRejectedError, x as SIMULATOR_FILE_MEDIA_KINDS, y as SendSimulatorMediaParams, z as SimulatorMediaKind, T as ToSimulatorClientParams, B as acceptsMediaKind, E as assertPreviewEnvironment, F as createPreviewMediaPoster, G as createPreviewMediaUploader, H as createPreviewWebhookClient, I as isConversationSimulatorClient, J as mediaKindOf, K as mediaTypeOf, M as signPreviewPayload, N as simulatorPanelLabelsOf, O as toConversationSimulatorClient } from '../ConversationSimulatorPanel-
|
|
2
|
+
import { C as ConversationSummary, a as ConversationEventSource, b as ConversationsApi, L as ListConversationsParams, c as ConversationPage, S as SSEProvider, d as ConversationDocument, e as SendPreviewMediaParams, P as PreviewUploadedMedia, f as PreviewWebhookClient, R as ResolveMediaUrl } from '../ConversationSimulatorPanel-P6eqTSAO.js';
|
|
3
|
+
export { A as AudioRecorderButton, g as AudioRecorderButtonLabels, h as AudioRecorderButtonProps, i as ConversationPreview, j as ConversationPreviewProps, k as ConversationSimulatorClient, l as ConversationSimulatorPanel, m as ConversationSimulatorPanelLabels, n as ConversationSimulatorPanelProps, o as CreatePreviewMediaUploaderParams, p as CreatePreviewWebhookClientParams, D as DEFAULT_AUDIO_RECORDER_BUTTON_LABELS, q as DEFAULT_CONVERSATION_SIMULATOR_PANEL_LABELS, r as DEFAULT_MEDIA_UPLOAD_PATH, s as PreviewInProductionError, t as PreviewMediaUploadRejectedError, u as PreviewMediaUploadRequest, v as PreviewUploadedMedia, w as PreviewWebhookRejectedError, x as SIMULATOR_FILE_MEDIA_KINDS, y as SendSimulatorMediaParams, z as SimulatorMediaKind, T as ToSimulatorClientParams, B as acceptsMediaKind, E as assertPreviewEnvironment, F as createPreviewMediaPoster, G as createPreviewMediaUploader, H as createPreviewWebhookClient, I as isConversationSimulatorClient, J as mediaKindOf, K as mediaTypeOf, M as signPreviewPayload, N as simulatorPanelLabelsOf, O as toConversationSimulatorClient } from '../ConversationSimulatorPanel-P6eqTSAO.js';
|
|
4
4
|
import { InteractiveReplyOption } from '@adatechnology/meta-whatsapp-contracts/testing';
|
|
5
5
|
import * as react from 'react';
|
|
6
6
|
export { PREVIEW_MEDIA_ID_PREFIX } from '@adatechnology/meta-whatsapp-contracts';
|
package/dist/preview/index.js
CHANGED
|
@@ -16,9 +16,9 @@ import {
|
|
|
16
16
|
mediaTypeOf,
|
|
17
17
|
simulatorPanelLabelsOf,
|
|
18
18
|
toConversationSimulatorClient
|
|
19
|
-
} from "../chunk-
|
|
19
|
+
} from "../chunk-Y326LLEP.js";
|
|
20
20
|
import "../chunk-PJNR6UMN.js";
|
|
21
|
-
import "../chunk-
|
|
21
|
+
import "../chunk-WCZNIJ3M.js";
|
|
22
22
|
|
|
23
23
|
// src/preview/previewStore.ts
|
|
24
24
|
var GLOBAL_CHANNEL = "global";
|
package/dist/styles.css
CHANGED
|
@@ -1382,8 +1382,8 @@
|
|
|
1382
1382
|
.cv-p-chip,
|
|
1383
1383
|
.cv-p-row,
|
|
1384
1384
|
.cv-p-thread__older,
|
|
1385
|
-
.cv-p-
|
|
1386
|
-
.cv-p-
|
|
1385
|
+
.cv-p-failed-menu__trigger,
|
|
1386
|
+
.cv-p-failed-menu__item {
|
|
1387
1387
|
font: inherit;
|
|
1388
1388
|
color: inherit;
|
|
1389
1389
|
cursor: pointer;
|
|
@@ -1970,8 +1970,7 @@
|
|
|
1970
1970
|
min-width: 0;
|
|
1971
1971
|
margin-inline-end: auto;
|
|
1972
1972
|
}
|
|
1973
|
-
.cv-p-bubble > .cv-p-attachment
|
|
1974
|
-
.cv-p-bubble > .cv-p-bubble__actions {
|
|
1973
|
+
.cv-p-bubble > .cv-p-attachment {
|
|
1975
1974
|
flex: 1 0 100%;
|
|
1976
1975
|
}
|
|
1977
1976
|
.cv-p-bubble__meta {
|
|
@@ -2004,33 +2003,103 @@
|
|
|
2004
2003
|
.cv-p-bubble .cv-status-ticks--failed {
|
|
2005
2004
|
color: var(--cv-p-i-danger);
|
|
2006
2005
|
}
|
|
2007
|
-
|
|
2008
|
-
|
|
2009
|
-
|
|
2010
|
-
|
|
2011
|
-
|
|
2012
|
-
|
|
2006
|
+
@supports (color: color-mix(in srgb, red, blue)) {
|
|
2007
|
+
.cv-p-bubble--failed {
|
|
2008
|
+
border-color: color-mix(in srgb, var(--cv-p-i-danger) 55%, var(--cv-p-i-border));
|
|
2009
|
+
--cv-p-tail-edge: color-mix(in srgb, var(--cv-p-i-danger) 55%, var(--cv-p-i-border));
|
|
2010
|
+
}
|
|
2011
|
+
.cv-p-bubble--mine.cv-p-bubble--failed {
|
|
2012
|
+
border-color: color-mix(in srgb, var(--cv-p-i-danger) 55%, var(--cv-p-i-accent));
|
|
2013
|
+
--cv-p-tail-edge: color-mix(in srgb, var(--cv-p-i-danger) 55%, var(--cv-p-i-accent));
|
|
2014
|
+
}
|
|
2015
|
+
}
|
|
2016
|
+
.cv-p-failed-retry {
|
|
2017
|
+
position: absolute;
|
|
2018
|
+
top: 50%;
|
|
2019
|
+
right: calc(100% + 0.125rem);
|
|
2020
|
+
transform: translateY(-50%);
|
|
2021
|
+
display: grid;
|
|
2022
|
+
place-items: center;
|
|
2023
|
+
width: var(--cv-p-i-touch);
|
|
2024
|
+
height: var(--cv-p-i-touch);
|
|
2025
|
+
padding: 0;
|
|
2013
2026
|
background: transparent;
|
|
2014
|
-
border:
|
|
2027
|
+
border: 0;
|
|
2015
2028
|
color: var(--cv-p-i-danger);
|
|
2016
|
-
|
|
2017
|
-
|
|
2029
|
+
font: inherit;
|
|
2030
|
+
cursor: pointer;
|
|
2018
2031
|
}
|
|
2019
|
-
.cv-p-
|
|
2020
|
-
display:
|
|
2021
|
-
|
|
2022
|
-
|
|
2032
|
+
.cv-p-failed-retry__icon {
|
|
2033
|
+
display: grid;
|
|
2034
|
+
place-items: center;
|
|
2035
|
+
width: 2rem;
|
|
2036
|
+
height: 2rem;
|
|
2037
|
+
border: 1px solid var(--cv-p-i-danger);
|
|
2038
|
+
border-radius: 50%;
|
|
2023
2039
|
}
|
|
2024
|
-
.cv-p-
|
|
2025
|
-
|
|
2040
|
+
.cv-p-failed-retry:hover .cv-p-failed-retry__icon,
|
|
2041
|
+
.cv-p-failed-retry:focus-visible .cv-p-failed-retry__icon {
|
|
2042
|
+
background: var(--cv-p-i-surface-raised);
|
|
2043
|
+
}
|
|
2044
|
+
.cv-p-failed-menu {
|
|
2026
2045
|
display: inline-flex;
|
|
2027
|
-
|
|
2028
|
-
|
|
2046
|
+
}
|
|
2047
|
+
.cv-p-failed-menu__trigger {
|
|
2048
|
+
position: relative;
|
|
2049
|
+
display: inline-grid;
|
|
2050
|
+
place-items: center;
|
|
2051
|
+
min-width: 1.25rem;
|
|
2052
|
+
height: 0.75rem;
|
|
2053
|
+
padding: 0;
|
|
2029
2054
|
background: transparent;
|
|
2055
|
+
border: 0;
|
|
2056
|
+
color: var(--cv-p-i-text-muted);
|
|
2057
|
+
font: inherit;
|
|
2058
|
+
line-height: 0.75rem;
|
|
2059
|
+
cursor: pointer;
|
|
2060
|
+
}
|
|
2061
|
+
.cv-p-failed-menu__trigger::after {
|
|
2062
|
+
content: "";
|
|
2063
|
+
position: absolute;
|
|
2064
|
+
inset: -0.5rem -0.4rem;
|
|
2065
|
+
}
|
|
2066
|
+
.cv-p-failed-menu__list {
|
|
2067
|
+
position: absolute;
|
|
2068
|
+
right: 0;
|
|
2069
|
+
bottom: calc(100% + 0.25rem);
|
|
2070
|
+
z-index: 2;
|
|
2071
|
+
display: flex;
|
|
2072
|
+
flex-direction: column;
|
|
2073
|
+
min-width: 8rem;
|
|
2074
|
+
background: var(--cv-p-i-surface-raised);
|
|
2030
2075
|
border: 1px solid var(--cv-p-i-border);
|
|
2031
|
-
color: inherit;
|
|
2032
2076
|
border-radius: var(--cv-p-i-radius);
|
|
2033
|
-
|
|
2077
|
+
}
|
|
2078
|
+
.cv-p-failed-menu__item {
|
|
2079
|
+
min-height: var(--cv-p-i-touch);
|
|
2080
|
+
padding: 0 0.75rem;
|
|
2081
|
+
background: transparent;
|
|
2082
|
+
border: 0;
|
|
2083
|
+
color: var(--cv-p-i-text);
|
|
2084
|
+
font:
|
|
2085
|
+
0.7rem "SFMono-Regular",
|
|
2086
|
+
Consolas,
|
|
2087
|
+
monospace;
|
|
2088
|
+
text-align: start;
|
|
2089
|
+
text-transform: uppercase;
|
|
2090
|
+
letter-spacing: 0.04em;
|
|
2091
|
+
cursor: pointer;
|
|
2092
|
+
}
|
|
2093
|
+
.cv-p-failed-menu__list--below {
|
|
2094
|
+
bottom: auto;
|
|
2095
|
+
top: calc(100% + 0.25rem);
|
|
2096
|
+
}
|
|
2097
|
+
.cv-p-failed-menu__item--danger {
|
|
2098
|
+
color: var(--cv-p-i-danger);
|
|
2099
|
+
}
|
|
2100
|
+
.cv-p-failed-menu__item:hover,
|
|
2101
|
+
.cv-p-failed-menu__item:focus-visible {
|
|
2102
|
+
background: var(--cv-p-i-highlight);
|
|
2034
2103
|
}
|
|
2035
2104
|
.cv-p-attachment {
|
|
2036
2105
|
display: flex;
|
|
@@ -2056,11 +2125,129 @@
|
|
|
2056
2125
|
width: 100%;
|
|
2057
2126
|
min-height: var(--cv-p-i-touch);
|
|
2058
2127
|
}
|
|
2128
|
+
.cv-p-audio {
|
|
2129
|
+
display: flex;
|
|
2130
|
+
flex-wrap: wrap;
|
|
2131
|
+
align-items: center;
|
|
2132
|
+
gap: 0.25rem 0.5rem;
|
|
2133
|
+
width: 100%;
|
|
2134
|
+
min-width: min(15rem, 100%);
|
|
2135
|
+
box-sizing: border-box;
|
|
2136
|
+
}
|
|
2137
|
+
.cv-p-audio__toggle,
|
|
2138
|
+
.cv-p-audio__rate {
|
|
2139
|
+
flex: 0 0 auto;
|
|
2140
|
+
display: inline-grid;
|
|
2141
|
+
place-items: center;
|
|
2142
|
+
min-width: var(--cv-p-i-touch);
|
|
2143
|
+
min-height: var(--cv-p-i-touch);
|
|
2144
|
+
padding: 0;
|
|
2145
|
+
background: transparent;
|
|
2146
|
+
border: 1px solid var(--cv-p-i-border);
|
|
2147
|
+
border-radius: var(--cv-p-i-radius);
|
|
2148
|
+
color: inherit;
|
|
2149
|
+
font: inherit;
|
|
2150
|
+
font-size: 0.75rem;
|
|
2151
|
+
cursor: pointer;
|
|
2152
|
+
}
|
|
2153
|
+
.cv-p-audio__toggle:disabled {
|
|
2154
|
+
opacity: 0.45;
|
|
2155
|
+
cursor: not-allowed;
|
|
2156
|
+
}
|
|
2157
|
+
.cv-p-audio__toggle svg {
|
|
2158
|
+
display: block;
|
|
2159
|
+
}
|
|
2160
|
+
.cv-p-audio__seek {
|
|
2161
|
+
flex: 1 1 5rem;
|
|
2162
|
+
min-width: 5rem;
|
|
2163
|
+
min-height: var(--cv-p-i-touch);
|
|
2164
|
+
margin: 0;
|
|
2165
|
+
accent-color: var(--cv-p-i-accent);
|
|
2166
|
+
cursor: pointer;
|
|
2167
|
+
}
|
|
2168
|
+
.cv-p-audio__time {
|
|
2169
|
+
flex: 0 0 auto;
|
|
2170
|
+
font-size: 0.75rem;
|
|
2171
|
+
font-variant-numeric: tabular-nums;
|
|
2172
|
+
color: var(--cv-p-i-text-muted);
|
|
2173
|
+
}
|
|
2174
|
+
.cv-p-audio__media {
|
|
2175
|
+
display: none;
|
|
2176
|
+
}
|
|
2177
|
+
.cv-p-audio__error {
|
|
2178
|
+
flex: 1 0 100%;
|
|
2179
|
+
font-size: 0.75rem;
|
|
2180
|
+
}
|
|
2181
|
+
.cv-p-recorder .cv-p-audio {
|
|
2182
|
+
flex: 1 1 8rem;
|
|
2183
|
+
width: auto;
|
|
2184
|
+
min-width: 0;
|
|
2185
|
+
}
|
|
2059
2186
|
.cv-p-attachment__name,
|
|
2060
2187
|
.cv-p-attachment__size {
|
|
2061
2188
|
font-size: 0.75rem;
|
|
2062
2189
|
overflow-wrap: anywhere;
|
|
2063
2190
|
}
|
|
2191
|
+
.cv-p-transcription {
|
|
2192
|
+
display: flex;
|
|
2193
|
+
flex-direction: column;
|
|
2194
|
+
gap: 0.25rem;
|
|
2195
|
+
margin: 0;
|
|
2196
|
+
font-size: 0.8rem;
|
|
2197
|
+
line-height: 1.4;
|
|
2198
|
+
color: var(--cv-p-i-text);
|
|
2199
|
+
}
|
|
2200
|
+
.cv-p-transcription--pending,
|
|
2201
|
+
.cv-p-transcription--too-long {
|
|
2202
|
+
font-size: 0.75rem;
|
|
2203
|
+
color: var(--cv-p-i-text-muted);
|
|
2204
|
+
}
|
|
2205
|
+
.cv-p-transcription__text,
|
|
2206
|
+
.cv-p-transcription__empty,
|
|
2207
|
+
.cv-p-transcription__notice {
|
|
2208
|
+
margin: 0;
|
|
2209
|
+
overflow-wrap: anywhere;
|
|
2210
|
+
}
|
|
2211
|
+
.cv-p-transcription__text {
|
|
2212
|
+
display: -webkit-box;
|
|
2213
|
+
-webkit-box-orient: vertical;
|
|
2214
|
+
-webkit-line-clamp: 6;
|
|
2215
|
+
line-clamp: 6;
|
|
2216
|
+
overflow: hidden;
|
|
2217
|
+
-webkit-user-select: text;
|
|
2218
|
+
user-select: text;
|
|
2219
|
+
}
|
|
2220
|
+
.cv-p-transcription__text[data-expanded=true] {
|
|
2221
|
+
display: block;
|
|
2222
|
+
overflow: visible;
|
|
2223
|
+
}
|
|
2224
|
+
.cv-p-transcription__empty {
|
|
2225
|
+
color: var(--cv-p-i-text-muted);
|
|
2226
|
+
}
|
|
2227
|
+
.cv-p-transcription__notice {
|
|
2228
|
+
order: 1;
|
|
2229
|
+
font-size: 0.7rem;
|
|
2230
|
+
color: var(--cv-p-i-text-muted);
|
|
2231
|
+
}
|
|
2232
|
+
.cv-p-transcription__toggle {
|
|
2233
|
+
order: 2;
|
|
2234
|
+
align-self: flex-start;
|
|
2235
|
+
min-width: var(--cv-p-i-touch);
|
|
2236
|
+
min-height: var(--cv-p-i-touch);
|
|
2237
|
+
padding: 0 0.5rem;
|
|
2238
|
+
background: transparent;
|
|
2239
|
+
border: 0;
|
|
2240
|
+
border-radius: var(--cv-p-i-radius);
|
|
2241
|
+
color: var(--cv-p-i-accent);
|
|
2242
|
+
font: inherit;
|
|
2243
|
+
font-size: 0.75rem;
|
|
2244
|
+
text-decoration: underline;
|
|
2245
|
+
cursor: pointer;
|
|
2246
|
+
}
|
|
2247
|
+
.cv-p-transcription__toggle:focus-visible {
|
|
2248
|
+
outline: 2px solid var(--cv-p-i-accent);
|
|
2249
|
+
outline-offset: 2px;
|
|
2250
|
+
}
|
|
2064
2251
|
.cv-p-composer {
|
|
2065
2252
|
display: flex;
|
|
2066
2253
|
flex-direction: column;
|
|
@@ -2121,6 +2308,66 @@
|
|
|
2121
2308
|
font-size: 0.8rem;
|
|
2122
2309
|
color: var(--cv-p-i-danger);
|
|
2123
2310
|
}
|
|
2311
|
+
.cv-p-composer__record {
|
|
2312
|
+
flex: 0 0 auto;
|
|
2313
|
+
padding: 0;
|
|
2314
|
+
margin-inline-start: 0.25rem;
|
|
2315
|
+
}
|
|
2316
|
+
.cv-p-composer__record svg,
|
|
2317
|
+
.cv-p-recorder svg {
|
|
2318
|
+
display: block;
|
|
2319
|
+
}
|
|
2320
|
+
.cv-p-recorder {
|
|
2321
|
+
display: flex;
|
|
2322
|
+
flex: 1 1 auto;
|
|
2323
|
+
flex-wrap: wrap;
|
|
2324
|
+
align-items: center;
|
|
2325
|
+
gap: 0.5rem;
|
|
2326
|
+
min-width: 0;
|
|
2327
|
+
}
|
|
2328
|
+
.cv-p-recorder__hint {
|
|
2329
|
+
flex: 1 1 100%;
|
|
2330
|
+
margin: 0;
|
|
2331
|
+
font-size: 0.8rem;
|
|
2332
|
+
color: var(--cv-p-i-text-muted);
|
|
2333
|
+
}
|
|
2334
|
+
.cv-p-recorder__discard {
|
|
2335
|
+
flex: 0 0 auto;
|
|
2336
|
+
padding: 0;
|
|
2337
|
+
color: var(--cv-p-i-text-muted);
|
|
2338
|
+
}
|
|
2339
|
+
.cv-p-recorder__dot {
|
|
2340
|
+
flex: 0 0 auto;
|
|
2341
|
+
width: 0.625rem;
|
|
2342
|
+
height: 0.625rem;
|
|
2343
|
+
border-radius: 50%;
|
|
2344
|
+
background: var(--cv-p-i-danger);
|
|
2345
|
+
animation: cv-p-recorder-pulse 1.2s ease-in-out infinite;
|
|
2346
|
+
}
|
|
2347
|
+
.cv-p-recorder__elapsed {
|
|
2348
|
+
flex: 1 1 auto;
|
|
2349
|
+
font:
|
|
2350
|
+
0.85rem "SFMono-Regular",
|
|
2351
|
+
Consolas,
|
|
2352
|
+
monospace;
|
|
2353
|
+
font-variant-numeric: tabular-nums;
|
|
2354
|
+
color: var(--cv-p-i-text);
|
|
2355
|
+
}
|
|
2356
|
+
.cv-p-recorder__stop,
|
|
2357
|
+
.cv-p-recorder__send {
|
|
2358
|
+
flex: 0 0 auto;
|
|
2359
|
+
padding: 0;
|
|
2360
|
+
}
|
|
2361
|
+
@keyframes cv-p-recorder-pulse {
|
|
2362
|
+
50% {
|
|
2363
|
+
opacity: 0.3;
|
|
2364
|
+
}
|
|
2365
|
+
}
|
|
2366
|
+
@media (prefers-reduced-motion: reduce) {
|
|
2367
|
+
.cv-p-recorder__dot {
|
|
2368
|
+
animation: none;
|
|
2369
|
+
}
|
|
2370
|
+
}
|
|
2124
2371
|
.cv-p-quick {
|
|
2125
2372
|
--cv-p-i-fade: 0.75rem;
|
|
2126
2373
|
display: flex;
|
|
@@ -2525,10 +2772,21 @@
|
|
|
2525
2772
|
.cv-p-thread__latest-badge {
|
|
2526
2773
|
border: 1px solid ButtonText;
|
|
2527
2774
|
}
|
|
2775
|
+
.cv-p-transcription__toggle {
|
|
2776
|
+
color: LinkText;
|
|
2777
|
+
border: 1px solid ButtonText;
|
|
2778
|
+
}
|
|
2528
2779
|
.cv-p-bubble .cv-status-ticks,
|
|
2529
2780
|
.cv-p-copyable__done {
|
|
2530
2781
|
color: CanvasText;
|
|
2531
2782
|
}
|
|
2783
|
+
.cv-p-failed-menu__list {
|
|
2784
|
+
border-color: CanvasText;
|
|
2785
|
+
}
|
|
2786
|
+
.cv-p-failed-menu__item:focus-visible,
|
|
2787
|
+
.cv-p-failed-menu__item:hover {
|
|
2788
|
+
outline: 2px solid Highlight;
|
|
2789
|
+
}
|
|
2532
2790
|
}
|
|
2533
2791
|
@media (forced-colors: active) {
|
|
2534
2792
|
.cv-p-text__code,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@adatechnology/conversations-ui",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.10.0",
|
|
4
4
|
"description": "WhatsApp conversation UI components — parametrizável por endpoint, tema e feature flags",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
@@ -35,7 +35,7 @@
|
|
|
35
35
|
"clsx": "^2.1.1",
|
|
36
36
|
"lucide-react": "^1.21.0",
|
|
37
37
|
"tailwind-merge": "^3.6.0",
|
|
38
|
-
"@adatechnology/conversation-contracts": "0.
|
|
38
|
+
"@adatechnology/conversation-contracts": "0.6.0",
|
|
39
39
|
"@adatechnology/meta-whatsapp-contracts": "0.6.0"
|
|
40
40
|
},
|
|
41
41
|
"peerDependencies": {
|
|
@@ -49,6 +49,7 @@
|
|
|
49
49
|
}
|
|
50
50
|
},
|
|
51
51
|
"devDependencies": {
|
|
52
|
+
"@happy-dom/global-registrator": "^18.0.1",
|
|
52
53
|
"@types/bun": "1.3.14",
|
|
53
54
|
"@types/react": "^18 || ^19",
|
|
54
55
|
"@types/react-dom": "^18 || ^19",
|
|
@@ -11,6 +11,10 @@
|
|
|
11
11
|
import { useCallback, useEffect, useId, useRef, useState } from 'react'
|
|
12
12
|
|
|
13
13
|
import { AudioPlayer } from './AudioPlayer'
|
|
14
|
+
import { DEFAULT_MAX_RECORDING_MILLISECONDS, RECORDING_FORMATS, resolveRecordingFormat } from './audioRecorderFormat'
|
|
15
|
+
|
|
16
|
+
export { DEFAULT_MAX_RECORDING_MILLISECONDS, RECORDING_FORMATS, resolveRecordingFormat }
|
|
17
|
+
export type { RecordingFormat } from './audioRecorderFormat'
|
|
14
18
|
|
|
15
19
|
export interface AudioRecorderButtonLabels {
|
|
16
20
|
start: string
|
|
@@ -64,34 +68,6 @@ export interface AudioRecorderButtonProps {
|
|
|
64
68
|
disabledHint?: string
|
|
65
69
|
}
|
|
66
70
|
|
|
67
|
-
/**
|
|
68
|
-
* Cinco minutos: com o codec de voz do WhatsApp isso dá menos de 3MB, folgado dentro do teto de
|
|
69
|
-
* 16MB que a Meta impõe a áudio, e é mais do que qualquer recado de cliente. O corte automático
|
|
70
|
-
* existe porque gravação esquecida aberta só se descobre no envio, com o arquivo inteiro perdido.
|
|
71
|
-
*/
|
|
72
|
-
export const DEFAULT_MAX_RECORDING_MILLISECONDS = 5 * 60 * 1000
|
|
73
|
-
|
|
74
|
-
/**
|
|
75
|
-
* Ordem de preferência de formato: os dois primeiros o WhatsApp aceita como áudio; `webm` é só
|
|
76
|
-
* saída de emergência para navegador que não grava mais nada — gravar em webm e descobrir na hora
|
|
77
|
-
* do envio que o formato é inválido é pior do que gravar já no formato certo.
|
|
78
|
-
*/
|
|
79
|
-
const RECORDING_FORMATS = [
|
|
80
|
-
{ mimeType: 'audio/ogg;codecs=opus', uploadMimeType: 'audio/ogg', extension: 'ogg' },
|
|
81
|
-
{ mimeType: 'audio/mp4', uploadMimeType: 'audio/mp4', extension: 'm4a' },
|
|
82
|
-
{ mimeType: 'audio/webm', uploadMimeType: 'audio/webm', extension: 'webm' },
|
|
83
|
-
] as const
|
|
84
|
-
|
|
85
|
-
export type RecordingFormat = (typeof RECORDING_FORMATS)[number]
|
|
86
|
-
|
|
87
|
-
/** Primeiro formato que o navegador sabe gravar, ou `undefined` se não souber gravar nenhum. */
|
|
88
|
-
export function resolveRecordingFormat(): RecordingFormat | undefined {
|
|
89
|
-
if (typeof MediaRecorder === 'undefined') return undefined
|
|
90
|
-
// `isTypeSupported` não existe em toda implementação; onde falta, o primeiro da lista é o palpite.
|
|
91
|
-
if (typeof MediaRecorder.isTypeSupported !== 'function') return RECORDING_FORMATS[0]
|
|
92
|
-
return RECORDING_FORMATS.find((format) => MediaRecorder.isTypeSupported(format.mimeType))
|
|
93
|
-
}
|
|
94
|
-
|
|
95
71
|
type PendingRecording = { file: File; objectURL: string }
|
|
96
72
|
|
|
97
73
|
export function AudioRecorderButton({
|
package/src/Tooltip.tsx
CHANGED
|
@@ -14,6 +14,8 @@
|
|
|
14
14
|
import { useEffect, useRef, useState } from 'react'
|
|
15
15
|
import { createPortal } from 'react-dom'
|
|
16
16
|
|
|
17
|
+
import { createTooltipLayerRegistry } from './tooltipLayerRegistry'
|
|
18
|
+
|
|
17
19
|
/** Atributo que marca um elemento como portador de dica. */
|
|
18
20
|
export const TOOLTIP_ATTRIBUTE = 'data-cv-tooltip'
|
|
19
21
|
|
|
@@ -64,25 +66,16 @@ function tooltipStateFor(target: Element, text: string): TooltipState {
|
|
|
64
66
|
/**
|
|
65
67
|
* Só uma camada desenha por vez. A camada é montada por cada superfície do pacote (inbox, fluxos,
|
|
66
68
|
* documentos) para o host não precisar montá-la à mão; duas superfícies na mesma tela renderizariam
|
|
67
|
-
* dois balões sobrepostos sem essa trava.
|
|
69
|
+
* dois balões sobrepostos sem essa trava. Quando a dona desmonta, a posse passa à próxima montada.
|
|
68
70
|
*/
|
|
69
|
-
|
|
71
|
+
const layerRegistry = createTooltipLayerRegistry()
|
|
70
72
|
|
|
71
73
|
export function TooltipLayer() {
|
|
72
74
|
const identity = useRef({})
|
|
73
75
|
const [isOwner, setIsOwner] = useState(false)
|
|
74
76
|
const [tooltip, setTooltip] = useState<TooltipState | undefined>(undefined)
|
|
75
77
|
|
|
76
|
-
useEffect(() =>
|
|
77
|
-
const owned = identity.current
|
|
78
|
-
if (layerOwner && layerOwner !== owned) return undefined
|
|
79
|
-
layerOwner = owned
|
|
80
|
-
setIsOwner(true)
|
|
81
|
-
|
|
82
|
-
return () => {
|
|
83
|
-
if (layerOwner === owned) layerOwner = undefined
|
|
84
|
-
}
|
|
85
|
-
}, [])
|
|
78
|
+
useEffect(() => layerRegistry.register(identity.current, setIsOwner), [])
|
|
86
79
|
|
|
87
80
|
useEffect(() => {
|
|
88
81
|
if (!isOwner) return undefined
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { afterAll, beforeAll, describe, expect, it } from 'bun:test'
|
|
2
|
+
import { act, useRef } from 'react'
|
|
3
|
+
|
|
4
|
+
import { setupDom, wait, type DomHarness } from './domHarness.test-helper'
|
|
5
|
+
import { TooltipLayer } from './Tooltip'
|
|
6
|
+
import { useSuppressTouchTooltips } from './participant/useSuppressTouchTooltips'
|
|
7
|
+
|
|
8
|
+
let harness: DomHarness
|
|
9
|
+
|
|
10
|
+
beforeAll(async () => {
|
|
11
|
+
harness = await setupDom()
|
|
12
|
+
})
|
|
13
|
+
afterAll(() => harness.teardown())
|
|
14
|
+
|
|
15
|
+
function hover(target: Element): void {
|
|
16
|
+
act(() => void target.dispatchEvent(new Event('pointerover', { bubbles: true })))
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function balloons(): number {
|
|
20
|
+
return document.body.querySelectorAll('[role="tooltip"]').length
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function Target() {
|
|
24
|
+
return <button type="button" data-cv-tooltip="Hint">x</button>
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
describe('TooltipLayer ownership', () => {
|
|
28
|
+
it('draws one balloon with two layers, and keeps drawing after the owner unmounts', async () => {
|
|
29
|
+
const first = harness.mount(<TooltipLayer />)
|
|
30
|
+
harness.mount(<TooltipLayer />)
|
|
31
|
+
const target = harness.mount(<Target />).container.querySelector('button')!
|
|
32
|
+
|
|
33
|
+
hover(target)
|
|
34
|
+
await wait(200)
|
|
35
|
+
expect(balloons()).toBe(1)
|
|
36
|
+
|
|
37
|
+
first.unmount()
|
|
38
|
+
act(() => void target.dispatchEvent(new Event('pointerdown', { bubbles: true })))
|
|
39
|
+
hover(target)
|
|
40
|
+
await wait(200)
|
|
41
|
+
expect(balloons()).toBe(1)
|
|
42
|
+
})
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
function Thread() {
|
|
46
|
+
const rootRef = useRef<HTMLDivElement>(null)
|
|
47
|
+
useSuppressTouchTooltips(rootRef)
|
|
48
|
+
return (
|
|
49
|
+
<div ref={rootRef}>
|
|
50
|
+
<Target />
|
|
51
|
+
</div>
|
|
52
|
+
)
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
describe('touch on the participant thread', () => {
|
|
56
|
+
it('does not light the balloon when a tap focuses the button', async () => {
|
|
57
|
+
harness.mount(<TooltipLayer />)
|
|
58
|
+
const button = harness.mount(<Thread />).container.querySelector('button')!
|
|
59
|
+
|
|
60
|
+
act(() => void button.dispatchEvent(new PointerEvent('pointerdown', { bubbles: true, pointerType: 'touch' })))
|
|
61
|
+
act(() => void button.dispatchEvent(new Event('focusin', { bubbles: true })))
|
|
62
|
+
await wait(200)
|
|
63
|
+
|
|
64
|
+
expect(balloons()).toBe(0)
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
it('still shows it for keyboard focus after a touch', async () => {
|
|
68
|
+
harness.mount(<TooltipLayer />)
|
|
69
|
+
const button = harness.mount(<Thread />).container.querySelector('button')!
|
|
70
|
+
|
|
71
|
+
act(() => void button.dispatchEvent(new PointerEvent('pointerdown', { bubbles: true, pointerType: 'touch' })))
|
|
72
|
+
act(() => void button.dispatchEvent(new KeyboardEvent('keydown', { key: 'Tab', bubbles: true })))
|
|
73
|
+
act(() => void button.dispatchEvent(new Event('focusin', { bubbles: true })))
|
|
74
|
+
await wait(200)
|
|
75
|
+
|
|
76
|
+
expect(balloons()).toBe(1)
|
|
77
|
+
})
|
|
78
|
+
})
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cinco minutos: com o codec de voz do WhatsApp isso dá menos de 3MB, folgado dentro do teto de
|
|
3
|
+
* 16MB que a Meta impõe a áudio, e é mais do que qualquer recado de cliente. O corte automático
|
|
4
|
+
* existe porque gravação esquecida aberta só se descobre no envio, com o arquivo inteiro perdido.
|
|
5
|
+
*/
|
|
6
|
+
export const DEFAULT_MAX_RECORDING_MILLISECONDS = 5 * 60 * 1000
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Ordem de preferência de formato: os dois primeiros o WhatsApp aceita como áudio; `webm` é só
|
|
10
|
+
* saída de emergência para navegador que não grava mais nada — gravar em webm e descobrir na hora
|
|
11
|
+
* do envio que o formato é inválido é pior do que gravar já no formato certo.
|
|
12
|
+
*/
|
|
13
|
+
export const RECORDING_FORMATS = [
|
|
14
|
+
{ mimeType: 'audio/ogg;codecs=opus', uploadMimeType: 'audio/ogg', extension: 'ogg' },
|
|
15
|
+
{ mimeType: 'audio/mp4', uploadMimeType: 'audio/mp4', extension: 'm4a' },
|
|
16
|
+
{ mimeType: 'audio/webm', uploadMimeType: 'audio/webm', extension: 'webm' },
|
|
17
|
+
] as const
|
|
18
|
+
|
|
19
|
+
export type RecordingFormat = (typeof RECORDING_FORMATS)[number]
|
|
20
|
+
|
|
21
|
+
/** Primeiro formato que o navegador sabe gravar, ou `undefined` se não souber gravar nenhum. */
|
|
22
|
+
export function resolveRecordingFormat(): RecordingFormat | undefined {
|
|
23
|
+
if (typeof MediaRecorder === 'undefined') return undefined
|
|
24
|
+
// `isTypeSupported` não existe em toda implementação; onde falta, o primeiro da lista é o palpite.
|
|
25
|
+
if (typeof MediaRecorder.isTypeSupported !== 'function') return RECORDING_FORMATS[0]
|
|
26
|
+
return RECORDING_FORMATS.find((format) => MediaRecorder.isTypeSupported(format.mimeType))
|
|
27
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { GlobalRegistrator } from '@happy-dom/global-registrator'
|
|
2
|
+
import { act, type ReactElement } from 'react'
|
|
3
|
+
import type { Root } from 'react-dom/client'
|
|
4
|
+
|
|
5
|
+
export type MountedTree = {
|
|
6
|
+
readonly container: HTMLElement
|
|
7
|
+
readonly unmount: () => void
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export type DomHarness = {
|
|
11
|
+
readonly mount: (element: ReactElement) => MountedTree
|
|
12
|
+
readonly teardown: () => void
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Registers happy-dom for one test file only and mounts React trees into it. react-dom/client is
|
|
17
|
+
* imported after the registration because it reads the DOM globals when it loads.
|
|
18
|
+
*/
|
|
19
|
+
export async function setupDom(): Promise<DomHarness> {
|
|
20
|
+
GlobalRegistrator.register()
|
|
21
|
+
;(globalThis as { IS_REACT_ACT_ENVIRONMENT?: boolean }).IS_REACT_ACT_ENVIRONMENT = true
|
|
22
|
+
const { createRoot } = await import('react-dom/client')
|
|
23
|
+
const roots: Root[] = []
|
|
24
|
+
|
|
25
|
+
function mount(element: ReactElement): MountedTree {
|
|
26
|
+
const container = document.createElement('div')
|
|
27
|
+
document.body.appendChild(container)
|
|
28
|
+
const root = createRoot(container)
|
|
29
|
+
roots.push(root)
|
|
30
|
+
act(() => root.render(element))
|
|
31
|
+
return { container, unmount: () => act(() => root.unmount()) }
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function teardown(): void {
|
|
35
|
+
for (const root of roots) act(() => root.unmount())
|
|
36
|
+
document.body.innerHTML = ''
|
|
37
|
+
GlobalRegistrator.unregister()
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return { mount, teardown }
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function pressKey(target: Element, key: string): void {
|
|
44
|
+
act(() => {
|
|
45
|
+
target.dispatchEvent(new KeyboardEvent('keydown', { key, bubbles: true, cancelable: true }))
|
|
46
|
+
})
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function wait(milliseconds: number): Promise<void> {
|
|
50
|
+
return act(async () => {
|
|
51
|
+
await new Promise((resolve) => setTimeout(resolve, milliseconds))
|
|
52
|
+
})
|
|
53
|
+
}
|
|
@@ -10,6 +10,7 @@ import { ParticipantThread } from './ParticipantThread'
|
|
|
10
10
|
import { toParticipantApi, type ConversationThreadApi } from './conversationThreadApi'
|
|
11
11
|
import { buildThreadSummary } from './conversationThreadSummary'
|
|
12
12
|
import type { ParticipantSubjectGroup, ParticipantSubjectIconRenderer } from './participant.types'
|
|
13
|
+
import type { ParticipantAudioRecordingOptions } from './participantAudio.types'
|
|
13
14
|
import type { ParticipantPendingMessage } from './participantApi.types'
|
|
14
15
|
import { draftKey, type ParticipantDrafts } from './participantDrafts'
|
|
15
16
|
import { DEFAULT_PARTICIPANT_CONVERSATIONS_LABELS, type ParticipantConversationsLabels } from './participantLabels'
|
|
@@ -50,6 +51,8 @@ export type ConversationThreadProps = {
|
|
|
50
51
|
readonly tail?: boolean
|
|
51
52
|
readonly avatars?: 'initials'
|
|
52
53
|
readonly renderAuthorAvatar?: ParticipantAuthorAvatarRenderer
|
|
54
|
+
/** Absent draws no microphone. Present lets the user record and send an audio as a message of its own. */
|
|
55
|
+
readonly audioRecording?: ParticipantAudioRecordingOptions
|
|
53
56
|
}
|
|
54
57
|
|
|
55
58
|
function useConversationThreadCore(props: ConversationThreadProps): ParticipantThreadCoreProps {
|
|
@@ -105,6 +108,7 @@ function ConversationThreadBody(props: ConversationThreadProps) {
|
|
|
105
108
|
tail={props.tail}
|
|
106
109
|
counterpartLabel={props.counterpartLabel}
|
|
107
110
|
headerActions={props.headerActions}
|
|
111
|
+
audioRecording={props.audioRecording}
|
|
108
112
|
/>
|
|
109
113
|
</div>
|
|
110
114
|
</div>
|