PageSourceSearch

https://veefriends.com/_content/VeeFriends.Web.Www/Pages/FeatureSe…/CharacterIntroduction.fl8gflk6tt.razor.js

js veefriends.com collected 2026-09-25 14:34:22 UTC 4,242 bytes, 148 lines download raw bytes

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.