mirror of
https://github.com/warmbly/warmbly.git
synced 2026-09-13 08:05:05 +00:00
feat: settle who owns a cell's title attribute between React and the clipped-text tooltip helper, matching on the exact string the helper last wrote rather than on the element, so a caller that starts supplying a title after the helper has already titled that node keeps it, and giving a lead's status label the failure reason as a React-managed title so a transition into failed while the pointer rests on the pill overwrites the status word the helper left there instead of stranding it over the reason for the life of the node
This commit is contained in:
@@ -1625,7 +1625,10 @@ function LeadStatusPill({ lead }: { lead?: ContactCampaignProgress | null }) {
|
||||
<Icon className="w-3 h-3 shrink-0" />
|
||||
)}
|
||||
<span className="sr-only">{meta.label}</span>
|
||||
<span aria-hidden className="hidden sm:inline truncate" {...(title ? {} : clippedTitle)}>
|
||||
{/* The reason, when there is one, is worth more than the word it
|
||||
covers — and React owning the attribute is what clears any word
|
||||
the tooltip helper left here before the lead changed state. */}
|
||||
<span aria-hidden className="hidden sm:inline truncate" title={title} {...(title ? {} : clippedTitle)}>
|
||||
{meta.label}
|
||||
</span>
|
||||
</span>
|
||||
|
||||
@@ -58,6 +58,19 @@ describe("clippedTitle", () => {
|
||||
expect(el.getAttribute("title")).toBe("Could not send: mailbox rejected the recipient");
|
||||
});
|
||||
|
||||
// A caller can start supplying a title after we have already titled the
|
||||
// element, so ownership has to be judged on the value, not the element.
|
||||
it("stands down when a caller titles an element it had titled", () => {
|
||||
const el = span("Queued", 420, 160);
|
||||
fire(clippedTitle.onMouseEnter, el);
|
||||
expect(el.getAttribute("title")).toBe("Queued");
|
||||
el.title = "Could not send: mailbox rejected the recipient";
|
||||
fire(clippedTitle.onMouseEnter, el);
|
||||
expect(el.getAttribute("title")).toBe("Could not send: mailbox rejected the recipient");
|
||||
fire(clippedTitle.onMouseLeave, el);
|
||||
expect(el.getAttribute("title")).toBe("Could not send: mailbox rejected the recipient");
|
||||
});
|
||||
|
||||
// Nothing removes the attribute on re-render, so it must not outlive the
|
||||
// hover that set it: a row whose text changes underneath a resting cursor
|
||||
// would otherwise show the previous value.
|
||||
|
||||
@@ -7,19 +7,22 @@
|
||||
|
||||
import type { MouseEvent } from "react";
|
||||
|
||||
// Only ever remove a title this module put there. An element with its own
|
||||
// `title` prop keeps it: React rewrites that attribute only when the prop value
|
||||
// changes, so taking it away once would lose it for good.
|
||||
const ours = new WeakSet<HTMLElement>();
|
||||
// The exact string this module last wrote on an element. Anything else in the
|
||||
// attribute belongs to the caller and is left alone: React rewrites a `title`
|
||||
// prop only when its value changes, so clobbering it once would lose it for
|
||||
// good. Matching on the value rather than the element matters because a caller
|
||||
// can start supplying a title after we have already titled it.
|
||||
const ours = new WeakMap<HTMLElement, string>();
|
||||
|
||||
function titleWhenClipped(e: MouseEvent<HTMLElement>) {
|
||||
const el = e.currentTarget;
|
||||
if (el.hasAttribute("title") && !ours.has(el)) return;
|
||||
const current = el.getAttribute("title");
|
||||
if (current !== null && current !== ours.get(el)) return;
|
||||
const text = el.textContent ?? "";
|
||||
if (text && el.scrollWidth > el.clientWidth) {
|
||||
el.title = text;
|
||||
ours.add(el);
|
||||
} else if (ours.has(el)) {
|
||||
ours.set(el, text);
|
||||
} else if (current !== null) {
|
||||
el.removeAttribute("title");
|
||||
ours.delete(el);
|
||||
}
|
||||
@@ -27,7 +30,7 @@ function titleWhenClipped(e: MouseEvent<HTMLElement>) {
|
||||
|
||||
function clearTitle(e: MouseEvent<HTMLElement>) {
|
||||
const el = e.currentTarget;
|
||||
if (!ours.has(el)) return;
|
||||
if (el.getAttribute("title") !== ours.get(el)) return;
|
||||
el.removeAttribute("title");
|
||||
ours.delete(el);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user