| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -161,6 +161,8 @@ public enum ComponentType { | |||
| 161 | 161 | ||
| 162 | 162 | Tooltip("tt", "PF6/Component/Tooltip"), | |
| 163 | 163 | ||
| 164 | + Tooltip2("tt2", "PF6/Component/Tooltip2"), | ||
| 165 | + | ||
| 164 | 166 | TreeView("tv", "PF6/Component/TreeView"), | |
| 165 | 167 | ||
| 166 | 168 | Truncate("tr", "PF6/Component/Truncate"), | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -0,0 +1,360 @@ | |||
| 1 | + /* | ||
| 2 | + * Copyright 2023 Red Hat | ||
| 3 | + * | ||
| 4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | ||
| 5 | + * you may not use this file except in compliance with the License. | ||
| 6 | + * You may obtain a copy of the License at | ||
| 7 | + * | ||
| 8 | + * https://www.apache.org/licenses/LICENSE-2.0 | ||
| 9 | + * | ||
| 10 | + * Unless required by applicable law or agreed to in writing, software | ||
| 11 | + * distributed under the License is distributed on an "AS IS" BASIS, | ||
| 12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| 13 | + * See the License for the specific language governing permissions and | ||
| 14 | + * limitations under the License. | ||
| 15 | + */ | ||
| 16 | + package org.patternfly.component.tooltip; | ||
| 17 | + | ||
| 18 | + import java.util.ArrayList; | ||
| 19 | + import java.util.List; | ||
| 20 | + import java.util.function.Supplier; | ||
| 21 | + | ||
| 22 | + import org.gwtproject.event.shared.HandlerRegistration; | ||
| 23 | + import org.jboss.elemento.Attachable; | ||
| 24 | + import org.jboss.elemento.By; | ||
| 25 | + import org.jboss.elemento.ElementContainerDelegate; | ||
| 26 | + import org.jboss.elemento.ElementTextDelegate; | ||
| 27 | + import org.jboss.elemento.Elements; | ||
| 28 | + import org.jboss.elemento.Id; | ||
| 29 | + import org.jboss.elemento.logger.Logger; | ||
| 30 | + import org.patternfly.component.BaseComponent; | ||
| 31 | + import org.patternfly.component.Closeable; | ||
| 32 | + import org.patternfly.component.ComponentType; | ||
| 33 | + import org.patternfly.core.Roles; | ||
| 34 | + import org.patternfly.handler.CloseHandler; | ||
| 35 | + import org.patternfly.popper.Placement; | ||
| 36 | + import elemental2.dom.Element; | ||
| 37 | + import elemental2.dom.Event; | ||
| 38 | + import elemental2.dom.HTMLDivElement; | ||
| 39 | + import elemental2.dom.HTMLElement; | ||
| 40 | + import elemental2.dom.MutationRecord; | ||
| 41 | + | ||
| 42 | + import static elemental2.dom.DomGlobal.clearTimeout; | ||
| 43 | + import static elemental2.dom.DomGlobal.document; | ||
| 44 | + import static elemental2.dom.DomGlobal.setTimeout; | ||
| 45 | + import static org.gwtproject.event.shared.HandlerRegistrations.compose; | ||
| 46 | + import static org.jboss.elemento.Elements.div; | ||
| 47 | + import static org.jboss.elemento.EventType.bind; | ||
| 48 | + import static org.jboss.elemento.EventType.focusin; | ||
| 49 | + import static org.jboss.elemento.EventType.focusout; | ||
| 50 | + import static org.jboss.elemento.EventType.mouseenter; | ||
| 51 | + import static org.jboss.elemento.EventType.mouseleave; | ||
| 52 | + import static org.patternfly.component.tooltip.TriggerAria.describedBy; | ||
| 53 | + import static org.patternfly.component.tooltip.TriggerAria.none; | ||
| 54 | + import static org.patternfly.core.Aria.live; | ||
| 55 | + import static org.patternfly.core.Attributes.role; | ||
| 56 | + import static org.patternfly.handler.CloseHandler.fireEvent; | ||
| 57 | + import static org.patternfly.handler.CloseHandler.shouldClose; | ||
| 58 | + import static org.patternfly.popper.Placement.top; | ||
| 59 | + import static org.patternfly.style.Classes.arrow; | ||
| 60 | + import static org.patternfly.style.Classes.component; | ||
| 61 | + import static org.patternfly.style.Classes.content; | ||
| 62 | + import static org.patternfly.style.Classes.modifier; | ||
| 63 | + import static org.patternfly.style.Classes.textAlignLeft; | ||
| 64 | + import static org.patternfly.style.Classes.tooltip; | ||
| 65 | + | ||
| 66 | + /** | ||
| 67 | + * A tooltip is in-app messaging used to identify elements on a page with short, clarifying text. | ||
| 68 | + * <p> | ||
| 69 | + * This implementation uses the Popover API and CSS anchor positioning instead of Popper.js. The tooltip uses the browser's | ||
| 70 | + * top-layer rendering for correct stacking, eliminating z-index issues. | ||
| 71 | + */ | ||
| 72 | + public class Tooltip2 extends BaseComponent<HTMLDivElement, Tooltip2> implements | ||
| 73 | + Attachable, | ||
| 74 | + Closeable<HTMLDivElement, Tooltip2>, | ||
| 75 | + ElementContainerDelegate<HTMLDivElement, Tooltip2>, | ||
| 76 | + ElementTextDelegate<HTMLDivElement, Tooltip2> { | ||
| 77 | + | ||
| 78 | + // ------------------------------------------------------ factory | ||
| 79 | + | ||
| 80 | + public static Tooltip2 tooltip2() { | ||
| 81 | + return new Tooltip2(null, null); | ||
| 82 | + } | ||
| 83 | + | ||
| 84 | + public static Tooltip2 tooltip2(String text) { | ||
| 85 | + return new Tooltip2(null, text); | ||
| 86 | + } | ||
| 87 | + | ||
| 88 | + public static Tooltip2 tooltip2(By trigger) { | ||
| 89 | + return new Tooltip2(() -> Elements.querySelector(document.body, trigger), null); | ||
| 90 | + } | ||
| 91 | + | ||
| 92 | + public static Tooltip2 tooltip2(By trigger, String text) { | ||
| 93 | + return new Tooltip2(() -> Elements.querySelector(document.body, trigger), text); | ||
| 94 | + } | ||
| 95 | + | ||
| 96 | + public static Tooltip2 tooltip2(HTMLElement trigger) { | ||
| 97 | + return new Tooltip2(() -> trigger, null); | ||
| 98 | + } | ||
| 99 | + | ||
| 100 | + public static Tooltip2 tooltip2(HTMLElement trigger, String text) { | ||
| 101 | + return new Tooltip2(() -> trigger, text); | ||
| 102 | + } | ||
| 103 | + | ||
| 104 | + public static Tooltip2 tooltip2(Supplier<HTMLElement> trigger) { | ||
| 105 | + return new Tooltip2(trigger, null); | ||
| 106 | + } | ||
| 107 | + | ||
| 108 | + public static Tooltip2 tooltip2(Supplier<HTMLElement> trigger, String text) { | ||
| 109 | + return new Tooltip2(trigger, text); | ||
| 110 | + } | ||
| 111 | + | ||
| 112 | + // ------------------------------------------------------ instance | ||
| 113 | + | ||
| 114 | + private static final Logger logger = Logger.getLogger(Tooltip2.class.getName()); | ||
| 115 | + | ||
| 116 | + public static final int ENTRY_DELAY = 300; | ||
| 117 | + public static final int EXIT_DELAY = 300; | ||
| 118 | + public static final int DISTANCE = 15; | ||
| 119 | + | ||
| 120 | + private final String id; | ||
| 121 | + private final HTMLElement contentElement; | ||
| 122 | + private final List<CloseHandler<Tooltip2>> closeHandler; | ||
| 123 | + private Supplier<HTMLElement> triggerSupplier; | ||
| 124 | + private HTMLElement trigger; | ||
| 125 | + private boolean visible; | ||
| 126 | + private int distance; | ||
| 127 | + private int entryDelay; | ||
| 128 | + private int exitDelay; | ||
| 129 | + private double showTimeout; | ||
| 130 | + private double hideTimeout; | ||
| 131 | + private Placement placement; | ||
| 132 | + private TriggerAria aria; | ||
| 133 | + private HandlerRegistration triggerHandlers; | ||
| 134 | + private HandlerRegistration anchorHandlers; | ||
| 135 | + | ||
| 136 | + Tooltip2(Supplier<HTMLElement> trigger, String text) { | ||
| 137 | + super(ComponentType.Tooltip2, div().css(component(tooltip)) | ||
| 138 | + .attr(role, Roles.tooltip) | ||
| 139 | + .aria(live, "polite") | ||
| 140 | + .attr("popover", "manual") | ||
| 141 | + .element()); | ||
| 142 | + | ||
| 143 | + this.id = Id.unique(componentType().id); | ||
| 144 | + this.triggerSupplier = trigger; | ||
| 145 | + this.closeHandler = new ArrayList<>(); | ||
| 146 | + this.visible = false; | ||
| 147 | + this.distance = DISTANCE; | ||
| 148 | + this.entryDelay = ENTRY_DELAY; | ||
| 149 | + this.exitDelay = EXIT_DELAY; | ||
| 150 | + this.placement = top; | ||
| 151 | + this.aria = describedBy; | ||
| 152 | + this.showTimeout = 0; | ||
| 153 | + this.hideTimeout = 0; | ||
| 154 | + | ||
| 155 | + id(id); | ||
| 156 | + element().appendChild(div().css(component(tooltip, arrow)).element()); | ||
| 157 | + element().appendChild(contentElement = div().css(component(tooltip, content)).element()); | ||
| 158 | + if (text != null) { | ||
| 159 | + contentElement.textContent = text; | ||
| 160 | + } | ||
| 161 | + | ||
| 162 | + Attachable.register(this, this); | ||
| 163 | + } | ||
| 164 | + | ||
| 165 | + @Override | ||
| 166 | + public Element containerDelegate() { | ||
| 167 | + return contentElement; | ||
| 168 | + } | ||
| 169 | + | ||
| 170 | + @Override | ||
| 171 | + public Element textDelegate() { | ||
| 172 | + return contentElement; | ||
| 173 | + } | ||
| 174 | + | ||
| 175 | + @Override | ||
| 176 | + public void attach(MutationRecord mutationRecord) { | ||
| 177 | + if (triggerSupplier != null) { | ||
| 178 | + trigger = triggerSupplier.get(); | ||
| 179 | + if (trigger != null) { | ||
| 180 | + // CSS anchor positioning | ||
| 181 | + String anchorName = "--" + id; | ||
| 182 | + trigger.style.setProperty("anchor-name", anchorName); | ||
| 183 | + style("position-anchor", anchorName); | ||
| 184 | + style("margin", distance + "px"); | ||
| 185 | + applyPlacement(); | ||
| 186 | + | ||
| 187 | + // event listeners on trigger | ||
| 188 | + triggerHandlers = compose( | ||
| 189 | + bind(trigger, mouseenter, this::scheduleShow), | ||
| 190 | + bind(trigger, mouseleave, this::scheduleHide), | ||
| 191 | + bind(trigger, focusin, this::scheduleShow), | ||
| 192 | + bind(trigger, focusout, this::scheduleHide)); | ||
| 193 | + | ||
| 194 | + // event listeners on tooltip itself: | ||
| 195 | + // hovering into the tooltip cancels the hide timer, | ||
| 196 | + // hovering out of the tooltip schedules hide | ||
| 197 | + anchorHandlers = compose( | ||
| 198 | + bind(element(), mouseenter, this::cancelTimers), | ||
| 199 | + bind(element(), mouseleave, this::scheduleHide)); | ||
| 200 | + } else { | ||
| 201 | + logger.error("Unable to find trigger element for tooltip %o", element()); | ||
| 202 | + } | ||
| 203 | + } else { | ||
| 204 | + logger.error("No trigger element defined for tooltip %o", element()); | ||
| 205 | + } | ||
| 206 | + } | ||
| 207 | + | ||
| 208 | + @Override | ||
| 209 | + public void detach(MutationRecord mutationRecord) { | ||
| 210 | + cancelTimers(new Event("")); | ||
| 211 | + if (visible) { | ||
| 212 | + element().hidePopover(); | ||
| 213 | + visible = false; | ||
| 214 | + } | ||
| 215 | + if (anchorHandlers != null) { | ||
| 216 | + anchorHandlers.removeHandler(); | ||
| 217 | + } | ||
| 218 | + if (triggerHandlers != null) { | ||
| 219 | + triggerHandlers.removeHandler(); | ||
| 220 | + } | ||
| 221 | + if (trigger != null) { | ||
| 222 | + trigger.style.removeProperty("anchor-name"); | ||
| 223 | + trigger = null; | ||
| 224 | + } | ||
| 225 | + } | ||
| 226 | + | ||
| 227 | + // ------------------------------------------------------ builder | ||
| 228 | + | ||
| 229 | + public Tooltip2 entryDelay(int delay) { | ||
| 230 | + this.entryDelay = delay; | ||
| 231 | + return this; | ||
| 232 | + } | ||
| 233 | + | ||
| 234 | + public Tooltip2 exitDelay(int delay) { | ||
| 235 | + this.exitDelay = delay; | ||
| 236 | + return this; | ||
| 237 | + } | ||
| 238 | + | ||
| 239 | + public Tooltip2 distance(int distance) { | ||
| 240 | + this.distance = distance; | ||
| 241 | + return this; | ||
| 242 | + } | ||
| 243 | + | ||
| 244 | + public Tooltip2 leftAligned() { | ||
| 245 | + contentElement.classList.add(modifier(textAlignLeft)); | ||
| 246 | + return this; | ||
| 247 | + } | ||
| 248 | + | ||
| 249 | + public Tooltip2 placement(Placement placement) { | ||
| 250 | + this.placement = placement; | ||
| 251 | + return this; | ||
| 252 | + } | ||
| 253 | + | ||
| 254 | + public Tooltip2 trigger(String trigger) { | ||
| 255 | + this.triggerSupplier = () -> Elements.querySelector(document.body, By.selector(trigger)); | ||
| 256 | + return this; | ||
| 257 | + } | ||
| 258 | + | ||
| 259 | + public Tooltip2 trigger(By trigger) { | ||
| 260 | + this.triggerSupplier = () -> Elements.querySelector(document.body, trigger); | ||
| 261 | + return this; | ||
| 262 | + } | ||
| 263 | + | ||
| 264 | + public Tooltip2 trigger(HTMLElement trigger) { | ||
| 265 | + this.triggerSupplier = () -> trigger; | ||
| 266 | + return this; | ||
| 267 | + } | ||
| 268 | + | ||
| 269 | + public Tooltip2 trigger(Supplier<HTMLElement> trigger) { | ||
| 270 | + this.triggerSupplier = trigger; | ||
| 271 | + return this; | ||
| 272 | + } | ||
| 273 | + | ||
| 274 | + @Override | ||
| 275 | + public Tooltip2 that() { | ||
| 276 | + return this; | ||
| 277 | + } | ||
| 278 | + | ||
| 279 | + // ------------------------------------------------------ aria | ||
| 280 | + | ||
| 281 | + public Tooltip2 aria(TriggerAria aria) { | ||
| 282 | + this.aria = aria; | ||
| 283 | + return this; | ||
| 284 | + } | ||
| 285 | + | ||
| 286 | + // ------------------------------------------------------ events | ||
| 287 | + | ||
| 288 | + @Override | ||
| 289 | + public Tooltip2 onClose(CloseHandler<Tooltip2> closeHandler) { | ||
| 290 | + if (closeHandler != null) { | ||
| 291 | + this.closeHandler.add(closeHandler); | ||
| 292 | + } | ||
| 293 | + return this; | ||
| 294 | + } | ||
| 295 | + | ||
| 296 | + // ------------------------------------------------------ api | ||
| 297 | + | ||
| 298 | + public void show() { | ||
| 299 | + show(new Event("")); | ||
| 300 | + } | ||
| 301 | + | ||
| 302 | + public void show(Event event) { | ||
| 303 | + if (!visible) { | ||
| 304 | + element().showPopover(); | ||
| 305 | + visible = true; | ||
| 306 | + if (aria != none && trigger != null) { | ||
| 307 | + trigger.setAttribute(aria.attribute, id); | ||
| 308 | + } | ||
| 309 | + } | ||
| 310 | + } | ||
| 311 | + | ||
| 312 | + @Override | ||
| 313 | + public void close(Event event, boolean fireEvent) { | ||
| 314 | + if (shouldClose(this, closeHandler, event, fireEvent)) { | ||
| 315 | + if (visible) { | ||
| 316 | + element().hidePopover(); | ||
| 317 | + visible = false; | ||
| 318 | + if (aria != none && trigger != null) { | ||
| 319 | + trigger.removeAttribute(aria.attribute); | ||
| 320 | + } | ||
| 321 | + fireEvent(this, closeHandler, event, fireEvent); | ||
| 322 | + } | ||
| 323 | + } | ||
| 324 | + } | ||
| 325 | + | ||
| 326 | + @Override | ||
| 327 | + public String text() { | ||
| 328 | + return Elements.textNode(contentElement); | ||
| 329 | + } | ||
| 330 | + | ||
| 331 | + // ------------------------------------------------------ internal | ||
| 332 | + | ||
| 333 | + private void scheduleShow(Event event) { | ||
| 334 | + cancelTimers(event); | ||
| 335 | + showTimeout = setTimeout(e -> show(), entryDelay); | ||
| 336 | + } | ||
| 337 | + | ||
| 338 | + private void scheduleHide(Event event) { | ||
| 339 | + cancelTimers(event); | ||
| 340 | + hideTimeout = setTimeout(e -> close(new Event(""), true), exitDelay); | ||
| 341 | + } | ||
| 342 | + | ||
| 343 | + private void cancelTimers(Event event) { | ||
| 344 | + clearTimeout(showTimeout); | ||
| 345 | + clearTimeout(hideTimeout); | ||
| 346 | + } | ||
| 347 | + | ||
| 348 | + private void applyPlacement() { | ||
| 349 | + // remove old placement modifier | ||
| 350 | + for (String mod : Placement.modifiers) { | ||
| 351 | + classList().remove(mod); | ||
| 352 | + } | ||
| 353 | + | ||
| 354 | + // apply placement modifier CSS class | ||
| 355 | + // controls both arrow direction (PF CSS) and position-area (core.css) | ||
| 356 | + if (placement.modifier != null && !placement.modifier.isEmpty()) { | ||
| 357 | + classList().add(placement.modifier); | ||
| 358 | + } | ||
| 359 | + } | ||
| 360 | + } | ||
| Back | FazBrowse Home | New Git URL |
0 commit comments