22// Use of this source code is governed by a BSD-style license that can be
33// found in the LICENSE file.
44
5+ import * as Common from '../../../core/common/common.js' ;
56import * as Host from '../../../core/host/host.js' ;
67import * as i18n from '../../../core/i18n/i18n.js' ;
78import type * as Platform from '../../../core/platform/platform.js' ;
@@ -55,12 +56,21 @@ const UIStringsNotTranslate = {
5556 * issue with the AI assistance message.
5657 */
5758 report : 'Report legal issue' ,
59+ /**
60+ * @description The title of the button for scrolling to see next suggestions
61+ */
62+ scrollToNext : 'Scroll to next suggestions' ,
63+ /**
64+ * @description The title of the button for scrolling to see previous suggestions
65+ */
66+ scrollToPrevious : 'Scroll to previous suggestions' ,
5867} ;
5968
6069const lockedString = i18n . i18n . lockedString ;
6170
6271const REPORT_URL = 'https://support.google.com/legal/troubleshooter/1114905?hl=en#ts=1115658%2C13380504' as
6372 Platform . DevToolsPath . UrlString ;
73+ const SCROLL_ROUNDING_OFFSET = 1 ;
6474export interface UserActionRowProps {
6575 showRateButtons : boolean ;
6676 onFeedbackSubmit : ( rate : Host . AidaClient . Rating , feedback ?: string ) => void ;
@@ -75,6 +85,11 @@ export class UserActionRow extends HTMLElement {
7585 #isShowingFeedbackForm = false ;
7686 #currentRating?: Host . AidaClient . Rating ;
7787 #isSubmitButtonDisabled = true ;
88+ #suggestionsScrollContainerRef = LitHtml . Directives . createRef < HTMLElement > ( ) ;
89+ #suggestionsLeftScrollButtonContainerRef = LitHtml . Directives . createRef < HTMLElement > ( ) ;
90+ #suggestionsRightScrollButtonContainerRef = LitHtml . Directives . createRef < HTMLElement > ( ) ;
91+ #suggestionsResizeObserver = new ResizeObserver ( ( ) => this . #handleSuggestionsScrollOrResize( ) ) ;
92+ #suggestionsEvaluateLayoutThrottler = new Common . Throttler . Throttler ( 50 ) ;
7893
7994 constructor ( props : UserActionRowProps ) {
8095 super ( ) ;
@@ -84,13 +99,44 @@ export class UserActionRow extends HTMLElement {
8499 set props ( props : UserActionRowProps ) {
85100 this . #props = props ;
86101 this . #render( ) ;
102+ this . #evaluateSuggestionsLayout( ) ;
87103 }
88104
89105 connectedCallback ( ) : void {
90106 this . #shadow. adoptedStyleSheets = [ userActionRowStyles , Input . textInputStyles ] ;
91107 this . #render( ) ;
108+ this . #evaluateSuggestionsLayout( ) ;
109+
110+ if ( this . #suggestionsScrollContainerRef. value ) {
111+ this . #suggestionsResizeObserver. observe ( this . #suggestionsScrollContainerRef. value ) ;
112+ }
113+ }
114+
115+ disconnectedCallback ( ) : void {
116+ this . #suggestionsResizeObserver. disconnect ( ) ;
92117 }
93118
119+ #handleSuggestionsScrollOrResize = ( ) : void => {
120+ void this . #suggestionsEvaluateLayoutThrottler. schedule ( ( ) => {
121+ this . #evaluateSuggestionsLayout( ) ;
122+ return Promise . resolve ( ) ;
123+ } ) ;
124+ } ;
125+
126+ #scrollSuggestionsScrollContainer = ( direction : 'left' | 'right' ) : void => {
127+ const suggestionsScrollContainer = this . #suggestionsScrollContainerRef. value ;
128+ if ( ! suggestionsScrollContainer ) {
129+ return ;
130+ }
131+
132+ suggestionsScrollContainer . scroll ( {
133+ top : 0 ,
134+ left : direction === 'left' ? suggestionsScrollContainer . scrollLeft - suggestionsScrollContainer . clientWidth :
135+ suggestionsScrollContainer . scrollLeft + suggestionsScrollContainer . clientWidth ,
136+ behavior : 'smooth' ,
137+ } ) ;
138+ } ;
139+
94140 #handleRateClick( rating : Host . AidaClient . Rating ) : void {
95141 if ( this . #currentRating === rating ) {
96142 return ;
@@ -177,6 +223,22 @@ export class UserActionRow extends HTMLElement {
177223 }
178224 } ;
179225
226+ #evaluateSuggestionsLayout = ( ) : void => {
227+ const suggestionsScrollContainer = this . #suggestionsScrollContainerRef. value ;
228+ const leftScrollButtonContainer = this . #suggestionsLeftScrollButtonContainerRef. value ;
229+ const rightScrollButtonContainer = this . #suggestionsRightScrollButtonContainerRef. value ;
230+ if ( ! suggestionsScrollContainer || ! leftScrollButtonContainer || ! rightScrollButtonContainer ) {
231+ return ;
232+ }
233+
234+ const shouldShowLeftButton = suggestionsScrollContainer . scrollLeft > SCROLL_ROUNDING_OFFSET ;
235+ const shouldShowRightButton =
236+ suggestionsScrollContainer . scrollLeft + suggestionsScrollContainer . offsetWidth + SCROLL_ROUNDING_OFFSET <
237+ suggestionsScrollContainer . scrollWidth ;
238+ leftScrollButtonContainer . classList . toggle ( 'hidden' , ! shouldShowLeftButton ) ;
239+ rightScrollButtonContainer . classList . toggle ( 'hidden' , ! shouldShowRightButton ) ;
240+ } ;
241+
180242 #renderFeedbackForm( ) : LitHtml . LitTemplate {
181243 // clang-format off
182244 return html `
@@ -232,6 +294,54 @@ export class UserActionRow extends HTMLElement {
232294 // clang-format on
233295 }
234296
297+ #renderSuggestions( ) : LitHtml . LitTemplate {
298+ // clang-format off
299+ if ( ! this . #props. suggestions ) {
300+ return LitHtml . nothing ;
301+ }
302+
303+ return html `< div class ="suggestions-container ">
304+ < div class ="scroll-button-container left hidden " ${ LitHtml . Directives . ref ( this . #suggestionsLeftScrollButtonContainerRef) } >
305+ < devtools-button
306+ class ='scroll-button '
307+ .data =${ {
308+ variant : Buttons . Button . Variant . ICON ,
309+ size : Buttons . Button . Size . SMALL ,
310+ iconName : 'chevron-left' ,
311+ title : lockedString ( UIStringsNotTranslate . scrollToPrevious ) ,
312+ jslogContext : 'chevron-left' ,
313+ } as Buttons . Button . ButtonData }
314+ @click =${ ( ) => this . #scrollSuggestionsScrollContainer( 'left' ) }
315+ > </ devtools-button >
316+ </ div >
317+ < div class ="suggestions-scroll-container " @scroll =${ this . #handleSuggestionsScrollOrResize} ${ LitHtml . Directives . ref ( this . #suggestionsScrollContainerRef) } >
318+ ${ this . #props. suggestions ?. map ( suggestion => html `< devtools-button
319+ class ='suggestion '
320+ .data =${ {
321+ variant : Buttons . Button . Variant . OUTLINED ,
322+ title : suggestion ,
323+ jslogContext : 'suggestion' ,
324+ } as Buttons . Button . ButtonData }
325+ @click =${ ( ) => this . #props. handleSuggestionClick ( suggestion ) }
326+ > ${ suggestion } </ devtools-button > ` ) }
327+ </ div >
328+ < div class ="scroll-button-container right hidden " ${ LitHtml . Directives . ref ( this . #suggestionsRightScrollButtonContainerRef) } >
329+ < devtools-button
330+ class ='scroll-button '
331+ .data =${ {
332+ variant : Buttons . Button . Variant . ICON ,
333+ size : Buttons . Button . Size . SMALL ,
334+ iconName : 'chevron-right' ,
335+ title : lockedString ( UIStringsNotTranslate . scrollToNext ) ,
336+ jslogContext : 'chevron-right' ,
337+ } as Buttons . Button . ButtonData }
338+ @click =${ ( ) => this . #scrollSuggestionsScrollContainer( 'right' ) }
339+ > </ devtools-button >
340+ </ div >
341+ </ div > ` ;
342+ // clang-format on
343+ }
344+
235345 #render( ) : void {
236346 // clang-format off
237347 LitHtml . render (
@@ -240,17 +350,7 @@ export class UserActionRow extends HTMLElement {
240350 < div class ="rate-buttons ">
241351 ${ this . #props. showRateButtons ? this . #renderButtons( ) : LitHtml . nothing }
242352 </ div >
243- ${ this . #props. suggestions ?
244- html `< div class ="suggestions ">
245- ${ this . #props. suggestions ?. map ( suggestion => html `< devtools-button
246- .data =${ {
247- variant : Buttons . Button . Variant . OUTLINED ,
248- title : suggestion ,
249- jslogContext : 'suggestion' ,
250- } as Buttons . Button . ButtonData }
251- @click =${ ( ) => this . #props. handleSuggestionClick ( suggestion ) }
252- > ${ suggestion } </ devtools-button > ` ) }
253- </ div > ` : LitHtml . nothing }
353+ ${ this . #props. suggestions ? this . #renderSuggestions( ) : LitHtml . nothing }
254354 </ div >
255355 ${ this . #isShowingFeedbackForm
256356 ? this . #renderFeedbackForm( )
0 commit comments