diff --git a/src/api/gramjs/apiBuilders/messages.ts b/src/api/gramjs/apiBuilders/messages.ts index bcd568728..fed83c3f6 100644 --- a/src/api/gramjs/apiBuilders/messages.ts +++ b/src/api/gramjs/apiBuilders/messages.ts @@ -929,6 +929,7 @@ function buildUploadingMedia( audio, shouldSendAsFile, shouldSendAsSpoiler, + ttlSeconds, } = attachment; if (!shouldSendAsFile) { @@ -973,6 +974,7 @@ function buildUploadingMedia( duration, waveform: inputWaveform, }, + ttlSeconds, }; } if (SUPPORTED_AUDIO_CONTENT_TYPES.has(mimeType)) { diff --git a/src/api/gramjs/methods/messages.ts b/src/api/gramjs/methods/messages.ts index cf8d40372..c5c04a936 100644 --- a/src/api/gramjs/methods/messages.ts +++ b/src/api/gramjs/methods/messages.ts @@ -624,7 +624,7 @@ export async function rescheduleMessage({ async function uploadMedia(localMessage: ApiMessage, attachment: ApiAttachment, onProgress: ApiOnProgress) { const { - filename, blobUrl, mimeType, quick, voice, audio, previewBlobUrl, shouldSendAsFile, shouldSendAsSpoiler, + filename, blobUrl, mimeType, quick, voice, audio, previewBlobUrl, shouldSendAsFile, shouldSendAsSpoiler, ttlSeconds, } = attachment; const patchedOnProgress: ApiOnProgress = (progress) => { @@ -698,6 +698,7 @@ async function uploadMedia(localMessage: ApiMessage, attachment: ApiAttachment, thumb, forceFile: shouldSendAsFile, spoiler: shouldSendAsSpoiler, + ttlSeconds, }); } diff --git a/src/api/types/misc.ts b/src/api/types/misc.ts index e204e4486..aa6f22940 100644 --- a/src/api/types/misc.ts +++ b/src/api/types/misc.ts @@ -52,6 +52,7 @@ export interface ApiAttachment { shouldSendAsSpoiler?: true; uniqueId?: string; + ttlSeconds?: number; } export interface ApiWallpaper { diff --git a/src/assets/font-icons/one-filled.svg b/src/assets/font-icons/one-filled.svg new file mode 100644 index 000000000..88076a85b --- /dev/null +++ b/src/assets/font-icons/one-filled.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/font-icons/view-once.svg b/src/assets/font-icons/view-once.svg index 7c58270b1..f4764edc5 100644 --- a/src/assets/font-icons/view-once.svg +++ b/src/assets/font-icons/view-once.svg @@ -1,6 +1 @@ - - - - - - + \ No newline at end of file diff --git a/src/components/common/Audio.scss b/src/components/common/Audio.scss index 155092941..54debfa94 100644 --- a/src/components/common/Audio.scss +++ b/src/components/common/Audio.scss @@ -26,6 +26,22 @@ .toogle-play-wrapper { margin: 0; + + .icon-view-once { + position: absolute; + padding: 0.125rem; + left: 2rem; + bottom: 0; + font-size: 1rem; + border-radius: 50%; + color: var(--color-white); + background-color: var(--color-primary); + outline: var(--background-color) solid 0.125rem; + z-index: var(--z-badge); + opacity: 1; + transform: scale(1); + transition: opacity 0.4s, transform 0.6s cubic-bezier(0.34, 1.56, 0.64, 1); + } .toggle-play { margin-inline-end: 0.5rem; @@ -66,27 +82,16 @@ transform: scale(1); transition: opacity 0.4s, transform 0.6s cubic-bezier(0.34, 1.56, 0.64, 1); } - - &.play .icon-pause, - &.pause .icon-play, - &.loading .icon-play, - &.loading .icon-pause, - &.loading .flame { - opacity: 0; - transform: scale(0.5); - } } - .icon-view-once { - position: absolute; - left: 2rem; - bottom: 0rem; - font-size: 1.1875rem; - border-radius: 50%; - color: var(--color-white); - background-color: var(--color-primary); - outline: var(--background-color) solid 0.125rem; - z-index: var(--z-badge); + &.play .icon-pause, + &.pause .icon-play, + &.loading .icon-play, + &.loading .icon-pause, + &.loading .flame, + &.loading .icon-view-once { + opacity: 0; + transform: scale(0.5); } } diff --git a/src/components/common/Audio.tsx b/src/components/common/Audio.tsx index 5cc544b20..7174524c4 100644 --- a/src/components/common/Audio.tsx +++ b/src/components/common/Audio.tsx @@ -123,7 +123,7 @@ const Audio: FC = ({ const { isRtl } = lang; const { isMobile } = useAppLayout(); - const [isActivated, setIsActivated] = useState(Boolean(autoPlay)); + const [isActivated, setIsActivated] = useState(false); const shouldLoad = isActivated || PRELOAD; const coverHash = getMessageMediaHash(message, 'pictogram'); const coverBlobUrl = useMedia(coverHash, false, ApiMediaFormat.BlobUrl); @@ -167,13 +167,14 @@ const Audio: FC = ({ bufferingHandlers, undefined, checkBuffering, - isActivated, + Boolean(isActivated || autoPlay), handleForcePlay, handleTrackChange, isMessageLocal(message) || hasTtl, undefined, onPause, noReset, + hasTtl && !isInOneTimeModal, ); const reversePlayProgress = 1 - playProgress; @@ -220,8 +221,7 @@ const Audio: FC = ({ } if (hasTtl) { - // Set new date to prevent saving state of the track - openOneTimeMediaModal({ message: { ...message, date: Date.now() } }); + openOneTimeMediaModal({ message }); onReadMedia?.(); return; } @@ -329,7 +329,7 @@ const Audio: FC = ({ isSelected && 'audio-is-selected', ); - const buttonClassNames = ['toggle-play']; + const buttonClassNames = ['toogle-play-wrapper']; if (shouldRenderCross) { buttonClassNames.push('loading'); } else { @@ -371,13 +371,13 @@ const Audio: FC = ({ function renderTooglePlayWrapper() { return ( - + .icon { animation-duration: 0ms !important; } diff --git a/src/components/common/Composer.tsx b/src/components/common/Composer.tsx index 625baded2..9b3bbfc1f 100644 --- a/src/components/common/Composer.tsx +++ b/src/components/common/Composer.tsx @@ -40,6 +40,7 @@ import { EDITABLE_INPUT_MODAL_ID, HEART_REACTION, MAX_UPLOAD_FILEPART_SIZE, + ONE_TIME_MEDIA_TTL_SECONDS, REPLIES_USER_ID, SCHEDULED_WHEN_ONLINE, SEND_MESSAGE_ACTION_INTERVAL, @@ -157,6 +158,7 @@ import ResponsiveHoverButton from '../ui/ResponsiveHoverButton'; import Spinner from '../ui/Spinner'; import Avatar from './Avatar'; import DeleteMessageModal from './DeleteMessageModal.async'; +import Icon from './Icon'; import ReactionAnimatedEmoji from './reactions/ReactionAnimatedEmoji'; import './Composer.scss'; @@ -203,7 +205,7 @@ type StateProps = botKeyboardMessageId?: number; botKeyboardPlaceholder?: string; withScheduledButton?: boolean; - shouldSchedule?: boolean; + isInScheduledList?: boolean; canScheduleUntilOnline?: boolean; stickersForEmoji?: ApiSticker[]; customEmojiForEmoji?: ApiSticker[]; @@ -245,6 +247,7 @@ type StateProps = shouldCollectDebugLogs?: boolean; sentStoryReaction?: ApiReaction; stealthMode?: ApiStealthMode; + canSendOneTimeMedia?: boolean; }; enum MainButtonState { @@ -253,6 +256,7 @@ enum MainButtonState { Edit = 'edit', Schedule = 'schedule', Forward = 'forward', + SendOneTime = 'sendOneTime', } type ScheduledMessageArgs = TabState['contentToBeScheduled'] | { @@ -273,7 +277,7 @@ const Composer: FC = ({ type, isOnActiveTab, dropAreaState, - shouldSchedule, + isInScheduledList, canScheduleUntilOnline, isReady, isMobile, @@ -346,6 +350,7 @@ const Composer: FC = ({ shouldCollectDebugLogs, sentStoryReaction, stealthMode, + canSendOneTimeMedia, onForward, }) => { const { @@ -544,6 +549,9 @@ const Composer: FC = ({ currentRecordTime, recordButtonRef: mainButtonRef, startRecordTimeRef, + isViewOnceEnabled, + setIsViewOnceEnabled, + toogleViewOnceEnabled, } = useVoiceRecording(); const shouldSendRecordingStatus = isForCurrentMessageList && !isInStoryViewer; @@ -753,16 +761,16 @@ const Composer: FC = ({ return MainButtonState.Record; } - if (shouldSchedule) { + if (isInScheduledList) { return MainButtonState.Schedule; } return MainButtonState.Send; }, [ activeVoiceRecording, editingMessage, getHtml, hasAttachments, isForwarding, isInputHasFocus, onForward, - shouldForceShowEditing, shouldSchedule, + shouldForceShowEditing, isInScheduledList, ]); - const canShowCustomSendMenu = !shouldSchedule; + const canShowCustomSendMenu = !isInScheduledList; const { isContextMenuOpen: isCustomSendMenuOpen, @@ -928,12 +936,13 @@ const Composer: FC = ({ if (activeVoiceRecording) { const record = await stopRecordingVoice(); + const ttlSeconds = isViewOnceEnabled ? ONE_TIME_MEDIA_TTL_SECONDS : undefined; if (record) { const { blob, duration, waveform } = record; currentAttachments = [await buildAttachment( VOICE_RECORDING_FILENAME, blob, - { voice: { duration, waveform } }, + { voice: { duration, waveform }, ttlSeconds }, )]; } } @@ -1082,7 +1091,7 @@ const Composer: FC = ({ return; } - if (shouldSchedule || isScheduleRequested) { + if (isInScheduledList || isScheduleRequested) { forceShowSymbolMenu(); requestCalendar((scheduledAt) => { cancelForceShowSymbolMenu(); @@ -1115,7 +1124,7 @@ const Composer: FC = ({ isPreloadedGlobally: true, }; - if (shouldSchedule || isScheduleRequested) { + if (isInScheduledList || isScheduleRequested) { forceShowSymbolMenu(); requestCalendar((scheduledAt) => { cancelForceShowSymbolMenu(); @@ -1144,7 +1153,7 @@ const Composer: FC = ({ return; } - if (shouldSchedule || isScheduleRequested) { + if (isInScheduledList || isScheduleRequested) { requestCalendar((scheduledAt) => { handleMessageSchedule({ id: inlineResult.id, @@ -1184,7 +1193,7 @@ const Composer: FC = ({ return; } - if (shouldSchedule) { + if (isInScheduledList) { requestCalendar((scheduledAt) => { handleMessageSchedule({ poll }, scheduledAt, currentMessageList); }); @@ -1196,7 +1205,7 @@ const Composer: FC = ({ }); const sendSilent = useLastCallback((additionalArgs?: ScheduledMessageArgs) => { - if (shouldSchedule) { + if (isInScheduledList) { requestCalendar((scheduledAt) => { handleMessageSchedule({ ...additionalArgs, isSilent: true }, scheduledAt, currentMessageList!); }); @@ -1340,6 +1349,7 @@ const Composer: FC = ({ showAllowedMessageTypesNotification({ chatId }); } } else { + setIsViewOnceEnabled(false); void startRecordingVoice(); } break; @@ -1513,7 +1523,7 @@ const Composer: FC = ({ shouldForceAsFile={shouldForceAsFile} isForCurrentMessageList={isForCurrentMessageList} isForMessage={isInMessageList} - shouldSchedule={shouldSchedule} + shouldSchedule={isInScheduledList} forceDarkTheme={isInStoryViewer} onCaptionUpdate={onCaptionUpdate} onSendSilent={handleSendSilentAttachments} @@ -1762,7 +1772,7 @@ const Composer: FC = ({ canSendAudios={canSendAudios} onFileSelect={handleFileSelect} onPollCreate={openPollModal} - isScheduled={shouldSchedule} + isScheduled={isInScheduledList} attachBots={isInMessageList ? attachBots : undefined} peerType={attachMenuPeerType} shouldCollectDebugLogs={shouldCollectDebugLogs} @@ -1813,6 +1823,18 @@ const Composer: FC = ({ /> + {canSendOneTimeMedia && activeVoiceRecording && ( + + + + + )} {activeVoiceRecording && ( = ({ {canShowCustomSendMenu && ( ( const replyToTopic = chat?.isForum && chat.isForumAsMessages && threadId === MAIN_THREAD_ID && replyToMessage ? selectTopicFromMessage(global, replyToMessage) : undefined; + const isInScheduledList = messageListType === 'scheduled'; return { availableReactions: type === 'story' ? global.availableReactions : undefined, @@ -1977,7 +2000,7 @@ export default memo(withGlobal( messageListType === 'thread' && Boolean(scheduledIds?.length) ), - shouldSchedule: messageListType === 'scheduled', + isInScheduledList, botKeyboardMessageId, botKeyboardPlaceholder: keyboardMessage?.keyboardPlaceholder, isForwarding: chatId === tabState.forwardMessages.toChatId, @@ -2019,6 +2042,7 @@ export default memo(withGlobal( isReactionPickerOpen: selectIsReactionPickerOpen(global), canBuyPremium: !isCurrentUserPremium && !selectIsPremiumPurchaseBlocked(global), canPlayAnimatedEmojis: selectCanPlayAnimatedEmojis(global), + canSendOneTimeMedia: isChatWithUser && !isChatWithBot && !isInScheduledList, shouldCollectDebugLogs: global.settings.byKey.shouldCollectDebugLogs, sentStoryReaction, stealthMode: global.stories.stealthMode, diff --git a/src/components/middle/composer/hooks/useVoiceRecording.ts b/src/components/middle/composer/hooks/useVoiceRecording.ts index 80abba62b..9927a8797 100644 --- a/src/components/middle/composer/hooks/useVoiceRecording.ts +++ b/src/components/middle/composer/hooks/useVoiceRecording.ts @@ -17,6 +17,7 @@ const useVoiceRecording = () => { const [activeVoiceRecording, setActiveVoiceRecording] = useState(); const startRecordTimeRef = useRef(); const [currentRecordTime, setCurrentRecordTime] = useState(); + const [isViewOnceEnabled, setIsViewOnceEnabled] = useState(false); useEffect(() => { // Preloading worker fixes silent first record on iOS @@ -96,6 +97,10 @@ const useVoiceRecording = () => { return activeVoiceRecording ? captureEscKeyListener(stopRecordingVoice) : undefined; }, [activeVoiceRecording, stopRecordingVoice]); + const toogleViewOnceEnabled = useLastCallback(() => { + setIsViewOnceEnabled(!isViewOnceEnabled); + }); + return { startRecordingVoice, pauseRecordingVoice, @@ -104,6 +109,9 @@ const useVoiceRecording = () => { currentRecordTime, recordButtonRef, startRecordTimeRef, + isViewOnceEnabled, + setIsViewOnceEnabled, + toogleViewOnceEnabled, }; }; diff --git a/src/config.ts b/src/config.ts index 5bffd0a44..b936874d7 100644 --- a/src/config.ts +++ b/src/config.ts @@ -343,3 +343,5 @@ export const DEFAULT_LIMITS: Record = { recommendedChannels: [10, 100], savedDialogsPinned: [5, 100], }; + +export const ONE_TIME_MEDIA_TTL_SECONDS = 2147483647; diff --git a/src/hooks/useAudioPlayer.ts b/src/hooks/useAudioPlayer.ts index 21cb043c9..1de4b0f1b 100644 --- a/src/hooks/useAudioPlayer.ts +++ b/src/hooks/useAudioPlayer.ts @@ -34,6 +34,7 @@ const useAudioPlayer = ( noProgressUpdates = false, onPause?: NoneToVoidFunction, noReset = false, + noHandleEvents = false, ) => { // eslint-disable-next-line no-null/no-null const controllerRef = useRef>(null); @@ -50,6 +51,9 @@ const useAudioPlayer = ( useSyncEffect(() => { controllerRef.current = register(trackId, trackType, (eventName, e) => { + if (noHandleEvents) { + return; + } switch (eventName) { case 'onPlay': { const { @@ -67,7 +71,6 @@ const useAudioPlayer = ( if (trackType === 'voice' || duration > PLAYBACK_RATE_FOR_AUDIO_MIN_DURATION) { setPlaybackRate(audioPlayer.playbackRate); } - setPositionState({ duration: proxy.duration || 0, playbackRate: proxy.playbackRate, @@ -103,7 +106,6 @@ const useAudioPlayer = ( break; } } - handlers?.[eventName]?.(e); }, onForcePlay, handleTrackChange); @@ -116,7 +118,7 @@ const useAudioPlayer = ( isPlayingSync = true; } - if (onInit) { + if (onInit && !noHandleEvents) { onInit(proxy); } }, [trackId]); @@ -159,7 +161,7 @@ const useAudioPlayer = ( // Autoplay once `src` is present useEffectWithPrevDeps(([prevShouldPlay, prevSrc]) => { - if (prevShouldPlay === shouldPlay && src === prevSrc && trackType !== 'oneTimeVoice') { + if (prevShouldPlay === shouldPlay && src === prevSrc) { return; } diff --git a/src/styles/icons.scss b/src/styles/icons.scss index dc1bc687f..e5b128566 100644 --- a/src/styles/icons.scss +++ b/src/styles/icons.scss @@ -157,102 +157,103 @@ $icons-map: ( "next": "\f17e", "noise-suppression": "\f17f", "non-contacts": "\f180", - "open-in-new-tab": "\f181", - "password-off": "\f182", - "pause": "\f183", - "permissions": "\f184", - "phone-discard-outline": "\f185", - "phone-discard": "\f186", - "phone": "\f187", - "photo": "\f188", - "pin-badge": "\f189", - "pin-list": "\f18a", - "pin": "\f18b", - "pinned-chat": "\f18c", - "pinned-message": "\f18d", - "pip": "\f18e", - "play-story": "\f18f", - "play": "\f190", - "poll": "\f191", - "premium": "\f192", - "previous": "\f193", - "privacy-policy": "\f194", - "quote-text": "\f195", - "quote": "\f196", - "readchats": "\f197", - "recent": "\f198", - "reload": "\f199", - "remove": "\f19a", - "reopen-topic": "\f19b", - "replace": "\f19c", - "replies": "\f19d", - "reply-filled": "\f19e", - "reply": "\f19f", - "revote": "\f1a0", - "save-story": "\f1a1", - "saved-messages": "\f1a2", - "schedule": "\f1a3", - "search": "\f1a4", - "select": "\f1a5", - "send-outline": "\f1a6", - "send": "\f1a7", - "settings-filled": "\f1a8", - "settings": "\f1a9", - "share-filled": "\f1aa", - "share-screen-outlined": "\f1ab", - "share-screen-stop": "\f1ac", - "share-screen": "\f1ad", - "sidebar": "\f1ae", - "skip-next": "\f1af", - "skip-previous": "\f1b0", - "smallscreen": "\f1b1", - "smile": "\f1b2", - "sort": "\f1b3", - "speaker-muted-story": "\f1b4", - "speaker-outline": "\f1b5", - "speaker-story": "\f1b6", - "speaker": "\f1b7", - "spoiler-disable": "\f1b8", - "spoiler": "\f1b9", - "sport": "\f1ba", - "stats": "\f1bb", - "stealth-future": "\f1bc", - "stealth-past": "\f1bd", - "stickers": "\f1be", - "stop-raising-hand": "\f1bf", - "stop": "\f1c0", - "story-caption": "\f1c1", - "story-expired": "\f1c2", - "story-priority": "\f1c3", - "story-reply": "\f1c4", - "strikethrough": "\f1c5", - "timer": "\f1c6", - "transcribe": "\f1c7", - "truck": "\f1c8", - "unarchive": "\f1c9", - "underlined": "\f1ca", - "unlock-badge": "\f1cb", - "unlock": "\f1cc", - "unmute": "\f1cd", - "unpin": "\f1ce", - "unread": "\f1cf", - "up": "\f1d0", - "user-filled": "\f1d1", - "user-online": "\f1d2", - "user": "\f1d3", - "video-outlined": "\f1d4", - "video-stop": "\f1d5", - "video": "\f1d6", - "view-once": "\f1d7", - "voice-chat": "\f1d8", - "volume-1": "\f1d9", - "volume-2": "\f1da", - "volume-3": "\f1db", - "web": "\f1dc", - "webapp": "\f1dd", - "word-wrap": "\f1de", - "zoom-in": "\f1df", - "zoom-out": "\f1e0", + "one-filled": "\f181", + "open-in-new-tab": "\f182", + "password-off": "\f183", + "pause": "\f184", + "permissions": "\f185", + "phone-discard-outline": "\f186", + "phone-discard": "\f187", + "phone": "\f188", + "photo": "\f189", + "pin-badge": "\f18a", + "pin-list": "\f18b", + "pin": "\f18c", + "pinned-chat": "\f18d", + "pinned-message": "\f18e", + "pip": "\f18f", + "play-story": "\f190", + "play": "\f191", + "poll": "\f192", + "premium": "\f193", + "previous": "\f194", + "privacy-policy": "\f195", + "quote-text": "\f196", + "quote": "\f197", + "readchats": "\f198", + "recent": "\f199", + "reload": "\f19a", + "remove": "\f19b", + "reopen-topic": "\f19c", + "replace": "\f19d", + "replies": "\f19e", + "reply-filled": "\f19f", + "reply": "\f1a0", + "revote": "\f1a1", + "save-story": "\f1a2", + "saved-messages": "\f1a3", + "schedule": "\f1a4", + "search": "\f1a5", + "select": "\f1a6", + "send-outline": "\f1a7", + "send": "\f1a8", + "settings-filled": "\f1a9", + "settings": "\f1aa", + "share-filled": "\f1ab", + "share-screen-outlined": "\f1ac", + "share-screen-stop": "\f1ad", + "share-screen": "\f1ae", + "sidebar": "\f1af", + "skip-next": "\f1b0", + "skip-previous": "\f1b1", + "smallscreen": "\f1b2", + "smile": "\f1b3", + "sort": "\f1b4", + "speaker-muted-story": "\f1b5", + "speaker-outline": "\f1b6", + "speaker-story": "\f1b7", + "speaker": "\f1b8", + "spoiler-disable": "\f1b9", + "spoiler": "\f1ba", + "sport": "\f1bb", + "stats": "\f1bc", + "stealth-future": "\f1bd", + "stealth-past": "\f1be", + "stickers": "\f1bf", + "stop-raising-hand": "\f1c0", + "stop": "\f1c1", + "story-caption": "\f1c2", + "story-expired": "\f1c3", + "story-priority": "\f1c4", + "story-reply": "\f1c5", + "strikethrough": "\f1c6", + "timer": "\f1c7", + "transcribe": "\f1c8", + "truck": "\f1c9", + "unarchive": "\f1ca", + "underlined": "\f1cb", + "unlock-badge": "\f1cc", + "unlock": "\f1cd", + "unmute": "\f1ce", + "unpin": "\f1cf", + "unread": "\f1d0", + "up": "\f1d1", + "user-filled": "\f1d2", + "user-online": "\f1d3", + "user": "\f1d4", + "video-outlined": "\f1d5", + "video-stop": "\f1d6", + "video": "\f1d7", + "view-once": "\f1d8", + "voice-chat": "\f1d9", + "volume-1": "\f1da", + "volume-2": "\f1db", + "volume-3": "\f1dc", + "web": "\f1dd", + "webapp": "\f1de", + "word-wrap": "\f1df", + "zoom-in": "\f1e0", + "zoom-out": "\f1e1", ); .icon-active-sessions::before { @@ -639,6 +640,9 @@ $icons-map: ( .icon-non-contacts::before { content: map.get($icons-map, "non-contacts"); } +.icon-one-filled::before { + content: map.get($icons-map, "one-filled"); +} .icon-open-in-new-tab::before { content: map.get($icons-map, "open-in-new-tab"); } diff --git a/src/styles/icons.woff b/src/styles/icons.woff index 2aa797b3a..f1dbca30b 100644 Binary files a/src/styles/icons.woff and b/src/styles/icons.woff differ diff --git a/src/styles/icons.woff2 b/src/styles/icons.woff2 index 7233a54f3..6de97315d 100644 Binary files a/src/styles/icons.woff2 and b/src/styles/icons.woff2 differ diff --git a/src/types/icons/font.ts b/src/types/icons/font.ts index 6ac39c671..ad85f4562 100644 --- a/src/types/icons/font.ts +++ b/src/types/icons/font.ts @@ -127,6 +127,7 @@ export type FontIconName = | 'next' | 'noise-suppression' | 'non-contacts' + | 'one-filled' | 'open-in-new-tab' | 'password-off' | 'pause'