@ng-cn/core 1.0.17 → 1.0.18
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.
- package/package.json +6 -5
- package/src/app/lib/components/ui/alert-dialog/alert-dialog-content.component.ts +21 -20
- package/src/app/lib/components/ui/avatar/ui-avatar.component.ts +4 -0
- package/src/app/lib/components/ui/calendar/calendar.component.ts +5 -1
- package/src/app/lib/components/ui/carousel/carousel-content.component.ts +1 -0
- package/src/app/lib/components/ui/carousel/carousel-item.component.ts +1 -0
- package/src/app/lib/components/ui/carousel/carousel-next.component.ts +1 -0
- package/src/app/lib/components/ui/carousel/carousel-previous.component.ts +1 -0
- package/src/app/lib/components/ui/carousel/carousel.component.ts +1 -0
- package/src/app/lib/components/ui/chart/chart-container.component.ts +1 -0
- package/src/app/lib/components/ui/chart/chart-legend-content.component.ts +1 -0
- package/src/app/lib/components/ui/chart/chart-legend.component.ts +5 -5
- package/src/app/lib/components/ui/chart/chart-tooltip-content.component.ts +5 -5
- package/src/app/lib/components/ui/chart/chart-tooltip.component.ts +5 -5
- package/src/app/lib/components/ui/chart/chart.component.ts +1 -0
- package/src/app/lib/components/ui/checkbox/checkbox.component.ts +1 -1
- package/src/app/lib/components/ui/collapsible/collapsible-content.component.ts +2 -1
- package/src/app/lib/components/ui/collapsible/collapsible-context.ts +1 -0
- package/src/app/lib/components/ui/collapsible/collapsible-trigger.component.ts +1 -0
- package/src/app/lib/components/ui/collapsible/collapsible.component.ts +3 -0
- package/src/app/lib/components/ui/context-menu/context-menu-content.component.ts +48 -17
- package/src/app/lib/components/ui/context-menu/context-menu-sub-content.component.ts +2 -0
- package/src/app/lib/components/ui/context-menu/context-menu-sub-trigger.component.ts +30 -1
- package/src/app/lib/components/ui/context-menu/context-menu-sub.component.ts +3 -0
- package/src/app/lib/components/ui/date-picker/date-picker.component.ts +1 -0
- package/src/app/lib/components/ui/dialog/dialog-content.component.ts +26 -19
- package/src/app/lib/components/ui/direction/direction-context.ts +9 -0
- package/src/app/lib/components/ui/direction/direction.component.ts +48 -0
- package/src/app/lib/components/ui/direction/index.ts +2 -0
- package/src/app/lib/components/ui/drawer/drawer-content.component.ts +44 -0
- package/src/app/lib/components/ui/dropdown-menu/dropdown-menu-content.component.ts +2 -2
- package/src/app/lib/components/ui/dropdown-menu/dropdown-menu-radio-item.component.ts +1 -0
- package/src/app/lib/components/ui/dropdown-menu/dropdown-menu-sub-content.component.ts +2 -0
- package/src/app/lib/components/ui/dropdown-menu/dropdown-menu-sub-trigger.component.ts +28 -2
- package/src/app/lib/components/ui/dropdown-menu/dropdown-menu-sub.component.ts +3 -0
- package/src/app/lib/components/ui/dropdown-menu/dropdown-menu-trigger.component.ts +25 -0
- package/src/app/lib/components/ui/empty/empty-action.component.ts +1 -0
- package/src/app/lib/components/ui/empty/empty-description.component.ts +1 -0
- package/src/app/lib/components/ui/empty/empty-icon.component.ts +2 -1
- package/src/app/lib/components/ui/empty/empty-title.component.ts +1 -0
- package/src/app/lib/components/ui/empty/empty.component.ts +1 -0
- package/src/app/lib/components/ui/form/form-description.component.ts +2 -2
- package/src/app/lib/components/ui/hover-card/hover-card-content.component.ts +108 -60
- package/src/app/lib/components/ui/hover-card/hover-card-context.ts +4 -2
- package/src/app/lib/components/ui/hover-card/hover-card-trigger.component.ts +5 -3
- package/src/app/lib/components/ui/hover-card/hover-card.component.ts +8 -3
- package/src/app/lib/components/ui/input-group/input-group-addon.component.ts +1 -0
- package/src/app/lib/components/ui/input-group/input-group-input.component.ts +1 -0
- package/src/app/lib/components/ui/input-group/input-group.component.ts +1 -0
- package/src/app/lib/components/ui/menubar/menubar-content.component.ts +1 -1
- package/src/app/lib/components/ui/navigation-menu/navigation-menu-content.component.ts +7 -1
- package/src/app/lib/components/ui/navigation-menu/navigation-menu-context.ts +14 -0
- package/src/app/lib/components/ui/navigation-menu/navigation-menu-item.component.ts +9 -4
- package/src/app/lib/components/ui/navigation-menu/navigation-menu-trigger.component.ts +69 -2
- package/src/app/lib/components/ui/navigation-menu/navigation-menu.component.ts +32 -4
- package/src/app/lib/components/ui/pagination/pagination.component.ts +3 -1
- package/src/app/lib/components/ui/popover/popover-content.component.ts +11 -0
- package/src/app/lib/components/ui/popover/popover-context.ts +2 -0
- package/src/app/lib/components/ui/popover/popover.component.ts +4 -0
- package/src/app/lib/components/ui/progress/progress.component.ts +1 -2
- package/src/app/lib/components/ui/scroll-area/scroll-area.component.ts +7 -6
- package/src/app/lib/components/ui/segmented/segmented-item.component.ts +1 -0
- package/src/app/lib/components/ui/segmented/segmented.component.ts +1 -0
- package/src/app/lib/components/ui/select/select-content.component.ts +35 -15
- package/src/app/lib/components/ui/select/select-context.ts +10 -0
- package/src/app/lib/components/ui/select/select-item.component.ts +25 -7
- package/src/app/lib/components/ui/select/select-trigger.component.ts +6 -13
- package/src/app/lib/components/ui/select/select.component.ts +46 -0
- package/src/app/lib/components/ui/sheet/sheet-content.component.ts +22 -5
- package/src/app/lib/components/ui/slider/slider.component.ts +2 -2
- package/src/app/lib/components/ui/sonner/index.ts +2 -0
- package/src/app/lib/components/ui/sonner/sonner.component.ts +70 -0
- package/src/app/lib/components/ui/switch/switch.component.ts +1 -14
- package/src/app/lib/components/ui/tabs/tabs-list.component.ts +18 -0
- package/src/app/lib/components/ui/tabs/tabs-trigger.component.ts +0 -1
- package/src/app/lib/components/ui/toggle/toggle.component.ts +12 -6
- package/src/app/lib/components/ui/tooltip/tooltip-content.component.ts +141 -17
- package/src/app/lib/components/ui/tooltip/tooltip-context.ts +3 -1
- package/src/app/lib/components/ui/tooltip/tooltip-provider.component.ts +1 -1
- package/src/app/lib/components/ui/tooltip/tooltip-trigger.component.ts +5 -2
- package/src/app/lib/components/ui/tooltip/tooltip.component.ts +3 -1
|
@@ -1,5 +1,16 @@
|
|
|
1
|
-
import { cn, Presence } from '@/lib/utils';
|
|
2
|
-
import {
|
|
1
|
+
import { Align, cn, computePosition, getTransformOrigin, Presence, Side } from '@/lib/utils';
|
|
2
|
+
import {
|
|
3
|
+
afterNextRender,
|
|
4
|
+
ChangeDetectionStrategy,
|
|
5
|
+
Component,
|
|
6
|
+
computed,
|
|
7
|
+
effect,
|
|
8
|
+
ElementRef,
|
|
9
|
+
inject,
|
|
10
|
+
Injector,
|
|
11
|
+
input,
|
|
12
|
+
signal,
|
|
13
|
+
} from '@angular/core';
|
|
3
14
|
import { TOOLTIP_CONTEXT, TooltipAlign, TooltipSide } from './tooltip-context';
|
|
4
15
|
|
|
5
16
|
export type TooltipContentState = 'open' | 'closed';
|
|
@@ -34,7 +45,8 @@ export interface TooltipContentProps {
|
|
|
34
45
|
*
|
|
35
46
|
* @description
|
|
36
47
|
* TooltipContent displays the actual tooltip content. It supports positioning
|
|
37
|
-
* on different sides and alignments relative to the trigger
|
|
48
|
+
* on different sides and alignments relative to the trigger using fixed positioning
|
|
49
|
+
* via computePosition to avoid clipping inside overflow:hidden ancestors.
|
|
38
50
|
*
|
|
39
51
|
* ## Features
|
|
40
52
|
* - Configurable side (top, right, bottom, left)
|
|
@@ -42,10 +54,14 @@ export interface TooltipContentProps {
|
|
|
42
54
|
* - Customizable offset from trigger
|
|
43
55
|
* - Smooth enter/exit animations
|
|
44
56
|
* - Uses Presence for proper exit animations
|
|
57
|
+
* - Fixed positioning — not clipped by overflow:hidden ancestors
|
|
58
|
+
* - Escape key to dismiss
|
|
59
|
+
* - disableHoverableContent support
|
|
45
60
|
*
|
|
46
61
|
* ## Accessibility
|
|
47
62
|
* - `role="tooltip"` on the element
|
|
48
63
|
* - Unique ID for aria-describedby relationship with trigger
|
|
64
|
+
* - Escape key dismisses tooltip (WCAG 1.4.13)
|
|
49
65
|
*
|
|
50
66
|
* @example Basic usage
|
|
51
67
|
* ```html
|
|
@@ -81,10 +97,12 @@ export interface TooltipContentProps {
|
|
|
81
97
|
<div
|
|
82
98
|
[class]="computedClass()"
|
|
83
99
|
[attr.data-state]="state()"
|
|
84
|
-
[attr.data-side]="
|
|
85
|
-
[attr.data-align]="
|
|
100
|
+
[attr.data-side]="computedSide()"
|
|
101
|
+
[attr.data-align]="computedAlign()"
|
|
102
|
+
[style]="positionStyles()"
|
|
86
103
|
role="tooltip"
|
|
87
104
|
[id]="context.tooltipId"
|
|
105
|
+
(mouseenter)="onMouseEnter()"
|
|
88
106
|
>
|
|
89
107
|
<ng-content />
|
|
90
108
|
</div>
|
|
@@ -93,10 +111,35 @@ export interface TooltipContentProps {
|
|
|
93
111
|
host: {
|
|
94
112
|
'attr.data-slot': '"tooltip-content"',
|
|
95
113
|
class: 'contents',
|
|
114
|
+
'(document:keydown.escape)': 'onEscape()',
|
|
96
115
|
},
|
|
97
116
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
98
117
|
})
|
|
99
118
|
export class TooltipContent {
|
|
119
|
+
constructor() {
|
|
120
|
+
// Recalculate position when open state changes (browser-only via afterNextRender)
|
|
121
|
+
effect(() => {
|
|
122
|
+
const isOpen = this.context.open();
|
|
123
|
+
if (isOpen) {
|
|
124
|
+
this.isPositioned.set(false);
|
|
125
|
+
afterNextRender(
|
|
126
|
+
() => {
|
|
127
|
+
this.schedulePositionUpdate();
|
|
128
|
+
},
|
|
129
|
+
{ injector: this._injector },
|
|
130
|
+
);
|
|
131
|
+
} else {
|
|
132
|
+
this.cancelScheduledPositionUpdate();
|
|
133
|
+
this.isPositioned.set(false);
|
|
134
|
+
this.positionStyles.set({
|
|
135
|
+
position: 'fixed',
|
|
136
|
+
top: '-9999px',
|
|
137
|
+
left: '-9999px',
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
|
|
100
143
|
/** The preferred side of the trigger to render against */
|
|
101
144
|
readonly side = input<TooltipSide>('top');
|
|
102
145
|
/** The distance in pixels from the trigger */
|
|
@@ -108,29 +151,110 @@ export class TooltipContent {
|
|
|
108
151
|
/** Additional CSS classes */
|
|
109
152
|
readonly class = input<string>('');
|
|
110
153
|
|
|
154
|
+
private readonly _elementRef = inject(ElementRef);
|
|
155
|
+
private readonly _injector = inject(Injector);
|
|
156
|
+
|
|
111
157
|
protected readonly context = inject(TOOLTIP_CONTEXT);
|
|
112
158
|
|
|
159
|
+
protected readonly isPositioned = signal(false);
|
|
160
|
+
/** Computed position after collision detection */
|
|
161
|
+
protected readonly computedSide = signal<Side>('top');
|
|
162
|
+
protected readonly computedAlign = signal<Align>('center');
|
|
163
|
+
protected readonly positionStyles = signal<Record<string, string>>({
|
|
164
|
+
position: 'fixed',
|
|
165
|
+
top: '-9999px',
|
|
166
|
+
left: '-9999px',
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
private positionFrameId: number | null = null;
|
|
170
|
+
|
|
113
171
|
/** Current state: open or closed */
|
|
114
172
|
protected readonly state = computed<TooltipContentState>(() =>
|
|
115
173
|
this.context.open() ? 'open' : 'closed',
|
|
116
174
|
);
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
left: 'right-full top-1/2 -translate-y-1/2 mr-2',
|
|
122
|
-
right: 'left-full top-1/2 -translate-y-1/2 ml-2',
|
|
123
|
-
};
|
|
124
|
-
|
|
125
|
-
return cn(
|
|
126
|
-
'absolute z-50 overflow-hidden rounded-md border bg-popover px-3 py-1.5 text-sm text-popover-foreground shadow-md',
|
|
175
|
+
|
|
176
|
+
protected readonly computedClass = computed(() =>
|
|
177
|
+
cn(
|
|
178
|
+
'z-50 overflow-hidden rounded-md border bg-popover px-3 py-1.5 text-sm text-popover-foreground shadow-md',
|
|
127
179
|
'data-[state=open]:animate-in data-[state=closed]:animate-out',
|
|
128
180
|
'data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0',
|
|
129
181
|
'data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95',
|
|
130
182
|
'data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2',
|
|
131
183
|
'data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2',
|
|
132
|
-
|
|
184
|
+
!this.isPositioned() && 'opacity-0 pointer-events-none',
|
|
133
185
|
this.class(),
|
|
186
|
+
),
|
|
187
|
+
);
|
|
188
|
+
|
|
189
|
+
onMouseEnter(): void {
|
|
190
|
+
if (this.context.disableHoverableContent?.()) {
|
|
191
|
+
this.context.setOpen(false);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
protected onEscape(): void {
|
|
196
|
+
if (this.context.open()) {
|
|
197
|
+
this.context.setOpen(false);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
private schedulePositionUpdate(): void {
|
|
202
|
+
this.cancelScheduledPositionUpdate();
|
|
203
|
+
|
|
204
|
+
this.positionFrameId = requestAnimationFrame(() => {
|
|
205
|
+
this.updatePosition();
|
|
206
|
+
|
|
207
|
+
this.positionFrameId = requestAnimationFrame(() => {
|
|
208
|
+
this.updatePosition();
|
|
209
|
+
this.positionFrameId = null;
|
|
210
|
+
});
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
private cancelScheduledPositionUpdate(): void {
|
|
215
|
+
if (this.positionFrameId !== null) {
|
|
216
|
+
cancelAnimationFrame(this.positionFrameId);
|
|
217
|
+
this.positionFrameId = null;
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
private updatePosition(): void {
|
|
222
|
+
const triggerElement = this.context.triggerRef?.();
|
|
223
|
+
const contentElement = this._elementRef.nativeElement.querySelector(
|
|
224
|
+
'[role="tooltip"]',
|
|
225
|
+
) as HTMLElement;
|
|
226
|
+
|
|
227
|
+
if (!triggerElement || !contentElement) return;
|
|
228
|
+
|
|
229
|
+
const triggerRect = triggerElement.getBoundingClientRect();
|
|
230
|
+
const contentRect = contentElement.getBoundingClientRect();
|
|
231
|
+
const overlayWidth = Math.round(contentRect.width || 100);
|
|
232
|
+
const overlayHeight = Math.round(contentRect.height || 32);
|
|
233
|
+
|
|
234
|
+
const result = computePosition(
|
|
235
|
+
triggerRect,
|
|
236
|
+
{ width: overlayWidth, height: overlayHeight },
|
|
237
|
+
{
|
|
238
|
+
side: this.side() as Side,
|
|
239
|
+
align: this.align() as Align,
|
|
240
|
+
sideOffset: this.sideOffset(),
|
|
241
|
+
alignOffset: this.alignOffset(),
|
|
242
|
+
avoidCollisions: true,
|
|
243
|
+
collisionPadding: 8,
|
|
244
|
+
},
|
|
134
245
|
);
|
|
135
|
-
|
|
246
|
+
|
|
247
|
+
this.computedSide.set(result.side);
|
|
248
|
+
this.computedAlign.set(result.align);
|
|
249
|
+
|
|
250
|
+
// Set position styles with transform origin for animations
|
|
251
|
+
const transformOrigin = getTransformOrigin(result.side, result.align);
|
|
252
|
+
this.positionStyles.set({
|
|
253
|
+
position: 'fixed',
|
|
254
|
+
top: result.styles.top || '',
|
|
255
|
+
left: result.styles.left || '',
|
|
256
|
+
'--radix-tooltip-content-transform-origin': transformOrigin,
|
|
257
|
+
});
|
|
258
|
+
this.isPositioned.set(true);
|
|
259
|
+
}
|
|
136
260
|
}
|
|
@@ -9,13 +9,15 @@ export interface TooltipContextValue {
|
|
|
9
9
|
/** Set open state */
|
|
10
10
|
setOpen: (open: boolean) => void;
|
|
11
11
|
/** Delay before showing tooltip (ms) */
|
|
12
|
-
delayDuration: number;
|
|
12
|
+
delayDuration: () => number;
|
|
13
13
|
/** Skip delay duration when quickly hovering between tooltips (ms) */
|
|
14
14
|
skipDelayDuration: number;
|
|
15
15
|
/** Unique ID for aria-describedby relationship */
|
|
16
16
|
tooltipId: string;
|
|
17
17
|
/** Whether hoverable content is disabled */
|
|
18
18
|
disableHoverableContent: () => boolean;
|
|
19
|
+
/** Reference to the trigger element for fixed positioning */
|
|
20
|
+
triggerRef?: WritableSignal<HTMLElement | null>;
|
|
19
21
|
}
|
|
20
22
|
|
|
21
23
|
export const TOOLTIP_CONTEXT = new InjectionToken<TooltipContextValue>('TOOLTIP_CONTEXT');
|
|
@@ -73,7 +73,7 @@ export class TooltipProvider implements TooltipContextValue {
|
|
|
73
73
|
/** Unique ID for aria-describedby relationship */
|
|
74
74
|
readonly tooltipId = `tooltip-provider-${++tooltipIdCounter}`;
|
|
75
75
|
/** The duration from when the pointer enters the trigger until the tooltip opens */
|
|
76
|
-
readonly delayDuration = 700;
|
|
76
|
+
readonly delayDuration = input<number>(700);
|
|
77
77
|
/** How much time a user has to enter another trigger without incurring a delay again */
|
|
78
78
|
readonly skipDelayDuration = 300;
|
|
79
79
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ChangeDetectionStrategy, Component, inject, input, OnDestroy } from '@angular/core';
|
|
1
|
+
import { ChangeDetectionStrategy, Component, ElementRef, inject, input, OnDestroy } from '@angular/core';
|
|
2
2
|
import { TOOLTIP_CONTEXT } from './tooltip-context';
|
|
3
3
|
|
|
4
4
|
/**
|
|
@@ -70,6 +70,7 @@ export class TooltipTrigger implements OnDestroy {
|
|
|
70
70
|
readonly asChild = input<boolean>(false);
|
|
71
71
|
|
|
72
72
|
protected readonly context = inject(TOOLTIP_CONTEXT);
|
|
73
|
+
private readonly _elementRef = inject(ElementRef);
|
|
73
74
|
|
|
74
75
|
private showTimeout: ReturnType<typeof setTimeout> | null = null;
|
|
75
76
|
private hideTimeout: ReturnType<typeof setTimeout> | null = null;
|
|
@@ -80,9 +81,10 @@ export class TooltipTrigger implements OnDestroy {
|
|
|
80
81
|
|
|
81
82
|
onMouseEnter(): void {
|
|
82
83
|
this.clearTimeouts();
|
|
84
|
+
this.context.triggerRef?.set(this._elementRef.nativeElement);
|
|
83
85
|
this.showTimeout = setTimeout(() => {
|
|
84
86
|
this.context.setOpen(true);
|
|
85
|
-
}, this.context.delayDuration);
|
|
87
|
+
}, this.context.delayDuration());
|
|
86
88
|
}
|
|
87
89
|
onMouseLeave(): void {
|
|
88
90
|
this.clearTimeouts();
|
|
@@ -93,6 +95,7 @@ export class TooltipTrigger implements OnDestroy {
|
|
|
93
95
|
}
|
|
94
96
|
onFocus(): void {
|
|
95
97
|
// Show immediately on focus for keyboard users
|
|
98
|
+
this.context.triggerRef?.set(this._elementRef.nativeElement);
|
|
96
99
|
this.context.setOpen(true);
|
|
97
100
|
}
|
|
98
101
|
onBlur(): void {
|
|
@@ -126,9 +126,11 @@ export class Tooltip implements TooltipContextValue {
|
|
|
126
126
|
readonly disableHoverableContent = input<boolean>(false);
|
|
127
127
|
|
|
128
128
|
readonly open = signal(false);
|
|
129
|
+
/** Reference to the trigger element for fixed positioning */
|
|
130
|
+
readonly triggerRef = signal<HTMLElement | null>(null);
|
|
129
131
|
|
|
130
132
|
/** The duration from when the pointer enters the trigger until the tooltip opens */
|
|
131
|
-
readonly delayDuration = 700;
|
|
133
|
+
readonly delayDuration = input<number>(700);
|
|
132
134
|
/** How much time a user has to enter another trigger without incurring a delay again */
|
|
133
135
|
readonly skipDelayDuration = 300;
|
|
134
136
|
/** Unique ID for aria-describedby relationship */
|