@lucca-front/ng 21.1.3 → 21.2.0-rc.3

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.
Files changed (103) hide show
  1. package/fesm2022/lucca-front-ng-a11y.mjs +2 -2
  2. package/fesm2022/lucca-front-ng-a11y.mjs.map +1 -1
  3. package/fesm2022/lucca-front-ng-activity-feed.mjs +117 -0
  4. package/fesm2022/lucca-front-ng-activity-feed.mjs.map +1 -0
  5. package/fesm2022/lucca-front-ng-app-layout.mjs +2 -2
  6. package/fesm2022/lucca-front-ng-app-layout.mjs.map +1 -1
  7. package/fesm2022/lucca-front-ng-breadcrumbs.mjs +2 -2
  8. package/fesm2022/lucca-front-ng-breadcrumbs.mjs.map +1 -1
  9. package/fesm2022/lucca-front-ng-callout.mjs +2 -2
  10. package/fesm2022/lucca-front-ng-callout.mjs.map +1 -1
  11. package/fesm2022/lucca-front-ng-color.mjs +2 -2
  12. package/fesm2022/lucca-front-ng-color.mjs.map +1 -1
  13. package/fesm2022/lucca-front-ng-comment.mjs +12 -11
  14. package/fesm2022/lucca-front-ng-comment.mjs.map +1 -1
  15. package/fesm2022/lucca-front-ng-core-select.mjs +2 -5
  16. package/fesm2022/lucca-front-ng-core-select.mjs.map +1 -1
  17. package/fesm2022/lucca-front-ng-date2.mjs +6 -6
  18. package/fesm2022/lucca-front-ng-date2.mjs.map +1 -1
  19. package/fesm2022/lucca-front-ng-dialog.mjs +13 -7
  20. package/fesm2022/lucca-front-ng-dialog.mjs.map +1 -1
  21. package/fesm2022/lucca-front-ng-divider.mjs +2 -2
  22. package/fesm2022/lucca-front-ng-divider.mjs.map +1 -1
  23. package/fesm2022/lucca-front-ng-dropdown.mjs +4 -4
  24. package/fesm2022/lucca-front-ng-empty-state.mjs +33 -8
  25. package/fesm2022/lucca-front-ng-empty-state.mjs.map +1 -1
  26. package/fesm2022/lucca-front-ng-file-upload.mjs +25 -19
  27. package/fesm2022/lucca-front-ng-file-upload.mjs.map +1 -1
  28. package/fesm2022/lucca-front-ng-filter-pills.mjs +4 -4
  29. package/fesm2022/lucca-front-ng-filter-pills.mjs.map +1 -1
  30. package/fesm2022/lucca-front-ng-footer.mjs +2 -2
  31. package/fesm2022/lucca-front-ng-footer.mjs.map +1 -1
  32. package/fesm2022/lucca-front-ng-form-field.mjs +4 -4
  33. package/fesm2022/lucca-front-ng-form-field.mjs.map +1 -1
  34. package/fesm2022/lucca-front-ng-forms-phone-number-input.mjs +2 -2
  35. package/fesm2022/lucca-front-ng-forms-phone-number-input.mjs.map +1 -1
  36. package/fesm2022/lucca-front-ng-forms-rich-text-input.mjs +67 -166
  37. package/fesm2022/lucca-front-ng-forms-rich-text-input.mjs.map +1 -1
  38. package/fesm2022/lucca-front-ng-forms.mjs +18 -18
  39. package/fesm2022/lucca-front-ng-forms.mjs.map +1 -1
  40. package/fesm2022/lucca-front-ng-gauge.mjs +2 -2
  41. package/fesm2022/lucca-front-ng-gauge.mjs.map +1 -1
  42. package/fesm2022/lucca-front-ng-highlight-data.mjs.map +1 -1
  43. package/fesm2022/lucca-front-ng-horizontal-navigation.mjs +2 -2
  44. package/fesm2022/lucca-front-ng-index-table.mjs +2 -2
  45. package/fesm2022/lucca-front-ng-index-table.mjs.map +1 -1
  46. package/fesm2022/lucca-front-ng-link.mjs +2 -2
  47. package/fesm2022/lucca-front-ng-link.mjs.map +1 -1
  48. package/fesm2022/lucca-front-ng-listing.mjs +13 -8
  49. package/fesm2022/lucca-front-ng-listing.mjs.map +1 -1
  50. package/fesm2022/lucca-front-ng-modal.mjs +1 -1
  51. package/fesm2022/lucca-front-ng-modal.mjs.map +1 -1
  52. package/fesm2022/lucca-front-ng-multi-select.mjs +10 -20
  53. package/fesm2022/lucca-front-ng-multi-select.mjs.map +1 -1
  54. package/fesm2022/lucca-front-ng-page-header.mjs +2 -2
  55. package/fesm2022/lucca-front-ng-page-header.mjs.map +1 -1
  56. package/fesm2022/lucca-front-ng-plg-push.mjs +2 -2
  57. package/fesm2022/lucca-front-ng-plg-push.mjs.map +1 -1
  58. package/fesm2022/lucca-front-ng-popover2.mjs +2 -2
  59. package/fesm2022/lucca-front-ng-popover2.mjs.map +1 -1
  60. package/fesm2022/lucca-front-ng-read-more.mjs +2 -2
  61. package/fesm2022/lucca-front-ng-read-more.mjs.map +1 -1
  62. package/fesm2022/lucca-front-ng-resource-card.mjs +2 -2
  63. package/fesm2022/lucca-front-ng-resource-card.mjs.map +1 -1
  64. package/fesm2022/lucca-front-ng-segmented-control-tabs.mjs +2 -2
  65. package/fesm2022/lucca-front-ng-segmented-control-tabs.mjs.map +1 -1
  66. package/fesm2022/lucca-front-ng-segmented-control.mjs +2 -2
  67. package/fesm2022/lucca-front-ng-segmented-control.mjs.map +1 -1
  68. package/fesm2022/lucca-front-ng-simple-select.mjs +4 -5
  69. package/fesm2022/lucca-front-ng-simple-select.mjs.map +1 -1
  70. package/fesm2022/lucca-front-ng-skeleton.mjs +10 -10
  71. package/fesm2022/lucca-front-ng-skeleton.mjs.map +1 -1
  72. package/fesm2022/lucca-front-ng-time.mjs +4 -4
  73. package/fesm2022/lucca-front-ng-time.mjs.map +1 -1
  74. package/fesm2022/lucca-front-ng-toast.mjs +2 -2
  75. package/fesm2022/lucca-front-ng-toast.mjs.map +1 -1
  76. package/fesm2022/lucca-front-ng-tooltip.mjs +162 -129
  77. package/fesm2022/lucca-front-ng-tooltip.mjs.map +1 -1
  78. package/package.json +18 -14
  79. package/schematics/lib/component-mapper.js +1 -1
  80. package/schematics/lib/css-mapper.js +1 -1
  81. package/schematics/lu-button/index.js +1 -1
  82. package/schematics/lu-container/index.js +1 -1
  83. package/schematics/lu-icon/index.js +1 -1
  84. package/schematics/lu-loading/index.js +1 -1
  85. package/schematics/lu-select/index.js +1 -1
  86. package/schematics/lu-text-input/index.js +1 -1
  87. package/schematics/new-icons/index.js +1 -1
  88. package/src/_definitions.scss +1 -2
  89. package/src/components/cdk/_dragDrop.scss +0 -1
  90. package/src/components/cdk/_global.scss +11 -11
  91. package/src/components/cdk/_misc.scss +1 -2
  92. package/src/components/cdk/_overlay.scss +2 -2
  93. package/src/components/cdk/_textarea.scss +0 -1
  94. package/src/definitions/option/_option-item.scss +4 -2
  95. package/src/definitions/select/_select-input.scss +1 -1
  96. package/types/lucca-front-ng-activity-feed.d.ts +38 -0
  97. package/types/lucca-front-ng-comment.d.ts +2 -1
  98. package/types/lucca-front-ng-dialog.d.ts +4 -3
  99. package/types/lucca-front-ng-empty-state.d.ts +15 -2
  100. package/types/lucca-front-ng-file-upload.d.ts +2 -2
  101. package/types/lucca-front-ng-forms-rich-text-input.d.ts +14 -69
  102. package/types/lucca-front-ng-listing.d.ts +7 -2
  103. package/types/lucca-front-ng-tooltip.d.ts +17 -9
@@ -1,12 +1,12 @@
1
1
  import { trigger, state, style, transition, animate } from '@angular/animations';
2
2
  import * as i0 from '@angular/core';
3
- import { inject, DestroyRef, signal, ChangeDetectionStrategy, Component, Injectable, ElementRef, Renderer2, Injector, input, linkedSignal, numberAttribute, booleanAttribute, computed, effect, Directive, NgModule } from '@angular/core';
4
- import { Subject, switchMap, of, from, timer, startWith } from 'rxjs';
3
+ import { inject, DestroyRef, signal, ChangeDetectionStrategy, Component, Injectable, ElementRef, Renderer2, NgZone, Injector, input, linkedSignal, numberAttribute, booleanAttribute, computed, effect, Directive, NgModule } from '@angular/core';
4
+ import { Subject, timer, shareReplay, filter, firstValueFrom, Observable, combineLatest, switchMap, startWith } from 'rxjs';
5
5
  import { Overlay, OverlayModule } from '@angular/cdk/overlay';
6
6
  import { ComponentPortal } from '@angular/cdk/portal';
7
- import { toSignal, toObservable, takeUntilDestroyed } from '@angular/core/rxjs-interop';
7
+ import { toObservable, toSignal, takeUntilDestroyed } from '@angular/core/rxjs-interop';
8
8
  import { isNotNil, ɵeffectWithDeps as _effectWithDeps } from '@lucca-front/ng/core';
9
- import { debounceTime, filter, debounce, tap, map } from 'rxjs/operators';
9
+ import { debounceTime, filter as filter$1, debounce, tap, map } from 'rxjs/operators';
10
10
  import { DOCUMENT } from '@angular/common';
11
11
 
12
12
  const luTransformTooltip = trigger('transformTooltip', [
@@ -30,6 +30,7 @@ class LuTooltipPanelComponent {
30
30
  this.mouseEnter$ = new Subject();
31
31
  this.mouseLeave$ = new Subject();
32
32
  this.content = signal(null, ...(ngDevMode ? [{ debugName: "content" }] : []));
33
+ this.id = signal(null, ...(ngDevMode ? [{ debugName: "id" }] : []));
33
34
  this.contentPositionClasses = signal({}, ...(ngDevMode ? [{ debugName: "contentPositionClasses" }] : []));
34
35
  }
35
36
  setPanelPosition(posX, posY) {
@@ -41,75 +42,115 @@ class LuTooltipPanelComponent {
41
42
  });
42
43
  }
43
44
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.8", ngImport: i0, type: LuTooltipPanelComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
44
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.8", type: LuTooltipPanelComponent, isStandalone: true, selector: "lu-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){[luTooltipWhenEllipsis]: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 }); }
45
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.8", type: LuTooltipPanelComponent, isStandalone: true, selector: "lu-tooltip-panel", host: { attributes: { "role": "tooltip" }, listeners: { "mouseenter": "mouseEnter$.next()", "mouseleave": "mouseLeave$.next()" }, properties: { "attr.id": "id" } }, 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){[luTooltipWhenEllipsis]: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 }); }
45
46
  }
46
47
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.8", ngImport: i0, type: LuTooltipPanelComponent, decorators: [{
47
48
  type: Component,
48
49
  args: [{ selector: 'lu-tooltip-panel', host: {
49
50
  role: 'tooltip',
51
+ '[attr.id]': 'id',
50
52
  '(mouseenter)': 'mouseEnter$.next()',
51
53
  '(mouseleave)': 'mouseLeave$.next()',
52
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){[luTooltipWhenEllipsis]: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"] }]
53
55
  }] });
54
56
 
55
57
  class EllipsisRuler {
56
- #document = inject(DOCUMENT);
57
- #parentMasked = this.#document.createElement('div');
58
+ // As EllipsisRuler is a singleton, we can use shareReplay so the first subscriber starts the timer and the last one stops it
59
+ // The timer allows us to alternate between read and write phases
60
+ #interval;
61
+ #readPhase;
62
+ #writePhase;
63
+ #document;
58
64
  constructor() {
59
- this.#parentMasked.classList.add('pr-u-mask');
60
- this.#parentMasked.setAttribute('aria-hidden', 'true');
61
- this.#document.body.appendChild(this.#parentMasked);
65
+ // As EllipsisRuler is a singleton, we can use shareReplay so the first subscriber starts the timer and the last one stops it
66
+ // The timer allows us to alternate between read and write phases
67
+ this.#interval = timer(100, 100).pipe(shareReplay({ refCount: true, bufferSize: 1 }));
68
+ this.#readPhase = this.#interval.pipe(filter((n) => n % 2 === 0));
69
+ this.#writePhase = this.#interval.pipe(filter((n) => n % 2 === 1));
70
+ this.#document = inject(DOCUMENT);
71
+ this.parentMasked = this.#document.createElement('div');
72
+ this.parentMasked.classList.add('pr-u-mask');
73
+ this.parentMasked.setAttribute('aria-hidden', 'true');
74
+ this.#document.body.appendChild(this.parentMasked);
62
75
  }
63
76
  /**
64
- * Checks for ellipsis by cloning the node and comparing its unconstrained width against the original element.
77
+ * Hacky af but let's explain everything
78
+ * This method checks for ellipsis by cloning the node and checking its width against original element.
65
79
  *
66
80
  * We used to do this using scrollWidth but the thing is, it's a rounded value. Sometimes,
67
81
  * you'd get true while it should be false and vice-versa, because of rounding.
68
82
  *
69
83
  * So we duplicate the properties we're interested in on the element to be tested to calculate its ideal size,
70
84
  * which we then compare with its current size.
85
+ *
86
+ * To avoid doing multiple reflow per check, we wait for the next microtask on each key step of the process:
87
+ * - After computing element style
88
+ * - After cloning the element and appending it to the DOM
89
+ * - After computing the width of the cloned element
90
+ * - After removing the cloned element from the DOM
91
+ *
92
+ * This way, we have 2 reflows per check, no matter how many elements are checked in a row.
71
93
  */
72
94
  async hasEllipsis(element) {
73
- await this.#nextFrame();
74
- const elementStyle = getComputedStyle(element);
75
- const bodyStyle = getComputedStyle(this.#document.body);
95
+ let elementStyle;
96
+ let bodyStyle;
97
+ await this.#readOperation(() => {
98
+ elementStyle = getComputedStyle(element);
99
+ bodyStyle = getComputedStyle(document.body);
100
+ });
76
101
  if (elementStyle.textOverflow !== 'ellipsis') {
77
102
  return false;
78
103
  }
79
104
  const { padding, borderWidth, borderStyle, boxSizing, fontFamily, fontWeight, fontStyle } = elementStyle;
80
105
  const fontSize = (Number(elementStyle.fontSize.replace('px', '')) / Number(bodyStyle.fontSize.replace('px', ''))).toString() + 'rem';
81
- await this.#nextFrame();
82
- const elementCloned = this.#document.createElement('div');
83
- Object.assign(elementCloned.style, {
84
- inlineSize: 'fit-content',
85
- whiteSpace: 'nowrap',
86
- position: 'absolute',
87
- visibility: 'hidden',
88
- padding,
89
- borderWidth,
90
- borderStyle,
91
- boxSizing,
92
- fontFamily,
93
- fontWeight,
94
- fontStyle,
95
- fontSize,
106
+ let elementCloned;
107
+ await this.#writeOperation(() => {
108
+ elementCloned = this.#document.createElement('div');
109
+ Object.assign(elementCloned.style, {
110
+ inlineSize: 'fit-content',
111
+ whiteSpace: 'nowrap',
112
+ position: 'absolute',
113
+ visibility: 'hidden',
114
+ padding,
115
+ borderWidth,
116
+ borderStyle,
117
+ boxSizing,
118
+ fontFamily,
119
+ fontWeight,
120
+ fontStyle,
121
+ fontSize,
122
+ });
123
+ this.parentMasked.appendChild(elementCloned);
124
+ elementCloned.innerHTML = element.innerHTML;
96
125
  });
97
- this.#parentMasked.appendChild(elementCloned);
98
- elementCloned.innerHTML = element.innerHTML;
126
+ // To avoid multiple reflows, we wait for the next microtask before calculating the width
99
127
  try {
100
- const clonedElementWidth = elementCloned.getBoundingClientRect().width;
101
- const elementWidth = element.getBoundingClientRect().width;
128
+ let clonedElementWidth;
129
+ let elementWidth;
130
+ await this.#readOperation(() => {
131
+ clonedElementWidth = elementCloned.getBoundingClientRect().width;
132
+ elementWidth = element.getBoundingClientRect().width;
133
+ });
102
134
  return clonedElementWidth > elementWidth;
103
135
  }
104
136
  catch {
105
137
  return false;
106
138
  }
107
139
  finally {
108
- this.#parentMasked.removeChild(elementCloned);
140
+ await this.#writeOperation(() => {
141
+ this.parentMasked.removeChild(elementCloned);
142
+ });
109
143
  }
110
144
  }
111
- #nextFrame() {
112
- return new Promise((resolve) => requestAnimationFrame(() => resolve()));
145
+ // To avoid multiple reflows, we wait for the next read phase before computing/reading element style
146
+ async #readOperation(operation) {
147
+ await firstValueFrom(this.#readPhase);
148
+ operation();
149
+ }
150
+ // To avoid multiple reflows, we wait for the next write before inserting/removing the cloned element
151
+ async #writeOperation(operation) {
152
+ await firstValueFrom(this.#writePhase);
153
+ operation();
113
154
  }
114
155
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.8", ngImport: i0, type: EllipsisRuler, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
115
156
  static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.0.8", ngImport: i0, type: EllipsisRuler, providedIn: 'root' }); }
@@ -125,21 +166,40 @@ class LuTooltipTriggerDirective {
125
166
  #host;
126
167
  #renderer;
127
168
  #ruler;
169
+ #zone;
128
170
  #injector;
129
171
  #destroyRef;
130
- #isVisible;
131
- #ellipsisTrigger;
172
+ #previousTickContent;
173
+ #innerTextChange$;
174
+ #hasEllipsis$;
132
175
  #hasEllipsis;
133
176
  #action;
134
177
  #realAction;
178
+ onMouseEnter() {
179
+ this.#action.set('open');
180
+ }
181
+ onMouseLeave() {
182
+ this.#action.set('close');
183
+ }
184
+ onFocus() {
185
+ if (this.#host.nativeElement.getAttribute('aria-expanded') !== 'true') {
186
+ this.#action.set('open');
187
+ }
188
+ }
189
+ onBlur() {
190
+ this.#action.set('close');
191
+ }
135
192
  #effectRef;
193
+ #idEffectRef;
136
194
  constructor() {
137
195
  this.#overlay = inject(Overlay);
138
196
  this.#host = inject(ElementRef);
139
197
  this.#renderer = inject(Renderer2);
140
198
  this.#ruler = inject(EllipsisRuler);
199
+ this.#zone = inject(NgZone, { optional: true });
141
200
  this.#injector = inject(Injector);
142
201
  this.#destroyRef = inject(DestroyRef);
202
+ this.#previousTickContent = this.#host.nativeElement.innerText;
143
203
  this.luTooltipInput = input('', { ...(ngDevMode ? { debugName: "luTooltipInput" } : {}), alias: 'luTooltip' });
144
204
  this.luTooltip = linkedSignal(() => this.luTooltipInput(), ...(ngDevMode ? [{ debugName: "luTooltip" }] : []));
145
205
  this.luTooltipEnterDelay = input(300, { ...(ngDevMode ? { debugName: "luTooltipEnterDelay" } : {}), transform: numberAttribute });
@@ -151,20 +211,30 @@ class LuTooltipTriggerDirective {
151
211
  this.luTooltipWhenEllipsis = linkedSignal(() => this.luTooltipWhenEllipsisInput(), ...(ngDevMode ? [{ debugName: "luTooltipWhenEllipsis" }] : []));
152
212
  this.luTooltipAnchor = input(this.#host, ...(ngDevMode ? [{ debugName: "luTooltipAnchor" }] : []));
153
213
  this.id = input(`${this.#host.nativeElement.tagName.toLowerCase()}-tooltip-${nextId++}`, ...(ngDevMode ? [{ debugName: "id" }] : []));
154
- this.ariaDescribedBy = computed(() => {
155
- if (this.luTooltipDisabled() || this.luTooltipWhenEllipsis() || this.luTooltipOnlyForDisplay()) {
156
- return null;
157
- }
158
- return `${this.id()}-panel`;
159
- }, ...(ngDevMode ? [{ debugName: "ariaDescribedBy" }] : []));
160
- this.#isVisible = signal(false, ...(ngDevMode ? [{ debugName: "#isVisible" }] : []));
161
- this.#ellipsisTrigger = signal(0, ...(ngDevMode ? [{ debugName: "#ellipsisTrigger" }] : []));
162
- this.#hasEllipsis = toSignal(toObservable(this.#ellipsisTrigger).pipe(debounceTime(150), switchMap(() => {
163
- if (!this.luTooltipWhenEllipsis() || this.luTooltipDisabled()) {
164
- return of(false);
165
- }
166
- return from(this.#ruler.hasEllipsis(this.#host.nativeElement));
167
- })), { initialValue: false });
214
+ this.resize$ = new Observable((observer) => {
215
+ const resizeObserver = new ResizeObserver(() => {
216
+ observer.next();
217
+ });
218
+ resizeObserver.observe(this.#host.nativeElement);
219
+ return () => {
220
+ resizeObserver.disconnect();
221
+ };
222
+ });
223
+ this.#innerTextChange$ = new Subject();
224
+ this.#hasEllipsis$ = combineLatest([
225
+ toObservable(
226
+ // 1. Group necessary inputs
227
+ computed(() => ({ whenEllipsis: this.luTooltipWhenEllipsis(), disabled: this.luTooltipDisabled() }))),
228
+ // Resend resize events to trigger the check
229
+ this.resize$.pipe(debounceTime(150)),
230
+ // Include content changes
231
+ this.#innerTextChange$,
232
+ ]).pipe(
233
+ // 2. Keep only necessary inputs
234
+ filter$1(([{ whenEllipsis, disabled }]) => !disabled && whenEllipsis),
235
+ // 3. Check for ellipsis
236
+ switchMap(() => this.runOutsideZoneJS(() => this.#ruler.hasEllipsis(this.#host.nativeElement))));
237
+ this.#hasEllipsis = toSignal(this.#hasEllipsis$, { initialValue: false });
168
238
  this.#action = signal(null, ...(ngDevMode ? [{ debugName: "#action" }] : []));
169
239
  this.#realAction = linkedSignal({ ...(ngDevMode ? { debugName: "#realAction" } : {}), source: this.#action,
170
240
  computation: (action, previous) => {
@@ -174,16 +244,23 @@ class LuTooltipTriggerDirective {
174
244
  // We only filter open events because even if it's disabled while opened,
175
245
  // we want the tooltip to be able to close itself no matter what
176
246
  if (this.luTooltipDisabled()) {
177
- return previous?.value ?? null;
247
+ return previous?.value;
178
248
  }
249
+ // If not disabled, let's check for ellipsis if needed
179
250
  if (this.luTooltipWhenEllipsis()) {
180
- return this.#hasEllipsis() ? 'open' : (previous?.value ?? null);
251
+ return this.#hasEllipsis() ? 'open' : previous.value;
181
252
  }
253
+ // If it's not disabled and is not triggered based on ellipsis, just return true
182
254
  return 'open';
183
255
  } });
184
- // Action debounce pipeline — kept as Observable since signals can't debounce
256
+ this.ariaDescribedBy = computed(() => {
257
+ if (this.luTooltipDisabled() || this.luTooltipWhenEllipsis() || this.luTooltipOnlyForDisplay()) {
258
+ return null;
259
+ }
260
+ return `${this.id()}-panel`;
261
+ }, ...(ngDevMode ? [{ debugName: "ariaDescribedBy" }] : []));
185
262
  toObservable(this.#realAction)
186
- .pipe(filter(isNotNil), debounce((action) => timer(action === 'open' ? this.luTooltipEnterDelay() : this.luTooltipLeaveDelay())), tap((event) => {
263
+ .pipe(filter$1(isNotNil), debounce((action) => timer(action === 'open' ? this.luTooltipEnterDelay() : this.luTooltipLeaveDelay())), tap((event) => {
187
264
  if (event === 'open') {
188
265
  this.openTooltip();
189
266
  }
@@ -200,88 +277,27 @@ class LuTooltipTriggerDirective {
200
277
  this.setAccessibilityProperties(null);
201
278
  }
202
279
  });
203
- effect((onCleanup) => {
204
- if (!this.luTooltipWhenEllipsis() || this.luTooltipDisabled()) {
205
- this.#isVisible.set(false);
206
- return;
207
- }
208
- const observer = new IntersectionObserver((entries) => this.#isVisible.set(entries.some((e) => e.isIntersecting)), { rootMargin: '100px' });
209
- observer.observe(this.#host.nativeElement);
210
- onCleanup(() => observer.disconnect());
211
- });
212
- effect((onCleanup) => {
213
- if (!this.#isVisible() || !this.luTooltipWhenEllipsis() || this.luTooltipDisabled()) {
214
- return;
215
- }
216
- const el = this.#host.nativeElement;
217
- const bump = () => this.#ellipsisTrigger.update((v) => v + 1);
218
- const resizeObserver = new ResizeObserver(() => bump());
219
- resizeObserver.observe(el);
220
- const mutationObserver = new MutationObserver(() => bump());
221
- mutationObserver.observe(el, { characterData: true, subtree: true, childList: true });
222
- // Initial check when element becomes visible — prevents regression where tooltips never appear
223
- bump();
224
- onCleanup(() => {
225
- resizeObserver.disconnect();
226
- mutationObserver.disconnect();
227
- });
228
- });
229
280
  }
230
- onMouseEnter() {
231
- this.#action.set('open');
232
- }
233
- onMouseLeave() {
234
- this.#action.set('close');
235
- }
236
- onFocus() {
237
- if (this.#host.nativeElement.getAttribute('aria-expanded') !== 'true') {
238
- this.#action.set('open');
281
+ ngAfterContentChecked() {
282
+ if (this.#previousTickContent != this.#host.nativeElement.innerText) {
283
+ this.#innerTextChange$.next();
284
+ this.#previousTickContent = this.#host.nativeElement.innerText;
239
285
  }
240
286
  }
241
- onBlur() {
242
- this.#action.set('close');
243
- }
244
- requestOpen() {
245
- this.#action.set('open');
246
- }
247
- requestClose() {
248
- this.#action.set('close');
249
- }
250
287
  ngOnDestroy() {
251
288
  this.closeTooltip();
252
- if (this.overlayRef) {
253
- this.overlayRef.dispose();
254
- delete this.overlayRef;
255
- }
256
289
  }
257
- prepareOverlay() {
290
+ openTooltip() {
291
+ // If tooltip is already opened, don't do anything
258
292
  if (this.overlayRef) {
259
293
  return;
260
294
  }
295
+ const position = this.legacyPositionBuilder();
261
296
  this.overlayRef = this.#overlay.create({
297
+ positionStrategy: position,
262
298
  scrollStrategy: this.#overlay.scrollStrategies.close(),
263
299
  disposeOnNavigation: true,
264
300
  });
265
- const describedBy = this.ariaDescribedBy();
266
- if (describedBy !== null) {
267
- this.overlayRef.overlayElement.id = describedBy;
268
- }
269
- }
270
- openTooltip() {
271
- if (this.overlayRef?.hasAttached()) {
272
- return;
273
- }
274
- const position = this.legacyPositionBuilder();
275
- if (!this.overlayRef) {
276
- this.overlayRef = this.#overlay.create({
277
- positionStrategy: position,
278
- scrollStrategy: this.#overlay.scrollStrategies.close(),
279
- disposeOnNavigation: true,
280
- });
281
- }
282
- else {
283
- this.overlayRef.updatePositionStrategy(position);
284
- }
285
301
  const portal = new ComponentPortal(LuTooltipPanelComponent);
286
302
  const ref = this.overlayRef.attach(portal);
287
303
  position.positionChanges
@@ -300,23 +316,27 @@ class LuTooltipTriggerDirective {
300
316
  else {
301
317
  ref.instance.content.set('');
302
318
  }
319
+ this.#idEffectRef = _effectWithDeps([this.ariaDescribedBy], (ariaDescribedBy) => {
320
+ ref.instance.id.set(ariaDescribedBy);
321
+ }, { injector: this.#injector });
322
+ // On tooltip leave => trigger close
303
323
  ref.instance.mouseLeave$.pipe(takeUntilDestroyed(ref.instance.destroyRef)).subscribe(() => this.#action.set('close'));
324
+ // On tooltip enter => trigger open to keep it opened
304
325
  ref.instance.mouseEnter$.pipe(takeUntilDestroyed(ref.instance.destroyRef)).subscribe(() => this.#action.set('open'));
305
326
  }
306
327
  closeTooltip() {
307
328
  if (this.overlayRef) {
308
329
  this.overlayRef.detach();
330
+ delete this.overlayRef;
309
331
  }
310
332
  this.#effectRef?.destroy();
333
+ this.#idEffectRef?.destroy();
311
334
  }
312
335
  setAccessibilityProperties(tabindex) {
313
336
  if (tabindex === null) {
314
337
  this.#renderer.removeAttribute(this.#host.nativeElement, 'tabindex');
315
338
  return;
316
339
  }
317
- if (!this.luTooltipWhenEllipsis() && !this.luTooltipOnlyForDisplay()) {
318
- this.prepareOverlay();
319
- }
320
340
  const tag = this.#host.nativeElement.tagName.toLowerCase();
321
341
  const nativelyFocusableTags = ['a', 'button', 'input', 'select', 'textarea'];
322
342
  const isNativelyFocusableTag = nativelyFocusableTags.includes(tag);
@@ -328,7 +348,14 @@ class LuTooltipTriggerDirective {
328
348
  this.#renderer.setAttribute(this.#host.nativeElement, 'role', 'button');
329
349
  }
330
350
  }
331
- // Legacy position builder to handle existing position API
351
+ runOutsideZoneJS(callback) {
352
+ return this.#zone ? this.#zone.runOutsideAngular(callback) : callback();
353
+ }
354
+ /**********************
355
+ *
356
+ * LEGACY STUFF TO HANDLE EXISTING POSITIONS
357
+ *
358
+ ***********************/
332
359
  legacyPositionBuilder() {
333
360
  const connectionPosition = {
334
361
  originX: 'start',
@@ -415,6 +442,12 @@ class LuTooltipTriggerDirective {
415
442
  }
416
443
  return x;
417
444
  }
445
+ requestClose() {
446
+ this.#action.set('close');
447
+ }
448
+ requestOpen() {
449
+ this.#action.set('open');
450
+ }
418
451
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.8", ngImport: i0, type: LuTooltipTriggerDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
419
452
  static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.0.8", type: LuTooltipTriggerDirective, isStandalone: true, selector: "[luTooltip]", inputs: { luTooltipInput: { classPropertyName: "luTooltipInput", publicName: "luTooltip", isSignal: true, isRequired: false, transformFunction: null }, luTooltipEnterDelay: { classPropertyName: "luTooltipEnterDelay", publicName: "luTooltipEnterDelay", isSignal: true, isRequired: false, transformFunction: null }, luTooltipLeaveDelay: { classPropertyName: "luTooltipLeaveDelay", publicName: "luTooltipLeaveDelay", isSignal: true, isRequired: false, transformFunction: null }, luTooltipDisabled: { classPropertyName: "luTooltipDisabled", publicName: "luTooltipDisabled", isSignal: true, isRequired: false, transformFunction: null }, luTooltipOnlyForDisplay: { classPropertyName: "luTooltipOnlyForDisplay", publicName: "luTooltipOnlyForDisplay", isSignal: true, isRequired: false, transformFunction: null }, luTooltipPosition: { classPropertyName: "luTooltipPosition", publicName: "luTooltipPosition", isSignal: true, isRequired: false, transformFunction: null }, luTooltipWhenEllipsisInput: { classPropertyName: "luTooltipWhenEllipsisInput", publicName: "luTooltipWhenEllipsis", isSignal: true, isRequired: false, transformFunction: null }, luTooltipAnchor: { classPropertyName: "luTooltipAnchor", publicName: "luTooltipAnchor", 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()", "blur": "onBlur()" }, properties: { "attr.aria-describedby": "ariaDescribedBy()", "attr.id": "id()" } }, exportAs: ["luTooltip"], ngImport: i0 }); }
420
453
  }