@carbon/react 1.85.0-rc.0 → 1.85.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 (38) hide show
  1. package/.playwright/INTERNAL_AVT_REPORT_DO_NOT_USE.json +942 -872
  2. package/es/components/DatePicker/DatePicker.d.ts +1 -1
  3. package/es/components/DatePicker/DatePicker.js +2 -2
  4. package/es/components/DatePicker/plugins/appendToPlugin.d.ts +12 -0
  5. package/es/components/DatePicker/plugins/appendToPlugin.js +9 -12
  6. package/es/components/Menu/Menu.js +7 -2
  7. package/es/components/Menu/MenuItem.js +13 -2
  8. package/es/components/MultiSelect/FilterableMultiSelect.js +1 -1
  9. package/es/components/MultiSelect/filter.d.ts +10 -0
  10. package/es/components/MultiSelect/filter.js +21 -0
  11. package/es/components/Slider/Slider.d.ts +59 -198
  12. package/es/components/Slider/Slider.js +68 -120
  13. package/es/components/Tabs/usePressable.d.ts +19 -0
  14. package/es/components/Tabs/usePressable.js +19 -33
  15. package/es/components/Tooltip/Tooltip.d.ts +2 -2
  16. package/es/components/Tooltip/Tooltip.js +2 -2
  17. package/es/components/TreeView/TreeNode.d.ts +22 -0
  18. package/es/components/TreeView/TreeNode.js +116 -9
  19. package/lib/components/DatePicker/DatePicker.d.ts +1 -1
  20. package/lib/components/DatePicker/DatePicker.js +1 -1
  21. package/lib/components/DatePicker/plugins/appendToPlugin.d.ts +12 -0
  22. package/lib/components/DatePicker/plugins/appendToPlugin.js +9 -12
  23. package/lib/components/Menu/Menu.js +7 -2
  24. package/lib/components/Menu/MenuItem.js +13 -2
  25. package/lib/components/MultiSelect/FilterableMultiSelect.js +1 -1
  26. package/lib/components/MultiSelect/filter.d.ts +10 -0
  27. package/lib/components/MultiSelect/filter.js +25 -0
  28. package/lib/components/Slider/Slider.d.ts +59 -198
  29. package/lib/components/Slider/Slider.js +67 -119
  30. package/lib/components/Tabs/usePressable.d.ts +19 -0
  31. package/lib/components/Tabs/usePressable.js +19 -33
  32. package/lib/components/Tooltip/Tooltip.d.ts +2 -2
  33. package/lib/components/Tooltip/Tooltip.js +2 -2
  34. package/lib/components/TreeView/TreeNode.d.ts +22 -0
  35. package/lib/components/TreeView/TreeNode.js +115 -8
  36. package/package.json +6 -6
  37. package/es/components/ComboBox/tools/filter.js +0 -18
  38. package/lib/components/ComboBox/tools/filter.js +0 -22
@@ -9,14 +9,83 @@ import { extends as _extends } from '../../_virtual/_rollupPluginBabelHelpers.js
9
9
  import { CaretDown } from '@carbon/icons-react';
10
10
  import cx from 'classnames';
11
11
  import PropTypes from 'prop-types';
12
- import React, { useRef, useState, useEffect } from 'react';
12
+ import React, { useRef, useState, useEffect, useCallback } from 'react';
13
13
  import { ArrowLeft, ArrowRight, Enter, Space } from '../../internal/keyboard/keys.js';
14
14
  import { matches, match } from '../../internal/keyboard/match.js';
15
15
  import { useControllableState } from '../../internal/useControllableState.js';
16
16
  import { usePrefix } from '../../internal/usePrefix.js';
17
17
  import { useId } from '../../internal/useId.js';
18
18
  import { useFeatureFlag } from '../FeatureFlags/index.js';
19
+ import { IconButton } from '../IconButton/index.js';
19
20
 
21
+ const extractTextContent = node => {
22
+ if (node === null || node === undefined) return '';
23
+ if (typeof node === 'string') return node;
24
+ if (typeof node === 'number') return String(node);
25
+ if (typeof node === 'boolean') return String(node);
26
+ if (Array.isArray(node)) {
27
+ return node.map(extractTextContent).join('');
28
+ }
29
+ if (/*#__PURE__*/React.isValidElement(node)) {
30
+ const element = node;
31
+ const children = element.props.children;
32
+ return extractTextContent(children);
33
+ }
34
+ return '';
35
+ };
36
+ const useEllipsisCheck = (label, detailsWrapperRef) => {
37
+ const [isEllipsisApplied, setIsEllipsisApplied] = useState(false);
38
+ const labelTextRef = useRef(null);
39
+ const checkEllipsis = useCallback(() => {
40
+ const element = labelTextRef.current;
41
+ if (!element) {
42
+ setIsEllipsisApplied(false);
43
+ return;
44
+ }
45
+ if (element.offsetWidth === 0) {
46
+ setIsEllipsisApplied(false);
47
+ return;
48
+ }
49
+ const checkElement = detailsWrapperRef.current || element;
50
+ if (checkElement && checkElement.offsetWidth > 0) {
51
+ const isTextTruncated = element.scrollWidth > checkElement.offsetWidth;
52
+ setIsEllipsisApplied(isTextTruncated);
53
+ } else {
54
+ setIsEllipsisApplied(false);
55
+ }
56
+ }, [detailsWrapperRef]);
57
+ useEffect(() => {
58
+ let animationFrameId;
59
+ animationFrameId = requestAnimationFrame(checkEllipsis);
60
+ let resizeObserver;
61
+ if (typeof window !== 'undefined' && typeof window.ResizeObserver !== 'undefined' && labelTextRef.current) {
62
+ resizeObserver = new window.ResizeObserver(() => {
63
+ requestAnimationFrame(checkEllipsis);
64
+ });
65
+ resizeObserver.observe(labelTextRef.current);
66
+ if (detailsWrapperRef.current) {
67
+ resizeObserver.observe(detailsWrapperRef.current);
68
+ }
69
+ }
70
+ return () => {
71
+ cancelAnimationFrame(animationFrameId);
72
+ if (resizeObserver) {
73
+ if (labelTextRef.current) {
74
+ resizeObserver.unobserve(labelTextRef.current);
75
+ }
76
+ if (detailsWrapperRef.current) {
77
+ resizeObserver.unobserve(detailsWrapperRef.current);
78
+ }
79
+ resizeObserver.disconnect();
80
+ }
81
+ };
82
+ }, [checkEllipsis, detailsWrapperRef]);
83
+ return {
84
+ labelTextRef,
85
+ isEllipsisApplied,
86
+ tooltipText: extractTextContent(label)
87
+ };
88
+ };
20
89
  const TreeNode = /*#__PURE__*/React.forwardRef(({
21
90
  active,
22
91
  children,
@@ -35,11 +104,18 @@ const TreeNode = /*#__PURE__*/React.forwardRef(({
35
104
  selected: propSelected,
36
105
  value,
37
106
  href,
107
+ align = 'bottom',
108
+ autoAlign = false,
38
109
  ...rest
39
110
  }, forwardedRef) => {
40
- // These are provided by the parent TreeView component
41
111
  const depth = propDepth;
42
112
  const selected = propSelected;
113
+ const detailsWrapperRef = useRef(null);
114
+ const {
115
+ labelTextRef,
116
+ isEllipsisApplied,
117
+ tooltipText
118
+ } = useEllipsisCheck(label, detailsWrapperRef);
43
119
  const enableTreeviewControllable = useFeatureFlag('enable-treeview-controllable');
44
120
  const {
45
121
  current: id
@@ -61,6 +137,25 @@ const TreeNode = /*#__PURE__*/React.forwardRef(({
61
137
  const currentNode = useRef(null);
62
138
  const currentNodeLabel = useRef(null);
63
139
  const prefix = usePrefix();
140
+ const renderLabelText = () => {
141
+ if (isEllipsisApplied && tooltipText) {
142
+ return /*#__PURE__*/React.createElement(IconButton, {
143
+ label: tooltipText,
144
+ kind: "ghost",
145
+ align: align,
146
+ autoAlign: autoAlign,
147
+ className: `${prefix}--tree-node__label__text-button`,
148
+ wrapperClasses: `${prefix}--popover-container`
149
+ }, /*#__PURE__*/React.createElement("span", {
150
+ ref: labelTextRef,
151
+ className: `${prefix}--tree-node__label__text`
152
+ }, label));
153
+ }
154
+ return /*#__PURE__*/React.createElement("span", {
155
+ ref: labelTextRef,
156
+ className: `${prefix}--tree-node__label__text`
157
+ }, label);
158
+ };
64
159
  const setRefs = element => {
65
160
  currentNode.current = element;
66
161
  if (typeof forwardedRef === 'function') {
@@ -300,7 +395,7 @@ const TreeNode = /*#__PURE__*/React.forwardRef(({
300
395
  ref: currentNodeLabel
301
396
  }, Icon && /*#__PURE__*/React.createElement(Icon, {
302
397
  className: `${prefix}--tree-node__icon`
303
- }), label)));
398
+ }), renderLabelText())));
304
399
  } else {
305
400
  return /*#__PURE__*/React.createElement("li", _extends({}, treeNodeProps, {
306
401
  ref: setRefs
@@ -309,7 +404,7 @@ const TreeNode = /*#__PURE__*/React.forwardRef(({
309
404
  ref: currentNodeLabel
310
405
  }, Icon && /*#__PURE__*/React.createElement(Icon, {
311
406
  className: `${prefix}--tree-node__icon`
312
- }), label));
407
+ }), renderLabelText()));
313
408
  }
314
409
  }
315
410
  if (href) {
@@ -332,10 +427,11 @@ const TreeNode = /*#__PURE__*/React.forwardRef(({
332
427
  }, /*#__PURE__*/React.createElement(CaretDown, {
333
428
  className: toggleClasses
334
429
  })), /*#__PURE__*/React.createElement("span", {
335
- className: `${prefix}--tree-node__label__details`
430
+ className: `${prefix}--tree-node__label__details`,
431
+ ref: detailsWrapperRef
336
432
  }, Icon && /*#__PURE__*/React.createElement(Icon, {
337
433
  className: `${prefix}--tree-node__icon`
338
- }), label))), /*#__PURE__*/React.createElement("ul", {
434
+ }), renderLabelText()))), /*#__PURE__*/React.createElement("ul", {
339
435
  id: `${id}-subtree`,
340
436
  role: "group",
341
437
  className: cx(`${prefix}--tree-node__children`, {
@@ -358,10 +454,11 @@ const TreeNode = /*#__PURE__*/React.forwardRef(({
358
454
  }, /*#__PURE__*/React.createElement(CaretDown, {
359
455
  className: toggleClasses
360
456
  })), /*#__PURE__*/React.createElement("span", {
361
- className: `${prefix}--tree-node__label__details`
457
+ className: `${prefix}--tree-node__label__details`,
458
+ ref: detailsWrapperRef
362
459
  }, Icon && /*#__PURE__*/React.createElement(Icon, {
363
460
  className: `${prefix}--tree-node__icon`
364
- }), label)), /*#__PURE__*/React.createElement("ul", {
461
+ }), renderLabelText())), /*#__PURE__*/React.createElement("ul", {
365
462
  id: `${id}-subtree`,
366
463
  role: "group",
367
464
  className: cx(`${prefix}--tree-node__children`, {
@@ -444,7 +541,17 @@ TreeNode.propTypes = {
444
541
  /**
445
542
  * Optional: The URL the TreeNode is linking to
446
543
  */
447
- href: PropTypes.string
544
+ href: PropTypes.string,
545
+ /**
546
+ * Specify how the tooltip should align when text is truncated
547
+ */
548
+ align: PropTypes.oneOf(['top', 'bottom', 'left', 'right', 'top-start', 'top-end', 'bottom-start', 'bottom-end', 'left-end', 'left-start', 'right-end', 'right-start']),
549
+ /**
550
+ * **Experimental**: Will attempt to automatically align the floating
551
+ * element to avoid collisions with the viewport and being clipped by
552
+ * ancestor elements.
553
+ */
554
+ autoAlign: PropTypes.bool
448
555
  };
449
556
  TreeNode.displayName = 'TreeNode';
450
557
 
@@ -29,7 +29,7 @@ export interface DatePickerProps {
29
29
  /**
30
30
  * The DOM element the flatpickr should be inserted into `<body>` by default.
31
31
  */
32
- appendTo?: object;
32
+ appendTo?: HTMLElement;
33
33
  /**
34
34
  * The child nodes.
35
35
  */
@@ -352,7 +352,7 @@ const DatePicker = /*#__PURE__*/React__default["default"].forwardRef(function Da
352
352
  parseDate: parseDate,
353
353
  plugins: [datePickerType === 'range' ? rangePlugin["default"]({
354
354
  input: endInputField.current
355
- }) : () => {}, appendTo ? appendToPlugin["default"]({
355
+ }) : () => {}, appendTo ? appendToPlugin.appendToPlugin({
356
356
  appendTo
357
357
  }) : () => {}, carbonFlatpickrMonthSelectPlugin({
358
358
  selectorFlatpickrMonthYearContainer: '.flatpickr-current-month',
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Copyright IBM Corp. 2019, 2025
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 { Plugin } from 'flatpickr/dist/types/options';
8
+ interface AppendToPluginConfig {
9
+ appendTo: HTMLElement;
10
+ }
11
+ export declare const appendToPlugin: (config: AppendToPluginConfig) => Plugin;
12
+ export {};
@@ -9,13 +9,9 @@
9
9
 
10
10
  Object.defineProperty(exports, '__esModule', { value: true });
11
11
 
12
- /**
13
- * @param {object} config Plugin configuration.
14
- * @returns {Plugin} A Flatpickr plugin to put adjust the position of calendar dropdown.
15
- */
16
- var carbonFlatpickrAppendToPlugin = config => fp => {
12
+ const appendToPlugin = config => fp => {
17
13
  /**
18
- * Adjusts the floating menu position after Flatpicker sets it.
14
+ * Adjusts the floating menu position after Flatpickr sets it.
19
15
  */
20
16
  const handlePreCalendarPosition = () => {
21
17
  Promise.resolve().then(() => {
@@ -24,9 +20,10 @@ var carbonFlatpickrAppendToPlugin = config => fp => {
24
20
  config: fpConfig,
25
21
  _positionElement: positionElement
26
22
  } = fp;
27
- const {
28
- appendTo
29
- } = fpConfig;
23
+ const appendTo = fpConfig.appendTo;
24
+ if (!appendTo) {
25
+ throw new Error('[appendToPlugin] Missing `appendTo` element.');
26
+ }
30
27
  const {
31
28
  left: containerLeft,
32
29
  top: containerTop
@@ -35,8 +32,8 @@ var carbonFlatpickrAppendToPlugin = config => fp => {
35
32
  left: refLeft,
36
33
  bottom: refBottom
37
34
  } = positionElement.getBoundingClientRect();
38
- if ((appendTo !== appendTo.ownerDocument.body || containerLeft !== 0 || containerTop !== 0) && appendTo.ownerDocument.defaultView.getComputedStyle(appendTo).getPropertyValue('position') === 'static') {
39
- throw new Error('Floating menu container must not have `position:static`.');
35
+ if ((appendTo !== appendTo.ownerDocument.body || containerLeft !== 0 || containerTop !== 0) && appendTo.ownerDocument.defaultView?.getComputedStyle(appendTo).getPropertyValue('position') === 'static') {
36
+ throw new Error('Floating menu container must not have `position: static`.');
40
37
  }
41
38
  // `2` for negative margin on calendar dropdown
42
39
  calendarContainer.style.top = `${refBottom - containerTop + 2}px`;
@@ -57,4 +54,4 @@ var carbonFlatpickrAppendToPlugin = config => fp => {
57
54
  };
58
55
  };
59
56
 
60
- exports["default"] = carbonFlatpickrAppendToPlugin;
57
+ exports.appendToPlugin = appendToPlugin;
@@ -254,8 +254,13 @@ const Menu = /*#__PURE__*/React.forwardRef(function Menu({
254
254
  return [fitValue(ranges.x, 'x') ?? -1, fitValue(ranges.y, 'y') ?? -1];
255
255
  }
256
256
  React.useEffect(() => {
257
- if (open && focusableItems.length > 0) {
258
- focusItem();
257
+ if (open) {
258
+ const raf = requestAnimationFrame(() => {
259
+ if (focusableItems.length > 0) {
260
+ focusItem();
261
+ }
262
+ });
263
+ return () => cancelAnimationFrame(raf);
259
264
  }
260
265
  // eslint-disable-next-line react-hooks/exhaustive-deps
261
266
  }, [open, focusableItems]);
@@ -117,7 +117,11 @@ const MenuItem = /*#__PURE__*/React.forwardRef(function MenuItem({
117
117
  function handleKeyDown(e) {
118
118
  if (hasChildren && match.match(e, keys.ArrowRight)) {
119
119
  openSubmenu();
120
+ requestAnimationFrame(() => {
121
+ refs.floating.current?.focus();
122
+ });
120
123
  e.stopPropagation();
124
+ e.preventDefault();
121
125
  }
122
126
  pendingKeyboardClick.current = keyboardClickEvent(e);
123
127
  if (rest.onKeyDown) {
@@ -134,11 +138,18 @@ const MenuItem = /*#__PURE__*/React.forwardRef(function MenuItem({
134
138
  [`${prefix}--menu-item--disabled`]: isDisabled,
135
139
  [`${prefix}--menu-item--danger`]: isDanger
136
140
  });
137
-
141
+ const [isFocusable, setIsFocusable] = React.useState(false);
138
142
  // on first render, register this menuitem in the context's state
139
143
  // (used for keyboard navigation)
140
144
  React.useEffect(() => {
141
145
  registerItem();
146
+
147
+ // Detects if this is the first focusable item
148
+ const currentItems = context.state.items;
149
+ if (!disabled && menuItem.current && currentItems.length === 0) {
150
+ setIsFocusable(true);
151
+ }
152
+
142
153
  // eslint-disable-next-line react-hooks/exhaustive-deps
143
154
  }, []);
144
155
 
@@ -177,7 +188,7 @@ const MenuItem = /*#__PURE__*/React.forwardRef(function MenuItem({
177
188
  }, rest, {
178
189
  ref: ref,
179
190
  className: classNames,
180
- tabIndex: -1,
191
+ tabIndex: isFocusable ? 0 : -1,
181
192
  "aria-disabled": isDisabled ?? undefined,
182
193
  "aria-haspopup": hasChildren ?? undefined,
183
194
  "aria-expanded": hasChildren ? submenuOpen : undefined,
@@ -16,7 +16,7 @@ var Downshift = require('downshift');
16
16
  var isEqual = require('react-fast-compare');
17
17
  var PropTypes = require('prop-types');
18
18
  var React = require('react');
19
- var filter = require('../ComboBox/tools/filter.js');
19
+ var filter = require('./filter.js');
20
20
  var MultiSelectPropTypes = require('./MultiSelectPropTypes.js');
21
21
  var index$1 = require('../ListBox/index.js');
22
22
  var ListBoxSelection = require('../ListBox/next/ListBoxSelection.js');
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Copyright IBM Corp. 2016, 2025
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 declare const defaultFilterItems: <ItemType>(items: ItemType[], { itemToString, inputValue, }: {
8
+ itemToString: (item: ItemType | null) => string;
9
+ inputValue: string | null;
10
+ }) => ItemType[];
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Copyright IBM Corp. 2016, 2023
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
+ 'use strict';
9
+
10
+ Object.defineProperty(exports, '__esModule', { value: true });
11
+
12
+ // TODO [@carbon-design-system/monorepo-reviewers]: This file was in the
13
+ // `ComboBox` directory before but it wasn't used there. Now it's used in
14
+ // `FilterableMultiSelect`. Is that expected?
15
+
16
+ const defaultFilterItems = (items, {
17
+ itemToString,
18
+ inputValue
19
+ }) => {
20
+ if (!inputValue) return items;
21
+ const normalizedInput = inputValue.toLowerCase();
22
+ return items.filter(item => itemToString(item).toLowerCase().includes(normalizedInput));
23
+ };
24
+
25
+ exports.defaultFilterItems = defaultFilterItems;