3232import org .patternfly .component .ComponentType ;
3333import org .patternfly .core .Roles ;
3434import org .patternfly .handler .CloseHandler ;
35+ import org .patternfly .popover .NativeAnchor ;
3536import org .patternfly .popper .Placement ;
3637
3738import elemental2 .dom .DOMRect ;
@@ -126,12 +127,10 @@ public static NativeTooltip nativeTooltip(Supplier<HTMLElement> trigger, String
126127 public static final int DISTANCE = 15 ;
127128
128129 private final String id ;
130+ private final NativeAnchor anchor ;
129131 private final HTMLElement contentElement ;
130132 private final List <CloseHandler <NativeTooltip >> closeHandler ;
131- private Supplier <HTMLElement > triggerSupplier ;
132- private HTMLElement trigger ;
133133 private boolean visible ;
134- private int distance ;
135134 private int entryDelay ;
136135 private int exitDelay ;
137136 private double showTimeout ;
@@ -150,10 +149,9 @@ public static NativeTooltip nativeTooltip(Supplier<HTMLElement> trigger, String
150149 .element ());
151150
152151 this .id = Id .unique (componentType ().id );
153- this .triggerSupplier = trigger ;
152+ this .anchor = new NativeAnchor ( id , DISTANCE , element (), trigger ) ;
154153 this .closeHandler = new ArrayList <>();
155154 this .visible = false ;
156- this .distance = DISTANCE ;
157155 this .entryDelay = ENTRY_DELAY ;
158156 this .exitDelay = EXIT_DELAY ;
159157 this .placement = auto ;
@@ -183,37 +181,29 @@ public Element textDelegate() {
183181
184182 @ Override
185183 public void attach (MutationRecord mutationRecord ) {
186- if (triggerSupplier != null ) {
187- trigger = triggerSupplier .get ();
188- if (trigger != null ) {
189- // CSS anchor positioning
190- String anchorName = "--" + id ;
191- trigger .style .setProperty ("anchor-name" , anchorName );
192- style ("position-anchor" , anchorName );
193- style ("margin" , distance + "px" );
194-
195- // top is the default for auto and recalculated on show()
196- applyPlacement (placement == auto ? top : placement );
197-
198- // event listeners on trigger
199- triggerHandlers = compose (
200- bind (trigger , mouseenter , this ::scheduleShow ),
201- bind (trigger , mouseleave , this ::scheduleHide ),
202- bind (trigger , focusin , this ::scheduleShow ),
203- bind (trigger , focusout , this ::scheduleHide ));
204-
205- // event listeners on tooltip itself:
206- // hovering into the tooltip cancels the hide timer,
207- // hovering out of the tooltip schedules hide
208- anchorHandlers = compose (
209- bind (element (), mouseenter , this ::cancelTimers ),
210- bind (element (), mouseleave , this ::scheduleHide ));
211-
212- // hide tooltip immediately on scroll to prevent jump artifacts
213- scrollHandler = bind (window , scroll .name , this ::hideOnScroll );
214- } else {
215- logger .error ("Unable to find trigger element for tooltip %o" , element ());
216- }
184+ HTMLElement trigger = anchor .attach ();
185+ if (trigger != null ) {
186+ // top is the default for auto and recalculated on show()
187+ anchor .applyPlacement (placement == auto ? top : placement );
188+
189+ // event listeners on trigger
190+ triggerHandlers = compose (
191+ bind (trigger , mouseenter , this ::scheduleShow ),
192+ bind (trigger , mouseleave , this ::scheduleHide ),
193+ bind (trigger , focusin , this ::scheduleShow ),
194+ bind (trigger , focusout , this ::scheduleHide ));
195+
196+ // event listeners on tooltip itself:
197+ // hovering into the tooltip cancels the hide timer,
198+ // hovering out of the tooltip schedules hide
199+ anchorHandlers = compose (
200+ bind (element (), mouseenter , this ::cancelTimers ),
201+ bind (element (), mouseleave , this ::scheduleHide ));
202+
203+ // hide tooltip immediately on scroll to prevent jump artifacts
204+ scrollHandler = bind (window , scroll .name , this ::hideOnScroll );
205+ } else if (anchor .hasTriggerSupplier ()) {
206+ logger .error ("Unable to find trigger element for tooltip %o" , element ());
217207 } else {
218208 logger .error ("No trigger element defined for tooltip %o" , element ());
219209 }
@@ -235,10 +225,7 @@ public void detach(MutationRecord mutationRecord) {
235225 if (triggerHandlers != null ) {
236226 triggerHandlers .removeHandler ();
237227 }
238- if (trigger != null ) {
239- trigger .style .removeProperty ("anchor-name" );
240- trigger = null ;
241- }
228+ anchor .detach ();
242229 }
243230
244231 // ------------------------------------------------------ builder
@@ -254,7 +241,7 @@ public NativeTooltip exitDelay(int delay) {
254241 }
255242
256243 public NativeTooltip distance (int distance ) {
257- this .distance = distance ;
244+ anchor .distance ( distance ) ;
258245 return this ;
259246 }
260247
@@ -269,22 +256,22 @@ public NativeTooltip placement(Placement placement) {
269256 }
270257
271258 public NativeTooltip trigger (String trigger ) {
272- this . triggerSupplier = () -> Elements . querySelector ( document . body , By . selector ( trigger ) );
259+ anchor . trigger ( trigger );
273260 return this ;
274261 }
275262
276263 public NativeTooltip trigger (By trigger ) {
277- this . triggerSupplier = () -> Elements . querySelector ( document . body , trigger );
264+ anchor . trigger ( trigger );
278265 return this ;
279266 }
280267
281268 public NativeTooltip trigger (HTMLElement trigger ) {
282- this . triggerSupplier = () -> trigger ;
269+ anchor . trigger ( trigger ) ;
283270 return this ;
284271 }
285272
286273 public NativeTooltip trigger (Supplier <HTMLElement > trigger ) {
287- this . triggerSupplier = trigger ;
274+ anchor . trigger ( trigger ) ;
288275 return this ;
289276 }
290277
@@ -317,14 +304,15 @@ public void show() {
317304 }
318305
319306 public void show (Event event ) {
307+ HTMLElement trigger = anchor .trigger ();
320308 if (!visible && trigger != null ) {
321309 // Show invisibly to get measurable dimensions, calculate placement, then reveal
322310 style ("visibility" , "hidden" );
323311 element ().showPopover ();
324- applyPlacement (bestPlacement (placement ));
312+ anchor . applyPlacement (bestPlacement (placement ));
325313 element ().style .removeProperty ("visibility" );
326314 visible = true ;
327- if (aria != none && trigger != null ) {
315+ if (aria != none ) {
328316 trigger .setAttribute (aria .attribute , id );
329317 }
330318 }
@@ -336,6 +324,7 @@ public void close(Event event, boolean fireEvent) {
336324 if (visible ) {
337325 element ().hidePopover ();
338326 visible = false ;
327+ HTMLElement trigger = anchor .trigger ();
339328 if (aria != none && trigger != null ) {
340329 trigger .removeAttribute (aria .attribute );
341330 }
@@ -373,28 +362,20 @@ private void cancelTimers(Event event) {
373362 clearTimeout (hideTimeout );
374363 }
375364
376- private void applyPlacement (Placement p ) {
377- for (String mod : Placement .modifiers ) {
378- classList ().remove (mod );
379- }
380- if (p .modifier != null && !p .modifier .isEmpty ()) {
381- classList ().add (p .modifier );
382- }
383- }
384-
385365 private Placement bestPlacement (Placement preferred ) {
386- DOMRect triggerRect = trigger .getBoundingClientRect ();
366+ DOMRect triggerRect = anchor . trigger () .getBoundingClientRect ();
387367 DOMRect tooltipRect = element ().getBoundingClientRect ();
388368 double above = triggerRect .top ;
389369 double below = window .innerHeight - triggerRect .bottom ;
390370 double toLeft = triggerRect .left ;
391371 double toRight = window .innerWidth - triggerRect .right ;
392372
393373 // Check which directions have enough space for the tooltip
394- boolean topFits = above >= tooltipRect .height + distance ;
395- boolean bottomFits = below >= tooltipRect .height + distance ;
396- boolean leftFits = toLeft >= tooltipRect .width / 3 + distance ;
397- boolean rightFits = toRight >= tooltipRect .width / 3 + distance ;
374+ int d = anchor .distance ();
375+ boolean topFits = above >= tooltipRect .height + d ;
376+ boolean bottomFits = below >= tooltipRect .height + d ;
377+ boolean leftFits = toLeft >= tooltipRect .width / 3 + d ;
378+ boolean rightFits = toRight >= tooltipRect .width / 3 + d ;
398379
399380 // Honor explicit preference if it fits
400381 if (preferred != auto ) {
0 commit comments