@acusti/input-text
Advanced tools
Comparing version 1.5.3 to 1.6.0
@@ -60,5 +60,3 @@ import * as React from 'react'; | ||
}; | ||
declare const _default: React.ForwardRefExoticComponent< | ||
Props & React.RefAttributes<HTMLInputElement> | ||
>; | ||
declare const _default: React.ForwardRefExoticComponent<Props & React.RefAttributes<HTMLInputElement>>; | ||
export default _default; |
import * as React from 'react'; | ||
const { useCallback, useEffect, useImperativeHandle, useRef, useState } = React; | ||
export default React.forwardRef(function InputText( | ||
{ | ||
autoCapitalize, | ||
autoComplete, | ||
autoFocus, | ||
className, | ||
disabled, | ||
doubleClickToEdit, | ||
enterKeyHint, | ||
form, | ||
initialValue, | ||
list, | ||
max, | ||
maxHeight = Infinity, | ||
maxLength, | ||
min, | ||
minLength, | ||
multiLine, | ||
multiple, | ||
name, | ||
onBlur, | ||
onChange, | ||
onFocus, | ||
onKeyDown, | ||
onKeyUp, | ||
pattern, | ||
placeholder, | ||
readOnly, | ||
required, | ||
selectTextOnFocus, | ||
size, | ||
style, | ||
step, | ||
submitOnEnter, | ||
tabIndex, | ||
title, | ||
type = 'text', | ||
}, | ||
ref, | ||
) { | ||
export default React.forwardRef(function InputText({ autoCapitalize, autoComplete, autoFocus, className, disabled, doubleClickToEdit, enterKeyHint, form, initialValue, list, max, maxHeight = Infinity, maxLength, min, minLength, multiLine, multiple, name, onBlur, onChange, onFocus, onKeyDown, onKeyUp, pattern, placeholder, readOnly, required, selectTextOnFocus, size, style, step, submitOnEnter, tabIndex, title, type = 'text', }, ref) { | ||
const inputRef = useRef(null); | ||
useImperativeHandle(ref, () => inputRef.current); | ||
const [inputElement, _setInputElement] = useState(null); | ||
const autoFocusRef = useRef(autoFocus); | ||
autoFocusRef.current = autoFocus; | ||
const setInputElement = useCallback((element) => { | ||
inputRef.current = element; | ||
_setInputElement(element); | ||
if (element && autoFocusRef.current) { | ||
element.focus(); | ||
} | ||
}, []); | ||
@@ -55,137 +21,105 @@ if (inputRef.current && autoFocus && !inputRef.current.autofocus) { | ||
useEffect(() => { | ||
if (!inputRef.current) return; | ||
inputRef.current.value = | ||
initialValue !== null && initialValue !== void 0 ? initialValue : ''; | ||
if (!inputRef.current) | ||
return; | ||
inputRef.current.value = initialValue !== null && initialValue !== void 0 ? initialValue : ''; | ||
}, [initialValue]); | ||
const [readOnlyState, setReadOnlyState] = useState( | ||
readOnly !== null && readOnly !== void 0 ? readOnly : doubleClickToEdit, | ||
); | ||
const [readOnlyState, setReadOnlyState] = useState(readOnly !== null && readOnly !== void 0 ? readOnly : doubleClickToEdit); | ||
const isInitialSelectionRef = useRef(true); | ||
const startEditing = useCallback(() => { | ||
if (!doubleClickToEdit) return; | ||
if (!doubleClickToEdit) | ||
return; | ||
setReadOnlyState(false); | ||
}, [doubleClickToEdit]); | ||
const handleBlur = useCallback( | ||
(event) => { | ||
if (onBlur) onBlur(event); | ||
if (doubleClickToEdit) { | ||
setReadOnlyState(true); | ||
} | ||
if (!selectTextOnFocus) return; | ||
setInputElement(event.currentTarget); | ||
// When input loses focus, reset isInitialSelection to true for next onSelect event | ||
isInitialSelectionRef.current = true; | ||
}, | ||
[doubleClickToEdit, onBlur, selectTextOnFocus, setInputElement], | ||
); | ||
const setInputHeight = useCallback(() => { | ||
if (!inputElement) return; | ||
if (!inputElement) | ||
return; | ||
if (inputElement.style.height) { | ||
inputElement.style.height = ''; | ||
} | ||
if (!multiLine) return; | ||
const height = Math.min( | ||
inputElement.scrollHeight, | ||
typeof maxHeight === 'string' ? parseFloat(maxHeight) : maxHeight, | ||
); | ||
inputElement.style.height = `${height}px`; | ||
if (!multiLine) | ||
return; | ||
const height = Math.min(inputElement.scrollHeight, typeof maxHeight === 'string' ? parseFloat(maxHeight) : maxHeight); | ||
if (height) { | ||
inputElement.style.height = `${height}px`; | ||
} | ||
}, [inputElement, maxHeight, multiLine]); | ||
// Initialize input height in useEffect | ||
useEffect(setInputHeight, [setInputHeight]); | ||
const handleFocus = useCallback((event) => { | ||
if (onFocus) | ||
onFocus(event); | ||
if (multiLine) | ||
setInputHeight(); | ||
}, [multiLine, onFocus, setInputHeight]); | ||
const handleBlur = useCallback((event) => { | ||
if (onBlur) | ||
onBlur(event); | ||
if (doubleClickToEdit) { | ||
setReadOnlyState(true); | ||
} | ||
if (!selectTextOnFocus) | ||
return; | ||
setInputElement(event.currentTarget); | ||
// When input loses focus, reset isInitialSelection to true for next onSelect event | ||
isInitialSelectionRef.current = true; | ||
}, [doubleClickToEdit, onBlur, selectTextOnFocus, setInputElement]); | ||
// NOTE Selecting the contents of the input onFocus makes for the best UX, | ||
// but it doesn’t work in Safari, so we use the initial onSelect event instead | ||
const handleSelect = useCallback( | ||
(event) => { | ||
if (!selectTextOnFocus) return; | ||
const input = event.currentTarget; | ||
setInputElement(input); | ||
// Do nothing if this isn’t the initial select-on-focus event | ||
if (!isInitialSelectionRef.current) return; | ||
// This is the initial select-on-focus event, so reset isInitialSelection to false | ||
isInitialSelectionRef.current = false; | ||
// Do nothing if input has no value | ||
if (!input.value) return; | ||
// Do nothing if input is no longer the document’s activeElement | ||
if (input.ownerDocument.activeElement !== input) return; | ||
// Do nothing if input’s contents are already selected | ||
const valueLength = input.value.length; | ||
if (input.selectionStart === 0 && input.selectionEnd === valueLength) return; | ||
input.selectionStart = 0; | ||
input.selectionEnd = valueLength; | ||
}, | ||
[selectTextOnFocus, setInputElement], | ||
); | ||
const handleKeyDown = useCallback( | ||
(event) => { | ||
if (onKeyDown) onKeyDown(event); | ||
if ( | ||
multiLine && | ||
submitOnEnter && | ||
event.key === 'Enter' && | ||
!event.shiftKey && | ||
!event.altKey && | ||
!event.ctrlKey | ||
) { | ||
event.preventDefault(); | ||
const form = event.currentTarget.closest('form'); | ||
if (form) { | ||
form.requestSubmit(); | ||
} else { | ||
// if no form to submit, trigger input blur | ||
event.currentTarget.blur(); | ||
const handleSelect = useCallback((event) => { | ||
if (!selectTextOnFocus) | ||
return; | ||
const input = event.currentTarget; | ||
setInputElement(input); | ||
// Do nothing if this isn’t the initial select-on-focus event | ||
if (!isInitialSelectionRef.current) | ||
return; | ||
// This is the initial select-on-focus event, so reset isInitialSelection to false | ||
isInitialSelectionRef.current = false; | ||
// Do nothing if input has no value | ||
if (!input.value) | ||
return; | ||
// Do nothing if input is no longer the document’s activeElement | ||
if (input.ownerDocument.activeElement !== input) | ||
return; | ||
// Do nothing if input’s contents are already selected | ||
const valueLength = input.value.length; | ||
if (input.selectionStart === 0 && input.selectionEnd === valueLength) | ||
return; | ||
input.selectionStart = 0; | ||
input.selectionEnd = valueLength; | ||
}, [selectTextOnFocus, setInputElement]); | ||
const handleKeyDown = useCallback((event) => { | ||
if (onKeyDown) | ||
onKeyDown(event); | ||
if (multiLine && | ||
submitOnEnter && | ||
event.key === 'Enter' && | ||
!event.shiftKey && | ||
!event.altKey && | ||
!event.ctrlKey) { | ||
event.preventDefault(); | ||
const form = event.currentTarget.closest('form'); | ||
if (form) { | ||
form.requestSubmit(); | ||
} | ||
else { | ||
// if no form to submit, trigger input blur | ||
event.currentTarget.blur(); | ||
} | ||
} | ||
else if (doubleClickToEdit && inputRef.current) { | ||
if (readOnlyState) { | ||
if (event.key === 'Enter') { | ||
setReadOnlyState(false); | ||
} | ||
} else if (doubleClickToEdit && inputRef.current) { | ||
if (readOnlyState) { | ||
if (event.key === 'Enter') { | ||
setReadOnlyState(false); | ||
} | ||
} else if (event.key === 'Enter' || event.key === 'Escape') { | ||
inputRef.current.blur(); | ||
} | ||
} | ||
}, | ||
[doubleClickToEdit, multiLine, onKeyDown, readOnlyState, submitOnEnter], | ||
); | ||
else if (event.key === 'Enter' || event.key === 'Escape') { | ||
inputRef.current.blur(); | ||
} | ||
} | ||
}, [doubleClickToEdit, multiLine, onKeyDown, readOnlyState, submitOnEnter]); | ||
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-assignment | ||
const Element = multiLine ? 'textarea' : 'input'; | ||
return React.createElement( | ||
Element, | ||
Object.assign( | ||
{ | ||
autoCapitalize: autoCapitalize, | ||
autoComplete: autoComplete, | ||
autoFocus: autoFocus, | ||
className: className, | ||
defaultValue: | ||
initialValue !== null && initialValue !== void 0 ? initialValue : '', | ||
disabled: disabled, | ||
enterKeyHint: enterKeyHint, | ||
form: form, | ||
list: list, | ||
maxLength: maxLength, | ||
minLength: minLength, | ||
multiple: multiple, | ||
name: name, | ||
onBlur: handleBlur, | ||
onChange: onChange, | ||
onDoubleClick: startEditing, | ||
onFocus: onFocus, | ||
onKeyDown: handleKeyDown, | ||
onKeyUp: onKeyUp, | ||
onSelect: handleSelect, | ||
pattern: pattern, | ||
placeholder: placeholder, | ||
readOnly: readOnlyState, | ||
ref: setInputElement, | ||
required: required, | ||
size: size, | ||
style: style, | ||
tabIndex: tabIndex, | ||
title: title, | ||
type: type, | ||
}, | ||
multiLine ? { onInput: setInputHeight, rows: 1 } : { max, min, step }, | ||
), | ||
); | ||
const Element = (multiLine ? 'textarea' : 'input'); | ||
return (React.createElement(Element, Object.assign({ autoCapitalize: autoCapitalize, autoComplete: autoComplete, className: className, defaultValue: initialValue !== null && initialValue !== void 0 ? initialValue : '', disabled: disabled, enterKeyHint: enterKeyHint, form: form, list: list, maxLength: maxLength, minLength: minLength, multiple: multiple, name: name, onBlur: handleBlur, onChange: onChange, onDoubleClick: startEditing, onFocus: handleFocus, onKeyDown: handleKeyDown, onKeyUp: onKeyUp, onSelect: handleSelect, pattern: pattern, placeholder: placeholder, readOnly: readOnlyState, ref: setInputElement, required: required, size: size, style: style, tabIndex: tabIndex, title: title, type: type }, (multiLine ? { onInput: setInputHeight, rows: 1 } : { max, min, step })))); | ||
}); | ||
//# sourceMappingURL=InputText.js.map | ||
//# sourceMappingURL=InputText.js.map |
@@ -10,3 +10,3 @@ // @vitest-environment happy-dom | ||
it('renders a text input with the given props.initialValue', () => { | ||
render(React.createElement(InputText, { initialValue: 'foo Bar' })); | ||
render(React.createElement(InputText, { initialValue: "foo Bar" })); | ||
// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion | ||
@@ -18,5 +18,3 @@ const input = screen.getByRole('textbox'); | ||
const user = userEvent.setup(); | ||
const { rerender } = render( | ||
React.createElement(InputText, { initialValue: 'foo Bar' }), | ||
); | ||
const { rerender } = render(React.createElement(InputText, { initialValue: "foo Bar" })); | ||
// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion | ||
@@ -28,6 +26,6 @@ const input = screen.getByRole('textbox'); | ||
// re-render with same initialValue, value state shouldn’t change | ||
rerender(React.createElement(InputText, { initialValue: 'foo Bar' })); | ||
rerender(React.createElement(InputText, { initialValue: "foo Bar" })); | ||
expect(input.value).toBe('foo bar'); | ||
// re-render with different initialValue, value state should reset | ||
rerender(React.createElement(InputText, { initialValue: 'foo Bar ' })); | ||
rerender(React.createElement(InputText, { initialValue: "foo Bar " })); | ||
expect(input.value).toBe('foo Bar '); | ||
@@ -37,7 +35,4 @@ }); | ||
const user = userEvent.setup(); | ||
const longText = | ||
'Lorem ipsum dolor sit amet. Consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi'; | ||
render( | ||
React.createElement(InputText, { initialValue: longText, multiLine: true }), | ||
); | ||
const longText = 'Lorem ipsum dolor sit amet. Consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi'; | ||
render(React.createElement(InputText, { initialValue: longText, multiLine: true })); | ||
// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion | ||
@@ -50,2 +45,2 @@ const textarea = screen.getByRole('textbox'); | ||
}); | ||
//# sourceMappingURL=InputText.test.js.map | ||
//# sourceMappingURL=InputText.test.js.map |
{ | ||
"name": "@acusti/input-text", | ||
"version": "1.5.3", | ||
"version": "1.6.0", | ||
"type": "module", | ||
@@ -5,0 +5,0 @@ "sideEffects": false, |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
38376
525