@oxy.so/bloom 3.0.1 → 3.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/docs/adoption-matrix.mdx +266 -0
- package/docs/checkbox.mdx +16 -0
- package/docs/composition.mdx +88 -16
- package/docs/date-picker.mdx +11 -0
- package/docs/field.mdx +35 -4
- package/docs/file-upload.mdx +13 -0
- package/docs/input-group.mdx +22 -0
- package/docs/input-otp.mdx +14 -0
- package/docs/layout.mdx +34 -0
- package/docs/migrating-to-3.0.mdx +43 -4
- package/docs/phone-input.mdx +17 -0
- package/docs/radio.mdx +16 -0
- package/docs/rating.mdx +14 -0
- package/docs/segmented-control.mdx +16 -4
- package/docs/select.mdx +22 -0
- package/docs/slider.mdx +14 -0
- package/docs/stepper.mdx +3 -1
- package/docs/switch.mdx +14 -0
- package/docs/text-field.mdx +14 -0
- package/docs/textarea.mdx +20 -0
- package/lib/commonjs/bottom-sheet/BottomSheetBase.js +11 -4
- package/lib/commonjs/bottom-sheet/BottomSheetBase.js.map +1 -1
- package/lib/commonjs/checkbox/Checkbox.js +24 -8
- package/lib/commonjs/checkbox/Checkbox.js.map +1 -1
- package/lib/commonjs/date-picker/TimeField.js +18 -6
- package/lib/commonjs/date-picker/TimeField.js.map +1 -1
- package/lib/commonjs/dialog/DialogContent.js +36 -29
- package/lib/commonjs/dialog/DialogContent.js.map +1 -1
- package/lib/commonjs/field/index.js +7 -0
- package/lib/commonjs/field/index.js.map +1 -1
- package/lib/commonjs/field/membership.js +105 -0
- package/lib/commonjs/field/membership.js.map +1 -0
- package/lib/commonjs/file-upload/FileUpload.js +17 -3
- package/lib/commonjs/file-upload/FileUpload.js.map +1 -1
- package/lib/commonjs/index.js +21 -0
- package/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/index.web.js +21 -0
- package/lib/commonjs/index.web.js.map +1 -1
- package/lib/commonjs/input-group/InputGroup.js +18 -5
- package/lib/commonjs/input-group/InputGroup.js.map +1 -1
- package/lib/commonjs/input-otp/InputOtp.js +20 -8
- package/lib/commonjs/input-otp/InputOtp.js.map +1 -1
- package/lib/commonjs/layout/index.js +7 -0
- package/lib/commonjs/layout/index.js.map +1 -1
- package/lib/commonjs/layout/screen-scope.js +61 -0
- package/lib/commonjs/layout/screen-scope.js.map +1 -0
- package/lib/commonjs/phone-input/PhoneInput.js +20 -8
- package/lib/commonjs/phone-input/PhoneInput.js.map +1 -1
- package/lib/commonjs/radio/Radio.js +35 -9
- package/lib/commonjs/radio/Radio.js.map +1 -1
- package/lib/commonjs/rating/RatingInput.js +14 -6
- package/lib/commonjs/rating/RatingInput.js.map +1 -1
- package/lib/commonjs/segmented-control/SegmentedControl.js +38 -8
- package/lib/commonjs/segmented-control/SegmentedControl.js.map +1 -1
- package/lib/commonjs/select/Select.js +15 -3
- package/lib/commonjs/select/Select.js.map +1 -1
- package/lib/commonjs/select/Select.web.js +13 -5
- package/lib/commonjs/select/Select.web.js.map +1 -1
- package/lib/commonjs/slider/Slider.js +17 -3
- package/lib/commonjs/slider/Slider.js.map +1 -1
- package/lib/commonjs/stepper/Stepper.js +16 -7
- package/lib/commonjs/stepper/Stepper.js.map +1 -1
- package/lib/commonjs/styles/styled-primitives.js.map +1 -1
- package/lib/commonjs/switch/Switch.js +23 -8
- package/lib/commonjs/switch/Switch.js.map +1 -1
- package/lib/commonjs/text-field/TextField.js +19 -4
- package/lib/commonjs/text-field/TextField.js.map +1 -1
- package/lib/commonjs/textarea/Textarea.js +39 -15
- package/lib/commonjs/textarea/Textarea.js.map +1 -1
- package/lib/module/bottom-sheet/BottomSheetBase.js +8 -1
- package/lib/module/bottom-sheet/BottomSheetBase.js.map +1 -1
- package/lib/module/checkbox/Checkbox.js +24 -8
- package/lib/module/checkbox/Checkbox.js.map +1 -1
- package/lib/module/date-picker/TimeField.js +19 -5
- package/lib/module/date-picker/TimeField.js.map +1 -1
- package/lib/module/dialog/DialogContent.js +37 -30
- package/lib/module/dialog/DialogContent.js.map +1 -1
- package/lib/module/field/index.js +1 -0
- package/lib/module/field/index.js.map +1 -1
- package/lib/module/field/membership.js +101 -0
- package/lib/module/field/membership.js.map +1 -0
- package/lib/module/file-upload/FileUpload.js +17 -3
- package/lib/module/file-upload/FileUpload.js.map +1 -1
- package/lib/module/index.js +2 -2
- package/lib/module/index.js.map +1 -1
- package/lib/module/index.web.js +2 -2
- package/lib/module/index.web.js.map +1 -1
- package/lib/module/input-group/InputGroup.js +19 -4
- package/lib/module/input-group/InputGroup.js.map +1 -1
- package/lib/module/input-otp/InputOtp.js +21 -7
- package/lib/module/input-otp/InputOtp.js.map +1 -1
- package/lib/module/layout/index.js +4 -1
- package/lib/module/layout/index.js.map +1 -1
- package/lib/module/layout/screen-scope.js +57 -0
- package/lib/module/layout/screen-scope.js.map +1 -0
- package/lib/module/phone-input/PhoneInput.js +22 -9
- package/lib/module/phone-input/PhoneInput.js.map +1 -1
- package/lib/module/radio/Radio.js +35 -9
- package/lib/module/radio/Radio.js.map +1 -1
- package/lib/module/rating/RatingInput.js +15 -5
- package/lib/module/rating/RatingInput.js.map +1 -1
- package/lib/module/segmented-control/SegmentedControl.js +38 -8
- package/lib/module/segmented-control/SegmentedControl.js.map +1 -1
- package/lib/module/select/Select.js +15 -3
- package/lib/module/select/Select.js.map +1 -1
- package/lib/module/select/Select.web.js +14 -4
- package/lib/module/select/Select.web.js.map +1 -1
- package/lib/module/slider/Slider.js +17 -3
- package/lib/module/slider/Slider.js.map +1 -1
- package/lib/module/stepper/Stepper.js +17 -6
- package/lib/module/stepper/Stepper.js.map +1 -1
- package/lib/module/styles/styled-primitives.js.map +1 -1
- package/lib/module/switch/Switch.js +23 -8
- package/lib/module/switch/Switch.js.map +1 -1
- package/lib/module/text-field/TextField.js +19 -4
- package/lib/module/text-field/TextField.js.map +1 -1
- package/lib/module/textarea/Textarea.js +39 -15
- package/lib/module/textarea/Textarea.js.map +1 -1
- package/lib/typescript/commonjs/bottom-sheet/BottomSheetBase.d.ts.map +1 -1
- package/lib/typescript/commonjs/checkbox/Checkbox.d.ts.map +1 -1
- package/lib/typescript/commonjs/checkbox/types.d.ts +5 -0
- package/lib/typescript/commonjs/checkbox/types.d.ts.map +1 -1
- package/lib/typescript/commonjs/date-picker/TimeField.d.ts +1 -1
- package/lib/typescript/commonjs/date-picker/TimeField.d.ts.map +1 -1
- package/lib/typescript/commonjs/date-picker/types.d.ts +6 -3
- package/lib/typescript/commonjs/date-picker/types.d.ts.map +1 -1
- package/lib/typescript/commonjs/dialog/DialogContent.d.ts.map +1 -1
- package/lib/typescript/commonjs/field/index.d.ts +2 -0
- package/lib/typescript/commonjs/field/index.d.ts.map +1 -1
- package/lib/typescript/commonjs/field/membership.d.ts +60 -0
- package/lib/typescript/commonjs/field/membership.d.ts.map +1 -0
- package/lib/typescript/commonjs/file-upload/FileUpload.d.ts +1 -1
- package/lib/typescript/commonjs/file-upload/FileUpload.d.ts.map +1 -1
- package/lib/typescript/commonjs/floating/types.d.ts +13 -0
- package/lib/typescript/commonjs/floating/types.d.ts.map +1 -1
- package/lib/typescript/commonjs/index.d.ts +3 -3
- package/lib/typescript/commonjs/index.d.ts.map +1 -1
- package/lib/typescript/commonjs/index.web.d.ts +3 -3
- package/lib/typescript/commonjs/index.web.d.ts.map +1 -1
- package/lib/typescript/commonjs/input-group/InputGroup.d.ts +1 -1
- package/lib/typescript/commonjs/input-group/InputGroup.d.ts.map +1 -1
- package/lib/typescript/commonjs/input-otp/InputOtp.d.ts.map +1 -1
- package/lib/typescript/commonjs/input-otp/types.d.ts +7 -1
- package/lib/typescript/commonjs/input-otp/types.d.ts.map +1 -1
- package/lib/typescript/commonjs/layout/index.d.ts +4 -1
- package/lib/typescript/commonjs/layout/index.d.ts.map +1 -1
- package/lib/typescript/commonjs/layout/screen-scope.d.ts +40 -0
- package/lib/typescript/commonjs/layout/screen-scope.d.ts.map +1 -0
- package/lib/typescript/commonjs/phone-input/PhoneInput.d.ts.map +1 -1
- package/lib/typescript/commonjs/radio/Radio.d.ts +1 -1
- package/lib/typescript/commonjs/radio/Radio.d.ts.map +1 -1
- package/lib/typescript/commonjs/radio/types.d.ts +11 -3
- package/lib/typescript/commonjs/radio/types.d.ts.map +1 -1
- package/lib/typescript/commonjs/rating/RatingInput.d.ts +1 -1
- package/lib/typescript/commonjs/rating/RatingInput.d.ts.map +1 -1
- package/lib/typescript/commonjs/rating/types.d.ts +6 -3
- package/lib/typescript/commonjs/rating/types.d.ts.map +1 -1
- package/lib/typescript/commonjs/segmented-control/SegmentedControl.d.ts +18 -2
- package/lib/typescript/commonjs/segmented-control/SegmentedControl.d.ts.map +1 -1
- package/lib/typescript/commonjs/select/Select.d.ts.map +1 -1
- package/lib/typescript/commonjs/select/Select.web.d.ts.map +1 -1
- package/lib/typescript/commonjs/select/types.d.ts +8 -1
- package/lib/typescript/commonjs/select/types.d.ts.map +1 -1
- package/lib/typescript/commonjs/slider/Slider.d.ts.map +1 -1
- package/lib/typescript/commonjs/stepper/Stepper.d.ts +1 -1
- package/lib/typescript/commonjs/stepper/Stepper.d.ts.map +1 -1
- package/lib/typescript/commonjs/stepper/types.d.ts +8 -1
- package/lib/typescript/commonjs/stepper/types.d.ts.map +1 -1
- package/lib/typescript/commonjs/styles/styled-primitives.d.ts +16 -0
- package/lib/typescript/commonjs/styles/styled-primitives.d.ts.map +1 -1
- package/lib/typescript/commonjs/switch/Switch.d.ts.map +1 -1
- package/lib/typescript/commonjs/switch/types.d.ts +7 -0
- package/lib/typescript/commonjs/switch/types.d.ts.map +1 -1
- package/lib/typescript/commonjs/text-field/TextField.d.ts.map +1 -1
- package/lib/typescript/commonjs/textarea/Textarea.d.ts +1 -1
- package/lib/typescript/commonjs/textarea/Textarea.d.ts.map +1 -1
- package/lib/typescript/module/bottom-sheet/BottomSheetBase.d.ts.map +1 -1
- package/lib/typescript/module/checkbox/Checkbox.d.ts.map +1 -1
- package/lib/typescript/module/checkbox/types.d.ts +5 -0
- package/lib/typescript/module/checkbox/types.d.ts.map +1 -1
- package/lib/typescript/module/date-picker/TimeField.d.ts +1 -1
- package/lib/typescript/module/date-picker/TimeField.d.ts.map +1 -1
- package/lib/typescript/module/date-picker/types.d.ts +6 -3
- package/lib/typescript/module/date-picker/types.d.ts.map +1 -1
- package/lib/typescript/module/dialog/DialogContent.d.ts.map +1 -1
- package/lib/typescript/module/field/index.d.ts +2 -0
- package/lib/typescript/module/field/index.d.ts.map +1 -1
- package/lib/typescript/module/field/membership.d.ts +60 -0
- package/lib/typescript/module/field/membership.d.ts.map +1 -0
- package/lib/typescript/module/file-upload/FileUpload.d.ts +1 -1
- package/lib/typescript/module/file-upload/FileUpload.d.ts.map +1 -1
- package/lib/typescript/module/floating/types.d.ts +13 -0
- package/lib/typescript/module/floating/types.d.ts.map +1 -1
- package/lib/typescript/module/index.d.ts +3 -3
- package/lib/typescript/module/index.d.ts.map +1 -1
- package/lib/typescript/module/index.web.d.ts +3 -3
- package/lib/typescript/module/index.web.d.ts.map +1 -1
- package/lib/typescript/module/input-group/InputGroup.d.ts +1 -1
- package/lib/typescript/module/input-group/InputGroup.d.ts.map +1 -1
- package/lib/typescript/module/input-otp/InputOtp.d.ts.map +1 -1
- package/lib/typescript/module/input-otp/types.d.ts +7 -1
- package/lib/typescript/module/input-otp/types.d.ts.map +1 -1
- package/lib/typescript/module/layout/index.d.ts +4 -1
- package/lib/typescript/module/layout/index.d.ts.map +1 -1
- package/lib/typescript/module/layout/screen-scope.d.ts +40 -0
- package/lib/typescript/module/layout/screen-scope.d.ts.map +1 -0
- package/lib/typescript/module/phone-input/PhoneInput.d.ts.map +1 -1
- package/lib/typescript/module/radio/Radio.d.ts +1 -1
- package/lib/typescript/module/radio/Radio.d.ts.map +1 -1
- package/lib/typescript/module/radio/types.d.ts +11 -3
- package/lib/typescript/module/radio/types.d.ts.map +1 -1
- package/lib/typescript/module/rating/RatingInput.d.ts +1 -1
- package/lib/typescript/module/rating/RatingInput.d.ts.map +1 -1
- package/lib/typescript/module/rating/types.d.ts +6 -3
- package/lib/typescript/module/rating/types.d.ts.map +1 -1
- package/lib/typescript/module/segmented-control/SegmentedControl.d.ts +18 -2
- package/lib/typescript/module/segmented-control/SegmentedControl.d.ts.map +1 -1
- package/lib/typescript/module/select/Select.d.ts.map +1 -1
- package/lib/typescript/module/select/Select.web.d.ts.map +1 -1
- package/lib/typescript/module/select/types.d.ts +8 -1
- package/lib/typescript/module/select/types.d.ts.map +1 -1
- package/lib/typescript/module/slider/Slider.d.ts.map +1 -1
- package/lib/typescript/module/stepper/Stepper.d.ts +1 -1
- package/lib/typescript/module/stepper/Stepper.d.ts.map +1 -1
- package/lib/typescript/module/stepper/types.d.ts +8 -1
- package/lib/typescript/module/stepper/types.d.ts.map +1 -1
- package/lib/typescript/module/styles/styled-primitives.d.ts +16 -0
- package/lib/typescript/module/styles/styled-primitives.d.ts.map +1 -1
- package/lib/typescript/module/switch/Switch.d.ts.map +1 -1
- package/lib/typescript/module/switch/types.d.ts +7 -0
- package/lib/typescript/module/switch/types.d.ts.map +1 -1
- package/lib/typescript/module/text-field/TextField.d.ts.map +1 -1
- package/lib/typescript/module/textarea/Textarea.d.ts +1 -1
- package/lib/typescript/module/textarea/Textarea.d.ts.map +1 -1
- package/package.json +2 -1
- package/src/bottom-sheet/BottomSheetBase.tsx +6 -1
- package/src/checkbox/Checkbox.tsx +24 -8
- package/src/checkbox/types.ts +5 -0
- package/src/date-picker/TimeField.tsx +14 -3
- package/src/date-picker/types.ts +6 -3
- package/src/dialog/DialogContent.tsx +6 -2
- package/src/field/index.ts +2 -0
- package/src/field/membership.ts +164 -0
- package/src/file-upload/FileUpload.tsx +14 -3
- package/src/floating/types.ts +13 -0
- package/src/index.ts +9 -2
- package/src/index.web.ts +9 -2
- package/src/input-group/InputGroup.tsx +13 -2
- package/src/input-otp/InputOtp.tsx +19 -5
- package/src/input-otp/types.ts +7 -1
- package/src/layout/index.ts +4 -1
- package/src/layout/screen-scope.tsx +54 -0
- package/src/phone-input/PhoneInput.tsx +20 -8
- package/src/radio/Radio.tsx +32 -9
- package/src/radio/types.ts +11 -3
- package/src/rating/RatingInput.tsx +10 -3
- package/src/rating/types.ts +6 -3
- package/src/segmented-control/SegmentedControl.tsx +51 -8
- package/src/select/Select.tsx +14 -2
- package/src/select/Select.web.tsx +11 -2
- package/src/select/types.ts +8 -1
- package/src/slider/Slider.tsx +13 -3
- package/src/stepper/Stepper.tsx +12 -4
- package/src/stepper/types.ts +8 -1
- package/src/styles/styled-primitives.ts +16 -0
- package/src/switch/Switch.tsx +22 -9
- package/src/switch/types.ts +7 -0
- package/src/text-field/TextField.tsx +16 -4
- package/src/textarea/Textarea.tsx +38 -15
package/src/slider/Slider.tsx
CHANGED
|
@@ -22,6 +22,7 @@ import { clamp } from '../styles/clamp';
|
|
|
22
22
|
import { BUTTON_SHADOW, mixColor, resolveButtonRamps } from '../button/shared';
|
|
23
23
|
import { useAccessibleNameWarning } from '../hooks/use-accessible-name-warning';
|
|
24
24
|
import type { RangeSliderProps, SliderProps } from './types';
|
|
25
|
+
import { useFieldMembership } from '../field/membership';
|
|
25
26
|
|
|
26
27
|
function quantize(raw: number, min: number, max: number, step: number): number {
|
|
27
28
|
if (step <= 0) return clamp(raw, min, max);
|
|
@@ -184,7 +185,7 @@ function SliderBase({
|
|
|
184
185
|
min,
|
|
185
186
|
max,
|
|
186
187
|
step,
|
|
187
|
-
disabled,
|
|
188
|
+
disabled: disabledProp,
|
|
188
189
|
trackHeight,
|
|
189
190
|
thumbSize,
|
|
190
191
|
minimumTrackTintColor,
|
|
@@ -199,6 +200,12 @@ function SliderBase({
|
|
|
199
200
|
testID,
|
|
200
201
|
}: SliderBaseProps) {
|
|
201
202
|
const theme = useTheme();
|
|
203
|
+
// A slider draws no text of its own — its `label` is rendered but hidden from
|
|
204
|
+
// assistive technology, which is why the name has to be a prop. Inside a
|
|
205
|
+
// `Field` the field's label is that name (STACKED placement), and the field's
|
|
206
|
+
// `disabled` constrains the slider whatever the caller passed.
|
|
207
|
+
const field = useFieldMembership({ accessibilityLabel, label, disabled: disabledProp });
|
|
208
|
+
const disabled = field.disabled;
|
|
202
209
|
const ringOffset = useRingOffsetStyle();
|
|
203
210
|
React.useEffect(() => {
|
|
204
211
|
adoptStyleSheet(STYLE_ID, BLOOM_SLIDER_CSS);
|
|
@@ -365,11 +372,14 @@ function SliderBase({
|
|
|
365
372
|
return (
|
|
366
373
|
<View
|
|
367
374
|
testID={testID}
|
|
375
|
+
nativeID={field.nativeID}
|
|
376
|
+
aria-describedby={field.describedBy}
|
|
377
|
+
aria-invalid={field.invalid || undefined}
|
|
368
378
|
{...(isRange
|
|
369
|
-
? { accessibilityLabel: accessibilityLabel
|
|
379
|
+
? { accessibilityLabel: field.accessibilityLabel }
|
|
370
380
|
: {
|
|
371
381
|
accessibilityRole: 'adjustable' as const,
|
|
372
|
-
accessibilityLabel: accessibilityLabel
|
|
382
|
+
accessibilityLabel: field.accessibilityLabel,
|
|
373
383
|
// react-native-web reads the FLAT `aria-value*` props; it has no
|
|
374
384
|
// handling for the `accessibilityValue` object at all, so this
|
|
375
385
|
// rendered a `role="slider"` carrying no value whatsoever. React
|
package/src/stepper/Stepper.tsx
CHANGED
|
@@ -14,6 +14,7 @@ import { useTheme } from '../theme/use-theme';
|
|
|
14
14
|
import { Text } from '../typography';
|
|
15
15
|
import type { TypeScaleVariant } from '../typography/scale';
|
|
16
16
|
import type { StepperProps, StepperSize } from './types';
|
|
17
|
+
import { useFieldMembership } from '../field/membership';
|
|
17
18
|
|
|
18
19
|
/**
|
|
19
20
|
* Bloom's numeric counter: a round `−` button, the value, a round `+` button.
|
|
@@ -77,7 +78,7 @@ function StepperComponent({
|
|
|
77
78
|
min = 0,
|
|
78
79
|
max,
|
|
79
80
|
step = 1,
|
|
80
|
-
disabled = false,
|
|
81
|
+
disabled: disabledProp = false,
|
|
81
82
|
size = 'medium',
|
|
82
83
|
formatValue,
|
|
83
84
|
accessibilityLabel,
|
|
@@ -88,7 +89,12 @@ function StepperComponent({
|
|
|
88
89
|
}: StepperProps) {
|
|
89
90
|
const theme = useTheme();
|
|
90
91
|
const ringOffset = useRingOffsetStyle();
|
|
91
|
-
|
|
92
|
+
// The digits are not a name, so the stepper is named by a prop or by the
|
|
93
|
+
// enclosing `Field` — and the warning has to see the resolved name, or a
|
|
94
|
+
// field-labelled stepper warns anyway.
|
|
95
|
+
const field = useFieldMembership({ accessibilityLabel, disabled: disabledProp });
|
|
96
|
+
const disabled = field.disabled;
|
|
97
|
+
useAccessibleNameWarning('Stepper', field.accessibilityLabel);
|
|
92
98
|
useInteractiveWebCss(STYLE_ID, BLOOM_STEPPER_CSS);
|
|
93
99
|
const { accent } = useMemo(() => resolveButtonRamps(theme), [theme]);
|
|
94
100
|
const config = SIZE_CONFIG[size];
|
|
@@ -166,7 +172,9 @@ function StepperComponent({
|
|
|
166
172
|
<View
|
|
167
173
|
testID={testID}
|
|
168
174
|
role="group"
|
|
169
|
-
|
|
175
|
+
nativeID={field.nativeID}
|
|
176
|
+
accessibilityLabel={field.accessibilityLabel}
|
|
177
|
+
aria-describedby={field.describedBy}
|
|
170
178
|
aria-disabled={disabled || undefined}
|
|
171
179
|
style={[{ flexDirection: 'row', alignItems: 'center', gap: config.gap }, style]}
|
|
172
180
|
>
|
|
@@ -183,7 +191,7 @@ function StepperComponent({
|
|
|
183
191
|
/>
|
|
184
192
|
<View
|
|
185
193
|
accessibilityRole="adjustable"
|
|
186
|
-
accessibilityLabel={accessibilityLabel}
|
|
194
|
+
accessibilityLabel={field.accessibilityLabel}
|
|
187
195
|
aria-valuemin={min}
|
|
188
196
|
aria-valuemax={max}
|
|
189
197
|
aria-valuenow={value}
|
package/src/stepper/types.ts
CHANGED
|
@@ -26,7 +26,14 @@ export interface StepperProps {
|
|
|
26
26
|
* number and two glyphs, so nothing else can tell a screen reader which of
|
|
27
27
|
* several counters this is.
|
|
28
28
|
*/
|
|
29
|
-
|
|
29
|
+
/**
|
|
30
|
+
* The control's accessible name.
|
|
31
|
+
*
|
|
32
|
+
* Optional only because an enclosing `Field`'s label can supply it — with
|
|
33
|
+
* neither, the control announces nothing and warns once in development
|
|
34
|
+
* (`hooks/use-accessible-name-warning.ts`).
|
|
35
|
+
*/
|
|
36
|
+
accessibilityLabel?: string;
|
|
30
37
|
/** Name of the `−` button. Default `"Decrease"`. */
|
|
31
38
|
decrementLabel?: string;
|
|
32
39
|
/** Name of the `+` button. Default `"Increase"`. */
|
|
@@ -73,6 +73,22 @@ export interface WebAriaProps {
|
|
|
73
73
|
* currently needs; the union is the ARIA one, narrowed to what is spellable.
|
|
74
74
|
*/
|
|
75
75
|
'aria-haspopup'?: 'menu' | 'listbox' | 'tree' | 'grid' | 'dialog';
|
|
76
|
+
/**
|
|
77
|
+
* The id list of the elements that DESCRIBE this control — a field's hint or
|
|
78
|
+
* its error message (`field/context.ts`).
|
|
79
|
+
*
|
|
80
|
+
* Here for the same reason as `aria-haspopup`: React Native types no such prop
|
|
81
|
+
* (there is no `accessibilityState` entry for it to fold into) while
|
|
82
|
+
* react-native-web forwards it, so web gets the association and native is
|
|
83
|
+
* unaffected. A control that needs the description READ on native carries it
|
|
84
|
+
* in its accessible name instead.
|
|
85
|
+
*/
|
|
86
|
+
'aria-describedby'?: string;
|
|
87
|
+
/**
|
|
88
|
+
* Whether the control's current value is invalid. Same platform split as
|
|
89
|
+
* `aria-describedby`.
|
|
90
|
+
*/
|
|
91
|
+
'aria-invalid'?: boolean;
|
|
76
92
|
}
|
|
77
93
|
|
|
78
94
|
/**
|
package/src/switch/Switch.tsx
CHANGED
|
@@ -5,6 +5,7 @@ import { useTheme } from '../theme/use-theme';
|
|
|
5
5
|
import { animation } from '../styles/tokens';
|
|
6
6
|
import { bloomShadowStyle } from '../design-tokens/shadows';
|
|
7
7
|
import { useAccessibleNameWarning } from '../hooks/use-accessible-name-warning';
|
|
8
|
+
import { useFieldMembership } from '../field/membership';
|
|
8
9
|
import type { SwitchProps } from './types';
|
|
9
10
|
|
|
10
11
|
const TRACK = { default: { w: 44, h: 26 }, sm: { w: 36, h: 22 } } as const;
|
|
@@ -13,9 +14,18 @@ const PADDING = 2;
|
|
|
13
14
|
const SQUEEZE_RATIO = 0.75; // thumb height shrinks to 75% when pressed
|
|
14
15
|
|
|
15
16
|
const SwitchComponent = React.forwardRef<React.ElementRef<typeof Pressable>, SwitchProps>(
|
|
16
|
-
(
|
|
17
|
+
(
|
|
18
|
+
{ value, onValueChange, disabled, style, size = 'default', accessibilityLabel, nativeID, testID },
|
|
19
|
+
ref,
|
|
20
|
+
) => {
|
|
17
21
|
const theme = useTheme();
|
|
18
|
-
|
|
22
|
+
// A switch draws no text, so a `Field` is often the only thing that names
|
|
23
|
+
// it — and the warning has to see the RESOLVED name, or a correctly
|
|
24
|
+
// labelled switch inside a field warns anyway and the warning stops
|
|
25
|
+
// meaning anything.
|
|
26
|
+
const field = useFieldMembership({ accessibilityLabel, disabled, nativeID });
|
|
27
|
+
const isDisabled = field.disabled;
|
|
28
|
+
useAccessibleNameWarning('Switch', field.accessibilityLabel);
|
|
19
29
|
const anim = useRef(new Animated.Value(value ? 1 : 0)).current;
|
|
20
30
|
const pressAnim = useRef(new Animated.Value(0)).current;
|
|
21
31
|
|
|
@@ -32,18 +42,18 @@ const SwitchComponent = React.forwardRef<React.ElementRef<typeof Pressable>, Swi
|
|
|
32
42
|
}
|
|
33
43
|
|
|
34
44
|
const handlePress = useCallback(() => {
|
|
35
|
-
if (
|
|
45
|
+
if (isDisabled) return;
|
|
36
46
|
onValueChange(!value);
|
|
37
|
-
}, [
|
|
47
|
+
}, [isDisabled, value, onValueChange]);
|
|
38
48
|
|
|
39
49
|
const onPressIn = useCallback(() => {
|
|
40
|
-
if (
|
|
50
|
+
if (isDisabled) return;
|
|
41
51
|
Animated.spring(pressAnim, {
|
|
42
52
|
toValue: 1,
|
|
43
53
|
useNativeDriver: false,
|
|
44
54
|
...animation.spring.snappy,
|
|
45
55
|
}).start();
|
|
46
|
-
}, [
|
|
56
|
+
}, [isDisabled, pressAnim]);
|
|
47
57
|
|
|
48
58
|
const onPressOut = useCallback(() => {
|
|
49
59
|
Animated.spring(pressAnim, {
|
|
@@ -91,18 +101,21 @@ const SwitchComponent = React.forwardRef<React.ElementRef<typeof Pressable>, Swi
|
|
|
91
101
|
// `createDOMProps` emits `aria-label` from it, React Native reads it
|
|
92
102
|
// directly. That is NOT the rule for `accessibilityState` two lines up,
|
|
93
103
|
// which reaches native alone; the state and the name differ here.
|
|
94
|
-
accessibilityLabel={accessibilityLabel}
|
|
104
|
+
accessibilityLabel={field.accessibilityLabel}
|
|
105
|
+
nativeID={field.nativeID}
|
|
106
|
+
aria-describedby={field.describedBy}
|
|
107
|
+
aria-invalid={field.invalid || undefined}
|
|
95
108
|
// The disabled state has to travel on this prop, not on `aria-disabled`:
|
|
96
109
|
// react-native-web's `Pressable` appends its own `aria-disabled` from
|
|
97
110
|
// `disabled` AFTER spreading the caller's props, so a caller-supplied
|
|
98
111
|
// one is overwritten. `handlePress` already no-ops when disabled, so
|
|
99
112
|
// this only adds what was missing — the announced state, and skipping
|
|
100
113
|
// the control in the tab order.
|
|
101
|
-
disabled={
|
|
114
|
+
disabled={isDisabled}
|
|
102
115
|
onPress={handlePress}
|
|
103
116
|
onPressIn={onPressIn}
|
|
104
117
|
onPressOut={onPressOut}
|
|
105
|
-
style={[
|
|
118
|
+
style={[isDisabled && styles.disabled, style]}
|
|
106
119
|
hitSlop={HIT_SLOP}
|
|
107
120
|
testID={testID}
|
|
108
121
|
>
|
package/src/switch/types.ts
CHANGED
|
@@ -26,7 +26,14 @@ export interface SwitchProps {
|
|
|
26
26
|
* are not the same rule.)
|
|
27
27
|
*
|
|
28
28
|
* Omitting it warns once in development; see `use-accessible-name-warning`.
|
|
29
|
+
* Inside a `Field` the field's label supplies the name, so a switch in a
|
|
30
|
+
* labelled field needs nothing here and does not warn.
|
|
29
31
|
*/
|
|
30
32
|
accessibilityLabel?: string;
|
|
33
|
+
/**
|
|
34
|
+
* The switch's id. Inside a `Field` the field supplies one, so the label's
|
|
35
|
+
* `htmlFor` points at the control on web.
|
|
36
|
+
*/
|
|
37
|
+
nativeID?: string;
|
|
31
38
|
testID?: string;
|
|
32
39
|
}
|
|
@@ -53,6 +53,8 @@ import type {
|
|
|
53
53
|
TextFieldLabelProps,
|
|
54
54
|
TextFieldProps,
|
|
55
55
|
} from './types';
|
|
56
|
+
import { useInheritedControl } from '../control-surface';
|
|
57
|
+
import { useFieldMembership } from '../field/membership';
|
|
56
58
|
|
|
57
59
|
interface TextFieldContextValue {
|
|
58
60
|
inputRef: React.RefObject<TextInput | null>;
|
|
@@ -153,7 +155,7 @@ export function TextField({
|
|
|
153
155
|
children,
|
|
154
156
|
isInvalid: isInvalidProp = false,
|
|
155
157
|
disabled: disabledProp = false,
|
|
156
|
-
size: sizeProp
|
|
158
|
+
size: sizeProp,
|
|
157
159
|
radius = TEXT_FIELD_RADIUS,
|
|
158
160
|
leadingAddon,
|
|
159
161
|
style,
|
|
@@ -161,9 +163,19 @@ export function TextField({
|
|
|
161
163
|
const theme = useTheme();
|
|
162
164
|
const group = useContext(TextFieldGroupContext);
|
|
163
165
|
const bare = group !== null;
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
166
|
+
// The enclosing `Field`, read HERE as well as in `TextFieldInput`: the shell
|
|
167
|
+
// is what paints the disabled and invalid chrome, and a shell that waited for
|
|
168
|
+
// its input to report up would paint nothing at all around a child that is not
|
|
169
|
+
// Bloom's input. Both directions are OR, so the two readings cannot disagree.
|
|
170
|
+
const member = useFieldMembership({ disabled: disabledProp, invalid: isInvalidProp });
|
|
171
|
+
const isInvalid = member.invalid || (group?.isInvalid ?? false);
|
|
172
|
+
const disabled = member.disabled || (group?.disabled ?? false);
|
|
173
|
+
// An `InputGroup` outranks everything: its members have to be one height. Then
|
|
174
|
+
// the caller's own prop, then the container's density (`ControlSurface`), then
|
|
175
|
+
// `medium` — the precedence rule in `docs/composition.mdx`, with the group's
|
|
176
|
+
// geometry first because it is a constraint rather than a default.
|
|
177
|
+
const inheritedSize = useInheritedControl('density', sizeProp, 'medium');
|
|
178
|
+
const size = group?.size ?? inheritedSize;
|
|
167
179
|
const inputRef = useRef<TextInput>(null);
|
|
168
180
|
const [inputDisabled, setInputDisabled] = useState(false);
|
|
169
181
|
const {
|
|
@@ -19,6 +19,8 @@ import {
|
|
|
19
19
|
resolveShellPaint,
|
|
20
20
|
useTextFieldPalette,
|
|
21
21
|
} from '../text-field/shared';
|
|
22
|
+
import { useInheritedControl } from '../control-surface';
|
|
23
|
+
import { useFieldMembership } from '../field/membership';
|
|
22
24
|
import type { TextareaProps } from './types';
|
|
23
25
|
|
|
24
26
|
const IS_WEB = Platform.OS === 'web';
|
|
@@ -52,7 +54,7 @@ export function Textarea({
|
|
|
52
54
|
onChangeText,
|
|
53
55
|
onFocus,
|
|
54
56
|
onBlur,
|
|
55
|
-
size
|
|
57
|
+
size: sizeProp,
|
|
56
58
|
rows = 3,
|
|
57
59
|
autoResize = false,
|
|
58
60
|
maxRows,
|
|
@@ -67,10 +69,29 @@ export function Textarea({
|
|
|
67
69
|
inputStyle,
|
|
68
70
|
inputRef,
|
|
69
71
|
accessibilityLabel,
|
|
72
|
+
nativeID,
|
|
70
73
|
testID,
|
|
71
74
|
...rest
|
|
72
75
|
}: TextareaProps) {
|
|
73
76
|
const theme = useTheme();
|
|
77
|
+
// The two contracts a textarea sits inside: a container's density
|
|
78
|
+
// (`ControlSurface`) and an enclosing `Field`'s association. The label is
|
|
79
|
+
// STACKED here, as it is on `TextField`, so the field's label wins — they are
|
|
80
|
+
// two spellings of one thing and rendering both is the mistake `docs/field.mdx`
|
|
81
|
+
// names.
|
|
82
|
+
const size = useInheritedControl('density', sizeProp, 'medium');
|
|
83
|
+
const field = useFieldMembership({
|
|
84
|
+
accessibilityLabel,
|
|
85
|
+
label: label ?? placeholder,
|
|
86
|
+
disabled,
|
|
87
|
+
invalid: isInvalid,
|
|
88
|
+
required,
|
|
89
|
+
nativeID,
|
|
90
|
+
// An `aria-describedby` the caller spread through `rest` is KEPT and the
|
|
91
|
+
// field's is added to it, the same way `TextFieldInput` treats it: the two
|
|
92
|
+
// are an id list, not a choice.
|
|
93
|
+
describedBy: (rest as Record<string, unknown>)['aria-describedby'] as string | undefined,
|
|
94
|
+
});
|
|
74
95
|
const palette = useTextFieldPalette();
|
|
75
96
|
const innerRef = useRef<TextInput>(null);
|
|
76
97
|
const { state: hovered, onIn: onHoverIn, onOut: onHoverOut } = useInteractionState();
|
|
@@ -82,7 +103,7 @@ export function Textarea({
|
|
|
82
103
|
const [typedLength, setTypedLength] = useState(() => (defaultValue ?? '').length);
|
|
83
104
|
const count = value !== undefined ? value.length : typedLength;
|
|
84
105
|
|
|
85
|
-
const state = { hovered, focused, invalid:
|
|
106
|
+
const state = { hovered, focused, invalid: field.invalid, disabled: field.disabled };
|
|
86
107
|
const paint = resolveShellPaint(palette, state);
|
|
87
108
|
const line = TEXT_FIELD_TEXT.lineHeight;
|
|
88
109
|
|
|
@@ -92,7 +113,7 @@ export function Textarea({
|
|
|
92
113
|
fontSize: TEXT_FIELD_TEXT.fontSize,
|
|
93
114
|
lineHeight: line,
|
|
94
115
|
fontWeight: TEXT_FIELD_TEXT.fontWeight,
|
|
95
|
-
color: disabled ? palette.textDisabled : palette.text,
|
|
116
|
+
color: field.disabled ? palette.textDisabled : palette.text,
|
|
96
117
|
paddingLeft: TEXT_FIELD_INPUT_INSET,
|
|
97
118
|
paddingRight: TEXT_FIELD_INPUT_INSET,
|
|
98
119
|
paddingTop: 0,
|
|
@@ -114,18 +135,16 @@ export function Textarea({
|
|
|
114
135
|
outlineStyle: 'none',
|
|
115
136
|
resize: autoResize ? 'none' : resize,
|
|
116
137
|
fieldSizing: autoResize ? 'content' : undefined,
|
|
117
|
-
cursor: disabled ? 'not-allowed' : undefined,
|
|
138
|
+
cursor: field.disabled ? 'not-allowed' : undefined,
|
|
118
139
|
} as unknown as TextStyle)
|
|
119
140
|
: undefined,
|
|
120
141
|
inputStyle,
|
|
121
142
|
]) as TextStyle;
|
|
122
143
|
|
|
123
|
-
const name = label ?? accessibilityLabel ?? placeholder;
|
|
124
|
-
|
|
125
144
|
return (
|
|
126
145
|
<View style={[{ width: '100%' }, style]} testID={testID}>
|
|
127
146
|
{label ? (
|
|
128
|
-
<TextFieldLabel required={required} tooltip={tooltip}>
|
|
147
|
+
<TextFieldLabel required={field.required} tooltip={tooltip}>
|
|
129
148
|
{label}
|
|
130
149
|
</TextFieldLabel>
|
|
131
150
|
) : null}
|
|
@@ -143,24 +162,28 @@ export function Textarea({
|
|
|
143
162
|
]}
|
|
144
163
|
{...(IS_WEB
|
|
145
164
|
? ({
|
|
146
|
-
onClick: () =>
|
|
165
|
+
onClick: () => {
|
|
166
|
+
if (!field.disabled) innerRef.current?.focus();
|
|
167
|
+
},
|
|
147
168
|
onMouseEnter: onHoverIn,
|
|
148
169
|
onMouseLeave: onHoverOut,
|
|
149
170
|
} as Record<string, unknown>)
|
|
150
171
|
: {})}>
|
|
151
172
|
<TextInput
|
|
152
173
|
{...rest}
|
|
153
|
-
{...(IS_WEB && disabled ? ({ disabled: true } as Record<string, unknown>) : {})}
|
|
174
|
+
{...(IS_WEB && field.disabled ? ({ disabled: true } as Record<string, unknown>) : {})}
|
|
154
175
|
ref={mergeRefs([innerRef, inputRef])}
|
|
155
176
|
multiline
|
|
156
177
|
numberOfLines={rows}
|
|
157
178
|
value={value}
|
|
158
179
|
defaultValue={defaultValue}
|
|
159
180
|
maxLength={maxLength}
|
|
160
|
-
editable={disabled ? false : rest.editable}
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
aria-
|
|
181
|
+
editable={field.disabled ? false : rest.editable}
|
|
182
|
+
nativeID={field.nativeID}
|
|
183
|
+
accessibilityLabel={field.accessibilityLabel}
|
|
184
|
+
aria-describedby={field.describedBy}
|
|
185
|
+
aria-invalid={field.invalid || undefined}
|
|
186
|
+
aria-disabled={field.disabled || undefined}
|
|
164
187
|
placeholder={placeholder}
|
|
165
188
|
placeholderTextColor={resolvePlaceholderColor(palette, state)}
|
|
166
189
|
keyboardAppearance={theme.isDark ? 'dark' : 'light'}
|
|
@@ -190,7 +213,7 @@ export function Textarea({
|
|
|
190
213
|
marginTop: TEXT_FIELD_STACK_GAP,
|
|
191
214
|
}}>
|
|
192
215
|
{hint ? (
|
|
193
|
-
<TextFieldHint isInvalid={
|
|
216
|
+
<TextFieldHint isInvalid={field.invalid} style={{ marginTop: 0 }}>
|
|
194
217
|
{hint}
|
|
195
218
|
</TextFieldHint>
|
|
196
219
|
) : null}
|
|
@@ -202,7 +225,7 @@ export function Textarea({
|
|
|
202
225
|
flexShrink: 0,
|
|
203
226
|
paddingTop: 1,
|
|
204
227
|
fontVariant: ['tabular-nums'],
|
|
205
|
-
color:
|
|
228
|
+
color: field.invalid ? palette.error : palette.count,
|
|
206
229
|
}}>
|
|
207
230
|
{maxLength ? `${count}/${maxLength}` : String(count)}
|
|
208
231
|
</Text>
|