1/** 2 * Character marquee tap handling. 3 * 4 * BlazorFastMarquee's drag support takes pointer capture on the row container, so the browser 5 * dispatches the click at that container instead of the character link and Blazor's own click 6 * handler on the link never runs. Pointerdown still targets the link, so remember it there and 7 * open the character modal on release, once it is clear the gesture was a tap and not a drag of 8 * the row. 9 * 10 * The links also carry draggable="false": an anchor is draggable by default, so the smallest 11 * mouse movement while pressed used to start a native link drag, which cancels the gesture 12 * outright and swallows both the pointerup and the click. 13 * 14 * Modifier and non-primary clicks are left alone so the browser can still open the character 15 * page in a new tab from the link's href. 16 */ 17export function Component(dotnetContext, itemRef) { 18 // Same threshold the marquee uses, so no gesture counts as a drag there and a tap here. 19 const DRAG_THRESHOLD_PX = 5; 20 21 let startX = 0; 22 let startY = 0; 23 let startSlug = null; 24 let dragged = false; 25 let handledTap = false; 26 27 const isModifiedEvent = (e) => 28 e.ctrlKey || e.metaKey || e.shiftKey || e.altKey; 29 30 const findLink = (e) => 31 e.target instanceof Element 32 ? e.target.closest("a[data-character-link]") 33 : null; 34 35 const openCharacter = (slug) => { 36 if (!slug) { 37 return; 38 } 39 40 dotnetContext.invokeMethodAsync("OpenCharacter", slug); 41 }; 42 43 const onPointerDown = (e) => { 44 startSlug = null; 45 dragged = false; 46 handledTap = false; 47 48 // Middle and right buttons never get pointer capture from the marquee, so the browser 49 // still handles those natively. 50 if (e.pointerType === "mouse" && e.button !== 0) { 51 return; 52 } 53 54 const link = findLink(e); 55 56 if (!link) { 57 return; 58 } 59 60 if (isModifiedEvent(e)) { 61 // Keep the marquee from capturing the pointer and retargeting the click to its row. 62 // Do not preventDefault: the browser must still perform the modified link action. 63 e.stopPropagation(); 64 return; 65 } 66 67 startSlug = link.dataset.characterSlug ?? null; 68 startX = e.clientX; 69 startY = e.clientY; 70 }; 71 72 const onPointerMove = (e) => { 73 if (!startSlug || dragged) { 74 return; 75 } 76 77 if ( 78 Math.abs(e.clientX - startX) > DRAG_THRESHOLD_PX || 79 Math.abs(e.clientY - startY) > DRAG_THRESHOLD_PX 80 ) { 81 dragged = true; 82 } 83 }; 84 85 const onPointerUp = () => { 86 const slug = startSlug; 87 const wasDragged = dragged; 88 89 startSlug = null; 90 dragged = false; 91 92 if (!slug || wasDragged) { 93 return; 94 } 95 96 // Open the character that was pressed rather than whatever sits under the release point. 97 // Touch has no hover to pause the row, so the tile drifts during the tap. 98 handledTap = true; 99 openCharacter(slug); 100 }; 101 102 const onPointerCancel = () => { 103 startSlug = null; 104 dragged = false; 105 handledTap = false; 106 }; 107 108 const onClick = (e) => { 109 // The click trailing a gesture already handled on pointerup only has to be stopped. 110 if (handledTap) { 111 handledTap = false; 112 e.preventDefault(); 113 e.stopImmediatePropagation(); 114 return; 115 } 116 117 if (isModifiedEvent(e)) { 118 return; 119 } 120 121 const link = findLink(e); 122 123 if (!link) { 124 return; 125 } 126 127 // Keyboard activation fires a click on the link without any pointer events first. 128 e.preventDefault(); 129 e.stopImmediatePropagation(); 130 openCharacter(link.dataset.characterSlug ?? null); 131 }; 132 133 itemRef.addEventListener("pointerdown", onPointerDown, true); 134 itemRef.addEventListener("pointermove", onPointerMove, true); 135 itemRef.addEventListener("pointerup", onPointerUp, true); 136 itemRef.addEventListener("pointercancel", onPointerCancel, true); 137 itemRef.addEventListener("click", onClick, true); 138 139 return { 140 dispose() { 141 itemRef.removeEventListener("pointerdown", onPointerDown, true); 142 itemRef.removeEventListener("pointermove", onPointerMove, true); 143 itemRef.removeEventListener("pointerup", onPointerUp, true); 144 itemRef.removeEventListener("pointercancel", onPointerCancel, true); 145 itemRef.removeEventListener("click", onClick, true); 146 }, 147 }; 148}
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.