@lucca/prisme 22.1.0-rc.1 → 22.1.0-rc.2

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.
@@ -31,6 +31,8 @@ class LuTooltipPanelComponent {
31
31
  this.mouseLeave$ = new Subject();
32
32
  this.content = signal(null, /* @ts-ignore */
33
33
  ...(ngDevMode ? [{ debugName: "content" }] : /* istanbul ignore next */ []));
34
+ this.enterDelay = signal(300, /* @ts-ignore */
35
+ ...(ngDevMode ? [{ debugName: "enterDelay" }] : /* istanbul ignore next */ []));
34
36
  this.contentPositionClasses = signal({}, /* @ts-ignore */
35
37
  ...(ngDevMode ? [{ debugName: "contentPositionClasses" }] : /* istanbul ignore next */ []));
36
38
  }
@@ -43,7 +45,7 @@ class LuTooltipPanelComponent {
43
45
  });
44
46
  }
45
47
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type: LuTooltipPanelComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
46
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.4", 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 }); }
48
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.4", 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\n\t\tclass=\"tooltip\"\n\t\t[class]=\"contentPositionClasses()\"\n\t\t[innerHtml]=\"content\"\n\t\t[style.--components-tooltip-animationDelay]=\"enterDelay() + 'ms'\"\n\t></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;--components-tooltip-animationDelay: .3s;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,tooltipPointerEventsIn;animation-delay:var(--components-tooltip-animationDelay);animation-duration:var(--commons-animations-durations-fast);animation-iteration-count:1;animation-fill-mode:backwards;overflow-wrap:break-word}@keyframes scaleIn{0%{transform:scale(0)}to{transform:scale(1)}}@keyframes tooltipPointerEventsIn{0%{pointer-events:none}1%{pointer-events:auto}}.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
49
  }
48
50
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type: LuTooltipPanelComponent, decorators: [{
49
51
  type: Component,
@@ -51,7 +53,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.4", ngImpor
51
53
  role: 'tooltip',
52
54
  '(mouseenter)': 'mouseEnter$.next()',
53
55
  '(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"] }]
56
+ }, changeDetection: ChangeDetectionStrategy.OnPush, template: "@if (content(); as content) {\n\t<div\n\t\tclass=\"tooltip\"\n\t\t[class]=\"contentPositionClasses()\"\n\t\t[innerHtml]=\"content\"\n\t\t[style.--components-tooltip-animationDelay]=\"enterDelay() + 'ms'\"\n\t></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;--components-tooltip-animationDelay: .3s;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,tooltipPointerEventsIn;animation-delay:var(--components-tooltip-animationDelay);animation-duration:var(--commons-animations-durations-fast);animation-iteration-count:1;animation-fill-mode:backwards;overflow-wrap:break-word}@keyframes scaleIn{0%{transform:scale(0)}to{transform:scale(1)}}@keyframes tooltipPointerEventsIn{0%{pointer-events:none}1%{pointer-events:auto}}.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
57
  }] });
56
58
 
57
59
  /**
@@ -117,6 +119,14 @@ class LuTooltipTriggerDirective {
117
119
  #hasEllipsis;
118
120
  // reusable hidden clone, one per directive, used to measure the unconstrained width
119
121
  #clone;
122
+ // Each measurement phase hands over to the next one through these signals rather than through
123
+ // the value `afterRenderEffect` pipes between phases. A phase only receives that value when the
124
+ // phases before it ran in the same pass; a directive created while the after-render hooks are
125
+ // running starts mid-pipeline and gets Angular's `registerCleanupFn` as its first argument
126
+ // instead. Calling it then registers `undefined` as a cleanup function, which throws
127
+ // `fn is not a function` when the view is destroyed, far away from the cause.
128
+ #measurementRequest;
129
+ #preparedMeasurement;
120
130
  #action;
121
131
  #realAction;
122
132
  #effectRef;
@@ -138,13 +148,13 @@ class LuTooltipTriggerDirective {
138
148
  ...(ngDevMode ? [{ debugName: "prTooltip" }] : /* istanbul ignore next */ []));
139
149
  this.tooltipContent = computed(() => this.luTooltip() || this.prTooltip(), /* @ts-ignore */
140
150
  ...(ngDevMode ? [{ debugName: "tooltipContent" }] : /* istanbul ignore next */ []));
141
- this.luTooltipEnterDelay = input(300, { ...(ngDevMode ? { debugName: "luTooltipEnterDelay" } : /* istanbul ignore next */ {}), transform: numberAttribute });
151
+ this.luTooltipEnterDelay = input(0, { ...(ngDevMode ? { debugName: "luTooltipEnterDelay" } : /* istanbul ignore next */ {}), transform: numberAttribute });
142
152
  this.prTooltipEnterDelay = input(300, { ...(ngDevMode ? { debugName: "prTooltipEnterDelay" } : /* istanbul ignore next */ {}), transform: numberAttribute });
143
- this.tooltipEnterDelay = computed(() => this.prTooltipEnterDelay() || this.luTooltipEnterDelay(), /* @ts-ignore */
153
+ this.tooltipEnterDelay = computed(() => this.luTooltipEnterDelay() || this.prTooltipEnterDelay(), /* @ts-ignore */
144
154
  ...(ngDevMode ? [{ debugName: "tooltipEnterDelay" }] : /* istanbul ignore next */ []));
145
- this.luTooltipLeaveDelay = input(100, { ...(ngDevMode ? { debugName: "luTooltipLeaveDelay" } : /* istanbul ignore next */ {}), transform: numberAttribute });
155
+ this.luTooltipLeaveDelay = input(0, { ...(ngDevMode ? { debugName: "luTooltipLeaveDelay" } : /* istanbul ignore next */ {}), transform: numberAttribute });
146
156
  this.prTooltipLeaveDelay = input(100, { ...(ngDevMode ? { debugName: "prTooltipLeaveDelay" } : /* istanbul ignore next */ {}), transform: numberAttribute });
147
- this.tooltipLeaveDelay = computed(() => this.prTooltipLeaveDelay() || this.luTooltipLeaveDelay(), /* @ts-ignore */
157
+ this.tooltipLeaveDelay = computed(() => this.luTooltipLeaveDelay() || this.prTooltipLeaveDelay(), /* @ts-ignore */
148
158
  ...(ngDevMode ? [{ debugName: "tooltipLeaveDelay" }] : /* istanbul ignore next */ []));
149
159
  this.luTooltipDisabled = input(false, { ...(ngDevMode ? { debugName: "luTooltipDisabled" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
150
160
  this.prTooltipDisabled = input(false, { ...(ngDevMode ? { debugName: "prTooltipDisabled" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
@@ -154,11 +164,11 @@ class LuTooltipTriggerDirective {
154
164
  this.prTooltipOnlyForDisplay = input(false, { ...(ngDevMode ? { debugName: "prTooltipOnlyForDisplay" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
155
165
  this.tooltipOnlyForDisplay = computed(() => this.prTooltipOnlyForDisplay() || this.luTooltipOnlyForDisplay(), /* @ts-ignore */
156
166
  ...(ngDevMode ? [{ debugName: "tooltipOnlyForDisplay" }] : /* istanbul ignore next */ []));
157
- this.luTooltipPosition = input('above', /* @ts-ignore */
167
+ this.luTooltipPosition = input(null, /* @ts-ignore */
158
168
  ...(ngDevMode ? [{ debugName: "luTooltipPosition" }] : /* istanbul ignore next */ []));
159
169
  this.prTooltipPosition = input('above', /* @ts-ignore */
160
170
  ...(ngDevMode ? [{ debugName: "prTooltipPosition" }] : /* istanbul ignore next */ []));
161
- this.tooltipPosition = computed(() => this.prTooltipPosition() || this.luTooltipPosition(), /* @ts-ignore */
171
+ this.tooltipPosition = computed(() => this.luTooltipPosition() ?? this.prTooltipPosition(), /* @ts-ignore */
162
172
  ...(ngDevMode ? [{ debugName: "tooltipPosition" }] : /* istanbul ignore next */ []));
163
173
  this.luTooltipWhenEllipsisInput = input(false, { ...(ngDevMode ? { debugName: "luTooltipWhenEllipsisInput" } : /* istanbul ignore next */ {}), alias: 'luTooltipWhenEllipsis', transform: booleanAttribute });
164
174
  this.prTooltipWhenEllipsisInput = input(false, { ...(ngDevMode ? { debugName: "prTooltipWhenEllipsisInput" } : /* istanbul ignore next */ {}), alias: 'prTooltipWhenEllipsis', transform: booleanAttribute });
@@ -168,12 +178,18 @@ class LuTooltipTriggerDirective {
168
178
  ...(ngDevMode ? [{ debugName: "prTooltipWhenEllipsis" }] : /* istanbul ignore next */ []));
169
179
  this.tooltipWhenEllipsis = computed(() => this.prTooltipWhenEllipsis() || this.luTooltipWhenEllipsis(), /* @ts-ignore */
170
180
  ...(ngDevMode ? [{ debugName: "tooltipWhenEllipsis" }] : /* istanbul ignore next */ []));
171
- this.luTooltipAnchor = input(this.#host, /* @ts-ignore */
181
+ this.luTooltipAnchor = input(null, /* @ts-ignore */
172
182
  ...(ngDevMode ? [{ debugName: "luTooltipAnchor" }] : /* istanbul ignore next */ []));
173
183
  this.prTooltipAnchor = input(this.#host, /* @ts-ignore */
174
184
  ...(ngDevMode ? [{ debugName: "prTooltipAnchor" }] : /* istanbul ignore next */ []));
175
- this.tooltipAnchor = computed(() => this.prTooltipAnchor() || this.luTooltipAnchor(), /* @ts-ignore */
185
+ this.tooltipAnchor = computed(() => this.luTooltipAnchor() || this.prTooltipAnchor(), /* @ts-ignore */
176
186
  ...(ngDevMode ? [{ debugName: "tooltipAnchor" }] : /* istanbul ignore next */ []));
187
+ this.luTooltipTriggerAnchor = input(null, /* @ts-ignore */
188
+ ...(ngDevMode ? [{ debugName: "luTooltipTriggerAnchor" }] : /* istanbul ignore next */ []));
189
+ this.prTooltipTriggerAnchor = input(null, /* @ts-ignore */
190
+ ...(ngDevMode ? [{ debugName: "prTooltipTriggerAnchor" }] : /* istanbul ignore next */ []));
191
+ this.tooltipTriggerAnchor = computed(() => this.luTooltipTriggerAnchor() || this.prTooltipTriggerAnchor(), /* @ts-ignore */
192
+ ...(ngDevMode ? [{ debugName: "tooltipTriggerAnchor" }] : /* istanbul ignore next */ []));
177
193
  this.id = input(`${this.#host.nativeElement.tagName.toLowerCase()}-tooltip-${nextId++}`, /* @ts-ignore */
178
194
  ...(ngDevMode ? [{ debugName: "id" }] : /* istanbul ignore next */ []));
179
195
  this.ariaDescribedBy = computed(() => {
@@ -196,6 +212,16 @@ class LuTooltipTriggerDirective {
196
212
  // written only from the `read` phase of the afterRenderEffect below
197
213
  this.#hasEllipsis = signal(false, /* @ts-ignore */
198
214
  ...(ngDevMode ? [{ debugName: "#hasEllipsis" }] : /* istanbul ignore next */ []));
215
+ // Each measurement phase hands over to the next one through these signals rather than through
216
+ // the value `afterRenderEffect` pipes between phases. A phase only receives that value when the
217
+ // phases before it ran in the same pass; a directive created while the after-render hooks are
218
+ // running starts mid-pipeline and gets Angular's `registerCleanupFn` as its first argument
219
+ // instead. Calling it then registers `undefined` as a cleanup function, which throws
220
+ // `fn is not a function` when the view is destroyed, far away from the cause.
221
+ this.#measurementRequest = signal(null, /* @ts-ignore */
222
+ ...(ngDevMode ? [{ debugName: "#measurementRequest" }] : /* istanbul ignore next */ []));
223
+ this.#preparedMeasurement = signal(null, /* @ts-ignore */
224
+ ...(ngDevMode ? [{ debugName: "#preparedMeasurement" }] : /* istanbul ignore next */ []));
199
225
  this.#action = signal(null, /* @ts-ignore */
200
226
  ...(ngDevMode ? [{ debugName: "#action" }] : /* istanbul ignore next */ []));
201
227
  this.#realAction = linkedSignal({ ...(ngDevMode ? { debugName: "#realAction" } : /* istanbul ignore next */ {}), source: this.#action,
@@ -216,11 +242,11 @@ class LuTooltipTriggerDirective {
216
242
  // pane the focus moved into when it last left this trigger, or null
217
243
  this.#focusLeftToPane = null;
218
244
  this.#destroyRef.onDestroy(() => (this.#destroyed = true));
219
- // Action debounce pipeline — kept as Observable since signals can't debounce
245
+ // content attach/detach — immediate on open, VoiceOver/Safari misses a late aria-describedby
220
246
  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();
247
+ .pipe(filter(isNotNil), debounce((action) => timer(action === 'open' ? 0 : this.tooltipLeaveDelay())), tap((action) => {
248
+ if (action === 'open') {
249
+ this.attachTooltip();
224
250
  }
225
251
  else {
226
252
  this.closeTooltip();
@@ -235,6 +261,22 @@ class LuTooltipTriggerDirective {
235
261
  this.setAccessibilityProperties(null);
236
262
  }
237
263
  });
264
+ effect((onCleanup) => {
265
+ const trigger = this.#resolveTriggerAnchorElement() ?? this.#host.nativeElement;
266
+ const unlisten = [
267
+ this.#renderer.listen(trigger, 'mouseenter', () => this.onMouseEnter()),
268
+ this.#renderer.listen(trigger, 'mouseleave', () => this.onMouseLeave()),
269
+ this.#renderer.listen(trigger, 'focus', () => this.onFocus()),
270
+ this.#renderer.listen(trigger, 'blur', () => this.onBlur()),
271
+ this.#renderer.listen(trigger, 'focusout', (event) => this.onFocusOut(event)),
272
+ this.#renderer.listen(trigger, 'keydown', (event) => {
273
+ if (event.key === 'Escape') {
274
+ this.onEscape(event);
275
+ }
276
+ }),
277
+ ];
278
+ onCleanup(() => unlisten.forEach((fn) => fn()));
279
+ });
238
280
  // Defer the first measurement until the element is near the viewport, then stop tracking
239
281
  // visibility: scrolling must not re-measure, so we arm the resize/mutation observers once.
240
282
  // A single shared IntersectionObserver handles every tooltip (see TooltipVisibilityObserver).
@@ -251,35 +293,27 @@ class LuTooltipTriggerDirective {
251
293
  // This keeps the whole batch to a single forced reflow instead of one reflow per element.
252
294
  afterRenderEffect({
253
295
  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 };
296
+ this.#measurementRequest.set(this.#readMeasurementRequest());
269
297
  },
270
- write: (earlyReadResult) => {
271
- const snapshot = earlyReadResult();
272
- if (!snapshot.measure) {
273
- return { measure: false };
298
+ write: () => {
299
+ const request = this.#measurementRequest();
300
+ if (!request) {
301
+ this.#preparedMeasurement.set(null);
302
+ return;
274
303
  }
275
304
  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 };
305
+ Object.assign(clone.style, request.cloneStyles);
306
+ const html = request.host.innerHTML;
307
+ // Writing the same markup back would invalidate layout for nothing, and every tooltip
308
+ // measured after this one would then pay for a new layout on its first geometry read.
309
+ if (clone.innerHTML !== html) {
310
+ clone.innerHTML = html;
311
+ }
312
+ this.#preparedMeasurement.set({ host: request.host, clone });
279
313
  },
280
- read: (writeResult) => {
281
- const measurement = writeResult();
282
- if (!measurement.measure) {
314
+ read: () => {
315
+ const measurement = this.#preparedMeasurement();
316
+ if (!measurement) {
283
317
  this.#hasEllipsis.set(false);
284
318
  return;
285
319
  }
@@ -304,6 +338,8 @@ class LuTooltipTriggerDirective {
304
338
  this.#measurementObserversArmed = true;
305
339
  const el = this.#host.nativeElement;
306
340
  const bump = () => this.#measureTrigger.update((v) => v + 1);
341
+ // `observe` always delivers an initial notification, which doubles as the first measurement:
342
+ // asking for one here as well would measure every tooltip on the page twice.
307
343
  const resizeObserver = new ResizeObserver(() => bump());
308
344
  resizeObserver.observe(el);
309
345
  const mutationObserver = new MutationObserver(() => bump());
@@ -312,8 +348,26 @@ class LuTooltipTriggerDirective {
312
348
  resizeObserver.disconnect();
313
349
  mutationObserver.disconnect();
314
350
  });
315
- // initial measurement now that the element has appeared
316
- bump();
351
+ }
352
+ // Reads everything the measurement needs from the host, in the read-only phase.
353
+ #readMeasurementRequest() {
354
+ // reading the trigger registers the dependency; 0 means "not measured yet"
355
+ const measured = this.#measureTrigger() > 0;
356
+ if (!measured || this.tooltipDisabled() || !this.tooltipWhenEllipsis()) {
357
+ return null;
358
+ }
359
+ const host = this.#host.nativeElement;
360
+ // A `CSSStyleDeclaration` is live: reading one property from it in the write phase, after
361
+ // another tooltip has already mutated the DOM, forces a full style recalculation — once per
362
+ // tooltip on the page. Every value is therefore copied out here.
363
+ const { textOverflow, display, padding, borderWidth, borderStyle, boxSizing, fontFamily, fontWeight, fontStyle, fontSize } = getComputedStyle(host);
364
+ // No need to run a test if the element is not truncated
365
+ // or if its `display` property is set to `inline`
366
+ // (especially for Safari, which still calculates a width, unlike other browsers)
367
+ if (textOverflow !== 'ellipsis' || display === 'inline') {
368
+ return null;
369
+ }
370
+ return { host, cloneStyles: { padding, borderWidth, borderStyle, boxSizing, fontFamily, fontWeight, fontStyle, fontSize } };
317
371
  }
318
372
  #createClone() {
319
373
  const clone = this.#document.createElement('div');
@@ -333,10 +387,6 @@ class LuTooltipTriggerDirective {
333
387
  this.#document.body.appendChild(clone);
334
388
  return clone;
335
389
  }
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
390
  onMouseEnter() {
341
391
  this.#action.set('open');
342
392
  }
@@ -391,18 +441,35 @@ class LuTooltipTriggerDirective {
391
441
  }
392
442
  prepareOverlay() {
393
443
  if (this.overlayRef) {
394
- return;
444
+ return this.overlayRef;
395
445
  }
396
- this.overlayRef = this.#overlay.create({
446
+ const overlayRef = this.#overlay.create({
397
447
  scrollStrategy: this.#overlay.scrollStrategies.close(),
398
448
  disposeOnNavigation: true,
399
449
  });
450
+ // `disposeOnNavigation` disposes the overlay on a history navigation (browser back/forward)
451
+ // while this directive may live on: forget it then, so the next opening creates a new one
452
+ // instead of attaching to a disposed overlay (`attach()` returns null).
453
+ overlayRef
454
+ .detachments()
455
+ .pipe(takeUntilDestroyed(this.#destroyRef))
456
+ .subscribe({
457
+ complete: () => {
458
+ if (this.overlayRef === overlayRef) {
459
+ delete this.overlayRef;
460
+ // the content effect writes to the disposed panel: a reopening would overwrite it without destroying it
461
+ this.#effectRef?.destroy();
462
+ }
463
+ },
464
+ });
400
465
  const describedBy = this.ariaDescribedBy();
401
466
  if (describedBy !== null) {
402
- this.overlayRef.overlayElement.id = describedBy;
467
+ overlayRef.overlayElement.id = describedBy;
403
468
  }
469
+ this.overlayRef = overlayRef;
470
+ return overlayRef;
404
471
  }
405
- openTooltip() {
472
+ attachTooltip() {
406
473
  // A pending debounced 'open' is flushed when `toObservable(#realAction)` completes on
407
474
  // destroy (`debounce` re-emits the held value on completion), i.e. AFTER ngOnDestroy has
408
475
  // disposed the overlay. Opening then would recreate an overlay anchored to a detached
@@ -411,18 +478,11 @@ class LuTooltipTriggerDirective {
411
478
  return;
412
479
  }
413
480
  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
- }
481
+ const overlayRef = this.prepareOverlay();
482
+ overlayRef.updatePositionStrategy(position);
424
483
  const portal = new ComponentPortal(LuTooltipPanelComponent);
425
- const ref = this.overlayRef.attach(portal);
484
+ const ref = overlayRef.attach(portal);
485
+ ref.instance.enterDelay.set(this.tooltipEnterDelay());
426
486
  position.positionChanges
427
487
  .pipe(takeUntilDestroyed(this.#destroyRef), map(({ connectionPair }) => connectionPair), startWith(position.positions[0]))
428
488
  .subscribe(({ overlayX, overlayY }) => {
@@ -449,6 +509,9 @@ class LuTooltipTriggerDirective {
449
509
  this.#effectRef?.destroy();
450
510
  }
451
511
  setAccessibilityProperties(tabindex) {
512
+ if (this.#resolveTriggerAnchorElement()) {
513
+ return;
514
+ }
452
515
  if (tabindex === null) {
453
516
  this.#renderer.removeAttribute(this.#host.nativeElement, 'tabindex');
454
517
  return;
@@ -549,6 +612,21 @@ class LuTooltipTriggerDirective {
549
612
  return anchor;
550
613
  }
551
614
  }
615
+ #resolveTriggerAnchorElement() {
616
+ const triggerAnchor = this.tooltipTriggerAnchor();
617
+ if (isNil(triggerAnchor)) {
618
+ return null;
619
+ }
620
+ else if (triggerAnchor instanceof HTMLElement) {
621
+ return triggerAnchor;
622
+ }
623
+ else if ('getElementRef' in triggerAnchor) {
624
+ return triggerAnchor.getElementRef().nativeElement;
625
+ }
626
+ else {
627
+ return triggerAnchor.nativeElement;
628
+ }
629
+ }
552
630
  invertVerticalPos(y) {
553
631
  if (y === 'top') {
554
632
  return 'bottom';
@@ -568,7 +646,7 @@ class LuTooltipTriggerDirective {
568
646
  return x;
569
647
  }
570
648
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type: LuTooltipTriggerDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
571
- static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "22.1.4", 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 }); }
649
+ static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "22.1.4", 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 }, luTooltipTriggerAnchor: { classPropertyName: "luTooltipTriggerAnchor", publicName: "luTooltipTriggerAnchor", isSignal: true, isRequired: false, transformFunction: null }, prTooltipTriggerAnchor: { classPropertyName: "prTooltipTriggerAnchor", publicName: "prTooltipTriggerAnchor", isSignal: true, isRequired: false, transformFunction: null }, id: { classPropertyName: "id", publicName: "id", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "attr.aria-describedby": "ariaDescribedBy()", "attr.id": "id()", "class.is-whenEllipsis": "luTooltipWhenEllipsis()" }, classAttribute: "tooltip_trigger" }, exportAs: ["luTooltip"], ngImport: i0 }); }
572
650
  }
573
651
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type: LuTooltipTriggerDirective, decorators: [{
574
652
  type: Directive,
@@ -578,17 +656,11 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.4", ngImpor
578
656
  host: {
579
657
  '[attr.aria-describedby]': 'ariaDescribedBy()',
580
658
  '[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
659
  class: 'tooltip_trigger',
588
660
  '[class.is-whenEllipsis]': 'luTooltipWhenEllipsis()',
589
661
  },
590
662
  }]
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 }] }] } });
663
+ }], 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 }] }], luTooltipTriggerAnchor: [{ type: i0.Input, args: [{ isSignal: true, alias: "luTooltipTriggerAnchor", required: false }] }], prTooltipTriggerAnchor: [{ type: i0.Input, args: [{ isSignal: true, alias: "prTooltipTriggerAnchor", required: false }] }], id: [{ type: i0.Input, args: [{ isSignal: true, alias: "id", required: false }] }] } });
592
664
 
593
665
  /**
594
666
  * @deprecated use `LuTooltipTriggerDirective` instead