-
widget-config.decimals-by-default
+
{{ 'widget-config.decimals-by-default' | translate }}
diff --git a/ui-ngx/src/app/shared/components/hint-tooltip-icon.component.html b/ui-ngx/src/app/shared/components/hint-tooltip-icon.component.html
index 32b66fd2b7..20593f042b 100644
--- a/ui-ngx/src/app/shared/components/hint-tooltip-icon.component.html
+++ b/ui-ngx/src/app/shared/components/hint-tooltip-icon.component.html
@@ -15,8 +15,12 @@
limitations under the License.
-->
-
-
{{ hintIcon }}
+
diff --git a/ui-ngx/src/app/shared/components/hint-tooltip-icon.component.scss b/ui-ngx/src/app/shared/components/hint-tooltip-icon.component.scss
index 7ed9925300..fb428e9fc3 100644
--- a/ui-ngx/src/app/shared/components/hint-tooltip-icon.component.scss
+++ b/ui-ngx/src/app/shared/components/hint-tooltip-icon.component.scss
@@ -13,21 +13,29 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-:host(.tb-hint-tooltip) {
- display: flex;
- flex-direction: row;
- align-items: center;
- gap: 4px;
-}
:host {
- .tb-hint-tooltip-icon {
- color: #E0E0E0;
- overflow: visible;
- order: 3;
- margin-left: 4px;
- &:hover {
- color: #9E9E9E;
+ .tb-hint-tooltip {
+ display: flex;
+ flex-direction: row;
+ align-items: center;
+ gap: 4px;
+
+ &-content {
+ flex: 1 1 auto;
+ min-width: 0;
+ }
+
+ &-icon {
+ color: #E0E0E0;
+ overflow: visible;
+ order: 3;
+ margin-left: 4px;
+ flex-shrink: 0;
+
+ &:hover {
+ color: #9E9E9E;
+ }
}
}
}
diff --git a/ui-ngx/src/app/shared/directives/public-api.ts b/ui-ngx/src/app/shared/directives/public-api.ts
new file mode 100644
index 0000000000..a82428fc19
--- /dev/null
+++ b/ui-ngx/src/app/shared/directives/public-api.ts
@@ -0,0 +1,17 @@
+///
+/// Copyright © 2016-2024 The Thingsboard Authors
+///
+/// Licensed under the Apache License, Version 2.0 (the "License");
+/// you may not use this file except in compliance with the License.
+/// You may obtain a copy of the License at
+///
+/// http://www.apache.org/licenses/LICENSE-2.0
+///
+/// Unless required by applicable law or agreed to in writing, software
+/// distributed under the License is distributed on an "AS IS" BASIS,
+/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+/// See the License for the specific language governing permissions and
+/// limitations under the License.
+///
+
+export * from './tooltip/tooltip.directive';
diff --git a/ui-ngx/src/app/shared/directives/tooltip/tooltip.directive.ts b/ui-ngx/src/app/shared/directives/tooltip/tooltip.directive.ts
new file mode 100644
index 0000000000..4d26c1388c
--- /dev/null
+++ b/ui-ngx/src/app/shared/directives/tooltip/tooltip.directive.ts
@@ -0,0 +1,108 @@
+///
+/// Copyright © 2016-2024 The Thingsboard Authors
+///
+/// Licensed under the Apache License, Version 2.0 (the "License");
+/// you may not use this file except in compliance with the License.
+/// You may obtain a copy of the License at
+///
+/// http://www.apache.org/licenses/LICENSE-2.0
+///
+/// Unless required by applicable law or agreed to in writing, software
+/// distributed under the License is distributed on an "AS IS" BASIS,
+/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+/// See the License for the specific language governing permissions and
+/// limitations under the License.
+///
+
+import {
+ AfterViewInit,
+ Directive,
+ ElementRef,
+ inject,
+ Input,
+ OnDestroy,
+ OnInit,
+ Renderer2,
+} from '@angular/core';
+import { fromEvent, Subject } from 'rxjs';
+import { filter, takeUntil, tap } from 'rxjs/operators';
+import { MatTooltip, TooltipPosition } from '@angular/material/tooltip';
+
+@Directive({
+ standalone: true,
+ selector: '[tbTruncateTooltip]',
+ providers: [MatTooltip],
+
+})
+export class TooltipDirective implements OnInit, AfterViewInit, OnDestroy {
+ @Input('tbTruncateTooltip') text: string;
+ @Input() tooltipEnabled = true;
+ @Input() position: TooltipPosition = 'above';
+
+ private elementRef = inject(ElementRef);
+ private renderer = inject(Renderer2);
+ private tooltip = inject(MatTooltip);
+ private destroy$ = new Subject
();
+
+ ngOnInit(): void {
+ this.observeMouseEvents();
+ }
+
+ ngAfterViewInit(): void {
+ this.applyTruncationStyles();
+
+ if (!this.text) {
+ this.text = this.elementRef.nativeElement.innerText;
+ }
+
+ this.tooltip.position = this.position;
+ }
+
+ ngOnDestroy(): void {
+ if (this.tooltip._isTooltipVisible()) {
+ this.hideTooltip();
+ }
+ this.destroy$.next();
+ this.destroy$.complete();
+ }
+
+ private observeMouseEvents(): void {
+ fromEvent(this.elementRef.nativeElement, 'mouseenter')
+ .pipe(
+ filter(() => this.tooltipEnabled),
+ filter(() => this.isOverflown(this.elementRef.nativeElement)),
+ tap(() => this.showTooltip()),
+ takeUntil(this.destroy$),
+ )
+ .subscribe();
+ fromEvent(this.elementRef.nativeElement, 'mouseleave')
+ .pipe(
+ filter(() => this.tooltipEnabled),
+ filter(() => this.tooltip._isTooltipVisible()),
+ tap(() => this.hideTooltip()),
+ takeUntil(this.destroy$),
+ )
+ .subscribe();
+ }
+
+ private applyTruncationStyles(): void {
+ this.renderer.setStyle(this.elementRef.nativeElement, 'white-space', 'nowrap');
+ this.renderer.setStyle(this.elementRef.nativeElement, 'overflow', 'hidden');
+ this.renderer.setStyle(this.elementRef.nativeElement, 'text-overflow', 'ellipsis');
+ }
+
+ private isOverflown(element: HTMLElement): boolean {
+ return element.clientWidth < element.scrollWidth;
+ }
+
+ private showTooltip(): void {
+ this.tooltip.message = this.text;
+
+ this.renderer.setAttribute(this.elementRef.nativeElement, 'matTooltip', this.text);
+ this.tooltip.show();
+ }
+
+ private hideTooltip(): void {
+ this.tooltip.hide();
+ }
+}
diff --git a/ui-ngx/src/app/shared/shared.module.ts b/ui-ngx/src/app/shared/shared.module.ts
index 567b80ed83..aa74c96f29 100644
--- a/ui-ngx/src/app/shared/shared.module.ts
+++ b/ui-ngx/src/app/shared/shared.module.ts
@@ -219,6 +219,7 @@ import { ImageGalleryDialogComponent } from '@shared/components/image/image-gall
import { RuleChainSelectPanelComponent } from '@shared/components/rule-chain/rule-chain-select-panel.component';
import { WidgetButtonComponent } from '@shared/components/button/widget-button.component';
import { HexInputComponent } from '@shared/components/color-picker/hex-input.component';
+import { TooltipDirective } from '@shared/directives/public-api';
export function MarkedOptionsFactory(markedOptionsService: MarkedOptionsService) {
return markedOptionsService;
@@ -472,6 +473,7 @@ export function MarkedOptionsFactory(markedOptionsService: MarkedOptionsService)
DndModule,
NgxFlowModule,
NgxFlowchartModule,
+ TooltipDirective,
// ngx-markdown
MarkdownModule.forRoot({
sanitize: SecurityContext.NONE,