Skip to content

Commit fc1b863

Browse files
committed
Move common code of NativeTooltip and NativePopover to NativeAnchor
1 parent 9713d6b commit fc1b863

8 files changed

Lines changed: 236 additions & 133 deletions

File tree

components/src/main/java/org/patternfly/component/popover/NativePopover.java

Lines changed: 19 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
import org.patternfly.component.button.Button;
3434
import org.patternfly.core.Aria;
3535
import org.patternfly.handler.CloseHandler;
36+
import org.patternfly.popover.NativeAnchor;
3637
import org.patternfly.popper.Placement;
3738
import org.patternfly.style.Modifiers.NoPadding;
3839

@@ -109,15 +110,11 @@ public static NativePopover nativePopover(Supplier<HTMLElement> trigger) {
109110

110111
public static final int DISTANCE = 20;
111112

112-
private final String id;
113+
private final NativeAnchor anchor;
113114
private final HTMLElement contentElement;
114115
private final List<CloseHandler<NativePopover>> closeHandler;
115-
private Supplier<HTMLElement> triggerSupplier;
116-
private HTMLElement trigger;
117116
private boolean visible;
118117
private boolean showClose;
119-
private int distance;
120-
// private Placement placement;
121118
private Severity severity;
122119
private Button closeButton;
123120
private NativePopoverHeader header;
@@ -131,13 +128,11 @@ public static NativePopover nativePopover(Supplier<HTMLElement> trigger) {
131128
.attr("popover", "manual")
132129
.element());
133130

134-
this.id = Id.unique(componentType().id);
135-
this.triggerSupplier = trigger;
131+
String id = Id.unique(componentType().id);
132+
this.anchor = new NativeAnchor(id, DISTANCE, element(), trigger);
136133
this.closeHandler = new ArrayList<>();
137134
this.visible = false;
138135
this.showClose = true;
139-
this.distance = DISTANCE;
140-
// this.placement = top;
141136

142137
String bodyId = Id.unique(componentType().id, "body");
143138
element().appendChild(div().css(component(popover, arrow)).element());
@@ -155,23 +150,12 @@ public void attach(MutationRecord mutationRecord) {
155150
failSafeRemoveFromParent(closeButton);
156151
}
157152

158-
if (triggerSupplier != null) {
159-
trigger = triggerSupplier.get();
160-
if (trigger != null) {
161-
// CSS anchor positioning
162-
String anchorName = "--" + id;
163-
trigger.style.setProperty("anchor-name", anchorName);
164-
style("position-anchor", anchorName);
165-
style("margin", distance + "px");
166-
167-
// placement
168-
// applyPlacement(placement);
169-
170-
// click trigger: toggle on click
171-
triggerHandlers = bind(trigger, click, this::togglePopover);
172-
} else {
173-
logger.error("Unable to find trigger element for popover %o", element());
174-
}
153+
HTMLElement trigger = anchor.attach();
154+
if (trigger != null) {
155+
// click trigger: toggle on click
156+
triggerHandlers = bind(trigger, click, this::togglePopover);
157+
} else if (anchor.hasTriggerSupplier()) {
158+
logger.error("Unable to find trigger element for popover %o", element());
175159
} else {
176160
logger.error("No trigger element defined for popover %o", element());
177161
}
@@ -189,10 +173,7 @@ public void detach(MutationRecord mutationRecord) {
189173
if (triggerHandlers != null) {
190174
triggerHandlers.removeHandler();
191175
}
192-
if (trigger != null) {
193-
trigger.style.removeProperty("anchor-name");
194-
trigger = null;
195-
}
176+
anchor.detach();
196177
}
197178

198179
// ------------------------------------------------------ add
@@ -270,7 +251,7 @@ public NativePopover closable(CloseHandler<NativePopover> closeHandler) {
270251
}
271252

272253
public NativePopover distance(int distance) {
273-
this.distance = distance;
254+
anchor.distance(distance);
274255
return this;
275256
}
276257

@@ -296,10 +277,7 @@ public NativePopover noClose() {
296277

297278
public NativePopover placement(Placement placement) {
298279
if (verifyEnum(element(), "placement", placement, top, right, bottom, left)) {
299-
for (String mod : Placement.modifiers) {
300-
classList().remove(mod);
301-
}
302-
classList().add(placement.modifier);
280+
anchor.applyPlacement(placement);
303281
}
304282
return this;
305283
}
@@ -319,22 +297,22 @@ public NativePopover status(Severity severity, String screenReaderText) {
319297
}
320298

321299
public NativePopover trigger(String trigger) {
322-
this.triggerSupplier = () -> Elements.querySelector(document.body, By.selector(trigger));
300+
anchor.trigger(trigger);
323301
return this;
324302
}
325303

326304
public NativePopover trigger(By trigger) {
327-
this.triggerSupplier = () -> Elements.querySelector(document.body, trigger);
305+
anchor.trigger(trigger);
328306
return this;
329307
}
330308

331309
public NativePopover trigger(HTMLElement trigger) {
332-
this.triggerSupplier = () -> trigger;
310+
anchor.trigger(trigger);
333311
return this;
334312
}
335313

336314
public NativePopover trigger(Supplier<HTMLElement> trigger) {
337-
this.triggerSupplier = trigger;
315+
anchor.trigger(trigger);
338316
return this;
339317
}
340318

@@ -375,7 +353,7 @@ public NativePopover onClose(CloseHandler<NativePopover> closeHandler) {
375353
// ------------------------------------------------------ api
376354

377355
public void show() {
378-
if (!visible && trigger != null) {
356+
if (!visible && anchor.trigger() != null) {
379357
element().showPopover();
380358
visible = true;
381359
outsideClickHandler = bind(document, click.name, this::onOutsideClick);
@@ -408,6 +386,7 @@ private void togglePopover(Event event) {
408386
}
409387

410388
private void onOutsideClick(Event event) {
389+
HTMLElement trigger = anchor.trigger();
411390
if (visible && trigger != null) {
412391
Element target = (Element) event.target;
413392
if (!element().contains(target) && !trigger.contains(target)) {

components/src/main/java/org/patternfly/component/tooltip/NativeTooltip.java

Lines changed: 42 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
import org.patternfly.component.ComponentType;
3333
import org.patternfly.core.Roles;
3434
import org.patternfly.handler.CloseHandler;
35+
import org.patternfly.popover.NativeAnchor;
3536
import org.patternfly.popper.Placement;
3637

3738
import 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

Comments
 (0)