openguessr-ui 0.3.6 → 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 +4 -1
- package/components/Notification.svelte +2 -3
- package/components/Popup.svelte +7 -8
- package/package.json +1 -1
- package/utils/tooltip.svelte.js +48 -0
package/README.md
CHANGED
|
@@ -343,7 +343,8 @@ The loading spinner component can be displayed while large content loads, such a
|
|
|
343
343
|
|
|
344
344
|
The notification component is used for short messages at the top of the screen, optionally with an action. It needs to be mounted. It takes the following props:
|
|
345
345
|
|
|
346
|
-
- `top`: Distance from the top of the viewport, defaults to `--
|
|
346
|
+
- `top`: Distance from the top of the viewport, defaults to `--box-margin`
|
|
347
|
+
- `offsetSafeArea`: Whether the top position should be offset by the safe area inset, defaults to true
|
|
347
348
|
- `onaccept`: Called when the accept button is used
|
|
348
349
|
- `ondismiss`: Called when the dismiss button is used
|
|
349
350
|
|
|
@@ -440,6 +441,8 @@ Options:
|
|
|
440
441
|
- `zIndex`: Tooltip z-index
|
|
441
442
|
- `mobile`: Set to false to hide the tooltip on mobile
|
|
442
443
|
|
|
444
|
+
Tooltips with automatic state handling are shown on hover (and on long-press with touch input).
|
|
445
|
+
|
|
443
446
|
## Sound effects
|
|
444
447
|
|
|
445
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:
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
import { Check, SquareArrowOutUpRight, X } from "@lucide/svelte";
|
|
4
4
|
import { fly } from "svelte/transition";
|
|
5
5
|
|
|
6
|
-
let { top = "var(--box-margin)", onaccept, ondismiss } = $props();
|
|
6
|
+
let { top = "var(--box-margin)", offsetSafeArea = true, onaccept, ondismiss } = $props();
|
|
7
7
|
|
|
8
8
|
function accept() {
|
|
9
9
|
if (notification.acceptFunction) {
|
|
@@ -36,7 +36,7 @@
|
|
|
36
36
|
);
|
|
37
37
|
</script>
|
|
38
38
|
|
|
39
|
-
<div class="notification-holder" style:top>
|
|
39
|
+
<div class="notification-holder" style:top style:margin-top={offsetSafeArea ? "env(safe-area-inset-top)" : ""}>
|
|
40
40
|
{#if notification.visible}
|
|
41
41
|
<div id="notification-bar" transition:fly={{ y: -20, duration: 250 }}>
|
|
42
42
|
<p id="notification-text">
|
|
@@ -103,7 +103,6 @@
|
|
|
103
103
|
position: fixed;
|
|
104
104
|
left: var(--box-margin);
|
|
105
105
|
right: var(--box-margin);
|
|
106
|
-
margin-top: env(safe-area-inset-top);
|
|
107
106
|
display: flex;
|
|
108
107
|
justify-content: center;
|
|
109
108
|
pointer-events: none;
|
package/components/Popup.svelte
CHANGED
|
@@ -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,
|
|
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,11 +117,11 @@
|
|
|
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,
|
|
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;
|
|
125
|
-
return animateDrawerOpen(); // Touch
|
|
124
|
+
return animateDrawerOpen(); // Touch could've stopped the drawer during open, since it's a content scroll not a drag, resume
|
|
126
125
|
}
|
|
127
126
|
}
|
|
128
127
|
|
|
@@ -140,7 +139,7 @@
|
|
|
140
139
|
};
|
|
141
140
|
|
|
142
141
|
const dragEnd = (event) => {
|
|
143
|
-
if (dragStartY == null || !findDragTouch(event.changedTouches)) return; // Only the dragging finger
|
|
142
|
+
if (dragStartY == null || !findDragTouch(event.changedTouches)) return; // Only the dragging finger can end the drag
|
|
144
143
|
dragStartY = null;
|
|
145
144
|
|
|
146
145
|
if (!isDragging) return animateDrawerOpen(); // Single tap without drag, resume opening if it was caught mid-animation
|
|
@@ -149,14 +148,14 @@
|
|
|
149
148
|
// Calculate velocity
|
|
150
149
|
const first = velocitySamples[0];
|
|
151
150
|
const last = velocitySamples.at(-1);
|
|
152
|
-
const velocity = last.time > first.time ? (last.y - first.y) / (last.time - first.time) : 0; // Pixels per ms
|
|
151
|
+
const velocity = last.time > first.time ? (last.y - first.y) / (last.time - first.time) : 0; // Pixels per ms
|
|
153
152
|
|
|
154
153
|
// If swiped with enough velocity or past 35%, animate it shut, otherwise open fully again
|
|
155
154
|
if (velocity > 0.4 || drawerVerticalOffset > uiWindowHeight * 0.35) {
|
|
156
155
|
const duration = clamp((closedOffset - drawerVerticalOffset) / Math.max(velocity, 1.5), 180, 420); // Faster drag velocity means lower duration
|
|
157
156
|
closeTiming = { duration, easing: cubicBezier(0.45, 0.55, 0.4, 0.92) };
|
|
158
157
|
close();
|
|
159
|
-
} else animateDrawerOpen();
|
|
158
|
+
} else animateDrawerOpen();
|
|
160
159
|
};
|
|
161
160
|
|
|
162
161
|
// Automatic z-indexing ---------------------------------------------------------------------
|
|
@@ -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}
|
|
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
package/utils/tooltip.svelte.js
CHANGED
|
@@ -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();
|