From a1497a784712ef45a2045cbd81ec58373071f7b1 Mon Sep 17 00:00:00 2001 From: pawel-twardziak Date: Thu, 26 Dec 2024 13:26:04 +0100 Subject: [PATCH] fix(tooltip): use core positioning utils (#227) --- packages/primitives/core/index.ts | 4 + .../src/positioning/constants.ts} | 45 +++---- .../primitives/core/src/positioning/types.ts | 33 +++++ .../src/positioning/utils.ts} | 113 +++++++++--------- packages/primitives/popover/index.ts | 1 - .../popover/src/popover-anchor.directive.ts | 4 +- .../popover/src/popover-arrow.directive.ts | 36 +++--- .../popover/src/popover-content.directive.ts | 24 ++-- .../popover/src/popover.constants.ts | 85 ------------- .../primitives/popover/src/popover.types.ts | 35 ------ .../popover/src/utils/cdk-event.service.ts | 10 +- .../stories/popover-animations.component.ts | 8 +- .../stories/popover-events.components.ts | 7 +- .../stories/popover-multiple.component.ts | 7 +- .../stories/popover-positioning.component.ts | 11 +- .../popover/stories/popover.docs.mdx | 4 +- ...nore-click-outside-container-base.class.ts | 4 +- .../tooltip/src/get-content-position.ts | 43 ------- .../tooltip/src/tooltip-arrow.directive.ts | 112 +++++++++++------ .../tooltip/src/tooltip-content.directive.ts | 17 +-- .../tooltip/src/tooltip-root.directive.ts | 18 +-- .../primitives/tooltip/src/tooltip.types.ts | 13 -- .../stories/tooltip-positioning.component.ts | 10 +- .../tooltip/stories/tooltip.docs.mdx | 6 +- .../tooltip/stories/tooltip.stories.ts | 2 +- 25 files changed, 282 insertions(+), 370 deletions(-) rename packages/primitives/{tooltip/src/tooltip.constants.ts => core/src/positioning/constants.ts} (65%) create mode 100644 packages/primitives/core/src/positioning/types.ts rename packages/primitives/{popover/src/popover.utils.ts => core/src/positioning/utils.ts} (60%) delete mode 100644 packages/primitives/tooltip/src/get-content-position.ts diff --git a/packages/primitives/core/index.ts b/packages/primitives/core/index.ts index c9d0a264..94ce9d06 100644 --- a/packages/primitives/core/index.ts +++ b/packages/primitives/core/index.ts @@ -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'; diff --git a/packages/primitives/tooltip/src/tooltip.constants.ts b/packages/primitives/core/src/positioning/constants.ts similarity index 65% rename from packages/primitives/tooltip/src/tooltip.constants.ts rename to packages/primitives/core/src/positioning/constants.ts index 3e763af6..7831b727 100644 --- a/packages/primitives/tooltip/src/tooltip.constants.ts +++ b/packages/primitives/core/src/positioning/constants.ts @@ -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; diff --git a/packages/primitives/core/src/positioning/types.ts b/packages/primitives/core/src/positioning/types.ts new file mode 100644 index 00000000..266e9b55 --- /dev/null +++ b/packages/primitives/core/src/positioning/types.ts @@ -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; +}; diff --git a/packages/primitives/popover/src/popover.utils.ts b/packages/primitives/core/src/positioning/utils.ts similarity index 60% rename from packages/primitives/popover/src/popover.utils.ts rename to packages/primitives/core/src/positioning/utils.ts index 2fdb9b92..19911db9 100644 --- a/packages/primitives/popover/src/popover.utils.ts +++ b/packages/primitives/core/src/positioning/utils.ts @@ -1,13 +1,40 @@ 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() { @@ -15,21 +42,20 @@ export function getAllPossibleConnectedPositions() { 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).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).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 && @@ -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 { @@ -90,23 +89,23 @@ 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 { @@ -114,11 +113,11 @@ export function getArrowPositionParams( 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)`; } } diff --git a/packages/primitives/popover/index.ts b/packages/primitives/popover/index.ts index e877d735..3c09a542 100644 --- a/packages/primitives/popover/index.ts +++ b/packages/primitives/popover/index.ts @@ -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, diff --git a/packages/primitives/popover/src/popover-anchor.directive.ts b/packages/primitives/popover/src/popover-anchor.directive.ts index 7656f3ee..1ab215ac 100644 --- a/packages/primitives/popover/src/popover-anchor.directive.ts +++ b/packages/primitives/popover/src/popover-anchor.directive.ts @@ -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'; @@ -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()}`); diff --git a/packages/primitives/popover/src/popover-arrow.directive.ts b/packages/primitives/popover/src/popover-arrow.directive.ts index 73023a94..442a7ecf 100644 --- a/packages/primitives/popover/src/popover-arrow.directive.ts +++ b/packages/primitives/popover/src/popover-arrow.directive.ts @@ -1,6 +1,6 @@ import { ConnectedOverlayPositionChange } from '@angular/cdk/overlay'; import { - AfterViewInit, + afterNextRender, computed, Directive, effect, @@ -13,9 +13,9 @@ import { untracked } from '@angular/core'; import { toSignal } from '@angular/core/rxjs-interop'; +import { getArrowPositionParams, getSideAndAlignFromAllPossibleConnectedPositions } from '@radix-ng/primitives/core'; import { RdxPopoverArrowToken } from './popover-arrow.token'; import { injectPopoverRoot } from './popover-root.inject'; -import { getArrowPositionParams, getSideAndAlignFromAllPossibleConnectedPositions } from './popover.utils'; @Directive({ selector: '[rdxPopoverArrow]', @@ -27,7 +27,7 @@ import { getArrowPositionParams, getSideAndAlignFromAllPossibleConnectedPosition } ] }) -export class RdxPopoverArrowDirective implements AfterViewInit { +export class RdxPopoverArrowDirective { /** @ignore */ private readonly renderer = inject(Renderer2); /** @ignore */ @@ -73,20 +73,21 @@ export class RdxPopoverArrowDirective implements AfterViewInit { private anchorOrTriggerRect: DOMRect; constructor() { + afterNextRender({ + write: () => { + if (this.elementRef.nativeElement.parentElement) { + this.renderer.setStyle(this.elementRef.nativeElement.parentElement, 'position', 'relative'); + } + this.renderer.setStyle(this.elementRef.nativeElement, 'position', 'absolute'); + this.renderer.setStyle(this.elementRef.nativeElement, 'boxSizing', ''); + this.renderer.setStyle(this.elementRef.nativeElement, 'fontSize', '0px'); + } + }); this.onArrowSvgElementChangeEffect(); - this.onContentPositionChangeEffect(); + this.onContentPositionAndArrowDimensionsChangeEffect(); } /** @ignore */ - ngAfterViewInit() { - if (this.elementRef.nativeElement.parentElement) { - this.renderer.setStyle(this.elementRef.nativeElement.parentElement, 'position', 'relative'); - } - this.renderer.setStyle(this.elementRef.nativeElement, 'position', 'absolute'); - this.renderer.setStyle(this.elementRef.nativeElement, 'boxSizing', ''); - this.renderer.setStyle(this.elementRef.nativeElement, 'fontSize', '0px'); - } - private setAnchorOrTriggerRect() { this.anchorOrTriggerRect = ( this.popoverRoot.popoverAnchorDirective() ?? this.popoverRoot.popoverTriggerDirective() @@ -94,11 +95,11 @@ export class RdxPopoverArrowDirective implements AfterViewInit { } /** @ignore */ - private setPosition(position: ConnectedOverlayPositionChange) { + private setPosition(position: ConnectedOverlayPositionChange, arrowDimensions: { width: number; height: number }) { this.setAnchorOrTriggerRect(); const posParams = getArrowPositionParams( getSideAndAlignFromAllPossibleConnectedPositions(position.connectionPair), - { width: this.width(), height: this.height() }, + { width: arrowDimensions.width, height: arrowDimensions.height }, { width: this.anchorOrTriggerRect.width, height: this.anchorOrTriggerRect.height } ); @@ -127,14 +128,15 @@ export class RdxPopoverArrowDirective implements AfterViewInit { } /** @ignore */ - private onContentPositionChangeEffect() { + private onContentPositionAndArrowDimensionsChangeEffect() { effect(() => { const position = this.position(); + const arrowDimensions = { width: this.width(), height: this.height() }; untracked(() => { if (!position) { return; } - this.setPosition(position); + this.setPosition(position, arrowDimensions); }); }); } diff --git a/packages/primitives/popover/src/popover-content.directive.ts b/packages/primitives/popover/src/popover-content.directive.ts index 997a7fff..291e3031 100644 --- a/packages/primitives/popover/src/popover-content.directive.ts +++ b/packages/primitives/popover/src/popover-content.directive.ts @@ -13,11 +13,17 @@ import { untracked } from '@angular/core'; import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; +import { + getAllPossibleConnectedPositions, + getContentPosition, + RdxPositionAlign, + RdxPositionSide, + RdxPositionSideAndAlignOffsets +} from '@radix-ng/primitives/core'; import { filter, tap } from 'rxjs'; import { injectPopoverRoot } from './popover-root.inject'; import { DEFAULTS } from './popover.constants'; -import { RdxPopoverAlign, RdxPopoverAttachDetachEvent, RdxPopoverSide, RdxSideAndAlignOffsets } from './popover.types'; -import { getAllPossibleConnectedPositions, getContentPosition } from './popover.utils'; +import { RdxPopoverAttachDetachEvent } from './popover.types'; @Directive({ selector: '[rdxPopoverContent]', @@ -45,7 +51,7 @@ export class RdxPopoverContentDirective implements OnInit { * @description The preferred side of the trigger to render against when open. Will be reversed when collisions occur and avoidCollisions is enabled. * @default top */ - readonly side = input(RdxPopoverSide.Top); + readonly side = input(RdxPositionSide.Top); /** * @description The distance in pixels from the trigger. * @default undefined @@ -55,7 +61,7 @@ export class RdxPopoverContentDirective implements OnInit { * @description The preferred alignment against the trigger. May change when collisions occur. * @default center */ - readonly align = input(RdxPopoverAlign.Center); + readonly align = input(RdxPositionAlign.Center); /** * @description An offset in pixels from the "start" or "end" alignment options. * @default undefined @@ -274,7 +280,7 @@ export class RdxPopoverContentDirective implements OnInit { this.popoverRoot.popoverArrowDirective()?.width() ?? 0, this.popoverRoot.popoverArrowDirective()?.height() ?? 0 ); - const offsets: RdxSideAndAlignOffsets = { + const offsets: RdxPositionSideAndAlignOffsets = { sideOffset: this.sideOffset() ?? (greatestDimensionFromTheArrow || DEFAULTS.offsets.side), alignOffset: this.alignOffset() ?? DEFAULTS.offsets.align }; @@ -293,13 +299,13 @@ export class RdxPopoverContentDirective implements OnInit { allPossibleConnectedPositions.forEach((_, key) => { const sideAndAlignArray = key.split('|'); if ( - (sideAndAlignArray[0] as RdxPopoverSide) !== this.side() || - (sideAndAlignArray[1] as RdxPopoverAlign) !== this.align() + (sideAndAlignArray[0] as RdxPositionSide) !== this.side() || + (sideAndAlignArray[1] as RdxPositionAlign) !== this.align() ) { positions.push( getContentPosition({ - side: sideAndAlignArray[0] as RdxPopoverSide, - align: sideAndAlignArray[1] as RdxPopoverAlign, + side: sideAndAlignArray[0] as RdxPositionSide, + align: sideAndAlignArray[1] as RdxPositionAlign, sideOffset: offsets.sideOffset, alignOffset: offsets.alignOffset }) diff --git a/packages/primitives/popover/src/popover.constants.ts b/packages/primitives/popover/src/popover.constants.ts index 2fd22f3a..5c6b958c 100644 --- a/packages/primitives/popover/src/popover.constants.ts +++ b/packages/primitives/popover/src/popover.constants.ts @@ -1,88 +1,3 @@ -import { RdxPopoverAlign, RdxPopoverPositions, RdxPopoverSide } from './popover.types'; - -export const POPOVER_POSITIONS: RdxPopoverPositions = { - [RdxPopoverSide.Top]: { - [RdxPopoverAlign.Center]: { - originX: 'center', - originY: 'top', - overlayX: 'center', - overlayY: 'bottom' - }, - [RdxPopoverAlign.Start]: { - originX: 'start', - originY: 'top', - overlayX: 'start', - overlayY: 'bottom' - }, - [RdxPopoverAlign.End]: { - originX: 'end', - originY: 'top', - overlayX: 'end', - overlayY: 'bottom' - } - }, - [RdxPopoverSide.Right]: { - [RdxPopoverAlign.Center]: { - originX: 'end', - originY: 'center', - overlayX: 'start', - overlayY: 'center' - }, - [RdxPopoverAlign.Start]: { - originX: 'end', - originY: 'top', - overlayX: 'start', - overlayY: 'top' - }, - [RdxPopoverAlign.End]: { - originX: 'end', - originY: 'bottom', - overlayX: 'start', - overlayY: 'bottom' - } - }, - [RdxPopoverSide.Bottom]: { - [RdxPopoverAlign.Center]: { - originX: 'center', - originY: 'bottom', - overlayX: 'center', - overlayY: 'top' - }, - [RdxPopoverAlign.Start]: { - originX: 'start', - originY: 'bottom', - overlayX: 'start', - overlayY: 'top' - }, - [RdxPopoverAlign.End]: { - originX: 'end', - originY: 'bottom', - overlayX: 'end', - overlayY: 'top' - } - }, - [RdxPopoverSide.Left]: { - [RdxPopoverAlign.Center]: { - originX: 'start', - originY: 'center', - overlayX: 'end', - overlayY: 'center' - }, - [RdxPopoverAlign.Start]: { - originX: 'start', - originY: 'top', - overlayX: 'end', - overlayY: 'top' - }, - [RdxPopoverAlign.End]: { - originX: 'start', - originY: 'bottom', - overlayX: 'end', - overlayY: 'bottom' - } - } -} as const; - export const DEFAULTS = { offsets: { side: 10, diff --git a/packages/primitives/popover/src/popover.types.ts b/packages/primitives/popover/src/popover.types.ts index b33e02ad..ccf53f28 100644 --- a/packages/primitives/popover/src/popover.types.ts +++ b/packages/primitives/popover/src/popover.types.ts @@ -1,18 +1,3 @@ -import { ConnectionPositionPair } from '@angular/cdk/overlay'; - -export enum RdxPopoverSide { - Top = 'top', - Right = 'right', - Bottom = 'bottom', - Left = 'left' -} - -export enum RdxPopoverAlign { - Start = 'start', - Center = 'center', - End = 'end' -} - export enum RdxPopoverState { OPEN = 'open', CLOSED = 'closed' @@ -29,23 +14,3 @@ export enum RdxPopoverAnimationStatus { CLOSED_STARTED = 'closed_started', CLOSED_ENDED = 'closed_ended' } - -export type RdxSideAndAlign = { side: RdxPopoverSide; align: RdxPopoverAlign }; -export type RdxSideAndAlignOffsets = { sideOffset: number; alignOffset: number }; - -export type RdxPopoverPositions = { - [key in RdxPopoverSide]: { - [key in RdxPopoverAlign]: ConnectionPositionPair; - }; -}; - -export type RdxAllPossibleConnectedPositions = ReadonlyMap< - `${RdxPopoverSide}|${RdxPopoverAlign}`, - ConnectionPositionPair ->; - -export type RdxArrowPositionParams = { - top: string; - left: string; - transform: string; -}; diff --git a/packages/primitives/popover/src/utils/cdk-event.service.ts b/packages/primitives/popover/src/utils/cdk-event.service.ts index 184f114c..acf756b6 100644 --- a/packages/primitives/popover/src/utils/cdk-event.service.ts +++ b/packages/primitives/popover/src/utils/cdk-event.service.ts @@ -1,4 +1,3 @@ -import { DOCUMENT } from '@angular/common'; import { DestroyRef, EnvironmentProviders, @@ -12,6 +11,7 @@ import { Renderer2, VERSION } from '@angular/core'; +import { injectDocument, injectWindow } from '@radix-ng/primitives/core'; import { RdxCdkEventServiceWindowKey } from './constants'; import { EventType, EventTypeAsPrimitiveConfigKey, PrimitiveConfig, PrimitiveConfigs } from './types'; @@ -21,14 +21,15 @@ function eventTypeAsPrimitiveConfigKey(eventType: EventType): EventTypeAsPrimiti @Injectable() class RdxCdkEventService { - document = inject(DOCUMENT); + document = injectDocument(); destroyRef = inject(DestroyRef); ngZone = inject(NgZone); renderer2 = inject(Renderer2); + window = injectWindow(); primitiveConfigs?: PrimitiveConfigs; - onDestroyCallbacks: Set<() => void> = new Set([deleteRdxCdkEventServiceWindowKey]); + onDestroyCallbacks: Set<() => void> = new Set([() => deleteRdxCdkEventServiceWindowKey(this.window)]); #clickDomRootEventCallbacks: Set<(event: MouseEvent) => void> = new Set(); @@ -174,7 +175,7 @@ const RdxCdkEventServiceToken = new InjectionToken('RdxCdkEv const existsErrorMessage = 'RdxCdkEventService should be provided only once!'; -const deleteRdxCdkEventServiceWindowKey = () => { +const deleteRdxCdkEventServiceWindowKey = (window: Window & typeof globalThis) => { delete (window as any)[RdxCdkEventServiceWindowKey]; }; @@ -182,6 +183,7 @@ const getProvider: (throwWhenExists?: boolean) => Provider = (throwWhenExists = provide: RdxCdkEventServiceToken, useFactory: () => { isDevMode() && console.log('providing RdxCdkEventService...'); + const window = injectWindow(); if ((window as any)[RdxCdkEventServiceWindowKey]) { if (throwWhenExists) { throw Error(existsErrorMessage); diff --git a/packages/primitives/popover/stories/popover-animations.component.ts b/packages/primitives/popover/stories/popover-animations.component.ts index db96b0c9..81baa951 100644 --- a/packages/primitives/popover/stories/popover-animations.component.ts +++ b/packages/primitives/popover/stories/popover-animations.component.ts @@ -1,9 +1,9 @@ import { Component, signal, viewChild } from '@angular/core'; import { FormsModule } from '@angular/forms'; +import { RdxPositionAlign, RdxPositionSide } from '@radix-ng/primitives/core'; import { LucideAngularModule, MountainSnow, TriangleAlert, X } from 'lucide-angular'; -import { RdxPopoverAlign, RdxPopoverModule, RdxPopoverRootDirective } from '../index'; +import { RdxPopoverModule, RdxPopoverRootDirective } from '../index'; import { RdxPopoverContentAttributesComponent } from '../src/popover-content-attributes.component'; -import { RdxPopoverSide } from '../src/popover.types'; import { provideRdxCdkEventService } from '../src/utils/cdk-event.service'; import { containerAlert } from './utils/constants'; import { IgnoreClickOutsideContainerBase } from './utils/ignore-click-outside-container-base.class'; @@ -101,8 +101,8 @@ export class RdxPopoverAnimationsComponent extends IgnoreClickOutsideContainerBa readonly MountainSnowIcon = MountainSnow; readonly XIcon = X; - readonly sides = RdxPopoverSide; - readonly aligns = RdxPopoverAlign; + readonly sides = RdxPositionSide; + readonly aligns = RdxPositionAlign; cssAnimation = signal(true); cssOpeningAnimation = signal(true); diff --git a/packages/primitives/popover/stories/popover-events.components.ts b/packages/primitives/popover/stories/popover-events.components.ts index c9ef2b84..b4045458 100644 --- a/packages/primitives/popover/stories/popover-events.components.ts +++ b/packages/primitives/popover/stories/popover-events.components.ts @@ -1,7 +1,8 @@ import { Component, viewChild } from '@angular/core'; import { FormsModule } from '@angular/forms'; +import { RdxPositionAlign, RdxPositionSide } from '@radix-ng/primitives/core'; import { LucideAngularModule, MountainSnow, TriangleAlert, X } from 'lucide-angular'; -import { RdxPopoverAlign, RdxPopoverModule, RdxPopoverRootDirective, RdxPopoverSide } from '../index'; +import { RdxPopoverModule, RdxPopoverRootDirective } from '../index'; import { RdxPopoverContentAttributesComponent } from '../src/popover-content-attributes.component'; import { provideRdxCdkEventService } from '../src/utils/cdk-event.service'; import { containerAlert } from './utils/constants'; @@ -81,8 +82,8 @@ export class RdxPopoverEventsComponent extends IgnoreClickOutsideContainerBase { readonly MountainSnowIcon = MountainSnow; readonly XIcon = X; - protected readonly sides = RdxPopoverSide; - protected readonly aligns = RdxPopoverAlign; + protected readonly sides = RdxPositionSide; + protected readonly aligns = RdxPositionAlign; protected readonly containerAlert = containerAlert; protected readonly TriangleAlert = TriangleAlert; } diff --git a/packages/primitives/popover/stories/popover-multiple.component.ts b/packages/primitives/popover/stories/popover-multiple.component.ts index 9e0a6ed6..80624c0f 100644 --- a/packages/primitives/popover/stories/popover-multiple.component.ts +++ b/packages/primitives/popover/stories/popover-multiple.component.ts @@ -1,7 +1,8 @@ import { Component, viewChild } from '@angular/core'; import { FormsModule } from '@angular/forms'; +import { RdxPositionAlign, RdxPositionSide } from '@radix-ng/primitives/core'; import { LucideAngularModule, MountainSnow, TriangleAlert, X } from 'lucide-angular'; -import { RdxPopoverAlign, RdxPopoverModule, RdxPopoverSide } from '../index'; +import { RdxPopoverModule } from '../index'; import { RdxPopoverContentAttributesComponent } from '../src/popover-content-attributes.component'; import { provideRdxCdkEventService } from '../src/utils/cdk-event.service'; import { containerAlert } from './utils/constants'; @@ -187,8 +188,8 @@ export class RdxPopoverMultipleComponent extends IgnoreClickOutsideContainerBase readonly MountainSnowIcon = MountainSnow; readonly XIcon = X; - readonly RdxPopoverSide = RdxPopoverSide; - readonly RdxPopoverAlign = RdxPopoverAlign; + readonly RdxPopoverSide = RdxPositionSide; + readonly RdxPopoverAlign = RdxPositionAlign; protected readonly containerAlert = containerAlert; protected readonly TriangleAlert = TriangleAlert; } diff --git a/packages/primitives/popover/stories/popover-positioning.component.ts b/packages/primitives/popover/stories/popover-positioning.component.ts index 2ce31378..ac26d940 100644 --- a/packages/primitives/popover/stories/popover-positioning.component.ts +++ b/packages/primitives/popover/stories/popover-positioning.component.ts @@ -1,7 +1,8 @@ import { Component, signal, viewChild } from '@angular/core'; import { FormsModule } from '@angular/forms'; +import { RdxPositionAlign, RdxPositionSide } from '@radix-ng/primitives/core'; import { LucideAngularModule, MountainSnow, TriangleAlert, X } from 'lucide-angular'; -import { RdxPopoverAlign, RdxPopoverModule, RdxPopoverRootDirective, RdxPopoverSide } from '../index'; +import { RdxPopoverModule, RdxPopoverRootDirective } from '../index'; import { RdxPopoverContentAttributesComponent } from '../src/popover-content-attributes.component'; import { provideRdxCdkEventService } from '../src/utils/cdk-event.service'; import { containerAlert } from './utils/constants'; @@ -110,14 +111,14 @@ import { WithEventBaseComponent } from './utils/with-event-base.component'; export class RdxPopoverPositioningComponent extends IgnoreClickOutsideContainerBase { readonly popoverRootDirective = viewChild(RdxPopoverRootDirective); - readonly selectedSide = signal(RdxPopoverSide.Top); - readonly selectedAlign = signal(RdxPopoverAlign.Center); + readonly selectedSide = signal(RdxPositionSide.Top); + readonly selectedAlign = signal(RdxPositionAlign.Center); readonly sideOffset = signal(8); readonly alignOffset = signal(void 0); readonly disableAlternatePositions = signal(false); - readonly sides = RdxPopoverSide; - readonly aligns = RdxPopoverAlign; + readonly sides = RdxPositionSide; + readonly aligns = RdxPositionAlign; readonly MountainSnowIcon = MountainSnow; readonly XIcon = X; diff --git a/packages/primitives/popover/stories/popover.docs.mdx b/packages/primitives/popover/stories/popover.docs.mdx index ac224798..a9242cab 100644 --- a/packages/primitives/popover/stories/popover.docs.mdx +++ b/packages/primitives/popover/stories/popover.docs.mdx @@ -138,8 +138,8 @@ A component with the content attributes that are necessary to run animations. | Data attribute | Value | |----------------|----------------| | [data-state] | "closed" | "open" (enum RdxPopoverState) -| [data-side] | "left" | "right" | "bottom" | "top" (enum RdxPopoverSide) -| [data-align] | "start" | "end" | "center" (enum RdxPopoverAlign) +| [data-side] | "left" | "right" | "bottom" | "top" (enum RdxPositionSide) +| [data-align] | "start" | "end" | "center" (enum RdxPositionAlign) `} diff --git a/packages/primitives/popover/stories/utils/ignore-click-outside-container-base.class.ts b/packages/primitives/popover/stories/utils/ignore-click-outside-container-base.class.ts index 655f70cf..03da94a8 100644 --- a/packages/primitives/popover/stories/utils/ignore-click-outside-container-base.class.ts +++ b/packages/primitives/popover/stories/utils/ignore-click-outside-container-base.class.ts @@ -1,5 +1,5 @@ -import { DOCUMENT } from '@angular/common'; import { afterNextRender, DestroyRef, Directive, ElementRef, inject, signal, viewChildren } from '@angular/core'; +import { injectDocument } from '@radix-ng/primitives/core'; import { RdxPopoverRootDirective } from '../../src/popover-root.directive'; import { injectRdxCdkEventService } from '../../src/utils/cdk-event.service'; import { deregisterContainer, registerContainer, setRdxCdkEventService } from './containers.registry'; @@ -13,7 +13,7 @@ export abstract class IgnoreClickOutsideContainerBase implements IIgnoreClickOut readonly elementRef = inject>(ElementRef); readonly destroyRef = inject(DestroyRef); readonly rootDirectives = viewChildren(RdxPopoverRootDirective); - readonly document = inject(DOCUMENT); + readonly document = injectDocument(); readonly rdxCdkEventService = injectRdxCdkEventService(); protected constructor() { diff --git a/packages/primitives/tooltip/src/get-content-position.ts b/packages/primitives/tooltip/src/get-content-position.ts deleted file mode 100644 index 8f07951f..00000000 --- a/packages/primitives/tooltip/src/get-content-position.ts +++ /dev/null @@ -1,43 +0,0 @@ -import { ConnectedPosition } from '@angular/cdk/overlay'; -import { TOOLTIP_POSITIONS } from './tooltip.constants'; -import { RdxTooltipAlign, RdxTooltipSide } from './tooltip.types'; - -export function getContentPosition( - side: RdxTooltipSide, - align: RdxTooltipAlign, - sideOffset: number, - alignOffset: number -): ConnectedPosition { - const position = TOOLTIP_POSITIONS[side][align] ?? TOOLTIP_POSITIONS[RdxTooltipSide.Top][RdxTooltipAlign.Center]; - - if (sideOffset > 0) { - let xFactor = 0; - let yFactor = 0; - - switch (side) { - case RdxTooltipSide.Top: - yFactor = -1; - break; - case RdxTooltipSide.Bottom: - yFactor = 1; - break; - case RdxTooltipSide.Left: - xFactor = -1; - break; - case RdxTooltipSide.Right: - xFactor = 1; - break; - } - - position.offsetX = xFactor * sideOffset; - position.offsetY = yFactor * sideOffset; - } - - if ([RdxTooltipAlign.Start, RdxTooltipAlign.End].includes(align) && alignOffset) { - const alignOffsetFactor = align === RdxTooltipAlign.End ? -1 : 1; - - position.offsetX = alignOffsetFactor * alignOffset; - } - - return position; -} diff --git a/packages/primitives/tooltip/src/tooltip-arrow.directive.ts b/packages/primitives/tooltip/src/tooltip-arrow.directive.ts index fe5f38b1..7dd3dd73 100644 --- a/packages/primitives/tooltip/src/tooltip-arrow.directive.ts +++ b/packages/primitives/tooltip/src/tooltip-arrow.directive.ts @@ -1,7 +1,21 @@ -import { computed, Directive, effect, ElementRef, forwardRef, inject, input, Renderer2 } from '@angular/core'; +import { ConnectionPositionPair } from '@angular/cdk/overlay'; +import { + afterNextRender, + computed, + Directive, + effect, + ElementRef, + forwardRef, + inject, + input, + Renderer2, + signal, + untracked +} from '@angular/core'; +import { getArrowPositionParams, getSideAndAlignFromAllPossibleConnectedPositions } from '@radix-ng/primitives/core'; import { RdxTooltipArrowToken } from './tooltip-arrow.token'; import { RdxTooltipContentToken } from './tooltip-content.token'; -import { RdxTooltipSide } from './tooltip.types'; +import { injectTooltipRoot } from './tooltip-root.directive'; @Directive({ selector: '[rdxTooltipArrow]', @@ -14,6 +28,8 @@ import { RdxTooltipSide } from './tooltip.types'; ] }) export class RdxTooltipArrowDirective { + /** @ignore */ + readonly tooltipRoot = injectTooltipRoot(); /** @ignore */ private readonly renderer = inject(Renderer2); /** @ignore */ @@ -31,6 +47,14 @@ export class RdxTooltipArrowDirective { */ readonly height = input(5); + /** + * @ignore + * */ + private triggerRect: DOMRect; + + /** @ignore */ + private readonly currentArrowSvgElement = signal(void 0); + /** @ignore */ readonly arrowSvgElement = computed(() => { const width = this.width(); @@ -48,46 +72,64 @@ export class RdxTooltipArrowDirective { return svgElement; }); - /** @ignore */ - private readonly onArrowSvgElementChangeEffect = effect(() => { - const arrowElement = this.arrowSvgElement(); - - this.renderer.appendChild(this.elementRef.nativeElement, arrowElement); - }); + constructor() { + afterNextRender({ + write: () => { + if (this.elementRef.nativeElement.parentElement) { + this.renderer.setStyle(this.elementRef.nativeElement.parentElement, 'position', 'relative'); + } + this.renderer.setStyle(this.elementRef.nativeElement, 'position', 'absolute'); + this.renderer.setStyle(this.elementRef.nativeElement, 'boxSizing', ''); + this.renderer.setStyle(this.elementRef.nativeElement, 'fontSize', '0px'); + } + }); + } /** @ignore */ - private readonly onSideChangeEffect = effect(() => { - const side = this.contentDirective.side(); + private setTriggerRect() { + this.triggerRect = this.tooltipRoot.tooltipTriggerDirective().elementRef.nativeElement.getBoundingClientRect(); + } - this.elementRef.nativeElement.parentElement?.setAttribute('style', `position: relative;`); - this.elementRef.nativeElement.style.position = 'absolute'; - this.elementRef.nativeElement.style.boxSizing = ''; - this.elementRef.nativeElement.style.width = `${this.width()}px`; - this.elementRef.nativeElement.style.height = `${this.height()}px`; - this.elementRef.nativeElement.style.fontSize = '0px'; - - if ([RdxTooltipSide.Top, RdxTooltipSide.Bottom].includes(side)) { - this.elementRef.nativeElement.style.left = `calc(50% - ${this.width() / 2}px)`; - this.elementRef.nativeElement.style.top = '100%'; - - if (side === RdxTooltipSide.Bottom) { - this.elementRef.nativeElement.style.transform = 'rotate(180deg)'; - this.elementRef.nativeElement.style.top = `-${this.height()}px`; - } - } + /** @ignore */ + private setPosition(position: ConnectionPositionPair, arrowDimensions: { width: number; height: number }) { + this.setTriggerRect(); + const posParams = getArrowPositionParams( + getSideAndAlignFromAllPossibleConnectedPositions(position), + { width: arrowDimensions.width, height: arrowDimensions.height }, + { width: this.triggerRect.width, height: this.triggerRect.height } + ); - if ([RdxTooltipSide.Left, RdxTooltipSide.Right].includes(side)) { - this.elementRef.nativeElement.style.top = `calc(50% - ${this.height() / 2}px)`; + this.renderer.setStyle(this.elementRef.nativeElement, 'top', posParams.top); + this.renderer.setStyle(this.elementRef.nativeElement, 'bottom', ''); + this.renderer.setStyle(this.elementRef.nativeElement, 'left', posParams.left); + this.renderer.setStyle(this.elementRef.nativeElement, 'right', ''); + this.renderer.setStyle(this.elementRef.nativeElement, 'transform', posParams.transform); + } - if (side === RdxTooltipSide.Left) { - this.elementRef.nativeElement.style.left = `100%`; - this.elementRef.nativeElement.style.transform = 'rotate(-90deg) translate(0, -50%)'; + /** @ignore */ + private readonly onArrowSvgElementChangeEffect = effect(() => { + const arrowElement = this.arrowSvgElement(); + untracked(() => { + const currentArrowSvgElement = this.currentArrowSvgElement(); + if (currentArrowSvgElement) { + this.renderer.removeChild(this.elementRef.nativeElement, currentArrowSvgElement); } + this.currentArrowSvgElement.set(arrowElement); + this.renderer.setStyle(this.elementRef.nativeElement, 'width', `${this.width()}px`); + this.renderer.setStyle(this.elementRef.nativeElement, 'height', `${this.height()}px`); + this.renderer.appendChild(this.elementRef.nativeElement, this.currentArrowSvgElement()); + }); + }); - if (side === RdxTooltipSide.Right) { - this.elementRef.nativeElement.style.right = `100%`; - this.elementRef.nativeElement.style.transform = 'rotate(90deg) translate(0, -50%)'; + /** @ignore */ + private readonly onContentPositionAndArrowDimensionsChangeEffect = effect(() => { + const position = this.contentDirective.position(); + const arrowDimensions = { width: this.width(), height: this.height() }; + untracked(() => { + if (!position) { + return; } - } + this.setPosition(position, arrowDimensions); + }); }); } diff --git a/packages/primitives/tooltip/src/tooltip-content.directive.ts b/packages/primitives/tooltip/src/tooltip-content.directive.ts index 0cca867c..90c5fe44 100644 --- a/packages/primitives/tooltip/src/tooltip-content.directive.ts +++ b/packages/primitives/tooltip/src/tooltip-content.directive.ts @@ -1,8 +1,6 @@ -import { ConnectedPosition } from '@angular/cdk/overlay'; import { computed, Directive, forwardRef, inject, input, output, TemplateRef } from '@angular/core'; -import { getContentPosition } from './get-content-position'; +import { getContentPosition, RdxPositionAlign, RdxPositionSide } from '@radix-ng/primitives/core'; import { RdxTooltipContentToken } from './tooltip-content.token'; -import { RdxTooltipAlign, RdxTooltipSide } from './tooltip.types'; @Directive({ selector: '[rdxTooltipContent]', @@ -16,7 +14,7 @@ export class RdxTooltipContentDirective { /** * The preferred side of the trigger to render against when open. Will be reversed when collisions occur and avoidCollisions is enabled. */ - readonly side = input(RdxTooltipSide.Top); + readonly side = input(RdxPositionSide.Top); /** * The distance in pixels from the trigger. @@ -26,7 +24,7 @@ export class RdxTooltipContentDirective { /** * The preferred alignment against the trigger. May change when collisions occur. */ - readonly align = input(RdxTooltipAlign.Center); + readonly align = input(RdxPositionAlign.Center); /** * An offset in pixels from the "start" or "end" alignment options. @@ -34,8 +32,13 @@ export class RdxTooltipContentDirective { readonly alignOffset = input(0); /** @ingore */ - readonly position = computed(() => - getContentPosition(this.side(), this.align(), this.sideOffset(), this.alignOffset()) + readonly position = computed(() => + getContentPosition({ + side: this.side(), + align: this.align(), + sideOffset: this.sideOffset(), + alignOffset: this.alignOffset() + }) ); /** diff --git a/packages/primitives/tooltip/src/tooltip-root.directive.ts b/packages/primitives/tooltip/src/tooltip-root.directive.ts index 9076a5b3..239572c8 100644 --- a/packages/primitives/tooltip/src/tooltip-root.directive.ts +++ b/packages/primitives/tooltip/src/tooltip-root.directive.ts @@ -1,13 +1,12 @@ import { ConnectedPosition, Overlay, OverlayRef, PositionStrategy } from '@angular/cdk/overlay'; import { TemplatePortal } from '@angular/cdk/portal'; -import { DOCUMENT, isPlatformBrowser } from '@angular/common'; +import { isPlatformBrowser } from '@angular/common'; import { computed, contentChild, DestroyRef, Directive, effect, - ElementRef, forwardRef, inject, InjectionToken, @@ -21,6 +20,7 @@ import { ViewRef } from '@angular/core'; import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; +import { injectDocument, injectWindow } from '@radix-ng/primitives/core'; import { asyncScheduler, filter, take } from 'rxjs'; import { RdxTooltipContentToken } from './tooltip-content.token'; import { RdxTooltipTriggerDirective } from './tooltip-trigger.directive'; @@ -54,7 +54,9 @@ export class RdxTooltipRootDirective implements OnInit { /** @ignore */ private readonly platformId = inject(PLATFORM_ID); /** @ignore */ - private readonly document = inject(DOCUMENT); + private readonly document = injectDocument(); + /** @ignore */ + private readonly window = injectWindow(); /** @ignore */ readonly tooltipConfig = injectTooltipConfig(); @@ -101,7 +103,7 @@ export class RdxTooltipRootDirective implements OnInit { /** @ignore */ readonly tooltipContentDirective = contentChild.required(RdxTooltipContentToken); /** @ignore */ - readonly tooltipTriggerElementRef = contentChild.required(RdxTooltipTriggerDirective, { read: ElementRef }); + readonly tooltipTriggerDirective = contentChild.required(RdxTooltipTriggerDirective); /** @ignore */ private openTimer = 0; @@ -155,7 +157,7 @@ export class RdxTooltipRootDirective implements OnInit { this.clearTimeout(this.skipDelayTimer); if (isPlatformBrowser(this.platformId)) { - this.skipDelayTimer = window.setTimeout(() => { + this.skipDelayTimer = this.window.setTimeout(() => { this.isOpenDelayed.set(true); }, this.tooltipConfig.skipDelayDuration); } @@ -219,7 +221,7 @@ export class RdxTooltipRootDirective implements OnInit { this.clearTimeout(this.openTimer); if (isPlatformBrowser(this.platformId)) { - this.openTimer = window.setTimeout(() => { + this.openTimer = this.window.setTimeout(() => { this.wasOpenDelayed.set(true); this.setOpen(true); }, this.delayDuration()); @@ -301,7 +303,7 @@ export class RdxTooltipRootDirective implements OnInit { private getPositionStrategy(connectedPosition: ConnectedPosition): PositionStrategy { return this.overlay .position() - .flexibleConnectedTo(this.tooltipTriggerElementRef()) + .flexibleConnectedTo(this.tooltipTriggerDirective().elementRef) .withFlexibleDimensions(false) .withPositions([ connectedPosition @@ -312,7 +314,7 @@ export class RdxTooltipRootDirective implements OnInit { /** @ignore */ private clearTimeout(timeoutId: number): void { if (isPlatformBrowser(this.platformId)) { - window.clearTimeout(timeoutId); + this.window.clearTimeout(timeoutId); } } diff --git a/packages/primitives/tooltip/src/tooltip.types.ts b/packages/primitives/tooltip/src/tooltip.types.ts index bead9992..3cf0af43 100644 --- a/packages/primitives/tooltip/src/tooltip.types.ts +++ b/packages/primitives/tooltip/src/tooltip.types.ts @@ -1,16 +1,3 @@ -export enum RdxTooltipSide { - Top = 'top', - Right = 'right', - Bottom = 'bottom', - Left = 'left' -} - -export enum RdxTooltipAlign { - Start = 'start', - Center = 'center', - End = 'end' -} - export type RdxTooltipConfig = { delayDuration: number; skipDelayDuration: number; diff --git a/packages/primitives/tooltip/stories/tooltip-positioning.component.ts b/packages/primitives/tooltip/stories/tooltip-positioning.component.ts index 475e67e2..5bc717b5 100644 --- a/packages/primitives/tooltip/stories/tooltip-positioning.component.ts +++ b/packages/primitives/tooltip/stories/tooltip-positioning.component.ts @@ -1,8 +1,8 @@ import { Component } from '@angular/core'; import { FormsModule } from '@angular/forms'; +import { RdxPositionAlign, RdxPositionSide } from '@radix-ng/primitives/core'; import { LucideAngularModule, Plus } from 'lucide-angular'; import { RdxTooltipModule } from '../index'; -import { RdxTooltipAlign, RdxTooltipSide } from '../src/tooltip.types'; @Component({ selector: 'rdx-tooltip-positioning', @@ -168,10 +168,10 @@ import { RdxTooltipAlign, RdxTooltipSide } from '../src/tooltip.types'; export class RdxTooltipPositioningComponent { readonly PlusIcon = Plus; - selectedSide = RdxTooltipSide.Top; - selectedAlign = RdxTooltipAlign.Center; + selectedSide = RdxPositionSide.Top; + selectedAlign = RdxPositionAlign.Center; sideOffset = 8; - readonly sides = RdxTooltipSide; - readonly aligns = RdxTooltipAlign; + readonly sides = RdxPositionSide; + readonly aligns = RdxPositionAlign; } diff --git a/packages/primitives/tooltip/stories/tooltip.docs.mdx b/packages/primitives/tooltip/stories/tooltip.docs.mdx index 066541aa..4e69a81d 100644 --- a/packages/primitives/tooltip/stories/tooltip.docs.mdx +++ b/packages/primitives/tooltip/stories/tooltip.docs.mdx @@ -106,8 +106,8 @@ The component that pops out when the tooltip is open. | Data attribute | Value | ----- | ----- | [data-state] | "closed" | "delayed-open" | "instant-open" (enum RdxTooltipState) - | [data-side] | "left" | "right" | "bottom" | "top" (enum RdxTooltipSide) - | [data-align] | "start" | "end" | "center" (enum RdxTooltipAlign) + | [data-side] | "left" | "right" | "bottom" | "top" (enum RdxPositionSide) + | [data-align] | "start" | "end" | "center" (enum RdxPositionAlign) `} @@ -120,7 +120,7 @@ Directive state attributes | Data attribute | Value | ----- | ----- | [data-state] | "closed" | "delayed-open" | "instant-open" (enum RdxTooltipState) - | [data-side] | "left" | "right" | "bottom" | "top" (enum RdxTooltipSide) + | [data-side] | "left" | "right" | "bottom" | "top" (enum RdxPositionSide) `} diff --git a/packages/primitives/tooltip/stories/tooltip.stories.ts b/packages/primitives/tooltip/stories/tooltip.stories.ts index 98e6dc99..a71d8487 100644 --- a/packages/primitives/tooltip/stories/tooltip.stories.ts +++ b/packages/primitives/tooltip/stories/tooltip.stories.ts @@ -178,7 +178,7 @@ export const Multiple: Story = { - +
Add to library