Instantly share code, notes, and snippets.
Created
May 6, 2025 13:52
-
Star
0
(0)
You must be signed in to star a gist -
Fork
0
(0)
You must be signed in to fork a gist
-
Save clintandrewhall/fc115e312ac27ae40f5f99fdc962adb9 to your computer and use it in GitHub Desktop.
Hacked-up version of resizeable button for Kibana Chrome
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| /* | |
| * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one | |
| * or more contributor license agreements. Licensed under the "Elastic License | |
| * 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side | |
| * Public License v 1"; you may not use this file except in compliance with, at | |
| * your election, the "Elastic License 2.0", the "GNU Affero General Public | |
| * License v3.0 only", or the "Server Side Public License, v 1". | |
| */ | |
| import React, { | |
| useCallback, | |
| useRef, | |
| MouseEvent as ReactMouseEvent, | |
| TouchEvent as ReactTouchEvent, | |
| } from 'react'; | |
| import { EuiResizableButton, keys, useLatest } from '@elastic/eui'; | |
| import { | |
| EuiResizableButtonKeyEvent, | |
| KeyMoveDirection, | |
| ResizeTrigger, | |
| } from '@elastic/eui/src/components/resizable_container/types'; | |
| import { | |
| setToolsPanelWidth, | |
| useChromeDispatch, | |
| useToolbarWidth, | |
| useToolsPanelWidth, | |
| } from '@kbn/core-chrome-state-internal'; | |
| import { styles } from './resizeable_button.styles'; | |
| export const isTouchEvent = ( | |
| event: MouseEvent | ReactMouseEvent | TouchEvent | ReactTouchEvent | |
| ): event is TouchEvent | ReactTouchEvent => typeof event === 'object' && 'targetTouches' in event; | |
| export const getPosition = ( | |
| event: ReactMouseEvent | MouseEvent | ReactTouchEvent | TouchEvent | |
| ): number => { | |
| const direction = 'clientX'; | |
| return isTouchEvent(event) ? event.targetTouches[0][direction] : event[direction]; | |
| }; | |
| interface Props { | |
| /** | |
| * Called when resizing starts | |
| */ | |
| onResizeStart?: (trigger: ResizeTrigger) => void; | |
| /** | |
| * Called when resizing ends | |
| */ | |
| onResizeEnd?: () => void; | |
| } | |
| export const ResizeableButton = ({ onResizeEnd, onResizeStart }: Props) => { | |
| const toolbarWidth = useToolbarWidth(); | |
| const toolsPanelWidth = useToolsPanelWidth(); | |
| const dispatch = useChromeDispatch(); | |
| const onResizeEndRef = useLatest(onResizeEnd); | |
| const onResizeStartRef = useLatest(onResizeStart); | |
| const resizeContext = useRef<{ | |
| trigger?: ResizeTrigger; | |
| keyMoveDirection?: KeyMoveDirection; | |
| }>({}); | |
| const resizeEnd = useCallback(() => { | |
| onResizeEndRef.current?.(); | |
| resizeContext.current = {}; | |
| }, [onResizeEndRef]); | |
| const resizeStart = useCallback( | |
| (trigger: ResizeTrigger, keyMoveDirection?: KeyMoveDirection) => { | |
| // If another resize starts while the previous one is still in progress | |
| // (e.g. user presses opposite arrow to change direction while the first | |
| // is still held down, or user presses an arrow while dragging with the | |
| // mouse), we want to signal the end of the previous resize first. | |
| if (resizeContext.current.trigger) { | |
| resizeEnd(); | |
| } | |
| onResizeStartRef.current?.(trigger); | |
| resizeContext.current = { trigger, keyMoveDirection }; | |
| }, | |
| [onResizeStartRef, resizeEnd] | |
| ); | |
| const onMouseDown = useCallback(() => { | |
| resizeStart('pointer'); | |
| // Window event listeners instead of React events are used to continue | |
| // detecting movement even if the user's mouse leaves the container | |
| const onMouseMove = (e: MouseEvent | TouchEvent) => { | |
| const pos = getPosition(e); | |
| // This shouldn't be here, should be a handler? | |
| dispatch(setToolsPanelWidth(document.body.clientWidth - pos - toolbarWidth)); | |
| }; | |
| const onMouseUp = () => { | |
| if (resizeContext.current.trigger === 'pointer') { | |
| resizeEnd(); | |
| } | |
| window.removeEventListener('mousemove', onMouseMove); | |
| window.removeEventListener('mouseup', onMouseUp); | |
| window.removeEventListener('touchmove', onMouseMove); | |
| window.removeEventListener('touchend', onMouseUp); | |
| }; | |
| window.addEventListener('mousemove', onMouseMove); | |
| window.addEventListener('mouseup', onMouseUp); | |
| window.addEventListener('touchmove', onMouseMove); | |
| window.addEventListener('touchend', onMouseUp); | |
| }, [resizeStart, resizeEnd, toolbarWidth, dispatch]); | |
| const getKeyMoveDirection = useCallback((key: string) => { | |
| let dir: KeyMoveDirection | null = null; | |
| if (key === keys.ARROW_LEFT) { | |
| dir = 'backward'; | |
| } else if (key === keys.ARROW_RIGHT) { | |
| dir = 'forward'; | |
| } | |
| return dir; | |
| }, []); | |
| const onKeyDown = useCallback( | |
| (event: EuiResizableButtonKeyEvent) => { | |
| const { key } = event; | |
| const dir = getKeyMoveDirection(key); | |
| if (dir) { | |
| if (!event.repeat) { | |
| // I'm not certain this would be the handler I'd expect. | |
| resizeStart('key', dir); | |
| } | |
| event.preventDefault(); | |
| // These shouldn't be here, should be handlers? | |
| if (dir === 'backward') { | |
| dispatch(setToolsPanelWidth(toolsPanelWidth + 10)); | |
| } else { | |
| dispatch(setToolsPanelWidth(toolsPanelWidth - 10)); | |
| } | |
| } | |
| }, | |
| [getKeyMoveDirection, resizeStart, toolsPanelWidth, dispatch] | |
| ); | |
| const onKeyUp = useCallback( | |
| ({ key }: EuiResizableButtonKeyEvent) => { | |
| // We only want to signal the end of a resize if the key that was released | |
| // is the same as the one that started the resize. This prevents the end | |
| // of a resize if the user presses one arrow key, then presses the opposite | |
| // arrow key to change direction, then releases the first arrow key. | |
| if ( | |
| resizeContext.current.trigger === 'key' && | |
| resizeContext.current.keyMoveDirection === getKeyMoveDirection(key) | |
| ) { | |
| resizeEnd(); | |
| } | |
| }, | |
| [getKeyMoveDirection, resizeEnd] | |
| ); | |
| const onBlur = useCallback(() => { | |
| if (resizeContext.current.trigger === 'key') { | |
| resizeEnd(); | |
| } | |
| }, [resizeEnd]); | |
| return ( | |
| <EuiResizableButton | |
| css={styles.root} | |
| isHorizontal={true} | |
| {...{ | |
| onKeyDown, | |
| onKeyUp, | |
| onMouseDown, | |
| onTouchStart: onMouseDown, | |
| onBlur, | |
| }} | |
| /> | |
| ); | |
| }; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment