11import {
22 type Middleware ,
3+ type MiddlewareData ,
4+ type Placement ,
5+ arrow ,
36 autoUpdate ,
47 computePosition ,
58 flip ,
9+ inline ,
610 limitShift ,
711 offset ,
812 shift ,
@@ -14,6 +18,7 @@ import { property, query, queryAssignedElements } from 'lit/decorators.js';
1418import { watch } from '../common/decorators/watch.js' ;
1519import { registerComponent } from '../common/definitions/register.js' ;
1620import {
21+ first ,
1722 getElementByIdFromRoot ,
1823 isEmpty ,
1924 isString ,
@@ -59,7 +64,7 @@ export default class IgcPopoverComponent extends LitElement {
5964 private dispose ?: ReturnType < typeof autoUpdate > ;
6065 private target ?: Element ;
6166
62- @query ( '#container' , true )
67+ @query ( '#container' )
6368 private _container ! : HTMLElement ;
6469
6570 @queryAssignedElements ( { slot : 'anchor' , flatten : true } )
@@ -72,6 +77,23 @@ export default class IgcPopoverComponent extends LitElement {
7277 @property ( )
7378 public anchor ?: Element | string ;
7479
80+ /**
81+ * Element to render as an "arrow" element for the current popover.
82+ */
83+ @property ( { attribute : false } )
84+ public arrow : HTMLElement | null = null ;
85+
86+ /** Additional offset to apply to the arrow element if enabled. */
87+ @property ( { type : Number , attribute : 'arrow-offset' } )
88+ public arrowOffset = 0 ;
89+
90+ /**
91+ * Improves positioning for inline reference elements that span over multiple lines.
92+ * Useful for tooltips or similar components.
93+ */
94+ @property ( { type : Boolean , reflect : true } )
95+ public inline = false ;
96+
7597 /**
7698 * When enabled this changes the placement of the floating element in order to keep it
7799 * in view along the main axis.
@@ -110,8 +132,14 @@ export default class IgcPopoverComponent extends LitElement {
110132 @property ( { type : Boolean , reflect : true } )
111133 public shift = false ;
112134
135+ /**
136+ * Virtual padding for the resolved overflow detection offsets in pixels.
137+ */
138+ @property ( { type : Number , attribute : 'shift-padding' } )
139+ public shiftPadding = 0 ;
140+
113141 @watch ( 'anchor' )
114- protected async anchorChange ( ) {
142+ protected anchorChange ( ) {
115143 const newTarget = isString ( this . anchor )
116144 ? getElementByIdFromRoot ( this , this . anchor )
117145 : this . anchor ;
@@ -127,11 +155,15 @@ export default class IgcPopoverComponent extends LitElement {
127155 this . open ? this . show ( ) : this . hide ( ) ;
128156 }
129157
158+ @watch ( 'arrow' , { waitUntilFirstUpdate : true } )
159+ @watch ( 'arrowOffset' , { waitUntilFirstUpdate : true } )
130160 @watch ( 'flip' , { waitUntilFirstUpdate : true } )
161+ @watch ( 'inline' , { waitUntilFirstUpdate : true } )
131162 @watch ( 'offset' , { waitUntilFirstUpdate : true } )
132163 @watch ( 'placement' , { waitUntilFirstUpdate : true } )
133164 @watch ( 'sameWidth' , { waitUntilFirstUpdate : true } )
134165 @watch ( 'shift' , { waitUntilFirstUpdate : true } )
166+ @watch ( 'shiftPadding' , { waitUntilFirstUpdate : true } )
135167 protected floatingPropChange ( ) {
136168 this . _updateState ( ) ;
137169 }
@@ -151,7 +183,10 @@ export default class IgcPopoverComponent extends LitElement {
151183 }
152184
153185 protected show ( ) {
154- if ( ! this . target ) return ;
186+ if ( ! this . target ) {
187+ return ;
188+ }
189+
155190 this . _showPopover ( ) ;
156191
157192 this . dispose = autoUpdate (
@@ -187,14 +222,23 @@ export default class IgcPopoverComponent extends LitElement {
187222 middleware . push ( offset ( this . offset ) ) ;
188223 }
189224
225+ if ( this . inline ) {
226+ middleware . push ( inline ( ) ) ;
227+ }
228+
190229 if ( this . shift ) {
191230 middleware . push (
192231 shift ( {
232+ padding : this . shiftPadding ,
193233 limiter : limitShift ( ) ,
194234 } )
195235 ) ;
196236 }
197237
238+ if ( this . arrow ) {
239+ middleware . push ( arrow ( { element : this . arrow } ) ) ;
240+ }
241+
198242 if ( this . flip ) {
199243 middleware . push ( flip ( ) ) ;
200244 }
@@ -222,25 +266,60 @@ export default class IgcPopoverComponent extends LitElement {
222266 }
223267
224268 private async _updatePosition ( ) {
225- if ( ! this . open || ! this . target ) {
269+ if ( ! ( this . open && this . target ) ) {
226270 return ;
227271 }
228272
229- const { x, y } = await computePosition ( this . target , this . _container , {
230- placement : this . placement ?? 'bottom-start' ,
231- middleware : this . _createMiddleware ( ) ,
232- strategy : 'fixed' ,
233- } ) ;
273+ const { x, y, middlewareData, placement } = await computePosition (
274+ this . target ,
275+ this . _container ,
276+ {
277+ placement : this . placement ?? 'bottom-start' ,
278+ middleware : this . _createMiddleware ( ) ,
279+ strategy : 'fixed' ,
280+ }
281+ ) ;
234282
235283 Object . assign ( this . _container . style , {
236284 left : 0 ,
237285 top : 0 ,
238286 transform : `translate(${ roundByDPR ( x ) } px,${ roundByDPR ( y ) } px)` ,
239287 } ) ;
288+
289+ this . _positionArrow ( placement , middlewareData ) ;
290+ }
291+
292+ private _positionArrow ( placement : Placement , data : MiddlewareData ) {
293+ if ( ! ( data . arrow && this . arrow ) ) {
294+ return ;
295+ }
296+
297+ const { x, y } = data . arrow ;
298+
299+ // The current placement of the popover along the x/y axis
300+ const currentPlacement = first ( placement . split ( '-' ) ) ;
301+
302+ // The opposite side where the arrow element should render based on the `currentPlacement`
303+ const staticSide = {
304+ top : 'bottom' ,
305+ right : 'left' ,
306+ bottom : 'top' ,
307+ left : 'right' ,
308+ } [ currentPlacement ] ! ;
309+
310+ this . arrow . part = currentPlacement ;
311+
312+ Object . assign ( this . arrow . style , {
313+ left : x != null ? `${ roundByDPR ( x + this . arrowOffset ) } px` : '' ,
314+ top : y != null ? `${ roundByDPR ( y + this . arrowOffset ) } px` : '' ,
315+ [ staticSide ] : '-4px' ,
316+ } ) ;
240317 }
241318
242319 private _anchorSlotChange ( ) {
243- if ( this . anchor || isEmpty ( this . _anchors ) ) return ;
320+ if ( this . anchor || isEmpty ( this . _anchors ) ) {
321+ return ;
322+ }
244323
245324 this . target = this . _anchors [ 0 ] ;
246325 this . _updateState ( ) ;
0 commit comments