Browse Source

feat(chip-overflow): optional showOverflowedTitle native tooltip

- Add a `showOverflowedTitle` boolean input (defaults to false,
  coerceBoolean-decorated) to ChipOverflowDirective. When true and
  hiddenCount > 0, the overflow `+N` chip gets a `title` attribute
  set to the comma-joined trimmed textContent of every overflowing
  chip, surfacing them via the native browser tooltip. When the
  flag is off, no items overflow, or hiddenCount drops back to
  zero, the title attribute is removed.
- Keep `pointer-events` in sync with the title so the native
  tooltip actually fires: remove `pointer-events: none` while a
  title is present; reapply it otherwise (matches the initial
  state set in createOverflowChip).
- Opt the IoT Hub item detail dialog's three tb-chip-overflow
  meta-chip groups (connectivity, categories, use-cases) into
  showOverflowedTitle so hovering the `+N` chip lists the hidden
  values.
pull/15772/head
Igor Kulikov 4 months ago
parent
commit
8afc94cfc9
  1. 6
      ui-ngx/src/app/modules/home/components/iot-hub/iot-hub-item-detail-dialog.component.html
  2. 23
      ui-ngx/src/app/shared/directives/chip-overflow.directive.ts

6
ui-ngx/src/app/modules/home/components/iot-hub/iot-hub-item-detail-dialog.component.html

@ -203,7 +203,7 @@
@if (item.dataDescriptor?.connectivity?.length) { @if (item.dataDescriptor?.connectivity?.length) {
<div class="dlg-meta-group dlg-meta-details-section"> <div class="dlg-meta-group dlg-meta-details-section">
<span class="dlg-meta-label">{{ 'iot-hub.connectivity' | translate }}</span> <span class="dlg-meta-label">{{ 'iot-hub.connectivity' | translate }}</span>
<div class="dlg-meta-chips" tb-chip-overflow chipSelector=".dlg-chip" overflowClass="dlg-chip dlg-chip-category" [gap]="4" [minChips]="2"> <div class="dlg-meta-chips" tb-chip-overflow chipSelector=".dlg-chip" overflowClass="dlg-chip dlg-chip-category" [gap]="4" [minChips]="2" showOverflowedTitle>
@for (conn of item.dataDescriptor.connectivity; track conn) { @for (conn of item.dataDescriptor.connectivity; track conn) {
<span class="dlg-chip dlg-chip-category">{{ conn }}</span> <span class="dlg-chip dlg-chip-category">{{ conn }}</span>
} }
@ -214,7 +214,7 @@
@if (item.categories?.length) { @if (item.categories?.length) {
<div class="dlg-meta-group dlg-meta-details-section"> <div class="dlg-meta-group dlg-meta-details-section">
<span class="dlg-meta-label">{{ 'iot-hub.category' | translate }}</span> <span class="dlg-meta-label">{{ 'iot-hub.category' | translate }}</span>
<div class="dlg-meta-chips" tb-chip-overflow chipSelector=".dlg-chip" overflowClass="dlg-chip dlg-chip-category" [gap]="4" [minChips]="2"> <div class="dlg-meta-chips" tb-chip-overflow chipSelector=".dlg-chip" overflowClass="dlg-chip dlg-chip-category" [gap]="4" [minChips]="2" showOverflowedTitle>
@for (cat of item.categories; track cat) { @for (cat of item.categories; track cat) {
<span class="dlg-chip dlg-chip-category">{{ cat }}</span> <span class="dlg-chip dlg-chip-category">{{ cat }}</span>
} }
@ -225,7 +225,7 @@
@if (item.useCases?.length) { @if (item.useCases?.length) {
<div class="dlg-meta-group dlg-meta-details-section"> <div class="dlg-meta-group dlg-meta-details-section">
<span class="dlg-meta-label">{{ 'iot-hub.use-cases' | translate }}</span> <span class="dlg-meta-label">{{ 'iot-hub.use-cases' | translate }}</span>
<div class="dlg-meta-chips" tb-chip-overflow chipSelector=".dlg-chip" overflowClass="dlg-chip dlg-chip-usecase" [gap]="4"> <div class="dlg-meta-chips" tb-chip-overflow chipSelector=".dlg-chip" overflowClass="dlg-chip dlg-chip-usecase" [gap]="4" showOverflowedTitle>
@for (uc of item.useCases; track uc) { @for (uc of item.useCases; track uc) {
<span class="dlg-chip dlg-chip-usecase">{{ uc }}</span> <span class="dlg-chip dlg-chip-usecase">{{ uc }}</span>
} }

23
ui-ngx/src/app/shared/directives/chip-overflow.directive.ts

@ -15,6 +15,7 @@
/// ///
import { AfterViewInit, Directive, ElementRef, Input, NgZone, OnDestroy, Renderer2 } from '@angular/core'; import { AfterViewInit, Directive, ElementRef, Input, NgZone, OnDestroy, Renderer2 } from '@angular/core';
import { coerceBoolean } from '@shared/decorators/coercion';
@Directive({ @Directive({
selector: '[tb-chip-overflow]', selector: '[tb-chip-overflow]',
@ -28,6 +29,10 @@ export class ChipOverflowDirective implements AfterViewInit, OnDestroy {
@Input() overflowClass = 'tb-overflow-chip'; @Input() overflowClass = 'tb-overflow-chip';
@Input() minChips = 1; @Input() minChips = 1;
@Input()
@coerceBoolean()
showOverflowedTitle = false;
private resizeObserver?: ResizeObserver; private resizeObserver?: ResizeObserver;
private mutationObserver?: MutationObserver; private mutationObserver?: MutationObserver;
private overflowEl!: HTMLElement; private overflowEl!: HTMLElement;
@ -151,6 +156,7 @@ export class ChipOverflowDirective implements AfterViewInit, OnDestroy {
// 4. Determine which chips fit // 4. Determine which chips fit
let usedWidth = 0; let usedWidth = 0;
let hiddenCount = 0; let hiddenCount = 0;
const hiddenChips: HTMLElement[] = [];
const minChips = Math.max(1, this.minChips); const minChips = Math.max(1, this.minChips);
for (let i = 0; i < chips.length; i++) { for (let i = 0; i < chips.length; i++) {
@ -163,12 +169,29 @@ export class ChipOverflowDirective implements AfterViewInit, OnDestroy {
usedWidth = nextUsedWidth; usedWidth = nextUsedWidth;
} else { } else {
hiddenCount++; hiddenCount++;
hiddenChips.push(chips[i]);
} }
} }
if (hiddenCount > 0) { if (hiddenCount > 0) {
this.renderer.setProperty(this.overflowEl, 'textContent', this.overflowTemplate.replace('{n}', String(hiddenCount))); this.renderer.setProperty(this.overflowEl, 'textContent', this.overflowTemplate.replace('{n}', String(hiddenCount)));
this.renderer.setStyle(this.overflowEl, 'display', 'inline-flex'); this.renderer.setStyle(this.overflowEl, 'display', 'inline-flex');
if (this.showOverflowedTitle) {
const title = hiddenChips
.map(chip => (chip.textContent || '').trim())
.filter(text => !!text)
.join(', ');
this.renderer.setAttribute(this.overflowEl, 'title', title);
// Allow pointer interaction so the browser's native title
// tooltip appears on hover.
this.renderer.removeStyle(this.overflowEl, 'pointerEvents');
} else {
this.renderer.removeAttribute(this.overflowEl, 'title');
this.renderer.setStyle(this.overflowEl, 'pointerEvents', 'none');
}
} else {
this.renderer.removeAttribute(this.overflowEl, 'title');
this.renderer.setStyle(this.overflowEl, 'pointerEvents', 'none');
} }
} }
} }

Loading…
Cancel
Save