1
1
import {
2
2
type Middleware ,
3
+ type MiddlewareData ,
4
+ type Placement ,
5
+ arrow ,
3
6
autoUpdate ,
4
7
computePosition ,
5
8
flip ,
9
+ inline ,
6
10
limitShift ,
7
11
offset ,
8
12
shift ,
@@ -14,6 +18,7 @@ import { property, query, queryAssignedElements } from 'lit/decorators.js';
14
18
import { watch } from '../common/decorators/watch.js' ;
15
19
import { registerComponent } from '../common/definitions/register.js' ;
16
20
import {
21
+ first ,
17
22
getElementByIdFromRoot ,
18
23
isEmpty ,
19
24
isString ,
@@ -59,7 +64,7 @@ export default class IgcPopoverComponent extends LitElement {
59
64
private dispose ?: ReturnType < typeof autoUpdate > ;
60
65
private target ?: Element ;
61
66
62
- @query ( '#container' , true )
67
+ @query ( '#container' )
63
68
private _container ! : HTMLElement ;
64
69
65
70
@queryAssignedElements ( { slot : 'anchor' , flatten : true } )
@@ -72,6 +77,23 @@ export default class IgcPopoverComponent extends LitElement {
72
77
@property ( )
73
78
public anchor ?: Element | string ;
74
79
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
+
75
97
/**
76
98
* When enabled this changes the placement of the floating element in order to keep it
77
99
* in view along the main axis.
@@ -110,8 +132,14 @@ export default class IgcPopoverComponent extends LitElement {
110
132
@property ( { type : Boolean , reflect : true } )
111
133
public shift = false ;
112
134
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
+
113
141
@watch ( 'anchor' )
114
- protected async anchorChange ( ) {
142
+ protected anchorChange ( ) {
115
143
const newTarget = isString ( this . anchor )
116
144
? getElementByIdFromRoot ( this , this . anchor )
117
145
: this . anchor ;
@@ -127,11 +155,15 @@ export default class IgcPopoverComponent extends LitElement {
127
155
this . open ? this . show ( ) : this . hide ( ) ;
128
156
}
129
157
158
+ @watch ( 'arrow' , { waitUntilFirstUpdate : true } )
159
+ @watch ( 'arrowOffset' , { waitUntilFirstUpdate : true } )
130
160
@watch ( 'flip' , { waitUntilFirstUpdate : true } )
161
+ @watch ( 'inline' , { waitUntilFirstUpdate : true } )
131
162
@watch ( 'offset' , { waitUntilFirstUpdate : true } )
132
163
@watch ( 'placement' , { waitUntilFirstUpdate : true } )
133
164
@watch ( 'sameWidth' , { waitUntilFirstUpdate : true } )
134
165
@watch ( 'shift' , { waitUntilFirstUpdate : true } )
166
+ @watch ( 'shiftPadding' , { waitUntilFirstUpdate : true } )
135
167
protected floatingPropChange ( ) {
136
168
this . _updateState ( ) ;
137
169
}
@@ -151,7 +183,10 @@ export default class IgcPopoverComponent extends LitElement {
151
183
}
152
184
153
185
protected show ( ) {
154
- if ( ! this . target ) return ;
186
+ if ( ! this . target ) {
187
+ return ;
188
+ }
189
+
155
190
this . _showPopover ( ) ;
156
191
157
192
this . dispose = autoUpdate (
@@ -187,14 +222,23 @@ export default class IgcPopoverComponent extends LitElement {
187
222
middleware . push ( offset ( this . offset ) ) ;
188
223
}
189
224
225
+ if ( this . inline ) {
226
+ middleware . push ( inline ( ) ) ;
227
+ }
228
+
190
229
if ( this . shift ) {
191
230
middleware . push (
192
231
shift ( {
232
+ padding : this . shiftPadding ,
193
233
limiter : limitShift ( ) ,
194
234
} )
195
235
) ;
196
236
}
197
237
238
+ if ( this . arrow ) {
239
+ middleware . push ( arrow ( { element : this . arrow } ) ) ;
240
+ }
241
+
198
242
if ( this . flip ) {
199
243
middleware . push ( flip ( ) ) ;
200
244
}
@@ -222,25 +266,60 @@ export default class IgcPopoverComponent extends LitElement {
222
266
}
223
267
224
268
private async _updatePosition ( ) {
225
- if ( ! this . open || ! this . target ) {
269
+ if ( ! ( this . open && this . target ) ) {
226
270
return ;
227
271
}
228
272
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
+ ) ;
234
282
235
283
Object . assign ( this . _container . style , {
236
284
left : 0 ,
237
285
top : 0 ,
238
286
transform : `translate(${ roundByDPR ( x ) } px,${ roundByDPR ( y ) } px)` ,
239
287
} ) ;
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
+ } ) ;
240
317
}
241
318
242
319
private _anchorSlotChange ( ) {
243
- if ( this . anchor || isEmpty ( this . _anchors ) ) return ;
320
+ if ( this . anchor || isEmpty ( this . _anchors ) ) {
321
+ return ;
322
+ }
244
323
245
324
this . target = this . _anchors [ 0 ] ;
246
325
this . _updateState ( ) ;
0 commit comments