fix(touch): pointer event parity for slash commands, collab overlay, canvas, and wallet charts
Convert remaining mouse-only event listeners to Pointer Events so touch and pen inputs work identically to mouse across rApps and canvas: - rnotes slash-command: mousedown/mouseenter → pointerdown/pointerenter - collab overlay: mousemove → pointermove for cursor broadcast, click → pointerdown for collab-id tracking - canvas.html: toolbar label reveal gated behind @media (hover: hover), :active fallbacks on 12 button selectors, JS mouseenter/mouseleave → pointerenter/pointerleave - wallet-viz D3 charts: all mouseover/mousemove/mouseout → pointerenter/pointermove/pointerleave on transaction paths, balance area, Sankey links, and reset button Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
690e4dedb4
commit
5bc64a5b4e
|
|
@ -172,12 +172,12 @@ export function createSlashCommandPlugin(editor: Editor, shadowRoot: ShadowRoot)
|
|||
|
||||
// Click handlers
|
||||
menuEl.querySelectorAll('.slash-menu-item').forEach((el) => {
|
||||
el.addEventListener('mousedown', (e) => {
|
||||
el.addEventListener('pointerdown', (e) => {
|
||||
e.preventDefault();
|
||||
const idx = parseInt((el as HTMLElement).dataset.index || '0');
|
||||
executeItem(idx);
|
||||
});
|
||||
el.addEventListener('mouseenter', () => {
|
||||
el.addEventListener('pointerenter', () => {
|
||||
selectedIndex = parseInt((el as HTMLElement).dataset.index || '0');
|
||||
updateMenuContent();
|
||||
});
|
||||
|
|
|
|||
|
|
@ -70,8 +70,8 @@ function addResetButton(parent: HTMLElement, svg: any, zoom: any): void {
|
|||
border: "1px solid rgba(255,255,255,0.15)", borderRadius: "6px",
|
||||
cursor: "pointer", backdropFilter: "blur(4px)",
|
||||
});
|
||||
btn.addEventListener("mouseenter", () => { btn.style.background = "rgba(255,255,255,0.15)"; });
|
||||
btn.addEventListener("mouseleave", () => { btn.style.background = "rgba(255,255,255,0.08)"; });
|
||||
btn.addEventListener("pointerenter", () => { btn.style.background = "rgba(255,255,255,0.15)"; });
|
||||
btn.addEventListener("pointerleave", () => { btn.style.background = "rgba(255,255,255,0.08)"; });
|
||||
btn.addEventListener("click", () => {
|
||||
svg.transition().duration(500).call(zoom.transform, d3.zoomIdentity);
|
||||
});
|
||||
|
|
@ -293,9 +293,9 @@ export function renderTimeline(
|
|||
|
||||
contentGroup.append("path").attr("d", path.toString()).attr("fill", `url(#${id}-inflow)`)
|
||||
.attr("opacity", 0.85).style("cursor", "pointer").style("transition", "opacity 0.2s, filter 0.2s")
|
||||
.on("mouseover", (event: any) => showTxTooltip(event, tx, prevBalance))
|
||||
.on("mousemove", (event: any) => moveTip(event))
|
||||
.on("mouseout", () => { tooltip.style.display = "none"; });
|
||||
.on("pointerenter", (event: any) => showTxTooltip(event, tx, prevBalance))
|
||||
.on("pointermove", (event: any) => moveTip(event))
|
||||
.on("pointerleave", () => { tooltip.style.display = "none"; });
|
||||
} else {
|
||||
const startY = riverBottomBefore;
|
||||
const endY = startY + flowHeight;
|
||||
|
|
@ -318,9 +318,9 @@ export function renderTimeline(
|
|||
|
||||
contentGroup.append("path").attr("d", path.toString()).attr("fill", `url(#${id}-outflow)`)
|
||||
.attr("opacity", 0.85).style("cursor", "pointer").style("transition", "opacity 0.2s, filter 0.2s")
|
||||
.on("mouseover", (event: any) => showTxTooltip(event, tx, prevBalance))
|
||||
.on("mousemove", (event: any) => moveTip(event))
|
||||
.on("mouseout", () => { tooltip.style.display = "none"; });
|
||||
.on("pointerenter", (event: any) => showTxTooltip(event, tx, prevBalance))
|
||||
.on("pointermove", (event: any) => moveTip(event))
|
||||
.on("pointerleave", () => { tooltip.style.display = "none"; });
|
||||
}
|
||||
});
|
||||
|
||||
|
|
@ -330,7 +330,7 @@ export function renderTimeline(
|
|||
.attr("x", scale.range()[0] - 50).attr("y", centerY - 100)
|
||||
.attr("width", scale.range()[1] - scale.range()[0] + 100).attr("height", 200)
|
||||
.attr("fill", "transparent").style("cursor", "crosshair")
|
||||
.on("mousemove", function(event: any) {
|
||||
.on("pointermove", function(event: any) {
|
||||
const [mouseX] = d3.pointer(event);
|
||||
const hoveredDate = scale.invert(mouseX);
|
||||
let balAtPoint = 0;
|
||||
|
|
@ -357,7 +357,7 @@ export function renderTimeline(
|
|||
tooltip.style.display = "block";
|
||||
moveTip(event);
|
||||
})
|
||||
.on("mouseout", function() {
|
||||
.on("pointerleave", function() {
|
||||
riverHoverGroup.selectAll(".bal-ind").remove();
|
||||
tooltip.style.display = "none";
|
||||
});
|
||||
|
|
@ -627,8 +627,8 @@ export function renderSankey(
|
|||
.attr("stroke-opacity", 0.4)
|
||||
.attr("stroke-width", (d: any) => Math.max(1, d.width))
|
||||
.style("transition", "stroke-opacity 0.2s")
|
||||
.on("mouseover", function(this: any) { d3.select(this).attr("stroke-opacity", 0.7); })
|
||||
.on("mouseout", function(this: any) { d3.select(this).attr("stroke-opacity", 0.4); })
|
||||
.on("pointerenter", function(this: any) { d3.select(this).attr("stroke-opacity", 0.7); })
|
||||
.on("pointerleave", function(this: any) { d3.select(this).attr("stroke-opacity", 0.4); })
|
||||
.append("title")
|
||||
.text((d: any) => `${d.source.name} -> ${d.target.name}\n${d.value.toLocaleString()} ${d.token}`);
|
||||
|
||||
|
|
|
|||
|
|
@ -392,14 +392,14 @@ export class RStackCollabOverlay extends HTMLElement {
|
|||
});
|
||||
}
|
||||
|
||||
// ── Mouse tracking (15Hz throttle) ──
|
||||
// ── Pointer tracking (15Hz throttle) — covers mouse, touch, and pen ──
|
||||
|
||||
#mouseHandler = (e: MouseEvent) => {
|
||||
#pointerHandler = (e: PointerEvent) => {
|
||||
this.#lastCursor = { x: e.clientX, y: e.clientY };
|
||||
};
|
||||
|
||||
#startMouseTracking() {
|
||||
document.addEventListener('mousemove', this.#mouseHandler, { passive: true });
|
||||
document.addEventListener('pointermove', this.#pointerHandler, { passive: true });
|
||||
// Broadcast at 15Hz
|
||||
this.#mouseMoveTimer = setInterval(() => {
|
||||
this.#broadcastPresence(this.#lastCursor, undefined);
|
||||
|
|
@ -407,7 +407,7 @@ export class RStackCollabOverlay extends HTMLElement {
|
|||
}
|
||||
|
||||
#stopMouseTracking() {
|
||||
document.removeEventListener('mousemove', this.#mouseHandler);
|
||||
document.removeEventListener('pointermove', this.#pointerHandler);
|
||||
if (this.#mouseMoveTimer) clearInterval(this.#mouseMoveTimer);
|
||||
this.#mouseMoveTimer = null;
|
||||
}
|
||||
|
|
@ -429,18 +429,18 @@ export class RStackCollabOverlay extends HTMLElement {
|
|||
|
||||
#startFocusTracking() {
|
||||
document.addEventListener('focusin', this.#focusHandler, { passive: true });
|
||||
document.addEventListener('click', this.#clickHandler, { passive: true });
|
||||
document.addEventListener('pointerdown', this.#clickHandler, { passive: true });
|
||||
document.addEventListener('focusout', this.#blurHandler, { passive: true });
|
||||
}
|
||||
|
||||
#stopFocusTracking() {
|
||||
document.removeEventListener('focusin', this.#focusHandler);
|
||||
document.removeEventListener('click', this.#clickHandler);
|
||||
document.removeEventListener('pointerdown', this.#clickHandler);
|
||||
document.removeEventListener('focusout', this.#blurHandler);
|
||||
}
|
||||
|
||||
// Also track clicks on data-collab-id (many elements aren't focusable)
|
||||
#clickHandler = (e: MouseEvent) => {
|
||||
// Also track taps/clicks on data-collab-id (many elements aren't focusable)
|
||||
#clickHandler = (e: PointerEvent) => {
|
||||
const real = (e.composedPath()[0] as HTMLElement);
|
||||
const target = real?.closest?.('[data-collab-id]');
|
||||
if (target) {
|
||||
|
|
|
|||
|
|
@ -83,7 +83,7 @@
|
|||
justify-content: center;
|
||||
}
|
||||
|
||||
.toolbar-group-toggle:hover {
|
||||
.toolbar-group-toggle:hover, .toolbar-group-toggle:active {
|
||||
background: var(--rs-toolbar-btn-hover);
|
||||
}
|
||||
|
||||
|
|
@ -136,7 +136,7 @@
|
|||
touch-action: manipulation;
|
||||
}
|
||||
|
||||
.toolbar-dropdown button:hover {
|
||||
.toolbar-dropdown button:hover, .toolbar-dropdown button:active {
|
||||
background: var(--rs-toolbar-btn-bg);
|
||||
}
|
||||
|
||||
|
|
@ -281,7 +281,7 @@
|
|||
touch-action: manipulation;
|
||||
}
|
||||
|
||||
#toolbar > button:hover {
|
||||
#toolbar > button:hover, #toolbar > button:active {
|
||||
background: var(--rs-toolbar-btn-hover);
|
||||
}
|
||||
|
||||
|
|
@ -321,11 +321,15 @@
|
|||
position: relative;
|
||||
}
|
||||
|
||||
/* Show label outside toolbar on hover or when group is open */
|
||||
.toolbar-group:hover > .toolbar-group-toggle .tg-label,
|
||||
/* Show label outside toolbar on hover (only on hover-capable devices) or when group is open */
|
||||
.toolbar-group.open > .toolbar-group-toggle .tg-label {
|
||||
display: block;
|
||||
}
|
||||
@media (hover: hover) {
|
||||
.toolbar-group:hover > .toolbar-group-toggle .tg-label {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
/* Collapse/expand toggle — at bottom of toolbar */
|
||||
#toolbar-collapse {
|
||||
|
|
@ -347,7 +351,7 @@
|
|||
justify-content: center;
|
||||
}
|
||||
|
||||
#toolbar-collapse:hover {
|
||||
#toolbar-collapse:hover, #toolbar-collapse:active {
|
||||
opacity: 1;
|
||||
background: var(--rs-toolbar-btn-bg);
|
||||
color: var(--rs-text-primary);
|
||||
|
|
@ -401,7 +405,7 @@
|
|||
position: relative;
|
||||
}
|
||||
|
||||
#bottom-toolbar .tool-btn:hover {
|
||||
#bottom-toolbar .tool-btn:hover, #bottom-toolbar .tool-btn:active {
|
||||
background: var(--rs-toolbar-btn-hover);
|
||||
}
|
||||
|
||||
|
|
@ -449,7 +453,7 @@
|
|||
opacity: 0.7;
|
||||
}
|
||||
|
||||
#recent-tools .recent-tool-btn:hover {
|
||||
#recent-tools .recent-tool-btn:hover, #recent-tools .recent-tool-btn:active {
|
||||
background: var(--rs-toolbar-btn-hover);
|
||||
opacity: 1;
|
||||
}
|
||||
|
|
@ -506,7 +510,7 @@
|
|||
transition: background 0.15s;
|
||||
}
|
||||
|
||||
#tool-plus-menu button:hover {
|
||||
#tool-plus-menu button:hover, #tool-plus-menu button:active {
|
||||
background: var(--rs-toolbar-btn-hover);
|
||||
}
|
||||
|
||||
|
|
@ -563,7 +567,7 @@
|
|||
transition: background 0.15s;
|
||||
}
|
||||
|
||||
#arrow-style-menu button:hover {
|
||||
#arrow-style-menu button:hover, #arrow-style-menu button:active {
|
||||
background: var(--rs-toolbar-btn-hover);
|
||||
}
|
||||
|
||||
|
|
@ -642,7 +646,7 @@
|
|||
touch-action: manipulation;
|
||||
}
|
||||
|
||||
.corner-btn:hover {
|
||||
.corner-btn:hover, .corner-btn:active {
|
||||
background: var(--rs-toolbar-btn-hover);
|
||||
}
|
||||
|
||||
|
|
@ -886,7 +890,7 @@
|
|||
cursor: pointer;
|
||||
}
|
||||
|
||||
#shape-context-menu button:hover {
|
||||
#shape-context-menu button:hover, #shape-context-menu button:active {
|
||||
background: var(--rs-bg-hover);
|
||||
}
|
||||
|
||||
|
|
@ -894,7 +898,7 @@
|
|||
color: var(--rs-error);
|
||||
}
|
||||
|
||||
#shape-context-menu button.danger:hover {
|
||||
#shape-context-menu button.danger:hover, #shape-context-menu button.danger:active {
|
||||
background: rgba(239, 68, 68, 0.1);
|
||||
}
|
||||
|
||||
|
|
@ -935,7 +939,7 @@
|
|||
cursor: pointer;
|
||||
}
|
||||
|
||||
.submenu button:hover {
|
||||
.submenu button:hover, .submenu button:active {
|
||||
background: var(--rs-bg-hover);
|
||||
}
|
||||
|
||||
|
|
@ -5949,10 +5953,10 @@ Use real coordinates, YYYY-MM-DD dates, ISO currency codes. Ask clarifying quest
|
|||
menu.innerHTML = items;
|
||||
document.body.appendChild(menu);
|
||||
|
||||
// Hover effect
|
||||
// Hover/press effect
|
||||
menu.querySelectorAll("button").forEach(b => {
|
||||
b.addEventListener("mouseenter", () => b.style.background = "#f1f5f9");
|
||||
b.addEventListener("mouseleave", () => b.style.background = "none");
|
||||
b.addEventListener("pointerenter", () => b.style.background = "#f1f5f9");
|
||||
b.addEventListener("pointerleave", () => b.style.background = "none");
|
||||
});
|
||||
|
||||
const closeMenu = (ev) => {
|
||||
|
|
|
|||
Loading…
Reference in New Issue