10
10
* governing permissions and limitations under the License.
11
11
*/
12
12
13
+ import { ChangeEvent , RefObject , useCallback , useRef } from 'react' ;
13
14
import { DOMAttributes } from '@react-types/shared' ;
14
15
import { focusSafely } from '@react-aria/focus' ;
16
+ import { focusWithoutScrolling , mergeProps , useId } from '@react-aria/utils' ;
17
+ import { getColumnHeaderId } from './utils' ;
15
18
import { GridNode } from '@react-types/grid' ;
16
- import { mergeProps } from '@react-aria/utils' ;
17
- import { RefObject , useRef } from 'react ' ;
19
+ // @ts -ignore
20
+ import intlMessages from '../intl/*.json ' ;
18
21
import { TableColumnResizeState , TableState } from '@react-stately/table' ;
19
- import { useKeyboard , useMove } from '@react-aria/interactions' ;
20
- import { useLocale } from '@react-aria/i18n' ;
22
+ import { useKeyboard , useMove , usePress } from '@react-aria/interactions' ;
23
+ import { useLocale , useLocalizedStringFormatter } from '@react-aria/i18n' ;
21
24
22
25
export interface TableColumnResizeAria {
26
+ inputProps : DOMAttributes ,
23
27
resizerProps : DOMAttributes
24
28
}
25
29
26
30
export interface AriaTableColumnResizeProps < T > {
27
31
column : GridNode < T > ,
28
- showResizer : boolean ,
29
- label : string
32
+ label : string ,
33
+ triggerRef : RefObject < HTMLDivElement >
30
34
}
31
35
32
- export function useTableColumnResize < T > ( props : AriaTableColumnResizeProps < T > , state : TableState < T > & TableColumnResizeState < T > , ref : RefObject < HTMLDivElement > ) : TableColumnResizeAria {
33
- let { column : item , showResizer } = props ;
34
- const stateRef = useRef ( null ) ;
36
+ export function useTableColumnResize < T > ( props : AriaTableColumnResizeProps < T > , state : TableState < T > , columnState : TableColumnResizeState < T > , ref : RefObject < HTMLInputElement > ) : TableColumnResizeAria {
37
+ let { column : item , triggerRef } = props ;
38
+ const stateRef = useRef < TableColumnResizeState < T > > ( null ) ;
35
39
// keep track of what the cursor on the body is so it can be restored back to that when done resizing
36
- const cursor = useRef ( null ) ;
37
- stateRef . current = state ;
40
+ const cursor = useRef < string | null > ( null ) ;
41
+ stateRef . current = columnState ;
42
+ const stringFormatter = useLocalizedStringFormatter ( intlMessages ) ;
43
+ let id = useId ( ) ;
38
44
39
45
let { direction} = useLocale ( ) ;
40
46
let { keyboardProps} = useKeyboard ( {
41
47
onKeyDown : ( e ) => {
42
48
if ( e . key === 'Escape' || e . key === 'Enter' || e . key === ' ' || e . key === 'Tab' ) {
43
49
e . preventDefault ( ) ;
44
50
// switch focus back to the column header on anything that ends edit mode
45
- focusSafely ( ref . current . closest ( '[role="columnheader"]' ) ) ;
51
+ focusSafely ( triggerRef . current ) ;
46
52
}
47
53
}
48
54
} ) ;
49
55
50
- const columnResizeWidthRef = useRef ( null ) ;
56
+ const columnResizeWidthRef = useRef < number > ( 0 ) ;
51
57
const { moveProps} = useMove ( {
52
- onMoveStart ( { pointerType} ) {
53
- if ( pointerType !== 'keyboard' ) {
54
- stateRef . current . onColumnResizeStart ( item ) ;
55
- }
58
+ onMoveStart ( ) {
56
59
columnResizeWidthRef . current = stateRef . current . getColumnWidth ( item . key ) ;
57
60
cursor . current = document . body . style . cursor ;
58
61
} ,
@@ -76,45 +79,89 @@ export function useTableColumnResize<T>(props: AriaTableColumnResizeProps<T>, st
76
79
}
77
80
}
78
81
} ,
79
- onMoveEnd ( { pointerType} ) {
80
- if ( pointerType !== 'keyboard' ) {
81
- stateRef . current . onColumnResizeEnd ( item ) ;
82
- }
82
+ onMoveEnd ( ) {
83
83
columnResizeWidthRef . current = 0 ;
84
84
document . body . style . cursor = cursor . current ;
85
85
}
86
86
} ) ;
87
+ let min = Math . floor ( stateRef . current . getColumnMinWidth ( item . key ) ) ;
88
+ let max = Math . floor ( stateRef . current . getColumnMaxWidth ( item . key ) ) ;
89
+ if ( max === Infinity ) {
90
+ max = Number . MAX_SAFE_INTEGER ;
91
+ }
92
+ let value = Math . floor ( stateRef . current . getColumnWidth ( item . key ) ) ;
87
93
88
94
let ariaProps = {
89
- role : 'separator' ,
90
95
'aria-label' : props . label ,
91
- 'aria-orientation' : 'vertical' ,
92
- 'aria-labelledby' : item . key ,
93
- 'aria-valuenow' : stateRef . current . getColumnWidth ( item . key ) ,
94
- 'aria-valuemin' : stateRef . current . getColumnMinWidth ( item . key ) ,
95
- 'aria-valuemax' : stateRef . current . getColumnMaxWidth ( item . key )
96
+ 'aria-orientation' : 'horizontal' as 'horizontal' ,
97
+ 'aria-labelledby' : `${ id } ${ getColumnHeaderId ( state , item . key ) } ` ,
98
+ 'aria-valuetext' : stringFormatter . format ( 'columnSize' , { value} ) ,
99
+ min,
100
+ max,
101
+ value
102
+ } ;
103
+
104
+ const focusInput = useCallback ( ( ) => {
105
+ if ( ref . current ) {
106
+ focusWithoutScrolling ( ref . current ) ;
107
+ }
108
+ } , [ ref ] ) ;
109
+
110
+ let onChange = ( e : ChangeEvent < HTMLInputElement > ) => {
111
+ let currentWidth = stateRef . current . getColumnWidth ( item . key ) ;
112
+ let nextValue = parseFloat ( e . target . value ) ;
113
+
114
+ if ( nextValue > currentWidth ) {
115
+ nextValue = currentWidth + 10 ;
116
+ } else {
117
+ nextValue = currentWidth - 10 ;
118
+ }
119
+ stateRef . current . onColumnResize ( item , nextValue ) ;
96
120
} ;
97
121
122
+ let { pressProps} = usePress ( {
123
+ onPressStart : ( e ) => {
124
+ if ( e . ctrlKey || e . altKey || e . metaKey || e . shiftKey || e . pointerType === 'keyboard' ) {
125
+ return ;
126
+ }
127
+ if ( e . pointerType === 'virtual' && columnState . currentlyResizingColumn != null ) {
128
+ stateRef . current . onColumnResizeEnd ( item ) ;
129
+ focusSafely ( triggerRef . current ) ;
130
+ return ;
131
+ }
132
+ focusInput ( ) ;
133
+ } ,
134
+ onPress : ( e ) => {
135
+ if ( e . pointerType === 'touch' ) {
136
+ focusInput ( ) ;
137
+ } else if ( e . pointerType !== 'virtual' ) {
138
+ focusSafely ( triggerRef . current ) ;
139
+ }
140
+ }
141
+ } ) ;
142
+
98
143
return {
99
- resizerProps : {
100
- ...mergeProps (
101
- moveProps ,
102
- {
103
- onFocus : ( ) => {
104
- // useMove calls onMoveStart for every keypress, but we want resize start to only be called when we start resize mode
105
- // call instead during focus and blur
106
- stateRef . current . onColumnResizeStart ( item ) ;
107
- state . setKeyboardNavigationDisabled ( true ) ;
108
- } ,
109
- onBlur : ( ) => {
110
- stateRef . current . onColumnResizeEnd ( item ) ;
111
- state . setKeyboardNavigationDisabled ( false ) ;
112
- } ,
113
- tabIndex : showResizer ? 0 : undefined
144
+ resizerProps : mergeProps (
145
+ keyboardProps ,
146
+ moveProps ,
147
+ pressProps
148
+ ) ,
149
+ inputProps : mergeProps (
150
+ {
151
+ id,
152
+ onFocus : ( ) => {
153
+ // useMove calls onMoveStart for every keypress, but we want resize start to only be called when we start resize mode
154
+ // call instead during focus and blur
155
+ stateRef . current . onColumnResizeStart ( item ) ;
156
+ state . setKeyboardNavigationDisabled ( true ) ;
114
157
} ,
115
- keyboardProps ,
116
- ariaProps
117
- )
118
- }
158
+ onBlur : ( ) => {
159
+ stateRef . current . onColumnResizeEnd ( item ) ;
160
+ state . setKeyboardNavigationDisabled ( false ) ;
161
+ } ,
162
+ onChange
163
+ } ,
164
+ ariaProps
165
+ )
119
166
} ;
120
167
}
0 commit comments