11import { pick } from 'ramda' ;
22import React , {
3+ InputHTMLAttributes ,
34 KeyboardEvent ,
45 KeyboardEventHandler ,
56 useCallback ,
67 useEffect ,
78 useRef ,
89 useState ,
9- useId ,
1010} from 'react' ;
11+ import uniqid from 'uniqid' ;
1112import fastIsNumeric from 'fast-isnumeric' ;
1213import LoadingElement from '../utils/_LoadingElement' ;
1314import './css/input.css' ;
@@ -260,7 +261,7 @@ type InputProps = {
260261 type ?: HTMLInputTypes ;
261262} ;
262263
263- const inputProps : ( keyof InputProps ) [ ] = [
264+ const inputProps = [
264265 'type' ,
265266 'placeholder' ,
266267 'inputMode' ,
@@ -279,20 +280,12 @@ const inputProps: (keyof InputProps)[] = [
279280 'maxLength' ,
280281 'pattern' ,
281282 'size' ,
282- ] ;
283-
284- const defaultProps : Partial < InputProps > = {
285- type : HTMLInputTypes . text ,
286- inputMode : 'verbatim' ,
287- n_blur : 0 ,
288- n_blur_timestamp : - 1 ,
289- n_submit : 0 ,
290- n_submit_timestamp : - 1 ,
291- debounce : false ,
292- step : 'any' ,
293- persisted_props : [ PersistedProps . value ] ,
294- persistence_type : PersistenceTypes . local ,
295- } ;
283+ ] as const ;
284+
285+ type HTMLInputProps = Extract <
286+ ( typeof inputProps ) [ number ] ,
287+ keyof InputHTMLAttributes < HTMLInputElement >
288+ > ;
296289
297290/**
298291 * A basic HTML input control for entering text, numbers, or passwords.
@@ -302,38 +295,25 @@ const defaultProps: Partial<InputProps> = {
302295 * are also supported through separate components.
303296 */
304297function Input ( {
305- type = defaultProps . type ,
306- inputMode = defaultProps . inputMode ,
307- n_blur = defaultProps . n_blur ,
308- n_blur_timestamp = defaultProps . n_blur_timestamp ,
309- n_submit = defaultProps . n_submit ,
310- n_submit_timestamp = defaultProps . n_submit_timestamp ,
311- debounce = defaultProps . debounce ,
312- step = defaultProps . step ,
313- persisted_props = defaultProps . persisted_props ,
314- persistence_type = defaultProps . persistence_type ,
315- ...rest
298+ type = HTMLInputTypes . text ,
299+ inputMode = 'verbatim' ,
300+ n_blur = 0 ,
301+ n_blur_timestamp = - 1 ,
302+ n_submit = 0 ,
303+ n_submit_timestamp = - 1 ,
304+ debounce = false ,
305+ step = 'any' ,
306+ persisted_props = [ PersistedProps . value ] ,
307+ persistence_type = PersistenceTypes . local ,
308+ disabled,
309+ ...props
316310} : InputProps ) {
317- const props = {
318- type,
319- inputMode,
320- n_blur,
321- n_blur_timestamp,
322- n_submit,
323- n_submit_timestamp,
324- debounce,
325- step,
326- persisted_props,
327- persistence_type,
328- ...rest ,
329- } ;
330311 const input = useRef ( document . createElement ( 'input' ) ) ;
331312 const [ value , setValue ] = useState < InputProps [ 'value' ] > ( props . value ) ;
332313 const [ pendingEvent , setPendingEvent ] = useState < number > ( ) ;
333- const inputId = useId ( ) ;
314+ const inputId = useState ( ( ) => uniqid ( 'input-' ) ) [ 0 ] ;
334315
335- const valprops =
336- props . type === HTMLInputTypes . number ? { } : { value : value ?? '' } ;
316+ const valprops = type === HTMLInputTypes . number ? { } : { value : value ?? '' } ;
337317 let { className} = props ;
338318 className = 'dash-input' + ( className ? ` ${ className } ` : '' ) ;
339319
@@ -350,49 +330,46 @@ function Input({
350330 [ props . setProps ]
351331 ) ;
352332
353- const onEvent = useCallback ( ( ) => {
333+ const onEvent = ( ) => {
354334 const { value : inputValue } = input . current ;
355- const { setProps} = props ;
356335 const valueAsNumber = convert ( inputValue ) ;
357- if ( props . type === HTMLInputTypes . number ) {
336+ if ( type === HTMLInputTypes . number ) {
358337 setPropValue ( props . value , valueAsNumber ?? value ) ;
359338 } else {
360339 const propValue =
361340 inputValue === '' && props . value === undefined
362341 ? undefined
363342 : inputValue ;
364- setProps ( { value : propValue } ) ;
343+ props . setProps ( { value : propValue } ) ;
365344 }
366345 setPendingEvent ( undefined ) ;
367- } , [ props . setProps ] ) ;
346+ } ;
368347
369348 const onBlur = useCallback ( ( ) => {
370- const { debounce, n_blur, setProps} = props ;
371- setProps ( {
349+ props . setProps ( {
372350 n_blur : ( n_blur ?? 0 ) + 1 ,
373351 n_blur_timestamp : Date . now ( ) ,
374352 } ) ;
375353 input . current . checkValidity ( ) ;
376354 return debounce === true && onEvent ( ) ;
377- } , [ props . setProps , props . n_blur , props . debounce ] ) ;
355+ } , [ n_blur , debounce ] ) ;
378356
379357 const onChange = useCallback ( ( ) => {
380358 const { value} = input . current ;
381359 setValue ( value ) ;
382- } , [ setValue ] ) ;
360+ } , [ ] ) ;
383361
384362 const onKeyPress : KeyboardEventHandler < HTMLInputElement > = useCallback (
385363 ( e : KeyboardEvent ) => {
386- const { setProps} = props ;
387364 if ( e . key === 'Enter' ) {
388- setProps ( {
389- n_submit : ( props . n_submit ?? 0 ) + 1 ,
365+ props . setProps ( {
366+ n_submit : ( n_submit ?? 0 ) + 1 ,
390367 n_submit_timestamp : Date . now ( ) ,
391368 } ) ;
392369 }
393- return props . debounce === true && e . key === 'Enter' && onEvent ( ) ;
370+ return debounce === true && e . key === 'Enter' && onEvent ( ) ;
394371 } ,
395- [ props . setProps , props . n_submit , props . debounce ]
372+ [ n_submit , debounce ]
396373 ) ;
397374
398375 const setInputValue = useCallback (
@@ -401,7 +378,11 @@ function Input({
401378 value = convert ( value ) ;
402379
403380 if ( ! isEquivalent ( base , value ) ) {
404- input . current . value = `${ value } ` ?? '' ;
381+ if ( typeof value === 'undefined' ) {
382+ input . current . value = '' ;
383+ } else {
384+ input . current . value = `${ value } ` ;
385+ }
405386 }
406387 } ,
407388 [ ]
@@ -425,11 +406,11 @@ function Input({
425406 const handleStepperClick = useCallback (
426407 ( direction : 'increment' | 'decrement' ) => {
427408 const currentValue = parseFloat ( input . current . value ) || 0 ;
428- const step = parseFloat ( props . step as string ) || 1 ;
409+ const stepAsNum = parseFloat ( step as string ) || 1 ;
429410 const newValue =
430411 direction === 'increment'
431- ? currentValue + step
432- : currentValue - step ;
412+ ? currentValue + stepAsNum
413+ : currentValue - stepAsNum ;
433414
434415 // Apply min/max constraints
435416 let constrainedValue = newValue ;
@@ -450,7 +431,7 @@ function Input({
450431 setValue ( constrainedValue . toString ( ) ) ;
451432 onEvent ( ) ;
452433 } ,
453- [ props . step , props . min , props . max , onEvent ]
434+ [ step , props . min , props . max , onEvent ]
454435 ) ;
455436
456437 useEffect ( ( ) => {
@@ -460,18 +441,17 @@ function Input({
460441 }
461442 const valueAsNumber = convert ( value ) ;
462443 setInputValue ( valueAsNumber ?? value , props . value ) ;
463- if ( props . type !== HTMLInputTypes . number ) {
444+ if ( type !== HTMLInputTypes . number ) {
464445 setValue ( props . value ) ;
465446 }
466- } , [ props . value , props . type , pendingEvent ] ) ;
447+ } , [ props . value , type , pendingEvent ] ) ;
467448
468449 useEffect ( ( ) => {
469450 // Skip this effect if the value change came from props update (not user input)
470451 if ( value === props . value ) {
471452 return ;
472453 }
473454
474- const { debounce, type} = props ;
475455 const { selectionStart : cursorPosition } = input . current ;
476456 if ( debounce ) {
477457 if ( typeof debounce === 'number' && Number . isFinite ( debounce ) ) {
@@ -488,26 +468,35 @@ function Input({
488468 } else {
489469 onEvent ( ) ;
490470 }
491- } , [ value , props . debounce , props . type ] ) ;
471+ } , [ value , debounce , type ] ) ;
492472
493- const pickedInputs = pick ( inputProps , props ) ;
473+ const disabledAsBool = [ true , 'disabled' , 'DISABLED' ] . includes (
474+ disabled ?? false
475+ ) ;
476+
477+ const pickedInputs = pick ( inputProps , {
478+ ...props ,
479+ type,
480+ inputMode,
481+ step,
482+ disabled : disabledAsBool ,
483+ } ) as Pick < InputHTMLAttributes < HTMLInputElement > , HTMLInputProps > ;
494484
495- const isNumberInput = props . type === HTMLInputTypes . number ;
485+ const isNumberInput = type === HTMLInputTypes . number ;
496486 const currentNumericValue = convert ( input . current . value || '0' ) ;
497487 const minValue = convert ( props . min ) ;
498488 const maxValue = convert ( props . max ) ;
499- const disabled = [ true , 'disabled' , 'DISABLED' ] . includes (
500- props . disabled ?? false
501- ) ;
502- const isDecrementDisabled = disabled || currentNumericValue <= minValue ;
503- const isIncrementDisabled = disabled || currentNumericValue >= maxValue ;
489+ const isDecrementDisabled =
490+ disabledAsBool || currentNumericValue <= minValue ;
491+ const isIncrementDisabled =
492+ disabledAsBool || currentNumericValue >= maxValue ;
504493
505494 return (
506495 < LoadingElement >
507496 { loadingProps => (
508497 < div
509498 className = { `dash-input-container ${ className } ${
510- props . type === HTMLInputTypes . hidden
499+ type === HTMLInputTypes . hidden
511500 ? ' dash-input-hidden'
512501 : ''
513502 } `. trim ( ) }
@@ -523,7 +512,7 @@ function Input({
523512 { ...valprops }
524513 { ...pickedInputs }
525514 { ...loadingProps }
526- disabled = { disabled }
515+ disabled = { disabledAsBool }
527516 />
528517 { isNumberInput && (
529518 < button
@@ -555,9 +544,9 @@ function Input({
555544 ) ;
556545}
557546
558- Input . dashPersistence = pick (
559- [ ' persisted_props' , 'persistence_type' ] ,
560- defaultProps
561- ) ;
547+ Input . dashPersistence = {
548+ persisted_props : [ PersistedProps . value ] ,
549+ persistence_type : PersistenceTypes . local ,
550+ } ;
562551
563552export default Input ;
0 commit comments