@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.
Files changed (81) hide show
  1. package/package.json +6 -5
  2. package/src/app/lib/components/ui/alert-dialog/alert-dialog-content.component.ts +21 -20
  3. package/src/app/lib/components/ui/avatar/ui-avatar.component.ts +4 -0
  4. package/src/app/lib/components/ui/calendar/calendar.component.ts +5 -1
  5. package/src/app/lib/components/ui/carousel/carousel-content.component.ts +1 -0
  6. package/src/app/lib/components/ui/carousel/carousel-item.component.ts +1 -0
  7. package/src/app/lib/components/ui/carousel/carousel-next.component.ts +1 -0
  8. package/src/app/lib/components/ui/carousel/carousel-previous.component.ts +1 -0
  9. package/src/app/lib/components/ui/carousel/carousel.component.ts +1 -0
  10. package/src/app/lib/components/ui/chart/chart-container.component.ts +1 -0
  11. package/src/app/lib/components/ui/chart/chart-legend-content.component.ts +1 -0
  12. package/src/app/lib/components/ui/chart/chart-legend.component.ts +5 -5
  13. package/src/app/lib/components/ui/chart/chart-tooltip-content.component.ts +5 -5
  14. package/src/app/lib/components/ui/chart/chart-tooltip.component.ts +5 -5
  15. package/src/app/lib/components/ui/chart/chart.component.ts +1 -0
  16. package/src/app/lib/components/ui/checkbox/checkbox.component.ts +1 -1
  17. package/src/app/lib/components/ui/collapsible/collapsible-content.component.ts +2 -1
  18. package/src/app/lib/components/ui/collapsible/collapsible-context.ts +1 -0
  19. package/src/app/lib/components/ui/collapsible/collapsible-trigger.component.ts +1 -0
  20. package/src/app/lib/components/ui/collapsible/collapsible.component.ts +3 -0
  21. package/src/app/lib/components/ui/context-menu/context-menu-content.component.ts +48 -17
  22. package/src/app/lib/components/ui/context-menu/context-menu-sub-content.component.ts +2 -0
  23. package/src/app/lib/components/ui/context-menu/context-menu-sub-trigger.component.ts +30 -1
  24. package/src/app/lib/components/ui/context-menu/context-menu-sub.component.ts +3 -0
  25. package/src/app/lib/components/ui/date-picker/date-picker.component.ts +1 -0
  26. package/src/app/lib/components/ui/dialog/dialog-content.component.ts +26 -19
  27. package/src/app/lib/components/ui/direction/direction-context.ts +9 -0
  28. package/src/app/lib/components/ui/direction/direction.component.ts +48 -0
  29. package/src/app/lib/components/ui/direction/index.ts +2 -0
  30. package/src/app/lib/components/ui/drawer/drawer-content.component.ts +44 -0
  31. package/src/app/lib/components/ui/dropdown-menu/dropdown-menu-content.component.ts +2 -2
  32. package/src/app/lib/components/ui/dropdown-menu/dropdown-menu-radio-item.component.ts +1 -0
  33. package/src/app/lib/components/ui/dropdown-menu/dropdown-menu-sub-content.component.ts +2 -0
  34. package/src/app/lib/components/ui/dropdown-menu/dropdown-menu-sub-trigger.component.ts +28 -2
  35. package/src/app/lib/components/ui/dropdown-menu/dropdown-menu-sub.component.ts +3 -0
  36. package/src/app/lib/components/ui/dropdown-menu/dropdown-menu-trigger.component.ts +25 -0
  37. package/src/app/lib/components/ui/empty/empty-action.component.ts +1 -0
  38. package/src/app/lib/components/ui/empty/empty-description.component.ts +1 -0
  39. package/src/app/lib/components/ui/empty/empty-icon.component.ts +2 -1
  40. package/src/app/lib/components/ui/empty/empty-title.component.ts +1 -0
  41. package/src/app/lib/components/ui/empty/empty.component.ts +1 -0
  42. package/src/app/lib/components/ui/form/form-description.component.ts +2 -2
  43. package/src/app/lib/components/ui/hover-card/hover-card-content.component.ts +108 -60
  44. package/src/app/lib/components/ui/hover-card/hover-card-context.ts +4 -2
  45. package/src/app/lib/components/ui/hover-card/hover-card-trigger.component.ts +5 -3
  46. package/src/app/lib/components/ui/hover-card/hover-card.component.ts +8 -3
  47. package/src/app/lib/components/ui/input-group/input-group-addon.component.ts +1 -0
  48. package/src/app/lib/components/ui/input-group/input-group-input.component.ts +1 -0
  49. package/src/app/lib/components/ui/input-group/input-group.component.ts +1 -0
  50. package/src/app/lib/components/ui/menubar/menubar-content.component.ts +1 -1
  51. package/src/app/lib/components/ui/navigation-menu/navigation-menu-content.component.ts +7 -1
  52. package/src/app/lib/components/ui/navigation-menu/navigation-menu-context.ts +14 -0
  53. package/src/app/lib/components/ui/navigation-menu/navigation-menu-item.component.ts +9 -4
  54. package/src/app/lib/components/ui/navigation-menu/navigation-menu-trigger.component.ts +69 -2
  55. package/src/app/lib/components/ui/navigation-menu/navigation-menu.component.ts +32 -4
  56. package/src/app/lib/components/ui/pagination/pagination.component.ts +3 -1
  57. package/src/app/lib/components/ui/popover/popover-content.component.ts +11 -0
  58. package/src/app/lib/components/ui/popover/popover-context.ts +2 -0
  59. package/src/app/lib/components/ui/popover/popover.component.ts +4 -0
  60. package/src/app/lib/components/ui/progress/progress.component.ts +1 -2
  61. package/src/app/lib/components/ui/scroll-area/scroll-area.component.ts +7 -6
  62. package/src/app/lib/components/ui/segmented/segmented-item.component.ts +1 -0
  63. package/src/app/lib/components/ui/segmented/segmented.component.ts +1 -0
  64. package/src/app/lib/components/ui/select/select-content.component.ts +35 -15
  65. package/src/app/lib/components/ui/select/select-context.ts +10 -0
  66. package/src/app/lib/components/ui/select/select-item.component.ts +25 -7
  67. package/src/app/lib/components/ui/select/select-trigger.component.ts +6 -13
  68. package/src/app/lib/components/ui/select/select.component.ts +46 -0
  69. package/src/app/lib/components/ui/sheet/sheet-content.component.ts +22 -5
  70. package/src/app/lib/components/ui/slider/slider.component.ts +2 -2
  71. package/src/app/lib/components/ui/sonner/index.ts +2 -0
  72. package/src/app/lib/components/ui/sonner/sonner.component.ts +70 -0
  73. package/src/app/lib/components/ui/switch/switch.component.ts +1 -14
  74. package/src/app/lib/components/ui/tabs/tabs-list.component.ts +18 -0
  75. package/src/app/lib/components/ui/tabs/tabs-trigger.component.ts +0 -1
  76. package/src/app/lib/components/ui/toggle/toggle.component.ts +12 -6
  77. package/src/app/lib/components/ui/tooltip/tooltip-content.component.ts +141 -17
  78. package/src/app/lib/components/ui/tooltip/tooltip-context.ts +3 -1
  79. package/src/app/lib/components/ui/tooltip/tooltip-provider.component.ts +1 -1
  80. package/src/app/lib/components/ui/tooltip/tooltip-trigger.component.ts +5 -2
  81. 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 { ChangeDetectionStrategy, Component, computed, inject, input } from '@angular/core';
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]="side()"
85
- [attr.data-align]="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
- protected readonly computedClass = computed(() => {
118
- const sideClasses = {
119
- top: 'bottom-full left-1/2 -translate-x-1/2 mb-2',
120
- bottom: 'top-full left-1/2 -translate-x-1/2 mt-2',
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
- sideClasses[this.side()],
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 */