@box/blueprint-web 17.0.0 → 17.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/lib-esm/components.css +16 -15
- package/dist/lib-esm/data-table/data-table.module.js +1 -1
- package/dist/lib-esm/index.css +16 -15
- package/dist/lib-esm/number-field/constants.d.ts +3 -3
- package/dist/lib-esm/number-field/constants.js +3 -3
- package/dist/lib-esm/number-field/number-field-control.d.ts +7 -4
- package/dist/lib-esm/number-field/number-field-control.js +21 -5
- package/dist/lib-esm/number-field/number-field.d.ts +5 -0
- package/dist/lib-esm/number-field/number-field.js +31 -12
- package/dist/lib-esm/number-field/stepper-button.d.ts +0 -4
- package/dist/lib-esm/number-field/stepper-button.js +8 -12
- package/dist/lib-esm/number-field/types.d.ts +47 -13
- package/package.json +3 -3
|
@@ -6950,55 +6950,56 @@
|
|
|
6950
6950
|
overflow:auto;
|
|
6951
6951
|
}
|
|
6952
6952
|
|
|
6953
|
-
.bp_data_table_module_dataTableWrapper--
|
|
6953
|
+
.bp_data_table_module_dataTableWrapper--c7392{
|
|
6954
6954
|
--border-radius-datatable:0;
|
|
6955
6955
|
--outline-offset-focus-and-selected-row-datatable:-0.25rem;
|
|
6956
6956
|
--outline-select-color-datatable:#0000;
|
|
6957
6957
|
--sticky-element-z-index:2;
|
|
6958
6958
|
height:100%;
|
|
6959
6959
|
overflow:auto;
|
|
6960
|
+
padding-block-end:var(--bp-space-020, .5rem);
|
|
6960
6961
|
scroll-padding:60px 100px;
|
|
6961
6962
|
width:100%;
|
|
6962
6963
|
}
|
|
6963
|
-
.bp_data_table_module_dataTableWrapper--
|
|
6964
|
+
.bp_data_table_module_dataTableWrapper--c7392 [data-modern=false]{
|
|
6964
6965
|
--border-radius-hover-datatable:var(--radius-4);
|
|
6965
6966
|
}
|
|
6966
|
-
.bp_data_table_module_dataTableWrapper--
|
|
6967
|
+
.bp_data_table_module_dataTableWrapper--c7392 [data-modern=true]{
|
|
6967
6968
|
--border-radius-hover-datatable:var(--bp-radius-08, 1rem);
|
|
6968
6969
|
}
|
|
6969
|
-
.bp_data_table_module_dataTableWrapper--
|
|
6970
|
+
.bp_data_table_module_dataTableWrapper--c7392 table tbody > tr[data-hovered]{
|
|
6970
6971
|
--list-item-border-radius:var(--border-radius-hover-datatable);
|
|
6971
6972
|
--list-item-row-border-radius:var(--list-item-border-radius);
|
|
6972
6973
|
--list-item-first-column-item-radius:var(--list-item-border-radius) 0 0 var(--list-item-border-radius);
|
|
6973
6974
|
--list-item-last-column-item-radius:0 var(--list-item-border-radius) var(--list-item-border-radius) 0;
|
|
6974
6975
|
}
|
|
6975
|
-
.bp_data_table_module_dataTableWrapper--
|
|
6976
|
+
.bp_data_table_module_dataTableWrapper--c7392.bp_data_table_module_isScrolledX--c7392 table tbody > tr[data-hovered][data-focus-visible]{
|
|
6976
6977
|
--list-item-border-radius:0;
|
|
6977
6978
|
--list-item-row-border-radius:0;
|
|
6978
6979
|
--list-item-first-column-item-radius:0;
|
|
6979
6980
|
--list-item-last-column-item-radius:0;
|
|
6980
6981
|
}
|
|
6981
|
-
.bp_data_table_module_dataTableWrapper--
|
|
6982
|
+
.bp_data_table_module_dataTableWrapper--c7392 table > thead > tr{
|
|
6982
6983
|
background:var(--gray-white);
|
|
6983
6984
|
position:sticky;
|
|
6984
6985
|
top:0;
|
|
6985
6986
|
z-index:var(--sticky-element-z-index);
|
|
6986
6987
|
}
|
|
6987
|
-
.bp_data_table_module_dataTableWrapper--
|
|
6988
|
+
.bp_data_table_module_dataTableWrapper--c7392 table > thead > tr th:first-child{
|
|
6988
6989
|
background:inherit;
|
|
6989
6990
|
height:100%;
|
|
6990
6991
|
left:0;
|
|
6991
6992
|
position:sticky;
|
|
6992
6993
|
z-index:var(--sticky-element-z-index);
|
|
6993
6994
|
}
|
|
6994
|
-
.bp_data_table_module_dataTableWrapper--
|
|
6995
|
+
.bp_data_table_module_dataTableWrapper--c7392 table > thead > tr th:first-child[data-focus-visible]{
|
|
6995
6996
|
position:sticky;
|
|
6996
6997
|
z-index:var(--sticky-element-z-index);
|
|
6997
6998
|
}
|
|
6998
|
-
.bp_data_table_module_dataTableWrapper--
|
|
6999
|
+
.bp_data_table_module_dataTableWrapper--c7392 table tbody > tr{
|
|
6999
7000
|
--is-row-focused:0;
|
|
7000
7001
|
}
|
|
7001
|
-
.bp_data_table_module_dataTableWrapper--
|
|
7002
|
+
.bp_data_table_module_dataTableWrapper--c7392 table tbody > tr > td [data-actionswrapper]::after{
|
|
7002
7003
|
background:inherit;
|
|
7003
7004
|
content:"";
|
|
7004
7005
|
display:block;
|
|
@@ -7008,18 +7009,18 @@
|
|
|
7008
7009
|
width:100%;
|
|
7009
7010
|
z-index:-1;
|
|
7010
7011
|
}
|
|
7011
|
-
.bp_data_table_module_dataTableWrapper--
|
|
7012
|
+
.bp_data_table_module_dataTableWrapper--c7392 table tbody > tr[data-focus-visible]{
|
|
7012
7013
|
--outline-offset-focus-cell:var(--outline-offset-focus-row);
|
|
7013
7014
|
--is-row-focused:1;
|
|
7014
7015
|
}
|
|
7015
|
-
.bp_data_table_module_dataTableWrapper--
|
|
7016
|
+
.bp_data_table_module_dataTableWrapper--c7392 table tbody > tr[data-focus-visible][aria-selected=true]{
|
|
7016
7017
|
--outline-offset-focus-cell:var(--outline-offset-focus-and-selected-row);
|
|
7017
7018
|
}
|
|
7018
7019
|
|
|
7019
|
-
.bp_data_table_module_isScrolledX--
|
|
7020
|
+
.bp_data_table_module_isScrolledX--c7392 table > thead > tr > th:first-child{
|
|
7020
7021
|
overflow:visible;
|
|
7021
7022
|
}
|
|
7022
|
-
.bp_data_table_module_isScrolledX--
|
|
7023
|
+
.bp_data_table_module_isScrolledX--c7392 table > thead > tr > th:first-child::after{
|
|
7023
7024
|
background:inherit;
|
|
7024
7025
|
border-right:1px solid var(--border-divider-border);
|
|
7025
7026
|
box-shadow:10px 0 7px -4px var(--item-background);
|
|
@@ -7033,7 +7034,7 @@
|
|
|
7033
7034
|
z-index:-1;
|
|
7034
7035
|
}
|
|
7035
7036
|
|
|
7036
|
-
.bp_data_table_module_hideActionWrapperRight--
|
|
7037
|
+
.bp_data_table_module_hideActionWrapperRight--c7392 table tbody > tr > td [data-actionswrapper]::after{
|
|
7037
7038
|
display:none;
|
|
7038
7039
|
}
|
|
7039
7040
|
.bp_list_checkbox_module_selection--711bf[data-modern=false]{
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
import '../index.css';
|
|
2
|
-
var styles = {"dataTableWrapper":"bp_data_table_module_dataTableWrapper--
|
|
2
|
+
var styles = {"dataTableWrapper":"bp_data_table_module_dataTableWrapper--c7392","isScrolledX":"bp_data_table_module_isScrolledX--c7392","hideActionWrapperRight":"bp_data_table_module_hideActionWrapperRight--c7392"};
|
|
3
3
|
|
|
4
4
|
export { styles as default };
|
package/dist/lib-esm/index.css
CHANGED
|
@@ -6950,55 +6950,56 @@
|
|
|
6950
6950
|
overflow:auto;
|
|
6951
6951
|
}
|
|
6952
6952
|
|
|
6953
|
-
.bp_data_table_module_dataTableWrapper--
|
|
6953
|
+
.bp_data_table_module_dataTableWrapper--c7392{
|
|
6954
6954
|
--border-radius-datatable:0;
|
|
6955
6955
|
--outline-offset-focus-and-selected-row-datatable:-0.25rem;
|
|
6956
6956
|
--outline-select-color-datatable:#0000;
|
|
6957
6957
|
--sticky-element-z-index:2;
|
|
6958
6958
|
height:100%;
|
|
6959
6959
|
overflow:auto;
|
|
6960
|
+
padding-block-end:var(--bp-space-020, .5rem);
|
|
6960
6961
|
scroll-padding:60px 100px;
|
|
6961
6962
|
width:100%;
|
|
6962
6963
|
}
|
|
6963
|
-
.bp_data_table_module_dataTableWrapper--
|
|
6964
|
+
.bp_data_table_module_dataTableWrapper--c7392 [data-modern=false]{
|
|
6964
6965
|
--border-radius-hover-datatable:var(--radius-4);
|
|
6965
6966
|
}
|
|
6966
|
-
.bp_data_table_module_dataTableWrapper--
|
|
6967
|
+
.bp_data_table_module_dataTableWrapper--c7392 [data-modern=true]{
|
|
6967
6968
|
--border-radius-hover-datatable:var(--bp-radius-08, 1rem);
|
|
6968
6969
|
}
|
|
6969
|
-
.bp_data_table_module_dataTableWrapper--
|
|
6970
|
+
.bp_data_table_module_dataTableWrapper--c7392 table tbody > tr[data-hovered]{
|
|
6970
6971
|
--list-item-border-radius:var(--border-radius-hover-datatable);
|
|
6971
6972
|
--list-item-row-border-radius:var(--list-item-border-radius);
|
|
6972
6973
|
--list-item-first-column-item-radius:var(--list-item-border-radius) 0 0 var(--list-item-border-radius);
|
|
6973
6974
|
--list-item-last-column-item-radius:0 var(--list-item-border-radius) var(--list-item-border-radius) 0;
|
|
6974
6975
|
}
|
|
6975
|
-
.bp_data_table_module_dataTableWrapper--
|
|
6976
|
+
.bp_data_table_module_dataTableWrapper--c7392.bp_data_table_module_isScrolledX--c7392 table tbody > tr[data-hovered][data-focus-visible]{
|
|
6976
6977
|
--list-item-border-radius:0;
|
|
6977
6978
|
--list-item-row-border-radius:0;
|
|
6978
6979
|
--list-item-first-column-item-radius:0;
|
|
6979
6980
|
--list-item-last-column-item-radius:0;
|
|
6980
6981
|
}
|
|
6981
|
-
.bp_data_table_module_dataTableWrapper--
|
|
6982
|
+
.bp_data_table_module_dataTableWrapper--c7392 table > thead > tr{
|
|
6982
6983
|
background:var(--gray-white);
|
|
6983
6984
|
position:sticky;
|
|
6984
6985
|
top:0;
|
|
6985
6986
|
z-index:var(--sticky-element-z-index);
|
|
6986
6987
|
}
|
|
6987
|
-
.bp_data_table_module_dataTableWrapper--
|
|
6988
|
+
.bp_data_table_module_dataTableWrapper--c7392 table > thead > tr th:first-child{
|
|
6988
6989
|
background:inherit;
|
|
6989
6990
|
height:100%;
|
|
6990
6991
|
left:0;
|
|
6991
6992
|
position:sticky;
|
|
6992
6993
|
z-index:var(--sticky-element-z-index);
|
|
6993
6994
|
}
|
|
6994
|
-
.bp_data_table_module_dataTableWrapper--
|
|
6995
|
+
.bp_data_table_module_dataTableWrapper--c7392 table > thead > tr th:first-child[data-focus-visible]{
|
|
6995
6996
|
position:sticky;
|
|
6996
6997
|
z-index:var(--sticky-element-z-index);
|
|
6997
6998
|
}
|
|
6998
|
-
.bp_data_table_module_dataTableWrapper--
|
|
6999
|
+
.bp_data_table_module_dataTableWrapper--c7392 table tbody > tr{
|
|
6999
7000
|
--is-row-focused:0;
|
|
7000
7001
|
}
|
|
7001
|
-
.bp_data_table_module_dataTableWrapper--
|
|
7002
|
+
.bp_data_table_module_dataTableWrapper--c7392 table tbody > tr > td [data-actionswrapper]::after{
|
|
7002
7003
|
background:inherit;
|
|
7003
7004
|
content:"";
|
|
7004
7005
|
display:block;
|
|
@@ -7008,18 +7009,18 @@
|
|
|
7008
7009
|
width:100%;
|
|
7009
7010
|
z-index:-1;
|
|
7010
7011
|
}
|
|
7011
|
-
.bp_data_table_module_dataTableWrapper--
|
|
7012
|
+
.bp_data_table_module_dataTableWrapper--c7392 table tbody > tr[data-focus-visible]{
|
|
7012
7013
|
--outline-offset-focus-cell:var(--outline-offset-focus-row);
|
|
7013
7014
|
--is-row-focused:1;
|
|
7014
7015
|
}
|
|
7015
|
-
.bp_data_table_module_dataTableWrapper--
|
|
7016
|
+
.bp_data_table_module_dataTableWrapper--c7392 table tbody > tr[data-focus-visible][aria-selected=true]{
|
|
7016
7017
|
--outline-offset-focus-cell:var(--outline-offset-focus-and-selected-row);
|
|
7017
7018
|
}
|
|
7018
7019
|
|
|
7019
|
-
.bp_data_table_module_isScrolledX--
|
|
7020
|
+
.bp_data_table_module_isScrolledX--c7392 table > thead > tr > th:first-child{
|
|
7020
7021
|
overflow:visible;
|
|
7021
7022
|
}
|
|
7022
|
-
.bp_data_table_module_isScrolledX--
|
|
7023
|
+
.bp_data_table_module_isScrolledX--c7392 table > thead > tr > th:first-child::after{
|
|
7023
7024
|
background:inherit;
|
|
7024
7025
|
border-right:1px solid var(--border-divider-border);
|
|
7025
7026
|
box-shadow:10px 0 7px -4px var(--item-background);
|
|
@@ -7033,7 +7034,7 @@
|
|
|
7033
7034
|
z-index:-1;
|
|
7034
7035
|
}
|
|
7035
7036
|
|
|
7036
|
-
.bp_data_table_module_hideActionWrapperRight--
|
|
7037
|
+
.bp_data_table_module_hideActionWrapperRight--c7392 table tbody > tr > td [data-actionswrapper]::after{
|
|
7037
7038
|
display:none;
|
|
7038
7039
|
}
|
|
7039
7040
|
.bp_list_checkbox_module_selection--711bf[data-modern=false]{
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
/**
|
|
2
|
-
declare const
|
|
1
|
+
/** How many digits wide the control is when `maxLength` is omitted and the field is unconstrained. */
|
|
2
|
+
declare const NUMBER_FIELD_DEFAULT_DIGITS = 3;
|
|
3
3
|
/** Non-negative integers only. Also governs what react-aria lets through, keeping the field digits-only. */
|
|
4
4
|
declare const NUMBER_FIELD_FORMAT_OPTIONS: Intl.NumberFormatOptions;
|
|
5
|
-
export {
|
|
5
|
+
export { NUMBER_FIELD_DEFAULT_DIGITS, NUMBER_FIELD_FORMAT_OPTIONS };
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
/**
|
|
2
|
-
const
|
|
1
|
+
/** How many digits wide the control is when `maxLength` is omitted and the field is unconstrained. */
|
|
2
|
+
const NUMBER_FIELD_DEFAULT_DIGITS = 3;
|
|
3
3
|
/** Non-negative integers only. Also governs what react-aria lets through, keeping the field digits-only. */
|
|
4
4
|
const NUMBER_FIELD_FORMAT_OPTIONS = {
|
|
5
5
|
maximumFractionDigits: 0,
|
|
6
6
|
useGrouping: false
|
|
7
7
|
};
|
|
8
8
|
|
|
9
|
-
export {
|
|
9
|
+
export { NUMBER_FIELD_DEFAULT_DIGITS, NUMBER_FIELD_FORMAT_OPTIONS };
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import { type ComponentPropsWithoutRef, type ForwardedRef } from 'react';
|
|
2
|
+
import { type NumberFieldProps } from './types';
|
|
2
3
|
type NumberFieldControlProps = {
|
|
3
|
-
ariaValueMax
|
|
4
|
-
ariaValueMin
|
|
4
|
+
ariaValueMax?: number;
|
|
5
|
+
ariaValueMin?: number;
|
|
5
6
|
decrementAriaLabel: string;
|
|
6
7
|
hasError: boolean;
|
|
7
8
|
incrementAriaLabel: string;
|
|
8
|
-
|
|
9
|
+
inputMaxLength?: number;
|
|
9
10
|
inputProps: ComponentPropsWithoutRef<'input'>;
|
|
10
11
|
inputRef: ForwardedRef<HTMLInputElement>;
|
|
11
12
|
isAnimationEnabled: boolean;
|
|
@@ -17,8 +18,10 @@ type NumberFieldControlProps = {
|
|
|
17
18
|
loadingAriaLabel?: string;
|
|
18
19
|
maxReachedTooltipContent?: string;
|
|
19
20
|
minReachedTooltipContent?: string;
|
|
21
|
+
onBlur?: NumberFieldProps['onBlur'];
|
|
22
|
+
onInputChange?: (rawValue: string) => void;
|
|
20
23
|
showDecrementTooltip: boolean;
|
|
21
24
|
showIncrementTooltip: boolean;
|
|
22
25
|
};
|
|
23
|
-
export declare const NumberFieldControl: ({ ariaValueMax, ariaValueMin, decrementAriaLabel, hasError, incrementAriaLabel,
|
|
26
|
+
export declare const NumberFieldControl: ({ ariaValueMax, ariaValueMin, decrementAriaLabel, hasError, incrementAriaLabel, inputMaxLength, inputProps, inputRef, isAnimationEnabled, isDecrementDisabled, isDecrementLoading, isIncrementDisabled, isIncrementLoading, labelId, loadingAriaLabel, maxReachedTooltipContent, minReachedTooltipContent, onBlur, onInputChange, showDecrementTooltip, showIncrementTooltip, }: NumberFieldControlProps) => React.ReactElement;
|
|
24
27
|
export {};
|
|
@@ -3,6 +3,7 @@ import { Minus, Plus } from '@box/blueprint-web-assets/icons/Medium';
|
|
|
3
3
|
import { useContext } from 'react';
|
|
4
4
|
import { NumberFieldStateContext, useContextProps, InputContext, Group } from 'react-aria-components';
|
|
5
5
|
import { BaseTextInput } from '../primitives/base-text-input/base-text-input.js';
|
|
6
|
+
import { composeEventHandlers } from '../utils/composeEventHandlers.js';
|
|
6
7
|
import { StepperButton } from './stepper-button.js';
|
|
7
8
|
import styles from './number-field.module.js';
|
|
8
9
|
|
|
@@ -12,7 +13,7 @@ const NumberFieldControl = ({
|
|
|
12
13
|
decrementAriaLabel,
|
|
13
14
|
hasError,
|
|
14
15
|
incrementAriaLabel,
|
|
15
|
-
|
|
16
|
+
inputMaxLength,
|
|
16
17
|
inputProps,
|
|
17
18
|
inputRef,
|
|
18
19
|
isAnimationEnabled,
|
|
@@ -24,13 +25,23 @@ const NumberFieldControl = ({
|
|
|
24
25
|
loadingAriaLabel,
|
|
25
26
|
maxReachedTooltipContent,
|
|
26
27
|
minReachedTooltipContent,
|
|
28
|
+
onBlur,
|
|
29
|
+
onInputChange,
|
|
27
30
|
showDecrementTooltip,
|
|
28
31
|
showIncrementTooltip
|
|
29
32
|
}) => {
|
|
30
33
|
const state = useContext(NumberFieldStateContext);
|
|
31
|
-
const
|
|
32
|
-
maxLength:
|
|
33
|
-
}
|
|
34
|
+
const racInputOverrides = inputMaxLength !== undefined ? {
|
|
35
|
+
maxLength: inputMaxLength
|
|
36
|
+
} : {};
|
|
37
|
+
const [racInputProps, racInputRef] = useContextProps(racInputOverrides, inputRef, InputContext);
|
|
38
|
+
// RAC `style` can be a render function; BaseTextInput only accepts CSSProperties.
|
|
39
|
+
const {
|
|
40
|
+
onBlur: racOnBlur,
|
|
41
|
+
onInput: racOnInput,
|
|
42
|
+
style: _racStyle,
|
|
43
|
+
...racDomProps
|
|
44
|
+
} = racInputProps;
|
|
34
45
|
if (!state) {
|
|
35
46
|
throw new Error('NumberFieldControl must be rendered inside NumberField');
|
|
36
47
|
}
|
|
@@ -42,6 +53,9 @@ const NumberFieldControl = ({
|
|
|
42
53
|
loading: true,
|
|
43
54
|
loadingAriaLabel
|
|
44
55
|
} : {};
|
|
56
|
+
const handleInput = event => {
|
|
57
|
+
onInputChange?.(event.currentTarget.value);
|
|
58
|
+
};
|
|
45
59
|
return jsxs(Group, {
|
|
46
60
|
"aria-labelledby": labelId,
|
|
47
61
|
className: styles.control,
|
|
@@ -56,7 +70,7 @@ const NumberFieldControl = ({
|
|
|
56
70
|
...decrementLoading
|
|
57
71
|
}), jsx(BaseTextInput, {
|
|
58
72
|
...inputProps,
|
|
59
|
-
...
|
|
73
|
+
...racDomProps,
|
|
60
74
|
ref: racInputRef,
|
|
61
75
|
"aria-valuemax": ariaValueMax,
|
|
62
76
|
"aria-valuemin": ariaValueMin,
|
|
@@ -64,6 +78,8 @@ const NumberFieldControl = ({
|
|
|
64
78
|
"data-bp-animated": isAnimationEnabled ? 'true' : 'false',
|
|
65
79
|
invalid: hasError,
|
|
66
80
|
label: "",
|
|
81
|
+
onBlur: composeEventHandlers(racOnBlur, onBlur),
|
|
82
|
+
onInput: composeEventHandlers(racOnInput, handleInput),
|
|
67
83
|
// Pinned to the `text` react-aria already sets, since it types this as any input type.
|
|
68
84
|
type: "text"
|
|
69
85
|
}), jsx(StepperButton, {
|
|
@@ -5,5 +5,10 @@ import { type NumberFieldProps } from './types';
|
|
|
5
5
|
* Formats non-negative whole numbers out of the box; `formatOptions` opens that up to decimals, currency
|
|
6
6
|
* and percent, and doubles as the rule for what may be typed. The control is sized to hold `maxLength`
|
|
7
7
|
* characters and does not grow or shrink with its container.
|
|
8
|
+
*
|
|
9
|
+
* Unconstrained by default: no clamp, no implied stepper bounds. Pass `clampToRange` with both
|
|
10
|
+
* `min` and `max` for library-owned range behavior (omitting either bound leaves the field
|
|
11
|
+
* unconstrained); or drive steppers yourself with `isIncrementDisabled` / `isDecrementDisabled`
|
|
12
|
+
* and validate via `onChange` / `onBlur` / `onInputChange`.
|
|
8
13
|
*/
|
|
9
14
|
export declare const NumberField: import("react").ForwardRefExoticComponent<NumberFieldProps & import("react").RefAttributes<HTMLInputElement>>;
|
|
@@ -9,7 +9,7 @@ import { InlineError } from '../primitives/inline-error/inline-error.js';
|
|
|
9
9
|
import { Text } from '../text/text.js';
|
|
10
10
|
import { useLabelable } from '../util-components/labelable/useLabelable.js';
|
|
11
11
|
import { useUniqueId } from '../utils/useUniqueId.js';
|
|
12
|
-
import { NUMBER_FIELD_FORMAT_OPTIONS,
|
|
12
|
+
import { NUMBER_FIELD_FORMAT_OPTIONS, NUMBER_FIELD_DEFAULT_DIGITS } from './constants.js';
|
|
13
13
|
import { NumberFieldControl } from './number-field-control.js';
|
|
14
14
|
import styles from './number-field.module.js';
|
|
15
15
|
|
|
@@ -19,9 +19,15 @@ import styles from './number-field.module.js';
|
|
|
19
19
|
* Formats non-negative whole numbers out of the box; `formatOptions` opens that up to decimals, currency
|
|
20
20
|
* and percent, and doubles as the rule for what may be typed. The control is sized to hold `maxLength`
|
|
21
21
|
* characters and does not grow or shrink with its container.
|
|
22
|
+
*
|
|
23
|
+
* Unconstrained by default: no clamp, no implied stepper bounds. Pass `clampToRange` with both
|
|
24
|
+
* `min` and `max` for library-owned range behavior (omitting either bound leaves the field
|
|
25
|
+
* unconstrained); or drive steppers yourself with `isIncrementDisabled` / `isDecrementDisabled`
|
|
26
|
+
* and validate via `onChange` / `onBlur` / `onInputChange`.
|
|
22
27
|
*/
|
|
23
28
|
const NumberField = /*#__PURE__*/forwardRef((props, forwardedRef) => {
|
|
24
29
|
const {
|
|
30
|
+
clampToRange = false,
|
|
25
31
|
className,
|
|
26
32
|
decrementAriaLabel,
|
|
27
33
|
disabled = false,
|
|
@@ -31,16 +37,20 @@ const NumberField = /*#__PURE__*/forwardRef((props, forwardedRef) => {
|
|
|
31
37
|
hideLabel = false,
|
|
32
38
|
id,
|
|
33
39
|
incrementAriaLabel,
|
|
40
|
+
isDecrementDisabled = false,
|
|
41
|
+
isIncrementDisabled = false,
|
|
34
42
|
label,
|
|
35
43
|
loading = false,
|
|
36
44
|
loadingAriaLabel,
|
|
37
|
-
max
|
|
45
|
+
max,
|
|
38
46
|
maxLength,
|
|
39
47
|
maxReachedTooltipContent,
|
|
40
|
-
min
|
|
48
|
+
min,
|
|
41
49
|
minReachedTooltipContent,
|
|
42
50
|
name,
|
|
51
|
+
onBlur,
|
|
43
52
|
onChange,
|
|
53
|
+
onInputChange,
|
|
44
54
|
readOnly = false,
|
|
45
55
|
required = false,
|
|
46
56
|
step = 1,
|
|
@@ -62,8 +72,15 @@ const NumberField = /*#__PURE__*/forwardRef((props, forwardedRef) => {
|
|
|
62
72
|
// InlineError alone would leave an inconsistent a11y state.
|
|
63
73
|
const hasError = !!error && !disabled && !isAnyLoading;
|
|
64
74
|
const hasSubtext = !hasError && !!subtext;
|
|
65
|
-
|
|
66
|
-
const
|
|
75
|
+
const shouldClamp = clampToRange && min !== undefined && max !== undefined;
|
|
76
|
+
const atMin = shouldClamp && value <= min;
|
|
77
|
+
const atMax = shouldClamp && value >= max;
|
|
78
|
+
const decrementBlocked = isDecrementDisabled || atMin;
|
|
79
|
+
const incrementBlocked = isIncrementDisabled || atMax;
|
|
80
|
+
// Width is always set. Native maxLength is only a real limit: an explicit prop, or the digits of
|
|
81
|
+
// `max` when clamping. Unconstrained fields stay three digits wide without capping typing.
|
|
82
|
+
const inputLength = maxLength ?? (shouldClamp && max !== undefined ? String(max).length : NUMBER_FIELD_DEFAULT_DIGITS);
|
|
83
|
+
const inputMaxLength = maxLength ?? (shouldClamp ? inputLength : undefined);
|
|
67
84
|
const Label = useLabelable(label, inputId, required);
|
|
68
85
|
// Mirrors TextInput
|
|
69
86
|
const {
|
|
@@ -85,8 +102,8 @@ const NumberField = /*#__PURE__*/forwardRef((props, forwardedRef) => {
|
|
|
85
102
|
isInvalid: hasError,
|
|
86
103
|
isReadOnly: readOnly,
|
|
87
104
|
isRequired: required,
|
|
88
|
-
maxValue: max,
|
|
89
|
-
minValue: min,
|
|
105
|
+
maxValue: shouldClamp ? max : undefined,
|
|
106
|
+
minValue: shouldClamp ? min : undefined,
|
|
90
107
|
name: name,
|
|
91
108
|
onChange: onChange,
|
|
92
109
|
step: step,
|
|
@@ -107,20 +124,22 @@ const NumberField = /*#__PURE__*/forwardRef((props, forwardedRef) => {
|
|
|
107
124
|
decrementAriaLabel: decrementAriaLabel,
|
|
108
125
|
hasError: hasError,
|
|
109
126
|
incrementAriaLabel: incrementAriaLabel,
|
|
110
|
-
|
|
127
|
+
inputMaxLength: inputMaxLength,
|
|
111
128
|
inputProps: rest,
|
|
112
129
|
inputRef: forwardedRef,
|
|
113
130
|
isAnimationEnabled: isAnimationEnabled,
|
|
114
|
-
isDecrementDisabled: !isInteractive ||
|
|
131
|
+
isDecrementDisabled: !isInteractive || decrementBlocked || isAnyLoading && !isDecrementLoading,
|
|
115
132
|
isDecrementLoading: isDecrementLoading,
|
|
116
|
-
isIncrementDisabled: !isInteractive ||
|
|
133
|
+
isIncrementDisabled: !isInteractive || incrementBlocked || isAnyLoading && !isIncrementLoading,
|
|
117
134
|
isIncrementLoading: isIncrementLoading,
|
|
118
135
|
labelId: labelId,
|
|
119
136
|
loadingAriaLabel: loadingAriaLabel,
|
|
120
137
|
maxReachedTooltipContent: maxReachedTooltipContent,
|
|
121
138
|
minReachedTooltipContent: minReachedTooltipContent,
|
|
122
|
-
|
|
123
|
-
|
|
139
|
+
onBlur: onBlur,
|
|
140
|
+
onInputChange: onInputChange,
|
|
141
|
+
showDecrementTooltip: isInteractive && !isAnyLoading && decrementBlocked && !!minReachedTooltipContent,
|
|
142
|
+
showIncrementTooltip: isInteractive && !isAnyLoading && incrementBlocked && !!maxReachedTooltipContent
|
|
124
143
|
}), hasError ? jsx(InlineError, {
|
|
125
144
|
className: styles.inlineError,
|
|
126
145
|
id: inlineErrorId,
|
|
@@ -13,9 +13,5 @@ type StepperButtonProps = {
|
|
|
13
13
|
showTooltip: boolean;
|
|
14
14
|
tooltipContent?: string;
|
|
15
15
|
} & StepperLoadingProps;
|
|
16
|
-
/**
|
|
17
|
-
* With a boundary tooltip to show, `disabled` gives way to `aria-disabled` so the button stays focusable
|
|
18
|
-
* and hoverable enough to surface it.
|
|
19
|
-
*/
|
|
20
16
|
export declare const StepperButton: ({ ariaLabel, className, disabled, icon, onClick, showTooltip, tooltipContent, ...loadingProps }: StepperButtonProps) => React.ReactElement;
|
|
21
17
|
export type { StepperLoadingProps };
|
|
@@ -2,10 +2,6 @@ import { jsx } from 'react/jsx-runtime';
|
|
|
2
2
|
import { Button } from '../button/button.js';
|
|
3
3
|
import { Tooltip } from '../tooltip/tooltip.js';
|
|
4
4
|
|
|
5
|
-
/**
|
|
6
|
-
* With a boundary tooltip to show, `disabled` gives way to `aria-disabled` so the button stays focusable
|
|
7
|
-
* and hoverable enough to surface it.
|
|
8
|
-
*/
|
|
9
5
|
const StepperButton = ({
|
|
10
6
|
ariaLabel,
|
|
11
7
|
className,
|
|
@@ -16,10 +12,9 @@ const StepperButton = ({
|
|
|
16
12
|
tooltipContent,
|
|
17
13
|
...loadingProps
|
|
18
14
|
}) => {
|
|
19
|
-
const tooltip = showTooltip ? tooltipContent : undefined;
|
|
20
15
|
const button = jsx(Button, {
|
|
21
16
|
...loadingProps,
|
|
22
|
-
accessibleWhenDisabled:
|
|
17
|
+
accessibleWhenDisabled: showTooltip,
|
|
23
18
|
"aria-label": ariaLabel,
|
|
24
19
|
className: className,
|
|
25
20
|
disabled: disabled,
|
|
@@ -29,13 +24,14 @@ const StepperButton = ({
|
|
|
29
24
|
type: "button",
|
|
30
25
|
variant: "secondary"
|
|
31
26
|
});
|
|
32
|
-
if (
|
|
33
|
-
return
|
|
34
|
-
content: tooltip,
|
|
35
|
-
children: button
|
|
36
|
-
});
|
|
27
|
+
if (!tooltipContent) {
|
|
28
|
+
return button;
|
|
37
29
|
}
|
|
38
|
-
return
|
|
30
|
+
return jsx(Tooltip, {
|
|
31
|
+
content: tooltipContent,
|
|
32
|
+
open: showTooltip ? undefined : false,
|
|
33
|
+
children: button
|
|
34
|
+
});
|
|
39
35
|
};
|
|
40
36
|
|
|
41
37
|
export { StepperButton };
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { type ComponentPropsWithoutRef, type ReactNode } from 'react';
|
|
2
2
|
import { type RequireAllOrNone } from 'type-fest';
|
|
3
|
+
import { type BaseTextInputProps } from '../primitives/base-text-input';
|
|
3
4
|
import { type Labelable } from '../util-components/labelable';
|
|
4
5
|
export interface Loading {
|
|
5
6
|
/**
|
|
@@ -14,12 +15,25 @@ export interface Loading {
|
|
|
14
15
|
* Attributes NumberField sets from its own props, so they cannot be forwarded. `size` is included because
|
|
15
16
|
* the control takes its width from `maxLength`.
|
|
16
17
|
*/
|
|
17
|
-
type ReservedInputAttributes = 'defaultValue' | 'disabled' | 'inputMode' | 'max' | 'min' | 'onBlur' | 'onChange' | 'onKeyDown' | 'pattern' | 'readOnly' | 'required' | 'size' | 'step' | 'type' | 'value';
|
|
18
|
+
type ReservedInputAttributes = 'defaultValue' | 'disabled' | 'inputMode' | 'max' | 'min' | 'onBlur' | 'onChange' | 'onInput' | 'onKeyDown' | 'pattern' | 'readOnly' | 'required' | 'size' | 'step' | 'type' | 'value';
|
|
18
19
|
/**
|
|
19
20
|
* Remaining props are forwarded to the `<input>`, so the field takes part in native form submission.
|
|
20
21
|
* `className` styles the wrapper holding the label, the control and the subtext.
|
|
21
22
|
*/
|
|
22
23
|
interface NumberFieldBaseProps extends Omit<ComponentPropsWithoutRef<'input'>, ReservedInputAttributes>, Labelable {
|
|
24
|
+
/**
|
|
25
|
+
* When true **and both `min` and `max` are set**, typed commits and controlled values are clamped
|
|
26
|
+
* into `[min, max]` via RAC, and steppers are disabled at the bounds.
|
|
27
|
+
*
|
|
28
|
+
* If either bound is omitted, this prop is a no-op — the field stays unconstrained (same as the
|
|
29
|
+
* default). Example: `clampToRange min={1} max={10}`.
|
|
30
|
+
*
|
|
31
|
+
* Without this (the default), the consumer owns validation and stepper bounds via `onChange` /
|
|
32
|
+
* `onBlur` / `isIncrementDisabled` / `isDecrementDisabled`.
|
|
33
|
+
*
|
|
34
|
+
* @default false
|
|
35
|
+
*/
|
|
36
|
+
clampToRange?: boolean;
|
|
23
37
|
/** Accessible label for the decrement button. */
|
|
24
38
|
decrementAriaLabel: string;
|
|
25
39
|
/**
|
|
@@ -45,33 +59,53 @@ interface NumberFieldBaseProps extends Omit<ComponentPropsWithoutRef<'input'>, R
|
|
|
45
59
|
hideLabel?: boolean;
|
|
46
60
|
/** Accessible label for the increment button. */
|
|
47
61
|
incrementAriaLabel: string;
|
|
62
|
+
/**
|
|
63
|
+
* When true, disables the decrement stepper. Use this to own stepper bounds when `clampToRange` is
|
|
64
|
+
* off (e.g. `value <= min`). Combined with `clampToRange`, also disables at `min`.
|
|
65
|
+
*/
|
|
66
|
+
isDecrementDisabled?: boolean;
|
|
67
|
+
/**
|
|
68
|
+
* When true, disables the increment stepper. Use this to own stepper bounds when `clampToRange` is
|
|
69
|
+
* off (e.g. `value >= max`). Combined with `clampToRange`, also disables at `max`.
|
|
70
|
+
*/
|
|
71
|
+
isIncrementDisabled?: boolean;
|
|
48
72
|
/** The field label rendered above the control. */
|
|
49
73
|
label: Labelable['label'];
|
|
50
74
|
/**
|
|
51
|
-
*
|
|
52
|
-
*
|
|
53
|
-
*
|
|
54
|
-
* @default 999
|
|
75
|
+
* Upper bound hint: exposed as `aria-valuemax` and used for `maxReachedTooltipContent`. Only drives
|
|
76
|
+
* RAC clamp / auto-disabled steppers, and the default `maxLength`, when `clampToRange` is true
|
|
77
|
+
* **and `min` is also set**; otherwise it is a hint only.
|
|
55
78
|
*/
|
|
56
79
|
max?: number;
|
|
57
80
|
/**
|
|
58
|
-
* How many characters the input accepts
|
|
59
|
-
*
|
|
81
|
+
* How many characters the input accepts. Always wins when set, and also sizes the control.
|
|
82
|
+
*
|
|
83
|
+
* When omitted: the digits of `max` if `clampToRange` is on with both bounds (width and typing
|
|
84
|
+
* cap). Unconstrained fields stay three digits wide and do not cap typing.
|
|
60
85
|
*/
|
|
61
86
|
maxLength?: number;
|
|
62
|
-
/** Localized tooltip content shown when the increment button is disabled
|
|
87
|
+
/** Localized tooltip content shown when the increment button is disabled. */
|
|
63
88
|
maxReachedTooltipContent?: string;
|
|
64
89
|
/**
|
|
65
|
-
*
|
|
66
|
-
*
|
|
67
|
-
*
|
|
68
|
-
* @default 0
|
|
90
|
+
* Lower bound hint: exposed as `aria-valuemin` and used for `minReachedTooltipContent`. Only drives
|
|
91
|
+
* RAC clamp / auto-disabled steppers when `clampToRange` is true **and `max` is also set**;
|
|
92
|
+
* otherwise it is a hint only.
|
|
69
93
|
*/
|
|
70
94
|
min?: number;
|
|
71
|
-
/** Localized tooltip content shown when the decrement button is disabled
|
|
95
|
+
/** Localized tooltip content shown when the decrement button is disabled. */
|
|
72
96
|
minReachedTooltipContent?: string;
|
|
97
|
+
/**
|
|
98
|
+
* Called when the input loses focus. Fires when tabbing or clicking away, including when moving
|
|
99
|
+
* from the input to a stepper.
|
|
100
|
+
*/
|
|
101
|
+
onBlur?: BaseTextInputProps['onBlur'];
|
|
73
102
|
/** Called with the new value when a stepper is used, or when the typed draft is committed on blur or Enter. */
|
|
74
103
|
onChange: (value: number) => void;
|
|
104
|
+
/**
|
|
105
|
+
* Called with the raw input string as the user types, before the draft is committed via `onChange`.
|
|
106
|
+
* Use this for free-typed intermediate values (e.g. keeping a below-min draft visible).
|
|
107
|
+
*/
|
|
108
|
+
onInputChange?: (rawValue: string) => void;
|
|
75
109
|
/**
|
|
76
110
|
* When true, the value is displayed but cannot be edited and the stepper buttons are inactive.
|
|
77
111
|
*
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@box/blueprint-web",
|
|
3
|
-
"version": "17.
|
|
3
|
+
"version": "17.1.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"license": "SEE LICENSE IN LICENSE",
|
|
6
6
|
"publishConfig": {
|
|
@@ -55,7 +55,7 @@
|
|
|
55
55
|
"dependencies": {
|
|
56
56
|
"@ariakit/react": "0.4.21",
|
|
57
57
|
"@ariakit/react-core": "0.4.21",
|
|
58
|
-
"@box/blueprint-web-assets": "^5.7.
|
|
58
|
+
"@box/blueprint-web-assets": "^5.7.17",
|
|
59
59
|
"@internationalized/date": "^3.12.0",
|
|
60
60
|
"@radix-ui/react-accordion": "1.1.2",
|
|
61
61
|
"@radix-ui/react-checkbox": "1.0.4",
|
|
@@ -86,7 +86,7 @@
|
|
|
86
86
|
"devDependencies": {
|
|
87
87
|
"@box/box-test-client": "^3.2.7",
|
|
88
88
|
"@box/playwright-utils": "^2.8.5",
|
|
89
|
-
"@box/storybook-utils": "^1.2.
|
|
89
|
+
"@box/storybook-utils": "^1.2.26",
|
|
90
90
|
"@figma/code-connect": "1.4.4",
|
|
91
91
|
"@playwright/test": "1.46.1",
|
|
92
92
|
"@types/js-yaml": "^4.0.9",
|