Skip to content

Commit

Permalink
fix(tooltip): use core positioning utils (#227)
Browse files Browse the repository at this point in the history
  • Loading branch information
pawel-twardziak authored Dec 26, 2024
1 parent 1bc4910 commit a1497a7
Show file tree
Hide file tree
Showing 25 changed files with 282 additions and 370 deletions.
4 changes: 4 additions & 0 deletions packages/primitives/core/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,7 @@ export * from './src/inject-ng-control';
export * from './src/is-client';
export * from './src/is-inside-form';
export * from './src/window';

export * from './src/positioning/constants';
export * from './src/positioning/types';
export * from './src/positioning/utils';
Original file line number Diff line number Diff line change
@@ -1,91 +1,84 @@
import { ConnectionPositionPair } from '@angular/cdk/overlay';
import { RdxTooltipAlign, RdxTooltipSide } from './tooltip.types';
import { RdxPositionAlign, RdxPositions, RdxPositionSide } from './types';

type TooltipPositions = {
[key in RdxTooltipSide]: {
[key in RdxTooltipAlign]: ConnectionPositionPair;
};
};

export const TOOLTIP_POSITIONS: TooltipPositions = {
[RdxTooltipSide.Top]: {
[RdxTooltipAlign.Center]: {
export const RDX_POSITIONS: RdxPositions = {
[RdxPositionSide.Top]: {
[RdxPositionAlign.Center]: {
originX: 'center',
originY: 'top',
overlayX: 'center',
overlayY: 'bottom'
},
[RdxTooltipAlign.Start]: {
[RdxPositionAlign.Start]: {
originX: 'start',
originY: 'top',
overlayX: 'start',
overlayY: 'bottom'
},
[RdxTooltipAlign.End]: {
[RdxPositionAlign.End]: {
originX: 'end',
originY: 'top',
overlayX: 'end',
overlayY: 'bottom'
}
},
[RdxTooltipSide.Right]: {
[RdxTooltipAlign.Center]: {
[RdxPositionSide.Right]: {
[RdxPositionAlign.Center]: {
originX: 'end',
originY: 'center',
overlayX: 'start',
overlayY: 'center'
},
[RdxTooltipAlign.Start]: {
[RdxPositionAlign.Start]: {
originX: 'end',
originY: 'top',
overlayX: 'start',
overlayY: 'top'
},
[RdxTooltipAlign.End]: {
[RdxPositionAlign.End]: {
originX: 'end',
originY: 'bottom',
overlayX: 'start',
overlayY: 'bottom'
}
},
[RdxTooltipSide.Bottom]: {
[RdxTooltipAlign.Center]: {
[RdxPositionSide.Bottom]: {
[RdxPositionAlign.Center]: {
originX: 'center',
originY: 'bottom',
overlayX: 'center',
overlayY: 'top'
},
[RdxTooltipAlign.Start]: {
[RdxPositionAlign.Start]: {
originX: 'start',
originY: 'bottom',
overlayX: 'start',
overlayY: 'top'
},
[RdxTooltipAlign.End]: {
[RdxPositionAlign.End]: {
originX: 'end',
originY: 'bottom',
overlayX: 'end',
overlayY: 'top'
}
},
[RdxTooltipSide.Left]: {
[RdxTooltipAlign.Center]: {
[RdxPositionSide.Left]: {
[RdxPositionAlign.Center]: {
originX: 'start',
originY: 'center',
overlayX: 'end',
overlayY: 'center'
},
[RdxTooltipAlign.Start]: {
[RdxPositionAlign.Start]: {
originX: 'start',
originY: 'top',
overlayX: 'end',
overlayY: 'top'
},
[RdxTooltipAlign.End]: {
[RdxPositionAlign.End]: {
originX: 'start',
originY: 'bottom',
overlayX: 'end',
overlayY: 'bottom'
}
}
};
} as const;
33 changes: 33 additions & 0 deletions packages/primitives/core/src/positioning/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { ConnectionPositionPair } from '@angular/cdk/overlay';

export enum RdxPositionSide {
Top = 'top',
Right = 'right',
Bottom = 'bottom',
Left = 'left'
}

export enum RdxPositionAlign {
Start = 'start',
Center = 'center',
End = 'end'
}

export type RdxPositionSideAndAlign = { side: RdxPositionSide; align: RdxPositionAlign };
export type RdxPositionSideAndAlignOffsets = { sideOffset: number; alignOffset: number };

export type RdxPositions = {
[key in RdxPositionSide]: {
[key in RdxPositionAlign]: ConnectionPositionPair;
};
};

export type RdxAllPossibleConnectedPositions = ReadonlyMap<
`${RdxPositionSide}|${RdxPositionAlign}`,
ConnectionPositionPair
>;
export type RdxArrowPositionParams = {
top: string;
left: string;
transform: string;
};
Original file line number Diff line number Diff line change
@@ -1,35 +1,61 @@
import { ConnectedPosition, ConnectionPositionPair } from '@angular/cdk/overlay';
import { POPOVER_POSITIONS } from './popover.constants';
import { RDX_POSITIONS } from './constants';
import {
RdxAllPossibleConnectedPositions,
RdxArrowPositionParams,
RdxPopoverAlign,
RdxPopoverSide,
RdxSideAndAlign,
RdxSideAndAlignOffsets
} from './popover.types';
RdxPositionAlign,
RdxPositionSide,
RdxPositionSideAndAlign,
RdxPositionSideAndAlignOffsets
} from './types';

export function getContentPosition(
sideAndAlignWithOffsets: RdxPositionSideAndAlign & RdxPositionSideAndAlignOffsets
): ConnectedPosition {
const { side, align, sideOffset, alignOffset } = sideAndAlignWithOffsets;
const position = {
...(RDX_POSITIONS[side]?.[align] ?? RDX_POSITIONS[RdxPositionSide.Top][RdxPositionAlign.Center])
};
if (sideOffset || alignOffset) {
if ([RdxPositionSide.Top, RdxPositionSide.Bottom].includes(side)) {
if (sideOffset) {
position.offsetY = side === RdxPositionSide.Top ? -sideOffset : sideOffset;
}
if (alignOffset) {
position.offsetX = alignOffset;
}
} else {
if (sideOffset) {
position.offsetX = side === RdxPositionSide.Left ? -sideOffset : sideOffset;
}
if (alignOffset) {
position.offsetY = alignOffset;
}
}
}
return position;
}

let allPossibleConnectedPositions: RdxAllPossibleConnectedPositions;
export function getAllPossibleConnectedPositions() {
if (!allPossibleConnectedPositions) {
allPossibleConnectedPositions = new Map();
}
if (allPossibleConnectedPositions.size < 1) {
Object.keys(POPOVER_POSITIONS).forEach((side) => {
Object.keys(POPOVER_POSITIONS[side as RdxPopoverSide] ?? {}).forEach((align) => {
(allPossibleConnectedPositions as Map<any, any>).set(
`${side as RdxPopoverSide}|${align as RdxPopoverAlign}`,
POPOVER_POSITIONS[side as RdxPopoverSide][align as RdxPopoverAlign]
);
});
});
for (const [side, aligns] of Object.entries(RDX_POSITIONS)) {
for (const [align, position] of Object.entries(aligns)) {
(allPossibleConnectedPositions as Map<any, any>).set(`${side}|${align}`, position);
}
}
}
return allPossibleConnectedPositions;
}

export function getSideAndAlignFromAllPossibleConnectedPositions(position: ConnectionPositionPair): RdxSideAndAlign {
export function getSideAndAlignFromAllPossibleConnectedPositions(
position: ConnectionPositionPair
): RdxPositionSideAndAlign {
const allPossibleConnectedPositions = getAllPossibleConnectedPositions();
let sideAndAlign: RdxSideAndAlign | undefined;
let sideAndAlign: RdxPositionSideAndAlign | undefined;
allPossibleConnectedPositions.forEach((value, key) => {
if (
position.originX === value.originX &&
Expand All @@ -39,48 +65,21 @@ export function getSideAndAlignFromAllPossibleConnectedPositions(position: Conne
) {
const sideAndAlignArray = key.split('|');
sideAndAlign = {
side: sideAndAlignArray[0] as RdxPopoverSide,
align: sideAndAlignArray[1] as RdxPopoverAlign
side: sideAndAlignArray[0] as RdxPositionSide,
align: sideAndAlignArray[1] as RdxPositionAlign
};
}
});
if (!sideAndAlign) {
throw Error(
`[RdxPopover] cannot infer both side and align from the given position (${JSON.stringify(position)})`
`[Rdx positioning] cannot infer both side and align from the given position (${JSON.stringify(position)})`
);
}
return sideAndAlign;
}

export function getContentPosition(
sideAndAlignWithOffsets: RdxSideAndAlign & RdxSideAndAlignOffsets
): ConnectedPosition {
const { side, align, sideOffset, alignOffset } = sideAndAlignWithOffsets;
const position = {
...(POPOVER_POSITIONS[side]?.[align] ?? POPOVER_POSITIONS[RdxPopoverSide.Top][RdxPopoverAlign.Center])
};
if (sideOffset || alignOffset) {
if ([RdxPopoverSide.Top, RdxPopoverSide.Bottom].includes(side)) {
if (sideOffset) {
position.offsetY = side === RdxPopoverSide.Top ? -sideOffset : sideOffset;
}
if (alignOffset) {
position.offsetX = alignOffset;
}
} else {
if (sideOffset) {
position.offsetX = side === RdxPopoverSide.Left ? -sideOffset : sideOffset;
}
if (alignOffset) {
position.offsetY = alignOffset;
}
}
}
return position;
}

export function getArrowPositionParams(
sideAndAlign: RdxSideAndAlign,
sideAndAlign: RdxPositionSideAndAlign,
arrowWidthAndHeight: { width: number; height: number },
triggerWidthAndHeight: { width: number; height: number }
): RdxArrowPositionParams {
Expand All @@ -90,35 +89,35 @@ export function getArrowPositionParams(
transform: ''
};

if ([RdxPopoverSide.Top, RdxPopoverSide.Bottom].includes(sideAndAlign.side)) {
if (sideAndAlign.side === RdxPopoverSide.Top) {
if ([RdxPositionSide.Top, RdxPositionSide.Bottom].includes(sideAndAlign.side)) {
if (sideAndAlign.side === RdxPositionSide.Top) {
posParams.top = '100%';
} else {
posParams.top = `-${arrowWidthAndHeight.height}px`;
posParams.transform = `rotate(180deg)`;
}

if (sideAndAlign.align === RdxPopoverAlign.Start) {
if (sideAndAlign.align === RdxPositionAlign.Start) {
posParams.left = `${(triggerWidthAndHeight.width - arrowWidthAndHeight.width) / 2}px`;
} else if (sideAndAlign.align === RdxPopoverAlign.Center) {
} else if (sideAndAlign.align === RdxPositionAlign.Center) {
posParams.left = `calc(50% - ${arrowWidthAndHeight.width / 2}px)`;
} else if (sideAndAlign.align === RdxPopoverAlign.End) {
} else if (sideAndAlign.align === RdxPositionAlign.End) {
posParams.left = `calc(100% - ${(triggerWidthAndHeight.width + arrowWidthAndHeight.width) / 2}px)`;
}
} else if ([RdxPopoverSide.Left, RdxPopoverSide.Right].includes(sideAndAlign.side)) {
if (sideAndAlign.side === RdxPopoverSide.Left) {
} else if ([RdxPositionSide.Left, RdxPositionSide.Right].includes(sideAndAlign.side)) {
if (sideAndAlign.side === RdxPositionSide.Left) {
posParams.left = `100%`;
posParams.transform = `rotate(-90deg) translate(0, -50%)`;
} else {
posParams.left = `-${arrowWidthAndHeight.width}px`;
posParams.transform = `rotate(90deg) translate(0, -50%)`;
}

if (sideAndAlign.align === RdxPopoverAlign.Start) {
if (sideAndAlign.align === RdxPositionAlign.Start) {
posParams.top = `${(triggerWidthAndHeight.height - arrowWidthAndHeight.height) / 2}px`;
} else if (sideAndAlign.align === RdxPopoverAlign.Center) {
} else if (sideAndAlign.align === RdxPositionAlign.Center) {
posParams.top = `calc(50% - ${arrowWidthAndHeight.height / 2}px)`;
} else if (sideAndAlign.align === RdxPopoverAlign.End) {
} else if (sideAndAlign.align === RdxPositionAlign.End) {
posParams.top = `calc(100% - ${(triggerWidthAndHeight.height + arrowWidthAndHeight.height) / 2}px)`;
}
}
Expand Down
1 change: 0 additions & 1 deletion packages/primitives/popover/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ export * from './src/popover-content-attributes.component';
export * from './src/popover-content.directive';
export * from './src/popover-root.directive';
export * from './src/popover-trigger.directive';
export * from './src/popover.types';

const _imports = [
RdxPopoverArrowDirective,
Expand Down
4 changes: 2 additions & 2 deletions packages/primitives/popover/src/popover-anchor.directive.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { CdkOverlayOrigin } from '@angular/cdk/overlay';
import { DOCUMENT } from '@angular/common';
import { computed, Directive, ElementRef, forwardRef, inject } from '@angular/core';
import { injectDocument } from '@radix-ng/primitives/core';
import { RdxPopoverAnchorToken } from './popover-anchor.token';
import { RdxPopoverRootDirective } from './popover-root.directive';
import { injectPopoverRoot } from './popover-root.inject';
Expand Down Expand Up @@ -35,7 +35,7 @@ export class RdxPopoverAnchorDirective {
/** @ignore */
readonly overlayOrigin = inject(CdkOverlayOrigin);
/** @ignore */
readonly document = inject(DOCUMENT);
readonly document = injectDocument();

/** @ignore */
readonly name = computed(() => `rdx-popover-external-anchor-${this.popoverRoot?.uniqueId()}`);
Expand Down
Loading

0 comments on commit a1497a7

Please sign in to comment.