@snack-uikit/input-private
Advanced tools
Comparing version 3.2.1 to 4.0.0
@@ -6,2 +6,13 @@ # Change Log | ||
# 4.0.0 (2024-09-27) | ||
### BREAKING CHANGES | ||
* **PDS-199:** differentiate postfix and prefix buttons in useButtonNavigation hook ([662f72c](https://github.com/cloud-ru-tech/snack-uikit/commit/662f72ce6797a563f6e13535dcf3912c961b632c)) | ||
## 3.2.1 (2024-09-23) | ||
@@ -8,0 +19,0 @@ |
@@ -17,2 +17,7 @@ /** | ||
/** | ||
* Проверяет находится ли курсор в начале поля ввода | ||
* @function helper | ||
*/ | ||
export declare function isCursorInTheBeginning(input: HTMLInputElement | HTMLTextAreaElement | null): boolean; | ||
/** | ||
* Проверяет находится ли курсор в конце поля ввода | ||
@@ -19,0 +24,0 @@ * @function helper |
@@ -25,2 +25,9 @@ /** | ||
/** | ||
* Проверяет находится ли курсор в начале поля ввода | ||
* @function helper | ||
*/ | ||
export function isCursorInTheBeginning(input) { | ||
return (input === null || input === void 0 ? void 0 : input.selectionStart) === 0; | ||
} | ||
/** | ||
* Проверяет находится ли курсор в конце поля ввода | ||
@@ -27,0 +34,0 @@ * @function helper |
import { KeyboardEventHandler, MouseEventHandler, ReactElement, RefObject } from 'react'; | ||
type RenderButtonProps = { | ||
type RenderActiveButtonProps = { | ||
key: string; | ||
@@ -9,8 +9,19 @@ ref: RefObject<HTMLButtonElement>; | ||
}; | ||
export type ButtonProps = { | ||
type RenderInactiveButtonProps = { | ||
key: string; | ||
}; | ||
export type InactiveItem = { | ||
active: false; | ||
id: string; | ||
render(props: RenderInactiveButtonProps): ReactElement; | ||
show: boolean; | ||
}; | ||
export type ActiveItem = { | ||
active: true; | ||
id: string; | ||
ref: RefObject<HTMLButtonElement>; | ||
show: boolean; | ||
render(props: RenderButtonProps): ReactElement; | ||
render(props: RenderActiveButtonProps): ReactElement; | ||
}; | ||
export type ButtonProps = InactiveItem | ActiveItem; | ||
export {}; |
@@ -6,3 +6,4 @@ import { KeyboardEventHandler, RefObject } from 'react'; | ||
inputRef: RefObject<T>; | ||
buttons: ButtonProps[]; | ||
postfixButtons: ButtonProps[]; | ||
prefixButtons?: ButtonProps[]; | ||
onButtonKeyDown?: KeyboardEventHandler<HTMLButtonElement>; | ||
@@ -16,8 +17,9 @@ readonly: boolean; | ||
*/ | ||
export declare function useButtonNavigation<T extends HTMLInputElement | HTMLTextAreaElement>({ inputRef, setInputFocus, buttons, onButtonKeyDown, readonly, submitKeys, }: UseButtonNavigationProps<T>): { | ||
export declare function useButtonNavigation<T extends HTMLInputElement | HTMLTextAreaElement>({ inputRef, setInputFocus, postfixButtons, prefixButtons, onButtonKeyDown, readonly, submitKeys, }: UseButtonNavigationProps<T>): { | ||
inputTabIndex: number; | ||
onInputKeyDown: KeyboardEventHandler<T>; | ||
setInitialTabIndices: () => void; | ||
buttons: import("react/jsx-runtime").JSX.Element; | ||
prefixButtons: import("react/jsx-runtime").JSX.Element | undefined; | ||
postfixButtons: import("react/jsx-runtime").JSX.Element | undefined; | ||
}; | ||
export {}; |
import { Fragment as _Fragment, jsx as _jsx } from "react/jsx-runtime"; | ||
import { useCallback, useState } from 'react'; | ||
import { useEventHandler } from '@snack-uikit/utils'; | ||
import { isCursorInTheEnd, runAfterRerender, selectAll } from '../helpers'; | ||
import { isCursorInTheBeginning, isCursorInTheEnd, runAfterRerender, selectAll } from '../helpers'; | ||
/** | ||
@@ -9,12 +9,14 @@ * Позволяет использовать клавиатуру для навигации по элементам управления | ||
*/ | ||
export function useButtonNavigation({ inputRef, setInputFocus = () => { var _a; return (_a = inputRef.current) === null || _a === void 0 ? void 0 : _a.focus(); }, buttons, onButtonKeyDown = () => { }, readonly, submitKeys, }) { | ||
const getInitialButtonTabIndices = useCallback(() => buttons.map(() => -1), [buttons]); | ||
export function useButtonNavigation({ inputRef, setInputFocus = () => { var _a; return (_a = inputRef.current) === null || _a === void 0 ? void 0 : _a.focus(); }, postfixButtons, prefixButtons = [], onButtonKeyDown = () => { }, readonly, submitKeys, }) { | ||
const [inputTabIndex, setInputTabIndex] = useState(0); | ||
const [buttonTabIndices, setButtonTabIndices] = useState(getInitialButtonTabIndices); | ||
const buttonKeyDownEventHandler = useEventHandler(onButtonKeyDown); | ||
const findVisibleButton = useCallback((index, direction) => { | ||
const getInitialPrefixButtonTabIndices = useCallback(() => prefixButtons.map(() => -1), [prefixButtons]); | ||
const [prefixButtonTabIndices, setPrefixButtonTabIndices] = useState(getInitialPrefixButtonTabIndices); | ||
const getInitialPostfixButtonTabIndices = useCallback(() => postfixButtons.map(() => -1), [postfixButtons]); | ||
const [postfixButtonTabIndices, setPostfixButtonTabIndices] = useState(getInitialPostfixButtonTabIndices); | ||
const findVisiblePrefixButton = useCallback((index, direction) => { | ||
const delta = direction === 'ArrowLeft' ? -1 : 1; | ||
const condition = (i) => (direction === 'ArrowLeft' ? i >= 0 : i < buttons.length); | ||
const condition = (i) => (direction === 'ArrowLeft' ? i >= 0 : i < prefixButtons.length); | ||
for (let i = index + delta; condition(i); i += delta) { | ||
if (buttons[i].show) { | ||
if (prefixButtons[i].active && prefixButtons[i].show) { | ||
return i; | ||
@@ -24,7 +26,18 @@ } | ||
return index; | ||
}, [buttons]); | ||
}, [prefixButtons]); | ||
const findVisiblePostfixButton = useCallback((index, direction) => { | ||
const delta = direction === 'ArrowLeft' ? -1 : 1; | ||
const condition = (i) => (direction === 'ArrowLeft' ? i >= 0 : i < postfixButtons.length); | ||
for (let i = index + delta; condition(i); i += delta) { | ||
if (postfixButtons[i].active && postfixButtons[i].show) { | ||
return i; | ||
} | ||
} | ||
return index; | ||
}, [postfixButtons]); | ||
const setInitialTabIndices = useCallback(() => { | ||
setInputTabIndex(0); | ||
setButtonTabIndices(getInitialButtonTabIndices); | ||
}, [getInitialButtonTabIndices]); | ||
setPrefixButtonTabIndices(getInitialPrefixButtonTabIndices); | ||
setPostfixButtonTabIndices(getInitialPostfixButtonTabIndices); | ||
}, [getInitialPrefixButtonTabIndices, getInitialPostfixButtonTabIndices]); | ||
const focusInput = useCallback(() => { | ||
@@ -34,20 +47,77 @@ setInitialTabIndices(); | ||
}, [setInitialTabIndices, setInputFocus]); | ||
const focusButton = useCallback((index) => { | ||
const focusPrefixButton = useCallback((index) => { | ||
var _a; | ||
setInputTabIndex(-1); | ||
setButtonTabIndices(tabIndices => tabIndices.map((_, i) => (i === index ? 0 : -1))); | ||
(_a = buttons[index].ref.current) === null || _a === void 0 ? void 0 : _a.focus(); | ||
}, [buttons]); | ||
setPrefixButtonTabIndices(tabIndices => tabIndices.map((_, i) => (i === index ? 0 : -1))); | ||
setPostfixButtonTabIndices(getInitialPostfixButtonTabIndices); | ||
prefixButtons[index].active && ((_a = prefixButtons[index].ref.current) === null || _a === void 0 ? void 0 : _a.focus()); | ||
}, [getInitialPostfixButtonTabIndices, prefixButtons]); | ||
const focusPostfixButton = useCallback((index) => { | ||
var _a; | ||
setInputTabIndex(-1); | ||
setPrefixButtonTabIndices(getInitialPrefixButtonTabIndices); | ||
setPostfixButtonTabIndices(tabIndices => tabIndices.map((_, i) => (i === index ? 0 : -1))); | ||
postfixButtons[index].active && ((_a = postfixButtons[index].ref.current) === null || _a === void 0 ? void 0 : _a.focus()); | ||
}, [getInitialPrefixButtonTabIndices, postfixButtons]); | ||
const handleInputKeyDown = useCallback(event => { | ||
setInitialTabIndices(); | ||
if (event.key === 'ArrowRight' && (readonly || isCursorInTheEnd(inputRef.current))) { | ||
const index = findVisibleButton(-1, event.key); | ||
const index = findVisiblePostfixButton(-1, event.key); | ||
if (index >= 0) { | ||
focusButton(index); | ||
focusPostfixButton(index); | ||
} | ||
} | ||
}, [findVisibleButton, focusButton, inputRef, readonly, setInitialTabIndices]); | ||
const handleButtonKeyDown = useCallback((index) => event => { | ||
if (event.key === 'ArrowLeft' && (readonly || isCursorInTheBeginning(inputRef.current))) { | ||
const index = findVisiblePrefixButton(prefixButtons.length, event.key); | ||
if (index >= 0) { | ||
focusPrefixButton(index); | ||
} | ||
} | ||
}, [ | ||
findVisiblePostfixButton, | ||
findVisiblePrefixButton, | ||
focusPostfixButton, | ||
focusPrefixButton, | ||
inputRef, | ||
prefixButtons.length, | ||
readonly, | ||
setInitialTabIndices, | ||
]); | ||
const handlePrefixButtonKeyDown = useCallback((index) => event => { | ||
if (event.key === 'ArrowRight') { | ||
const nextIndex = findVisiblePrefixButton(index, event.key); | ||
if (index === nextIndex) { | ||
event.preventDefault(); | ||
focusInput(); | ||
if (readonly) { | ||
runAfterRerender(() => selectAll(inputRef.current)); | ||
} | ||
} | ||
else { | ||
focusPrefixButton(nextIndex); | ||
} | ||
} | ||
if (event.key === 'ArrowLeft') { | ||
const nextIndex = findVisibleButton(index, event.key); | ||
if (index <= prefixButtons.length - 1) { | ||
focusPrefixButton(findVisiblePrefixButton(index, event.key)); | ||
} | ||
} | ||
if (submitKeys.includes(event.key)) { | ||
runAfterRerender(() => setInitialTabIndices()); | ||
} | ||
buttonKeyDownEventHandler === null || buttonKeyDownEventHandler === void 0 ? void 0 : buttonKeyDownEventHandler(event); | ||
}, [ | ||
buttonKeyDownEventHandler, | ||
prefixButtons.length, | ||
findVisiblePrefixButton, | ||
focusPrefixButton, | ||
focusInput, | ||
inputRef, | ||
readonly, | ||
setInitialTabIndices, | ||
submitKeys, | ||
]); | ||
const handlePostfixButtonKeyDown = useCallback((index) => event => { | ||
if (event.key === 'ArrowLeft') { | ||
const nextIndex = findVisiblePostfixButton(index, event.key); | ||
if (index === nextIndex) { | ||
@@ -61,8 +131,8 @@ event.preventDefault(); | ||
else { | ||
focusButton(nextIndex); | ||
focusPostfixButton(nextIndex); | ||
} | ||
} | ||
if (event.key === 'ArrowRight') { | ||
if (index <= buttons.length - 1) { | ||
focusButton(findVisibleButton(index, event.key)); | ||
if (index <= postfixButtons.length - 1) { | ||
focusPostfixButton(findVisiblePostfixButton(index, event.key)); | ||
} | ||
@@ -76,5 +146,5 @@ } | ||
buttonKeyDownEventHandler, | ||
buttons.length, | ||
findVisibleButton, | ||
focusButton, | ||
postfixButtons.length, | ||
findVisiblePostfixButton, | ||
focusPostfixButton, | ||
focusInput, | ||
@@ -93,12 +163,31 @@ inputRef, | ||
setInitialTabIndices, | ||
buttons: (_jsx(_Fragment, { children: buttons.map(({ id, render, ref, show }, index) => show | ||
? render({ | ||
key: id, | ||
ref, | ||
tabIndex: buttonTabIndices[index], | ||
onKeyDown: handleButtonKeyDown(index), | ||
onClick: onButtonClick, | ||
}) | ||
: null) })), | ||
prefixButtons: prefixButtons.some(button => button.show) ? (_jsx(_Fragment, { children: prefixButtons.map((props, index) => { | ||
if (!props.show) { | ||
return null; | ||
} | ||
return props.active | ||
? props.render({ | ||
key: props.id, | ||
ref: props.ref, | ||
tabIndex: prefixButtonTabIndices[index], | ||
onKeyDown: handlePrefixButtonKeyDown(index), | ||
onClick: onButtonClick, | ||
}) | ||
: props.render({ key: props.id }); | ||
}) })) : undefined, | ||
postfixButtons: postfixButtons.some(button => button.show) ? (_jsx(_Fragment, { children: postfixButtons.map((props, index) => { | ||
if (!props.show) { | ||
return null; | ||
} | ||
return props.active | ||
? props.render({ | ||
key: props.id, | ||
ref: props.ref, | ||
tabIndex: postfixButtonTabIndices[index], | ||
onKeyDown: handlePostfixButtonKeyDown(index), | ||
onClick: onButtonClick, | ||
}) | ||
: props.render({ key: props.id }); | ||
}) })) : undefined, | ||
}; | ||
} |
@@ -14,2 +14,3 @@ import { jsx as _jsx } from "react/jsx-runtime"; | ||
id: 'clear', | ||
active: true, | ||
ref: clearButtonRef, | ||
@@ -16,0 +17,0 @@ show: showClearButton, |
@@ -7,3 +7,3 @@ { | ||
"title": "Input Private", | ||
"version": "3.2.1", | ||
"version": "4.0.0", | ||
"sideEffects": [ | ||
@@ -40,3 +40,3 @@ "*.css", | ||
}, | ||
"gitHead": "f9212fe04b0dbba9bedea1bb3a034ab612287841" | ||
"gitHead": "36a86f3bf30c115e8c6b49f6c8d7a2d2ffb86ff1" | ||
} |
@@ -80,2 +80,6 @@ # Input Private | ||
Откладывает колбек на следующий цикл EventLoop | ||
## isCursorInTheBeginning | ||
`helper` | ||
Проверяет находится ли курсор в начале поля ввода | ||
## isCursorInTheEnd | ||
@@ -82,0 +86,0 @@ `helper` |
@@ -27,2 +27,10 @@ /** | ||
/** | ||
* Проверяет находится ли курсор в начале поля ввода | ||
* @function helper | ||
*/ | ||
export function isCursorInTheBeginning(input: HTMLInputElement | HTMLTextAreaElement | null) { | ||
return input?.selectionStart === 0; | ||
} | ||
/** | ||
* Проверяет находится ли курсор в конце поля ввода | ||
@@ -29,0 +37,0 @@ * @function helper |
import { KeyboardEventHandler, MouseEventHandler, ReactElement, RefObject } from 'react'; | ||
type RenderButtonProps = { | ||
type RenderActiveButtonProps = { | ||
key: string; | ||
@@ -11,7 +11,21 @@ ref: RefObject<HTMLButtonElement>; | ||
export type ButtonProps = { | ||
type RenderInactiveButtonProps = { | ||
key: string; | ||
}; | ||
export type InactiveItem = { | ||
active: false; | ||
id: string; | ||
render(props: RenderInactiveButtonProps): ReactElement; | ||
show: boolean; | ||
}; | ||
export type ActiveItem = { | ||
active: true; | ||
id: string; | ||
ref: RefObject<HTMLButtonElement>; | ||
show: boolean; | ||
render(props: RenderButtonProps): ReactElement; | ||
render(props: RenderActiveButtonProps): ReactElement; | ||
}; | ||
export type ButtonProps = InactiveItem | ActiveItem; |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
69712
1158
91