@porsche-design-system/components-react 4.2.0-rc.4 → 4.2.0-rc.5

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 (24) hide show
  1. package/CHANGELOG.md +17 -0
  2. package/cjs/lib/components/flyout.wrapper.cjs +3 -3
  3. package/esm/lib/components/flyout.wrapper.d.ts +11 -1
  4. package/esm/lib/components/flyout.wrapper.mjs +3 -3
  5. package/package.json +2 -2
  6. package/ssr/cjs/components/dist/styles/esm/styles-entry.cjs +85 -41
  7. package/ssr/cjs/components/dist/utils/esm/utils-entry.cjs +139 -21
  8. package/ssr/cjs/components-react/projects/react-ssr-wrapper/src/lib/components/flyout.wrapper.cjs +4 -4
  9. package/ssr/cjs/components-react/projects/react-ssr-wrapper/src/lib/dsr-components/banner.cjs +6 -0
  10. package/ssr/cjs/components-react/projects/react-ssr-wrapper/src/lib/dsr-components/flyout.cjs +7 -1
  11. package/ssr/cjs/components-react/projects/react-ssr-wrapper/src/lib/dsr-components/modal.cjs +6 -0
  12. package/ssr/cjs/components-react/projects/react-ssr-wrapper/src/lib/dsr-components/sheet.cjs +6 -0
  13. package/ssr/esm/components/dist/styles/esm/styles-entry.mjs +85 -41
  14. package/ssr/esm/components/dist/utils/esm/utils-entry.mjs +136 -22
  15. package/ssr/esm/components-react/projects/react-ssr-wrapper/src/lib/components/flyout.wrapper.mjs +4 -4
  16. package/ssr/esm/components-react/projects/react-ssr-wrapper/src/lib/dsr-components/banner.mjs +7 -1
  17. package/ssr/esm/components-react/projects/react-ssr-wrapper/src/lib/dsr-components/flyout.mjs +8 -2
  18. package/ssr/esm/components-react/projects/react-ssr-wrapper/src/lib/dsr-components/modal.mjs +7 -1
  19. package/ssr/esm/components-react/projects/react-ssr-wrapper/src/lib/dsr-components/sheet.mjs +7 -1
  20. package/ssr/esm/lib/components/flyout.wrapper.d.ts +11 -1
  21. package/ssr/esm/lib/dsr-components/banner.d.ts +1 -0
  22. package/ssr/esm/lib/dsr-components/flyout.d.ts +1 -0
  23. package/ssr/esm/lib/dsr-components/modal.d.ts +1 -0
  24. package/ssr/esm/lib/dsr-components/sheet.d.ts +1 -0
package/CHANGELOG.md CHANGED
@@ -14,6 +14,23 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0),
14
14
 
15
15
  ## [Unreleased]
16
16
 
17
+ ## [4.2.0-rc.5] - 2026-06-12
18
+
19
+ ### Added
20
+
21
+ - `Flyout`: Prop `fullscreen` to stretch the flyout to the full viewport width with squared corners, useful for smaller
22
+ viewports ([#4498](https://github.com/porsche-design-system/porsche-design-system/pull/4498))
23
+
24
+ ### Fixed
25
+
26
+ - `Flyout`, `Modal`, `Sheet` ([#4498](https://github.com/porsche-design-system/porsche-design-system/pull/4498)):
27
+ - Nested dialog elements no longer cause rendering issues in Chromium
28
+ - Keep the dialog element on the `#top-layer` during the fade-out animation in Safari and Firefox, so closing
29
+ (especially nested) dialogs no longer drop behind surrounding content
30
+ - `Banner`: Keep the popover on the `#top-layer` during the fade-out animation in Safari and Firefox, so it no longer
31
+ drops behind surrounding content while closing
32
+ ([#4498](https://github.com/porsche-design-system/porsche-design-system/pull/4498))
33
+
17
34
  ## [4.2.0-rc.4] - 2026-06-10
18
35
 
19
36
  ### Fixed
@@ -6,16 +6,16 @@ var react = require('react');
6
6
  var hooks = require('../../hooks.cjs');
7
7
  var utils = require('../../utils.cjs');
8
8
 
9
- const PFlyout = /*#__PURE__*/ react.forwardRef(({ aria, backdrop = 'blur', background = 'canvas', disableBackdropClick = false, footerBehavior = 'sticky', onDismiss, onMotionHiddenEnd, onMotionVisibleEnd, open = false, position = 'end', className, ...rest }, ref) => {
9
+ const PFlyout = /*#__PURE__*/ react.forwardRef(({ aria, backdrop = 'blur', background = 'canvas', disableBackdropClick = false, footerBehavior = 'sticky', fullscreen = false, onDismiss, onMotionHiddenEnd, onMotionVisibleEnd, open = false, position = 'end', className, ...rest }, ref) => {
10
10
  const elementRef = react.useRef(undefined);
11
11
  hooks.useEventCallback(elementRef, 'dismiss', onDismiss);
12
12
  hooks.useEventCallback(elementRef, 'motionHiddenEnd', onMotionHiddenEnd);
13
13
  hooks.useEventCallback(elementRef, 'motionVisibleEnd', onMotionVisibleEnd);
14
14
  const WebComponentTag = hooks.usePrefix('p-flyout');
15
- const propsToSync = [aria, backdrop, background, disableBackdropClick, footerBehavior, open, position];
15
+ const propsToSync = [aria, backdrop, background, disableBackdropClick, footerBehavior, fullscreen, open, position];
16
16
  hooks.useBrowserLayoutEffect(() => {
17
17
  const { current } = elementRef;
18
- ['aria', 'backdrop', 'background', 'disableBackdropClick', 'footerBehavior', 'open', 'position'].forEach((propName, i) => (current[propName] = propsToSync[i]));
18
+ ['aria', 'backdrop', 'background', 'disableBackdropClick', 'footerBehavior', 'fullscreen', 'open', 'position'].forEach((propName, i) => (current[propName] = propsToSync[i]));
19
19
  }, propsToSync);
20
20
  const props = {
21
21
  ...rest,
@@ -1,5 +1,5 @@
1
1
  import type { BaseProps } from '../../BaseProps';
2
- import type { SelectedAriaAttributes, FlyoutAriaAttribute, FlyoutBackdrop, FlyoutBackground, FlyoutFooterBehavior, FlyoutMotionHiddenEndEventDetail, FlyoutMotionVisibleEndEventDetail, FlyoutPosition } from '../types';
2
+ import type { SelectedAriaAttributes, FlyoutAriaAttribute, FlyoutBackdrop, FlyoutBackground, FlyoutFooterBehavior, BreakpointCustomizable, FlyoutMotionHiddenEndEventDetail, FlyoutMotionVisibleEndEventDetail, FlyoutPosition } from '../types';
3
3
  export type PFlyoutProps = BaseProps & {
4
4
  /**
5
5
  * Sets ARIA attributes.
@@ -25,6 +25,11 @@ export type PFlyoutProps = BaseProps & {
25
25
  * @default 'sticky'
26
26
  */
27
27
  footerBehavior?: FlyoutFooterBehavior;
28
+ /**
29
+ * If true the flyout stretches to the full viewport width with squared corners. Useful for smaller viewports where the flyout would otherwise fill the screen but still show rounded corners.
30
+ * @default false
31
+ */
32
+ fullscreen?: BreakpointCustomizable<boolean>;
28
33
  /**
29
34
  * Emitted when the component requests to be dismissed.
30
35
  */
@@ -73,6 +78,11 @@ export declare const PFlyout: import("react").ForwardRefExoticComponent<Omit<imp
73
78
  * @default 'sticky'
74
79
  */
75
80
  footerBehavior?: FlyoutFooterBehavior;
81
+ /**
82
+ * If true the flyout stretches to the full viewport width with squared corners. Useful for smaller viewports where the flyout would otherwise fill the screen but still show rounded corners.
83
+ * @default false
84
+ */
85
+ fullscreen?: BreakpointCustomizable<boolean>;
76
86
  /**
77
87
  * Emitted when the component requests to be dismissed.
78
88
  */
@@ -4,16 +4,16 @@ import { forwardRef, useRef } from 'react';
4
4
  import { useEventCallback, usePrefix, useBrowserLayoutEffect, useMergedClass } from '../../hooks.mjs';
5
5
  import { syncRef } from '../../utils.mjs';
6
6
 
7
- const PFlyout = /*#__PURE__*/ forwardRef(({ aria, backdrop = 'blur', background = 'canvas', disableBackdropClick = false, footerBehavior = 'sticky', onDismiss, onMotionHiddenEnd, onMotionVisibleEnd, open = false, position = 'end', className, ...rest }, ref) => {
7
+ const PFlyout = /*#__PURE__*/ forwardRef(({ aria, backdrop = 'blur', background = 'canvas', disableBackdropClick = false, footerBehavior = 'sticky', fullscreen = false, onDismiss, onMotionHiddenEnd, onMotionVisibleEnd, open = false, position = 'end', className, ...rest }, ref) => {
8
8
  const elementRef = useRef(undefined);
9
9
  useEventCallback(elementRef, 'dismiss', onDismiss);
10
10
  useEventCallback(elementRef, 'motionHiddenEnd', onMotionHiddenEnd);
11
11
  useEventCallback(elementRef, 'motionVisibleEnd', onMotionVisibleEnd);
12
12
  const WebComponentTag = usePrefix('p-flyout');
13
- const propsToSync = [aria, backdrop, background, disableBackdropClick, footerBehavior, open, position];
13
+ const propsToSync = [aria, backdrop, background, disableBackdropClick, footerBehavior, fullscreen, open, position];
14
14
  useBrowserLayoutEffect(() => {
15
15
  const { current } = elementRef;
16
- ['aria', 'backdrop', 'background', 'disableBackdropClick', 'footerBehavior', 'open', 'position'].forEach((propName, i) => (current[propName] = propsToSync[i]));
16
+ ['aria', 'backdrop', 'background', 'disableBackdropClick', 'footerBehavior', 'fullscreen', 'open', 'position'].forEach((propName, i) => (current[propName] = propsToSync[i]));
17
17
  }, propsToSync);
18
18
  const props = {
19
19
  ...rest,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@porsche-design-system/components-react",
3
- "version": "4.2.0-rc.4",
3
+ "version": "4.2.0-rc.5",
4
4
  "description": "Porsche Design System is a component library designed to help developers create the best experience for software or services distributed by Dr. Ing. h.c. F. Porsche AG.",
5
5
  "keywords": [
6
6
  "porsche",
@@ -21,7 +21,7 @@
21
21
  "url": "https://github.com/porsche-design-system/porsche-design-system"
22
22
  },
23
23
  "dependencies": {
24
- "@porsche-design-system/components-js": "4.2.0-rc.4"
24
+ "@porsche-design-system/components-js": "4.2.0-rc.5"
25
25
  },
26
26
  "peerDependencies": {
27
27
  "ag-grid-community": ">= 35.0.0 <36.0.0",
@@ -4132,12 +4132,30 @@ const OPTION_LIST_SAFE_ZONE = 6;
4132
4132
 
4133
4133
  const getCDNBaseURL = () => global.PORSCHE_DESIGN_SYSTEM_CDN_URL + "/porsche-design-system";
4134
4134
 
4135
- const prefix = `[Porsche Design System v${"4.2.0-rc.4"}]` // this part isn't covered by unit tests
4135
+ const prefix = `[Porsche Design System v${"4.2.0-rc.5"}]` // this part isn't covered by unit tests
4136
4136
  ;
4137
4137
  const consoleError = (...messages) => {
4138
4138
  console.error(prefix, ...messages);
4139
4139
  };
4140
4140
 
4141
+ // Single source of truth for the two CSS feature queries behind the "keep on #top-layer during fade-out" capability,
4142
+ // shared by the JS detection (`supportsOverlayTransition`) and the CSS `@supports` wrapper
4143
+ // (`overlayTransitionSupportsQuery`) so the two can never drift.
4144
+ const overlayFeature = 'overlay: auto';
4145
+ const allowDiscreteFeature = 'transition-behavior: allow-discrete';
4146
+ /**
4147
+ * Wraps JSS styles in `@supports (overlay: auto) and (transition-behavior: allow-discrete)`, kept in sync with
4148
+ * `supportsOverlayTransition()`. Use it for styles that should only apply where the dialog/popover can stay on the
4149
+ * `#top-layer` during its fade-out (Chromium); other browsers defer the native hide instead (see
4150
+ * `createTopLayerController`).
4151
+ *
4152
+ * @param {JssStyle} style - The styles to apply only when the `overlay` transition is supported.
4153
+ * @returns {JssStyle} The `@supports`-wrapped styles.
4154
+ */
4155
+ const overlayTransitionSupportsQuery = (style) => {
4156
+ return { [`@supports (${overlayFeature}) and (${allowDiscreteFeature})`]: style };
4157
+ };
4158
+
4141
4159
  const headerSlot = 'header';
4142
4160
  const anchorSlot = 'anchor';
4143
4161
 
@@ -4685,6 +4703,7 @@ const getComponentCss$18 = (isOpen, position, state, hasDismissButton, hasHeadin
4685
4703
  })),
4686
4704
  left: '50vw',
4687
4705
  width: `min(calc(100vw - 2 * ${ref(cssVarInsetX, gridExtendedOffsetBase)}),${ref(cssVarMaxWidth, '100ch')})`,
4706
+ overlay: 'none',
4688
4707
  '&:popover-open': {
4689
4708
  overlay: 'auto',
4690
4709
  },
@@ -4699,10 +4718,10 @@ const getComponentCss$18 = (isOpen, position, state, hasDismissButton, hasHeadin
4699
4718
  transform: 'translate3d(-50%,0,0)',
4700
4719
  }),
4701
4720
  transition,
4702
- // during transition the element will be removed from top-layer immediately, resulting in other elements laying over (as of Mai 2024 only Chrome is fixed by this)
4703
- '@supports (transition-behavior: allow-discrete)': {
4721
+ // keep the popover on the #top-layer while the fade-out runs (Chromium only; see `overlayTransitionSupportsQuery`)
4722
+ ...overlayTransitionSupportsQuery({
4704
4723
  transition: `${transition},${getTransition('overlay', duration, easing)} allow-discrete`,
4705
- },
4724
+ }),
4706
4725
  },
4707
4726
  },
4708
4727
  }, {
@@ -6093,6 +6112,7 @@ const getDialogBackdropTransitionJssStyle = (isVisible, backdrop = 'blur') => {
6093
6112
  height: '100dvh',
6094
6113
  visibility: 'inherit',
6095
6114
  pointerEvents: 'auto',
6115
+ overlay: 'auto',
6096
6116
  background: ref(colorBackdrop),
6097
6117
  ...(isBackdropBlur && {
6098
6118
  WebkitBackdropFilter: ref(blurFrosted),
@@ -6108,14 +6128,14 @@ const getDialogBackdropTransitionJssStyle = (isVisible, backdrop = 'blur') => {
6108
6128
  height: '0px',
6109
6129
  visibility: 'hidden', // element shall not be tabbable with keyboard after fade out transition has finished
6110
6130
  pointerEvents: 'none', // element can't be interacted with mouse
6131
+ overlay: 'none',
6111
6132
  background: 'transparent',
6112
6133
  }),
6113
6134
  transition,
6114
- // `allow-discrete` transition for ua-style `overlay` (supported browsers only) ensures dialog is rendered on
6115
- // #top-layer as long as fade-in or fade-out transition/animation is running
6116
- '@supports (transition-behavior: allow-discrete)': {
6135
+ // keep the dialog on the #top-layer while the fade-out runs (Chromium only; see `overlayTransitionSupportsQuery`)
6136
+ ...overlayTransitionSupportsQuery({
6117
6137
  transition: `${transition}, ${getTransition('overlay', duration, easing)} allow-discrete`,
6118
- },
6138
+ }),
6119
6139
  };
6120
6140
  };
6121
6141
  const getScrollerJssStyle = (position) => {
@@ -6143,13 +6163,17 @@ const getScrollerJssStyle = (position) => {
6143
6163
  overscrollBehaviorY: 'none',
6144
6164
  // TODO: check if smooth scrolling on iOS is given?
6145
6165
  background: background.light,
6166
+ // ensure a translate3d style is always applied on .scroller and .modal/.flyout/.sheet to create a new stacking
6167
+ // context and prevent a Chromium paint bug: when a dialog element is nested inside another (e.g. `p-modal` within
6168
+ // `p-flyout`)
6169
+ transform: 'translate3d(0,0,0)',
6146
6170
  };
6147
6171
  };
6148
6172
  const dialogBorderRadius = ref(radius3Xl);
6149
6173
  const dialogPaddingTop = ref(spacingFluidMd$1);
6150
6174
  const dialogPaddingBottom = `calc(${dialogBorderRadius} + ${ref(spacingFluidMd$1)})`;
6151
6175
  const dialogPaddingInline = ref(spacingFluidLg);
6152
- const dialogGridJssStyle = (clipPath = 'none') => {
6176
+ const dialogGridJssStyle = () => {
6153
6177
  return {
6154
6178
  position: 'relative',
6155
6179
  display: 'grid',
@@ -6158,8 +6182,12 @@ const dialogGridJssStyle = (clipPath = 'none') => {
6158
6182
  paddingTop: dialogPaddingTop,
6159
6183
  paddingBottom: dialogPaddingBottom,
6160
6184
  alignContent: 'flex-start',
6185
+ // Consumers set their own `clip-path` next to their corner `border-radius` (e.g. `inset(0 round …)`).
6161
6186
  // `overflow: clip` can't be used due to a Chromium bug that drops descendant backdrop-filter tiles (e.g. frosted p-tag); `clip-path` clips slotted content to the rounded corners without the faulty paint-containment box while keeping the scroll behavior intact
6162
- clipPath,
6187
+ // Chromium paint bug: when a dialog element is nested inside another (e.g. `p-modal` within `p-flyout`),
6188
+ // the inner dialog's grid content fails to render. Forcing a new compositing layer via `translate3d`
6189
+ // triggers a repaint and fixes it. Re-check periodically; remove once the upstream Chromium bug is resolved.
6190
+ transform: 'translate3d(0,0,0)',
6163
6191
  };
6164
6192
  };
6165
6193
  const getDialogColorJssStyle = () => {
@@ -6177,13 +6205,13 @@ const getDialogTransitionJssStyle = (isVisible, slideIn) => {
6177
6205
  ...(isVisible
6178
6206
  ? {
6179
6207
  opacity: 1,
6180
- transform: 'initial',
6208
+ transform: 'translate3d(0,0,0)',
6181
6209
  }
6182
6210
  : {
6183
6211
  opacity: 0,
6184
- transform: slideIn === '^' ? 'translateY(25vh)' : `translateX(${slideIn === '>' ? '-' : ''}100%)`,
6212
+ transform: slideIn === '^' ? 'translate3d(0,25vh,0)' : `translate3d(${slideIn === '>' ? '-' : ''}100%,0,0)`,
6185
6213
  '&:dir(rtl)': {
6186
- transform: slideIn === '^' ? 'translateY(25vh)' : `translateX(${slideIn === '>' ? '' : '-'}100%)`,
6214
+ transform: slideIn === '^' ? 'translate3d(0,25vh,0)' : `translate3d(${slideIn === '>' ? '' : '-'}100%,0,0)`,
6187
6215
  },
6188
6216
  }),
6189
6217
  transition: `${getTransition('opacity', duration, easing)}, ${getTransition('transform', duration, easing)}`,
@@ -7294,7 +7322,7 @@ const cssVarRefPaddingBottom$2 = '--ref-p-flyout-pb';
7294
7322
  * @css-variable {"name": "--ref-p-flyout-px", "description": "Exposes the internally used padding-inline of the Flyout as read only CSS variable. When slotting e.g. a media container, this variable can be used to stretch the element to the full horizontal size of the Flyout."}
7295
7323
  */
7296
7324
  const cssVarRefPaddingInline$2 = '--ref-p-flyout-px';
7297
- const getComponentCss$V = (isOpen, background, backdrop, position, hasHeader, hasFooter, hasSubFooter, footerBehavior) => {
7325
+ const getComponentCss$V = (isOpen, background, backdrop, position, hasHeader, hasFooter, hasSubFooter, footerBehavior, fullscreen) => {
7298
7326
  const isPositionStart = position === 'start';
7299
7327
  const isFooterFixed = footerBehavior === 'fixed';
7300
7328
  return getCss({
@@ -7345,35 +7373,49 @@ const getComponentCss$V = (isOpen, background, backdrop, position, hasHeader, ha
7345
7373
  },
7346
7374
  },
7347
7375
  flyout: {
7348
- ...dialogGridJssStyle(isPositionStart
7349
- ? `inset(0 round 0 ${dialogBorderRadius} ${dialogBorderRadius} 0)` // position 'start': round inline-end (right in LTR) corners only
7350
- : `inset(0 round ${dialogBorderRadius} 0 0 ${dialogBorderRadius})` // position 'end': round inline-start (left in LTR) corners only
7351
- ),
7376
+ ...dialogGridJssStyle(),
7352
7377
  ...getDialogColorJssStyle(),
7353
- width: ref(cssVariableWidth$2, 'auto'),
7354
- minWidth: '320px',
7355
- maxWidth: '100vw',
7356
- // `clip-path` uses physical corners, so mirror for RTL to keep parity with the logical border*Radius below
7357
- '&:dir(rtl)': {
7358
- clipPath: isPositionStart
7359
- ? `inset(0 round ${dialogBorderRadius} 0 0 ${dialogBorderRadius})`
7360
- : `inset(0 round 0 ${dialogBorderRadius} ${dialogBorderRadius} 0)`,
7361
- },
7362
- ...(isPositionStart
7378
+ ...buildResponsiveStyles(fullscreen, (fullscreenValue) => fullscreenValue
7363
7379
  ? {
7364
- borderStartEndRadius: dialogBorderRadius,
7365
- borderEndEndRadius: dialogBorderRadius,
7366
- ...forcedColorsMediaQuery({
7367
- borderInlineEnd: '2px solid CanvasText',
7368
- }),
7380
+ // fullscreen spans the whole viewport width, so corners are squared and corner clipping is disabled
7381
+ width: '100dvw',
7382
+ minWidth: 'auto',
7383
+ maxWidth: 'none',
7384
+ borderRadius: 0,
7385
+ clipPath: 'none',
7386
+ // the flyout touches both inline edges, so the inner-side HCM border is no longer needed
7387
+ '&:dir(rtl)': {
7388
+ clipPath: 'none',
7389
+ },
7369
7390
  }
7370
7391
  : {
7371
- borderStartStartRadius: dialogBorderRadius,
7372
- borderEndStartRadius: dialogBorderRadius,
7373
- // TODO: Fix needs to be implemented for Fullscreen (which is not available as prop for Flyout yet)
7374
- ...forcedColorsMediaQuery({
7375
- borderInlineStart: '2px solid CanvasText',
7376
- }),
7392
+ width: ref(cssVariableWidth$2, 'auto'),
7393
+ minWidth: '320px',
7394
+ maxWidth: '100vw',
7395
+ clipPath: isPositionStart
7396
+ ? `inset(0 round 0 ${dialogBorderRadius} ${dialogBorderRadius} 0)` // position 'start': round inline-end (right in LTR) corners only
7397
+ : `inset(0 round ${dialogBorderRadius} 0 0 ${dialogBorderRadius})`, // position 'end': round inline-start (left in LTR) corners only
7398
+ // `clip-path` uses physical corners, so mirror for RTL to keep parity with the logical border*Radius below
7399
+ '&:dir(rtl)': {
7400
+ clipPath: isPositionStart
7401
+ ? `inset(0 round ${dialogBorderRadius} 0 0 ${dialogBorderRadius})`
7402
+ : `inset(0 round 0 ${dialogBorderRadius} ${dialogBorderRadius} 0)`,
7403
+ },
7404
+ ...(isPositionStart
7405
+ ? {
7406
+ borderStartEndRadius: dialogBorderRadius,
7407
+ borderEndEndRadius: dialogBorderRadius,
7408
+ ...forcedColorsMediaQuery({
7409
+ borderInlineEnd: '2px solid CanvasText',
7410
+ }),
7411
+ }
7412
+ : {
7413
+ borderStartStartRadius: dialogBorderRadius,
7414
+ borderEndStartRadius: dialogBorderRadius,
7415
+ ...forcedColorsMediaQuery({
7416
+ borderInlineStart: '2px solid CanvasText',
7417
+ }),
7418
+ }),
7377
7419
  }),
7378
7420
  ...(isFooterFixed && {
7379
7421
  gridTemplateRows: hasHeader ? 'auto 1fr auto' : '1fr',
@@ -8167,7 +8209,7 @@ const getComponentCss$C = (isOpen, background, backdrop, fullscreen, hasDismissB
8167
8209
  },
8168
8210
  scroller: getScrollerJssStyle('fullscreen'),
8169
8211
  modal: {
8170
- ...dialogGridJssStyle(`inset(0 round ${dialogBorderRadius})`),
8212
+ ...dialogGridJssStyle(),
8171
8213
  ...getDialogColorJssStyle(),
8172
8214
  ...getDialogTransitionJssStyle(isOpen, '^'),
8173
8215
  ...buildResponsiveStyles(fullscreen, (fullscreenValue) => fullscreenValue
@@ -8187,6 +8229,7 @@ const getComponentCss$C = (isOpen, background, backdrop, fullscreen, hasDismissB
8187
8229
  placeSelf: 'center',
8188
8230
  margin: `${ref(cssVariableSpacingTop, 'clamp(16px, 10vh, 192px)')} ${gridExtendedOffsetBase} ${ref(cssVariableSpacingBottom, 'clamp(16px, 10vh, 192px)')}`, // horizontal margin is needed to ensure modal is placed on "Porsche Grid" when slotted content is wider than the viewport width
8189
8231
  borderRadius: dialogBorderRadius,
8232
+ clipPath: `inset(0 round ${dialogBorderRadius})`, // non-fullscreen has rounded corners, so clip slotted content to them
8190
8233
  ...forcedColorsMediaQuery({
8191
8234
  outline: '2px solid CanvasText',
8192
8235
  outlineOffset: '-2px',
@@ -9239,7 +9282,7 @@ const getComponentCss$n = (isOpen, background, hasDismissButton) => {
9239
9282
  },
9240
9283
  scroller: getScrollerJssStyle('fullscreen'),
9241
9284
  sheet: {
9242
- ...dialogGridJssStyle(`inset(0 round ${dialogBorderRadius} ${dialogBorderRadius} 0 0)`), // round top corners only
9285
+ ...dialogGridJssStyle(),
9243
9286
  ...getDialogColorJssStyle(),
9244
9287
  ...getDialogTransitionJssStyle(isOpen, '^'),
9245
9288
  width: '100%',
@@ -9247,6 +9290,7 @@ const getComponentCss$n = (isOpen, background, hasDismissButton) => {
9247
9290
  marginBlockStart: ref(spacingFluidLg), // ensures minimal space at the top to visualize paper sheet like border top radius in case sheet becomes scrollable
9248
9291
  borderTopLeftRadius: dialogBorderRadius,
9249
9292
  borderTopRightRadius: dialogBorderRadius,
9293
+ clipPath: `inset(0 round ${dialogBorderRadius} ${dialogBorderRadius} 0 0)`, // round top corners only
9250
9294
  ...forcedColorsMediaQuery({
9251
9295
  borderTop: '2px solid CanvasText',
9252
9296
  }),
@@ -1,5 +1,26 @@
1
1
  'use strict';
2
2
 
3
+ function getHTMLElement(element, selector) {
4
+ return element?.querySelector(selector);
5
+ }
6
+
7
+ const transformSelectorToDirectChildSelector = (selector) => selector
8
+ .split(',')
9
+ .map((part) => `:scope>${part}`)
10
+ .join();
11
+
12
+ /* eslint-disable prefer-arrow/prefer-arrow-functions */
13
+ function getDirectChildHTMLElement(element, selector) {
14
+ // querySelector(All) doesn't work with :scope pseudo class and comma separator in jsdom, yet
15
+ // https://github.com/jsdom/jsdom/issues/3141
16
+ // therefore we got a workaround so it works nicely when consumed from jsdom-polyfill package
17
+ return (transformSelectorToDirectChildSelector(selector)
18
+ .split(',')
19
+ .map((sel) => getHTMLElement(element, sel))
20
+ .filter((x) => x)[0] || null // comma separated selector might return null, so we have to filter
21
+ );
22
+ }
23
+
3
24
  function _extends() {
4
25
  _extends = Object.assign ? Object.assign.bind() : function (target) {
5
26
  for (var i = 1; i < arguments.length; i++) {
@@ -3191,26 +3212,30 @@ const getTagNameWithoutPrefix = (host) => {
3191
3212
  return (tagNameWithoutPrefix || tagName); // return tagName as fallback for default tags
3192
3213
  };
3193
3214
 
3194
- function getHTMLElement(element, selector) {
3195
- return element?.querySelector(selector);
3196
- }
3197
-
3198
- const transformSelectorToDirectChildSelector = (selector) => selector
3199
- .split(',')
3200
- .map((part) => `:scope>${part}`)
3201
- .join();
3202
-
3203
- /* eslint-disable prefer-arrow/prefer-arrow-functions */
3204
- function getDirectChildHTMLElement(element, selector) {
3205
- // querySelector(All) doesn't work with :scope pseudo class and comma separator in jsdom, yet
3206
- // https://github.com/jsdom/jsdom/issues/3141
3207
- // therefore we got a workaround so it works nicely when consumed from jsdom-polyfill package
3208
- return (transformSelectorToDirectChildSelector(selector)
3209
- .split(',')
3210
- .map((sel) => getHTMLElement(element, sel))
3211
- .filter((x) => x)[0] || null // comma separated selector might return null, so we have to filter
3212
- );
3213
- }
3215
+ const parseCssTimeToMs = (value) => {
3216
+ const trimmed = value.trim();
3217
+ const num = Number.parseFloat(trimmed);
3218
+ if (Number.isNaN(num)) {
3219
+ return 0;
3220
+ }
3221
+ return trimmed.endsWith('ms') ? num : num * 1000; // seconds otherwise
3222
+ };
3223
+ /**
3224
+ * Returns the longest `transition-duration` + `transition-delay` (in ms) across an element's computed transitions.
3225
+ * Useful as a safety-net timeout when a `transitionend` event might not fire (e.g. with reduced motion or a 0 duration).
3226
+ *
3227
+ * @param {HTMLElement} element - The element to read the computed transition values from.
3228
+ * @returns {number} The maximum combined duration and delay in milliseconds.
3229
+ */
3230
+ const getMaxTransitionDurationMs = (element) => {
3231
+ const { transitionDuration, transitionDelay } = getComputedStyle(element);
3232
+ const durations = transitionDuration.split(',');
3233
+ const delays = transitionDelay.split(',');
3234
+ return durations.reduce((max, duration, index) => {
3235
+ const total = parseCssTimeToMs(duration) + parseCssTimeToMs(delays[index] ?? '0s');
3236
+ return total > max ? total : max;
3237
+ }, 0);
3238
+ };
3214
3239
 
3215
3240
  const hasSpecificDirectChildTag = (host, tag) => {
3216
3241
  const el = getDirectChildHTMLElement(host, ':only-child');
@@ -3457,6 +3482,18 @@ hasWindow$1 &&
3457
3482
  }
3458
3483
  });
3459
3484
 
3485
+ const showDialog = (dialog, scrollArea) => {
3486
+ // Must only be called when the dialog isn't already open and after the render cycle has finished (e.g. in
3487
+ // `componentDidRender()`), so visibility states are ready and the dismiss button can be focused correctly.
3488
+ // The "only when not already open" precondition is guaranteed by the caller (`createTopLayerController`'s `requestShow`
3489
+ // guards with `!isShown()`), since `showModal()` throws if the dialog is already open.
3490
+ scrollArea.scrollTo(0, 0); // reset scroll position each time dialog gets opened again
3491
+ dialog.inert = true; // This will prevent the autofocus of focusable elements inside the dialog (e.g. close button) element which is conflicting with our transition
3492
+ dialog.showModal(); // shows modal on `#top-layer`
3493
+ dialog.inert = false; // Re-enable focus on dialog element
3494
+ dialog.focus(); // set focus programmatically to dialog element to prevent transition bug in Safari
3495
+ };
3496
+
3460
3497
  const getCDNBaseURL = () => global.PORSCHE_DESIGN_SYSTEM_CDN_URL + "/porsche-design-system";
3461
3498
 
3462
3499
  const hasDocument = typeof document !== 'undefined';
@@ -3465,7 +3502,7 @@ const hasShowPickerSupport = () => (hasDocument &&
3465
3502
  'showPicker' in HTMLInputElement.prototype &&
3466
3503
  CSS.supports('selector(::-webkit-calendar-picker-indicator)'));
3467
3504
 
3468
- const prefix = `[Porsche Design System v${"4.2.0-rc.4"}]` // this part isn't covered by unit tests
3505
+ const prefix = `[Porsche Design System v${"4.2.0-rc.5"}]` // this part isn't covered by unit tests
3469
3506
  ;
3470
3507
  const consoleError$1 = (...messages) => {
3471
3508
  console.error(prefix, ...messages);
@@ -3486,6 +3523,83 @@ const supportsNativePopover = () => {
3486
3523
  const hasNativePopoverSupport = supportsNativePopover();
3487
3524
  // getter for easy mocking
3488
3525
  const getHasNativePopoverSupport = () => hasNativePopoverSupport;
3526
+
3527
+ // Single source of truth for the two CSS feature queries behind the "keep on #top-layer during fade-out" capability,
3528
+ // shared by the JS detection (`supportsOverlayTransition`) and the CSS `@supports` wrapper
3529
+ // (`overlayTransitionSupportsQuery`) so the two can never drift.
3530
+ const overlayFeature = 'overlay: auto';
3531
+ const allowDiscreteFeature = 'transition-behavior: allow-discrete';
3532
+ /**
3533
+ * Detects whether the browser can keep an element (e.g. a `dialog` or a `popover`) on the `#top-layer` during a
3534
+ * fade-out animation via the `overlay` property combined with `transition-behavior: allow-discrete`.
3535
+ *
3536
+ * BOTH capabilities are required: `transition-behavior: allow-discrete` is now widely supported (e.g. Firefox), but the
3537
+ * `overlay` property itself is Chromium-only. Firefox supports `allow-discrete` yet NOT `overlay`, so it would drop out
3538
+ * of the `#top-layer` immediately when leaving it and fall back to a high `z-index` — which breaks as soon as an
3539
+ * ancestor creates a new stacking context (e.g. `transform`/`isolation`, like a nested `p-modal` within `p-flyout`).
3540
+ *
3541
+ * For browsers lacking the `overlay` transition the element must be kept natively shown during the fade-out and only be
3542
+ * removed from the `#top-layer` once the transition has finished (see `createTopLayerController`).
3543
+ *
3544
+ * @returns {boolean} `true` if both `overlay` and `transition-behavior: allow-discrete` are supported.
3545
+ */
3546
+ const supportsOverlayTransition = () => typeof CSS !== 'undefined' && CSS.supports(overlayFeature) && CSS.supports(allowDiscreteFeature);
3547
+
3548
+ // Extra time added on top of the computed transition duration before hiding. The timer starts in `requestHide()`,
3549
+ // one frame before the transition actually begins, so this buffer biases the hide slightly *after* the visual
3550
+ // transition ends — preventing the element from leaving the #top-layer too early and flickering.
3551
+ const HIDE_BUFFER_MS = 50;
3552
+ /**
3553
+ * Creates a controller that manages an element's presence on the `#top-layer`, keeping it there during its fade-out
3554
+ * animation in browsers that don't support the `overlay` transition (Safari/Firefox). On hide it defers the native
3555
+ * removal (`dialog.close()` / `element.hidePopover()`) until the fade-out has finished, scheduled via a timeout derived
3556
+ * from the element's computed transition duration. In Chromium the native removal happens immediately because the
3557
+ * `overlay` + `allow-discrete` transition keeps the element on the `#top-layer` while it fades out.
3558
+ *
3559
+ * Both `requestShow` and `requestHide` are idempotent (guarded by `isShown`), so they can safely be called on every
3560
+ * render. State is scoped to the controller instance, so no shared registry is required.
3561
+ *
3562
+ * @param {TopLayerOptions} options - Element-specific hooks (show state, show/hide actions, element getter).
3563
+ * @returns {TopLayerController} The controller used to request show, request hide, and cancel.
3564
+ */
3565
+ const createTopLayerController = (options) => {
3566
+ const { getElement, isShown, show, hide } = options;
3567
+ let hideTimer;
3568
+ const cancel = () => {
3569
+ if (hideTimer) {
3570
+ clearTimeout(hideTimer);
3571
+ hideTimer = undefined;
3572
+ }
3573
+ };
3574
+ const requestShow = () => {
3575
+ cancel(); // cancel any pending deferred hide first, so re-opening during fade-out wins (element may still be shown)
3576
+ if (isShown()) {
3577
+ return; // `showModal()` / `showPopover()` throw if the element is already shown
3578
+ }
3579
+ show();
3580
+ };
3581
+ const requestHide = () => {
3582
+ if (!isShown()) {
3583
+ return; // already hidden (or a deferred hide already completed)
3584
+ }
3585
+ if (supportsOverlayTransition()) {
3586
+ hide(); // Chromium: `overlay` + `allow-discrete` keeps it on the #top-layer during the fade-out
3587
+ }
3588
+ else {
3589
+ // Safari/Firefox: keep it on the real #top-layer during the fade-out and hide once the transition has finished.
3590
+ cancel(); // drop any in-flight deferred hide before re-scheduling
3591
+ const element = getElement();
3592
+ const timeoutMs = (element ? getMaxTransitionDurationMs(element) : 0) + HIDE_BUFFER_MS;
3593
+ hideTimer = setTimeout(() => {
3594
+ cancel();
3595
+ if (isShown()) {
3596
+ hide();
3597
+ }
3598
+ }, timeoutMs);
3599
+ }
3600
+ };
3601
+ return { requestShow, requestHide, cancel };
3602
+ };
3489
3603
  const headerSlot = 'header';
3490
3604
  const anchorSlot = 'anchor';
3491
3605
 
@@ -4121,6 +4235,7 @@ exports.buildIconUrl = buildIconUrl;
4121
4235
  exports.consoleError = consoleError$1;
4122
4236
  exports.createPaginationItems = createPaginationItems;
4123
4237
  exports.createRange = createRange;
4238
+ exports.createTopLayerController = createTopLayerController;
4124
4239
  exports.crestSize = crestSize;
4125
4240
  exports.descriptionId = descriptionId;
4126
4241
  exports.displaySizeToTagMap = displaySizeToTagMap;
@@ -4140,6 +4255,7 @@ exports.getHasNativePopoverSupport = getHasNativePopoverSupport;
4140
4255
  exports.getHeadingTagType = getHeadingTagType;
4141
4256
  exports.getInlineNotificationAriaAttributes = getInlineNotificationAriaAttributes;
4142
4257
  exports.getListboxAriaAttributes = getListboxAriaAttributes;
4258
+ exports.getMaxTransitionDurationMs = getMaxTransitionDurationMs;
4143
4259
  exports.getSanitizedActiveTabIndex = getSanitizedActiveTabIndex;
4144
4260
  exports.getSegmentedControlItemAriaAttributes = getSegmentedControlItemAriaAttributes;
4145
4261
  exports.getStepperHorizontalIconName = getStepperHorizontalIconName;
@@ -4170,8 +4286,10 @@ exports.observedNodesMap = observedNodesMap;
4170
4286
  exports.parseAndGetAriaAttributes = parseAndGetAriaAttributes;
4171
4287
  exports.parseJSONAttribute = parseJSONAttribute;
4172
4288
  exports.setAriaIDREF = setAriaIDREF;
4289
+ exports.showDialog = showDialog;
4173
4290
  exports.supportsConstructableStylesheets = supportsConstructableStylesheets;
4174
4291
  exports.supportsNativePopover = supportsNativePopover;
4292
+ exports.supportsOverlayTransition = supportsOverlayTransition;
4175
4293
  exports.tempDiv = tempDiv;
4176
4294
  exports.tempIcon = tempIcon;
4177
4295
  exports.tempLabel = tempLabel;
@@ -7,23 +7,23 @@ var hooks = require('../../hooks.cjs');
7
7
  var utils = require('../../utils.cjs');
8
8
  var flyout = require('../dsr-components/flyout.cjs');
9
9
 
10
- const PFlyout = /*#__PURE__*/ react.forwardRef(({ aria, backdrop = 'blur', background = 'canvas', disableBackdropClick = false, footerBehavior = 'sticky', onDismiss, onMotionHiddenEnd, onMotionVisibleEnd, open = false, position = 'end', className, children, ...rest }, ref) => {
10
+ const PFlyout = /*#__PURE__*/ react.forwardRef(({ aria, backdrop = 'blur', background = 'canvas', disableBackdropClick = false, footerBehavior = 'sticky', fullscreen = false, onDismiss, onMotionHiddenEnd, onMotionVisibleEnd, open = false, position = 'end', className, children, ...rest }, ref) => {
11
11
  const elementRef = react.useRef(undefined);
12
12
  hooks.useEventCallback(elementRef, 'dismiss', onDismiss);
13
13
  hooks.useEventCallback(elementRef, 'motionHiddenEnd', onMotionHiddenEnd);
14
14
  hooks.useEventCallback(elementRef, 'motionVisibleEnd', onMotionVisibleEnd);
15
15
  const WebComponentTag = hooks.usePrefix('p-flyout');
16
- const propsToSync = [aria, backdrop, background, disableBackdropClick, footerBehavior, open, position];
16
+ const propsToSync = [aria, backdrop, background, disableBackdropClick, footerBehavior, fullscreen, open, position];
17
17
  hooks.useBrowserLayoutEffect(() => {
18
18
  const { current } = elementRef;
19
- ['aria', 'backdrop', 'background', 'disableBackdropClick', 'footerBehavior', 'open', 'position'].forEach((propName, i) => (current[propName] = propsToSync[i]));
19
+ ['aria', 'backdrop', 'background', 'disableBackdropClick', 'footerBehavior', 'fullscreen', 'open', 'position'].forEach((propName, i) => (current[propName] = propsToSync[i]));
20
20
  }, propsToSync);
21
21
  const props = {
22
22
  ...rest,
23
23
  // @ts-ignore
24
24
  ...(!process.browser
25
25
  ? {
26
- children: (jsxRuntime.jsx(flyout.DSRFlyout, { aria, backdrop, background, disableBackdropClick, footerBehavior, open, position, children })),
26
+ children: (jsxRuntime.jsx(flyout.DSRFlyout, { aria, backdrop, background, disableBackdropClick, footerBehavior, fullscreen, open, position, children })),
27
27
  }
28
28
  : {
29
29
  children,
@@ -23,6 +23,12 @@ class DSRBanner extends react.Component {
23
23
  refDismiss;
24
24
  hasHeadingSlot;
25
25
  hasDescriptionSlot;
26
+ topLayer = utilsEntry.createTopLayerController({
27
+ getElement: () => this.props.refPopover,
28
+ isShown: () => !!this.props.refPopover?.matches(':popover-open'),
29
+ show: () => this.props.refPopover?.showPopover(),
30
+ hide: () => this.props.refPopover?.hidePopover(),
31
+ });
26
32
  render() {
27
33
  const { children, namedSlotChildren, otherChildren } = splitChildren.splitChildren(this.props.children);
28
34
  const hasHeadingSlot = namedSlotChildren.filter(({ props: { slot } }) => slot === 'heading').length > 0;