@box/blueprint-web 16.19.2 → 16.20.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.
@@ -11,6 +11,7 @@ import '@internationalized/date';
11
11
  import { IconButton } from '../primitives/icon-button/icon-button.js';
12
12
  import { InlineError } from '../primitives/inline-error/inline-error.js';
13
13
  import { useLabelable } from '../util-components/labelable/useLabelable.js';
14
+ import { useControllableState } from '../utils/useControllableState.js';
14
15
  import { useUniqueId } from '../utils/useUniqueId.js';
15
16
  import styles from './date-picker.module.js';
16
17
 
@@ -38,6 +39,8 @@ const DatePicker = /*#__PURE__*/forwardRef((props, forwardedRef) => {
38
39
  openCalendarDropdownAriaLabel,
39
40
  clearDatePickerAriaLabel,
40
41
  error,
42
+ isOpen: isOpenProp,
43
+ onOpenChange,
41
44
  defaultOpen = false,
42
45
  isDisabled,
43
46
  calendarAriaLabel,
@@ -55,10 +58,17 @@ const DatePicker = /*#__PURE__*/forwardRef((props, forwardedRef) => {
55
58
  setInternalValue(value);
56
59
  }
57
60
  }, [value]);
58
- const [isCalendarOpen, setIsCalendarOpen] = useState(defaultOpen);
59
- const firstDateSegmentRef = useRef(null);
61
+ const [isCalendarOpen = false, setIsCalendarOpen] = useControllableState({
62
+ prop: isOpenProp,
63
+ defaultProp: defaultOpen,
64
+ onChange: onOpenChange
65
+ });
66
+ const dateInputRef = useRef(null);
60
67
  const calendarButtonRef = useRef(null);
61
68
  const shouldFocusStayInInput = useRef(false);
69
+ const focusFirstDateSegment = useCallback(() => {
70
+ dateInputRef.current?.querySelector('[role="spinbutton"]')?.focus();
71
+ }, []);
62
72
  const onDatePickerValueChanged = useCallback(newValue => {
63
73
  onChange?.(newValue);
64
74
  if (value === undefined) {
@@ -73,23 +83,26 @@ const DatePicker = /*#__PURE__*/forwardRef((props, forwardedRef) => {
73
83
  }, [onChange, value]);
74
84
  const onCalendarDateSelect = useCallback(() => {
75
85
  setIsCalendarOpen(false);
76
- }, []);
86
+ }, [setIsCalendarOpen]);
77
87
  const onPressCalendar = useCallback(() => {
78
- setIsCalendarOpen(v => !v);
79
88
  shouldFocusStayInInput.current = false;
80
- }, []);
89
+ setIsCalendarOpen(isOpen => !isOpen);
90
+ }, [setIsCalendarOpen]);
91
+ const onPopoverOpenChange = useCallback(isOpen => {
92
+ setIsCalendarOpen(isOpen);
93
+ }, [setIsCalendarOpen]);
81
94
  const onClearButtonKeypress = useCallback(({
82
95
  key
83
96
  }) => {
84
97
  handleSubmitKeyPress(key, () => {
85
98
  onClearInputButtonClicked();
86
- firstDateSegmentRef.current?.focus();
99
+ focusFirstDateSegment();
87
100
  });
88
- }, [onClearInputButtonClicked]);
101
+ }, [focusFirstDateSegment, onClearInputButtonClicked]);
89
102
  const onDateInputClick = useCallback(() => {
90
103
  setIsCalendarOpen(true);
91
104
  shouldFocusStayInInput.current = true;
92
- }, []);
105
+ }, [setIsCalendarOpen]);
93
106
  const hasError = !!error && !isDisabled;
94
107
  const calendarKey = `${internalValue?.day}/${internalValue?.month}/${internalValue?.year}${internalValue?.era}`;
95
108
  const onOpenAutoFocus = useCallback(e => {
@@ -104,15 +117,19 @@ const DatePicker = /*#__PURE__*/forwardRef((props, forwardedRef) => {
104
117
  } = document;
105
118
  const isFocusLost = !activeElement || activeElement === document.body || activeElement === document.documentElement || isRadixFocusGuard(activeElement);
106
119
  const focusAlreadyOutsidePopover = !isFocusLost && !(e.currentTarget instanceof Node && e.currentTarget.contains(activeElement));
120
+ // Clear after close-focus uses it so a later open does not keep suppressing autofocus,
121
+ // without racing Esc/date-select focus restore (which needs the flag during this callback).
122
+ const shouldKeepFocusInInput = shouldFocusStayInInput.current;
123
+ shouldFocusStayInInput.current = false;
107
124
  if (focusAlreadyOutsidePopover) {
108
125
  return;
109
126
  }
110
- if (shouldFocusStayInInput.current) {
111
- firstDateSegmentRef.current?.focus();
127
+ if (shouldKeepFocusInInput) {
128
+ focusFirstDateSegment();
112
129
  } else {
113
130
  calendarButtonRef.current?.focus();
114
131
  }
115
- }, []);
132
+ }, [focusFirstDateSegment]);
116
133
  const onFocusOutside = useCallback(e => {
117
134
  const wasCalendarButtonPressed = calendarButtonRef.current && calendarButtonRef.current.contains(e.target);
118
135
  if (!wasCalendarButtonPressed) {
@@ -126,7 +143,7 @@ const DatePicker = /*#__PURE__*/forwardRef((props, forwardedRef) => {
126
143
  [inlineErrorId]: hasError
127
144
  });
128
145
  return jsx(RadixPopover.Root, {
129
- onOpenChange: setIsCalendarOpen,
146
+ onOpenChange: onPopoverOpenChange,
130
147
  open: isCalendarOpen,
131
148
  children: jsxs(DatePicker$1, {
132
149
  ...rest,
@@ -151,6 +168,7 @@ const DatePicker = /*#__PURE__*/forwardRef((props, forwardedRef) => {
151
168
  className: clsx(styles.group),
152
169
  "data-target-id": rest.dataTargetId,
153
170
  children: [jsx("div", {
171
+ ref: dateInputRef,
154
172
  className: clsx(styles.groupContainer, {
155
173
  [styles.error]: hasError
156
174
  }),
@@ -159,11 +177,6 @@ const DatePicker = /*#__PURE__*/forwardRef((props, forwardedRef) => {
159
177
  children: jsx(DateInput, {
160
178
  className: clsx(styles.groupContainerInput),
161
179
  children: segment => jsx(DateSegment, {
162
- ref: node => {
163
- if (!firstDateSegmentRef.current) {
164
- firstDateSegmentRef.current = node;
165
- }
166
- },
167
180
  className: styles.groupContainerInputSegment,
168
181
  segment: segment
169
182
  })
@@ -3,7 +3,7 @@ import { type CalendarBase } from '../primitives/calendar/types';
3
3
  import { type InputValueProps } from '../types/input-value-props';
4
4
  import { type Modify } from '../types/modify';
5
5
  import { type Labelable } from '../util-components/labelable/types';
6
- interface DatePickerPropsBase extends Omit<RACDatePickerProps<DateValue>, 'children'>, Labelable {
6
+ interface DatePickerPropsBase extends Omit<RACDatePickerProps<DateValue>, 'children' | 'isOpen' | 'onOpenChange' | 'defaultOpen'>, Labelable {
7
7
  /**
8
8
  * When `true`, label text is hidden.
9
9
  *
@@ -23,7 +23,15 @@ interface DatePickerPropsBase extends Omit<RACDatePickerProps<DateValue>, 'child
23
23
  */
24
24
  error?: React.ReactNode;
25
25
  /**
26
- * controls whether calendar dialog is initially opened
26
+ * Controlled popover open state.
27
+ */
28
+ isOpen?: boolean;
29
+ /**
30
+ * Called when the calendar popover open state changes.
31
+ */
32
+ onOpenChange?: (isOpen: boolean) => void;
33
+ /**
34
+ * Controls whether calendar dialog is initially opened in uncontrolled mode.
27
35
  */
28
36
  defaultOpen?: boolean;
29
37
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@box/blueprint-web",
3
- "version": "16.19.2",
3
+ "version": "16.20.0",
4
4
  "type": "module",
5
5
  "license": "SEE LICENSE IN LICENSE",
6
6
  "publishConfig": {
@@ -50,7 +50,7 @@
50
50
  "dependencies": {
51
51
  "@ariakit/react": "0.4.21",
52
52
  "@ariakit/react-core": "0.4.21",
53
- "@box/blueprint-web-assets": "^5.7.6",
53
+ "@box/blueprint-web-assets": "^5.7.7",
54
54
  "@internationalized/date": "^3.12.0",
55
55
  "@radix-ui/react-accordion": "1.1.2",
56
56
  "@radix-ui/react-checkbox": "1.0.4",
@@ -79,7 +79,7 @@
79
79
  "type-fest": "^3.2.0"
80
80
  },
81
81
  "devDependencies": {
82
- "@box/storybook-utils": "^1.2.15",
82
+ "@box/storybook-utils": "^1.2.16",
83
83
  "@figma/code-connect": "1.4.4",
84
84
  "@types/react": "^18.0.0",
85
85
  "@types/react-dom": "^18.0.0",