Skip to content

Commit f4b2add

Browse files
committed
Merge remote-tracking branch 'origin/v4' into feature/dcc-refactor-slider
2 parents 5c3d7b7 + 2444c36 commit f4b2add

File tree

19 files changed

+408
-12368
lines changed

19 files changed

+408
-12368
lines changed

CHANGELOG.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,17 @@
22
All notable changes to `dash` will be documented in this file.
33
This project adheres to [Semantic Versioning](https://semver.org/).
44

5+
## [UNRELEASED]
6+
7+
## Added
8+
- [#3395](https://github.com/plotly/dash/pull/3396) Add position argument to hooks.devtool
9+
- [#3403](https://github.com/plotly/dash/pull/3403) Add app_context to get_app, allowing to get the current app in routes.
10+
- [#3407](https://github.com/plotly/dash/pull/3407) Add `hidden` to callback arguments, hiding the callback from appearing in the devtool callback graph.
11+
12+
## Fixed
13+
- [#3395](https://github.com/plotly/dash/pull/3395) Fix Components added through set_props() cannot trigger related callback functions. Fix [#3316](https://github.com/plotly/dash/issues/3316)
14+
- [#3397](https://github.com/plotly/dash/pull/3397) Add optional callbacks, suppressing callback warning for missing component ids for a single callback.
15+
516
## [3.2.0] - 2025-07-31
617

718
## Added

components/dash-core-components/package-lock.json

Lines changed: 8 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

components/dash-core-components/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@
8181
"@types/ramda": "^0.31.0",
8282
"@types/react": "^16.14.8",
8383
"@types/react-dom": "^16.9.13",
84+
"@types/uniqid": "^5.3.4",
8485
"@typescript-eslint/eslint-plugin": "^5.59.7",
8586
"@typescript-eslint/parser": "^5.59.7",
8687
"babel-loader": "^9.2.1",

components/dash-core-components/src/components/Input.tsx

Lines changed: 68 additions & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
11
import {pick} from 'ramda';
22
import 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';
1112
import fastIsNumeric from 'fast-isnumeric';
1213
import LoadingElement from '../utils/_LoadingElement';
1314
import './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
*/
304297
function 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

563552
export default Input;

0 commit comments

Comments
 (0)