Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions packages/components/src/components/popover/popover.scss
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,11 @@
block-size: fit-content;
inline-size: fit-content;

// CSS Anchor Positioning: set this element as the anchor for the popover content
@supports (anchor-name: --db-popover-anchor) {
anchor-name: --db-popover-anchor;
}

&:is([data-e2e-hover="true"], :hover, :focus-within) {
> article:not([data-open="false"]) {
@extend %show-popover;
Expand Down
5 changes: 5 additions & 0 deletions packages/components/src/components/tooltip/tooltip.scss
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,11 @@

[data-has-tooltip="true"] {
position: var(--db-tooltip-parent-position, relative);

// CSS Anchor Positioning: set this element as the anchor for the tooltip
@supports (anchor-name: --db-popover-anchor) {
anchor-name: --db-popover-anchor;
}
}

$popover-states: "hover", "focus-visible";
Expand Down
111 changes: 111 additions & 0 deletions packages/components/src/styles/internal/_popover-component.scss
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,17 @@ $popover-gap-size: var(--db-popover-distance);
$popover-inset: calc(100% + #{$popover-gap-size} * var(--db-popover-gap, 0));
$popover-gap-inset: calc(-1 * #{$popover-gap-size});

// CSS Anchor Positioning fallback strategies for different placement types
// Used with position-try-fallbacks to enable automatic repositioning when viewport edges are reached
$anchor-fallbacks-vertical:
flip-block,
flip-inline,
flip-block flip-inline;
$anchor-fallbacks-horizontal:
flip-inline,
flip-block,
flip-block flip-inline;

%popover-center {
&:not([data-placement], [data-corrected-placement]),
&[data-placement="bottom"]:not([data-corrected-placement]),
Expand Down Expand Up @@ -155,6 +166,106 @@ $popover-gap-inset: calc(-1 * #{$popover-gap-size});
}
}
}

// CSS Anchor Positioning with automatic collision detection
// This is progressively enhanced - only applied when the browser supports it
// stylelint-disable no-invalid-position-declaration
@supports (anchor-name: --db-popover-anchor) {
position: fixed;
position-anchor: --db-popover-anchor;
inset: unset;
// stylelint-disable-next-line db-ux/use-spacings
margin: var(--db-popover-distance);

// The pseudo element for bridging the gap is not needed with anchor positioning
&::before {
display: none;
}

// Default placement (bottom center) with fallbacks
&:not([data-placement]) {
position-area: block-end center;
// stylelint-disable-next-line property-no-unknown
position-try-fallbacks: $anchor-fallbacks-vertical;
}

// Bottom placements
&[data-placement="bottom"] {
position-area: block-end center;
// stylelint-disable-next-line property-no-unknown
position-try-fallbacks: $anchor-fallbacks-vertical;
}

&[data-placement="bottom-start"] {
position-area: block-end start;
// stylelint-disable-next-line property-no-unknown
position-try-fallbacks: $anchor-fallbacks-vertical;
}

&[data-placement="bottom-end"] {
position-area: block-end end;
// stylelint-disable-next-line property-no-unknown
position-try-fallbacks: $anchor-fallbacks-vertical;
}

// Top placements
&[data-placement="top"] {
position-area: block-start center;
// stylelint-disable-next-line property-no-unknown
position-try-fallbacks: $anchor-fallbacks-vertical;
}

&[data-placement="top-start"] {
position-area: block-start start;
// stylelint-disable-next-line property-no-unknown
position-try-fallbacks: $anchor-fallbacks-vertical;
}

&[data-placement="top-end"] {
position-area: block-start end;
// stylelint-disable-next-line property-no-unknown
position-try-fallbacks: $anchor-fallbacks-vertical;
}

// Left placements
&[data-placement="left"] {
position-area: center inline-start;
// stylelint-disable-next-line property-no-unknown
position-try-fallbacks: $anchor-fallbacks-horizontal;
}

&[data-placement="left-start"] {
position-area: start inline-start;
// stylelint-disable-next-line property-no-unknown
position-try-fallbacks: $anchor-fallbacks-horizontal;
}

&[data-placement="left-end"] {
position-area: end inline-start;
// stylelint-disable-next-line property-no-unknown
position-try-fallbacks: $anchor-fallbacks-horizontal;
}

// Right placements
&[data-placement="right"] {
position-area: center inline-end;
// stylelint-disable-next-line property-no-unknown
position-try-fallbacks: $anchor-fallbacks-horizontal;
}

&[data-placement="right-start"] {
position-area: start inline-end;
// stylelint-disable-next-line property-no-unknown
position-try-fallbacks: $anchor-fallbacks-horizontal;
}

&[data-placement="right-end"] {
position-area: end inline-end;
// stylelint-disable-next-line property-no-unknown
position-try-fallbacks: $anchor-fallbacks-horizontal;
}
}
// stylelint-enable no-invalid-position-declaration
}

@mixin set-no-animation {
Expand Down
41 changes: 40 additions & 1 deletion packages/components/src/utils/floating-components.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,31 @@
// TODO: We should reevaluate this as soon as CSS Anchor Positioning is supported in all relevant browsers
/**
* Check if the browser supports CSS Anchor Positioning.
* This is used to determine whether to use JavaScript positioning or let CSS handle it.
*/
export const supportsCSSAnchorPositioning = (): boolean => {
if (typeof window === 'undefined' || typeof CSS === 'undefined') {
return false;
}

return CSS.supports('anchor-name', '--db-popover-anchor');
};

// Cached result for CSS Anchor Positioning support to avoid repeated checks
let _cssAnchorPositioningSupported: boolean | undefined;

/**
* Get cached result for CSS Anchor Positioning support.
* Uses memoization to avoid repeated CSS.supports() calls.
*/
const getCSSAnchorPositioningSupport = (): boolean => {
if (_cssAnchorPositioningSupported === undefined) {
_cssAnchorPositioningSupported = supportsCSSAnchorPositioning();
}
return _cssAnchorPositioningSupported;
};

// The JavaScript positioning code below is only used for browsers that don't support CSS Anchor Positioning.
// Modern browsers will use CSS Anchor Positioning with automatic collision detection via position-try-fallbacks.
const isInView = (el: HTMLElement) => {
const { top, bottom, left, right } = el.getBoundingClientRect();
const { innerHeight, innerWidth } = window;
Expand Down Expand Up @@ -75,6 +102,12 @@ export const handleFixedDropdown = (
parent: HTMLElement,
placement: string
) => {
// Skip JavaScript positioning if CSS Anchor Positioning is supported
// CSS will handle positioning and collision detection automatically
if (getCSSAnchorPositioningSupport()) {
return;
}

if (!element || !parent) return;
// We skip this if we are in mobile it's already fixed
if (getComputedStyle(element).zIndex === '9999') return;
Expand Down Expand Up @@ -291,6 +324,12 @@ export const handleFixedPopover = (
parent: HTMLElement,
placement: string
) => {
// Skip JavaScript positioning if CSS Anchor Positioning is supported
// CSS will handle positioning and collision detection automatically
if (getCSSAnchorPositioningSupport()) {
return;
}

if (!element || !parent) return;
const parentComputedStyles = getComputedStyle(parent);
const parentHasFloatingPosition = ['absolute', 'fixed'].includes(
Expand Down
Loading