From c5b13c8d452ce2dd63db4aa545dea75a651a9cd8 Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Wed, 10 Jun 2026 15:47:19 +0530 Subject: [PATCH] feat: animate widget resize --- app/javascript/sdk/sdk.js | 7 ++-- app/javascript/widget/App.vue | 28 +++++++++++++-- .../widget/constants/widgetBusEvents.js | 1 + app/javascript/widget/views/ArticleViewer.vue | 36 ++++++++++++++----- 4 files changed, 58 insertions(+), 14 deletions(-) diff --git a/app/javascript/sdk/sdk.js b/app/javascript/sdk/sdk.js index 44473c8c8..82f72b81e 100644 --- a/app/javascript/sdk/sdk.js +++ b/app/javascript/sdk/sdk.js @@ -12,8 +12,9 @@ export const SDK_CSS = ` overflow: hidden !important; position: fixed !important; transition: opacity 0.2s linear, transform 0.25s linear, - width 0.3s cubic-bezier(0.4, 0, 0.2, 1), - height 0.3s cubic-bezier(0.4, 0, 0.2, 1); + width 0.2s cubic-bezier(0.4, 0, 0.2, 1), + height 0.2s cubic-bezier(0.4, 0, 0.2, 1), + max-height 0.2s cubic-bezier(0.4, 0, 0.2, 1); z-index: 2147483000 !important; } @@ -293,7 +294,7 @@ export const SDK_CSS = ` .woot-widget-holder.has-article-view { width: min(640px, max(0px, -20px + 100dvw)) !important; height: calc(100% - 125px) !important; - max-height: none !important; + max-height: calc(100% - 125px) !important; } } diff --git a/app/javascript/widget/App.vue b/app/javascript/widget/App.vue index c5ed2f9cc..0fc9f12aa 100755 --- a/app/javascript/widget/App.vue +++ b/app/javascript/widget/App.vue @@ -16,7 +16,12 @@ import { ON_AGENT_MESSAGE_RECEIVED, ON_CAMPAIGN_MESSAGE_CLICK, ON_UNREAD_MESSAGE_CLICK, + ON_ARTICLE_VIEW_RESIZING, } from './constants/widgetBusEvents'; + +// Keep in sync with the widget holder width/height transition in sdk.js. The +// article view is masked for this long so the iframe can reflow off-screen. +const ARTICLE_VIEW_RESIZE_DURATION = 200; import { useDarkMode } from 'widget/composables/useDarkMode'; import { useRouter } from 'vue-router'; import { useAvailability } from 'widget/composables/useAvailability'; @@ -44,6 +49,7 @@ export default { return { isMobile: false, campaignsSnoozedTill: undefined, + isArticleView: false, }; }, computed: { @@ -87,6 +93,7 @@ export default { // Here we just collapse when leaving the article view, since the iframe is // torn down then and can no longer signal. if (this.isIFrame && previousRouteName === 'article-viewer') { + this.isArticleView = false; IFrameHelper.sendMessage({ event: 'collapseWidget' }); } }, @@ -193,6 +200,23 @@ export default { query: { link, v: Date.now() }, }); }, + setArticleView(isArticle) { + // Only resize when the page type actually changes, so navigating between + // two articles (or two listing pages) doesn't trigger a needless mask. + if (!this.isIFrame || isArticle === this.isArticleView) return; + this.isArticleView = isArticle; + + // Mask the iframe while the widget resizes so its text reflow happens + // off-screen, then reveal it once the size transition has settled. + emitter.emit(ON_ARTICLE_VIEW_RESIZING, true); + IFrameHelper.sendMessage({ + event: isArticle ? 'expandWidget' : 'collapseWidget', + }); + setTimeout( + () => emitter.emit(ON_ARTICLE_VIEW_RESIZING, false), + ARTICLE_VIEW_RESIZE_DURATION + ); + }, registerUnreadEvents() { emitter.on(ON_AGENT_MESSAGE_RECEIVED, () => { const { name: routeName } = this.$route; @@ -352,9 +376,7 @@ export default { } else if (message.event === 'open-article') { this.openArticle(message.slug); } else if (message.event === 'portalPageLoaded') { - IFrameHelper.sendMessage({ - event: message.isArticle ? 'expandWidget' : 'collapseWidget', - }); + this.setArticleView(message.isArticle); } else if (message.event === 'toggle-open') { this.$store.dispatch('appConfig/toggleWidgetOpen', message.isOpen); diff --git a/app/javascript/widget/constants/widgetBusEvents.js b/app/javascript/widget/constants/widgetBusEvents.js index 1ffcd3d51..1f3f573c3 100644 --- a/app/javascript/widget/constants/widgetBusEvents.js +++ b/app/javascript/widget/constants/widgetBusEvents.js @@ -2,3 +2,4 @@ export const ON_AGENT_MESSAGE_RECEIVED = 'ON_AGENT_MESSAGE_RECEIVED'; export const ON_UNREAD_MESSAGE_CLICK = 'ON_UNREAD_MESSAGE_CLICK'; export const ON_CAMPAIGN_MESSAGE_CLICK = 'ON_CAMPAIGN_MESSAGE_CLICK'; export const ON_CONVERSATION_CREATED = 'ON_CONVERSATION_CREATED'; +export const ON_ARTICLE_VIEW_RESIZING = 'ON_ARTICLE_VIEW_RESIZING'; diff --git a/app/javascript/widget/views/ArticleViewer.vue b/app/javascript/widget/views/ArticleViewer.vue index bb04efcc4..b4ce2be2e 100644 --- a/app/javascript/widget/views/ArticleViewer.vue +++ b/app/javascript/widget/views/ArticleViewer.vue @@ -1,21 +1,41 @@ -