diff --git a/components/lib/splitter/Splitter.js b/components/lib/splitter/Splitter.js index 5a45c3d79e..5877a1b524 100644 --- a/components/lib/splitter/Splitter.js +++ b/components/lib/splitter/Splitter.js @@ -26,6 +26,7 @@ export const Splitter = React.memo( const nextPanelSize = React.useRef(null); const nextPanelSizeNew = React.useRef(null); const prevPanelIndex = React.useRef(null); + const timer = React.useRef(null); const [panelSizes, setPanelSizes] = React.useState([]); const [nested, setNested] = React.useState(false); const isStateful = props.stateKey != null; @@ -130,19 +131,27 @@ export const Splitter = React.memo( if (stateString) setPanelSizes(JSON.parse(stateString)); }, [getStorage, props.stateKey]); - const onResizeStart = (event, index) => { + const onResizeStart = (event, index, isKeyDown) => { gutterRef.current = gutterRefs.current[index]; let pageX = event.type === 'touchstart' ? event.touches[0].pageX : event.pageX; let pageY = event.type === 'touchstart' ? event.touches[0].pageY : event.pageY; + const horizontal = props.layout === 'horizontal'; - size.current = props.layout === 'horizontal' ? DomHandler.getWidth(elementRef.current) : DomHandler.getHeight(elementRef.current); + size.current = horizontal ? DomHandler.getWidth(elementRef.current) : DomHandler.getHeight(elementRef.current); dragging.current = true; - startPos.current = props.layout === 'horizontal' ? pageX : pageY; + startPos.current = horizontal ? pageX : pageY; prevPanelElement.current = gutterRef.current.previousElementSibling; nextPanelElement.current = gutterRef.current.nextElementSibling; - prevPanelSize.current = (100 * (props.layout === 'horizontal' ? DomHandler.getOuterWidth(prevPanelElement.current, true) : DomHandler.getOuterHeight(prevPanelElement.current, true))) / size.current; + + if (isKeyDown) { + prevPanelSize.current = horizontal ? DomHandler.getOuterWidth(prevPanelElement.current, true) : DomHandler.getOuterHeight(prevPanelElement.current, true); + nextPanelSize.current = horizontal ? DomHandler.getOuterWidth(nextPanelElement.current, true) : DomHandler.getOuterHeight(nextPanelElement.current, true); + } else { + prevPanelSize.current = (100 * (horizontal ? DomHandler.getOuterWidth(prevPanelElement.current, true) : DomHandler.getOuterHeight(prevPanelElement.current, true))) / size.current; + nextPanelSize.current = (100 * (horizontal ? DomHandler.getOuterWidth(nextPanelElement.current, true) : DomHandler.getOuterHeight(nextPanelElement.current, true))) / size.current; + } + prevPanelSizeNew.current = prevPanelSize.current; - nextPanelSize.current = (100 * (props.layout === 'horizontal' ? DomHandler.getOuterWidth(nextPanelElement.current, true) : DomHandler.getOuterHeight(nextPanelElement.current, true))) / size.current; nextPanelSizeNew.current = nextPanelSize.current; prevPanelIndex.current = index; !isUnstyled() && DomHandler.addClass(gutterRef.current, 'p-splitter-gutter-resizing'); @@ -151,16 +160,27 @@ export const Splitter = React.memo( elementRef.current.setAttribute('data-p-splitter-resizing', true); }; - const onResize = (event) => { - let newPos; + const onResize = (event, step = 0, isKeyDown = false) => { + let newPos, newNextPanelSize, newPrevPanelSize; + let horizontal = props.layout === 'horizontal'; let pageX = event.type === 'touchmove' ? event.touches[0].pageX : event.pageX; let pageY = event.type === 'touchmove' ? event.touches[0].pageY : event.pageY; - if (props.layout === 'horizontal') newPos = (pageX * 100) / size.current - (startPos.current * 100) / size.current; - else newPos = (pageY * 100) / size.current - (startPos.current * 100) / size.current; + if (isKeyDown) { + if (horizontal) { + newPrevPanelSize = (100 * (prevPanelSize.current + step)) / size.current; + newNextPanelSize = (100 * (nextPanelSize.current - step)) / size.current; + } else { + newPrevPanelSize = (100 * (prevPanelSize.current - step)) / size.current; + newNextPanelSize = (100 * (nextPanelSize.current + step)) / size.current; + } + } else { + if (horizontal) newPos = (pageX * 100) / size.current - (startPos.current * 100) / size.current; + else newPos = (pageY * 100) / size.current - (startPos.current * 100) / size.current; - let newPrevPanelSize = prevPanelSize.current + newPos; - let newNextPanelSize = nextPanelSize.current - newPos; + newPrevPanelSize = prevPanelSize.current + newPos; + newNextPanelSize = nextPanelSize.current - newPos; + } if (validateResize(newPrevPanelSize, newNextPanelSize)) { prevPanelSizeNew.current = newPrevPanelSize; @@ -192,19 +212,86 @@ export const Splitter = React.memo( }); !isUnstyled() && DomHandler.removeClass(gutterRef.current, 'p-splitter-gutter-resizing'); - gutterRef.current.setAttribute('data-p-splitter-gutter-resizing', false); + gutterRefs.current && Object.keys(gutterRefs.current).forEach((key) => gutterRefs.current[key].setAttribute('data-p-splitter-gutter-resizing', false)); !isUnstyled() && DomHandler.removeClass(elementRef.current, 'p-splitter-resizing'); elementRef.current.setAttribute('data-p-splitter-resizing', false); clear(); }; + const onGutterKeyUp = () => { + clearTimer(); + onResizeEnd(); + }; + + const onGutterKeyDown = (event, index) => { + switch (event.code) { + case 'ArrowLeft': { + if (props.layout === 'horizontal') { + setTimer(event, index, props.step * -1); + } + + event.preventDefault(); + break; + } + + case 'ArrowRight': { + if (props.layout === 'horizontal') { + setTimer(event, index, props.step); + } + + event.preventDefault(); + break; + } + + case 'ArrowDown': { + if (props.layout === 'vertical') { + setTimer(event, index, props.step * -1); + } + + event.preventDefault(); + break; + } + + case 'ArrowUp': { + if (props.layout === 'vertical') { + setTimer(event, index, props.step); + } + + event.preventDefault(); + break; + } + + default: + //no op + break; + } + }; + + const repeat = (event, index, step) => { + onResizeStart(event, index, true); + onResize(event, step, true); + }; + + const setTimer = (event, index, step) => { + clearTimer(); + timer.current = setTimeout(() => { + repeat(event, index, step); + }, 40); + }; + + const clearTimer = () => { + if (timer.current) { + clearTimeout(timer.current); + } + }; + const onGutterMouseDown = (event, index) => { - onResizeStart(event, index); + onResizeStart(event, index, false); bindMouseListeners(); }; const onGutterTouchStart = (event, index) => { - onResizeStart(event, index); + onResizeStart(event, index, false); window.addEventListener('touchmove', onGutterTouchMove, { passive: false, cancelable: false }); window.addEventListener('touchend', onGutterTouchEnd); @@ -253,6 +340,8 @@ export const Splitter = React.memo( className: cx('gutter'), style: props.layout === 'horizontal' ? { width: props.gutterSize + 'px' } : { height: props.gutterSize + 'px' }, onMouseDown: (event) => onGutterMouseDown(event, index), + onKeyDown: (event) => onGutterKeyDown(event, index), + onKeyUp: onGutterKeyUp, onTouchStart: (event) => onGutterTouchStart(event, index), onTouchMove: (event) => onGutterTouchMove(event), onTouchEnd: (event) => onGutterTouchEnd(event), diff --git a/components/lib/splitter/SplitterBase.js b/components/lib/splitter/SplitterBase.js index 80dd471e85..666b7f4921 100644 --- a/components/lib/splitter/SplitterBase.js +++ b/components/lib/splitter/SplitterBase.js @@ -16,24 +16,24 @@ const styles = ` display: flex; flex-wrap: nowrap; } - + .p-splitter-vertical { flex-direction: column; } - + .p-splitter-panel { flex-grow: 1; } - + .p-splitter-panel-nested { display: flex; } - + .p-splitter-panel .p-splitter { flex-grow: 1; border: 0 none; } - + .p-splitter-gutter { flex-grow: 0; flex-shrink: 0; @@ -42,30 +42,30 @@ const styles = ` justify-content: center; cursor: col-resize; } - + .p-splitter-horizontal.p-splitter-resizing { cursor: col-resize; user-select: none; } - + .p-splitter-horizontal > .p-splitter-gutter > .p-splitter-gutter-handle { height: 24px; width: 100%; } - + .p-splitter-horizontal > .p-splitter-gutter { cursor: col-resize; } - + .p-splitter-vertical.p-splitter-resizing { cursor: row-resize; user-select: none; } - + .p-splitter-vertical > .p-splitter-gutter { cursor: row-resize; } - + .p-splitter-vertical > .p-splitter-gutter > .p-splitter-gutter-handle { width: 24px; height: 100%; @@ -80,6 +80,7 @@ export const SplitterBase = ComponentBase.extend({ className: null, gutterSize: 4, id: null, + step: 5, layout: 'horizontal', onResizeEnd: null, stateKey: null, diff --git a/components/lib/splitter/splitter.d.ts b/components/lib/splitter/splitter.d.ts index 8a1525507d..38747e0d11 100644 --- a/components/lib/splitter/splitter.d.ts +++ b/components/lib/splitter/splitter.d.ts @@ -127,6 +127,11 @@ interface SplitterPanelProps { * @readonly */ children?: React.ReactNode | undefined; + /** + * Step factor to increment/decrement the size of the panels while pressing the arrow keys. + * @defaultValue 5 + */ + step?: number | undefined; /** * Uses to pass attributes to DOM elements inside the component. * @type {SplitterPanelPassThroughOptions}