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
|
// Click handlers
|
||||||
menuEl.querySelectorAll('.slash-menu-item').forEach((el) => {
|
menuEl.querySelectorAll('.slash-menu-item').forEach((el) => {
|
||||||
el.addEventListener('mousedown', (e) => {
|
el.addEventListener('pointerdown', (e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
const idx = parseInt((el as HTMLElement).dataset.index || '0');
|
const idx = parseInt((el as HTMLElement).dataset.index || '0');
|
||||||
executeItem(idx);
|
executeItem(idx);
|
||||||
});
|
});
|
||||||
el.addEventListener('mouseenter', () => {
|
el.addEventListener('pointerenter', () => {
|
||||||
selectedIndex = parseInt((el as HTMLElement).dataset.index || '0');
|
selectedIndex = parseInt((el as HTMLElement).dataset.index || '0');
|
||||||
updateMenuContent();
|
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",
|
border: "1px solid rgba(255,255,255,0.15)", borderRadius: "6px",
|
||||||
cursor: "pointer", backdropFilter: "blur(4px)",
|
cursor: "pointer", backdropFilter: "blur(4px)",
|
||||||
});
|
});
|
||||||
btn.addEventListener("mouseenter", () => { btn.style.background = "rgba(255,255,255,0.15)"; });
|
btn.addEventListener("pointerenter", () => { btn.style.background = "rgba(255,255,255,0.15)"; });
|
||||||
btn.addEventListener("mouseleave", () => { btn.style.background = "rgba(255,255,255,0.08)"; });
|
btn.addEventListener("pointerleave", () => { btn.style.background = "rgba(255,255,255,0.08)"; });
|
||||||
btn.addEventListener("click", () => {
|
btn.addEventListener("click", () => {
|
||||||
svg.transition().duration(500).call(zoom.transform, d3.zoomIdentity);
|
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)`)
|
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")
|
.attr("opacity", 0.85).style("cursor", "pointer").style("transition", "opacity 0.2s, filter 0.2s")
|
||||||
.on("mouseover", (event: any) => showTxTooltip(event, tx, prevBalance))
|
.on("pointerenter", (event: any) => showTxTooltip(event, tx, prevBalance))
|
||||||
.on("mousemove", (event: any) => moveTip(event))
|
.on("pointermove", (event: any) => moveTip(event))
|
||||||
.on("mouseout", () => { tooltip.style.display = "none"; });
|
.on("pointerleave", () => { tooltip.style.display = "none"; });
|
||||||
} else {
|
} else {
|
||||||
const startY = riverBottomBefore;
|
const startY = riverBottomBefore;
|
||||||
const endY = startY + flowHeight;
|
const endY = startY + flowHeight;
|
||||||
|
|
@ -318,9 +318,9 @@ export function renderTimeline(
|
||||||
|
|
||||||
contentGroup.append("path").attr("d", path.toString()).attr("fill", `url(#${id}-outflow)`)
|
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")
|
.attr("opacity", 0.85).style("cursor", "pointer").style("transition", "opacity 0.2s, filter 0.2s")
|
||||||
.on("mouseover", (event: any) => showTxTooltip(event, tx, prevBalance))
|
.on("pointerenter", (event: any) => showTxTooltip(event, tx, prevBalance))
|
||||||
.on("mousemove", (event: any) => moveTip(event))
|
.on("pointermove", (event: any) => moveTip(event))
|
||||||
.on("mouseout", () => { tooltip.style.display = "none"; });
|
.on("pointerleave", () => { tooltip.style.display = "none"; });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -330,7 +330,7 @@ export function renderTimeline(
|
||||||
.attr("x", scale.range()[0] - 50).attr("y", centerY - 100)
|
.attr("x", scale.range()[0] - 50).attr("y", centerY - 100)
|
||||||
.attr("width", scale.range()[1] - scale.range()[0] + 100).attr("height", 200)
|
.attr("width", scale.range()[1] - scale.range()[0] + 100).attr("height", 200)
|
||||||
.attr("fill", "transparent").style("cursor", "crosshair")
|
.attr("fill", "transparent").style("cursor", "crosshair")
|
||||||
.on("mousemove", function(event: any) {
|
.on("pointermove", function(event: any) {
|
||||||
const [mouseX] = d3.pointer(event);
|
const [mouseX] = d3.pointer(event);
|
||||||
const hoveredDate = scale.invert(mouseX);
|
const hoveredDate = scale.invert(mouseX);
|
||||||
let balAtPoint = 0;
|
let balAtPoint = 0;
|
||||||
|
|
@ -357,7 +357,7 @@ export function renderTimeline(
|
||||||
tooltip.style.display = "block";
|
tooltip.style.display = "block";
|
||||||
moveTip(event);
|
moveTip(event);
|
||||||
})
|
})
|
||||||
.on("mouseout", function() {
|
.on("pointerleave", function() {
|
||||||
riverHoverGroup.selectAll(".bal-ind").remove();
|
riverHoverGroup.selectAll(".bal-ind").remove();
|
||||||
tooltip.style.display = "none";
|
tooltip.style.display = "none";
|
||||||
});
|
});
|
||||||
|
|
@ -627,8 +627,8 @@ export function renderSankey(
|
||||||
.attr("stroke-opacity", 0.4)
|
.attr("stroke-opacity", 0.4)
|
||||||
.attr("stroke-width", (d: any) => Math.max(1, d.width))
|
.attr("stroke-width", (d: any) => Math.max(1, d.width))
|
||||||
.style("transition", "stroke-opacity 0.2s")
|
.style("transition", "stroke-opacity 0.2s")
|
||||||
.on("mouseover", function(this: any) { d3.select(this).attr("stroke-opacity", 0.7); })
|
.on("pointerenter", 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("pointerleave", function(this: any) { d3.select(this).attr("stroke-opacity", 0.4); })
|
||||||
.append("title")
|
.append("title")
|
||||||
.text((d: any) => `${d.source.name} -> ${d.target.name}\n${d.value.toLocaleString()} ${d.token}`);
|
.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 };
|
this.#lastCursor = { x: e.clientX, y: e.clientY };
|
||||||
};
|
};
|
||||||
|
|
||||||
#startMouseTracking() {
|
#startMouseTracking() {
|
||||||
document.addEventListener('mousemove', this.#mouseHandler, { passive: true });
|
document.addEventListener('pointermove', this.#pointerHandler, { passive: true });
|
||||||
// Broadcast at 15Hz
|
// Broadcast at 15Hz
|
||||||
this.#mouseMoveTimer = setInterval(() => {
|
this.#mouseMoveTimer = setInterval(() => {
|
||||||
this.#broadcastPresence(this.#lastCursor, undefined);
|
this.#broadcastPresence(this.#lastCursor, undefined);
|
||||||
|
|
@ -407,7 +407,7 @@ export class RStackCollabOverlay extends HTMLElement {
|
||||||
}
|
}
|
||||||
|
|
||||||
#stopMouseTracking() {
|
#stopMouseTracking() {
|
||||||
document.removeEventListener('mousemove', this.#mouseHandler);
|
document.removeEventListener('pointermove', this.#pointerHandler);
|
||||||
if (this.#mouseMoveTimer) clearInterval(this.#mouseMoveTimer);
|
if (this.#mouseMoveTimer) clearInterval(this.#mouseMoveTimer);
|
||||||
this.#mouseMoveTimer = null;
|
this.#mouseMoveTimer = null;
|
||||||
}
|
}
|
||||||
|
|
@ -429,18 +429,18 @@ export class RStackCollabOverlay extends HTMLElement {
|
||||||
|
|
||||||
#startFocusTracking() {
|
#startFocusTracking() {
|
||||||
document.addEventListener('focusin', this.#focusHandler, { passive: true });
|
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 });
|
document.addEventListener('focusout', this.#blurHandler, { passive: true });
|
||||||
}
|
}
|
||||||
|
|
||||||
#stopFocusTracking() {
|
#stopFocusTracking() {
|
||||||
document.removeEventListener('focusin', this.#focusHandler);
|
document.removeEventListener('focusin', this.#focusHandler);
|
||||||
document.removeEventListener('click', this.#clickHandler);
|
document.removeEventListener('pointerdown', this.#clickHandler);
|
||||||
document.removeEventListener('focusout', this.#blurHandler);
|
document.removeEventListener('focusout', this.#blurHandler);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Also track clicks on data-collab-id (many elements aren't focusable)
|
// Also track taps/clicks on data-collab-id (many elements aren't focusable)
|
||||||
#clickHandler = (e: MouseEvent) => {
|
#clickHandler = (e: PointerEvent) => {
|
||||||
const real = (e.composedPath()[0] as HTMLElement);
|
const real = (e.composedPath()[0] as HTMLElement);
|
||||||
const target = real?.closest?.('[data-collab-id]');
|
const target = real?.closest?.('[data-collab-id]');
|
||||||
if (target) {
|
if (target) {
|
||||||
|
|
|
||||||
|
|
@ -83,7 +83,7 @@
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.toolbar-group-toggle:hover {
|
.toolbar-group-toggle:hover, .toolbar-group-toggle:active {
|
||||||
background: var(--rs-toolbar-btn-hover);
|
background: var(--rs-toolbar-btn-hover);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -136,7 +136,7 @@
|
||||||
touch-action: manipulation;
|
touch-action: manipulation;
|
||||||
}
|
}
|
||||||
|
|
||||||
.toolbar-dropdown button:hover {
|
.toolbar-dropdown button:hover, .toolbar-dropdown button:active {
|
||||||
background: var(--rs-toolbar-btn-bg);
|
background: var(--rs-toolbar-btn-bg);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -281,7 +281,7 @@
|
||||||
touch-action: manipulation;
|
touch-action: manipulation;
|
||||||
}
|
}
|
||||||
|
|
||||||
#toolbar > button:hover {
|
#toolbar > button:hover, #toolbar > button:active {
|
||||||
background: var(--rs-toolbar-btn-hover);
|
background: var(--rs-toolbar-btn-hover);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -321,11 +321,15 @@
|
||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Show label outside toolbar on hover or when group is open */
|
/* Show label outside toolbar on hover (only on hover-capable devices) or when group is open */
|
||||||
.toolbar-group:hover > .toolbar-group-toggle .tg-label,
|
|
||||||
.toolbar-group.open > .toolbar-group-toggle .tg-label {
|
.toolbar-group.open > .toolbar-group-toggle .tg-label {
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
|
@media (hover: hover) {
|
||||||
|
.toolbar-group:hover > .toolbar-group-toggle .tg-label {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/* Collapse/expand toggle — at bottom of toolbar */
|
/* Collapse/expand toggle — at bottom of toolbar */
|
||||||
#toolbar-collapse {
|
#toolbar-collapse {
|
||||||
|
|
@ -347,7 +351,7 @@
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
#toolbar-collapse:hover {
|
#toolbar-collapse:hover, #toolbar-collapse:active {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
background: var(--rs-toolbar-btn-bg);
|
background: var(--rs-toolbar-btn-bg);
|
||||||
color: var(--rs-text-primary);
|
color: var(--rs-text-primary);
|
||||||
|
|
@ -401,7 +405,7 @@
|
||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
#bottom-toolbar .tool-btn:hover {
|
#bottom-toolbar .tool-btn:hover, #bottom-toolbar .tool-btn:active {
|
||||||
background: var(--rs-toolbar-btn-hover);
|
background: var(--rs-toolbar-btn-hover);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -449,7 +453,7 @@
|
||||||
opacity: 0.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);
|
background: var(--rs-toolbar-btn-hover);
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
}
|
}
|
||||||
|
|
@ -506,7 +510,7 @@
|
||||||
transition: background 0.15s;
|
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);
|
background: var(--rs-toolbar-btn-hover);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -563,7 +567,7 @@
|
||||||
transition: background 0.15s;
|
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);
|
background: var(--rs-toolbar-btn-hover);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -642,7 +646,7 @@
|
||||||
touch-action: manipulation;
|
touch-action: manipulation;
|
||||||
}
|
}
|
||||||
|
|
||||||
.corner-btn:hover {
|
.corner-btn:hover, .corner-btn:active {
|
||||||
background: var(--rs-toolbar-btn-hover);
|
background: var(--rs-toolbar-btn-hover);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -886,7 +890,7 @@
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
#shape-context-menu button:hover {
|
#shape-context-menu button:hover, #shape-context-menu button:active {
|
||||||
background: var(--rs-bg-hover);
|
background: var(--rs-bg-hover);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -894,7 +898,7 @@
|
||||||
color: var(--rs-error);
|
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);
|
background: rgba(239, 68, 68, 0.1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -935,7 +939,7 @@
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
.submenu button:hover {
|
.submenu button:hover, .submenu button:active {
|
||||||
background: var(--rs-bg-hover);
|
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;
|
menu.innerHTML = items;
|
||||||
document.body.appendChild(menu);
|
document.body.appendChild(menu);
|
||||||
|
|
||||||
// Hover effect
|
// Hover/press effect
|
||||||
menu.querySelectorAll("button").forEach(b => {
|
menu.querySelectorAll("button").forEach(b => {
|
||||||
b.addEventListener("mouseenter", () => b.style.background = "#f1f5f9");
|
b.addEventListener("pointerenter", () => b.style.background = "#f1f5f9");
|
||||||
b.addEventListener("mouseleave", () => b.style.background = "none");
|
b.addEventListener("pointerleave", () => b.style.background = "none");
|
||||||
});
|
});
|
||||||
|
|
||||||
const closeMenu = (ev) => {
|
const closeMenu = (ev) => {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue