@carbon/react 1.113.0 → 1.114.0-rc.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.
Files changed (51) hide show
  1. package/.playwright/INTERNAL_AVT_REPORT_DO_NOT_USE.json +901 -901
  2. package/es/components/DatePicker/DatePicker.d.ts +10 -0
  3. package/es/components/DatePicker/DatePicker.js +6 -5
  4. package/es/components/DatePicker/next/components/Calendar.d.ts +40 -0
  5. package/es/components/DatePicker/next/components/Calendar.js +181 -0
  6. package/es/components/DatePicker/next/components/DatePicker.d.ts +86 -0
  7. package/es/components/DatePicker/next/components/DatePicker.js +147 -0
  8. package/es/components/DatePicker/next/components/DatePickerInput.d.ts +143 -0
  9. package/es/components/DatePicker/next/components/DatePickerInput.js +124 -0
  10. package/es/components/DatePicker/next/components/DatePickerSkeleton.d.ts +22 -0
  11. package/es/components/DatePicker/next/components/DatePickerSkeleton.js +32 -0
  12. package/es/components/DatePicker/next/hooks/useDatePicker.d.ts +144 -0
  13. package/es/components/DatePicker/next/hooks/useDatePicker.js +286 -0
  14. package/es/components/DatePicker/next/index.d.ts +12 -0
  15. package/es/components/DatePicker/next/index.js +19 -0
  16. package/es/index.d.ts +1 -0
  17. package/es/index.js +2 -1
  18. package/es/internal/checkForOverflow.d.ts +5 -0
  19. package/es/internal/useFocus.d.ts +23 -0
  20. package/es/internal/usePrefersReducedMotion.d.ts +11 -0
  21. package/es/internal/useSidePanelPresence.d.ts +21 -0
  22. package/lib/components/DatePicker/DatePicker.d.ts +10 -0
  23. package/lib/components/DatePicker/DatePicker.js +6 -5
  24. package/lib/components/DatePicker/next/components/Calendar.d.ts +40 -0
  25. package/lib/components/DatePicker/next/components/Calendar.js +184 -0
  26. package/lib/components/DatePicker/next/components/DatePicker.d.ts +86 -0
  27. package/lib/components/DatePicker/next/components/DatePicker.js +150 -0
  28. package/lib/components/DatePicker/next/components/DatePickerInput.d.ts +143 -0
  29. package/lib/components/DatePicker/next/components/DatePickerInput.js +127 -0
  30. package/lib/components/DatePicker/next/components/DatePickerSkeleton.d.ts +22 -0
  31. package/lib/components/DatePicker/next/components/DatePickerSkeleton.js +41 -0
  32. package/lib/components/DatePicker/next/hooks/useDatePicker.d.ts +144 -0
  33. package/lib/components/DatePicker/next/hooks/useDatePicker.js +286 -0
  34. package/lib/components/DatePicker/next/index.d.ts +12 -0
  35. package/lib/components/DatePicker/next/index.js +27 -0
  36. package/lib/index.d.ts +1 -0
  37. package/lib/index.js +29 -22
  38. package/lib/internal/checkForOverflow.d.ts +5 -0
  39. package/lib/internal/useFocus.d.ts +23 -0
  40. package/lib/internal/usePrefersReducedMotion.d.ts +11 -0
  41. package/lib/internal/useSidePanelPresence.d.ts +21 -0
  42. package/package.json +11 -11
  43. package/scss/components/action-set/_action-set.scss +9 -0
  44. package/scss/components/action-set/_index.scss +9 -0
  45. package/scss/components/resizer/_index.scss +9 -0
  46. package/scss/components/resizer/_resizer.scss +9 -0
  47. package/scss/components/side-panel/_index.scss +9 -0
  48. package/scss/components/side-panel/_side-panel.scss +9 -0
  49. package/scss/components/truncated-text/_index.scss +9 -0
  50. package/scss/components/truncated-text/_truncated-text.scss +9 -0
  51. package/telemetry.yml +41 -1
@@ -0,0 +1,124 @@
1
+ /**
2
+ * Copyright IBM Corp. 2016, 2026
3
+ *
4
+ * This source code is licensed under the Apache-2.0 license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ */
7
+
8
+ import { usePrefix } from "../../../../internal/usePrefix.js";
9
+ import { cloneElement, forwardRef, isValidElement } from "react";
10
+ import cx from "classnames";
11
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
12
+ //#region src/components/DatePicker/next/components/DatePickerInput.tsx
13
+ /**
14
+ * Copyright IBM Corp. 2026
15
+ *
16
+ * This source code is licensed under the Apache-2.0 license found in the
17
+ * LICENSE file in the root directory of this source tree.
18
+ */
19
+ /**
20
+ * DatePickerInput component
21
+ * Input field for date picker with Carbon Design System styling
22
+ * Maintains 100% backwards compatibility with Carbon React v11
23
+ */
24
+ const DatePickerInput = forwardRef(function DatePickerInput({ id, labelText, placeholder = "mm/dd/yyyy", size = "md", disabled = false, invalid = false, invalidText, warn = false, warnText, helperText, hideLabel = false, className, value, defaultValue, onChange, onFocus, onBlur, onKeyDown, onClick, iconDescription = "Open calendar", onIconClick, hideIcon = false, pattern, type = "text", readOnly = false, autoComplete = "off", name, required = false, onOpen: _onOpen, decorator, slug, ...rest }, ref) {
25
+ const prefix = usePrefix();
26
+ const candidate = decorator || slug;
27
+ const normalizedDecorator = candidate && isValidElement(candidate) && candidate.type?.displayName === "AILabel" && isValidElement(candidate) ? cloneElement(candidate, { size: "mini" }) : candidate;
28
+ const wrapperClasses = cx(`${prefix}--date-picker-input__wrapper`, className, {
29
+ [`${prefix}--date-picker-input__wrapper--slug`]: slug,
30
+ [`${prefix}--date-picker-input__wrapper--decorator`]: decorator
31
+ });
32
+ const inputClasses = cx(`${prefix}--date-picker__input`, {
33
+ [`${prefix}--date-picker__input--sm`]: size === "sm",
34
+ [`${prefix}--date-picker__input--lg`]: size === "lg",
35
+ [`${prefix}--date-picker__input--invalid`]: invalid,
36
+ [`${prefix}--date-picker__input--warn`]: warn && !invalid
37
+ });
38
+ const labelClasses = cx(`${prefix}--label`, {
39
+ [`${prefix}--visually-hidden`]: hideLabel,
40
+ [`${prefix}--label--disabled`]: disabled
41
+ });
42
+ const showInvalidText = invalid && invalidText;
43
+ const showWarnText = !invalid && warn && warnText;
44
+ const showHelperText = !invalid && !warn && helperText;
45
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
46
+ labelText && /* @__PURE__ */ jsxs("label", {
47
+ htmlFor: id,
48
+ className: labelClasses,
49
+ children: [labelText, required && /* @__PURE__ */ jsx("span", {
50
+ className: `${prefix}--label__required`,
51
+ children: "*"
52
+ })]
53
+ }),
54
+ /* @__PURE__ */ jsx("div", {
55
+ className: wrapperClasses,
56
+ children: /* @__PURE__ */ jsxs("span", { children: [
57
+ /* @__PURE__ */ jsx("input", {
58
+ ...rest,
59
+ ref,
60
+ id,
61
+ name,
62
+ type,
63
+ className: inputClasses,
64
+ placeholder,
65
+ value,
66
+ defaultValue,
67
+ disabled,
68
+ readOnly,
69
+ pattern,
70
+ autoComplete,
71
+ required,
72
+ onChange,
73
+ onFocus,
74
+ onBlur,
75
+ onKeyDown,
76
+ onClick,
77
+ "aria-invalid": invalid || void 0,
78
+ "aria-describedby": showInvalidText ? `${id}-error` : showWarnText ? `${id}-warn` : showHelperText ? `${id}-helper` : void 0
79
+ }),
80
+ slug ? normalizedDecorator : decorator ? /* @__PURE__ */ jsx("div", {
81
+ className: `${prefix}--date-picker-input-inner-wrapper--decorator`,
82
+ children: normalizedDecorator
83
+ }) : null,
84
+ !hideIcon && /* @__PURE__ */ jsx("button", {
85
+ type: "button",
86
+ className: `${prefix}--date-picker__icon`,
87
+ onClick: onIconClick,
88
+ disabled: disabled || readOnly,
89
+ "aria-label": iconDescription,
90
+ tabIndex: -1,
91
+ children: /* @__PURE__ */ jsx("svg", {
92
+ focusable: "false",
93
+ preserveAspectRatio: "xMidYMid meet",
94
+ xmlns: "http://www.w3.org/2000/svg",
95
+ fill: "currentColor",
96
+ width: "16",
97
+ height: "16",
98
+ viewBox: "0 0 32 32",
99
+ "aria-hidden": "true",
100
+ children: /* @__PURE__ */ jsx("path", { d: "M26,4h-4V2h-2v2h-8V2h-2v2H6C4.9,4,4,4.9,4,6v20c0,1.1,0.9,2,2,2h20c1.1,0,2-0.9,2-2V6C28,4.9,27.1,4,26,4z M26,26H6V12h20 V26z M26,10H6V6h4v2h2V6h8v2h2V6h4V10z" })
101
+ })
102
+ })
103
+ ] })
104
+ }),
105
+ showInvalidText && /* @__PURE__ */ jsx("div", {
106
+ id: `${id}-error`,
107
+ className: `${prefix}--form-requirement`,
108
+ children: invalidText
109
+ }),
110
+ showWarnText && /* @__PURE__ */ jsx("div", {
111
+ id: `${id}-warn`,
112
+ className: `${prefix}--form-requirement`,
113
+ children: warnText
114
+ }),
115
+ showHelperText && /* @__PURE__ */ jsx("div", {
116
+ id: `${id}-helper`,
117
+ className: `${prefix}--form__helper-text`,
118
+ children: helperText
119
+ })
120
+ ] });
121
+ });
122
+ DatePickerInput.displayName = "DatePickerInput";
123
+ //#endregion
124
+ export { DatePickerInput };
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Copyright IBM Corp. 2026
3
+ *
4
+ * This source code is licensed under the Apache-2.0 license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ */
7
+ import React from 'react';
8
+ export interface DatePickerSkeletonProps {
9
+ /**
10
+ * Specify whether the skeleton should be a range date picker
11
+ */
12
+ range?: boolean;
13
+ /**
14
+ * Additional CSS class names
15
+ */
16
+ className?: string;
17
+ }
18
+ /**
19
+ * DatePickerSkeleton component for loading states
20
+ */
21
+ export declare const DatePickerSkeleton: React.FC<DatePickerSkeletonProps>;
22
+ export default DatePickerSkeleton;
@@ -0,0 +1,32 @@
1
+ /**
2
+ * Copyright IBM Corp. 2016, 2026
3
+ *
4
+ * This source code is licensed under the Apache-2.0 license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ */
7
+
8
+ import { usePrefix } from "../../../../internal/usePrefix.js";
9
+ import "react";
10
+ import { jsx, jsxs } from "react/jsx-runtime";
11
+ //#region src/components/DatePicker/next/components/DatePickerSkeleton.tsx
12
+ /**
13
+ * DatePickerSkeleton component for loading states
14
+ */
15
+ const DatePickerSkeleton = ({ range = false, className }) => {
16
+ const prefix = usePrefix();
17
+ return /* @__PURE__ */ jsx("div", {
18
+ className: `${prefix}--form-item`,
19
+ children: /* @__PURE__ */ jsxs("div", {
20
+ className: `${prefix}--date-picker ${prefix}--date-picker--next ${prefix}--date-picker--${range ? "range" : "single"} ${prefix}--skeleton ${className || ""}`,
21
+ children: [/* @__PURE__ */ jsxs("div", {
22
+ className: `${prefix}--date-picker-container`,
23
+ children: [/* @__PURE__ */ jsx("span", { className: `${prefix}--label ${prefix}--skeleton` }), /* @__PURE__ */ jsx("div", { className: `${prefix}--date-picker__input ${prefix}--skeleton` })]
24
+ }), range && /* @__PURE__ */ jsxs("div", {
25
+ className: `${prefix}--date-picker-container`,
26
+ children: [/* @__PURE__ */ jsx("span", { className: `${prefix}--label ${prefix}--skeleton` }), /* @__PURE__ */ jsx("div", { className: `${prefix}--date-picker__input ${prefix}--skeleton` })]
27
+ })]
28
+ })
29
+ });
30
+ };
31
+ //#endregion
32
+ export { DatePickerSkeleton, DatePickerSkeleton as default };
@@ -0,0 +1,144 @@
1
+ /**
2
+ * Copyright IBM Corp. 2026
3
+ *
4
+ * This source code is licensed under the Apache-2.0 license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ */
7
+ import { type RefObject } from 'react';
8
+ import { DatePickerState, type DatePickerContext } from '@carbon/utilities/date-picker';
9
+ /**
10
+ * Configuration for the useDatePicker hook
11
+ * Maintains 100% backwards compatibility with Carbon React v11 API
12
+ */
13
+ export interface UseDatePickerConfig {
14
+ /**
15
+ * The type of date picker (Carbon API uses datePickerType, not mode)
16
+ */
17
+ datePickerType?: 'simple' | 'single' | 'range';
18
+ /**
19
+ * Initial value as ISO date string
20
+ */
21
+ value?: string;
22
+ /**
23
+ * Minimum selectable date (mm/dd/yyyy format - Carbon API)
24
+ */
25
+ minDate?: string | null;
26
+ /**
27
+ * Maximum selectable date (mm/dd/yyyy format - Carbon API)
28
+ */
29
+ maxDate?: string | null;
30
+ /**
31
+ * Date format string (Flatpickr-compatible format)
32
+ */
33
+ dateFormat?: string;
34
+ /**
35
+ * Whether to allow manual input
36
+ */
37
+ allowInput?: boolean;
38
+ /**
39
+ * Whether to close calendar on date selection
40
+ */
41
+ closeOnSelect?: boolean;
42
+ /**
43
+ * Whether the picker is disabled
44
+ */
45
+ disabled?: boolean;
46
+ /**
47
+ * Whether the picker is read-only (Carbon uses readOnly, not readonly)
48
+ */
49
+ readOnly?: boolean;
50
+ /**
51
+ * Locale for date formatting
52
+ */
53
+ locale?: string;
54
+ /**
55
+ * Change handler - receives array of Date objects (Carbon API)
56
+ */
57
+ onChange?: (dates: Date[]) => void;
58
+ /**
59
+ * Open handler
60
+ */
61
+ onOpen?: () => void;
62
+ /**
63
+ * Close handler
64
+ */
65
+ onClose?: () => void;
66
+ }
67
+ /**
68
+ * Return type for the useDatePicker hook
69
+ */
70
+ export interface UseDatePickerReturn {
71
+ /**
72
+ * Current state machine context
73
+ */
74
+ context: DatePickerContext;
75
+ /**
76
+ * Ref to attach to the exit sentinel element in the render tree.
77
+ * The sentinel is a visually-hidden, aria-hidden span placed just after the
78
+ * calendar container. It has tabindex="-1" by default and is briefly set to
79
+ * tabindex="0" when Tab is pressed from the calendar so the browser delivers
80
+ * focus there naturally — no DOM scan needed.
81
+ */
82
+ exitSentinelRef: RefObject<HTMLSpanElement | null>;
83
+ /**
84
+ * onFocus handler to attach to the exit sentinel element.
85
+ * Restores tabindex="-1" on the sentinel and closes the calendar if still open.
86
+ */
87
+ handleExitSentinelFocus: () => void;
88
+ /**
89
+ * Current state
90
+ */
91
+ state: DatePickerState;
92
+ /**
93
+ * Whether the calendar is open
94
+ */
95
+ isOpen: boolean;
96
+ /**
97
+ * Send an event to the state machine
98
+ */
99
+ send: (eventType: string, payload?: unknown) => void;
100
+ /**
101
+ * Open the calendar
102
+ */
103
+ openCalendar: () => void;
104
+ /**
105
+ * Close the calendar
106
+ */
107
+ closeCalendar: () => void;
108
+ /**
109
+ * Select a date
110
+ */
111
+ selectDate: (date: Temporal.PlainDate) => void;
112
+ /**
113
+ * Handle input focus
114
+ */
115
+ handleInputFocus: (inputType?: 'from' | 'to') => void;
116
+ /**
117
+ * Handle input blur
118
+ */
119
+ handleInputBlur: () => void;
120
+ /**
121
+ * Handle input value change
122
+ */
123
+ handleInputChange: (value: string, inputType?: 'from' | 'to') => void;
124
+ /**
125
+ * Ref for the start input
126
+ */
127
+ startInputRef: React.RefObject<HTMLInputElement | null>;
128
+ /**
129
+ * Ref for the end input (range mode)
130
+ */
131
+ endInputRef: React.RefObject<HTMLInputElement | null>;
132
+ /**
133
+ * Ref for the calendar container
134
+ */
135
+ calendarRef: React.RefObject<HTMLDivElement | null>;
136
+ }
137
+ /**
138
+ * React hook for managing date picker state using the shared state machine
139
+ * Maintains 100% backwards compatibility with Carbon React v11 API
140
+ *
141
+ * @param {UseDatePickerConfig} config - Configuration options
142
+ * @returns {UseDatePickerReturn} Hook return object with state and handlers
143
+ */
144
+ export declare function useDatePicker(config?: UseDatePickerConfig): UseDatePickerReturn;
@@ -0,0 +1,286 @@
1
+ /**
2
+ * Copyright IBM Corp. 2016, 2026
3
+ *
4
+ * This source code is licensed under the Apache-2.0 license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ */
7
+
8
+ import { useCallback, useEffect, useRef, useState } from "react";
9
+ import { ClickOutsideHandler, DatePickerEvent, DatePickerState, DatePickerStateMachine, mapKeyboardToStateMachineEvent, parseDateToPlainDate, plainDateToDate } from "@carbon/utilities/date-picker";
10
+ //#region src/components/DatePicker/next/hooks/useDatePicker.ts
11
+ /**
12
+ * Copyright IBM Corp. 2026
13
+ *
14
+ * This source code is licensed under the Apache-2.0 license found in the
15
+ * LICENSE file in the root directory of this source tree.
16
+ */
17
+ /**
18
+ * React hook for managing date picker state using the shared state machine
19
+ * Maintains 100% backwards compatibility with Carbon React v11 API
20
+ *
21
+ * @param {UseDatePickerConfig} config - Configuration options
22
+ * @returns {UseDatePickerReturn} Hook return object with state and handlers
23
+ */
24
+ function useDatePicker(config = {}) {
25
+ const { datePickerType = "single", value = "", minDate = null, maxDate = null, dateFormat = "m/d/Y", closeOnSelect = true, disabled = false, readOnly = false, onChange, onOpen, onClose } = config;
26
+ const startInputRef = useRef(null);
27
+ const endInputRef = useRef(null);
28
+ const calendarRef = useRef(null);
29
+ const exitSentinelRef = useRef(null);
30
+ const machineRef = useRef(null);
31
+ const suppressOpenOnFocusRef = useRef(false);
32
+ const [context, setContext] = useState(() => {
33
+ const machine = new DatePickerStateMachine({
34
+ mode: datePickerType,
35
+ value,
36
+ minDate: parseDateToPlainDate(minDate),
37
+ maxDate: parseDateToPlainDate(maxDate),
38
+ dateFormat,
39
+ isDisabled: disabled,
40
+ isReadonly: readOnly
41
+ });
42
+ machineRef.current = machine;
43
+ return machine.getContext();
44
+ });
45
+ const [state, setState] = useState(() => {
46
+ return machineRef.current?.getState() || DatePickerState.IDLE;
47
+ });
48
+ useEffect(() => {
49
+ const machine = machineRef.current;
50
+ if (!machine) return;
51
+ let wasCalendarOpen = machine.getContext().isOpen;
52
+ return machine.subscribe((transition) => {
53
+ setContext(transition.context);
54
+ setState(transition.to);
55
+ if (transition.to === DatePickerState.CALENDAR_OPEN && onOpen) onOpen();
56
+ if (wasCalendarOpen && !transition.context.isOpen && onClose) onClose();
57
+ wasCalendarOpen = transition.context.isOpen;
58
+ });
59
+ }, [onOpen, onClose]);
60
+ useEffect(() => {
61
+ if (!context.shouldRestoreFocus) return;
62
+ const targetRef = context.restoreFocusTo === "to" ? endInputRef : startInputRef;
63
+ const timeoutId = window.setTimeout(() => {
64
+ suppressOpenOnFocusRef.current = true;
65
+ targetRef.current?.focus();
66
+ machineRef.current?.updateContext({
67
+ shouldRestoreFocus: false,
68
+ restoreFocusTo: null
69
+ });
70
+ setContext(machineRef.current.getContext());
71
+ queueMicrotask(() => {
72
+ suppressOpenOnFocusRef.current = false;
73
+ });
74
+ }, 0);
75
+ return () => {
76
+ window.clearTimeout(timeoutId);
77
+ };
78
+ }, [context.shouldRestoreFocus, context.restoreFocusTo]);
79
+ const prevDatesRef = useRef("");
80
+ useEffect(() => {
81
+ if (!onChange) return;
82
+ const dates = [];
83
+ if (context.startDate) {
84
+ const date = plainDateToDate(context.startDate);
85
+ if (date) dates.push(date);
86
+ }
87
+ if (context.endDate) {
88
+ const date = plainDateToDate(context.endDate);
89
+ if (date) dates.push(date);
90
+ }
91
+ const datesKey = dates.map((d) => d.toISOString()).join(",");
92
+ if (dates.length > 0 && datesKey !== prevDatesRef.current) {
93
+ prevDatesRef.current = datesKey;
94
+ onChange(dates);
95
+ }
96
+ }, [
97
+ context.startDate,
98
+ context.endDate,
99
+ onChange
100
+ ]);
101
+ useEffect(() => {
102
+ const machine = machineRef.current;
103
+ if (!machine) return;
104
+ if (disabled !== context.isDisabled) machine.send(disabled ? DatePickerEvent.DISABLE : DatePickerEvent.ENABLE);
105
+ if (readOnly !== context.isReadonly) machine.send(readOnly ? DatePickerEvent.SET_READONLY : DatePickerEvent.UNSET_READONLY);
106
+ const newMinDate = parseDateToPlainDate(minDate);
107
+ const newMaxDate = parseDateToPlainDate(maxDate);
108
+ if (newMinDate) machine.send(DatePickerEvent.SET_MIN_DATE, { date: newMinDate });
109
+ if (newMaxDate) machine.send(DatePickerEvent.SET_MAX_DATE, { date: newMaxDate });
110
+ }, [
111
+ disabled,
112
+ readOnly,
113
+ minDate,
114
+ maxDate,
115
+ context.isDisabled,
116
+ context.isReadonly
117
+ ]);
118
+ const send = useCallback((eventType, payload) => {
119
+ machineRef.current?.send(eventType, payload);
120
+ }, []);
121
+ const openCalendar = useCallback(() => {
122
+ send(DatePickerEvent.CALENDAR_ICON_CLICK);
123
+ }, [send]);
124
+ const closeCalendar = useCallback(() => {
125
+ send(DatePickerEvent.CALENDAR_CLOSE);
126
+ }, [send]);
127
+ const selectDate = useCallback((date) => {
128
+ if (datePickerType === "range") if (!context.startDate || context.endDate) send(DatePickerEvent.RANGE_START_SELECT, { date });
129
+ else {
130
+ send(DatePickerEvent.RANGE_END_SELECT, { date });
131
+ if (closeOnSelect) send(DatePickerEvent.CALENDAR_CLOSE);
132
+ }
133
+ else {
134
+ send(DatePickerEvent.DATE_SELECT, { date });
135
+ if (closeOnSelect) send(DatePickerEvent.CALENDAR_CLOSE);
136
+ }
137
+ }, [
138
+ closeOnSelect,
139
+ datePickerType,
140
+ context.startDate,
141
+ context.endDate,
142
+ send
143
+ ]);
144
+ const handleInputFocus = useCallback((inputType = "from") => {
145
+ send(DatePickerEvent.INPUT_FOCUS, { inputType });
146
+ if (suppressOpenOnFocusRef.current) return;
147
+ send(DatePickerEvent.CALENDAR_OPEN);
148
+ }, [send]);
149
+ const handleInputBlur = useCallback(() => {
150
+ send(DatePickerEvent.INPUT_BLUR);
151
+ }, [send]);
152
+ const handleInputChange = useCallback((value, inputType = "from") => {
153
+ send(DatePickerEvent.VALUE_CHANGE, {
154
+ value,
155
+ inputType
156
+ });
157
+ }, [send]);
158
+ useEffect(() => {
159
+ if (!context.isOpen) return;
160
+ const handler = new ClickOutsideHandler({
161
+ isOpen: context.isOpen,
162
+ /**
163
+ * Check if a node is contained within the date picker elements
164
+ * @param {Node} node - The node to check
165
+ * @returns {boolean} True if the node is within the date picker
166
+ */
167
+ containsNode: (node) => {
168
+ const calendarEl = calendarRef.current;
169
+ const startInputEl = startInputRef.current;
170
+ const endInputEl = endInputRef.current;
171
+ return (calendarEl?.contains(node) ?? false) || (startInputEl?.contains(node) ?? false) || (endInputEl?.contains(node) ?? false);
172
+ },
173
+ /**
174
+ * Handle clicks outside the date picker
175
+ */
176
+ onOutsideClick: () => send(DatePickerEvent.OUTSIDE_CLICK),
177
+ useCapture: true,
178
+ attachDelay: 0
179
+ });
180
+ handler.attach();
181
+ return () => {
182
+ handler.detach();
183
+ };
184
+ }, [context.isOpen, send]);
185
+ useEffect(() => {
186
+ /**
187
+ * Handle keyboard events for calendar navigation
188
+ *
189
+ * @param {KeyboardEvent} event - Keyboard event
190
+ */
191
+ const handleKeyDown = (event) => {
192
+ const { key } = event;
193
+ const target = event.target;
194
+ const calendarEl = calendarRef.current;
195
+ const startInputEl = startInputRef.current;
196
+ const endInputEl = endInputRef.current;
197
+ const isFocusInCalendar = calendarEl && (calendarEl.contains(target) || target === calendarEl || target.classList?.contains("cds--date-picker__calendar"));
198
+ const isFocusInInput = target === startInputEl || target === endInputEl || startInputEl && startInputEl.contains(target) || endInputEl && endInputEl.contains(target);
199
+ if (key === "Escape" && context.isOpen) {
200
+ event.preventDefault();
201
+ send(DatePickerEvent.ESCAPE_KEY);
202
+ return;
203
+ }
204
+ if (key === "Tab") {
205
+ if (isFocusInInput && !event.shiftKey) {
206
+ event.preventDefault();
207
+ if (calendarEl) calendarEl.querySelector(".cds--date-picker__calendar")?.focus();
208
+ return;
209
+ }
210
+ if (isFocusInCalendar && event.shiftKey) {
211
+ event.preventDefault();
212
+ if (datePickerType === "range" && context.lastFocusedInput === "to" && endInputEl) endInputEl.focus();
213
+ else if (startInputEl) startInputEl.focus();
214
+ return;
215
+ }
216
+ if (isFocusInCalendar && !event.shiftKey) {
217
+ send(DatePickerEvent.TAB_KEY);
218
+ if (exitSentinelRef.current) exitSentinelRef.current.tabIndex = 0;
219
+ return;
220
+ }
221
+ return;
222
+ }
223
+ if (!context.isOpen) return;
224
+ if (!isFocusInCalendar || isFocusInInput) return;
225
+ const mappedEvent = mapKeyboardToStateMachineEvent({
226
+ key,
227
+ shiftKey: event.shiftKey,
228
+ mode: datePickerType,
229
+ state,
230
+ focusedDate: context.focusedDate
231
+ });
232
+ if (mappedEvent) {
233
+ if (mappedEvent.preventDefault) event.preventDefault();
234
+ if (mappedEvent.eventType) send(mappedEvent.eventType, mappedEvent.payload);
235
+ return;
236
+ }
237
+ };
238
+ document.addEventListener("keydown", handleKeyDown);
239
+ return () => {
240
+ document.removeEventListener("keydown", handleKeyDown);
241
+ };
242
+ }, [
243
+ context.isOpen,
244
+ context.focusedDate,
245
+ context.lastFocusedInput,
246
+ state,
247
+ datePickerType,
248
+ send
249
+ ]);
250
+ /**
251
+ * Called when the browser delivers focus to the exit sentinel.
252
+ * Closes the calendar (state machine already received TAB_KEY in _handleKeyDown)
253
+ * and immediately restores tabindex="-1" so future Tab presses skip the sentinel.
254
+ *
255
+ * TODO (carbon monorepo migration): replace this hand-rolled sentinel with the
256
+ * upstream Carbon wrapFocus utility, which implements the same start/end sentinel
257
+ * pattern for focus trapping and exit.
258
+ * Upstream location: packages/react/src/internal/wrapFocus.ts (wrapFocus /
259
+ * wrapFocusWithoutSentinels exports). Note that wrapFocus is designed to trap
260
+ * focus inside a container (modal pattern) rather than release it; the exit-only
261
+ * direction we need maps to the endTrapNode path in that utility.
262
+ */
263
+ const handleExitSentinelFocus = useCallback(() => {
264
+ if (exitSentinelRef.current) exitSentinelRef.current.tabIndex = -1;
265
+ if (context.isOpen) send(DatePickerEvent.TAB_KEY);
266
+ }, [context.isOpen, send]);
267
+ return {
268
+ context,
269
+ state,
270
+ isOpen: context.isOpen,
271
+ send,
272
+ openCalendar,
273
+ closeCalendar,
274
+ selectDate,
275
+ handleInputFocus,
276
+ handleInputBlur,
277
+ handleInputChange,
278
+ startInputRef,
279
+ endInputRef,
280
+ calendarRef,
281
+ exitSentinelRef,
282
+ handleExitSentinelFocus
283
+ };
284
+ }
285
+ //#endregion
286
+ export { useDatePicker };
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Copyright IBM Corp. 2026
3
+ *
4
+ * This source code is licensed under the Apache-2.0 license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ */
7
+ export { DatePicker } from './components/DatePicker';
8
+ export { DatePickerInput } from './components/DatePickerInput';
9
+ export { DatePickerSkeleton } from './components/DatePickerSkeleton';
10
+ export type { DatePickerProps } from './components/DatePicker';
11
+ export type { DatePickerInputProps } from './components/DatePickerInput';
12
+ export type { DatePickerSkeletonProps } from './components/DatePickerSkeleton';
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Copyright IBM Corp. 2016, 2026
3
+ *
4
+ * This source code is licensed under the Apache-2.0 license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ */
7
+
8
+ import { __exportAll } from "../../../_virtual/_rolldown/runtime.js";
9
+ import { DatePicker } from "./components/DatePicker.js";
10
+ import { DatePickerInput } from "./components/DatePickerInput.js";
11
+ import { DatePickerSkeleton } from "./components/DatePickerSkeleton.js";
12
+ //#region src/components/DatePicker/next/index.tsx
13
+ var next_exports = /* @__PURE__ */ __exportAll({
14
+ DatePicker: () => DatePicker,
15
+ DatePickerInput: () => DatePickerInput,
16
+ DatePickerSkeleton: () => DatePickerSkeleton
17
+ });
18
+ //#endregion
19
+ export { DatePicker, DatePickerInput, DatePickerSkeleton, next_exports };
package/es/index.d.ts CHANGED
@@ -29,6 +29,7 @@ export * from './components/DataTable';
29
29
  export * from './components/DataTableSkeleton';
30
30
  export * from './components/DatePicker';
31
31
  export * from './components/DatePickerInput';
32
+ export * as preview__DatePicker from './components/DatePicker/next';
32
33
  export * from './components/Dropdown';
33
34
  export * from './components/ErrorBoundary';
34
35
  export * from './components/ExpandableSearch';