@@ -44,6 +44,9 @@ export class DDDraggable extends DDBaseImplement implements HTMLElementExtendOpt
4444 protected dragEls : HTMLElement [ ] ;
4545 /** @internal true while we are dragging an item around */
4646 protected dragging : boolean ;
47+
48+ /** @internal true while we are dragging an item around */
49+ protected keyboardSelected : HTMLElement ;
4750 /** @internal last drag event */
4851 protected lastDrag : DragEvent ;
4952 /** @internal */
@@ -74,6 +77,9 @@ export class DDDraggable extends DDBaseImplement implements HTMLElementExtendOpt
7477 }
7578 // create var event binding so we can easily remove and still look like TS methods (unlike anonymous functions)
7679 this . _mouseDown = this . _mouseDown . bind ( this ) ;
80+ this . _mouseKeyDown = this . _mouseKeyDown . bind ( this ) ;
81+ this . _keyMove = this . _keyMove . bind ( this ) ;
82+ this . _keyUp = this . _keyUp . bind ( this ) ;
7783 this . _mouseMove = this . _mouseMove . bind ( this ) ;
7884 this . _mouseUp = this . _mouseUp . bind ( this ) ;
7985 this . _keyEvent = this . _keyEvent . bind ( this ) ;
@@ -92,6 +98,7 @@ export class DDDraggable extends DDBaseImplement implements HTMLElementExtendOpt
9298 if ( this . disabled === false ) return ;
9399 super . enable ( ) ;
94100 this . dragEls . forEach ( dragEl => {
101+ dragEl . addEventListener ( 'keydown' , this . _mouseKeyDown )
95102 dragEl . addEventListener ( 'mousedown' , this . _mouseDown ) ;
96103 if ( isTouch ) {
97104 dragEl . addEventListener ( 'touchstart' , touchstart ) ;
@@ -131,6 +138,98 @@ export class DDDraggable extends DDBaseImplement implements HTMLElementExtendOpt
131138 return this ;
132139 }
133140
141+ protected _elCoordinates ( element : HTMLElement ) {
142+ const rect = element . getBoundingClientRect ( ) ;
143+ const clientX = rect . left ;
144+ const clientY = rect . top ;
145+ const offsetX = element . offsetLeft ;
146+ const offsetY = element . offsetTop ;
147+ const pageX = window . scrollX + rect . left ;
148+ const pageY = window . scrollY + rect . top ;
149+ const screenX = window . screenX + rect . left ;
150+ const screenY = window . screenY + rect . top ;
151+
152+ return { clientX : clientX ,
153+ clientY : clientY ,
154+ offsetX : offsetX ,
155+ offsetY : offsetY ,
156+ pageX : pageX ,
157+ pageY : pageY ,
158+ screenX : screenX ,
159+ screenY : screenY }
160+ }
161+
162+ protected _elNewCoordinates ( event : KeyboardEvent , element : HTMLElement ) {
163+ let coordinates = this . _elCoordinates ( element )
164+
165+ switch ( event . code ) {
166+ case 'ArrowRight' :
167+ coordinates . clientX = coordinates . clientX + 400
168+ break
169+ case 'ArrowLeft' :
170+ coordinates . clientX = coordinates . clientX - 400
171+ break
172+ case 'ArrowUp' :
173+ coordinates . clientY = coordinates . clientY - 400
174+ break
175+ case 'ArrowDown' :
176+ coordinates . clientY = coordinates . clientY + 400
177+ break
178+ }
179+ return coordinates
180+ }
181+
182+ protected _mouseKeyDown ( e : KeyboardEvent ) : void {
183+ if ( e . code === 'Space' ) {
184+ e . preventDefault ( )
185+
186+ const handle = e . target as HTMLElement
187+ const item : HTMLElement = handle ?. closest ( '.grid-stack-item' )
188+ this . keyboardSelected = item
189+ item . classList . add ( 'grid-stack-item-selected' )
190+
191+ e . target . dispatchEvent ( new MouseEvent ( 'mousedown' ) )
192+ document . addEventListener ( 'keyup' , this . _keyUp )
193+ }
194+ }
195+
196+ protected _keyUp ( ) {
197+ document . removeEventListener ( 'keyup' , this . _keyUp )
198+ document . addEventListener ( 'keydown' , this . _keyMove )
199+ }
200+
201+ protected _selectedItem ( element : HTMLElement ) : HTMLElement {
202+ const items = document . querySelectorAll ( '.grid-stack-item' )
203+
204+ return Array . from ( items ) . filter ( item => item === element ) [ 0 ] as HTMLElement
205+ }
206+
207+ protected _keyMove ( e : KeyboardEvent ) {
208+ if ( e . code === 'Space' ) {
209+ e . preventDefault ( )
210+
211+ this . keyboardSelected . classList . remove ( 'grid-stack-item-selected' )
212+ this . keyboardSelected . dispatchEvent ( new MouseEvent ( 'mouseup' ) )
213+ document . removeEventListener ( 'keydown' , this . _keyMove )
214+
215+ return
216+ }
217+
218+ if ( e . code === 'ArrowRight' ||
219+ e . code === 'ArrowLeft' ||
220+ e . code === 'ArrowUp' ||
221+ e . code === 'ArrowDown' ) {
222+ e . target . dispatchEvent ( new MouseEvent ( 'mousemove' , { ...this . _elCoordinates ( this . keyboardSelected ) } ) )
223+ e . target . dispatchEvent ( new MouseEvent ( 'mousemove' , { ...this . _elNewCoordinates ( e , this . keyboardSelected ) } ) )
224+ e . target . dispatchEvent ( new MouseEvent ( 'mouseup' ) )
225+
226+ this . keyboardSelected = this . _selectedItem ( this . keyboardSelected )
227+ const handle : HTMLElement = this . keyboardSelected . querySelector ( '.grid-item-handle' )
228+
229+ handle ?. dispatchEvent ( new MouseEvent ( 'mousedown' ) )
230+ }
231+ }
232+
134233 /** @internal call when mouse goes down before a dragstart happens */
135234 protected _mouseDown ( e : MouseEvent ) : boolean {
136235 // don't let more than one widget handle mouseStart
0 commit comments