lutra 0.1.32 → 0.1.34
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 +1 -1
- package/dist/components/Dialog.svelte +49 -34
- package/dist/components/Dialog.svelte.d.ts +5 -5
- package/dist/components/Layout.svelte +25 -24
- package/dist/components/Layout.svelte.d.ts +1 -1
- package/dist/components/MenuDropdown.svelte +172 -191
- package/dist/components/MenuDropdown.svelte.d.ts +2 -1
- package/dist/components/MenuItem.svelte +11 -2
- package/dist/components/MenuItem.svelte.d.ts +1 -0
- package/dist/components/MenuItemContent.svelte +5 -0
- package/dist/components/MenuItemContent.svelte.d.ts +2 -0
- package/dist/components/MenuTypes.d.ts +7 -2
- package/dist/components/Modal.svelte +236 -82
- package/dist/components/Modal.svelte.d.ts +20 -8
- package/dist/components/ModalContent.svelte +49 -7
- package/dist/components/ModalContent.svelte.d.ts +5 -0
- package/dist/components/ModalTypes.d.ts +4 -4
- package/dist/components/ModalTypes.js +1 -1
- package/dist/components/Popover.svelte +222 -0
- package/dist/components/Popover.svelte.d.ts +41 -0
- package/dist/components/Toast.svelte +150 -0
- package/dist/components/Toast.svelte.d.ts +20 -0
- package/dist/components/ToastContainer.svelte +128 -0
- package/dist/components/ToastContainer.svelte.d.ts +3 -0
- package/dist/components/Tooltip.svelte +60 -66
- package/dist/components/Tooltip.svelte.d.ts +1 -1
- package/dist/components/index.d.ts +4 -6
- package/dist/components/index.js +5 -7
- package/dist/components/modals.svelte.d.ts +15 -0
- package/dist/components/modals.svelte.js +74 -56
- package/dist/components/toasts.svelte.d.ts +77 -0
- package/dist/components/toasts.svelte.js +69 -0
- package/dist/css/1-props.css +21 -1
- package/dist/css/2-base.css +27 -0
- package/dist/css/themes/DefaultTheme.css +4 -0
- package/dist/form/ImageUpload.svelte +8 -4
- package/dist/form/types.d.ts +3 -3
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/types.d.ts +8 -0
- package/dist/util/RenderContent.svelte +49 -0
- package/dist/util/RenderContent.svelte.d.ts +32 -0
- package/dist/util/StringOrSnippet.svelte +8 -3
- package/dist/util/StringOrSnippet.svelte.d.ts +1 -1
- package/package.json +10 -10
- package/dist/components/Notification.svelte +0 -115
- package/dist/components/Notification.svelte.d.ts +0 -12
- package/dist/components/Overlay.svelte +0 -31
- package/dist/components/Overlay.svelte.d.ts +0 -14
- package/dist/components/OverlayContainer.svelte +0 -31
- package/dist/components/OverlayContainer.svelte.d.ts +0 -18
- package/dist/components/OverlayLayer.svelte +0 -168
- package/dist/components/OverlayLayer.svelte.d.ts +0 -8
- package/dist/components/notifications.svelte.d.ts +0 -21
- package/dist/components/notifications.svelte.js +0 -30
- package/dist/components/overlays.svelte.d.ts +0 -36
- package/dist/components/overlays.svelte.js +0 -44
- package/dist/util/StringOrComponent.svelte +0 -20
- package/dist/util/StringOrComponent.svelte.d.ts +0 -8
package/README.md
CHANGED
|
@@ -1,52 +1,55 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import type { Snippet } from "svelte";
|
|
3
|
-
import { slidefade } from "../util/transitions.js";
|
|
4
3
|
|
|
5
4
|
/**
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* Provides a modal dialog with a backdrop and proper focus management.
|
|
5
|
+
* A simple dialog component using the native `<dialog>` element.
|
|
6
|
+
* For more features (buttons, scrim control, etc.), use `<Modal>` instead.
|
|
9
7
|
*/
|
|
10
8
|
let {
|
|
11
9
|
open = $bindable(false),
|
|
12
10
|
title,
|
|
13
11
|
children,
|
|
14
|
-
|
|
12
|
+
onclose,
|
|
15
13
|
}: {
|
|
16
|
-
/**
|
|
14
|
+
/** Whether the dialog is open (bindable) */
|
|
17
15
|
open?: boolean;
|
|
18
|
-
/**
|
|
16
|
+
/** Optional title for the dialog header */
|
|
19
17
|
title?: string;
|
|
20
|
-
/**
|
|
18
|
+
/** Dialog content */
|
|
21
19
|
children: Snippet;
|
|
22
|
-
/** Callback when
|
|
23
|
-
|
|
20
|
+
/** Callback when dialog closes */
|
|
21
|
+
onclose?: () => void;
|
|
24
22
|
} = $props();
|
|
25
23
|
|
|
26
|
-
let
|
|
27
|
-
|
|
28
|
-
$effect(() => {
|
|
29
|
-
if (open && dialog) {
|
|
30
|
-
dialog.showModal();
|
|
31
|
-
} else if (dialog) {
|
|
32
|
-
dialog.close();
|
|
33
|
-
}
|
|
34
|
-
});
|
|
24
|
+
let dialogEl: HTMLDialogElement | null = $state(null);
|
|
35
25
|
|
|
36
26
|
function handleClose() {
|
|
37
|
-
|
|
27
|
+
open = false;
|
|
28
|
+
onclose?.();
|
|
38
29
|
}
|
|
39
30
|
|
|
31
|
+
function handleCancel(e: Event) {
|
|
32
|
+
handleClose();
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
$effect(() => {
|
|
36
|
+
if (open && dialogEl && !dialogEl.open) {
|
|
37
|
+
dialogEl.showModal();
|
|
38
|
+
} else if (!open && dialogEl?.open) {
|
|
39
|
+
dialogEl.close();
|
|
40
|
+
}
|
|
41
|
+
});
|
|
40
42
|
</script>
|
|
41
43
|
|
|
42
44
|
<dialog
|
|
43
|
-
bind:this={
|
|
45
|
+
bind:this={dialogEl}
|
|
44
46
|
class="Dialog"
|
|
45
|
-
|
|
47
|
+
onclose={handleClose}
|
|
48
|
+
oncancel={handleCancel}
|
|
46
49
|
>
|
|
47
50
|
{#if title}
|
|
48
51
|
<header class="DialogHeader">
|
|
49
|
-
<
|
|
52
|
+
<h4>{title}</h4>
|
|
50
53
|
</header>
|
|
51
54
|
{/if}
|
|
52
55
|
<div class="DialogContent">
|
|
@@ -57,22 +60,34 @@
|
|
|
57
60
|
<style>
|
|
58
61
|
.Dialog {
|
|
59
62
|
padding: 0;
|
|
60
|
-
border: var(--
|
|
61
|
-
border-radius: var(--
|
|
62
|
-
background: var(--
|
|
63
|
-
color: var(--
|
|
63
|
+
border: var(--modal-border-size) var(--modal-border-style) var(--modal-border-color);
|
|
64
|
+
border-radius: var(--modal-border-radius);
|
|
65
|
+
background: var(--modal-background);
|
|
66
|
+
color: var(--text-color-p);
|
|
64
67
|
max-width: min(calc(100vw - 2rem), 32rem);
|
|
65
|
-
max-height: min(calc(100vh - 2rem),
|
|
68
|
+
max-height: min(calc(100vh - 2rem), 80vh);
|
|
69
|
+
box-shadow: 0 0.5rem 1rem var(--modal-shadow-color);
|
|
66
70
|
}
|
|
71
|
+
|
|
67
72
|
.Dialog::backdrop {
|
|
68
|
-
background: var(--
|
|
69
|
-
backdrop-filter:
|
|
73
|
+
background: var(--scrim-background);
|
|
74
|
+
backdrop-filter: var(--scrim-backdrop-filter);
|
|
70
75
|
}
|
|
76
|
+
|
|
71
77
|
.DialogHeader {
|
|
72
|
-
padding: var(--
|
|
73
|
-
border-bottom: var(--
|
|
78
|
+
padding: var(--modal-padding-block) var(--modal-padding-inline);
|
|
79
|
+
border-bottom: var(--modal-border-size) var(--modal-border-style) var(--modal-border-color);
|
|
74
80
|
}
|
|
81
|
+
|
|
82
|
+
.DialogHeader h4 {
|
|
83
|
+
margin: 0;
|
|
84
|
+
font-size: var(--font-size-h5);
|
|
85
|
+
font-weight: var(--font-weight-semibold);
|
|
86
|
+
color: var(--text-color-heading);
|
|
87
|
+
}
|
|
88
|
+
|
|
75
89
|
.DialogContent {
|
|
76
|
-
padding: var(--
|
|
90
|
+
padding: var(--modal-padding-block) var(--modal-padding-inline);
|
|
91
|
+
overflow: auto;
|
|
77
92
|
}
|
|
78
|
-
</style>
|
|
93
|
+
</style>
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import type { Snippet } from "svelte";
|
|
2
2
|
type $$ComponentProps = {
|
|
3
|
-
/**
|
|
3
|
+
/** Whether the dialog is open (bindable) */
|
|
4
4
|
open?: boolean;
|
|
5
|
-
/**
|
|
5
|
+
/** Optional title for the dialog header */
|
|
6
6
|
title?: string;
|
|
7
|
-
/**
|
|
7
|
+
/** Dialog content */
|
|
8
8
|
children: Snippet;
|
|
9
|
-
/** Callback when
|
|
10
|
-
|
|
9
|
+
/** Callback when dialog closes */
|
|
10
|
+
onclose?: () => void;
|
|
11
11
|
};
|
|
12
12
|
declare const Dialog: import("svelte").Component<$$ComponentProps, {}, "open">;
|
|
13
13
|
type Dialog = ReturnType<typeof Dialog>;
|
|
@@ -1,33 +1,34 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import type { Snippet } from "svelte";
|
|
3
|
-
|
|
3
|
+
import "../css/lutra.css";
|
|
4
4
|
import Theme from "./Theme.svelte";
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
5
|
+
import ToastContainer from "./ToastContainer.svelte";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Default layout component that imports styles and provides theming.
|
|
9
|
+
* Includes ToastContainer for toast notifications.
|
|
10
|
+
*/
|
|
11
|
+
let {
|
|
12
|
+
theme,
|
|
13
|
+
children,
|
|
14
|
+
}: {
|
|
15
|
+
/** The theme to use. Leave empty for auto-detection. */
|
|
16
|
+
theme?: 'light' | 'dark' | undefined;
|
|
17
|
+
/** The content to display. */
|
|
18
|
+
children: Snippet;
|
|
19
|
+
} = $props();
|
|
19
20
|
</script>
|
|
20
21
|
|
|
21
22
|
<Theme theme={theme}>
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
23
|
+
<div class="Layout">
|
|
24
|
+
{@render children()}
|
|
25
|
+
</div>
|
|
26
|
+
<ToastContainer />
|
|
26
27
|
</Theme>
|
|
27
28
|
|
|
28
29
|
<style>
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
</style>
|
|
30
|
+
.Layout {
|
|
31
|
+
min-height: 100dvh;
|
|
32
|
+
height: 100dvh;
|
|
33
|
+
}
|
|
34
|
+
</style>
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { Snippet } from "svelte";
|
|
2
2
|
import "../css/lutra.css";
|
|
3
3
|
type $$ComponentProps = {
|
|
4
|
-
/** The theme to use
|
|
4
|
+
/** The theme to use. Leave empty for auto-detection. */
|
|
5
5
|
theme?: 'light' | 'dark' | undefined;
|
|
6
6
|
/** The content to display. */
|
|
7
7
|
children: Snippet;
|
|
@@ -1,202 +1,183 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import type { Snippet } from "svelte";
|
|
3
|
-
|
|
3
|
+
import type { MenuItem as Item } from "./MenuTypes.js";
|
|
4
4
|
import MenuItem from "./MenuItem.svelte";
|
|
5
|
+
import Popover from "./Popover.svelte";
|
|
5
6
|
import UiContent from "./UIContent.svelte";
|
|
6
|
-
import { arrowNavigation,
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
if(nextEl) {
|
|
90
|
-
nextEl.focus();
|
|
91
|
-
if(nextEl.tagName === "BUTTON" || nextEl.tagName === "A" && nextEl.parentElement?.classList.contains("Trigger")) {
|
|
92
|
-
nextEl.click();
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
}, 0);
|
|
96
|
-
break;
|
|
97
|
-
case "ArrowDown":
|
|
98
|
-
e.preventDefault();
|
|
99
|
-
arrowNavigation(contentEl, "down");
|
|
100
|
-
matchOnType(contentEl, e); // call to reset the search
|
|
101
|
-
keyboardHasFocus = true;
|
|
102
|
-
break;
|
|
103
|
-
case "ArrowUp":
|
|
104
|
-
e.preventDefault();
|
|
105
|
-
arrowNavigation(contentEl, "up");
|
|
106
|
-
matchOnType(contentEl, e); // call to reset the search
|
|
107
|
-
keyboardHasFocus = true;
|
|
108
|
-
break;
|
|
109
|
-
case "Enter":
|
|
110
|
-
case "Space":
|
|
111
|
-
e.preventDefault();
|
|
112
|
-
active.click();
|
|
113
|
-
break;
|
|
114
|
-
default:
|
|
115
|
-
matchOnType(contentEl, e);
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
function mouseover(e: MouseEvent, item: Item, index: number) {
|
|
120
|
-
if(item.type === "item") {
|
|
121
|
-
currentIndex = index;
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
|
|
7
|
+
import { arrowNavigation, matchOnType } from "../util/keyboard.svelte.js";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* A dropdown menu using the base Popover component.
|
|
11
|
+
* Handles menu-specific keyboard navigation and item rendering.
|
|
12
|
+
*/
|
|
13
|
+
let {
|
|
14
|
+
open = $bindable(false),
|
|
15
|
+
items,
|
|
16
|
+
trigger: triggerProp,
|
|
17
|
+
width,
|
|
18
|
+
maxWidth,
|
|
19
|
+
}: {
|
|
20
|
+
/** Whether the menu is open */
|
|
21
|
+
open?: boolean;
|
|
22
|
+
/** The items to display in the menu */
|
|
23
|
+
items: Item[];
|
|
24
|
+
/** The trigger snippet or string */
|
|
25
|
+
trigger: string | Snippet<[{ toggle: () => void; isOpen: boolean; triggerAttrs?: Record<string, string> }]>;
|
|
26
|
+
/** The width of the menu */
|
|
27
|
+
width?: string;
|
|
28
|
+
/** The max width of the menu */
|
|
29
|
+
maxWidth?: string;
|
|
30
|
+
} = $props();
|
|
31
|
+
|
|
32
|
+
let menuEl: HTMLDivElement | null = $state(null);
|
|
33
|
+
let keyboardHasFocus: boolean = $state(false);
|
|
34
|
+
|
|
35
|
+
function handleOpen() {
|
|
36
|
+
window.addEventListener("keydown", onkeydown);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function handleClose() {
|
|
40
|
+
window.removeEventListener("keydown", onkeydown);
|
|
41
|
+
keyboardHasFocus = false;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function onkeydown(e: KeyboardEvent) {
|
|
45
|
+
if (!open || !menuEl) return;
|
|
46
|
+
|
|
47
|
+
switch (e.key) {
|
|
48
|
+
case "ArrowDown":
|
|
49
|
+
e.preventDefault();
|
|
50
|
+
arrowNavigation(menuEl, "down");
|
|
51
|
+
matchOnType(menuEl, e);
|
|
52
|
+
keyboardHasFocus = true;
|
|
53
|
+
break;
|
|
54
|
+
case "ArrowUp":
|
|
55
|
+
e.preventDefault();
|
|
56
|
+
arrowNavigation(menuEl, "up");
|
|
57
|
+
matchOnType(menuEl, e);
|
|
58
|
+
keyboardHasFocus = true;
|
|
59
|
+
break;
|
|
60
|
+
case "Enter":
|
|
61
|
+
case " ":
|
|
62
|
+
e.preventDefault();
|
|
63
|
+
(document.activeElement as HTMLElement)?.click();
|
|
64
|
+
break;
|
|
65
|
+
case "Tab":
|
|
66
|
+
e.preventDefault();
|
|
67
|
+
open = false;
|
|
68
|
+
break;
|
|
69
|
+
default:
|
|
70
|
+
matchOnType(menuEl, e);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function handleMouseover(e: MouseEvent, item: Item, index: number) {
|
|
75
|
+
if (item.type === "item") {
|
|
76
|
+
keyboardHasFocus = false;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function handleSelect(item: Item, index: number) {
|
|
81
|
+
open = false;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
let scrollable = $derived.by(() => {
|
|
85
|
+
if (!menuEl) return false;
|
|
86
|
+
return menuEl.scrollHeight > menuEl.clientHeight;
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
const isStringTrigger = $derived(typeof triggerProp === "string");
|
|
125
90
|
</script>
|
|
126
91
|
|
|
127
|
-
|
|
128
92
|
<UiContent>
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
93
|
+
<Popover
|
|
94
|
+
bind:open
|
|
95
|
+
position="block-end span-inline-end"
|
|
96
|
+
fallbacks={["flip-block", "flip-inline", "flip-block flip-inline"]}
|
|
97
|
+
offset="0.5rem 0 0 0"
|
|
98
|
+
{width}
|
|
99
|
+
{maxWidth}
|
|
100
|
+
onopen={handleOpen}
|
|
101
|
+
onclose={handleClose}
|
|
102
|
+
class="MenuDropdownPopover"
|
|
103
|
+
>
|
|
104
|
+
{#snippet trigger(popoverArgs)}
|
|
105
|
+
<span class="MenuDropdownTrigger" style={popoverArgs.anchorAttrs.style}>
|
|
106
|
+
{#if isStringTrigger}
|
|
107
|
+
<button
|
|
108
|
+
type="button"
|
|
109
|
+
class="button"
|
|
110
|
+
{...popoverArgs.triggerAttrs}
|
|
111
|
+
aria-haspopup="menu"
|
|
112
|
+
>
|
|
113
|
+
{triggerProp}
|
|
114
|
+
</button>
|
|
115
|
+
{:else if typeof triggerProp !== "string"}
|
|
116
|
+
{@render triggerProp({
|
|
117
|
+
toggle: popoverArgs.toggle,
|
|
118
|
+
isOpen: popoverArgs.isOpen,
|
|
119
|
+
triggerAttrs: popoverArgs.triggerAttrs,
|
|
120
|
+
})}
|
|
121
|
+
{/if}
|
|
122
|
+
</span>
|
|
123
|
+
{/snippet}
|
|
124
|
+
|
|
125
|
+
<div
|
|
126
|
+
class="MenuDropdownContent"
|
|
127
|
+
class:scrollable
|
|
128
|
+
role="menu"
|
|
129
|
+
bind:this={menuEl}
|
|
130
|
+
>
|
|
131
|
+
<ul>
|
|
132
|
+
{#each items as item, index}
|
|
133
|
+
<MenuItem
|
|
134
|
+
{keyboardHasFocus}
|
|
135
|
+
onmouseover={handleMouseover}
|
|
136
|
+
onselect={handleSelect}
|
|
137
|
+
{item}
|
|
138
|
+
{index}
|
|
139
|
+
/>
|
|
140
|
+
{/each}
|
|
141
|
+
</ul>
|
|
142
|
+
</div>
|
|
143
|
+
</Popover>
|
|
160
144
|
</UiContent>
|
|
161
145
|
|
|
162
146
|
<style>
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
list-style: none;
|
|
200
|
-
padding: 0;
|
|
201
|
-
}
|
|
147
|
+
.MenuDropdownTrigger {
|
|
148
|
+
display: inline-block;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
:global(.MenuDropdownPopover) {
|
|
152
|
+
/* Override Popover defaults for menu styling */
|
|
153
|
+
border: var(--menu-border-size) var(--menu-border-style) var(--menu-border-color);
|
|
154
|
+
border-radius: var(--menu-border-radius);
|
|
155
|
+
box-shadow: 0 0.5rem 1rem var(--shadow-color);
|
|
156
|
+
background-color: var(--menu-background-color);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
.MenuDropdownContent {
|
|
160
|
+
width: var(--popover-width, fit-content);
|
|
161
|
+
max-width: var(--popover-max-width, 50ch);
|
|
162
|
+
max-height: calc(50vh - 2rem);
|
|
163
|
+
overflow-x: clip;
|
|
164
|
+
overflow-y: auto;
|
|
165
|
+
scrollbar-width: thin;
|
|
166
|
+
scrollbar-color: var(--scrollbar-color);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
.MenuDropdownContent.scrollable {
|
|
170
|
+
border-top-right-radius: 0;
|
|
171
|
+
border-bottom-right-radius: 0;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
.MenuDropdownContent :global(:has(li:last-of-type[data-type="item"])) {
|
|
175
|
+
padding-block-end: 0.5rem;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
ul {
|
|
179
|
+
margin: 0;
|
|
180
|
+
list-style: none;
|
|
181
|
+
padding: 0;
|
|
182
|
+
}
|
|
202
183
|
</style>
|
|
@@ -5,10 +5,11 @@ type $$ComponentProps = {
|
|
|
5
5
|
open?: boolean;
|
|
6
6
|
/** The items to display in the menu */
|
|
7
7
|
items: Item[];
|
|
8
|
-
/** The trigger
|
|
8
|
+
/** The trigger snippet or string */
|
|
9
9
|
trigger: string | Snippet<[{
|
|
10
10
|
toggle: () => void;
|
|
11
11
|
isOpen: boolean;
|
|
12
|
+
triggerAttrs?: Record<string, string>;
|
|
12
13
|
}]>;
|
|
13
14
|
/** The width of the menu */
|
|
14
15
|
width?: string;
|
|
@@ -7,12 +7,14 @@
|
|
|
7
7
|
item,
|
|
8
8
|
index,
|
|
9
9
|
onmouseover,
|
|
10
|
+
onselect,
|
|
10
11
|
keyboardHasFocus,
|
|
11
12
|
shape = 'default',
|
|
12
13
|
}: {
|
|
13
14
|
item: Item;
|
|
14
15
|
index: number;
|
|
15
16
|
onmouseover?: (e: MouseEvent, item: Item, index: number) => void;
|
|
17
|
+
onselect?: (item: Item, index: number) => void;
|
|
16
18
|
keyboardHasFocus?: boolean;
|
|
17
19
|
shape?: 'default' | 'rounded' | 'pill';
|
|
18
20
|
} = $props();
|
|
@@ -25,6 +27,13 @@
|
|
|
25
27
|
el?.focus();
|
|
26
28
|
}
|
|
27
29
|
}
|
|
30
|
+
|
|
31
|
+
function handleClick(e: MouseEvent | KeyboardEvent) {
|
|
32
|
+
if(item.type === 'item' && item.onclick) {
|
|
33
|
+
item.onclick(e as MouseEvent, item);
|
|
34
|
+
onselect?.(item, index);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
28
37
|
</script>
|
|
29
38
|
|
|
30
39
|
<!-- svelte-ignore a11y_mouse_events_have_key_events -->
|
|
@@ -59,7 +68,7 @@
|
|
|
59
68
|
{/if}
|
|
60
69
|
</a>
|
|
61
70
|
{:else if item.onclick}
|
|
62
|
-
<button type="button" onclick={
|
|
71
|
+
<button type="button" onclick={handleClick} class="Item" bind:this={el}>
|
|
63
72
|
<span class="Content">
|
|
64
73
|
<MenuItemContent {...item} />
|
|
65
74
|
</span>
|
|
@@ -67,7 +76,7 @@
|
|
|
67
76
|
<span class="Shortcut">{item.shortcut}</span>
|
|
68
77
|
{/if}
|
|
69
78
|
</button>
|
|
70
|
-
{:else if item.component}
|
|
79
|
+
{:else if item.component || item.render || item.snippet}
|
|
71
80
|
<div class="Item Custom">
|
|
72
81
|
<MenuItemContent {...item} />
|
|
73
82
|
</div>
|