@carbon/ibm-products 2.83.0 → 2.84.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 (125) hide show
  1. package/css/carbon.css +4 -0
  2. package/css/carbon.css.map +1 -1
  3. package/css/index-full-carbon.css +99 -33
  4. package/css/index-full-carbon.css.map +1 -1
  5. package/css/index-full-carbon.min.css +1 -1
  6. package/css/index-full-carbon.min.css.map +1 -1
  7. package/css/index-without-carbon-released-only.css +95 -33
  8. package/css/index-without-carbon-released-only.css.map +1 -1
  9. package/css/index-without-carbon-released-only.min.css +1 -1
  10. package/css/index-without-carbon-released-only.min.css.map +1 -1
  11. package/css/index-without-carbon.css +95 -33
  12. package/css/index-without-carbon.css.map +1 -1
  13. package/css/index-without-carbon.min.css +1 -1
  14. package/css/index-without-carbon.min.css.map +1 -1
  15. package/css/index.css +95 -33
  16. package/css/index.css.map +1 -1
  17. package/css/index.min.css +1 -1
  18. package/css/index.min.css.map +1 -1
  19. package/es/components/AddSelect/AddSelectBody.js +1 -1
  20. package/es/components/Coachmark/next/Coachmark/CoachmarkBeacon/CoachmarkBeacon.js +6 -6
  21. package/es/components/Datagrid/Datagrid/addons/CustomizeColumns/CustomizeColumnsTearsheet.js +1 -1
  22. package/es/components/PageHeader/PageHeader.js +4 -12
  23. package/es/components/PageHeader/next/PageHeader.js +29 -12
  24. package/es/components/PageHeader/next/context.d.ts +3 -0
  25. package/es/components/PageHeader/next/utils.js +8 -0
  26. package/es/components/SidePanel/SidePanel.js +11 -3
  27. package/es/components/Tearsheet/TearsheetPresence.d.ts +37 -0
  28. package/es/components/Tearsheet/TearsheetPresence.js +56 -0
  29. package/es/components/Tearsheet/TearsheetShell.js +76 -17
  30. package/es/components/Tearsheet/index.d.ts +2 -0
  31. package/es/components/Tearsheet/usePresence.d.ts +17 -0
  32. package/es/components/Tearsheet/usePresence.js +69 -0
  33. package/es/components/Tearsheet/usePresenceContext.d.ts +25 -0
  34. package/es/components/Tearsheet/usePresenceContext.js +50 -0
  35. package/es/global/js/hooks/useMergedRefs.d.ts +1 -0
  36. package/es/global/js/hooks/useMergedRefs.js +32 -0
  37. package/es/global/js/hooks/useOverflowString.js +1 -16
  38. package/es/index.js +1 -0
  39. package/lib/components/AddSelect/AddSelectBody.js +1 -1
  40. package/lib/components/Coachmark/next/Coachmark/CoachmarkBeacon/CoachmarkBeacon.js +6 -6
  41. package/lib/components/Datagrid/Datagrid/addons/CustomizeColumns/CustomizeColumnsTearsheet.js +1 -1
  42. package/lib/components/PageHeader/PageHeader.js +3 -11
  43. package/lib/components/PageHeader/next/PageHeader.js +29 -12
  44. package/lib/components/PageHeader/next/context.d.ts +3 -0
  45. package/lib/components/PageHeader/next/utils.js +8 -0
  46. package/lib/components/SidePanel/SidePanel.js +10 -2
  47. package/lib/components/Tearsheet/TearsheetPresence.d.ts +37 -0
  48. package/lib/components/Tearsheet/TearsheetPresence.js +61 -0
  49. package/lib/components/Tearsheet/TearsheetShell.js +74 -15
  50. package/lib/components/Tearsheet/index.d.ts +2 -0
  51. package/lib/components/Tearsheet/usePresence.d.ts +17 -0
  52. package/lib/components/Tearsheet/usePresence.js +71 -0
  53. package/lib/components/Tearsheet/usePresenceContext.d.ts +25 -0
  54. package/lib/components/Tearsheet/usePresenceContext.js +52 -0
  55. package/lib/global/js/hooks/useMergedRefs.d.ts +1 -0
  56. package/lib/global/js/hooks/useMergedRefs.js +34 -0
  57. package/lib/global/js/hooks/useOverflowString.js +0 -16
  58. package/lib/index.js +3 -0
  59. package/package.json +22 -21
  60. package/scss/components/APIKeyModal/_api-key-modal.scss +6 -4
  61. package/scss/components/AboutModal/_about-modal.scss +5 -5
  62. package/scss/components/ActionBar/_action-bar.scss +2 -0
  63. package/scss/components/ActionSet/_action-set.scss +12 -11
  64. package/scss/components/AddSelect/_add-select.scss +28 -29
  65. package/scss/components/BreadcrumbWithOverflow/_breadcrumb-with-overflow.scss +10 -8
  66. package/scss/components/ButtonMenu/_button-menu.scss +11 -9
  67. package/scss/components/Card/_card.scss +12 -10
  68. package/scss/components/Checklist/_checklist.scss +8 -6
  69. package/scss/components/Coachmark/_coachmark-overlay.scss +11 -9
  70. package/scss/components/Coachmark/_coachmark.scss +1 -1
  71. package/scss/components/CoachmarkStack/_coachmark-stack.scss +6 -4
  72. package/scss/components/ComboButton/_combo-button.scss +11 -9
  73. package/scss/components/CreateFullPage/_create-full-page.scss +9 -9
  74. package/scss/components/CreateModal/_create-modal.scss +9 -7
  75. package/scss/components/CreateSidePanel/_create-side-panel.scss +6 -4
  76. package/scss/components/CreateTearsheet/_create-tearsheet.scss +9 -9
  77. package/scss/components/CreateTearsheetNarrow/_create-tearsheet-narrow.scss +5 -3
  78. package/scss/components/Datagrid/_datagrid.scss +9 -7
  79. package/scss/components/Datagrid/styles/_datagrid.scss +86 -86
  80. package/scss/components/Datagrid/styles/_useExpandedRow.scss +11 -9
  81. package/scss/components/Datagrid/styles/_useInlineEdit.scss +48 -46
  82. package/scss/components/Datagrid/styles/_useNestedRows.scss +16 -16
  83. package/scss/components/Datagrid/styles/_useNestedTable.scss +5 -3
  84. package/scss/components/Datagrid/styles/_useSelectAllToggle.scss +4 -2
  85. package/scss/components/Datagrid/styles/_useSortableColumns.scss +21 -19
  86. package/scss/components/Datagrid/styles/addons/_CustomizeColumnsTearsheet.scss +5 -4
  87. package/scss/components/Datagrid/styles/addons/_FilterFlyout.scss +5 -5
  88. package/scss/components/Datagrid/styles/addons/_FilterPanel.scss +11 -8
  89. package/scss/components/Datagrid/styles/addons/_RowSizeDropdown.scss +18 -16
  90. package/scss/components/Datagrid/styles/addons/_animations.scss +4 -4
  91. package/scss/components/DescriptionList/_description-list.scss +6 -4
  92. package/scss/components/EditInPlace/_edit-in-place.scss +5 -9
  93. package/scss/components/EditSidePanel/_edit-side-panel.scss +6 -4
  94. package/scss/components/EditTearsheet/_edit-tearsheet.scss +8 -9
  95. package/scss/components/ExportModal/_export-modal.scss +7 -5
  96. package/scss/components/FilterPanel/_filter-panel-accordion-item.scss +6 -5
  97. package/scss/components/FilterPanel/_filter-panel-checkbox-with-overflow.scss +6 -5
  98. package/scss/components/FilterPanel/_filter-panel-checkbox.scss +6 -5
  99. package/scss/components/FilterPanel/_filter-panel.scss +6 -5
  100. package/scss/components/FilterSummary/_filter-summary.scss +5 -9
  101. package/scss/components/Guidebanner/_guidebanner.scss +5 -3
  102. package/scss/components/ImportModal/_import-modal.scss +16 -16
  103. package/scss/components/InterstitialScreen/_interstitial-screen.scss +6 -4
  104. package/scss/components/NotificationsPanel/_notifications-panel.scss +13 -8
  105. package/scss/components/OptionsTile/_options-tile.scss +8 -6
  106. package/scss/components/PageHeader/_page-header.scss +25 -21
  107. package/scss/components/RemoveModal/_remove-modal.scss +5 -4
  108. package/scss/components/Saving/_saving.scss +5 -3
  109. package/scss/components/SearchBar/_search-bar.scss +5 -4
  110. package/scss/components/SidePanel/_animations.scss +4 -4
  111. package/scss/components/SidePanel/_side-panel.scss +31 -12
  112. package/scss/components/SimpleHeader/_simple-header.scss +5 -4
  113. package/scss/components/StatusIcon/_status-icon.scss +5 -3
  114. package/scss/components/StatusIndicator/_status-indicator.scss +3 -2
  115. package/scss/components/StringFormatter/_string-formatter.scss +5 -4
  116. package/scss/components/TagOverflow/_tag-overflow.scss +7 -6
  117. package/scss/components/TagSet/_tag-set.scss +20 -18
  118. package/scss/components/Tearsheet/_tearsheet.scss +121 -30
  119. package/scss/components/Toolbar/_toolbar.scss +4 -2
  120. package/scss/components/TruncatedList/_truncated-list.scss +4 -3
  121. package/scss/components/TruncatedText/_truncated-text.scss +2 -2
  122. package/scss/components/UserAvatar/_user-avatar.scss +5 -4
  123. package/scss/components/UserProfileImage/_user-profile-image.scss +11 -7
  124. package/scss/components/WebTerminal/_web-terminal.scss +4 -2
  125. package/telemetry.yml +3 -0
@@ -12,6 +12,7 @@ import cx from 'classnames';
12
12
  import { Tag } from '@carbon/react';
13
13
  import { Tearsheet } from '../Tearsheet/Tearsheet.js';
14
14
  import { TearsheetNarrow } from '../Tearsheet/TearsheetNarrow.js';
15
+ import { pkg } from '../../settings.js';
15
16
  import '../EmptyStates/EmptyState.js';
16
17
  import '../EmptyStates/EmptyStateV2.deprecated.js';
17
18
  import '../EmptyStates/ErrorEmptyState/ErrorEmptyState.js';
@@ -30,7 +31,6 @@ import { getFilteredItems, sortItems } from './add-select-utils.js';
30
31
  import { useItemSort } from './hooks/useItemSort.js';
31
32
  import useParentSelect from './hooks/useParentSelect.js';
32
33
  import usePath from './hooks/usePath.js';
33
- import { pkg } from '../../settings.js';
34
34
 
35
35
  const blockClass = `${pkg.prefix}--add-select`;
36
36
  const componentName = 'AddSelectBody';
@@ -31,18 +31,18 @@ const CoachmarkBeacon = /*#__PURE__*/forwardRef((props, ref) => {
31
31
  } = props;
32
32
  return /*#__PURE__*/React__default.createElement("div", _extends({
33
33
  className: cx(blockClass, `${blockClass}-${kind}`, className)
34
- }, getDevtoolsProps(componentName), {
35
- role: "tooltip"
36
- }, rest, {
34
+ }, getDevtoolsProps(componentName), rest, {
37
35
  ref: ref
38
36
  }), /*#__PURE__*/React__default.createElement("button", _extends({
39
37
  type: "button"
40
38
  }, buttonProps, {
41
- className: `${blockClass}__target`
39
+ className: `${blockClass}__target`,
40
+ "aria-label": label
42
41
  }), /*#__PURE__*/React__default.createElement("svg", {
43
42
  className: `${blockClass}__center`,
44
- "aria-label": label
45
- }, /*#__PURE__*/React__default.createElement("title", null, label), _circle || (_circle = /*#__PURE__*/React__default.createElement("circle", {
43
+ "aria-hidden": "true",
44
+ focusable: "false"
45
+ }, _circle || (_circle = /*#__PURE__*/React__default.createElement("circle", {
46
46
  r: 1,
47
47
  cx: 38,
48
48
  cy: 38
@@ -9,9 +9,9 @@ import React__default, { useState, useRef, useCallback, useEffect } from 'react'
9
9
  import PropTypes from '../../../../../_virtual/index.js';
10
10
  import '../../../../Tearsheet/Tearsheet.js';
11
11
  import { TearsheetNarrow } from '../../../../Tearsheet/TearsheetNarrow.js';
12
+ import { pkg } from '../../../../../settings.js';
12
13
  import Columns from './Columns.js';
13
14
  import Actions from './Actions.js';
14
- import { pkg } from '../../../../../settings.js';
15
15
 
16
16
  const blockClass = `${pkg.prefix}--datagrid`;
17
17
  const CustomizeColumnsTearsheet = _ref => {
@@ -6,7 +6,7 @@
6
6
  */
7
7
 
8
8
  import { extends as _extends } from '../../_virtual/_rollupPluginBabelHelpers.js';
9
- import { Tooltip, Tag, Button, usePrefix, FlexGrid, Row, Column, DefinitionTooltip } from '@carbon/react';
9
+ import { Tooltip, Tag, Button, usePrefix, FlexGrid, Row, Column } from '@carbon/react';
10
10
  import React__default, { useState, useRef, useEffect } from 'react';
11
11
  import { TagSet } from '../TagSet/TagSet.js';
12
12
  import { spacing, baseFontSize, breakpoints } from '@carbon/layout';
@@ -19,7 +19,6 @@ import cx from 'classnames';
19
19
  import { getDevtoolsProps } from '../../global/js/utils/devtools.js';
20
20
  import { pkg } from '../../settings.js';
21
21
  import { useResizeObserver } from '../../global/js/hooks/useResizeObserver.js';
22
- import { useOverflowStringHeight } from '../../global/js/hooks/useOverflowString.js';
23
22
  import { useNearestScroll } from '../../global/js/hooks/useWindowScroll.js';
24
23
  import { useWindowResize } from '../../global/js/hooks/useWindowResize.js';
25
24
  import { useIsomorphicEffect } from '../../global/js/hooks/useIsomorphicEffect.js';
@@ -355,12 +354,6 @@ const PageHeader = /*#__PURE__*/React__default.forwardRef((_ref, ref) => {
355
354
  }
356
355
  });
357
356
  }, [headerRef, pageHeaderStyles]);
358
- const subtitleRef = useRef(null);
359
- const isOverflowing = useOverflowStringHeight(subtitleRef);
360
- const subtitleContent = /*#__PURE__*/React__default.createElement("span", {
361
- ref: subtitleRef,
362
- className: `${blockClass}__subtitle-text`
363
- }, subtitle);
364
357
  return /*#__PURE__*/React__default.createElement(React__default.Fragment, null, /*#__PURE__*/React__default.createElement("div", {
365
358
  className: `${blockClass}--offset-top-measuring-element`,
366
359
  ref: offsetTopMeasuringRef
@@ -437,10 +430,9 @@ const PageHeader = /*#__PURE__*/React__default.forwardRef((_ref, ref) => {
437
430
  className: `${blockClass}__subtitle-row`
438
431
  }, /*#__PURE__*/React__default.createElement(Column, {
439
432
  className: `${blockClass}__subtitle`
440
- }, isOverflowing ? /*#__PURE__*/React__default.createElement(DefinitionTooltip, {
441
- definition: subtitle,
442
- className: `${blockClass}__subtitle-tooltip`
443
- }, subtitleContent) : subtitleContent)), children ? /*#__PURE__*/React__default.createElement(Row, {
433
+ }, /*#__PURE__*/React__default.createElement("span", {
434
+ className: `${blockClass}__subtitle-text`
435
+ }, subtitle))), children ? /*#__PURE__*/React__default.createElement(Row, {
444
436
  className: `${blockClass}__available-row`
445
437
  }, /*#__PURE__*/React__default.createElement(Column, {
446
438
  className: `${blockClass}__available-column`
@@ -57,6 +57,7 @@ const PageHeader = /*#__PURE__*/React__default.forwardRef(function PageHeader(_r
57
57
  const [fullyCollapsed, setFullyCollapsed] = useState(false);
58
58
  const [titleClipped, setTitleClipped] = useState(false);
59
59
  const [contentActionsClipped, setContentActionsClipped] = useState(false);
60
+ const [breadcrumbActionsClipped, setBreadcrumbActionsClipped] = useState(false);
60
61
 
61
62
  // Intersection Observer setup, tracks if the PageHeaderContent is visible on page.
62
63
  // If it is not visible, we should set fully collapsed to true so that the
@@ -101,6 +102,7 @@ const PageHeader = /*#__PURE__*/React__default.forwardRef(function PageHeader(_r
101
102
  entries.forEach(entry => {
102
103
  if (entry.target === refs?.contentActions.current) {
103
104
  setContentActionsClipped(!entry.isIntersecting);
105
+ setBreadcrumbActionsClipped(entry.isIntersecting);
104
106
  }
105
107
  });
106
108
  }, {
@@ -140,7 +142,8 @@ const PageHeader = /*#__PURE__*/React__default.forwardRef(function PageHeader(_r
140
142
  pageActionsInstance,
141
143
  setPageActionsInstance,
142
144
  titleClipped,
143
- contentActionsClipped
145
+ contentActionsClipped,
146
+ breadcrumbActionsClipped
144
147
  }
145
148
  }, /*#__PURE__*/React__default.createElement("div", _extends({
146
149
  className: classNames,
@@ -167,10 +170,11 @@ const PageHeaderBreadcrumbBar = /*#__PURE__*/React__default.forwardRef(function
167
170
  pageActionsFlush,
168
171
  ...other
169
172
  } = _ref2;
173
+ const context = usePageHeader();
170
174
  const {
171
175
  pageActionsInstance: globalActions,
172
176
  contentActionsClipped
173
- } = usePageHeader();
177
+ } = context;
174
178
  const classNames = cx({
175
179
  [`${blockClass}__breadcrumb-bar`]: true,
176
180
  [`${blockClass}__breadcrumb-bar-border`]: border,
@@ -181,7 +185,12 @@ const PageHeaderBreadcrumbBar = /*#__PURE__*/React__default.forwardRef(function
181
185
  [`${blockClass}__breadcrumb__content-actions-with-global-actions`]: !!globalActions,
182
186
  [`${blockClass}__breadcrumb__content-actions-with-global-actions--show`]: contentActionsClipped
183
187
  });
184
- return /*#__PURE__*/React__default.createElement("div", _extends({
188
+ return /*#__PURE__*/React__default.createElement(PageHeaderContext.Provider, {
189
+ value: {
190
+ ...context,
191
+ isContentActionsInBreadcrumbBar: true
192
+ }
193
+ }, /*#__PURE__*/React__default.createElement("div", _extends({
185
194
  className: classNames,
186
195
  ref: ref
187
196
  }, other), /*#__PURE__*/React__default.createElement(Grid, null, /*#__PURE__*/React__default.createElement(Column, {
@@ -198,7 +207,7 @@ const PageHeaderBreadcrumbBar = /*#__PURE__*/React__default.forwardRef(function
198
207
  className: `${blockClass}__breadcrumb__actions`
199
208
  }, /*#__PURE__*/React__default.createElement("div", {
200
209
  className: contentActionsClasses
201
- }, contentActions), pageActions)))));
210
+ }, contentActions), pageActions))))));
202
211
  });
203
212
  PageHeaderBreadcrumbBar.displayName = 'PageHeaderBreadcrumbBar';
204
213
 
@@ -330,12 +339,14 @@ const PageHeaderContentPageActions = _ref4 => {
330
339
  } = _ref4;
331
340
  const {
332
341
  setRefs,
333
- contentActionsClipped
342
+ contentActionsClipped,
343
+ breadcrumbActionsClipped,
344
+ isContentActionsInBreadcrumbBar: isInBreadcrumbBar
334
345
  } = usePageHeader();
335
346
  const classNames = cx(`${blockClass}__content__page-actions`, {
336
347
  // Revisit this:
337
348
  // May want to only add this class if there are content actions in the breadcrumb bar as well
338
- [`${blockClass}__content__page-actions--clipped`]: contentActionsClipped
349
+ [`${blockClass}__content__page-actions--clipped`]: isInBreadcrumbBar ? breadcrumbActionsClipped : contentActionsClipped
339
350
  }, className);
340
351
  const containerRef = useRef(null);
341
352
  const offsetRef = useRef(null);
@@ -351,12 +362,18 @@ const PageHeaderContentPageActions = _ref4 => {
351
362
  }
352
363
  }, [menuButtonVisibility]);
353
364
  useEffect(() => {
354
- setRefs(prev => ({
355
- ...prev,
356
- contentActions: containerRef
357
- }));
358
- // eslint-disable-next-line react-hooks/exhaustive-deps
359
- }, []);
365
+ if (isInBreadcrumbBar) {
366
+ setRefs(prev => ({
367
+ ...prev,
368
+ breadcrumbActions: containerRef
369
+ }));
370
+ } else {
371
+ setRefs(prev => ({
372
+ ...prev,
373
+ contentActions: containerRef
374
+ }));
375
+ }
376
+ }, [isInBreadcrumbBar, setRefs]);
360
377
  useEffect(() => {
361
378
  if (!containerRef.current || !Array.isArray(actions)) {
362
379
  return;
@@ -14,6 +14,7 @@ export type PageHeaderRefs = {
14
14
  contentRef?: RefObject<HTMLDivElement | null>;
15
15
  titleRef?: RefObject<HTMLHeadingElement | null>;
16
16
  contentActions?: RefObject<HTMLDivElement | null>;
17
+ breadcrumbActions?: RefObject<HTMLDivElement | null>;
17
18
  };
18
19
  type PageHeaderContextType = {
19
20
  refs?: PageHeaderRefs;
@@ -23,6 +24,8 @@ type PageHeaderContextType = {
23
24
  fullyCollapsed?: boolean;
24
25
  titleClipped?: boolean;
25
26
  contentActionsClipped?: boolean;
27
+ breadcrumbActionsClipped?: boolean;
28
+ isContentActionsInBreadcrumbBar?: boolean;
26
29
  };
27
30
  export declare const PageHeaderContext: import("react").Context<PageHeaderContextType | undefined>;
28
31
  export declare function usePageHeader(): PageHeaderContextType;
@@ -39,6 +39,14 @@ const windowExists = typeof window !== `undefined`;
39
39
  */
40
40
  const scrollable = target => {
41
41
  const style = window.getComputedStyle(target);
42
+ const tagName = target.tagName.toLowerCase();
43
+
44
+ // Exclude body/html from hidden check (modals set overflow:hidden on body)
45
+ if (tagName === 'body' || tagName === 'html') {
46
+ return /(auto|scroll)/.test(style.overflow);
47
+ }
48
+
49
+ // For other elements, include hidden as it may be intentional scroll container
42
50
  return /(auto|scroll|hidden)/.test(style.overflow);
43
51
  };
44
52
 
@@ -9,7 +9,7 @@ import { extends as _extends } from '../../_virtual/_rollupPluginBabelHelpers.js
9
9
  import { ArrowLeft, Close } from '@carbon/react/icons';
10
10
  import { Button, Section, IconButton, Layer, Heading } from '@carbon/react';
11
11
  import { useFeatureFlag } from '../FeatureFlags/index.js';
12
- import React__default, { useState, useRef, useEffect, useCallback } from 'react';
12
+ import React__default, { useState, useRef, useCallback, useEffect } from 'react';
13
13
  import { ActionSet } from '../ActionSet/ActionSet.js';
14
14
  import { Resizer } from '@carbon-labs/react-resizer';
15
15
  import PropTypes from '../../_virtual/index.js';
@@ -113,11 +113,19 @@ const SidePanel = /*#__PURE__*/React__default.forwardRef((props, ref) => {
113
113
 
114
114
  // Title animation on scroll related state
115
115
  const [labelTextHeight, setLabelTextHeight] = useState(0);
116
- const handleEscapeKey = event => {
116
+ const handleEscapeKey = useCallback(event => {
117
117
  if (event.key === 'Escape' && open) {
118
118
  onRequestClose?.();
119
119
  }
120
- };
120
+ }, [onRequestClose, open]);
121
+ useEffect(() => {
122
+ if (open && !slideIn) {
123
+ window.addEventListener('keydown', handleEscapeKey);
124
+ return () => {
125
+ window.removeEventListener('keydown', handleEscapeKey);
126
+ };
127
+ }
128
+ }, [handleEscapeKey, open, slideIn]);
121
129
  useEffect(() => {
122
130
  if (!enableResizer) {
123
131
  return;
@@ -0,0 +1,37 @@
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
+ import React, { type ComponentType, type FC, type PropsWithChildren } from 'react';
8
+ import { type PresenceContext } from './usePresenceContext';
9
+ export interface TearsheetPresenceProps {
10
+ /**
11
+ * Specify whether the Modal is currently open
12
+ */
13
+ open: boolean;
14
+ /**
15
+ * Internal property for backwards compatibility. Specify whether the Modal should opt in to presence mode.
16
+ */
17
+ _autoEnablePresence?: boolean;
18
+ /**
19
+ * Internal property to predefine the presence context's id for exclusivity.
20
+ */
21
+ _presenceId?: string;
22
+ }
23
+ export declare const TearsheetPresence: ({ open, _presenceId: presenceId, _autoEnablePresence: autoEnablePresence, children, }: PropsWithChildren<TearsheetPresenceProps>) => React.JSX.Element | null;
24
+ interface ModalPresenceContextProps extends PresenceContext {
25
+ autoEnablePresence: boolean;
26
+ }
27
+ export declare const TearsheetPresenceContext: React.Context<ModalPresenceContextProps | undefined>;
28
+ /**
29
+ * Handles occurrences where only a single modal must consume a context.
30
+ */
31
+ export declare const useExclusiveTearsheetPresenceContext: (id: string) => ModalPresenceContextProps | undefined;
32
+ type WithModalPresenceProps = Pick<TearsheetPresenceProps, 'open'>;
33
+ /**
34
+ * Higher-order function that wraps a component with ModalPresence
35
+ */
36
+ export declare const withTearsheetPresence: <TProps extends object>(Component: ComponentType<TProps>) => FC<TProps & WithModalPresenceProps>;
37
+ export {};
@@ -0,0 +1,56 @@
1
+ /**
2
+ * Copyright IBM Corp. 2020, 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 React__default, { createContext, useContext, useMemo } from 'react';
9
+ import { usePresenceContext } from './usePresenceContext.js';
10
+
11
+ const TearsheetPresence = _ref => {
12
+ let {
13
+ open,
14
+ _presenceId: presenceId,
15
+ _autoEnablePresence: autoEnablePresence = true,
16
+ children
17
+ } = _ref;
18
+ const [isPresent, context] = usePresenceContext(open, presenceId);
19
+ const contextValue = useMemo(() => ({
20
+ autoEnablePresence,
21
+ ...context
22
+ }), [autoEnablePresence, context]);
23
+ if (!isPresent) {
24
+ return null;
25
+ }
26
+ return /*#__PURE__*/React__default.createElement(TearsheetPresenceContext.Provider, {
27
+ value: contextValue
28
+ }, children);
29
+ };
30
+ const TearsheetPresenceContext = /*#__PURE__*/createContext(undefined);
31
+
32
+ /**
33
+ * Handles occurrences where only a single modal must consume a context.
34
+ */
35
+ const useExclusiveTearsheetPresenceContext = id => {
36
+ const ctx = useContext(TearsheetPresenceContext);
37
+ return ctx?.isPresenceExclusive(id) ? ctx : undefined;
38
+ };
39
+ /**
40
+ * Higher-order function that wraps a component with ModalPresence
41
+ */
42
+ const withTearsheetPresence = Component => {
43
+ const WithModalPresence = props => {
44
+ const {
45
+ open,
46
+ ...componentProps
47
+ } = props;
48
+ return /*#__PURE__*/React__default.createElement(TearsheetPresence, {
49
+ open: open
50
+ }, /*#__PURE__*/React__default.createElement(Component, componentProps));
51
+ };
52
+ WithModalPresence.displayName = `withModalPresence(${Component.displayName || Component.name || 'Component'})`;
53
+ return WithModalPresence;
54
+ };
55
+
56
+ export { TearsheetPresence, TearsheetPresenceContext, useExclusiveTearsheetPresenceContext, withTearsheetPresence };
@@ -6,7 +6,7 @@
6
6
  */
7
7
 
8
8
  import { extends as _extends } from '../../_virtual/_rollupPluginBabelHelpers.js';
9
- import React__default, { useRef, useState, useEffect } from 'react';
9
+ import React__default, { useContext, useRef, useState, useEffect } from 'react';
10
10
  import { useResizeObserver } from '../../global/js/hooks/useResizeObserver.js';
11
11
  import PropTypes from '../../_virtual/index.js';
12
12
  import cx from 'classnames';
@@ -14,11 +14,14 @@ import { pkg } from '../../settings.js';
14
14
  import pconsole from '../../global/js/utils/pconsole.js';
15
15
  import { getNodeTextContent } from '../../global/js/utils/getNodeTextContent.js';
16
16
  import { deprecateProp } from '../../global/js/utils/props-helper.js';
17
- import { Button, usePrefix, unstable_FeatureFlags, ComposedModal, ModalHeader, Section, Layer, Heading } from '@carbon/react';
17
+ import { Button, useFeatureFlag, usePrefix, unstable_FeatureFlags, ComposedModal, ModalHeader, Layer, Section } from '@carbon/react';
18
18
  import { ActionSet } from '../ActionSet/ActionSet.js';
19
19
  import { Wrap } from '../../global/js/utils/Wrap.js';
20
20
  import { usePortalTarget } from '../../global/js/hooks/usePortalTarget.js';
21
21
  import { useFocus } from '../../global/js/hooks/useFocus.js';
22
+ import { useMergedRefs } from '../../global/js/hooks/useMergedRefs.js';
23
+ import { useId } from '../../global/js/utils/useId.js';
24
+ import { TearsheetPresenceContext, useExclusiveTearsheetPresenceContext, TearsheetPresence } from './TearsheetPresence.js';
22
25
  import { usePreviousValue } from '../../global/js/hooks/usePreviousValue.js';
23
26
  import { useIsomorphicEffect } from '../../global/js/hooks/useIsomorphicEffect.js';
24
27
 
@@ -72,6 +75,35 @@ const SectionLevel3 = _ref => {
72
75
  * See the canvas tab for the component API details.
73
76
  * */
74
77
  const TearsheetShell = /*#__PURE__*/React__default.forwardRef((_ref2, ref) => {
78
+ let {
79
+ open,
80
+ ...props
81
+ } = _ref2;
82
+ const id = useId();
83
+ const enablePresence = useFeatureFlag('enable-presence');
84
+ const hasPresenceContext = Boolean(useContext(TearsheetPresenceContext));
85
+ const hasPresenceOptIn = enablePresence || hasPresenceContext;
86
+ const exclusivePresenceContext = useExclusiveTearsheetPresenceContext(id);
87
+
88
+ // if opt in and not exclusive to a presence context, wrap with presence
89
+ if (hasPresenceOptIn && !exclusivePresenceContext) {
90
+ return /*#__PURE__*/React__default.createElement(TearsheetPresence, {
91
+ open: open ?? false,
92
+ _presenceId: id
93
+ // do not auto enable styles for opt-in by feature flag
94
+ ,
95
+ _autoEnablePresence: hasPresenceContext
96
+ }, /*#__PURE__*/React__default.createElement(TearsheetShellDialog, _extends({
97
+ open: true,
98
+ ref: ref
99
+ }, props)));
100
+ }
101
+ return /*#__PURE__*/React__default.createElement(TearsheetShellDialog, _extends({
102
+ ref: ref,
103
+ open: open
104
+ }, props));
105
+ });
106
+ const TearsheetShellDialog = /*#__PURE__*/React__default.forwardRef((_ref3, ref) => {
75
107
  let {
76
108
  // The component props, in alphabetical order (for consistency).
77
109
  actions,
@@ -91,7 +123,7 @@ const TearsheetShell = /*#__PURE__*/React__default.forwardRef((_ref2, ref) => {
91
123
  label,
92
124
  navigation,
93
125
  onClose,
94
- open,
126
+ open: externalOpen,
95
127
  portalTarget: portalTargetIn,
96
128
  selectorPrimaryFocus,
97
129
  selectorsFloatingMenus = [],
@@ -102,7 +134,7 @@ const TearsheetShell = /*#__PURE__*/React__default.forwardRef((_ref2, ref) => {
102
134
  launcherButtonRef,
103
135
  // Collect any other property values passed in.
104
136
  ...rest
105
- } = _ref2;
137
+ } = _ref3;
106
138
  const carbonPrefix = usePrefix();
107
139
  const bcModalHeader = `${carbonPrefix}--modal-header`;
108
140
  const renderPortalUse = usePortalTarget(portalTargetIn);
@@ -113,13 +145,19 @@ const TearsheetShell = /*#__PURE__*/React__default.forwardRef((_ref2, ref) => {
113
145
  const {
114
146
  width
115
147
  } = useResizeObserver(resizer);
116
- const prevOpen = usePreviousValue(open);
117
148
  const {
118
149
  keyDownListener,
119
150
  claimFocus
120
151
  } = useFocus(modalRef, selectorPrimaryFocus);
121
152
  modalRef.current;
122
153
  const wide = size === 'wide';
154
+ const presenceContext = useContext(TearsheetPresenceContext);
155
+ const mergedRefs = useMergedRefs([modalRef, presenceContext?.presenceRef]);
156
+ const enablePresence = useFeatureFlag('enable-presence') || presenceContext?.autoEnablePresence;
157
+
158
+ // always mark as open when mounted with presence
159
+ const open = externalOpen || enablePresence;
160
+ const prevOpen = usePreviousValue(open);
123
161
 
124
162
  // Keep track of the stack depth and our position in it (1-based, 0=closed)
125
163
  const [depth, setDepth] = useState(0);
@@ -146,13 +184,28 @@ const TearsheetShell = /*#__PURE__*/React__default.forwardRef((_ref2, ref) => {
146
184
  claimFocus();
147
185
  }
148
186
  }, [open, currentStep, effectiveHasCloseIcon, claimFocus]);
187
+
188
+ // Focus launcher button on open change for non presence tearsheet
149
189
  useEffect(() => {
150
- if (prevOpen && !open && launcherButtonRef?.current) {
190
+ if (!enablePresence && prevOpen && !open && launcherButtonRef?.current) {
151
191
  setTimeout(() => {
152
192
  launcherButtonRef?.current?.focus();
153
193
  }, 10);
154
194
  }
155
- }, [open, prevOpen, launcherButtonRef]);
195
+ }, [enablePresence, open, prevOpen, launcherButtonRef]);
196
+
197
+ // Focus launcher button on unmount for presence tearsheet
198
+ useEffect(() => {
199
+ const launcherButton = launcherButtonRef?.current;
200
+ if (!enablePresence || !launcherButton) {
201
+ return;
202
+ }
203
+ return () => {
204
+ setTimeout(() => {
205
+ launcherButton.focus();
206
+ }, 10);
207
+ };
208
+ }, [enablePresence, launcherButtonRef]);
156
209
  useEffect(() => {
157
210
  requestAnimationFrame(() => {
158
211
  if (open && depth === position && !modalRef?.current?.contains(document.activeElement)) {
@@ -167,6 +220,8 @@ const TearsheetShell = /*#__PURE__*/React__default.forwardRef((_ref2, ref) => {
167
220
  }
168
221
  }, [claimFocus, hasError, modalRef]);
169
222
  useEffect(() => {
223
+ // Other tearsheets should already be notified if this tearsheet is exiting
224
+ const isPresent = open && !presenceContext?.isExiting;
170
225
  const notify = () => stack.all.forEach(handler => {
171
226
  handler(Math.min(stack.open.length, maxDepth), stack.open.indexOf(handler) + 1);
172
227
  });
@@ -179,7 +234,7 @@ const TearsheetShell = /*#__PURE__*/React__default.forwardRef((_ref2, ref) => {
179
234
  // false to true to open it then append its notification callback to
180
235
  // the end of the stack array (as its ID), and call all the callbacks
181
236
  // to notify all open tearsheets that the stacking has changed.
182
- if (open) {
237
+ if (isPresent) {
183
238
  stack.open.push(handleStackChange);
184
239
  notify();
185
240
  }
@@ -201,7 +256,7 @@ const TearsheetShell = /*#__PURE__*/React__default.forwardRef((_ref2, ref) => {
201
256
  notify();
202
257
  }
203
258
  };
204
- }, [open, size]);
259
+ }, [open, presenceContext?.isExiting, size]);
205
260
  const areAllSameSizeVariant = () => new Set(stack.sizes).size === 1;
206
261
  useIsomorphicEffect(() => {
207
262
  const setScaleValues = () => {
@@ -217,8 +272,8 @@ const TearsheetShell = /*#__PURE__*/React__default.forwardRef((_ref2, ref) => {
217
272
  };
218
273
  };
219
274
  if (modalRef.current) {
220
- Object.entries(setScaleValues()).map(_ref3 => {
221
- let [key, value] = _ref3;
275
+ Object.entries(setScaleValues()).map(_ref4 => {
276
+ let [key, value] = _ref4;
222
277
  modalRef.current.style.setProperty(key, String(value));
223
278
  });
224
279
  }
@@ -245,7 +300,9 @@ const TearsheetShell = /*#__PURE__*/React__default.forwardRef((_ref2, ref) => {
245
300
  [`${bc}--has-slug`]: deprecated_slug,
246
301
  [`${bc}--has-ai-label`]: !!decorator && decorator['type']?.displayName === 'AILabel',
247
302
  [`${bc}--has-decorator`]: !!decorator && decorator['type']?.displayName !== 'AILabel',
248
- [`${bc}--has-close`]: effectiveHasCloseIcon
303
+ [`${bc}--has-close`]: effectiveHasCloseIcon,
304
+ ['is-visible']: enablePresence,
305
+ [`${bc}--tearsheet-enable-presence`]: presenceContext?.autoEnablePresence
249
306
  }),
250
307
  decorator: decorator || deprecated_slug,
251
308
  containerClassName: cx(`${bc}__container`, {
@@ -257,9 +314,10 @@ const TearsheetShell = /*#__PURE__*/React__default.forwardRef((_ref2, ref) => {
257
314
  selectorPrimaryFocus,
258
315
  onKeyDown: keyDownListener,
259
316
  preventCloseOnClickOutside: !isPassive,
260
- ref: modalRef,
317
+ ref: mergedRefs,
261
318
  selectorsFloatingMenus: [`.${carbonPrefix}--overflow-menu-options`, `.${carbonPrefix}--tooltip`, '.flatpickr-calendar', `.${bc}__container`, `.${carbonPrefix}--menu`, ...selectorsFloatingMenus],
262
- size: "sm"
319
+ size: "sm",
320
+ "data-tearsheet-exiting": presenceContext?.isExiting || undefined
263
321
  }), includeHeader && /*#__PURE__*/React__default.createElement(ModalHeader, {
264
322
  className: cx(`${bc}__header`, {
265
323
  [`${bc}__header--with-close-icon`]: effectiveHasCloseIcon,
@@ -270,16 +328,17 @@ const TearsheetShell = /*#__PURE__*/React__default.forwardRef((_ref2, ref) => {
270
328
  }),
271
329
  closeModal: onClose,
272
330
  iconDescription: effectiveHasCloseIcon ? closeIconDescription : undefined
273
- }, /*#__PURE__*/React__default.createElement(Section, {
331
+ }, /*#__PURE__*/React__default.createElement(Wrap, {
274
332
  className: `${bc}__header-content`,
275
333
  element: wide ? Layer : undefined
276
334
  }, /*#__PURE__*/React__default.createElement(Wrap, {
277
335
  className: `${bc}__header-fields`
278
336
  }, /*#__PURE__*/React__default.createElement(Wrap, {
279
337
  className: `${bcModalHeader}__label`
280
- }, label), /*#__PURE__*/React__default.createElement(Section, {
338
+ }, label), /*#__PURE__*/React__default.createElement(Wrap, {
339
+ element: "h3",
281
340
  className: cx(`${bcModalHeader}__heading`, `${bc}__heading`)
282
- }, /*#__PURE__*/React__default.createElement(Heading, null, title)), /*#__PURE__*/React__default.createElement(Wrap, {
341
+ }, title), /*#__PURE__*/React__default.createElement(Wrap, {
283
342
  className: `${bc}__header-description`
284
343
  }, description)), /*#__PURE__*/React__default.createElement(Wrap, {
285
344
  className: `${bc}__header-actions`
@@ -6,5 +6,7 @@
6
6
  */
7
7
  export { Tearsheet } from './Tearsheet';
8
8
  export { TearsheetNarrow } from './TearsheetNarrow';
9
+ export { TearsheetPresence, withTearsheetPresence } from './TearsheetPresence';
9
10
  export type { TearsheetProps } from './Tearsheet';
10
11
  export type { TearsheetNarrowProps } from './TearsheetNarrow';
12
+ export type { TearsheetPresenceProps } from './TearsheetPresence';
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Copyright IBM Corp. 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 RefObject } from 'react';
8
+ export declare const usePresence: (ref: RefObject<HTMLElement | null>, isOpen: boolean) => {
9
+ /**
10
+ * Indicates whether the ref object is supposed to be mounted
11
+ */
12
+ isPresent: boolean;
13
+ /**
14
+ * Indicates whether the ref object is currently exiting
15
+ */
16
+ isExiting: boolean;
17
+ };
@@ -0,0 +1,69 @@
1
+ /**
2
+ * Copyright IBM Corp. 2020, 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 { useState, useCallback, useLayoutEffect } from 'react';
9
+ import { pkg } from '../../settings.js';
10
+
11
+ const usePresence = (ref, isOpen) => {
12
+ const [exitState, setExitState] = useState(isOpen ? 'idle' : 'finished');
13
+ const isExiting = exitState === 'active';
14
+
15
+ // element is exiting
16
+ if (!isOpen && exitState === 'idle') {
17
+ setExitState('active');
18
+ }
19
+
20
+ // element exit was interrupted
21
+ if (isOpen && exitState !== 'idle') {
22
+ setExitState('idle');
23
+ }
24
+ const handleAnimationEnd = useCallback(() => {
25
+ setExitState('finished');
26
+ }, []);
27
+ useLayoutEffect(() => {
28
+ if (!ref.current || !isExiting) {
29
+ return;
30
+ }
31
+
32
+ // resolve for JSDOM
33
+ if (!('getAnimations' in ref.current)) {
34
+ handleAnimationEnd();
35
+ return;
36
+ }
37
+
38
+ // cover all animations that start with the presence prefix
39
+ const animations = ref.current.getAnimations({
40
+ subtree: true
41
+ }).filter(animation => animation instanceof CSSAnimation && animation.animationName.startsWith(`${pkg.prefix}`));
42
+ if (!animations.length) {
43
+ handleAnimationEnd();
44
+ return;
45
+ }
46
+ let cancelled = false;
47
+ Promise.all(animations.map(animation => animation.finished)).finally(() => {
48
+ if (cancelled) {
49
+ return;
50
+ }
51
+ handleAnimationEnd();
52
+ });
53
+ return () => {
54
+ cancelled = true;
55
+ };
56
+ }, [ref, isExiting, handleAnimationEnd]);
57
+ return {
58
+ /**
59
+ * Indicates whether the ref object is supposed to be mounted
60
+ */
61
+ isPresent: isOpen || exitState !== 'finished',
62
+ /**
63
+ * Indicates whether the ref object is currently exiting
64
+ */
65
+ isExiting
66
+ };
67
+ };
68
+
69
+ export { usePresence };