openguessr-ui 0.3.2 → 0.3.4
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 +2 -0
- package/base.css +4 -3
- package/components/ConfirmPopup.svelte +1 -1
- package/components/ErrorPopup.svelte +1 -1
- package/components/ExperimentSign.svelte +1 -1
- package/components/Notification.svelte +1 -1
- package/components/Popup.svelte +247 -16
- package/components/RoomCodeInput.svelte +1 -1
- package/index.js +4 -4
- package/package.json +3 -6
- package/utils/clamp.js +1 -0
- package/utils/cubic-bezier.js +12 -0
- package/utils/scroll-check.js +20 -0
- /package/{confirmPopup.svelte.js → utils/confirm-popup.svelte.js} +0 -0
- /package/{errorPopup.svelte.js → utils/error-popup.svelte.js} +0 -0
- /package/{notifications.svelte.js → utils/notifications.svelte.js} +0 -0
- /package/{tooltip.svelte.js → utils/tooltip.svelte.js} +0 -0
package/README.md
CHANGED
|
@@ -249,6 +249,8 @@ The popup component is used for dialogs. It takes the following props:
|
|
|
249
249
|
- `frameless`: Reduces the padding, e.g. for iframes
|
|
250
250
|
- `onuserclose`: Called when the user closes the popup
|
|
251
251
|
|
|
252
|
+
On mobile devices (portrait orientation, and narrow in terms of width), the popup transforms into a drawer.
|
|
253
|
+
|
|
252
254
|
A common design pattern is to include a heading at the top of a popup. For this, the built-in `popup-title` class can be used:
|
|
253
255
|
|
|
254
256
|
```html
|
package/base.css
CHANGED
|
@@ -14,7 +14,8 @@
|
|
|
14
14
|
html {
|
|
15
15
|
/* This UI library is for games, not sites, double-tap to zoom or drag down to refresh are not wanted */
|
|
16
16
|
overscroll-behavior: none;
|
|
17
|
-
|
|
17
|
+
/* Block double-tap or pinch to zoom */
|
|
18
|
+
touch-action: pan-x pan-y;
|
|
18
19
|
}
|
|
19
20
|
|
|
20
21
|
html * {
|
|
@@ -23,8 +24,8 @@ html * {
|
|
|
23
24
|
|
|
24
25
|
body {
|
|
25
26
|
font-family: Barlow, sans-serif;
|
|
26
|
-
/*
|
|
27
|
-
touch-action:
|
|
27
|
+
/* Set touch action again since body could be a scroll container */
|
|
28
|
+
touch-action: pan-x pan-y;
|
|
28
29
|
background-color: var(--background-color);
|
|
29
30
|
}
|
|
30
31
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<script>
|
|
2
|
-
import { notification, hideNotification } from "../notifications.svelte.js";
|
|
2
|
+
import { notification, hideNotification } from "../utils/notifications.svelte.js";
|
|
3
3
|
import { Check, SquareArrowOutUpRight, X } from "@lucide/svelte";
|
|
4
4
|
import { fly } from "svelte/transition";
|
|
5
5
|
|
package/components/Popup.svelte
CHANGED
|
@@ -5,13 +5,155 @@
|
|
|
5
5
|
<script>
|
|
6
6
|
import { X } from "@lucide/svelte";
|
|
7
7
|
import { untrack } from "svelte";
|
|
8
|
+
import { on } from "svelte/events";
|
|
9
|
+
import { cubicBezier } from "../utils/cubic-bezier.js";
|
|
10
|
+
import { clamp } from "../utils/clamp.js";
|
|
11
|
+
import { isScrolledDown, canScrollDown } from "../utils/scroll-check.js";
|
|
12
|
+
import { scale, fade } from "svelte/transition";
|
|
8
13
|
import { backOut } from "svelte/easing";
|
|
9
|
-
import {
|
|
10
|
-
|
|
14
|
+
import { MediaQuery } from "svelte/reactivity";
|
|
11
15
|
let { children, frameless, slim, verySlim, ultraSlim, open = $bindable(false), onuserclose } = $props();
|
|
12
16
|
|
|
13
17
|
let zIndex = $state(0);
|
|
14
18
|
|
|
19
|
+
// Transitions ------------------------------------------------------------------------------
|
|
20
|
+
const DRAWER_TIMING = { duration: 500, easing: cubicBezier(0.32, 0.72, 0, 1) };
|
|
21
|
+
const DRAWER_HIDDEN_OVERSHOOT = 50; // Added negative closed y-pos offset to move the panel shadow off-screen (100px blur - 50px --layout-spacer)
|
|
22
|
+
const popupToDrawer = new MediaQuery("(orientation: portrait) and (max-width: 500px)");
|
|
23
|
+
|
|
24
|
+
const popupHolderTransition = (node) => (popupToDrawer.current ? { duration: 0 } : fade(node, { duration: 100 }));
|
|
25
|
+
|
|
26
|
+
// Replaced when dragged closed, so the close continues the swipe's speed
|
|
27
|
+
let closeTiming = DRAWER_TIMING;
|
|
28
|
+
|
|
29
|
+
function windowTransition(node, _params, { direction }) {
|
|
30
|
+
if (!popupToDrawer.current) return scale(node, { easing: backOut, duration: 250, start: 0.9 });
|
|
31
|
+
|
|
32
|
+
if (direction == "in") {
|
|
33
|
+
closeTiming = DRAWER_TIMING;
|
|
34
|
+
drawerVerticalOffset = node.offsetHeight + DRAWER_HIDDEN_OVERSHOOT;
|
|
35
|
+
animateDrawerOpen(); // Opening is animated by this function, so a drag can take over at any point
|
|
36
|
+
return { duration: 0 };
|
|
37
|
+
} else {
|
|
38
|
+
// Close from wherever the drawer currently is
|
|
39
|
+
cancelAnimationFrame(openAnimationFrame);
|
|
40
|
+
const start = drawerVerticalOffset;
|
|
41
|
+
return {
|
|
42
|
+
...closeTiming,
|
|
43
|
+
css: (t) => `transform: translate3d(0, ${closedOffset + (start - closedOffset) * t}px, 0)`,
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function drawerOverlayOutTransition() {
|
|
49
|
+
if (!popupToDrawer.current) return { duration: 0 };
|
|
50
|
+
const start = overlayProgress; // Current strength
|
|
51
|
+
return {
|
|
52
|
+
...closeTiming,
|
|
53
|
+
// Fade color opacity and blur strength relative to time factor t (0-1)
|
|
54
|
+
css: (t) =>
|
|
55
|
+
`background-color: color-mix(in srgb, var(--overlay-color) ${start * t * 100}%, transparent); backdrop-filter: blur(calc(var(--weak-blur) * ${start * t}));`,
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Drawer logic ------------------------------------------------------------------------------------
|
|
60
|
+
|
|
61
|
+
// Bindings
|
|
62
|
+
let contentEl = $state();
|
|
63
|
+
let uiWindowHeight = $state();
|
|
64
|
+
|
|
65
|
+
// Drawer state
|
|
66
|
+
let dragStartY = $state();
|
|
67
|
+
let dragStartX;
|
|
68
|
+
let dragStartVerticalOffset;
|
|
69
|
+
let isDragging = $state(false);
|
|
70
|
+
let drawerVerticalOffset = $state(0);
|
|
71
|
+
let velocitySamples = $state([]); // Y positions with timestamps
|
|
72
|
+
let openAnimationFrame; // Latest open animation frame for cancelling it
|
|
73
|
+
|
|
74
|
+
const closedOffset = $derived(uiWindowHeight + DRAWER_HIDDEN_OVERSHOOT);
|
|
75
|
+
const overlayProgress = $derived(Math.min(1, 1 - drawerVerticalOffset / closedOffset));
|
|
76
|
+
|
|
77
|
+
// Animate drawer to fully open
|
|
78
|
+
const animateDrawerOpen = () => {
|
|
79
|
+
if (drawerVerticalOffset == 0) return; // Skip when the drawer is already fully open
|
|
80
|
+
const start = drawerVerticalOffset;
|
|
81
|
+
const startTime = performance.now();
|
|
82
|
+
const step = (now) => {
|
|
83
|
+
const progress = Math.min(1, (now - startTime) / DRAWER_TIMING.duration); // Progress (0-1)
|
|
84
|
+
drawerVerticalOffset = start * (1 - DRAWER_TIMING.easing(progress));
|
|
85
|
+
if (progress < 1) openAnimationFrame = requestAnimationFrame(step);
|
|
86
|
+
};
|
|
87
|
+
openAnimationFrame = requestAnimationFrame(step);
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
const dragStart = (event) => {
|
|
91
|
+
// Abort if not a drawer, or if content should be scrolled by the "finger downwards" movement because it isn't scrolled all the way up
|
|
92
|
+
if (!popupToDrawer.current || isScrolledDown(event.target, contentEl)) return;
|
|
93
|
+
|
|
94
|
+
// Catch the drawer where it is (could still be animating open)
|
|
95
|
+
cancelAnimationFrame(openAnimationFrame);
|
|
96
|
+
dragStartVerticalOffset = drawerVerticalOffset;
|
|
97
|
+
dragStartY = event.touches[0].clientY;
|
|
98
|
+
dragStartX = event.touches[0].clientX;
|
|
99
|
+
velocitySamples = [{ y: dragStartY, time: performance.now() }];
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
const drag = (event) => {
|
|
103
|
+
if (dragStartY == null) return;
|
|
104
|
+
|
|
105
|
+
const eventY = event.touches[0].clientY;
|
|
106
|
+
|
|
107
|
+
// On the first move, decide whether the gesture drags the drawer or scrolls the content
|
|
108
|
+
if (!isDragging) {
|
|
109
|
+
// Horizontal movements belong to the content, e.g. horizontally scrolling elements
|
|
110
|
+
const isScrollingHorizontally = Math.abs(event.touches[0].clientX - dragStartX) > Math.abs(eventY - dragStartY); // Check if more horizontal movement than vertical
|
|
111
|
+
|
|
112
|
+
// Dragging finger upwards while the touched element (or an ancestor up to the content) can still scroll down
|
|
113
|
+
// Mainly relevant for the slow stretch animation when the drwaer is dragged upwards (which shouldn't show when content is scrolled down)
|
|
114
|
+
const isScrollingDown = eventY <= dragStartY && canScrollDown(event.target, contentEl);
|
|
115
|
+
|
|
116
|
+
if (isScrollingHorizontally || isScrollingDown) {
|
|
117
|
+
dragStartY = null;
|
|
118
|
+
return animateDrawerOpen(); // Touch could've stopped the drawer during open, since it's a content scroll not a drag, resume
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
event.preventDefault(); // Prevent page scroll
|
|
123
|
+
isDragging = true;
|
|
124
|
+
|
|
125
|
+
// Collect samples for velocity calc
|
|
126
|
+
const now = performance.now();
|
|
127
|
+
velocitySamples.push({ y: eventY, time: now });
|
|
128
|
+
while (velocitySamples.length > 2 && now - velocitySamples[0].time > 80) velocitySamples.shift(); // Keep the last 80ms
|
|
129
|
+
|
|
130
|
+
// Apply offset
|
|
131
|
+
const delta = eventY - dragStartY + dragStartVerticalOffset;
|
|
132
|
+
drawerVerticalOffset = delta > 0 ? delta : delta * 0.05; // Allow dragging the drawer upwards, but make it less effective (that's what the x0.05 is for)
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
const dragEnd = () => {
|
|
136
|
+
if (dragStartY == null) return;
|
|
137
|
+
dragStartY = null;
|
|
138
|
+
|
|
139
|
+
if (!isDragging) return animateDrawerOpen(); // Single tap without drag, resume opening if it was caught mid-animation
|
|
140
|
+
isDragging = false;
|
|
141
|
+
|
|
142
|
+
// Calculate velocity
|
|
143
|
+
const first = velocitySamples[0];
|
|
144
|
+
const last = velocitySamples.at(-1);
|
|
145
|
+
const velocity = last.time > first.time ? (last.y - first.y) / (last.time - first.time) : 0; // Pixels per ms (time is ms)
|
|
146
|
+
|
|
147
|
+
// If swiped with enough velocity or past 35%, animate it shut, otherwise open fully again
|
|
148
|
+
if (velocity > 0.4 || drawerVerticalOffset > uiWindowHeight * 0.35) {
|
|
149
|
+
const duration = clamp((closedOffset - drawerVerticalOffset) / Math.max(velocity, 1.5), 180, 420); // Faster drag velocity means lower duration
|
|
150
|
+
closeTiming = { duration, easing: cubicBezier(0.45, 0.55, 0.4, 0.92) };
|
|
151
|
+
close();
|
|
152
|
+
} else animateDrawerOpen(); // Too little drag, snap back to fully open state
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
// Automatic z-indexing ---------------------------------------------------------------------
|
|
156
|
+
|
|
15
157
|
// Helper to remove the current popup's z-index
|
|
16
158
|
const removeZIndex = () => {
|
|
17
159
|
const index = openZIndexes.indexOf(zIndex);
|
|
@@ -33,6 +175,7 @@
|
|
|
33
175
|
}
|
|
34
176
|
});
|
|
35
177
|
|
|
178
|
+
// Close function ----------------------------------------------------------------------------
|
|
36
179
|
function close() {
|
|
37
180
|
onuserclose?.();
|
|
38
181
|
open = false;
|
|
@@ -53,24 +196,62 @@
|
|
|
53
196
|
<!-- svelte-ignore a11y_click_events_have_key_events -->
|
|
54
197
|
<div
|
|
55
198
|
class="popup-holder"
|
|
199
|
+
class:drawer-holder={popupToDrawer.current}
|
|
56
200
|
style:z-index={zIndex}
|
|
57
|
-
|
|
201
|
+
in:popupHolderTransition
|
|
202
|
+
out:popupHolderTransition
|
|
58
203
|
onclick={(e) => {
|
|
59
204
|
if (e.target == e.currentTarget) close();
|
|
60
205
|
}}
|
|
61
206
|
role="dialog"
|
|
62
207
|
tabindex={0}
|
|
63
208
|
>
|
|
209
|
+
<!-- Separate from the holder, a backdrop filter on an ancestor wouldn't work -->
|
|
210
|
+
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
|
211
|
+
<div
|
|
212
|
+
class="drawer-overlay"
|
|
213
|
+
out:drawerOverlayOutTransition
|
|
214
|
+
onclick={close}
|
|
215
|
+
style:background-color="color-mix(in srgb, var(--overlay-color) {overlayProgress * 100}%, transparent)"
|
|
216
|
+
style:backdrop-filter="blur(calc(var(--weak-blur) * {overlayProgress}))"
|
|
217
|
+
></div>
|
|
64
218
|
<div
|
|
65
219
|
class="ui-window"
|
|
66
|
-
|
|
220
|
+
role="menu"
|
|
221
|
+
tabindex="0"
|
|
222
|
+
class:drawer={popupToDrawer.current}
|
|
223
|
+
in:windowTransition
|
|
224
|
+
out:windowTransition
|
|
67
225
|
class:frame-less={frameless}
|
|
68
|
-
style:max-width={
|
|
226
|
+
style:max-width={popupToDrawer.current
|
|
227
|
+
? "none"
|
|
228
|
+
: ultraSlim
|
|
229
|
+
? "450px"
|
|
230
|
+
: verySlim
|
|
231
|
+
? "600px"
|
|
232
|
+
: slim
|
|
233
|
+
? "750px"
|
|
234
|
+
: "850px"}
|
|
235
|
+
style:transform={popupToDrawer.current && drawerVerticalOffset
|
|
236
|
+
? `translate3d(0, ${drawerVerticalOffset}px, 0)`
|
|
237
|
+
: ""}
|
|
238
|
+
bind:offsetHeight={uiWindowHeight}
|
|
239
|
+
{@attach (node) => {
|
|
240
|
+
// Needed since regular Svelte's touchmove is passive
|
|
241
|
+
const cleanup = on(node, "touchmove", drag, { passive: false });
|
|
242
|
+
return cleanup;
|
|
243
|
+
}}
|
|
244
|
+
ontouchstart={dragStart}
|
|
245
|
+
ontouchend={dragEnd}
|
|
246
|
+
ontouchcancel={dragEnd}
|
|
69
247
|
>
|
|
248
|
+
{#if popupToDrawer.current && !frameless}
|
|
249
|
+
<div class="drawer-handle"></div>
|
|
250
|
+
{/if}
|
|
70
251
|
<button class="close-popup-button standard-button" aria-label="close" onclick={close}
|
|
71
252
|
><X strokeWidth={2.25} /></button
|
|
72
253
|
>
|
|
73
|
-
<div class="content of-top of-bottom" class:content-frameless={frameless}>
|
|
254
|
+
<div class="content of-top of-bottom" class:content-frameless={frameless} bind:this={contentEl}>
|
|
74
255
|
{@render children?.()}
|
|
75
256
|
</div>
|
|
76
257
|
</div>
|
|
@@ -90,6 +271,7 @@
|
|
|
90
271
|
background-color: var(--overlay-color);
|
|
91
272
|
pointer-events: auto;
|
|
92
273
|
backdrop-filter: blur(var(--weak-blur));
|
|
274
|
+
--window-padding: calc(var(--layout-margin) + var(--content-margin));
|
|
93
275
|
}
|
|
94
276
|
|
|
95
277
|
.close-popup-button {
|
|
@@ -102,10 +284,6 @@
|
|
|
102
284
|
z-index: 10;
|
|
103
285
|
}
|
|
104
286
|
|
|
105
|
-
.frame-less {
|
|
106
|
-
padding: var(--content-margin) !important;
|
|
107
|
-
}
|
|
108
|
-
|
|
109
287
|
.content {
|
|
110
288
|
position: relative;
|
|
111
289
|
overflow-y: auto;
|
|
@@ -119,18 +297,18 @@
|
|
|
119
297
|
);
|
|
120
298
|
width: 100%;
|
|
121
299
|
scrollbar-width: none;
|
|
300
|
+
overscroll-behavior: contain;
|
|
122
301
|
border-radius: var(--box-margin);
|
|
123
302
|
}
|
|
124
303
|
|
|
125
304
|
.content-frameless {
|
|
126
305
|
max-height: calc(100dvh - var(--layout-spacer) - env(safe-area-inset-top) - env(safe-area-inset-bottom));
|
|
127
|
-
margin-bottom: -4px;
|
|
128
306
|
border-radius: 0;
|
|
129
307
|
}
|
|
130
308
|
|
|
131
309
|
.ui-window {
|
|
132
310
|
width: calc(100% - var(--layout-margin) * 2);
|
|
133
|
-
padding:
|
|
311
|
+
padding: var(--window-padding);
|
|
134
312
|
border-radius: calc(var(--layout-margin) + var(--box-margin));
|
|
135
313
|
text-align: left;
|
|
136
314
|
position: relative;
|
|
@@ -139,11 +317,64 @@
|
|
|
139
317
|
box-shadow:
|
|
140
318
|
0 2px 100px rgba(5, 6, 10, 0.5),
|
|
141
319
|
inset 0 0.05rem 1px hsla(0, 0%, 100%, 0.2),
|
|
142
|
-
inset 0 -0.2rem 1px rgba(0, 0, 2, 0.2)
|
|
320
|
+
inset 0 -0.2rem 1px rgba(0, 0, 2, 0.2);
|
|
143
321
|
color: white;
|
|
144
322
|
pointer-events: auto;
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
.frame-less {
|
|
326
|
+
padding: var(--content-margin);
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
.drawer-overlay {
|
|
330
|
+
display: none;
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
.drawer-holder {
|
|
334
|
+
align-items: flex-end;
|
|
335
|
+
background-color: transparent;
|
|
336
|
+
backdrop-filter: none;
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
.drawer-holder .drawer-overlay {
|
|
340
|
+
display: block;
|
|
341
|
+
position: absolute;
|
|
342
|
+
inset: 0;
|
|
343
|
+
background-color: var(--overlay-color);
|
|
344
|
+
backdrop-filter: blur(var(--weak-blur));
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
.drawer {
|
|
348
|
+
display: flex;
|
|
349
|
+
flex-direction: column;
|
|
350
|
+
width: 100%;
|
|
351
|
+
/* Max height is also shorter by the negative --layout-spacer margin bottom */
|
|
352
|
+
max-height: calc(100dvh - env(safe-area-inset-top));
|
|
353
|
+
padding-bottom: calc(max(var(--window-padding), env(safe-area-inset-bottom)) + var(--layout-spacer));
|
|
354
|
+
margin-bottom: calc(-1 * var(--layout-spacer));
|
|
355
|
+
border-bottom-left-radius: 0;
|
|
356
|
+
border-bottom-right-radius: 0;
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
.drawer.frame-less {
|
|
360
|
+
/* Add --layout-spacer since we have the negative --layout-spacer margin bottom */
|
|
361
|
+
padding-bottom: calc(max(var(--content-margin), env(safe-area-inset-bottom)) + var(--layout-spacer));
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
.drawer .content {
|
|
365
|
+
flex: 1;
|
|
366
|
+
min-height: 0;
|
|
367
|
+
max-height: none;
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
.drawer-handle {
|
|
371
|
+
position: absolute;
|
|
372
|
+
top: var(--box-margin);
|
|
373
|
+
border-radius: 4px;
|
|
374
|
+
left: 50%;
|
|
375
|
+
translate: -50% 0;
|
|
376
|
+
width: 40px;
|
|
377
|
+
height: 4px;
|
|
378
|
+
background-color: var(--line-color);
|
|
148
379
|
}
|
|
149
380
|
</style>
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
<script>
|
|
2
2
|
import { ArrowRight } from "@lucide/svelte";
|
|
3
|
-
import { tooltip } from "../tooltip.svelte.js";
|
|
3
|
+
import { tooltip } from "../utils/tooltip.svelte.js";
|
|
4
4
|
|
|
5
5
|
let { segments = $bindable(["", "", "", "", "", ""]), inPage = true, onsubmit } = $props(); // 6 segments for XXX-XXX
|
|
6
6
|
|
package/index.js
CHANGED
|
@@ -11,10 +11,10 @@ export { default as Tabs } from "./components/Tabs.svelte";
|
|
|
11
11
|
export { default as Ticker } from "./components/Ticker.svelte";
|
|
12
12
|
export { default as TitleSeparator } from "./components/TitleSeparator.svelte";
|
|
13
13
|
export { default as ExperimentSign } from "./components/ExperimentSign.svelte";
|
|
14
|
-
export { tooltip } from "./tooltip.svelte.js";
|
|
15
|
-
export { showNotification } from "./notifications.svelte.js";
|
|
16
|
-
export { showConfirmPopup } from "./
|
|
17
|
-
export { showErrorPopup } from "./
|
|
14
|
+
export { tooltip } from "./utils/tooltip.svelte.js";
|
|
15
|
+
export { showNotification } from "./utils/notifications.svelte.js";
|
|
16
|
+
export { showConfirmPopup } from "./utils/confirm-popup.svelte.js";
|
|
17
|
+
export { showErrorPopup } from "./utils/error-popup.svelte.js";
|
|
18
18
|
|
|
19
19
|
// Sounds
|
|
20
20
|
import basic_button from "./sound-effects/basic_button.ogg";
|
package/package.json
CHANGED
|
@@ -1,16 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "openguessr-ui",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.4",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"scripts": {
|
|
6
6
|
"dev": "vite example"
|
|
7
7
|
},
|
|
8
8
|
"files": [
|
|
9
9
|
"index.js",
|
|
10
|
-
"
|
|
11
|
-
"notifications.svelte.js",
|
|
12
|
-
"confirmPopup.svelte.js",
|
|
13
|
-
"errorPopup.svelte.js",
|
|
10
|
+
"utils",
|
|
14
11
|
"components",
|
|
15
12
|
"fonts",
|
|
16
13
|
"vector-graphics",
|
|
@@ -34,7 +31,7 @@
|
|
|
34
31
|
"peerDependencies": {
|
|
35
32
|
"@lucide/svelte": "^1.0.0",
|
|
36
33
|
"overfade": "^0.0.11",
|
|
37
|
-
"svelte": "^5.
|
|
34
|
+
"svelte": "^5.7.0"
|
|
38
35
|
},
|
|
39
36
|
"dependencies": {
|
|
40
37
|
"@floating-ui/dom": "^1.8.0"
|
package/utils/clamp.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const clamp = (val, min, max) => Math.min(Math.max(val, min), max);
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export function cubicBezier(x1, y1, x2, y2) {
|
|
2
|
+
const curve = (s, c1, c2) => (1 - 3 * c2 + 3 * c1) * s ** 3 + (3 * c2 - 6 * c1) * s ** 2 + 3 * c1 * s;
|
|
3
|
+
return (t) => {
|
|
4
|
+
let s = t;
|
|
5
|
+
for (let i = 0; i < 5; i++) {
|
|
6
|
+
const slope = 3 * (1 - 3 * x2 + 3 * x1) * s ** 2 + 2 * (3 * x2 - 6 * x1) * s + 3 * x1;
|
|
7
|
+
if (Math.abs(slope) < 1e-6) break;
|
|
8
|
+
s -= (curve(s, x1, x2) - t) / slope;
|
|
9
|
+
}
|
|
10
|
+
return curve(s, y1, y2);
|
|
11
|
+
};
|
|
12
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
// Whether the element or any ancestor up to root is scrolled down
|
|
2
|
+
export function isScrolledDown(el, root) {
|
|
3
|
+
while (el) {
|
|
4
|
+
if (el.scrollTop > 0) return true; // Return true when a scrollable element was found
|
|
5
|
+
if (el === root) return false;
|
|
6
|
+
el = el.parentElement;
|
|
7
|
+
}
|
|
8
|
+
return false;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
// Whether the element or any ancestor up to root can still scroll further down
|
|
12
|
+
export function canScrollDown(el, root) {
|
|
13
|
+
while (el) {
|
|
14
|
+
const scrollRemaining = el.scrollHeight - el.scrollTop - el.clientHeight;
|
|
15
|
+
if (scrollRemaining > 1) return true; // scrollTop can be fractional while scrollHeight and clientHeight are rounded
|
|
16
|
+
if (el === root) return false;
|
|
17
|
+
el = el.parentElement;
|
|
18
|
+
}
|
|
19
|
+
return false;
|
|
20
|
+
}
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|