@@ -11,6 +11,7 @@ import type { ReactiveController } from './types.js';
1111
1212class PopoverTriggerControllerTestElement extends HTMLElement {
1313disabled = false ;
14+ interestForElement : HTMLElement | null = null ;
1415popoverTargetAction ?: PopoverTargetAction ;
1516popoverTargetElement : HTMLElement | null = null ;
1617popovertarget ?: string ;
@@ -64,6 +65,102 @@ describe('PopoverTriggerController', () => {
6465expect ( togglePopover ) . toHaveBeenCalledWith ( { source : element } ) ;
6566} ) ;
6667
68+ it ( 'should not invoke a popover from a canceled click' , async ( ) => {
69+ fixture = await createFixture ( html `
70+ < popover-trigger-controller-test-element > </ popover-trigger-controller-test-element >
71+ < div id ="popover " popover > popover</ div >
72+ ` ) ;
73+ const element = fixture . querySelector < PopoverTriggerControllerTestElement > (
74+ 'popover-trigger-controller-test-element'
75+ ) ! ;
76+ const popover = fixture . querySelector < HTMLElement > ( '[popover]' ) ! ;
77+ const togglePopover = vi . spyOn ( popover , 'togglePopover' ) . mockImplementation ( ( ) => false ) ;
78+ fixture . addEventListener ( 'click' , event => event . preventDefault ( ) , { capture : true } ) ;
79+ element . popoverTargetElement = popover ;
80+
81+ element . dispatchEvent ( new MouseEvent ( 'click' , { bubbles : true , cancelable : true } ) ) ;
82+
83+ expect ( togglePopover ) . not . toHaveBeenCalled ( ) ;
84+ } ) ;
85+
86+ it ( 'should follow a changed popover target id' , async ( ) => {
87+ fixture = await createFixture ( html `
88+ < popover-trigger-controller-test-element > </ popover-trigger-controller-test-element >
89+ < div id ="tooltip "> </ div >
90+ < div id ="first " popover > first</ div >
91+ < div id ="second " popover > second</ div >
92+ ` ) ;
93+ const element = fixture . querySelector < PopoverTriggerControllerTestElement > (
94+ 'popover-trigger-controller-test-element'
95+ ) ! ;
96+ const tooltip = fixture . querySelector < HTMLElement > ( '#tooltip' ) ! ;
97+ const first = fixture . querySelector < HTMLElement > ( '#first' ) ! ;
98+ const second = fixture . querySelector < HTMLElement > ( '#second' ) ! ;
99+ const loseInterest = vi . fn ( ) ;
100+ tooltip . addEventListener ( 'loseinterest' , loseInterest ) ;
101+
102+ element . interestForElement = tooltip ;
103+ element . popoverTargetAction = 'show' ;
104+ element . popovertarget = 'first' ;
105+ await emulateClick ( element ) ;
106+ first . hidePopover ( ) ;
107+
108+ loseInterest . mockClear ( ) ;
109+ element . popovertarget = 'second' ;
110+ await emulateClick ( element ) ;
111+
112+ expect ( element . popoverTargetElement ) . toBe ( second ) ;
113+ expect ( first . matches ( ':popover-open' ) ) . toBe ( false ) ;
114+ expect ( second . matches ( ':popover-open' ) ) . toBe ( true ) ;
115+ expect ( loseInterest ) . toHaveBeenCalledOnce ( ) ;
116+ } ) ;
117+
118+ it ( 'should preserve tooltip interest when opening is canceled' , async ( ) => {
119+ fixture = await createFixture ( html `
120+ < popover-trigger-controller-test-element > </ popover-trigger-controller-test-element >
121+ < div id ="tooltip "> </ div >
122+ < div id ="popover " popover > popover</ div >
123+ ` ) ;
124+ const element = fixture . querySelector < PopoverTriggerControllerTestElement > (
125+ 'popover-trigger-controller-test-element'
126+ ) ! ;
127+ const tooltip = fixture . querySelector < HTMLElement > ( '#tooltip' ) ! ;
128+ const popover = fixture . querySelector < HTMLElement > ( '[popover]' ) ! ;
129+ const loseInterest = vi . fn ( ) ;
130+ tooltip . addEventListener ( 'loseinterest' , loseInterest ) ;
131+ popover . addEventListener ( 'beforetoggle' , event => event . preventDefault ( ) ) ;
132+ element . interestForElement = tooltip ;
133+ element . popoverTargetElement = popover ;
134+
135+ await emulateClick ( element ) ;
136+
137+ expect ( popover . matches ( ':popover-open' ) ) . toBe ( false ) ;
138+ expect ( loseInterest ) . not . toHaveBeenCalled ( ) ;
139+ } ) ;
140+
141+ it ( 'should preserve tooltip interest when toggling a popover closed' , async ( ) => {
142+ fixture = await createFixture ( html `
143+ < popover-trigger-controller-test-element > </ popover-trigger-controller-test-element >
144+ < div id ="tooltip "> </ div >
145+ < div id ="popover " popover > popover</ div >
146+ ` ) ;
147+ const element = fixture . querySelector < PopoverTriggerControllerTestElement > (
148+ 'popover-trigger-controller-test-element'
149+ ) ! ;
150+ const tooltip = fixture . querySelector < HTMLElement > ( '#tooltip' ) ! ;
151+ const popover = fixture . querySelector < HTMLElement > ( '[popover]' ) ! ;
152+ const loseInterest = vi . fn ( ) ;
153+ tooltip . addEventListener ( 'loseinterest' , loseInterest ) ;
154+ element . interestForElement = tooltip ;
155+ element . popoverTargetElement = popover ;
156+ popover . showPopover ( ) ;
157+
158+ await emulateClick ( element ) ;
159+
160+ expect ( popover . matches ( ':popover-open' ) ) . toBe ( false ) ;
161+ expect ( loseInterest ) . not . toHaveBeenCalled ( ) ;
162+ } ) ;
163+
67164it ( 'should support direct popover target properties and show or hide actions' , async ( ) => {
68165fixture = await createFixture ( html `
69166< popover-trigger-controller-test-element > </ popover-trigger-controller-test-element >
@@ -107,7 +204,7 @@ describe('PopoverTriggerController', () => {
107204expect ( togglePopover ) . not . toHaveBeenCalled ( ) ;
108205} ) ;
109206
110- it ( 'should pass anchored popover sources ' , async ( ) => {
207+ it ( 'should pass the anchor as the popover source when the target has an anchor ' , async ( ) => {
111208fixture = await createFixture ( html `
112209< popover-trigger-controller-test-element > </ popover-trigger-controller-test-element >
113210< div id ="anchor "> </ div >
@@ -128,6 +225,39 @@ describe('PopoverTriggerController', () => {
128225expect ( showPopover ) . toHaveBeenCalledWith ( { source : anchor } ) ;
129226} ) ;
130227
228+ it ( 'should hand over tooltip interest after an anchored target popover opens from the trigger' , async ( ) => {
229+ fixture = await createFixture ( html `
230+ < popover-trigger-controller-test-element interestfor ="tooltip " popovertarget ="popover "> </ popover-trigger-controller-test-element >
231+ < div id ="tooltip "> </ div >
232+ < div id ="anchor "> </ div >
233+ < div id ="popover " popover > popover</ div >
234+ ` ) ;
235+ const element = fixture . querySelector < PopoverTriggerControllerTestElement > (
236+ 'popover-trigger-controller-test-element'
237+ ) ! ;
238+ const tooltip = fixture . querySelector < HTMLElement > ( '#tooltip' ) ! ;
239+ const anchor = fixture . querySelector < HTMLElement > ( '#anchor' ) ! ;
240+ const popover = fixture . querySelector < HTMLElement > ( '[popover]' ) ! ;
241+ let interestSource : HTMLElement | undefined ;
242+ let popoverSource : HTMLElement | null | undefined ;
243+ tooltip . addEventListener ( 'loseinterest' , event => {
244+ interestSource = ( event as Event & { source : HTMLElement } ) . source ;
245+ } ) ;
246+ popover . addEventListener ( 'beforetoggle' , event => {
247+ popoverSource = ( event as ToggleEvent ) . source ;
248+ } ) ;
249+
250+ Object . defineProperty ( popover , 'anchor' , { configurable : true , value : 'anchor' } ) ;
251+ element . interestForElement = tooltip ;
252+ element . popovertarget = 'popover' ;
253+ await emulateClick ( element ) ;
254+
255+ expect ( element . popoverTargetElement ) . toBe ( popover ) ;
256+ expect ( popover . matches ( ':popover-open' ) ) . toBe ( true ) ;
257+ expect ( popoverSource ) . toBe ( anchor ) ;
258+ expect ( interestSource ) . toBe ( element ) ;
259+ } ) ;
260+
131261it ( 'should remove click behavior on disconnect' , async ( ) => {
132262fixture = await createFixture ( html `
133263< popover-trigger-controller-test-element > </ popover-trigger-controller-test-element >
0 commit comments