wally-ui 1.11.2 → 1.12.0

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 (35) hide show
  1. package/dist/cli.js +7 -0
  2. package/dist/cli.js.map +1 -1
  3. package/package.json +4 -2
  4. package/playground/showcase/public/sitemap.xml +32 -0
  5. package/playground/showcase/src/app/app.routes.server.ts +8 -0
  6. package/playground/showcase/src/app/components/ai/ai-chat/ai-chat.css +0 -0
  7. package/playground/showcase/src/app/components/ai/ai-chat/ai-chat.html +5 -0
  8. package/playground/showcase/src/app/components/ai/ai-chat/ai-chat.spec.ts +23 -0
  9. package/playground/showcase/src/app/components/ai/ai-chat/ai-chat.ts +17 -0
  10. package/playground/showcase/src/app/components/ai/ai-composer/ai-composer.css +0 -0
  11. package/playground/showcase/src/app/components/ai/ai-composer/ai-composer.html +69 -0
  12. package/playground/showcase/src/app/components/ai/ai-composer/ai-composer.spec.ts +23 -0
  13. package/playground/showcase/src/app/components/ai/ai-composer/ai-composer.ts +19 -0
  14. package/playground/showcase/src/app/components/ai/ai-prompt-input/ai-prompt-input.css +0 -0
  15. package/playground/showcase/src/app/components/ai/ai-prompt-input/ai-prompt-input.html +10 -0
  16. package/playground/showcase/src/app/components/ai/ai-prompt-input/ai-prompt-input.spec.ts +23 -0
  17. package/playground/showcase/src/app/components/ai/ai-prompt-input/ai-prompt-input.ts +25 -0
  18. package/playground/showcase/src/app/components/button/button.html +5 -1
  19. package/playground/showcase/src/app/components/button/button.ts +1 -0
  20. package/playground/showcase/src/app/components/carousel/carousel.ts +1 -2
  21. package/playground/showcase/src/app/components/tooltip/tooltip.css +0 -0
  22. package/playground/showcase/src/app/components/tooltip/tooltip.html +9 -0
  23. package/playground/showcase/src/app/components/tooltip/tooltip.ts +189 -0
  24. package/playground/showcase/src/app/pages/documentation/chat-sdk/chat-sdk.html +99 -0
  25. package/playground/showcase/src/app/pages/documentation/chat-sdk/chat-sdk.ts +20 -0
  26. package/playground/showcase/src/app/pages/documentation/components/components.html +23 -0
  27. package/playground/showcase/src/app/pages/documentation/components/components.routes.ts +4 -0
  28. package/playground/showcase/src/app/pages/documentation/components/tooltip-docs/tooltip-docs.css +1 -0
  29. package/playground/showcase/src/app/pages/documentation/components/tooltip-docs/tooltip-docs.examples.ts +150 -0
  30. package/playground/showcase/src/app/pages/documentation/components/tooltip-docs/tooltip-docs.html +471 -0
  31. package/playground/showcase/src/app/pages/documentation/components/tooltip-docs/tooltip-docs.ts +69 -0
  32. package/playground/showcase/src/app/pages/documentation/documentation.routes.ts +4 -0
  33. package/playground/showcase/src/app/pages/home/home.html +26 -0
  34. package/playground/showcase/src/app/pages/home/home.ts +3 -2
  35. package/playground/showcase/src/index.html +7 -4
package/dist/cli.js CHANGED
@@ -41,6 +41,13 @@ program
41
41
  .command('add <component>')
42
42
  .description('Add a new component')
43
43
  .action(async (component) => {
44
+ // Block AI components (coming soon in Chat SDK)
45
+ if (component.startsWith('ai-')) {
46
+ console.log(chalk_1.default.yellowBright(`\n AI components (${component}) are part of the Chat SDK (coming soon).`));
47
+ console.log(chalk_1.default.blueBright('These components will be available via: ') + chalk_1.default.cyan('npx wally-ui add chat-sdk'));
48
+ console.log(chalk_1.default.gray('Visit https://wally-ui.com/documentation/chat-sdk for more info.\n'));
49
+ return;
50
+ }
44
51
  // Check if Angular project
45
52
  if (!fs_extra_1.default.existsSync('angular.json')) {
46
53
  console.log(chalk_1.default.redBright('Not an Angular project. Run this in Angular project root.'));
package/dist/cli.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wally-ui",
3
- "version": "1.11.2",
3
+ "version": "1.12.0",
4
4
  "description": "About Where’s Wally? Right here — bringing you ready-to-use Angular components with Wally-UI. Stop searching, start building.",
5
5
  "bin": {
6
6
  "wally": "dist/cli.js"
@@ -35,7 +35,9 @@
35
35
  "design-system",
36
36
  "tailwind",
37
37
  "angular-tailwind",
38
- "typescript"
38
+ "typescript",
39
+ "chatbot",
40
+ "chatbot-sdk"
39
41
  ],
40
42
  "engines": {
41
43
  "node": ">=20.0.0"
@@ -52,4 +52,36 @@
52
52
  <priority>0.8</priority>
53
53
  </url>
54
54
 
55
+ <!-- Breadcrumb Documentation -->
56
+ <url>
57
+ <loc>https://wally-ui.com/documentation/components/breadcrumb</loc>
58
+ <lastmod>2025-10-08</lastmod>
59
+ <changefreq>monthly</changefreq>
60
+ <priority>0.8</priority>
61
+ </url>
62
+
63
+ <!-- Tooltip Documentation -->
64
+ <url>
65
+ <loc>https://wally-ui.com/documentation/components/tooltip</loc>
66
+ <lastmod>2025-10-08</lastmod>
67
+ <changefreq>monthly</changefreq>
68
+ <priority>0.8</priority>
69
+ </url>
70
+
71
+ <!-- Chat SDK Documentation -->
72
+ <url>
73
+ <loc>https://wally-ui.com/documentation/chat-sdk</loc>
74
+ <lastmod>2025-10-08</lastmod>
75
+ <changefreq>weekly</changefreq>
76
+ <priority>0.9</priority>
77
+ </url>
78
+
79
+ <!-- MCP Server Documentation -->
80
+ <url>
81
+ <loc>https://wally-ui.com/mcp</loc>
82
+ <lastmod>2025-10-08</lastmod>
83
+ <changefreq>monthly</changefreq>
84
+ <priority>0.8</priority>
85
+ </url>
86
+
55
87
  </urlset>
@@ -25,6 +25,14 @@ export const serverRoutes: ServerRoute[] = [
25
25
  path: 'documentation/components/carousel',
26
26
  renderMode: RenderMode.Prerender,
27
27
  },
28
+ {
29
+ path: 'documentation/components/tooltip',
30
+ renderMode: RenderMode.Prerender,
31
+ },
32
+ {
33
+ path: 'documentation/chat-sdk',
34
+ renderMode: RenderMode.Prerender,
35
+ },
28
36
  {
29
37
  path: 'components',
30
38
  renderMode: RenderMode.Prerender,
@@ -0,0 +1,5 @@
1
+ <div class="w-full border bg-white/50 border-neutral-300 dark:bg-[#1b1b1b] dark:border-neutral-700 shadow-lg rounded-4xl p-1">
2
+ <div class="w-full">
3
+ <wally-ai-composer></wally-ai-composer>
4
+ </div>
5
+ </div>
@@ -0,0 +1,23 @@
1
+ import { ComponentFixture, TestBed } from '@angular/core/testing';
2
+
3
+ import { AiChat } from './ai-chat';
4
+
5
+ describe('AiChat', () => {
6
+ let component: AiChat;
7
+ let fixture: ComponentFixture<AiChat>;
8
+
9
+ beforeEach(async () => {
10
+ await TestBed.configureTestingModule({
11
+ imports: [AiChat]
12
+ })
13
+ .compileComponents();
14
+
15
+ fixture = TestBed.createComponent(AiChat);
16
+ component = fixture.componentInstance;
17
+ fixture.detectChanges();
18
+ });
19
+
20
+ it('should create', () => {
21
+ expect(component).toBeTruthy();
22
+ });
23
+ });
@@ -0,0 +1,17 @@
1
+ import { Component } from '@angular/core';
2
+
3
+ import { AiPromptInput } from '../ai-prompt-input/ai-prompt-input';
4
+ import { AiComposer } from '../ai-composer/ai-composer';
5
+
6
+ @Component({
7
+ selector: 'wally-ai-chat',
8
+ imports: [
9
+ AiComposer,
10
+ AiPromptInput,
11
+ ],
12
+ templateUrl: './ai-chat.html',
13
+ styleUrl: './ai-chat.css'
14
+ })
15
+ export class AiChat {
16
+
17
+ }
@@ -0,0 +1,69 @@
1
+ <div class="w-full flex flex-col" role="region" aria-label="AI message composer">
2
+ <!-- Selected Text Context -->
3
+ <div class="p-0.5">
4
+ <div
5
+ id="selected-context"
6
+ class="w-full flex items-center gap-1 justify-between border bg-neutral-100 border-neutral-300 dark:bg-[#121212] dark:border-neutral-700 rounded-t-3xl rounded-b-lg p-3"
7
+ role="status"
8
+ aria-live="polite">
9
+ <div class="w-full flex gap-4 items-center">
10
+ <svg viewBox="0 0 20 20" fill="currentColor" xmlns="http://www.w3.org/2000/svg"
11
+ class="size-5 text-[#0a0a0a] dark:text-white"
12
+ aria-hidden="true">
13
+ <path
14
+ d="M12.5293 6.5293C12.7566 6.30203 13.1081 6.27383 13.3662 6.44434L13.4707 6.5293L17.4707 10.5293C17.7304 10.789 17.7304 11.211 17.4707 11.4707L13.4707 15.4707C13.211 15.7304 12.789 15.7304 12.5293 15.4707C12.2696 15.211 12.2696 14.789 12.5293 14.5293L15.3936 11.665H6C3.97588 11.665 2.33496 10.0241 2.33496 8V4.5C2.33496 4.13273 2.63273 3.83496 3 3.83496C3.36727 3.83496 3.66504 4.13273 3.66504 4.5V8C3.66504 9.28958 4.71042 10.335 6 10.335H15.3936L12.5293 7.4707L12.4443 7.36621C12.2738 7.10808 12.302 6.75657 12.5293 6.5293Z">
15
+ </path>
16
+ </svg>
17
+
18
+ <p class="text-sm text-[#0a0a0a] dark:text-white" id="context-label">
19
+ "Ask about"
20
+ </p>
21
+ </div>
22
+
23
+ <div>
24
+ <wally-button
25
+ [variant]="'ghost'"
26
+ [rounded]="true"
27
+ [ariaLabel]="'Clear selected context'">
28
+ <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5"
29
+ stroke="currentColor" class="size-5" aria-hidden="true">
30
+ <path stroke-linecap="round" stroke-linejoin="round" d="M6 18 18 6M6 6l12 12" />
31
+ </svg>
32
+ </wally-button>
33
+ </div>
34
+ </div>
35
+ </div>
36
+
37
+ <div class="w-full px-4 pt-4">
38
+ <wally-ai-prompt-input></wally-ai-prompt-input>
39
+ </div>
40
+
41
+ <!-- Action Buttons -->
42
+ <div class="w-full flex justify-between px-2 pb-2" role="toolbar" aria-label="Composer actions">
43
+ <div class="size-10">
44
+ <wally-tooltip [text]="'Add files and more'" position="bottom">
45
+ <wally-button
46
+ [variant]="'ghost'"
47
+ [rounded]="true"
48
+ [ariaLabel]="'Add attachment or insert content'">
49
+ <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2"
50
+ stroke="currentColor" class="size-5" aria-hidden="true">
51
+ <path stroke-linecap="round" stroke-linejoin="round" d="M12 4.5v15m7.5-7.5h-15" />
52
+ </svg>
53
+ </wally-button>
54
+ </wally-tooltip>
55
+ </div>
56
+
57
+ <div class="size-10">
58
+ <wally-button
59
+ [variant]="'primary'"
60
+ [rounded]="true"
61
+ [ariaLabel]="'Send message'">
62
+ <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="3"
63
+ stroke="currentColor" class="size-5" aria-hidden="true">
64
+ <path stroke-linecap="round" stroke-linejoin="round" d="M4.5 10.5 12 3m0 0 7.5 7.5M12 3v18" />
65
+ </svg>
66
+ </wally-button>
67
+ </div>
68
+ </div>
69
+ </div>
@@ -0,0 +1,23 @@
1
+ import { ComponentFixture, TestBed } from '@angular/core/testing';
2
+
3
+ import { AiComposer } from './ai-composer';
4
+
5
+ describe('AiComposer', () => {
6
+ let component: AiComposer;
7
+ let fixture: ComponentFixture<AiComposer>;
8
+
9
+ beforeEach(async () => {
10
+ await TestBed.configureTestingModule({
11
+ imports: [AiComposer]
12
+ })
13
+ .compileComponents();
14
+
15
+ fixture = TestBed.createComponent(AiComposer);
16
+ component = fixture.componentInstance;
17
+ fixture.detectChanges();
18
+ });
19
+
20
+ it('should create', () => {
21
+ expect(component).toBeTruthy();
22
+ });
23
+ });
@@ -0,0 +1,19 @@
1
+ import { Component } from '@angular/core';
2
+
3
+ import { AiPromptInput } from '../ai-prompt-input/ai-prompt-input';
4
+ import { Button } from '../../button/button';
5
+ import { Tooltip } from '../../tooltip/tooltip';
6
+
7
+ @Component({
8
+ selector: 'wally-ai-composer',
9
+ imports: [
10
+ AiPromptInput,
11
+ Tooltip,
12
+ Button
13
+ ],
14
+ templateUrl: './ai-composer.html',
15
+ styleUrl: './ai-composer.css'
16
+ })
17
+ export class AiComposer {
18
+
19
+ }
@@ -0,0 +1,10 @@
1
+ <textarea #textarea id="ai-prompt-input" class="w-full h-auto max-h-60 text-sm placeholder:text-neutral-400 dark:text-white dark:placeholder:text-neutral-500 resize-none focus:outline-none focus:ring-0 overflow-y-auto [&::-webkit-scrollbar]:w-2
2
+ [&::-webkit-scrollbar-track]:bg-transparent
3
+ [&::-webkit-scrollbar-thumb]:bg-neutral-300
4
+ [&::-webkit-scrollbar-thumb]:rounded-full
5
+ dark:[&::-webkit-scrollbar-thumb]:bg-neutral-700" name="ai-prompt" rows="1" placeholder="Ask something"
6
+ aria-label="AI prompt input" aria-describedby="ai-prompt-description" autocomplete="off" autocorrect="off"
7
+ aria-multiline="true" autocapitalize="sentences" spellcheck="true" (input)="onInput()"></textarea>
8
+ <span id="ai-prompt-description" class="sr-only">
9
+ Enter your message or prompt for the AI assistant. Press Enter to send, Shift+Enter for new line.
10
+ </span>
@@ -0,0 +1,23 @@
1
+ import { ComponentFixture, TestBed } from '@angular/core/testing';
2
+
3
+ import { AiPromptInput } from './ai-prompt-input';
4
+
5
+ describe('AiPromptInput', () => {
6
+ let component: AiPromptInput;
7
+ let fixture: ComponentFixture<AiPromptInput>;
8
+
9
+ beforeEach(async () => {
10
+ await TestBed.configureTestingModule({
11
+ imports: [AiPromptInput]
12
+ })
13
+ .compileComponents();
14
+
15
+ fixture = TestBed.createComponent(AiPromptInput);
16
+ component = fixture.componentInstance;
17
+ fixture.detectChanges();
18
+ });
19
+
20
+ it('should create', () => {
21
+ expect(component).toBeTruthy();
22
+ });
23
+ });
@@ -0,0 +1,25 @@
1
+ import { AfterViewInit, Component, ElementRef, ViewChild } from '@angular/core';
2
+
3
+ @Component({
4
+ selector: 'wally-ai-prompt-input',
5
+ imports: [],
6
+ templateUrl: './ai-prompt-input.html',
7
+ styleUrl: './ai-prompt-input.css'
8
+ })
9
+ export class AiPromptInput implements AfterViewInit {
10
+ @ViewChild('textarea') textarea!: ElementRef<HTMLTextAreaElement>;
11
+
12
+ ngAfterViewInit(): void {
13
+ this.adjustHeight();
14
+ }
15
+
16
+ public onInput(): void {
17
+ this.adjustHeight();
18
+ }
19
+
20
+ private adjustHeight(): void {
21
+ const element = this.textarea.nativeElement;
22
+ element.style.height = 'auto';
23
+ element.style.height = `${element.scrollHeight}px`;
24
+ }
25
+ }
@@ -1,7 +1,11 @@
1
1
  <button [type]="type()" [disabled]="disabled() || loading()" [attr.aria-label]="ariaLabel() || null"
2
2
  [attr.aria-describedby]="ariaDescribedBy() || null" [attr.aria-pressed]="ariaPressed()" [attr.aria-busy]="loading()"
3
3
  (click)="handleClick()"
4
- [class]="'group relative w-full flex items-center justify-center gap-2 text-sm font-medium text-white disabled:pointer-events-none p-2.5 rounded-md transition-all duration-300 ease-in-out antialiased cursor-pointer ' + variantClasses()">
4
+ [class]="'group relative w-full flex items-center justify-center gap-2 text-sm font-medium text-white disabled:pointer-events-none p-2.5 transition-all duration-300 ease-in-out antialiased cursor-pointer ' + variantClasses()"
5
+ [ngClass]="{
6
+ 'rounded-md': !rounded(),
7
+ 'rounded-full': rounded()
8
+ }">
5
9
 
6
10
  @if (showNotification()) {
7
11
  <span class="absolute top-0 right-0 -mt-1 -mr-1 flex size-3">
@@ -21,6 +21,7 @@ export class Button {
21
21
  showNotification: InputSignal<boolean> = input<boolean>(false);
22
22
  variant: InputSignal<ButtonVariant> = input<ButtonVariant>('primary');
23
23
  href: InputSignal<string> = input<string>('');
24
+ rounded: InputSignal<boolean> = input<boolean>(false);
24
25
 
25
26
  // Accessibility properties
26
27
  ariaLabel: InputSignal<string> = input<string>('');
@@ -1,13 +1,12 @@
1
1
  import { Component, signal, ViewChild, ElementRef, AfterViewInit, Renderer2, OnDestroy, HostListener, WritableSignal, input, InputSignal } from '@angular/core';
2
2
  import { CommonModule } from '@angular/common';
3
- import { it } from 'node:test';
4
3
 
5
4
  @Component({
6
5
  selector: 'wally-carousel',
7
6
  imports: [CommonModule],
8
7
  templateUrl: './carousel.html',
9
8
  })
10
- export class Carousel implements AfterViewInit {
9
+ export class Carousel implements AfterViewInit, OnDestroy {
11
10
  @ViewChild('carouselContainer', { static: false }) carouselContainer!: ElementRef;
12
11
 
13
12
  isNavigationIndicator: InputSignal<boolean> = input<boolean>(false);
@@ -0,0 +1,9 @@
1
+ <div class="relative inline-block tooltip-wrapper " (mouseenter)="show()" (mouseleave)="hide()" (focus)="show()" (blur)="hide()">
2
+ <ng-content></ng-content>
3
+
4
+ @if (visible() && !disabled()) {
5
+ <div #tooltipElement [id]="tooltipId" [class]="tooltipClasses()" role="tooltip" [attr.aria-hidden]="!visible()">
6
+ {{ text() }}
7
+ </div>
8
+ }
9
+ </div>
@@ -0,0 +1,189 @@
1
+ import { Component, computed, effect, ElementRef, input, InputSignal, OnDestroy, signal, ViewChild, WritableSignal } from '@angular/core';
2
+ import { CommonModule } from '@angular/common';
3
+
4
+ type TooltipPosition = 'top' | 'bottom' | 'left' | 'right' | 'auto';
5
+
6
+ @Component({
7
+ selector: 'wally-tooltip',
8
+ imports: [CommonModule],
9
+ templateUrl: './tooltip.html',
10
+ styleUrl: './tooltip.css'
11
+ })
12
+ export class Tooltip implements OnDestroy {
13
+ @ViewChild('tooltipElement', { read: ElementRef }) tooltipElement?: ElementRef<HTMLDivElement>;
14
+
15
+ text: InputSignal<string> = input.required<string>();
16
+ position: InputSignal<TooltipPosition> = input<TooltipPosition>('auto');
17
+ delay: InputSignal<number> = input<number>(200);
18
+ disabled: InputSignal<boolean> = input<boolean>(false);
19
+ offset: InputSignal<number> = input<number>(2);
20
+
21
+ visible: WritableSignal<boolean> = signal<boolean>(false);
22
+ actualPosition = signal<Exclude<TooltipPosition, 'auto'>>('top');
23
+ tooltipId = `tooltip-${Math.random().toString(36).substr(2, 9)}`;
24
+
25
+ private showTimeout?: number;
26
+ private hideTimeout?: number;
27
+
28
+ tooltipClasses = computed(() => {
29
+ const base = 'absolute z-50 py-1.5 px-3 text-sm text-white bg-[#121212] shadow-lg whitespace-nowrap pointer-events-none rounded-xl';
30
+ const darkMode = 'dark:bg-white dark:text-[#121212]';
31
+
32
+ const positions = {
33
+ top: `bottom-full left-1/2 -translate-x-1/2 mb-${this.offset()}`,
34
+ bottom: `top-full left-1/2 -translate-x-1/2 mt-${this.offset()}`,
35
+ left: `right-full top-1/2 -translate-y-1/2 mr-${this.offset()}`,
36
+ right: `left-full top-1/2 -translate-y-1/2 ml-${this.offset()}`
37
+ };
38
+
39
+ // Animação super suave - apenas fade rápido
40
+ const animation = this.visible()
41
+ ? 'transition-opacity duration-150 opacity-100'
42
+ : 'transition-opacity duration-100 opacity-0';
43
+
44
+ return `${base} ${darkMode} ${positions[this.actualPosition()]} ${animation}`;
45
+ });
46
+
47
+ constructor(
48
+ private elementRef: ElementRef
49
+ ) {
50
+ effect(() => {
51
+ if (this.visible() && this.position() === 'auto') {
52
+ setTimeout(() => this.calculateBestPosition(), 10);
53
+ }
54
+ });
55
+ }
56
+
57
+ ngOnDestroy() {
58
+ if (this.showTimeout) {
59
+ window.clearTimeout(this.showTimeout);
60
+ }
61
+ if (this.hideTimeout) {
62
+ window.clearTimeout(this.hideTimeout);
63
+ }
64
+ }
65
+
66
+ show(): void {
67
+ if (this.disabled()) {
68
+ return;
69
+ }
70
+
71
+ if (this.hideTimeout) {
72
+ window.clearTimeout(this.hideTimeout);
73
+ this.hideTimeout = undefined;
74
+ }
75
+
76
+ this.showTimeout = window.setTimeout(() => {
77
+ this.visible.set(true);
78
+
79
+ if (this.position() !== 'auto') {
80
+ this.actualPosition.set(this.position() as Exclude<TooltipPosition, 'auto'>);
81
+ }
82
+ }, this.delay());
83
+ }
84
+
85
+ hide(): void {
86
+ if (this.showTimeout) {
87
+ window.clearTimeout(this.showTimeout);
88
+ this.showTimeout = undefined;
89
+ }
90
+
91
+ this.hideTimeout = window.setTimeout(() => {
92
+ this.visible.set(false);
93
+ }, 100);
94
+ }
95
+
96
+ private calculateBestPosition() {
97
+ const hostElement =
98
+ this.elementRef.nativeElement.querySelector('.tooltip-wrapper');
99
+ if (!hostElement) return;
100
+
101
+ const rect = hostElement.getBoundingClientRect();
102
+ const tooltipEl = this.tooltipElement?.nativeElement;
103
+ if (!tooltipEl) return;
104
+
105
+ const tooltipRect = tooltipEl.getBoundingClientRect();
106
+ const offset = this.offset();
107
+
108
+ const viewportWidth = window.innerWidth;
109
+ const viewportHeight = window.innerHeight;
110
+
111
+ const spaceAbove = rect.top;
112
+ const spaceBelow = viewportHeight - rect.bottom;
113
+ const spaceLeft = rect.left;
114
+ const spaceRight = viewportWidth - rect.right;
115
+
116
+ const tooltipWidth = tooltipRect.width || 150;
117
+ const tooltipHeight = tooltipRect.height || 40;
118
+
119
+ const positions: {
120
+ position: Exclude<TooltipPosition, 'auto'>; space: number; isValid: boolean}[] = [
121
+ {
122
+ position: 'top',
123
+ space: spaceAbove,
124
+ isValid: spaceAbove >= tooltipHeight + offset
125
+ },
126
+ {
127
+ position: 'bottom',
128
+ space: spaceBelow,
129
+ isValid: spaceBelow >= tooltipHeight + offset
130
+ },
131
+ {
132
+ position: 'left',
133
+ space: spaceLeft,
134
+ isValid: spaceLeft >= tooltipWidth + offset
135
+ },
136
+ {
137
+ position: 'right',
138
+ space: spaceRight,
139
+ isValid: spaceRight >= tooltipWidth + offset
140
+ }
141
+ ];
142
+
143
+ const priorityOrder: Exclude<TooltipPosition, 'auto'>[] = ['top', 'bottom', 'right', 'left'];
144
+
145
+ let bestPosition: Exclude<TooltipPosition, 'auto'> = 'top';
146
+
147
+ for (const preferred of priorityOrder) {
148
+ const pos = positions.find(p => p.position === preferred);
149
+ if (pos?.isValid) {
150
+ bestPosition = preferred;
151
+ break;
152
+ }
153
+ }
154
+
155
+ if (!positions.find(p => p.position ===
156
+ bestPosition)?.isValid) {
157
+ const maxSpace = positions.reduce((max, pos) => pos.space >
158
+ max.space ? pos : max, positions[0]);
159
+ bestPosition = maxSpace.position;
160
+ }
161
+
162
+ this.actualPosition.set(bestPosition);
163
+
164
+ setTimeout(() => {
165
+ const updatedTooltipRect = tooltipEl.getBoundingClientRect();
166
+ const padding = 8;
167
+
168
+ if (bestPosition === 'top' || bestPosition === 'bottom') {
169
+ if (updatedTooltipRect.left < padding) {
170
+ const shift = padding - updatedTooltipRect.left;
171
+ tooltipEl.style.transform = `translateX(${shift}px)`;
172
+ } else if (updatedTooltipRect.right > viewportWidth - padding) {
173
+ const shift = (viewportWidth - padding) - updatedTooltipRect.right;
174
+ tooltipEl.style.transform = `translateX(${shift}px)`;
175
+ }
176
+ }
177
+
178
+ if (bestPosition === 'left' || bestPosition === 'right') {
179
+ if (updatedTooltipRect.top < padding) {
180
+ const shift = padding - updatedTooltipRect.top;
181
+ tooltipEl.style.transform = `translateY(${shift}px)`;
182
+ } else if (updatedTooltipRect.bottom > viewportHeight - padding) {
183
+ const shift = (viewportHeight - padding) - updatedTooltipRect.bottom;
184
+ tooltipEl.style.transform = `translateY(${shift}px)`;
185
+ }
186
+ }
187
+ }, 0);
188
+ }
189
+ }