@@ -18,6 +18,7 @@ export class IgxGridBodyDirective { }
1818 */
1919export interface RowEditPositionSettings extends PositionSettings {
2020 container ?: HTMLElement ;
21+ clipToVisibleArea ?: boolean ;
2122}
2223
2324/**
@@ -63,6 +64,73 @@ export class RowEditPositionStrategy extends ConnectedPositioningStrategy {
6364
6465 super . position ( contentElement , { width : targetElement . clientWidth , height : targetElement . clientHeight } ,
6566 document , initialCall , targetElement ) ;
67+
68+ if ( this . settings . clipToVisibleArea ) {
69+ // After positioning in the top layer, keep the overlay clipped to the visible grid body.
70+ this . updateContentClip ( contentElement ) ;
71+ }
72+ }
73+
74+ private updateContentClip ( contentElement : HTMLElement ) : void {
75+ const container = this . settings . container ;
76+
77+ if ( ! container ) {
78+ return ;
79+ }
80+
81+ const clippingRect = this . getClippingRect ( container ) ;
82+ const contentRect = contentElement . getBoundingClientRect ( ) ;
83+
84+ // Convert the clipped overflow on each side to CSS inset values.
85+ const top = Math . round ( Math . max ( clippingRect . top - contentRect . top , 0 ) ) ;
86+ const right = Math . round ( Math . max ( contentRect . right - clippingRect . right , 0 ) ) ;
87+ const bottom = Math . round ( Math . max ( contentRect . bottom - clippingRect . bottom , 0 ) ) ;
88+ const left = Math . round ( Math . max ( clippingRect . left - contentRect . left , 0 ) ) ;
89+
90+ // When the overlay is fully outside the clipping rect, hide it and block its action buttons.
91+ const fullyClipped = top >= contentRect . height || bottom >= contentRect . height ||
92+ left >= contentRect . width || right >= contentRect . width ;
93+
94+ // Row-edit overlays are rendered in the top layer, so clip the content explicitly to the grid's visible area.
95+ contentElement . style . clipPath = fullyClipped ? 'inset(100%)' :
96+ ( top || right || bottom || left ? `inset(${ top } px ${ right } px ${ bottom } px ${ left } px)` : '' ) ;
97+
98+ contentElement . style . pointerEvents = fullyClipped ? 'none' : '' ;
99+ contentElement . style . visibility = fullyClipped ? 'hidden' : '' ;
100+ }
101+
102+ private getClippingRect ( element : HTMLElement ) : Pick < DOMRect , 'top' | 'right' | 'bottom' | 'left' > {
103+ const document = element . ownerDocument ;
104+ const gridBody = element . closest ( '[igxgridbody]' ) as HTMLElement || element ;
105+ const rect = gridBody . getBoundingClientRect ( ) ;
106+ // Start with the current grid body, then narrow it by parent grid bodies.
107+ const clippingRect = { top : rect . top , right : rect . right , bottom : rect . bottom , left : rect . left } ;
108+
109+ let parent = gridBody . parentElement ?. closest ( '[igxgridbody]' ) as HTMLElement ;
110+
111+ // Intersect with parent grid bodies so nested grids respect their parent scroll bounds.
112+ while ( parent ) {
113+ const parentRect = parent . getBoundingClientRect ( ) ;
114+
115+ clippingRect . top = Math . max ( clippingRect . top , parentRect . top ) ;
116+ clippingRect . right = Math . min ( clippingRect . right , parentRect . right ) ;
117+ clippingRect . bottom = Math . min ( clippingRect . bottom , parentRect . bottom ) ;
118+ clippingRect . left = Math . max ( clippingRect . left , parentRect . left ) ;
119+
120+ if ( clippingRect . top >= clippingRect . bottom || clippingRect . left >= clippingRect . right ) {
121+ break ;
122+ }
123+
124+ parent = parent . parentElement ?. closest ( '[igxgridbody]' ) as HTMLElement ;
125+ }
126+
127+ // Keep the clipping area inside the viewport because popover content is viewport-positioned.
128+ return {
129+ top : Math . max ( clippingRect . top , 0 ) ,
130+ right : Math . min ( clippingRect . right , document . documentElement . clientWidth ) ,
131+ bottom : Math . min ( clippingRect . bottom , document . documentElement . clientHeight ) ,
132+ left : Math . max ( clippingRect . left , 0 )
133+ } ;
66134 }
67135
68136 /**
0 commit comments