@@ -11,22 +11,25 @@ import {
1111 offset as offsetMiddleware ,
1212 shift as shiftMiddleware ,
1313 flip as flipMiddleware ,
14+ arrow as arrowMiddleware
1415} from '@floating-ui/dom' ;
1516
1617type Lazy < T > = T | ( ( ) => T | null | undefined ) ;
1718
1819interface FloatingDOMControllerOptions {
1920 content : Lazy < HTMLElement > ;
2021 invoker ?: Lazy < HTMLElement > ;
21- arrow ?: boolean ;
22- flip ?: boolean ;
22+ arrow ?: Lazy < HTMLElement > ;
2323 shift ?: boolean ;
2424 padding ?: number ;
25+ fallbackPlacements ?: Placement [ ] ;
2526}
2627
2728interface ShowOptions {
2829 offset ?: Offset ;
2930 placement ?: Placement ;
31+ flip ?: boolean ;
32+ fallbackPlacements ?: Placement [ ] ;
3033}
3134
3235export type Anchor = '' | 'top' | 'left' | 'bottom' | 'right' ;
@@ -55,6 +58,11 @@ export class FloatingDOMController implements ReactiveController {
5558 return typeof content === 'function' ? content ( ) : content ;
5659 }
5760
61+ get #arrow( ) {
62+ const { arrow } = this . #options;
63+ return typeof arrow === 'function' ? arrow ( ) : arrow ;
64+ }
65+
5866 /** The crosswise alignment of the invoker on which to display the floating DOM */
5967 get alignment ( ) {
6068 return this . #alignment ?? 'center' ;
@@ -93,33 +101,52 @@ export class FloatingDOMController implements ReactiveController {
93101 host . addController ( this ) ;
94102 this . #options = options as Required < FloatingDOMControllerOptions > ;
95103 this . #options. invoker ??= host ;
96- this . #options. arrow ??= false ;
97- this . #options. flip ??= true ;
98104 this . #options. shift ??= true ;
99105 }
100106
101107 hostDisconnected ( ) {
102108 this . #cleanup?.( ) ;
103109 }
104110
105- async #update( placement : Placement = 'top' , offset ?: Offset ) {
106- const { flip , padding, shift } = this . #options;
111+ async #update( placement : Placement = 'top' , offset ?: Offset , flip = true , fallbackPlacements ?: Placement [ ] ) {
112+ const { padding, shift } = this . #options;
107113
108114 const invoker = this . #invoker;
109115 const content = this . #content;
116+ const arrow = this . #arrow;
110117 if ( ! invoker || ! content ) {
111118 return ;
112119 }
113- const { x, y, placement : _placement } = await computePosition ( invoker , content , {
120+ const { x, y, placement : _placement , middlewareData } = await computePosition ( invoker , content , {
114121 strategy : 'absolute' ,
115122 placement,
116123 middleware : [
117124 offsetMiddleware ( offset ) ,
118125 shift && shiftMiddleware ( { padding } ) ,
119- flip && flipMiddleware ( { padding } ) ,
126+ arrow && arrowMiddleware ( { element : arrow , padding : arrow . offsetHeight / 2 } ) ,
127+ flip && flipMiddleware ( { padding, fallbackPlacements } ) ,
120128 ] . filter ( Boolean )
121129 } ) ;
122130
131+ if ( arrow ) {
132+ const { x : arrowX , y : arrowY } = middlewareData . arrow || { } ;
133+
134+ const staticSide = {
135+ top : 'bottom' ,
136+ right : 'left' ,
137+ bottom : 'top' ,
138+ left : 'right' ,
139+ } [ _placement . split ( '-' ) [ 0 ] ] || '' ;
140+
141+ Object . assign ( arrow . style , {
142+ left : arrowX != null ? `${ arrowX } px` : '' ,
143+ top : arrowY != null && ! [ 'top' ] . includes ( _placement ) ? `${ arrowY } px` : '' ,
144+ right : '' ,
145+ bottom : '' ,
146+ [ staticSide ] : `-${ arrow . offsetHeight / 2 } px` ,
147+ } ) ;
148+ }
149+
123150 this . #placement = _placement ;
124151 [ this . #anchor, this . #alignment] = ( this . #placement. split ( '-' ) ?? [ ] ) as [ Anchor , Alignment ] ;
125152 this . #styles = {
@@ -129,17 +156,17 @@ export class FloatingDOMController implements ReactiveController {
129156 }
130157
131158 /** Show the floating DOM */
132- async show ( { offset, placement } : ShowOptions = { } ) {
159+ async show ( { offset, placement, flip , fallbackPlacements } : ShowOptions = { } ) {
133160 const invoker = this . #invoker;
134161 const content = this . #content;
135162 if ( ! invoker || ! content ) {
136163 return ;
137164 }
138165 if ( ! this . #opening) {
139166 this . #opening = true ;
140- const p = this . #update( placement , offset ) ;
167+ const p = this . #update( placement , offset , flip , fallbackPlacements ) ;
141168 this . #cleanup ??= autoUpdate ( invoker , content , ( ) =>
142- this . #update( placement , offset ) ) ;
169+ this . #update( placement , offset , flip , fallbackPlacements ) ) ;
143170 await p ;
144171 this . #opening = false ;
145172 }
0 commit comments