ng-hub-ui-utils 22.12.1 → 22.14.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.
- package/README.md +26 -8
- package/fesm2022/ng-hub-ui-utils.mjs +174 -79
- package/fesm2022/ng-hub-ui-utils.mjs.map +1 -1
- package/package.json +1 -1
- package/types/ng-hub-ui-utils.d.ts +81 -51
package/README.md
CHANGED
|
@@ -384,13 +384,14 @@ export class ExampleComponent {}
|
|
|
384
384
|
Inputs: `hubTooltip` (text), `hubTooltipPlacement` (`top` | `bottom` | `left` | `right`,
|
|
385
385
|
default `top`), `hubTooltipDelay` (fade ms, default `150`), `hubTooltipOffset` (px, default `8`).
|
|
386
386
|
|
|
387
|
-
> **`TooltipDirective` (`[tooltip]`)
|
|
388
|
-
>
|
|
389
|
-
>
|
|
390
|
-
>
|
|
391
|
-
> `placement` and with the `tooltip` input of `<hub-badge>`. Migration is attribute for
|
|
387
|
+
> **`TooltipDirective` (`[tooltip]`) was removed in 22.14.0**, having been deprecated since
|
|
388
|
+
> 22.9.0. Its bare input names (`tooltip`, `placement`, `delay`, `offset`) belonged to every
|
|
389
|
+
> directive on the element that declared them, which is how it collided with `[hubDropdown]`'s
|
|
390
|
+
> own `placement` and with the `tooltip` input of `<hub-badge>`. Migration is attribute for
|
|
392
391
|
> attribute: `tooltip` → `hubTooltip`, `placement` → `hubTooltipPlacement`,
|
|
393
|
-
> `delay` → `hubTooltipDelay`, `offset` → `hubTooltipOffset`.
|
|
392
|
+
> `delay` → `hubTooltipDelay`, `offset` → `hubTooltipOffset`. A template left writing
|
|
393
|
+
> `tooltip="…"` still compiles and shows nothing at all, so check the markup as well as the
|
|
394
|
+
> imports.
|
|
394
395
|
|
|
395
396
|
Show the label **only while the host is truncated** with `HubOverflowTooltipDirective`
|
|
396
397
|
(`[hubOverflowTooltip]`), which tracks truncation live with a `ResizeObserver` and a
|
|
@@ -400,6 +401,22 @@ Show the label **only while the host is truncated** with `HubOverflowTooltipDire
|
|
|
400
401
|
<span class="label" [hubOverflowTooltip]="item.label">{{ item.label }}</span>
|
|
401
402
|
```
|
|
402
403
|
|
|
404
|
+
The element that is **hovered** and the element that is **measured** need not be the same.
|
|
405
|
+
By default they are, but a control whose text is clipped by a box inside it wants them apart:
|
|
406
|
+
the hover area is the whole control, while the only box that can report truncation is the
|
|
407
|
+
inner one — a child that clips its own text never lets the overflow reach its parent, so
|
|
408
|
+
measuring the parent reports none and the tooltip goes quiet. Point
|
|
409
|
+
`hubOverflowTooltipMeasure` at the inner box with a CSS selector, resolved inside the host:
|
|
410
|
+
|
|
411
|
+
```html
|
|
412
|
+
<div class="chip" [hubOverflowTooltip]="item.label" hubOverflowTooltipMeasure=".chip__title">
|
|
413
|
+
<span class="chip__icon"></span>
|
|
414
|
+
<span class="chip__title">{{ item.label }}</span>
|
|
415
|
+
</div>
|
|
416
|
+
```
|
|
417
|
+
|
|
418
|
+
Unset — or pointing at nothing — the host measures itself, exactly as before.
|
|
419
|
+
|
|
403
420
|
Theme it from any scope with `--hub-tooltip-*` variables:
|
|
404
421
|
|
|
405
422
|
```css
|
|
@@ -710,8 +727,7 @@ library, because their selectors and data models differ.
|
|
|
710
727
|
### Directives
|
|
711
728
|
|
|
712
729
|
- `HubTooltipDirective` (`[hubTooltip]`) - Tooltip on hover/focus. Inputs: `hubTooltip`, `hubTooltipPlacement`, `hubTooltipDelay`, `hubTooltipOffset`
|
|
713
|
-
- `HubOverflowTooltipDirective` (`[hubOverflowTooltip]`) - Tooltip shown only while the
|
|
714
|
-
- `TooltipDirective` (`[tooltip]`) - **Deprecated since 22.9.0**, kept working. Inputs: `tooltip`, `placement`, `delay`, `offset`
|
|
730
|
+
- `HubOverflowTooltipDirective` (`[hubOverflowTooltip]`) - Tooltip shown only while the label is truncated. Inputs: `hubOverflowTooltip`, `placement`, `hubOverflowTooltipMeasure` (CSS selector, resolved inside the host, naming the box whose truncation decides it; defaults to the host)
|
|
715
731
|
- `provideHubTooltip(adapter: HubTooltipAdapter)` and `HUB_TOOLTIP_ADAPTER` - Swap the implementation behind `[hubOverflowTooltip]`, app-wide or per subtree; defaults to `hubTooltipAdapter`
|
|
716
732
|
|
|
717
733
|
### Pipes
|
|
@@ -788,6 +804,8 @@ class OverlayRef {
|
|
|
788
804
|
}
|
|
789
805
|
|
|
790
806
|
class OverlayPosition {
|
|
807
|
+
// The element the panel is anchored to. The overlay watches it and follows it when it moves.
|
|
808
|
+
readonly origin: HTMLElement | null;
|
|
791
809
|
flexibleConnectedTo(origin: ElementRef | HTMLElement): this;
|
|
792
810
|
withPositions(positions: ConnectionPosition[]): this;
|
|
793
811
|
// `start` / `end` are logical and read from the origin element; this overrides that.
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as i0 from '@angular/core';
|
|
2
|
-
import { signal, computed, Injectable, effect, InjectionToken, inject, makeEnvironmentProviders, provideAppInitializer, DestroyRef, ElementRef, TemplateRef, createComponent, ApplicationRef, Pipe, ChangeDetectorRef, Injector, ViewContainerRef,
|
|
2
|
+
import { signal, computed, Injectable, effect, InjectionToken, inject, makeEnvironmentProviders, provideAppInitializer, DestroyRef, ElementRef, TemplateRef, createComponent, NgZone, ApplicationRef, Pipe, ChangeDetectorRef, Injector, ViewContainerRef, input, Directive, afterNextRender } from '@angular/core';
|
|
3
3
|
import { fromEvent, Observable, Subject, isObservable, EMPTY, of, timer, race } from 'rxjs';
|
|
4
4
|
import { takeUntil, map, filter, withLatestFrom, endWith, take, mergeMap, tap } from 'rxjs/operators';
|
|
5
5
|
import { DOCUMENT } from '@angular/common';
|
|
@@ -1729,6 +1729,20 @@ class OverlayPosition {
|
|
|
1729
1729
|
this._origin = origin;
|
|
1730
1730
|
return this;
|
|
1731
1731
|
}
|
|
1732
|
+
/**
|
|
1733
|
+
* The element this overlay is anchored to, resolved to a plain node.
|
|
1734
|
+
*
|
|
1735
|
+
* Exposed because the strategy is the only place that knows what the panel is connected to,
|
|
1736
|
+
* and the overlay has to watch that element to keep up with it when it moves.
|
|
1737
|
+
*
|
|
1738
|
+
* @returns The origin element, or `null` while none has been set.
|
|
1739
|
+
*/
|
|
1740
|
+
get origin() {
|
|
1741
|
+
if (!this._origin) {
|
|
1742
|
+
return null;
|
|
1743
|
+
}
|
|
1744
|
+
return this._origin instanceof ElementRef ? this._origin.nativeElement : this._origin;
|
|
1745
|
+
}
|
|
1732
1746
|
/**
|
|
1733
1747
|
* Sets the preferred positions for the overlay.
|
|
1734
1748
|
* The order of the array determines the fallback priority.
|
|
@@ -1914,6 +1928,8 @@ class OverlayRef {
|
|
|
1914
1928
|
_isAttached = false;
|
|
1915
1929
|
_repositionHandler;
|
|
1916
1930
|
_repositionFrame = 0;
|
|
1931
|
+
_originFrame = 0;
|
|
1932
|
+
_originBox = null;
|
|
1917
1933
|
_keydownCallback;
|
|
1918
1934
|
_unregisterKeydown;
|
|
1919
1935
|
_backdropClickCallback;
|
|
@@ -2042,8 +2058,79 @@ class OverlayRef {
|
|
|
2042
2058
|
};
|
|
2043
2059
|
window.addEventListener('scroll', this._repositionHandler, { capture: true, passive: true });
|
|
2044
2060
|
window.addEventListener('resize', this._repositionHandler, { passive: true });
|
|
2061
|
+
this._followOrigin();
|
|
2062
|
+
}
|
|
2063
|
+
/**
|
|
2064
|
+
* Follows the origin while it moves for a reason no event announces.
|
|
2065
|
+
*
|
|
2066
|
+
* Scroll and resize both describe the page moving under an origin that stays put. Neither says
|
|
2067
|
+
* anything about the origin itself moving inside a page nobody scrolled — a sibling collapsing
|
|
2068
|
+
* above it, an image landing, an accordion animating shut — and the panel is then left hanging
|
|
2069
|
+
* where its trigger used to be. Since that collapse is animated, one re-measure when it starts
|
|
2070
|
+
* would only move the panel to a place the trigger is still on its way out of: the trigger has
|
|
2071
|
+
* to be followed for as long as it slides.
|
|
2072
|
+
*
|
|
2073
|
+
* The origin's box is read every frame, but the position is re-applied only when it actually
|
|
2074
|
+
* changed, so an overlay whose trigger sits still never touches the DOM. Scheduled outside
|
|
2075
|
+
* Angular's zone, or a zone-based application would run change detection on every frame an
|
|
2076
|
+
* overlay is open.
|
|
2077
|
+
*/
|
|
2078
|
+
_followOrigin() {
|
|
2079
|
+
if (!this._config.positionStrategy || typeof requestAnimationFrame !== 'function') {
|
|
2080
|
+
return;
|
|
2081
|
+
}
|
|
2082
|
+
this._originBox = this._measureOrigin();
|
|
2083
|
+
const track = () => {
|
|
2084
|
+
if (!this._isAttached) {
|
|
2085
|
+
this._originFrame = 0;
|
|
2086
|
+
return;
|
|
2087
|
+
}
|
|
2088
|
+
this._originFrame = requestAnimationFrame(track);
|
|
2089
|
+
// Read again rather than captured once: a consumer is free to re-anchor a live overlay,
|
|
2090
|
+
// and the panel has to end up following whatever it is connected to now.
|
|
2091
|
+
const box = this._measureOrigin();
|
|
2092
|
+
const previous = this._originBox;
|
|
2093
|
+
if (!box) {
|
|
2094
|
+
return;
|
|
2095
|
+
}
|
|
2096
|
+
if (previous &&
|
|
2097
|
+
previous.top === box.top &&
|
|
2098
|
+
previous.left === box.left &&
|
|
2099
|
+
previous.width === box.width &&
|
|
2100
|
+
previous.height === box.height) {
|
|
2101
|
+
return;
|
|
2102
|
+
}
|
|
2103
|
+
this._originBox = box;
|
|
2104
|
+
this.updatePosition();
|
|
2105
|
+
};
|
|
2106
|
+
const start = () => {
|
|
2107
|
+
this._originFrame = requestAnimationFrame(track);
|
|
2108
|
+
};
|
|
2109
|
+
// Resolved through the injector rather than taken in the constructor, so the shape of a
|
|
2110
|
+
// class consumers can instantiate does not change for a private scheduling detail.
|
|
2111
|
+
const zone = this._appRef.injector.get(NgZone, null);
|
|
2112
|
+
if (zone) {
|
|
2113
|
+
zone.runOutsideAngular(start);
|
|
2114
|
+
return;
|
|
2115
|
+
}
|
|
2116
|
+
start();
|
|
2117
|
+
}
|
|
2118
|
+
/**
|
|
2119
|
+
* Reads the origin's box, reduced to the four numbers a move can change.
|
|
2120
|
+
*
|
|
2121
|
+
* @returns The box, or `null` when there is no origin or it has left the document — an element
|
|
2122
|
+
* out of the document reports zeros for everything, and following that would fling the panel
|
|
2123
|
+
* into the corner rather than leave it where it was.
|
|
2124
|
+
*/
|
|
2125
|
+
_measureOrigin() {
|
|
2126
|
+
const origin = this._config.positionStrategy?.origin;
|
|
2127
|
+
if (!origin || !origin.isConnected) {
|
|
2128
|
+
return null;
|
|
2129
|
+
}
|
|
2130
|
+
const rect = origin.getBoundingClientRect();
|
|
2131
|
+
return { top: rect.top, left: rect.left, width: rect.width, height: rect.height };
|
|
2045
2132
|
}
|
|
2046
|
-
/**
|
|
2133
|
+
/** Releases everything {@link _listenForReposition} started, the origin follow included. */
|
|
2047
2134
|
_stopListeningForReposition() {
|
|
2048
2135
|
if (!this._repositionHandler || typeof window === 'undefined') {
|
|
2049
2136
|
return;
|
|
@@ -2055,6 +2142,11 @@ class OverlayRef {
|
|
|
2055
2142
|
cancelAnimationFrame(this._repositionFrame);
|
|
2056
2143
|
this._repositionFrame = 0;
|
|
2057
2144
|
}
|
|
2145
|
+
if (this._originFrame) {
|
|
2146
|
+
cancelAnimationFrame(this._originFrame);
|
|
2147
|
+
this._originFrame = 0;
|
|
2148
|
+
}
|
|
2149
|
+
this._originBox = null;
|
|
2058
2150
|
}
|
|
2059
2151
|
/**
|
|
2060
2152
|
* Disposes the overlay and cleans up all allocated resources.
|
|
@@ -2700,7 +2792,7 @@ let nextTooltipId = 0;
|
|
|
2700
2792
|
*
|
|
2701
2793
|
* Binds hover/focus listeners to a host element and renders a body-portaled,
|
|
2702
2794
|
* `--hub-tooltip-*`-themeable label on demand. It owns no Angular dependency, so
|
|
2703
|
-
* it can be reused both by the `[
|
|
2795
|
+
* it can be reused both by the `[hubTooltip]` directive and by other primitives
|
|
2704
2796
|
* (e.g. a badge overflow tooltip) that want the exact same visual contract
|
|
2705
2797
|
* without re-implementing the DOM logic.
|
|
2706
2798
|
*
|
|
@@ -3033,65 +3125,6 @@ function provideHubTooltip(adapter) {
|
|
|
3033
3125
|
]);
|
|
3034
3126
|
}
|
|
3035
3127
|
|
|
3036
|
-
/**
|
|
3037
|
-
* Lightweight tooltip directive.
|
|
3038
|
-
*
|
|
3039
|
-
* @deprecated Use {@link HubTooltipDirective} (`[hubTooltip]`) instead. The bare
|
|
3040
|
-
* attribute and the bare input names `placement`, `delay` and `offset` are not this
|
|
3041
|
-
* directive's to hold: Angular feeds one attribute to every directive on the element
|
|
3042
|
-
* that declares an input of that name, so `[hubDropdown] placement` fails to compile
|
|
3043
|
-
* beside a tooltip, and `[tooltip]` on a `<hub-badge>` — which owns an input of that
|
|
3044
|
-
* name — renders two. Kept working, unchanged, until the next major.
|
|
3045
|
-
*
|
|
3046
|
-
* Apply `[tooltip]` to any element to show a positioned label on hover/focus.
|
|
3047
|
-
* The tooltip element is appended to `<body>` so it is never clipped by an
|
|
3048
|
-
* overflow container, and every visual aspect is themeable through
|
|
3049
|
-
* `--hub-tooltip-*` CSS variables.
|
|
3050
|
-
*
|
|
3051
|
-
* All DOM work is delegated to {@link HubTooltipController}, so the directive and
|
|
3052
|
-
* any imperative consumer (e.g. a badge overflow tooltip) share the exact same
|
|
3053
|
-
* behaviour and styling.
|
|
3054
|
-
*
|
|
3055
|
-
* Styles ship in `styles/tooltip.scss` (mirroring `styles/overlay.scss`). Import
|
|
3056
|
-
* it once in your app: `@use 'ng-hub-ui-utils/styles/tooltip';`.
|
|
3057
|
-
*/
|
|
3058
|
-
class TooltipDirective {
|
|
3059
|
-
/** Tooltip text content. */
|
|
3060
|
-
tooltipTitle = input.required({ ...(ngDevMode ? { debugName: "tooltipTitle" } : /* istanbul ignore next */ {}), alias: 'tooltip' });
|
|
3061
|
-
/** Placement of the tooltip relative to the host. */
|
|
3062
|
-
placement = input('top', /* @ts-ignore */
|
|
3063
|
-
...(ngDevMode ? [{ debugName: "placement" }] : /* istanbul ignore next */ []));
|
|
3064
|
-
/** Fade duration in milliseconds, also used as the removal delay on hide. */
|
|
3065
|
-
delay = input(150, /* @ts-ignore */
|
|
3066
|
-
...(ngDevMode ? [{ debugName: "delay" }] : /* istanbul ignore next */ []));
|
|
3067
|
-
/** Gap in pixels between the host and the tooltip. */
|
|
3068
|
-
offset = input(8, /* @ts-ignore */
|
|
3069
|
-
...(ngDevMode ? [{ debugName: "offset" }] : /* istanbul ignore next */ []));
|
|
3070
|
-
host = inject(ElementRef);
|
|
3071
|
-
controller = new HubTooltipController(this.host.nativeElement);
|
|
3072
|
-
constructor() {
|
|
3073
|
-
effect(() => {
|
|
3074
|
-
this.controller.setOptions({
|
|
3075
|
-
placement: this.placement(),
|
|
3076
|
-
delay: this.delay(),
|
|
3077
|
-
offset: this.offset()
|
|
3078
|
-
});
|
|
3079
|
-
this.controller.setText(this.tooltipTitle());
|
|
3080
|
-
});
|
|
3081
|
-
}
|
|
3082
|
-
ngOnDestroy() {
|
|
3083
|
-
this.controller.destroy();
|
|
3084
|
-
}
|
|
3085
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: TooltipDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
|
|
3086
|
-
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "22.0.8", type: TooltipDirective, isStandalone: true, selector: "[tooltip]", inputs: { tooltipTitle: { classPropertyName: "tooltipTitle", publicName: "tooltip", isSignal: true, isRequired: true, transformFunction: null }, placement: { classPropertyName: "placement", publicName: "placement", isSignal: true, isRequired: false, transformFunction: null }, delay: { classPropertyName: "delay", publicName: "delay", isSignal: true, isRequired: false, transformFunction: null }, offset: { classPropertyName: "offset", publicName: "offset", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0 });
|
|
3087
|
-
}
|
|
3088
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: TooltipDirective, decorators: [{
|
|
3089
|
-
type: Directive,
|
|
3090
|
-
args: [{
|
|
3091
|
-
selector: '[tooltip]'
|
|
3092
|
-
}]
|
|
3093
|
-
}], ctorParameters: () => [], propDecorators: { tooltipTitle: [{ type: i0.Input, args: [{ isSignal: true, alias: "tooltip", required: true }] }], placement: [{ type: i0.Input, args: [{ isSignal: true, alias: "placement", required: false }] }], delay: [{ type: i0.Input, args: [{ isSignal: true, alias: "delay", required: false }] }], offset: [{ type: i0.Input, args: [{ isSignal: true, alias: "offset", required: false }] }] } });
|
|
3094
|
-
|
|
3095
3128
|
/**
|
|
3096
3129
|
* Tooltip directive, named the way the rest of the family names itself.
|
|
3097
3130
|
*
|
|
@@ -3100,12 +3133,12 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImpor
|
|
|
3100
3133
|
* container, and every visual aspect is themeable through `--hub-tooltip-*` CSS
|
|
3101
3134
|
* variables.
|
|
3102
3135
|
*
|
|
3103
|
-
* ## Why
|
|
3136
|
+
* ## Why every attribute here carries the prefix
|
|
3104
3137
|
*
|
|
3105
|
-
*
|
|
3106
|
-
* `placement`, `delay` and `offset`. Angular feeds one attribute to
|
|
3107
|
-
* on the element that declares an input of that name, so those bare
|
|
3108
|
-
*
|
|
3138
|
+
* This replaced an earlier directive that claimed the bare attribute `[tooltip]` and the
|
|
3139
|
+
* bare input names `placement`, `delay` and `offset`. Angular feeds one attribute to
|
|
3140
|
+
* **every** directive on the element that declares an input of that name, so those bare
|
|
3141
|
+
* names were never a library's to hold:
|
|
3109
3142
|
*
|
|
3110
3143
|
* - `[hubDropdown]` declares its own `placement`, typed over eight values where a
|
|
3111
3144
|
* tooltip understands four. A menu trigger that also wanted a tooltip did not merely
|
|
@@ -3165,7 +3198,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImpor
|
|
|
3165
3198
|
* Shows a tooltip with the given text **only while the host element is truncated**
|
|
3166
3199
|
* (its content is wider than its box).
|
|
3167
3200
|
*
|
|
3168
|
-
* Unlike `[
|
|
3201
|
+
* Unlike `[hubTooltip]`, which always shows on hover, this directive is meant for
|
|
3169
3202
|
* ellipsised labels: the tooltip appears solely when the text doesn't fit, so it
|
|
3170
3203
|
* never duplicates already-visible content. Truncation is tracked live with a
|
|
3171
3204
|
* `ResizeObserver` (host/container resizes) and a `MutationObserver` (text
|
|
@@ -3175,13 +3208,27 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImpor
|
|
|
3175
3208
|
* {@link HUB_TOOLTIP_ADAPTER} token, which defaults to the built-in hub-ui
|
|
3176
3209
|
* tooltip. Swap it app-wide (or per subtree) with `provideHubTooltip(...)`.
|
|
3177
3210
|
*
|
|
3211
|
+
* The element that is MEASURED and the element that is HOVERED need not be the same. By
|
|
3212
|
+
* default they are — the host does both — but a control whose text is clipped by a box
|
|
3213
|
+
* inside it wants them apart: the hover area is the whole control, while the only box that
|
|
3214
|
+
* can report truncation is the inner one. Point `hubOverflowTooltipMeasure` at that box and
|
|
3215
|
+
* the tooltip covers the control while answering to the text.
|
|
3216
|
+
*
|
|
3178
3217
|
* Requires the tooltip styles once in your app:
|
|
3179
3218
|
* `@use 'ng-hub-ui-utils/styles/tooltip';`.
|
|
3180
3219
|
*
|
|
3181
|
-
* @example
|
|
3220
|
+
* @example Host measures itself
|
|
3182
3221
|
* ```html
|
|
3183
3222
|
* <span class="label" [hubOverflowTooltip]="item.label">{{ item.label }}</span>
|
|
3184
3223
|
* ```
|
|
3224
|
+
*
|
|
3225
|
+
* @example Hover the whole chip, measure the title inside it
|
|
3226
|
+
* ```html
|
|
3227
|
+
* <div class="chip" [hubOverflowTooltip]="item.label" hubOverflowTooltipMeasure=".chip__title">
|
|
3228
|
+
* <span class="chip__icon"></span>
|
|
3229
|
+
* <span class="chip__title">{{ item.label }}</span>
|
|
3230
|
+
* </div>
|
|
3231
|
+
* ```
|
|
3185
3232
|
*/
|
|
3186
3233
|
class HubOverflowTooltipDirective {
|
|
3187
3234
|
/** Tooltip text; shown only while the host overflows. */
|
|
@@ -3189,6 +3236,15 @@ class HubOverflowTooltipDirective {
|
|
|
3189
3236
|
/** Placement of the tooltip relative to the host. */
|
|
3190
3237
|
placement = input('top', /* @ts-ignore */
|
|
3191
3238
|
...(ngDevMode ? [{ debugName: "placement" }] : /* istanbul ignore next */ []));
|
|
3239
|
+
/**
|
|
3240
|
+
* CSS selector, resolved inside the host, naming the element whose truncation decides
|
|
3241
|
+
* whether the tooltip speaks. The tooltip still belongs to the host, so the hover area is
|
|
3242
|
+
* unchanged; only the measurement moves.
|
|
3243
|
+
*
|
|
3244
|
+
* Left unset — or pointing at nothing — the host measures itself, which is what this
|
|
3245
|
+
* directive has always done.
|
|
3246
|
+
*/
|
|
3247
|
+
measureTarget = input(undefined, { ...(ngDevMode ? { debugName: "measureTarget" } : /* istanbul ignore next */ {}), alias: 'hubOverflowTooltipMeasure' });
|
|
3192
3248
|
host = inject(ElementRef);
|
|
3193
3249
|
adapter = inject(HUB_TOOLTIP_ADAPTER);
|
|
3194
3250
|
handle = null;
|
|
@@ -3200,6 +3256,14 @@ class HubOverflowTooltipDirective {
|
|
|
3200
3256
|
...(ngDevMode ? [{ debugName: "ready" }] : /* istanbul ignore next */ []));
|
|
3201
3257
|
constructor() {
|
|
3202
3258
|
afterNextRender(() => this.init());
|
|
3259
|
+
// A selector changed after the fact points at a different box, so the observers and
|
|
3260
|
+
// the truncation state both have to be taken again.
|
|
3261
|
+
effect(() => {
|
|
3262
|
+
this.measureTarget();
|
|
3263
|
+
if (this.ready()) {
|
|
3264
|
+
this.observe();
|
|
3265
|
+
}
|
|
3266
|
+
});
|
|
3203
3267
|
effect(() => {
|
|
3204
3268
|
if (!this.ready() || !this.handle) {
|
|
3205
3269
|
return;
|
|
@@ -3216,32 +3280,63 @@ class HubOverflowTooltipDirective {
|
|
|
3216
3280
|
/** Wires the tooltip handle and the browser-only truncation observers. */
|
|
3217
3281
|
init() {
|
|
3218
3282
|
const el = this.host.nativeElement;
|
|
3219
|
-
//
|
|
3283
|
+
// The tooltip belongs to the HOST whatever is measured: it is the control the pointer
|
|
3284
|
+
// is over, and the box that reports truncation may be a fraction of it.
|
|
3220
3285
|
this.handle = this.adapter.attach(el, '', { placement: this.placement() });
|
|
3221
|
-
this.measure(el);
|
|
3222
3286
|
if (typeof ResizeObserver !== 'undefined') {
|
|
3223
|
-
|
|
3224
|
-
this.
|
|
3287
|
+
// Re-resolving on every callback rather than closing over the element: content
|
|
3288
|
+
// rendered after this point can bring the measured box with it.
|
|
3289
|
+
this.resizeObserver = new ResizeObserver(() => this.measure());
|
|
3225
3290
|
}
|
|
3226
3291
|
if (typeof MutationObserver !== 'undefined') {
|
|
3227
|
-
|
|
3292
|
+
// A mutation can replace the measured box, so the observers are re-pointed rather
|
|
3293
|
+
// than merely re-read.
|
|
3294
|
+
this.mutationObserver = new MutationObserver(() => this.observe());
|
|
3228
3295
|
this.mutationObserver.observe(el, { childList: true, characterData: true, subtree: true });
|
|
3229
3296
|
}
|
|
3297
|
+
this.observe();
|
|
3230
3298
|
this.ready.set(true);
|
|
3231
3299
|
}
|
|
3232
|
-
/**
|
|
3233
|
-
|
|
3300
|
+
/**
|
|
3301
|
+
* Points the resize observer at the boxes that can change the answer — the host, whose
|
|
3302
|
+
* width bounds everything, and the measured box when it is a different element — and
|
|
3303
|
+
* takes the measurement again.
|
|
3304
|
+
*/
|
|
3305
|
+
observe() {
|
|
3306
|
+
const host = this.host.nativeElement;
|
|
3307
|
+
const target = this.resolveTarget();
|
|
3308
|
+
if (this.resizeObserver) {
|
|
3309
|
+
this.resizeObserver.disconnect();
|
|
3310
|
+
this.resizeObserver.observe(host);
|
|
3311
|
+
if (target !== host) {
|
|
3312
|
+
this.resizeObserver.observe(target);
|
|
3313
|
+
}
|
|
3314
|
+
}
|
|
3315
|
+
this.measure();
|
|
3316
|
+
}
|
|
3317
|
+
/**
|
|
3318
|
+
* The element whose overflow is the question. Falls back to the host, so a selector that
|
|
3319
|
+
* matches nothing behaves exactly as no selector at all rather than silently going quiet.
|
|
3320
|
+
*/
|
|
3321
|
+
resolveTarget() {
|
|
3322
|
+
const host = this.host.nativeElement;
|
|
3323
|
+
const selector = this.measureTarget();
|
|
3324
|
+
return (selector ? host.querySelector(selector) : null) ?? host;
|
|
3325
|
+
}
|
|
3326
|
+
/** Updates the truncation state from the measured box's layout. */
|
|
3327
|
+
measure() {
|
|
3328
|
+
const el = this.resolveTarget();
|
|
3234
3329
|
this.overflowing.set(el.scrollWidth > el.clientWidth + 1);
|
|
3235
3330
|
}
|
|
3236
3331
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: HubOverflowTooltipDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
|
|
3237
|
-
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "22.0.8", type: HubOverflowTooltipDirective, isStandalone: true, selector: "[hubOverflowTooltip]", inputs: { text: { classPropertyName: "text", publicName: "hubOverflowTooltip", isSignal: true, isRequired: false, transformFunction: null }, placement: { classPropertyName: "placement", publicName: "placement", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0 });
|
|
3332
|
+
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "22.0.8", type: HubOverflowTooltipDirective, isStandalone: true, selector: "[hubOverflowTooltip]", inputs: { text: { classPropertyName: "text", publicName: "hubOverflowTooltip", isSignal: true, isRequired: false, transformFunction: null }, placement: { classPropertyName: "placement", publicName: "placement", isSignal: true, isRequired: false, transformFunction: null }, measureTarget: { classPropertyName: "measureTarget", publicName: "hubOverflowTooltipMeasure", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0 });
|
|
3238
3333
|
}
|
|
3239
3334
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: HubOverflowTooltipDirective, decorators: [{
|
|
3240
3335
|
type: Directive,
|
|
3241
3336
|
args: [{
|
|
3242
3337
|
selector: '[hubOverflowTooltip]'
|
|
3243
3338
|
}]
|
|
3244
|
-
}], ctorParameters: () => [], propDecorators: { text: [{ type: i0.Input, args: [{ isSignal: true, alias: "hubOverflowTooltip", required: false }] }], placement: [{ type: i0.Input, args: [{ isSignal: true, alias: "placement", required: false }] }] } });
|
|
3339
|
+
}], ctorParameters: () => [], propDecorators: { text: [{ type: i0.Input, args: [{ isSignal: true, alias: "hubOverflowTooltip", required: false }] }], placement: [{ type: i0.Input, args: [{ isSignal: true, alias: "placement", required: false }] }], measureTarget: [{ type: i0.Input, args: [{ isSignal: true, alias: "hubOverflowTooltipMeasure", required: false }] }] } });
|
|
3245
3340
|
|
|
3246
3341
|
/*
|
|
3247
3342
|
* Public API Surface of utils
|
|
@@ -3251,5 +3346,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImpor
|
|
|
3251
3346
|
* Generated bundle index. Do not edit.
|
|
3252
3347
|
*/
|
|
3253
3348
|
|
|
3254
|
-
export { ContentRef, FOCUSABLE_ELEMENTS_SELECTOR, GetPipe, HUB_DROPDOWN_POSITIONS, HUB_INK_LIGHTNESS_THRESHOLD, HUB_NAMED_COLORS, HUB_TOOLTIP_ADAPTER, HUB_TRANSLATION_CONFIG, HUB_TRANSLATION_PREFIX, HUB_TRANSLATION_SOURCE, HubDragDropService, HubOverflowTooltipDirective, HubTooltipController, HubTooltipDirective, HubTranslationService, IsObjectPipe, IsObservablePipe, IsStringPipe, OverlayPosition, OverlayRef, OverlayService, PopupService, ScrollBar,
|
|
3349
|
+
export { ContentRef, FOCUSABLE_ELEMENTS_SELECTOR, GetPipe, HUB_DROPDOWN_POSITIONS, HUB_INK_LIGHTNESS_THRESHOLD, HUB_NAMED_COLORS, HUB_TOOLTIP_ADAPTER, HUB_TRANSLATION_CONFIG, HUB_TRANSLATION_PREFIX, HUB_TRANSLATION_SOURCE, HubDragDropService, HubOverflowTooltipDirective, HubTooltipController, HubTooltipDirective, HubTranslationService, IsObjectPipe, IsObservablePipe, IsStringPipe, OverlayPosition, OverlayRef, OverlayService, PopupService, ScrollBar, TranslatePipe, UcfirstPipe, UnwrapAsyncPipe, clamp, clampToSrgbGamut, closest, compositeOver, computeTargetIndex, containsNode, contrastAPCA, contrastRatio, copyArrayItem, createNativeDragImage, createPointerDragSession, debouncedSignal, equals, generateUniqueId, getActiveElement, getFocusableBoundaryElements, getValue, getValueInRange, hubCompleteTransition, hubFocusTrap, hubRunTransition, hubTooltipAdapter, interpolateString, isDefined, isInSrgbGamut, isInteger, isNumber, isObject, isPromise, isString, isValidColor, maxSrgbChroma, mergeDeep, moveItemInArray, oklchToRgb, padNumber, parseColor, provideHubTooltip, provideHubTranslation, provideHubTranslationAdapter, readableOn, reflow, regExpEscape, registerOverlayKeydown, relativeLuminance, removeAccents, resolveDropPosition, resolveHubAccent, rgbToOklch, runInZone, toAbsoluteIndex, toHex, toInteger, toRgb, toString, transferArrayItem };
|
|
3255
3350
|
//# sourceMappingURL=ng-hub-ui-utils.mjs.map
|