1010 * governing permissions and limitations under the License.
1111 */
1212
13+ import { ChangeEvent , RefObject , useCallback , useRef } from 'react' ;
1314import { DOMAttributes } from '@react-types/shared' ;
1415import { focusSafely } from '@react-aria/focus' ;
16+ import { focusWithoutScrolling , mergeProps , useId } from '@react-aria/utils' ;
17+ import { getColumnHeaderId } from './utils' ;
1518import { 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 ' ;
1821import { 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' ;
2124
2225export interface TableColumnResizeAria {
26+ inputProps : DOMAttributes ,
2327 resizerProps : DOMAttributes
2428}
2529
2630export interface AriaTableColumnResizeProps < T > {
2731 column : GridNode < T > ,
28- showResizer : boolean ,
29- label : string
32+ label : string ,
33+ triggerRef : RefObject < HTMLDivElement >
3034}
3135
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 ) ;
3539 // 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 ( ) ;
3844
3945 let { direction} = useLocale ( ) ;
4046 let { keyboardProps} = useKeyboard ( {
4147 onKeyDown : ( e ) => {
4248 if ( e . key === 'Escape' || e . key === 'Enter' || e . key === ' ' || e . key === 'Tab' ) {
4349 e . preventDefault ( ) ;
4450 // switch focus back to the column header on anything that ends edit mode
45- focusSafely ( ref . current . closest ( '[role="columnheader"]' ) ) ;
51+ focusSafely ( triggerRef . current ) ;
4652 }
4753 }
4854 } ) ;
4955
50- const columnResizeWidthRef = useRef ( null ) ;
56+ const columnResizeWidthRef = useRef < number > ( 0 ) ;
5157 const { moveProps} = useMove ( {
52- onMoveStart ( { pointerType} ) {
53- if ( pointerType !== 'keyboard' ) {
54- stateRef . current . onColumnResizeStart ( item ) ;
55- }
58+ onMoveStart ( ) {
5659 columnResizeWidthRef . current = stateRef . current . getColumnWidth ( item . key ) ;
5760 cursor . current = document . body . style . cursor ;
5861 } ,
@@ -76,45 +79,89 @@ export function useTableColumnResize<T>(props: AriaTableColumnResizeProps<T>, st
7679 }
7780 }
7881 } ,
79- onMoveEnd ( { pointerType} ) {
80- if ( pointerType !== 'keyboard' ) {
81- stateRef . current . onColumnResizeEnd ( item ) ;
82- }
82+ onMoveEnd ( ) {
8383 columnResizeWidthRef . current = 0 ;
8484 document . body . style . cursor = cursor . current ;
8585 }
8686 } ) ;
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 ) ) ;
8793
8894 let ariaProps = {
89- role : 'separator' ,
9095 '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 ) ;
96120 } ;
97121
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+
98143 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 ) ;
114157 } ,
115- keyboardProps ,
116- ariaProps
117- )
118- }
158+ onBlur : ( ) => {
159+ stateRef . current . onColumnResizeEnd ( item ) ;
160+ state . setKeyboardNavigationDisabled ( false ) ;
161+ } ,
162+ onChange
163+ } ,
164+ ariaProps
165+ )
119166 } ;
120167}
0 commit comments