@lucca/prisme 22.0.0-rc.5 → 22.0.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.
@@ -0,0 +1,630 @@
1
+ import { trigger, state, style, transition, animate } from '@angular/animations';
2
+ import * as i0 from '@angular/core';
3
+ import { inject, DestroyRef, signal, ChangeDetectionStrategy, Component, Injectable, ElementRef, Renderer2, Injector, input, linkedSignal, computed, numberAttribute, booleanAttribute, effect, afterRenderEffect, Directive, NgModule } from '@angular/core';
4
+ import { Subject, timer, startWith } from 'rxjs';
5
+ import { Overlay, OverlayModule } from '@angular/cdk/overlay';
6
+ import { ComponentPortal } from '@angular/cdk/portal';
7
+ import { DOCUMENT } from '@angular/common';
8
+ import { toObservable, takeUntilDestroyed } from '@angular/core/rxjs-interop';
9
+ import { isNotNil, ɵeffectWithDeps as _effectWithDeps, getPushPanelViewportMargin, isNil } from '@lucca/prisme/core';
10
+ import { filter, debounce, tap, map } from 'rxjs/operators';
11
+
12
+ const luTransformTooltip = trigger('transformTooltip', [
13
+ state('enter', style({
14
+ opacity: 1,
15
+ transform: `scale(1)`,
16
+ })),
17
+ transition('void => *', [
18
+ style({
19
+ opacity: 0,
20
+ transform: `scale(0)`,
21
+ }),
22
+ animate(`150ms cubic-bezier(0.25, 0.8, 0.25, 1)`),
23
+ ]),
24
+ transition('* => void', [animate('50ms 100ms linear', style({ opacity: 0 }))]),
25
+ ]);
26
+
27
+ class LuTooltipPanelComponent {
28
+ constructor() {
29
+ this.destroyRef = inject(DestroyRef);
30
+ this.mouseEnter$ = new Subject();
31
+ this.mouseLeave$ = new Subject();
32
+ this.content = signal(null, /* @ts-ignore */
33
+ ...(ngDevMode ? [{ debugName: "content" }] : /* istanbul ignore next */ []));
34
+ this.contentPositionClasses = signal({}, /* @ts-ignore */
35
+ ...(ngDevMode ? [{ debugName: "contentPositionClasses" }] : /* istanbul ignore next */ []));
36
+ }
37
+ setPanelPosition(posX, posY) {
38
+ this.contentPositionClasses.set({
39
+ 'is-before': posX === 'end',
40
+ 'is-after': posX === 'start',
41
+ 'is-above': posY === 'bottom',
42
+ 'is-below': posY === 'top',
43
+ });
44
+ }
45
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: LuTooltipPanelComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
46
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.8", type: LuTooltipPanelComponent, isStandalone: true, selector: "lu-tooltip-panel,pr-tooltip-panel", host: { attributes: { "role": "tooltip" }, listeners: { "mouseenter": "mouseEnter$.next()", "mouseleave": "mouseLeave$.next()" } }, ngImport: i0, template: "@if (content(); as content) {\n\t<div class=\"tooltip\" [class]=\"contentPositionClasses()\" [innerHtml]=\"content\"></div>\n}\n", styles: ["@layer components{.tooltip{--components-tooltip-background-color: var(--palettes-neutral-900);--components-tooltip-color: var(--pr-t-color-text-reverse);--components-tooltip-max-width: 15rem;--components-tooltip-transformOrigin: center;--components-tooltip-margin: 0;background-color:var(--components-tooltip-background-color);color:var(--components-tooltip-color);padding-block:var(--pr-t-spacings-50);padding-inline:var(--pr-t-spacings-100);max-inline-size:var(--components-tooltip-max-width);border-radius:var(--pr-t-border-radius-default);font:var(--pr-t-font-body-XS);transform-origin:var(--components-tooltip-transformOrigin);margin:var(--components-tooltip-margin);text-align:center;inline-size:fit-content;animation-name:scaleIn;animation-duration:var(--commons-animations-durations-fast);animation-iteration-count:1;overflow-wrap:break-word}@keyframes scaleIn{0%{transform:scale(0)}to{transform:scale(1)}}.tooltip:empty{display:none}@supports (background-image: -webkit-named-image(i)){@media(hover:hover){.tooltip_trigger.is-whenEllipsis:after{content:\"\";display:block}}}}@layer mods{.tooltip.is-above{--components-tooltip-transformOrigin: bottom center}.tooltip.is-below{--components-tooltip-transformOrigin: top center}.tooltip.is-before{--components-tooltip-transformOrigin: center right}.tooltip.is-before.is-above{--components-tooltip-transformOrigin: bottom right}.tooltip.is-before.is-below{--components-tooltip-transformOrigin: top right}.tooltip.is-after{--components-tooltip-transformOrigin: center left}.tooltip.is-after.is-above{--components-tooltip-transformOrigin: bottom left}.tooltip.is-after.is-below{--components-tooltip-transformOrigin: top left}}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
47
+ }
48
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: LuTooltipPanelComponent, decorators: [{
49
+ type: Component,
50
+ args: [{ selector: 'lu-tooltip-panel,pr-tooltip-panel', host: {
51
+ role: 'tooltip',
52
+ '(mouseenter)': 'mouseEnter$.next()',
53
+ '(mouseleave)': 'mouseLeave$.next()',
54
+ }, changeDetection: ChangeDetectionStrategy.OnPush, template: "@if (content(); as content) {\n\t<div class=\"tooltip\" [class]=\"contentPositionClasses()\" [innerHtml]=\"content\"></div>\n}\n", styles: ["@layer components{.tooltip{--components-tooltip-background-color: var(--palettes-neutral-900);--components-tooltip-color: var(--pr-t-color-text-reverse);--components-tooltip-max-width: 15rem;--components-tooltip-transformOrigin: center;--components-tooltip-margin: 0;background-color:var(--components-tooltip-background-color);color:var(--components-tooltip-color);padding-block:var(--pr-t-spacings-50);padding-inline:var(--pr-t-spacings-100);max-inline-size:var(--components-tooltip-max-width);border-radius:var(--pr-t-border-radius-default);font:var(--pr-t-font-body-XS);transform-origin:var(--components-tooltip-transformOrigin);margin:var(--components-tooltip-margin);text-align:center;inline-size:fit-content;animation-name:scaleIn;animation-duration:var(--commons-animations-durations-fast);animation-iteration-count:1;overflow-wrap:break-word}@keyframes scaleIn{0%{transform:scale(0)}to{transform:scale(1)}}.tooltip:empty{display:none}@supports (background-image: -webkit-named-image(i)){@media(hover:hover){.tooltip_trigger.is-whenEllipsis:after{content:\"\";display:block}}}}@layer mods{.tooltip.is-above{--components-tooltip-transformOrigin: bottom center}.tooltip.is-below{--components-tooltip-transformOrigin: top center}.tooltip.is-before{--components-tooltip-transformOrigin: center right}.tooltip.is-before.is-above{--components-tooltip-transformOrigin: bottom right}.tooltip.is-before.is-below{--components-tooltip-transformOrigin: top right}.tooltip.is-after{--components-tooltip-transformOrigin: center left}.tooltip.is-after.is-above{--components-tooltip-transformOrigin: bottom left}.tooltip.is-after.is-below{--components-tooltip-transformOrigin: top left}}\n"] }]
55
+ }] });
56
+
57
+ /**
58
+ * Single, shared IntersectionObserver used by every tooltip to defer its first ellipsis
59
+ * measurement until the host element is near the viewport.
60
+ *
61
+ * One observer for the whole page is far cheaper than one IntersectionObserver per tooltip when
62
+ * many tooltips are created at once (e.g. a large table being (re)rendered): the browser then
63
+ * delivers a single batched callback instead of one per element.
64
+ */
65
+ class TooltipVisibilityObserver {
66
+ #observer;
67
+ #callbacks = new WeakMap();
68
+ /** Calls `onVisible` once — the first time `element` comes near the viewport — then stops observing it. */
69
+ observeOnce(element, onVisible) {
70
+ this.#callbacks.set(element, onVisible);
71
+ this.#getObserver().observe(element);
72
+ }
73
+ unobserve(element) {
74
+ this.#callbacks.delete(element);
75
+ this.#observer?.unobserve(element);
76
+ }
77
+ // Created lazily so the observer is only instantiated in the browser, on first use.
78
+ #getObserver() {
79
+ return (this.#observer ??= new IntersectionObserver((entries) => {
80
+ for (const entry of entries) {
81
+ if (!entry.isIntersecting) {
82
+ continue;
83
+ }
84
+ const onVisible = this.#callbacks.get(entry.target);
85
+ this.unobserve(entry.target);
86
+ onVisible?.();
87
+ }
88
+ }, { rootMargin: '100px' }));
89
+ }
90
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: TooltipVisibilityObserver, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
91
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: TooltipVisibilityObserver, providedIn: 'root' }); }
92
+ }
93
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: TooltipVisibilityObserver, decorators: [{
94
+ type: Injectable,
95
+ args: [{ providedIn: 'root' }]
96
+ }] });
97
+
98
+ let nextId = 0;
99
+ class LuTooltipTriggerDirective {
100
+ #overlay;
101
+ #host;
102
+ #renderer;
103
+ #document;
104
+ #injector;
105
+ #destroyRef;
106
+ #visibilityObserver;
107
+ // 0 until the element first appears; bumped for the initial measurement and on every real
108
+ // size/content change. Scrolling in and out of view does NOT bump it (see #armMeasurementObservers).
109
+ #measureTrigger;
110
+ // guards the one-time setup of the persistent resize/mutation observers
111
+ #measurementObserversArmed;
112
+ // the IntersectionObserver callback and a debounced 'open' action (see openTooltip) can
113
+ // fire after the view is destroyed; avoid touching the destroyed injector (NG0911) or
114
+ // recreating the overlay when that happens
115
+ #destroyed;
116
+ // written only from the `read` phase of the afterRenderEffect below
117
+ #hasEllipsis;
118
+ // reusable hidden clone, one per directive, used to measure the unconstrained width
119
+ #clone;
120
+ #action;
121
+ #realAction;
122
+ #effectRef;
123
+ // pane the focus moved into when it last left this trigger, or null
124
+ #focusLeftToPane;
125
+ constructor() {
126
+ this.#overlay = inject(Overlay);
127
+ this.#host = inject(ElementRef);
128
+ this.#renderer = inject(Renderer2);
129
+ this.#document = inject(DOCUMENT);
130
+ this.#injector = inject(Injector);
131
+ this.#destroyRef = inject(DestroyRef);
132
+ this.#visibilityObserver = inject(TooltipVisibilityObserver);
133
+ this.luTooltipInput = input('', { ...(ngDevMode ? { debugName: "luTooltipInput" } : /* istanbul ignore next */ {}), alias: 'luTooltip' });
134
+ this.luTooltip = linkedSignal(() => this.luTooltipInput(), /* @ts-ignore */
135
+ ...(ngDevMode ? [{ debugName: "luTooltip" }] : /* istanbul ignore next */ []));
136
+ this.prTooltipInput = input('', { ...(ngDevMode ? { debugName: "prTooltipInput" } : /* istanbul ignore next */ {}), alias: 'prTooltip' });
137
+ this.prTooltip = linkedSignal(() => this.prTooltipInput(), /* @ts-ignore */
138
+ ...(ngDevMode ? [{ debugName: "prTooltip" }] : /* istanbul ignore next */ []));
139
+ this.tooltipContent = computed(() => this.luTooltip() || this.prTooltip(), /* @ts-ignore */
140
+ ...(ngDevMode ? [{ debugName: "tooltipContent" }] : /* istanbul ignore next */ []));
141
+ this.luTooltipEnterDelay = input(300, { ...(ngDevMode ? { debugName: "luTooltipEnterDelay" } : /* istanbul ignore next */ {}), transform: numberAttribute });
142
+ this.prTooltipEnterDelay = input(300, { ...(ngDevMode ? { debugName: "prTooltipEnterDelay" } : /* istanbul ignore next */ {}), transform: numberAttribute });
143
+ this.tooltipEnterDelay = computed(() => this.prTooltipEnterDelay() || this.luTooltipEnterDelay(), /* @ts-ignore */
144
+ ...(ngDevMode ? [{ debugName: "tooltipEnterDelay" }] : /* istanbul ignore next */ []));
145
+ this.luTooltipLeaveDelay = input(100, { ...(ngDevMode ? { debugName: "luTooltipLeaveDelay" } : /* istanbul ignore next */ {}), transform: numberAttribute });
146
+ this.prTooltipLeaveDelay = input(100, { ...(ngDevMode ? { debugName: "prTooltipLeaveDelay" } : /* istanbul ignore next */ {}), transform: numberAttribute });
147
+ this.tooltipLeaveDelay = computed(() => this.prTooltipLeaveDelay() || this.luTooltipLeaveDelay(), /* @ts-ignore */
148
+ ...(ngDevMode ? [{ debugName: "tooltipLeaveDelay" }] : /* istanbul ignore next */ []));
149
+ this.luTooltipDisabled = input(false, { ...(ngDevMode ? { debugName: "luTooltipDisabled" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
150
+ this.prTooltipDisabled = input(false, { ...(ngDevMode ? { debugName: "prTooltipDisabled" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
151
+ this.tooltipDisabled = computed(() => this.prTooltipDisabled() || this.luTooltipDisabled(), /* @ts-ignore */
152
+ ...(ngDevMode ? [{ debugName: "tooltipDisabled" }] : /* istanbul ignore next */ []));
153
+ this.luTooltipOnlyForDisplay = input(false, { ...(ngDevMode ? { debugName: "luTooltipOnlyForDisplay" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
154
+ this.prTooltipOnlyForDisplay = input(false, { ...(ngDevMode ? { debugName: "prTooltipOnlyForDisplay" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
155
+ this.tooltipOnlyForDisplay = computed(() => this.prTooltipOnlyForDisplay() || this.luTooltipOnlyForDisplay(), /* @ts-ignore */
156
+ ...(ngDevMode ? [{ debugName: "tooltipOnlyForDisplay" }] : /* istanbul ignore next */ []));
157
+ this.luTooltipPosition = input('above', /* @ts-ignore */
158
+ ...(ngDevMode ? [{ debugName: "luTooltipPosition" }] : /* istanbul ignore next */ []));
159
+ this.prTooltipPosition = input('above', /* @ts-ignore */
160
+ ...(ngDevMode ? [{ debugName: "prTooltipPosition" }] : /* istanbul ignore next */ []));
161
+ this.tooltipPosition = computed(() => this.prTooltipPosition() || this.luTooltipPosition(), /* @ts-ignore */
162
+ ...(ngDevMode ? [{ debugName: "tooltipPosition" }] : /* istanbul ignore next */ []));
163
+ this.luTooltipWhenEllipsisInput = input(false, { ...(ngDevMode ? { debugName: "luTooltipWhenEllipsisInput" } : /* istanbul ignore next */ {}), alias: 'luTooltipWhenEllipsis', transform: booleanAttribute });
164
+ this.prTooltipWhenEllipsisInput = input(false, { ...(ngDevMode ? { debugName: "prTooltipWhenEllipsisInput" } : /* istanbul ignore next */ {}), alias: 'prTooltipWhenEllipsis', transform: booleanAttribute });
165
+ this.luTooltipWhenEllipsis = linkedSignal(() => this.luTooltipWhenEllipsisInput(), /* @ts-ignore */
166
+ ...(ngDevMode ? [{ debugName: "luTooltipWhenEllipsis" }] : /* istanbul ignore next */ []));
167
+ this.prTooltipWhenEllipsis = linkedSignal(() => this.prTooltipWhenEllipsisInput(), /* @ts-ignore */
168
+ ...(ngDevMode ? [{ debugName: "prTooltipWhenEllipsis" }] : /* istanbul ignore next */ []));
169
+ this.tooltipWhenEllipsis = computed(() => this.prTooltipWhenEllipsis() || this.luTooltipWhenEllipsis(), /* @ts-ignore */
170
+ ...(ngDevMode ? [{ debugName: "tooltipWhenEllipsis" }] : /* istanbul ignore next */ []));
171
+ this.luTooltipAnchor = input(this.#host, /* @ts-ignore */
172
+ ...(ngDevMode ? [{ debugName: "luTooltipAnchor" }] : /* istanbul ignore next */ []));
173
+ this.prTooltipAnchor = input(this.#host, /* @ts-ignore */
174
+ ...(ngDevMode ? [{ debugName: "prTooltipAnchor" }] : /* istanbul ignore next */ []));
175
+ this.tooltipAnchor = computed(() => this.prTooltipAnchor() || this.luTooltipAnchor(), /* @ts-ignore */
176
+ ...(ngDevMode ? [{ debugName: "tooltipAnchor" }] : /* istanbul ignore next */ []));
177
+ this.id = input(`${this.#host.nativeElement.tagName.toLowerCase()}-tooltip-${nextId++}`, /* @ts-ignore */
178
+ ...(ngDevMode ? [{ debugName: "id" }] : /* istanbul ignore next */ []));
179
+ this.ariaDescribedBy = computed(() => {
180
+ if (this.tooltipDisabled() || this.tooltipWhenEllipsis() || this.tooltipOnlyForDisplay()) {
181
+ return null;
182
+ }
183
+ return `${this.id()}-panel`;
184
+ }, /* @ts-ignore */
185
+ ...(ngDevMode ? [{ debugName: "ariaDescribedBy" }] : /* istanbul ignore next */ []));
186
+ // 0 until the element first appears; bumped for the initial measurement and on every real
187
+ // size/content change. Scrolling in and out of view does NOT bump it (see #armMeasurementObservers).
188
+ this.#measureTrigger = signal(0, /* @ts-ignore */
189
+ ...(ngDevMode ? [{ debugName: "#measureTrigger" }] : /* istanbul ignore next */ []));
190
+ // guards the one-time setup of the persistent resize/mutation observers
191
+ this.#measurementObserversArmed = false;
192
+ // the IntersectionObserver callback and a debounced 'open' action (see openTooltip) can
193
+ // fire after the view is destroyed; avoid touching the destroyed injector (NG0911) or
194
+ // recreating the overlay when that happens
195
+ this.#destroyed = false;
196
+ // written only from the `read` phase of the afterRenderEffect below
197
+ this.#hasEllipsis = signal(false, /* @ts-ignore */
198
+ ...(ngDevMode ? [{ debugName: "#hasEllipsis" }] : /* istanbul ignore next */ []));
199
+ this.#action = signal(null, /* @ts-ignore */
200
+ ...(ngDevMode ? [{ debugName: "#action" }] : /* istanbul ignore next */ []));
201
+ this.#realAction = linkedSignal({ ...(ngDevMode ? { debugName: "#realAction" } : /* istanbul ignore next */ {}), source: this.#action,
202
+ computation: (action, previous) => {
203
+ if (!action || action === 'close') {
204
+ return action;
205
+ }
206
+ // We only filter open events because even if it's disabled while opened,
207
+ // we want the tooltip to be able to close itself no matter what
208
+ if (this.tooltipDisabled()) {
209
+ return previous?.value ?? null;
210
+ }
211
+ if (this.tooltipWhenEllipsis()) {
212
+ return this.#hasEllipsis() ? 'open' : (previous?.value ?? null);
213
+ }
214
+ return 'open';
215
+ } });
216
+ // pane the focus moved into when it last left this trigger, or null
217
+ this.#focusLeftToPane = null;
218
+ this.#destroyRef.onDestroy(() => (this.#destroyed = true));
219
+ // Action debounce pipeline — kept as Observable since signals can't debounce
220
+ toObservable(this.#realAction)
221
+ .pipe(filter(isNotNil), debounce((action) => timer(action === 'open' ? this.tooltipEnterDelay() : this.tooltipLeaveDelay())), tap((event) => {
222
+ if (event === 'open') {
223
+ this.openTooltip();
224
+ }
225
+ else {
226
+ this.closeTooltip();
227
+ }
228
+ }), takeUntilDestroyed())
229
+ .subscribe();
230
+ effect(() => {
231
+ if (!this.tooltipDisabled() && (!this.tooltipWhenEllipsis() || this.#hasEllipsis())) {
232
+ this.setAccessibilityProperties(0);
233
+ }
234
+ else {
235
+ this.setAccessibilityProperties(null);
236
+ }
237
+ });
238
+ // Defer the first measurement until the element is near the viewport, then stop tracking
239
+ // visibility: scrolling must not re-measure, so we arm the resize/mutation observers once.
240
+ // A single shared IntersectionObserver handles every tooltip (see TooltipVisibilityObserver).
241
+ effect((onCleanup) => {
242
+ if (!this.tooltipWhenEllipsis() || this.tooltipDisabled() || this.#measurementObserversArmed) {
243
+ return;
244
+ }
245
+ const el = this.#host.nativeElement;
246
+ this.#visibilityObserver.observeOnce(el, () => this.#armMeasurementObservers());
247
+ onCleanup(() => this.#visibilityObserver.unobserve(el));
248
+ });
249
+ // Ellipsis measurement, split across afterRenderEffect phases so that — across every tooltip
250
+ // on the page — all DOM writes happen together, then all geometry reads happen together.
251
+ // This keeps the whole batch to a single forced reflow instead of one reflow per element.
252
+ afterRenderEffect({
253
+ earlyRead: () => {
254
+ // reading the trigger registers the dependency; 0 means "not measured yet"
255
+ const measured = this.#measureTrigger() > 0;
256
+ const shouldMeasure = measured && !this.tooltipDisabled() && this.tooltipWhenEllipsis();
257
+ if (!shouldMeasure) {
258
+ return { measure: false };
259
+ }
260
+ const host = this.#host.nativeElement;
261
+ const hostStyle = getComputedStyle(host);
262
+ // No need to run a test if the element is not truncated
263
+ // or if its `display` property is set to `inline`
264
+ // (especially for Safari, which still calculates a width, unlike other browsers)
265
+ if (hostStyle.textOverflow !== 'ellipsis' || hostStyle.display === 'inline') {
266
+ return { measure: false };
267
+ }
268
+ return { measure: true, host, hostStyle };
269
+ },
270
+ write: (earlyReadResult) => {
271
+ const snapshot = earlyReadResult();
272
+ if (!snapshot.measure) {
273
+ return { measure: false };
274
+ }
275
+ const clone = (this.#clone ??= this.#createClone());
276
+ this.#applyClonedStyles(clone, snapshot.hostStyle);
277
+ clone.innerHTML = snapshot.host.innerHTML;
278
+ return { measure: true, host: snapshot.host, clone };
279
+ },
280
+ read: (writeResult) => {
281
+ const measurement = writeResult();
282
+ if (!measurement.measure) {
283
+ this.#hasEllipsis.set(false);
284
+ return;
285
+ }
286
+ // Computed `width`, unlike `getBoundingClientRect`, ignores ancestor CSS transforms
287
+ // (e.g. the popover's scale-in animation), so a mid-animation read can't mistake the
288
+ // host for narrower than it is and get stuck with a false "has ellipsis".
289
+ const cloneWidth = parseFloat(getComputedStyle(measurement.clone).width);
290
+ const hostWidth = parseFloat(getComputedStyle(measurement.host).width);
291
+ // rounded to 3 decimals to ignore sub-pixel noise
292
+ this.#hasEllipsis.set(Math.round(cloneWidth * 1000) > Math.round(hostWidth * 1000));
293
+ },
294
+ });
295
+ this.#destroyRef.onDestroy(() => this.#clone?.remove());
296
+ }
297
+ // Set up — once — the observers that ask for a re-measurement on real size/content changes.
298
+ // They stay connected for the directive lifetime (even off-screen), so scrolling never
299
+ // re-measures; only an actual resize/mutation does.
300
+ #armMeasurementObservers() {
301
+ if (this.#measurementObserversArmed || this.#destroyed) {
302
+ return;
303
+ }
304
+ this.#measurementObserversArmed = true;
305
+ const el = this.#host.nativeElement;
306
+ const bump = () => this.#measureTrigger.update((v) => v + 1);
307
+ const resizeObserver = new ResizeObserver(() => bump());
308
+ resizeObserver.observe(el);
309
+ const mutationObserver = new MutationObserver(() => bump());
310
+ mutationObserver.observe(el, { characterData: true, subtree: true, childList: true });
311
+ this.#destroyRef.onDestroy(() => {
312
+ resizeObserver.disconnect();
313
+ mutationObserver.disconnect();
314
+ });
315
+ // initial measurement now that the element has appeared
316
+ bump();
317
+ }
318
+ #createClone() {
319
+ const clone = this.#document.createElement('div');
320
+ clone.setAttribute('aria-hidden', 'true');
321
+ Object.assign(clone.style, {
322
+ inlineSize: 'fit-content',
323
+ whiteSpace: 'nowrap',
324
+ // `fixed` + pinned origin keeps the (potentially very wide) clone out of the
325
+ // document's scrollable overflow, so measuring never flashes a scrollbar.
326
+ position: 'fixed',
327
+ insetBlockStart: '0',
328
+ insetInlineStart: '0',
329
+ visibility: 'hidden',
330
+ pointerEvents: 'none',
331
+ contain: 'layout',
332
+ });
333
+ this.#document.body.appendChild(clone);
334
+ return clone;
335
+ }
336
+ #applyClonedStyles(clone, hostStyle) {
337
+ const { padding, borderWidth, borderStyle, boxSizing, fontFamily, fontWeight, fontStyle, fontSize } = hostStyle;
338
+ Object.assign(clone.style, { padding, borderWidth, borderStyle, boxSizing, fontFamily, fontWeight, fontStyle, fontSize });
339
+ }
340
+ onMouseEnter() {
341
+ this.#action.set('open');
342
+ }
343
+ onMouseLeave() {
344
+ this.#action.set('close');
345
+ }
346
+ onFocus() {
347
+ const leftToPane = this.#focusLeftToPane;
348
+ this.#focusLeftToPane = null;
349
+ // A closing overlay hands the focus back to its trigger.
350
+ // That is not the user reaching the trigger, so the tooltip stays closed.
351
+ if (this.#isForeignPane(leftToPane)) {
352
+ return;
353
+ }
354
+ if (this.#host.nativeElement.getAttribute('aria-expanded') !== 'true') {
355
+ this.#action.set('open');
356
+ }
357
+ }
358
+ onFocusOut(event) {
359
+ // Captured on the way out.
360
+ // Some overlays dispose their pane before handing the focus back, leaving nothing to inspect.
361
+ this.#focusLeftToPane = event.relatedTarget instanceof Element ? this.#paneOf(event.relatedTarget) : null;
362
+ }
363
+ #isForeignPane(pane) {
364
+ // Panes, not the whole container.
365
+ // A trigger inside an overlay keeps its tooltip when the focus moves within that same overlay.
366
+ return isNotNil(pane) && pane !== this.#paneOf(this.#host.nativeElement);
367
+ }
368
+ #paneOf(element) {
369
+ return element.closest('.cdk-overlay-pane');
370
+ }
371
+ onBlur() {
372
+ this.#action.set('close');
373
+ }
374
+ onEscape(event) {
375
+ event.stopPropagation();
376
+ this.#action.set(null);
377
+ this.closeTooltip();
378
+ }
379
+ requestOpen() {
380
+ this.#action.set('open');
381
+ }
382
+ requestClose() {
383
+ this.#action.set('close');
384
+ }
385
+ ngOnDestroy() {
386
+ this.closeTooltip();
387
+ if (this.overlayRef) {
388
+ this.overlayRef.dispose();
389
+ delete this.overlayRef;
390
+ }
391
+ }
392
+ prepareOverlay() {
393
+ if (this.overlayRef) {
394
+ return;
395
+ }
396
+ this.overlayRef = this.#overlay.create({
397
+ scrollStrategy: this.#overlay.scrollStrategies.close(),
398
+ disposeOnNavigation: true,
399
+ });
400
+ const describedBy = this.ariaDescribedBy();
401
+ if (describedBy !== null) {
402
+ this.overlayRef.overlayElement.id = describedBy;
403
+ }
404
+ }
405
+ openTooltip() {
406
+ // A pending debounced 'open' is flushed when `toObservable(#realAction)` completes on
407
+ // destroy (`debounce` re-emits the held value on completion), i.e. AFTER ngOnDestroy has
408
+ // disposed the overlay. Opening then would recreate an overlay anchored to a detached
409
+ // host — pinned to the viewport's top-left corner — that nothing would ever dispose.
410
+ if (this.#destroyed || this.overlayRef?.hasAttached()) {
411
+ return;
412
+ }
413
+ const position = this.legacyPositionBuilder();
414
+ if (!this.overlayRef) {
415
+ this.overlayRef = this.#overlay.create({
416
+ positionStrategy: position,
417
+ scrollStrategy: this.#overlay.scrollStrategies.close(),
418
+ disposeOnNavigation: true,
419
+ });
420
+ }
421
+ else {
422
+ this.overlayRef.updatePositionStrategy(position);
423
+ }
424
+ const portal = new ComponentPortal(LuTooltipPanelComponent);
425
+ const ref = this.overlayRef.attach(portal);
426
+ position.positionChanges
427
+ .pipe(takeUntilDestroyed(this.#destroyRef), map(({ connectionPair }) => connectionPair), startWith(position.positions[0]))
428
+ .subscribe(({ overlayX, overlayY }) => {
429
+ ref.instance.setPanelPosition(overlayX, overlayY);
430
+ });
431
+ if (this.tooltipContent()) {
432
+ this.#effectRef = _effectWithDeps([this.tooltipContent], (content) => {
433
+ ref.instance.content.set(content);
434
+ }, { injector: this.#injector });
435
+ }
436
+ else if (this.tooltipWhenEllipsis()) {
437
+ ref.instance.content.set(this.#host.nativeElement.innerText);
438
+ }
439
+ else {
440
+ ref.instance.content.set('');
441
+ }
442
+ ref.instance.mouseLeave$.pipe(takeUntilDestroyed(ref.instance.destroyRef)).subscribe(() => this.#action.set('close'));
443
+ ref.instance.mouseEnter$.pipe(takeUntilDestroyed(ref.instance.destroyRef)).subscribe(() => this.#action.set('open'));
444
+ }
445
+ closeTooltip() {
446
+ if (this.overlayRef) {
447
+ this.overlayRef.detach();
448
+ }
449
+ this.#effectRef?.destroy();
450
+ }
451
+ setAccessibilityProperties(tabindex) {
452
+ if (tabindex === null) {
453
+ this.#renderer.removeAttribute(this.#host.nativeElement, 'tabindex');
454
+ return;
455
+ }
456
+ if (!this.tooltipWhenEllipsis() && !this.tooltipOnlyForDisplay()) {
457
+ this.prepareOverlay();
458
+ }
459
+ const tag = this.#host.nativeElement.tagName.toLowerCase();
460
+ const nativelyFocusableTags = ['a', 'button', 'input', 'select', 'textarea'];
461
+ const isNativelyFocusableTag = nativelyFocusableTags.includes(tag);
462
+ const hasATabIndex = this.#host.nativeElement.getAttribute('tabindex') !== null;
463
+ if (!isNativelyFocusableTag && !hasATabIndex) {
464
+ this.#renderer.setAttribute(this.#host.nativeElement, 'tabindex', tabindex.toString());
465
+ }
466
+ if (!isNativelyFocusableTag && !this.tooltipWhenEllipsis() && !this.tooltipOnlyForDisplay()) {
467
+ this.#renderer.setAttribute(this.#host.nativeElement, 'role', 'button');
468
+ }
469
+ }
470
+ // Legacy position builder to handle existing position API
471
+ legacyPositionBuilder() {
472
+ const connectionPosition = {
473
+ originX: 'start',
474
+ originY: 'top',
475
+ };
476
+ // Position
477
+ const position = this.tooltipPosition();
478
+ if (position === 'above') {
479
+ connectionPosition.originY = 'top';
480
+ }
481
+ else if (position === 'below') {
482
+ connectionPosition.originY = 'bottom';
483
+ }
484
+ else if (position === 'before') {
485
+ connectionPosition.originX = 'start';
486
+ }
487
+ else if (position === 'after') {
488
+ connectionPosition.originX = 'end';
489
+ }
490
+ // Alignment
491
+ if (position === 'above' || position === 'below') {
492
+ connectionPosition.originX = 'center';
493
+ }
494
+ else {
495
+ connectionPosition.originY = 'center';
496
+ }
497
+ const overlayPosition = {
498
+ overlayX: 'start',
499
+ overlayY: 'top',
500
+ };
501
+ if (position === 'above' || position === 'below') {
502
+ overlayPosition.overlayX = connectionPosition.originX;
503
+ overlayPosition.overlayY = position === 'above' ? 'bottom' : 'top';
504
+ }
505
+ else {
506
+ overlayPosition.overlayX = position === 'before' ? 'end' : 'start';
507
+ overlayPosition.overlayY = connectionPosition.originY;
508
+ }
509
+ return this.#overlay
510
+ .position()
511
+ .flexibleConnectedTo(this.#resolveAnchor())
512
+ .withViewportMargin(getPushPanelViewportMargin(this.#host.nativeElement))
513
+ .withPositions([
514
+ {
515
+ originX: connectionPosition.originX,
516
+ originY: connectionPosition.originY,
517
+ overlayX: overlayPosition.overlayX,
518
+ overlayY: overlayPosition.overlayY,
519
+ },
520
+ {
521
+ originX: connectionPosition.originX,
522
+ originY: this.invertVerticalPos(connectionPosition.originY),
523
+ overlayX: overlayPosition.overlayX,
524
+ overlayY: this.invertVerticalPos(overlayPosition.overlayY),
525
+ },
526
+ {
527
+ originX: this.invertHorizontalPos(connectionPosition.originX),
528
+ originY: connectionPosition.originY,
529
+ overlayX: this.invertHorizontalPos(overlayPosition.overlayX),
530
+ overlayY: overlayPosition.overlayY,
531
+ },
532
+ {
533
+ originX: this.invertHorizontalPos(connectionPosition.originX),
534
+ originY: this.invertVerticalPos(connectionPosition.originY),
535
+ overlayX: this.invertHorizontalPos(overlayPosition.overlayX),
536
+ overlayY: this.invertVerticalPos(overlayPosition.overlayY),
537
+ },
538
+ ]);
539
+ }
540
+ #resolveAnchor() {
541
+ const anchor = this.tooltipAnchor();
542
+ if (isNil(anchor)) {
543
+ return this.#host;
544
+ }
545
+ else if ('getElementRef' in anchor) {
546
+ return anchor.getElementRef();
547
+ }
548
+ else {
549
+ return anchor;
550
+ }
551
+ }
552
+ invertVerticalPos(y) {
553
+ if (y === 'top') {
554
+ return 'bottom';
555
+ }
556
+ else if (y === 'bottom') {
557
+ return 'top';
558
+ }
559
+ return y;
560
+ }
561
+ invertHorizontalPos(x) {
562
+ if (x === 'end') {
563
+ return 'start';
564
+ }
565
+ else if (x === 'start') {
566
+ return 'end';
567
+ }
568
+ return x;
569
+ }
570
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: LuTooltipTriggerDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
571
+ static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "22.0.8", type: LuTooltipTriggerDirective, isStandalone: true, selector: "[luTooltip],[prTooltip]", inputs: { luTooltipInput: { classPropertyName: "luTooltipInput", publicName: "luTooltip", isSignal: true, isRequired: false, transformFunction: null }, prTooltipInput: { classPropertyName: "prTooltipInput", publicName: "prTooltip", isSignal: true, isRequired: false, transformFunction: null }, luTooltipEnterDelay: { classPropertyName: "luTooltipEnterDelay", publicName: "luTooltipEnterDelay", isSignal: true, isRequired: false, transformFunction: null }, prTooltipEnterDelay: { classPropertyName: "prTooltipEnterDelay", publicName: "prTooltipEnterDelay", isSignal: true, isRequired: false, transformFunction: null }, luTooltipLeaveDelay: { classPropertyName: "luTooltipLeaveDelay", publicName: "luTooltipLeaveDelay", isSignal: true, isRequired: false, transformFunction: null }, prTooltipLeaveDelay: { classPropertyName: "prTooltipLeaveDelay", publicName: "prTooltipLeaveDelay", isSignal: true, isRequired: false, transformFunction: null }, luTooltipDisabled: { classPropertyName: "luTooltipDisabled", publicName: "luTooltipDisabled", isSignal: true, isRequired: false, transformFunction: null }, prTooltipDisabled: { classPropertyName: "prTooltipDisabled", publicName: "prTooltipDisabled", isSignal: true, isRequired: false, transformFunction: null }, luTooltipOnlyForDisplay: { classPropertyName: "luTooltipOnlyForDisplay", publicName: "luTooltipOnlyForDisplay", isSignal: true, isRequired: false, transformFunction: null }, prTooltipOnlyForDisplay: { classPropertyName: "prTooltipOnlyForDisplay", publicName: "prTooltipOnlyForDisplay", isSignal: true, isRequired: false, transformFunction: null }, luTooltipPosition: { classPropertyName: "luTooltipPosition", publicName: "luTooltipPosition", isSignal: true, isRequired: false, transformFunction: null }, prTooltipPosition: { classPropertyName: "prTooltipPosition", publicName: "prTooltipPosition", isSignal: true, isRequired: false, transformFunction: null }, luTooltipWhenEllipsisInput: { classPropertyName: "luTooltipWhenEllipsisInput", publicName: "luTooltipWhenEllipsis", isSignal: true, isRequired: false, transformFunction: null }, prTooltipWhenEllipsisInput: { classPropertyName: "prTooltipWhenEllipsisInput", publicName: "prTooltipWhenEllipsis", isSignal: true, isRequired: false, transformFunction: null }, luTooltipAnchor: { classPropertyName: "luTooltipAnchor", publicName: "luTooltipAnchor", isSignal: true, isRequired: false, transformFunction: null }, prTooltipAnchor: { classPropertyName: "prTooltipAnchor", publicName: "prTooltipAnchor", isSignal: true, isRequired: false, transformFunction: null }, id: { classPropertyName: "id", publicName: "id", isSignal: true, isRequired: false, transformFunction: null } }, host: { listeners: { "mouseenter": "onMouseEnter()", "mouseleave": "onMouseLeave()", "focus": "onFocus()", "focusout": "onFocusOut($event)", "blur": "onBlur()", "keydown.escape": "onEscape($event)" }, properties: { "attr.aria-describedby": "ariaDescribedBy()", "attr.id": "id()", "class.is-whenEllipsis": "luTooltipWhenEllipsis()" }, classAttribute: "tooltip_trigger" }, exportAs: ["luTooltip"], ngImport: i0 }); }
572
+ }
573
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: LuTooltipTriggerDirective, decorators: [{
574
+ type: Directive,
575
+ args: [{
576
+ selector: '[luTooltip],[prTooltip]',
577
+ exportAs: 'luTooltip',
578
+ host: {
579
+ '[attr.aria-describedby]': 'ariaDescribedBy()',
580
+ '[attr.id]': 'id()',
581
+ '(mouseenter)': 'onMouseEnter()',
582
+ '(mouseleave)': 'onMouseLeave()',
583
+ '(focus)': 'onFocus()',
584
+ '(focusout)': 'onFocusOut($event)',
585
+ '(blur)': 'onBlur()',
586
+ '(keydown.escape)': 'onEscape($event)',
587
+ class: 'tooltip_trigger',
588
+ '[class.is-whenEllipsis]': 'luTooltipWhenEllipsis()',
589
+ },
590
+ }]
591
+ }], ctorParameters: () => [], propDecorators: { luTooltipInput: [{ type: i0.Input, args: [{ isSignal: true, alias: "luTooltip", required: false }] }], prTooltipInput: [{ type: i0.Input, args: [{ isSignal: true, alias: "prTooltip", required: false }] }], luTooltipEnterDelay: [{ type: i0.Input, args: [{ isSignal: true, alias: "luTooltipEnterDelay", required: false }] }], prTooltipEnterDelay: [{ type: i0.Input, args: [{ isSignal: true, alias: "prTooltipEnterDelay", required: false }] }], luTooltipLeaveDelay: [{ type: i0.Input, args: [{ isSignal: true, alias: "luTooltipLeaveDelay", required: false }] }], prTooltipLeaveDelay: [{ type: i0.Input, args: [{ isSignal: true, alias: "prTooltipLeaveDelay", required: false }] }], luTooltipDisabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "luTooltipDisabled", required: false }] }], prTooltipDisabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "prTooltipDisabled", required: false }] }], luTooltipOnlyForDisplay: [{ type: i0.Input, args: [{ isSignal: true, alias: "luTooltipOnlyForDisplay", required: false }] }], prTooltipOnlyForDisplay: [{ type: i0.Input, args: [{ isSignal: true, alias: "prTooltipOnlyForDisplay", required: false }] }], luTooltipPosition: [{ type: i0.Input, args: [{ isSignal: true, alias: "luTooltipPosition", required: false }] }], prTooltipPosition: [{ type: i0.Input, args: [{ isSignal: true, alias: "prTooltipPosition", required: false }] }], luTooltipWhenEllipsisInput: [{ type: i0.Input, args: [{ isSignal: true, alias: "luTooltipWhenEllipsis", required: false }] }], prTooltipWhenEllipsisInput: [{ type: i0.Input, args: [{ isSignal: true, alias: "prTooltipWhenEllipsis", required: false }] }], luTooltipAnchor: [{ type: i0.Input, args: [{ isSignal: true, alias: "luTooltipAnchor", required: false }] }], prTooltipAnchor: [{ type: i0.Input, args: [{ isSignal: true, alias: "prTooltipAnchor", required: false }] }], id: [{ type: i0.Input, args: [{ isSignal: true, alias: "id", required: false }] }] } });
592
+
593
+ /**
594
+ * @deprecated use `LuTooltipTriggerDirective` instead
595
+ */
596
+ class LuTooltipTriggerModule {
597
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: LuTooltipTriggerModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); }
598
+ static { this.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "22.0.8", ngImport: i0, type: LuTooltipTriggerModule, imports: [LuTooltipTriggerDirective, OverlayModule], exports: [LuTooltipTriggerDirective] }); }
599
+ static { this.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: LuTooltipTriggerModule, imports: [OverlayModule] }); }
600
+ }
601
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: LuTooltipTriggerModule, decorators: [{
602
+ type: NgModule,
603
+ args: [{
604
+ imports: [LuTooltipTriggerDirective, OverlayModule],
605
+ exports: [LuTooltipTriggerDirective],
606
+ }]
607
+ }] });
608
+
609
+ /**
610
+ * @deprecated use `LuTooltipTriggerDirective, LuTooltipPanelComponent` instead
611
+ */
612
+ class LuTooltipModule {
613
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: LuTooltipModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); }
614
+ static { this.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "22.0.8", ngImport: i0, type: LuTooltipModule, imports: [LuTooltipTriggerModule, LuTooltipPanelComponent], exports: [LuTooltipTriggerModule, LuTooltipPanelComponent] }); }
615
+ static { this.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: LuTooltipModule, imports: [LuTooltipTriggerModule, LuTooltipTriggerModule] }); }
616
+ }
617
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: LuTooltipModule, decorators: [{
618
+ type: NgModule,
619
+ args: [{
620
+ imports: [LuTooltipTriggerModule, LuTooltipPanelComponent],
621
+ exports: [LuTooltipTriggerModule, LuTooltipPanelComponent],
622
+ }]
623
+ }] });
624
+
625
+ /**
626
+ * Generated bundle index. Do not edit.
627
+ */
628
+
629
+ export { LuTooltipModule, LuTooltipPanelComponent, LuTooltipTriggerDirective, LuTooltipTriggerModule, luTransformTooltip };
630
+ //# sourceMappingURL=lucca-prisme-tooltip.mjs.map