@@ -32,6 +32,12 @@ export interface GridCellWidgetInputs {
3232
3333 /** The element that will receive focus when the widget is activated. */
3434 focusTarget : SignalLike < ElementResolver < HTMLElement > > ;
35+
36+ /** Callback hook used to notify parents or directives upon interaction. */
37+ onActivate ?: ( event : KeyboardEvent | FocusEvent | undefined ) => void ;
38+
39+ /** Callback hook used to notify parents or directives upon exit. */
40+ onDeactivate ?: ( event : KeyboardEvent | FocusEvent | undefined ) => void ;
3541}
3642
3743/** The UI pattern for a widget inside a grid cell. */
@@ -49,7 +55,12 @@ export class GridCellWidgetPattern {
4955 ) ;
5056
5157 /** The tab index for the widget. */
52- readonly tabIndex : SignalLike < - 1 | 0 > = computed ( ( ) => this . inputs . cell ( ) . widgetTabIndex ( ) ) ;
58+ readonly tabIndex : SignalLike < - 1 | 0 > = computed ( ( ) => {
59+ if ( this . inputs . focusTarget ( ) ) {
60+ return - 1 ;
61+ }
62+ return this . inputs . cell ( ) . widgetTabIndex ( ) ;
63+ } ) ;
5364
5465 /** Whether the widget is the active widget in the cell. */
5566 readonly active : SignalLike < boolean > = computed (
@@ -71,18 +82,25 @@ export class GridCellWidgetPattern {
7182 readonly keydown = computed ( ( ) => {
7283 const manager = new KeyboardEventManager ( ) ;
7384
85+ // Simple widgets emit notification on interaction without capturing event flow
86+ if ( this . inputs . widgetType ( ) === 'simple' ) {
87+ return manager
88+ . on ( 'Enter' , e => this . inputs . onActivate ?.( e ) , {
89+ preventDefault : false ,
90+ stopPropagation : false ,
91+ } )
92+ . on ( ' ' , e => this . inputs . onActivate ?.( e ) , {
93+ preventDefault : false ,
94+ stopPropagation : false ,
95+ } ) ;
96+ }
97+
7498 // If a widget is activated, only listen to events that exits activate state.
7599 if ( this . isActivated ( ) ) {
76- manager . on ( 'Escape' , e => {
77- this . deactivate ( e ) ;
78- this . focus ( ) ;
79- } ) ;
100+ manager . on ( 'Escape' , e => this . deactivate ( e ) ) ;
80101
81102 if ( this . inputs . widgetType ( ) === 'editable' ) {
82- manager . on ( 'Enter' , e => {
83- this . deactivate ( e ) ;
84- this . focus ( ) ;
85- } ) ;
103+ manager . on ( 'Enter' , e => this . deactivate ( e ) ) ;
86104 }
87105
88106 return manager ;
@@ -135,6 +153,32 @@ export class GridCellWidgetPattern {
135153 this . widgetHost ( ) . focus ( ) ;
136154 }
137155
156+ /** Side-effect executed whenever the widget activates. Runs in the write phase. */
157+ activationEffect ( ) : void {
158+ if ( this . isActivated ( ) ) {
159+ const event = this . lastActivateEvent ( ) ;
160+ this . inputs . onActivate ?.( event ) ;
161+
162+ // Only automatically redirect focus if explicit configuration was supplied.
163+ if ( this . inputs . focusTarget ( ) ) {
164+ this . focus ( ) ;
165+ }
166+ }
167+ }
168+
169+ /** Side-effect executed whenever the widget deactivates. Runs in the write phase. */
170+ deactivationEffect ( ) : void {
171+ const event = this . lastDeactivateEvent ( ) ;
172+ if ( event ) {
173+ this . inputs . onDeactivate ?.( event ) ;
174+
175+ // Only automatically restore focus if the deactivation was triggered by user keyboard interaction.
176+ if ( event instanceof KeyboardEvent ) {
177+ this . focus ( ) ;
178+ }
179+ }
180+ }
181+
138182 /** Activates the widget. */
139183 activate ( event ?: KeyboardEvent | FocusEvent ) : void {
140184 if ( this . isActivated ( ) ) return ;
0 commit comments