@w5-ui/svelte 0.8.0 → 0.8.1
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/package.json
CHANGED
|
@@ -26,6 +26,7 @@
|
|
|
26
26
|
<script lang="ts">
|
|
27
27
|
import { onDestroy, tick } from 'svelte';
|
|
28
28
|
import Pill from './Pill.svelte';
|
|
29
|
+
import { computeMenuPosition } from './menu-position';
|
|
29
30
|
|
|
30
31
|
const PILL_SLOT = '{pill}';
|
|
31
32
|
|
|
@@ -71,10 +72,29 @@
|
|
|
71
72
|
}, []),
|
|
72
73
|
);
|
|
73
74
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
75
|
+
/** Space kept between the menu and every viewport edge. */
|
|
76
|
+
const MENU_MARGIN = 8;
|
|
77
|
+
// Placed from the menu's MEASURED size once it is in the DOM, by the
|
|
78
|
+
// effect below. Effects run before the browser paints, so the menu is
|
|
79
|
+
// never seen at this starting point.
|
|
80
|
+
let cx = $state(0);
|
|
81
|
+
let cy = $state(0);
|
|
82
|
+
$effect(() => {
|
|
83
|
+
void items;
|
|
84
|
+
if (!open || menuEl === undefined) return;
|
|
85
|
+
const { width, height } = menuEl.getBoundingClientRect();
|
|
86
|
+
const placed = computeMenuPosition({
|
|
87
|
+
x,
|
|
88
|
+
y,
|
|
89
|
+
menuWidth: width,
|
|
90
|
+
menuHeight: height,
|
|
91
|
+
viewportWidth: window.innerWidth,
|
|
92
|
+
viewportHeight: window.innerHeight,
|
|
93
|
+
margin: MENU_MARGIN,
|
|
94
|
+
});
|
|
95
|
+
cx = placed.left;
|
|
96
|
+
cy = placed.top;
|
|
97
|
+
});
|
|
78
98
|
|
|
79
99
|
function activate(id: string) {
|
|
80
100
|
onaction?.(id);
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Where a context menu opens: at the pointer, the way OS menus do.
|
|
3
|
+
*
|
|
4
|
+
* Takes the menu's MEASURED size, never a guess: menus hold labels of
|
|
5
|
+
* any length, and a guessed width let a long one run off the edge.
|
|
6
|
+
* - It opens at the pointer when it fits.
|
|
7
|
+
* - It flips to the pointer's left (or above it) when it would cross
|
|
8
|
+
* the right edge (or the bottom).
|
|
9
|
+
* - When neither side has room, it is clamped inside the margins, and
|
|
10
|
+
* a menu bigger than the viewport is pinned to the margin.
|
|
11
|
+
*
|
|
12
|
+
* Pure. The React twin is `packages/react/src/components/menu-position.ts`.
|
|
13
|
+
*/
|
|
14
|
+
export interface MenuPositionInput {
|
|
15
|
+
/** The pointer, in viewport coordinates. */
|
|
16
|
+
x: number
|
|
17
|
+
y: number
|
|
18
|
+
menuWidth: number
|
|
19
|
+
menuHeight: number
|
|
20
|
+
viewportWidth: number
|
|
21
|
+
viewportHeight: number
|
|
22
|
+
/** Space kept between the menu and every viewport edge. */
|
|
23
|
+
margin: number
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function along(pointer: number, size: number, viewport: number, margin: number): number {
|
|
27
|
+
const far = viewport - margin
|
|
28
|
+
if (pointer + size <= far) return Math.max(margin, pointer)
|
|
29
|
+
if (pointer - size >= margin) return pointer - size
|
|
30
|
+
return Math.max(margin, Math.min(pointer, far - size))
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function computeMenuPosition(input: MenuPositionInput): { left: number; top: number } {
|
|
34
|
+
return {
|
|
35
|
+
left: along(input.x, input.menuWidth, input.viewportWidth, input.margin),
|
|
36
|
+
top: along(input.y, input.menuHeight, input.viewportHeight, input.margin),
|
|
37
|
+
}
|
|
38
|
+
}
|