Skip to content

Commit 1d24057

Browse files
author
Andrea Barbasso
committed
[UXP-126] create tooltip
1 parent f84d2b4 commit 1d24057

7 files changed

Lines changed: 117 additions & 41 deletions

src/app/app.component.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
<ds-themed-root
2-
(dsTextSelect)="onTextSelect($event)"
2+
dsTextSelectTooltip
33
[shouldShowFullscreenLoader]="(isAuthBlocking$ | async) || (isThemeLoading$ | async)"
44
[shouldShowRouteLoader]="isRouteLoading$ | async"></ds-themed-root>

src/app/app.component.ts

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,6 @@ import { distinctNext } from './core/shared/distinct-next';
2929
import { RouteService } from './core/services/route.service';
3030
import { getEditItemPageRoute, getWorkflowItemModuleRoute, getWorkspaceItemModuleRoute } from './app-routing-paths';
3131
import { SocialService } from './social/social.service';
32-
import { TextSelectEvent } from './directives/text-select/text-select.directive';
3332

3433
@Component({
3534
selector: 'ds-app',
@@ -167,14 +166,4 @@ export class AppComponent implements OnInit, AfterViewInit {
167166
});
168167
}
169168

170-
onTextSelect(event: TextSelectEvent) {
171-
console.group('Text selected');
172-
console.warn('Text selected:', event.text);
173-
console.warn('Viewport rectangle:', event.viewportRectangle);
174-
console.warn('Host rectangle:', event.hostRectangle);
175-
console.groupEnd();
176-
177-
const utterance = new SpeechSynthesisUtterance(event.text);
178-
speechSynthesis.speak(utterance);
179-
}
180169
}

src/app/directives/text-select/text-select.directive.ts

Lines changed: 42 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,15 @@
1-
import { Directive, ElementRef, EventEmitter, NgZone, OnDestroy, OnInit, Output } from '@angular/core';
1+
import {
2+
ApplicationRef, ComponentFactoryResolver,
3+
ComponentRef,
4+
Directive,
5+
ElementRef,
6+
EmbeddedViewRef,
7+
Injector,
8+
NgZone,
9+
OnDestroy,
10+
OnInit
11+
} from '@angular/core';
12+
import { TextSelectionTooltipComponent } from './text-selection-tooltip/text-selection-tooltip.component';
213

314
// Define the structure of the event that will be emitted when text is selected.
415
export interface TextSelectEvent {
@@ -15,22 +26,24 @@ interface SelectionRectangle {
1526
height: number;
1627
}
1728

29+
1830
// This directive emits an event when the user selects text within the host element.
1931
@Directive({
20-
selector: '[dsTextSelect]',
32+
selector: '[dsTextSelectTooltip]',
2133
})
2234
export class TextSelectDirective implements OnInit, OnDestroy {
2335

24-
// Event emitter for the text select event.
25-
@Output()
26-
dsTextSelect: EventEmitter<TextSelectEvent> = new EventEmitter();
27-
2836
hasSelection = false;
2937

38+
private componentRef: ComponentRef<any> = null;
39+
3040
// Initialize the directive.
3141
constructor(
3242
private elementRef: ElementRef,
33-
private zone: NgZone
43+
private zone: NgZone,
44+
private appRef: ApplicationRef,
45+
private componentFactoryResolver: ComponentFactoryResolver,
46+
private injector: Injector
3447
) {
3548
}
3649

@@ -42,6 +55,7 @@ export class TextSelectDirective implements OnInit, OnDestroy {
4255

4356
// Set up event listeners when the directive is initialized.
4457
public ngOnInit(): void {
58+
this.elementRef.nativeElement.style.position = 'relative';
4559
this.zone.runOutsideAngular(() => {
4660
this.elementRef.nativeElement.addEventListener('mousedown', this.handleMousedown, false);
4761
});
@@ -75,45 +89,44 @@ export class TextSelectDirective implements OnInit, OnDestroy {
7589
// this solves the issue of the previous selection not being cleared before the mouseup event
7690
setTimeout(() => {
7791
const selection = document.getSelection();
92+
const stringSelection = selection.toString().trim();
7893
if (this.hasSelection) {
7994
this.zone.runGuarded(() => {
8095
this.hasSelection = false;
81-
this.dsTextSelect.next({
82-
text: '',
83-
viewportRectangle: null,
84-
hostRectangle: null
85-
});
8696
});
97+
this.componentRef.destroy();
98+
this.componentRef = null;
8799
}
88-
if (!selection.rangeCount || !selection.toString()) {
100+
if (!selection.rangeCount || !stringSelection) {
89101
return;
90102
}
91103
let range = selection.getRangeAt(0);
92104
let rangeContainer = this.getRangeContainer(range);
93105
if (this.elementRef.nativeElement.contains(rangeContainer)) {
94106
let viewportRectangle = range.getBoundingClientRect();
95107
let localRectangle = this.viewportToHost(viewportRectangle, rangeContainer);
96-
const stringSelection = selection.toString();
97108
if (stringSelection) {
98109
this.zone.runGuarded(() => {
99110
this.hasSelection = true;
100-
this.dsTextSelect.emit({
101-
text: stringSelection,
102-
viewportRectangle: {
103-
left: viewportRectangle.left,
104-
top: viewportRectangle.top,
105-
width: viewportRectangle.width,
106-
height: viewportRectangle.height
107-
},
108-
hostRectangle: {
109-
left: localRectangle.left,
110-
top: localRectangle.top,
111-
width: localRectangle.width,
112-
height: localRectangle.height
113-
}
114-
});
115111
});
116112
}
113+
if (this.componentRef === null) {
114+
const componentFactory =
115+
this.componentFactoryResolver.resolveComponentFactory(TextSelectionTooltipComponent);
116+
this.componentRef = componentFactory.create(this.injector);
117+
118+
this.appRef.attachView(this.componentRef.hostView);
119+
120+
const domElem =
121+
(this.componentRef.hostView as EmbeddedViewRef<any>)
122+
.rootNodes[0] as HTMLElement;
123+
124+
document.body.appendChild(domElem);
125+
126+
this.componentRef.instance.left = localRectangle.left + localRectangle.width / 2;
127+
this.componentRef.instance.top = localRectangle.top;
128+
this.componentRef.instance.text = stringSelection;
129+
}
117130
}
118131
});
119132
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
<p (mousedown)="textToSpeech($event)">Click me for TTS</p>
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
:host {
2+
position: absolute;
3+
z-index: 9999;
4+
background-color: #f5f5f5;
5+
transform: translate(-50%, -100%);
6+
}
7+
8+
p {
9+
cursor: pointer;
10+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import { ComponentFixture, TestBed } from '@angular/core/testing';
2+
3+
import { TextSelectionTooltipComponent } from './text-selection-tooltip.component';
4+
5+
describe('TextSelectionTooltipComponent', () => {
6+
let component: TextSelectionTooltipComponent;
7+
let fixture: ComponentFixture<TextSelectionTooltipComponent>;
8+
9+
beforeEach(async () => {
10+
await TestBed.configureTestingModule({
11+
declarations: [ TextSelectionTooltipComponent ]
12+
})
13+
.compileComponents();
14+
15+
fixture = TestBed.createComponent(TextSelectionTooltipComponent);
16+
component = fixture.componentInstance;
17+
fixture.detectChanges();
18+
});
19+
20+
it('should create', () => {
21+
expect(component).toBeTruthy();
22+
});
23+
});
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import { ChangeDetectionStrategy, Component, HostBinding, Input, OnDestroy } from '@angular/core';
2+
3+
@Component({
4+
selector: 'ds-text-selection-tooltip',
5+
templateUrl: './text-selection-tooltip.component.html',
6+
styleUrls: ['./text-selection-tooltip.component.scss'],
7+
changeDetection: ChangeDetectionStrategy.OnPush,
8+
standalone: true
9+
})
10+
export class TextSelectionTooltipComponent implements OnDestroy {
11+
12+
@Input()
13+
@HostBinding('style.left.px')
14+
left: number;
15+
16+
@Input()
17+
@HostBinding('style.top.px')
18+
top: number;
19+
20+
@Input()
21+
text: string;
22+
23+
utterance: SpeechSynthesisUtterance;
24+
25+
public textToSpeech(event: MouseEvent): void {
26+
event.preventDefault();
27+
28+
if (this.utterance) {
29+
speechSynthesis.cancel();
30+
}
31+
this.utterance = new SpeechSynthesisUtterance(this.text);
32+
speechSynthesis.speak(this.utterance);
33+
}
34+
35+
public ngOnDestroy(): void {
36+
if (this.utterance) {
37+
speechSynthesis.cancel();
38+
}
39+
}
40+
}

0 commit comments

Comments
 (0)