10
10
* governing permissions and limitations under the License.
11
11
*/
12
12
13
- import { ChangeEvent , Key , RefObject , useCallback , useRef } from 'react' ;
13
+ import { ChangeEvent , Key , RefObject , useCallback , useEffect , useRef } from 'react' ;
14
+ import { ColumnSize } from '@react-types/table' ;
14
15
import { DOMAttributes , FocusableElement } from '@react-types/shared' ;
15
16
import { focusSafely } from '@react-aria/focus' ;
16
- import { focusWithoutScrolling , mergeProps , useDescription , useId } from '@react-aria/utils' ;
17
17
import { getColumnHeaderId } from './utils' ;
18
18
import { GridNode } from '@react-types/grid' ;
19
19
// @ts -ignore
20
20
import intlMessages from '../intl/*.json' ;
21
+ import { mergeProps , useDescription , useEffectEvent , useId } from '@react-aria/utils' ;
21
22
import { TableColumnResizeState } from '@react-stately/table' ;
22
23
import { useInteractionModality , useKeyboard , useMove , usePress } from '@react-aria/interactions' ;
23
24
import { useLocale , useLocalizedStringFormatter } from '@react-aria/i18n' ;
@@ -46,84 +47,82 @@ export interface AriaTableColumnResizeProps<T> {
46
47
/** If resizing is disabled. */
47
48
isDisabled ?: boolean ,
48
49
/** Called when resizing starts. */
49
- onResizeStart ?: ( widths : Map < Key , number | string > ) => void ,
50
+ onResizeStart ?: ( widths : Map < Key , ColumnSize > ) => void ,
50
51
/** Called for every resize event that results in new column sizes. */
51
- onResize ?: ( widths : Map < Key , number | string > ) => void ,
52
+ onResize ?: ( widths : Map < Key , ColumnSize > ) => void ,
52
53
/** Called when resizing ends. */
53
- onResizeEnd ?: ( widths : Map < Key , number | string > ) => void
54
+ onResizeEnd ?: ( widths : Map < Key , ColumnSize > ) => void
54
55
}
55
56
56
- export interface AriaTableColumnResizeState < T > extends Omit < TableColumnResizeState < T > , 'widths' > { }
57
-
58
57
/**
59
58
* Provides the behavior and accessibility implementation for a table column resizer element.
60
59
* @param props - Props for the resizer.
61
60
* @param state - State for the table's resizable columns, as returned by `useTableColumnResizeState`.
62
61
* @param ref - The ref attached to the resizer's visually hidden input element.
63
62
*/
64
- export function useTableColumnResize < T > ( props : AriaTableColumnResizeProps < T > , state : AriaTableColumnResizeState < T > , ref : RefObject < HTMLInputElement > ) : TableColumnResizeAria {
63
+ export function useTableColumnResize < T > ( props : AriaTableColumnResizeProps < T > , state : TableColumnResizeState < T > , ref : RefObject < HTMLInputElement > ) : TableColumnResizeAria {
65
64
let { column : item , triggerRef, isDisabled, onResizeStart, onResize, onResizeEnd, 'aria-label' : ariaLabel } = props ;
66
65
const stringFormatter = useLocalizedStringFormatter ( intlMessages ) ;
67
66
let id = useId ( ) ;
68
67
let isResizing = state . resizingColumn === item . key ;
69
68
let isResizingRef = useRef ( isResizing ) ;
70
69
let lastSize = useRef ( null ) ;
70
+ let wasFocusedOnResizeStart = useRef ( false ) ;
71
71
let editModeEnabled = state . tableState . isKeyboardNavigationDisabled ;
72
72
73
73
let { direction} = useLocale ( ) ;
74
74
let { keyboardProps} = useKeyboard ( {
75
75
onKeyDown : ( e ) => {
76
- let resizeOnFocus = ! ! triggerRef ?. current ;
77
76
if ( editModeEnabled ) {
78
77
if ( e . key === 'Escape' || e . key === 'Enter' || e . key === ' ' || e . key === 'Tab' ) {
79
78
e . preventDefault ( ) ;
80
- if ( resizeOnFocus ) {
81
- // switch focus back to the column header on anything that ends edit mode
82
- focusSafely ( triggerRef . current ) ;
83
- } else {
84
- endResize ( item ) ;
85
- state . tableState . setKeyboardNavigationDisabled ( false ) ;
86
- }
79
+ endResize ( item ) ;
87
80
}
88
- } else if ( ! resizeOnFocus ) {
81
+ } else {
89
82
// Continue propagation on keydown events so they still bubbles to useSelectableCollection and are handled there
90
83
e . continuePropagation ( ) ;
91
84
92
85
if ( e . key === 'Enter' ) {
93
86
startResize ( item ) ;
94
- state . tableState . setKeyboardNavigationDisabled ( true ) ;
95
87
}
96
88
}
97
89
}
98
90
} ) ;
99
91
100
- let startResize = useCallback ( ( item ) => {
92
+ let startResize = useEffectEvent ( ( item ) => {
101
93
if ( ! isResizingRef . current ) {
102
94
lastSize . current = state . updateResizedColumns ( item . key , state . getColumnWidth ( item . key ) ) ;
103
95
state . startResize ( item . key ) ;
96
+ state . tableState . setKeyboardNavigationDisabled ( true ) ;
104
97
onResizeStart ?.( lastSize . current ) ;
105
98
}
106
99
isResizingRef . current = true ;
107
- } , [ isResizingRef , onResizeStart , state ] ) ;
100
+ } ) ;
108
101
109
- let resize = useCallback ( ( item , newWidth ) => {
102
+ let resize = useEffectEvent ( ( item , newWidth ) => {
110
103
let sizes = state . updateResizedColumns ( item . key , newWidth ) ;
111
104
onResize ?.( sizes ) ;
112
105
lastSize . current = sizes ;
113
- } , [ onResize , state ] ) ;
106
+ } ) ;
114
107
115
- let endResize = useCallback ( ( item ) => {
108
+ let endResize = useEffectEvent ( ( item ) => {
116
109
if ( isResizingRef . current ) {
117
110
if ( lastSize . current == null ) {
118
111
lastSize . current = state . updateResizedColumns ( item . key , state . getColumnWidth ( item . key ) ) ;
119
112
}
120
113
121
114
state . endResize ( ) ;
115
+ state . tableState . setKeyboardNavigationDisabled ( false ) ;
122
116
onResizeEnd ?.( lastSize . current ) ;
117
+ isResizingRef . current = false ;
118
+
119
+ if ( triggerRef ?. current && ! wasFocusedOnResizeStart . current ) {
120
+ // switch focus back to the column header unless the resizer was already focused when resizing started.
121
+ focusSafely ( triggerRef . current ) ;
122
+ }
123
123
}
124
- isResizingRef . current = false ;
125
124
lastSize . current = null ;
126
- } , [ isResizingRef , onResizeEnd , state ] ) ;
125
+ } ) ;
127
126
128
127
const columnResizeWidthRef = useRef < number > ( 0 ) ;
129
128
const { moveProps} = useMove ( {
@@ -149,10 +148,9 @@ export function useTableColumnResize<T>(props: AriaTableColumnResizeProps<T>, st
149
148
}
150
149
} ,
151
150
onMoveEnd ( e ) {
152
- let resizeOnFocus = ! ! triggerRef ?. current ;
153
151
let { pointerType} = e ;
154
152
columnResizeWidthRef . current = 0 ;
155
- if ( pointerType === 'mouse' || ( pointerType === 'touch' && ! resizeOnFocus ) ) {
153
+ if ( pointerType === 'mouse' || ( pointerType === 'touch' && wasFocusedOnResizeStart . current ) ) {
156
154
endResize ( item ) ;
157
155
}
158
156
}
@@ -191,10 +189,24 @@ export function useTableColumnResize<T>(props: AriaTableColumnResizeProps<T>, st
191
189
192
190
const focusInput = useCallback ( ( ) => {
193
191
if ( ref . current ) {
194
- focusWithoutScrolling ( ref . current ) ;
192
+ focusSafely ( ref . current ) ;
195
193
}
196
194
} , [ ref ] ) ;
197
195
196
+ let resizingColumn = state . resizingColumn ;
197
+ let prevResizingColumn = useRef ( null ) ;
198
+ useEffect ( ( ) => {
199
+ if ( prevResizingColumn . current !== resizingColumn && resizingColumn != null && resizingColumn === item . key ) {
200
+ wasFocusedOnResizeStart . current = document . activeElement === ref . current ;
201
+ startResize ( item ) ;
202
+ focusInput ( ) ;
203
+ // VoiceOver on iOS has problems focusing the input from a menu.
204
+ let timeout = setTimeout ( focusInput , 400 ) ;
205
+ return ( ) => clearTimeout ( timeout ) ;
206
+ }
207
+ prevResizingColumn . current = resizingColumn ;
208
+ } , [ resizingColumn , item , focusInput , ref , startResize ] ) ;
209
+
198
210
let onChange = ( e : ChangeEvent < HTMLInputElement > ) => {
199
211
let currentWidth = state . getColumnWidth ( item . key ) ;
200
212
let nextValue = parseFloat ( e . target . value ) ;
@@ -213,11 +225,7 @@ export function useTableColumnResize<T>(props: AriaTableColumnResizeProps<T>, st
213
225
return ;
214
226
}
215
227
if ( e . pointerType === 'virtual' && state . resizingColumn != null ) {
216
- let resizeOnFocus = ! ! triggerRef ?. current ;
217
228
endResize ( item ) ;
218
- if ( resizeOnFocus ) {
219
- focusSafely ( triggerRef . current ) ;
220
- }
221
229
return ;
222
230
}
223
231
@@ -232,8 +240,7 @@ export function useTableColumnResize<T>(props: AriaTableColumnResizeProps<T>, st
232
240
}
233
241
} ,
234
242
onPress : ( e ) => {
235
- let resizeOnFocus = ! ! triggerRef ?. current ;
236
- if ( ( ( e . pointerType === 'touch' && ! resizeOnFocus ) || e . pointerType === 'mouse' ) && state . resizingColumn != null ) {
243
+ if ( ( ( e . pointerType === 'touch' && wasFocusedOnResizeStart . current ) || e . pointerType === 'mouse' ) && state . resizingColumn != null ) {
237
244
endResize ( item ) ;
238
245
}
239
246
}
@@ -244,24 +251,15 @@ export function useTableColumnResize<T>(props: AriaTableColumnResizeProps<T>, st
244
251
resizerProps : mergeProps (
245
252
keyboardProps ,
246
253
{ ...moveProps , onKeyDown} ,
247
- pressProps
254
+ pressProps ,
255
+ { style : { touchAction : 'none' } }
248
256
) ,
249
257
inputProps : mergeProps (
250
258
visuallyHiddenProps ,
251
259
{
252
260
id,
253
- onFocus : ( ) => {
254
- let resizeOnFocus = ! ! triggerRef ?. current ;
255
- if ( resizeOnFocus ) {
256
- // useMove calls onMoveStart for every keypress, but we want resize start to only be called when we start resize mode
257
- // call instead during focus and blur
258
- startResize ( item ) ;
259
- state . tableState . setKeyboardNavigationDisabled ( true ) ;
260
- }
261
- } ,
262
261
onBlur : ( ) => {
263
262
endResize ( item ) ;
264
- state . tableState . setKeyboardNavigationDisabled ( false ) ;
265
263
} ,
266
264
onChange,
267
265
disabled : isDisabled
0 commit comments