openguessr-ui 0.4.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -441,6 +441,8 @@ Options:
441
441
  - `zIndex`: Tooltip z-index
442
442
  - `mobile`: Set to false to hide the tooltip on mobile
443
443
 
444
+ Tooltips with automatic state handling are shown on hover (and on long-press with touch input).
445
+
444
446
  ## Sound effects
445
447
 
446
448
  UI sound effects are available as `.ogg` files and can be played with any audio library. The `sounds` export maps each name to its file URL:
@@ -59,7 +59,6 @@
59
59
  // Drawer logic ------------------------------------------------------------------------------------
60
60
 
61
61
  // Bindings
62
- let contentEl = $state();
63
62
  let uiWindowHeight = $state();
64
63
 
65
64
  // Drawer state
@@ -93,7 +92,7 @@
93
92
 
94
93
  const dragStart = (event) => {
95
94
  // Abort if not a drawer, already tracking a finger, or if content should be scrolled by the "finger downwards" movement because it isn't scrolled all the way up
96
- if (!popupToDrawer.current || dragStartY != null || isScrolledDown(event.target, contentEl)) return;
95
+ if (!popupToDrawer.current || dragStartY != null || isScrolledDown(event.target, event.currentTarget)) return; // Current target is ui-window
97
96
 
98
97
  // Catch the drawer where it is (could still be animating open)
99
98
  cancelAnimationFrame(openAnimationFrame);
@@ -118,7 +117,7 @@
118
117
 
119
118
  // Dragging finger upwards while the touched element (or an ancestor up to the content) can still scroll down
120
119
  // Mainly relevant for the slow stretch animation when the drwaer is dragged upwards (which shouldn't show when content is scrolled down)
121
- const isScrollingDown = eventY <= dragStartY && canScrollDown(event.target, contentEl);
120
+ const isScrollingDown = eventY <= dragStartY && canScrollDown(event.target, event.currentTarget); // Current target is ui-window
122
121
 
123
122
  if (isScrollingHorizontally || isScrollingDown) {
124
123
  dragStartY = null;
@@ -258,7 +257,7 @@
258
257
  <button class="close-popup-button standard-button" aria-label="close" onclick={close}
259
258
  ><X strokeWidth={2.25} /></button
260
259
  >
261
- <div class="content of-top of-bottom" class:content-frameless={frameless} bind:this={contentEl}>
260
+ <div class="content of-top of-bottom" class:content-frameless={frameless}>
262
261
  {@render children?.()}
263
262
  </div>
264
263
  </div>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "openguessr-ui",
3
- "version": "0.4.0",
3
+ "version": "0.5.0",
4
4
  "type": "module",
5
5
  "scripts": {
6
6
  "dev": "vite example"
@@ -6,6 +6,8 @@ export function tooltip({ text, imageSrc, imageAspectRatio = "1 / 1", state, onc
6
6
  let visible = false;
7
7
  let cleanupAutoUpdate;
8
8
  let showTimeout = null;
9
+ let longPressTimeout = null;
10
+ let suppressClick = false;
9
11
 
10
12
  const persistent = Boolean(state);
11
13
  const closedScale = imageSrc ? 0.85 : 0;
@@ -157,6 +159,43 @@ export function tooltip({ text, imageSrc, imageAspectRatio = "1 / 1", state, onc
157
159
  hide();
158
160
  }
159
161
 
162
+ // On touch devices, a long press shows the tooltip
163
+ function onPointerDown(e) {
164
+ suppressClick = false;
165
+ if (e.pointerType != "touch" || !e.isPrimary) return;
166
+
167
+ // Close the tooltip on any click or other pointer down (scroll, swipe...)
168
+ const closeLongPressTooltip = () => {
169
+ suppressClick = false;
170
+ hide();
171
+ }
172
+
173
+ // After the 500ms timeout, the tooltip is shown
174
+ clearTimeout(longPressTimeout);
175
+ longPressTimeout = setTimeout(() => {
176
+ if (!visible) show();
177
+ if (visible) {
178
+ suppressClick = true;
179
+ document.addEventListener('pointerdown', closeLongPressTooltip, { capture: true, once: true });
180
+ }
181
+ }, 500);
182
+ }
183
+
184
+ // On pointerup, and pointercancel since cancel can be e.g. a scroll starting
185
+ function cancelLongPress() {
186
+ clearTimeout(longPressTimeout);
187
+ longPressTimeout = null;
188
+ }
189
+
190
+ // Don't trigger the element's click action after a long press
191
+ function onClick(e) {
192
+ cancelLongPress(); // Pointerup doesn't fire reliably with clicks (especialy on WK)
193
+ if (!suppressClick) return hide(); // The click may change what's under the cursor (e.g. a new page) without a pointerleave until the mouse moves
194
+ suppressClick = false;
195
+ e.preventDefault();
196
+ e.stopImmediatePropagation();
197
+ }
198
+
160
199
  if (persistent) {
161
200
  $effect(() => {
162
201
  if (state.visible) scheduleShow();
@@ -165,13 +204,22 @@ export function tooltip({ text, imageSrc, imageAspectRatio = "1 / 1", state, onc
165
204
  } else {
166
205
  node.addEventListener('pointerenter', onPointerEnter);
167
206
  node.addEventListener('pointerleave', onPointerLeave);
207
+ node.addEventListener('pointerdown', onPointerDown);
208
+ node.addEventListener('pointerup', cancelLongPress);
209
+ node.addEventListener('pointercancel', cancelLongPress);
210
+ node.addEventListener('click', onClick, true);
168
211
  }
169
212
 
170
213
  return () => {
171
214
  if (!persistent) {
172
215
  node.removeEventListener('pointerenter', onPointerEnter);
173
216
  node.removeEventListener('pointerleave', onPointerLeave);
217
+ node.removeEventListener('pointerdown', onPointerDown);
218
+ node.removeEventListener('pointerup', cancelLongPress);
219
+ node.removeEventListener('pointercancel', cancelLongPress);
220
+ node.removeEventListener('click', onClick, true);
174
221
  }
222
+ if (longPressTimeout) clearTimeout(longPressTimeout);
175
223
  if (showTimeout) clearTimeout(showTimeout);
176
224
  cleanupAutoUpdate?.();
177
225
  tooltipElement?.remove();