lutra 0.1.34 → 0.1.36
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/dist/components/MenuDropdown.svelte +52 -54
- package/dist/components/MenuDropdown.svelte.d.ts +2 -0
- package/dist/components/ModalContent.svelte +12 -107
- package/dist/components/ModalContent.svelte.d.ts +0 -7
- package/dist/components/Popover.svelte +53 -22
- package/dist/components/Popover.svelte.d.ts +6 -2
- package/dist/components/Toast.svelte +19 -14
- package/dist/components/index.d.ts +0 -1
- package/dist/components/index.js +0 -1
- package/dist/components/modals.svelte.d.ts +7 -0
- package/dist/components/modals.svelte.js +7 -7
- package/dist/css/1-props.css +24 -0
- package/dist/css/themes/DefaultTheme.css +19 -1
- package/package.json +2 -2
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
open = $bindable(false),
|
|
15
15
|
items,
|
|
16
16
|
trigger: triggerProp,
|
|
17
|
+
align = "start",
|
|
17
18
|
width,
|
|
18
19
|
maxWidth,
|
|
19
20
|
}: {
|
|
@@ -23,6 +24,8 @@
|
|
|
23
24
|
items: Item[];
|
|
24
25
|
/** The trigger snippet or string */
|
|
25
26
|
trigger: string | Snippet<[{ toggle: () => void; isOpen: boolean; triggerAttrs?: Record<string, string> }]>;
|
|
27
|
+
/** Horizontal alignment: "start" (default), "center", or "end" */
|
|
28
|
+
align?: "start" | "center" | "end";
|
|
26
29
|
/** The width of the menu */
|
|
27
30
|
width?: string;
|
|
28
31
|
/** The max width of the menu */
|
|
@@ -92,54 +95,60 @@
|
|
|
92
95
|
<UiContent>
|
|
93
96
|
<Popover
|
|
94
97
|
bind:open
|
|
95
|
-
position="block-end
|
|
96
|
-
|
|
97
|
-
|
|
98
|
+
position="block-end"
|
|
99
|
+
{align}
|
|
100
|
+
fallbacks={["flip-block", "flip-inline"]}
|
|
101
|
+
offset="0.5rem"
|
|
98
102
|
{width}
|
|
99
|
-
{maxWidth}
|
|
103
|
+
maxWidth={maxWidth || "50ch"}
|
|
104
|
+
maxHeight="calc(50vh - 2rem)"
|
|
100
105
|
onopen={handleOpen}
|
|
101
106
|
onclose={handleClose}
|
|
102
107
|
class="MenuDropdownPopover"
|
|
108
|
+
--popover-background="var(--menu-background-color)"
|
|
109
|
+
--popover-border="var(--menu-border-size) var(--menu-border-style) var(--menu-border-color)"
|
|
110
|
+
--popover-border-radius="var(--menu-border-radius)"
|
|
111
|
+
--popover-shadow="0 0.5rem 1rem var(--shadow-color)"
|
|
103
112
|
>
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
113
|
+
{#snippet trigger(popoverArgs)}
|
|
114
|
+
<span class="MenuDropdownTrigger" style={popoverArgs.anchorAttrs.style}>
|
|
115
|
+
{#if isStringTrigger}
|
|
116
|
+
<button
|
|
117
|
+
type="button"
|
|
118
|
+
class="button"
|
|
119
|
+
{...popoverArgs.triggerAttrs}
|
|
120
|
+
aria-haspopup="menu"
|
|
121
|
+
>
|
|
122
|
+
{triggerProp}
|
|
123
|
+
</button>
|
|
124
|
+
{:else if typeof triggerProp !== "string"}
|
|
125
|
+
{@render triggerProp({
|
|
126
|
+
toggle: popoverArgs.toggle,
|
|
127
|
+
isOpen: popoverArgs.isOpen,
|
|
128
|
+
triggerAttrs: popoverArgs.triggerAttrs,
|
|
129
|
+
})}
|
|
130
|
+
{/if}
|
|
131
|
+
</span>
|
|
132
|
+
{/snippet}
|
|
133
|
+
|
|
134
|
+
<div
|
|
135
|
+
class="MenuDropdownContent"
|
|
136
|
+
class:scrollable
|
|
137
|
+
role="menu"
|
|
138
|
+
bind:this={menuEl}
|
|
139
|
+
>
|
|
140
|
+
<ul>
|
|
141
|
+
{#each items as item, index}
|
|
142
|
+
<MenuItem
|
|
143
|
+
{keyboardHasFocus}
|
|
144
|
+
onmouseover={handleMouseover}
|
|
145
|
+
onselect={handleSelect}
|
|
146
|
+
{item}
|
|
147
|
+
{index}
|
|
148
|
+
/>
|
|
149
|
+
{/each}
|
|
150
|
+
</ul>
|
|
151
|
+
</div>
|
|
143
152
|
</Popover>
|
|
144
153
|
</UiContent>
|
|
145
154
|
|
|
@@ -148,18 +157,7 @@
|
|
|
148
157
|
display: inline-block;
|
|
149
158
|
}
|
|
150
159
|
|
|
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
160
|
.MenuDropdownContent {
|
|
160
|
-
width: var(--popover-width, fit-content);
|
|
161
|
-
max-width: var(--popover-max-width, 50ch);
|
|
162
|
-
max-height: calc(50vh - 2rem);
|
|
163
161
|
overflow-x: clip;
|
|
164
162
|
overflow-y: auto;
|
|
165
163
|
scrollbar-width: thin;
|
|
@@ -11,6 +11,8 @@ type $$ComponentProps = {
|
|
|
11
11
|
isOpen: boolean;
|
|
12
12
|
triggerAttrs?: Record<string, string>;
|
|
13
13
|
}]>;
|
|
14
|
+
/** Horizontal alignment: "start" (default), "center", or "end" */
|
|
15
|
+
align?: "start" | "center" | "end";
|
|
14
16
|
/** The width of the menu */
|
|
15
17
|
width?: string;
|
|
16
18
|
/** The max width of the menu */
|
|
@@ -1,9 +1,16 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import type { Component, Snippet } from 'svelte';
|
|
3
|
-
import { onMount, onDestroy } from 'svelte';
|
|
4
3
|
import type { ModalButton } from './ModalTypes.js';
|
|
5
4
|
import type { RenderFn } from '../types.js';
|
|
6
5
|
|
|
6
|
+
/**
|
|
7
|
+
* Internal component for rendering modal content.
|
|
8
|
+
* Used by openModal() - not intended for direct use.
|
|
9
|
+
* Use <Modal> or <Dialog> instead.
|
|
10
|
+
*
|
|
11
|
+
* Note: Focus trapping, ESC handling, and backdrop are provided
|
|
12
|
+
* by the parent <dialog> element, not by this component.
|
|
13
|
+
*/
|
|
7
14
|
let {
|
|
8
15
|
children,
|
|
9
16
|
text,
|
|
@@ -18,13 +25,6 @@
|
|
|
18
25
|
shape = 'rounded',
|
|
19
26
|
contained = true,
|
|
20
27
|
unstyled = false,
|
|
21
|
-
showScrim = true,
|
|
22
|
-
closeOnScrim = true,
|
|
23
|
-
trapFocus = true,
|
|
24
|
-
dismissOnEsc = true,
|
|
25
|
-
width,
|
|
26
|
-
maxWidth,
|
|
27
|
-
maxHeight,
|
|
28
28
|
}: {
|
|
29
29
|
children?: Snippet<[close: () => void]>;
|
|
30
30
|
text?: string;
|
|
@@ -39,23 +39,17 @@
|
|
|
39
39
|
shape?: 'rounded' | 'sharp';
|
|
40
40
|
contained?: boolean;
|
|
41
41
|
unstyled?: boolean;
|
|
42
|
-
showScrim?: boolean;
|
|
43
|
-
closeOnScrim?: boolean;
|
|
44
|
-
trapFocus?: boolean;
|
|
45
|
-
dismissOnEsc?: boolean;
|
|
46
|
-
width?: string;
|
|
47
|
-
maxWidth?: string;
|
|
48
|
-
maxHeight?: string;
|
|
49
42
|
} = $props();
|
|
50
43
|
|
|
51
|
-
let dialogEl: HTMLDivElement | null = $state(null);
|
|
52
|
-
let previousActiveElement: HTMLElement | null = null;
|
|
53
44
|
const baseId = crypto.randomUUID();
|
|
54
45
|
let titleId = $derived(title ? `modal-title-${baseId}` : undefined);
|
|
55
46
|
let loading: Record<number, boolean> = $state({});
|
|
56
47
|
|
|
57
48
|
async function handleButtonClick(btn: ModalButton, index: number) {
|
|
58
|
-
if (!btn.onclick)
|
|
49
|
+
if (!btn.onclick) {
|
|
50
|
+
close();
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
59
53
|
const result = btn.onclick(close);
|
|
60
54
|
if (result instanceof Promise) {
|
|
61
55
|
loading[index] = true;
|
|
@@ -66,76 +60,8 @@
|
|
|
66
60
|
}
|
|
67
61
|
}
|
|
68
62
|
}
|
|
69
|
-
|
|
70
|
-
onMount(() => {
|
|
71
|
-
previousActiveElement = document.activeElement as HTMLElement;
|
|
72
|
-
if (dialogEl && trapFocus) {
|
|
73
|
-
// Focus first focusable element
|
|
74
|
-
const focusableElements = getFocusableElements();
|
|
75
|
-
if (focusableElements.length > 0) {
|
|
76
|
-
focusableElements[0].focus();
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
dialogEl?.focus();
|
|
80
|
-
window.addEventListener('keydown', handleKeydown);
|
|
81
|
-
});
|
|
82
|
-
|
|
83
|
-
onDestroy(() => {
|
|
84
|
-
// Restore focus on unmount
|
|
85
|
-
previousActiveElement?.focus();
|
|
86
|
-
window.removeEventListener('keydown', handleKeydown);
|
|
87
|
-
});
|
|
88
|
-
|
|
89
|
-
function getFocusableElements(): HTMLElement[] {
|
|
90
|
-
if (!dialogEl) return [];
|
|
91
|
-
const selector = 'a[href], button:not([disabled]), textarea:not([disabled]), input:not([disabled]), select:not([disabled]), [tabindex]:not([tabindex="-1"])';
|
|
92
|
-
return Array.from(dialogEl.querySelectorAll(selector));
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
function handleKeydown(e: KeyboardEvent) {
|
|
96
|
-
if (dismissOnEsc && e.key === 'Escape') {
|
|
97
|
-
e.preventDefault();
|
|
98
|
-
close();
|
|
99
|
-
return;
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
if (trapFocus && e.key === 'Tab') {
|
|
103
|
-
const focusableElements = getFocusableElements();
|
|
104
|
-
if (focusableElements.length === 0) return;
|
|
105
|
-
|
|
106
|
-
const firstElement = focusableElements[0];
|
|
107
|
-
const lastElement = focusableElements[focusableElements.length - 1];
|
|
108
|
-
const activeElement = document.activeElement;
|
|
109
|
-
|
|
110
|
-
if (e.shiftKey) {
|
|
111
|
-
// Shift+Tab
|
|
112
|
-
if (activeElement === firstElement) {
|
|
113
|
-
e.preventDefault();
|
|
114
|
-
lastElement.focus();
|
|
115
|
-
}
|
|
116
|
-
} else {
|
|
117
|
-
// Tab
|
|
118
|
-
if (activeElement === lastElement) {
|
|
119
|
-
e.preventDefault();
|
|
120
|
-
firstElement.focus();
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
function handleScrimClick(e: MouseEvent) {
|
|
127
|
-
if (closeOnScrim && e.target === e.currentTarget) {
|
|
128
|
-
close();
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
63
|
</script>
|
|
132
64
|
|
|
133
|
-
{#if showScrim}
|
|
134
|
-
<!-- svelte-ignore a11y_click_events_have_key_events -->
|
|
135
|
-
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
|
136
|
-
<div class="ModalScrim" onclick={handleScrimClick}></div>
|
|
137
|
-
{/if}
|
|
138
|
-
|
|
139
65
|
<div
|
|
140
66
|
class="ModalContent {shape}"
|
|
141
67
|
class:contained
|
|
@@ -143,10 +69,6 @@
|
|
|
143
69
|
role="dialog"
|
|
144
70
|
aria-modal="true"
|
|
145
71
|
aria-labelledby={titleId}
|
|
146
|
-
tabindex="-1"
|
|
147
|
-
bind:this={dialogEl}
|
|
148
|
-
onkeydown={handleKeydown}
|
|
149
|
-
style="--modal-width: {width}; --modal-max-width: {maxWidth}; --modal-max-height: {maxHeight};"
|
|
150
72
|
>
|
|
151
73
|
<div class="ModalContentArea">
|
|
152
74
|
{#if title}
|
|
@@ -185,14 +107,6 @@
|
|
|
185
107
|
</div>
|
|
186
108
|
|
|
187
109
|
<style>
|
|
188
|
-
.ModalScrim {
|
|
189
|
-
background: var(--scrim-background);
|
|
190
|
-
backdrop-filter: var(--scrim-backdrop-filter);
|
|
191
|
-
position: fixed;
|
|
192
|
-
inset: 0;
|
|
193
|
-
z-index: 0;
|
|
194
|
-
}
|
|
195
|
-
|
|
196
110
|
.ModalContent {
|
|
197
111
|
display: grid;
|
|
198
112
|
grid-template-rows: 1fr auto;
|
|
@@ -205,8 +119,6 @@
|
|
|
205
119
|
border-radius: var(--modal-border-radius);
|
|
206
120
|
box-shadow: 0 0.5rem 1rem var(--modal-shadow-color);
|
|
207
121
|
overflow: hidden;
|
|
208
|
-
position: relative;
|
|
209
|
-
z-index: 1;
|
|
210
122
|
}
|
|
211
123
|
|
|
212
124
|
.ModalContent.sharp {
|
|
@@ -269,11 +181,4 @@
|
|
|
269
181
|
border: none;
|
|
270
182
|
padding: 0;
|
|
271
183
|
}
|
|
272
|
-
|
|
273
|
-
@media (prefers-reduced-motion: reduce) {
|
|
274
|
-
.ModalContent {
|
|
275
|
-
transition: none;
|
|
276
|
-
}
|
|
277
|
-
}
|
|
278
184
|
</style>
|
|
279
|
-
|
|
@@ -15,13 +15,6 @@ type $$ComponentProps = {
|
|
|
15
15
|
shape?: 'rounded' | 'sharp';
|
|
16
16
|
contained?: boolean;
|
|
17
17
|
unstyled?: boolean;
|
|
18
|
-
showScrim?: boolean;
|
|
19
|
-
closeOnScrim?: boolean;
|
|
20
|
-
trapFocus?: boolean;
|
|
21
|
-
dismissOnEsc?: boolean;
|
|
22
|
-
width?: string;
|
|
23
|
-
maxWidth?: string;
|
|
24
|
-
maxHeight?: string;
|
|
25
18
|
};
|
|
26
19
|
declare const ModalContent: Component<$$ComponentProps, {}, "">;
|
|
27
20
|
type ModalContent = ReturnType<typeof ModalContent>;
|
|
@@ -5,17 +5,26 @@
|
|
|
5
5
|
/**
|
|
6
6
|
* A base popover component using the Popover API and CSS Anchor Positioning.
|
|
7
7
|
* Use this as a foundation for dropdowns, tooltips, contextual help, etc.
|
|
8
|
+
*
|
|
9
|
+
* Styling is controlled via CSS custom properties:
|
|
10
|
+
* - `--popover-background` - Background color
|
|
11
|
+
* - `--popover-border` - Border shorthand
|
|
12
|
+
* - `--popover-border-radius` - Border radius
|
|
13
|
+
* - `--popover-shadow` - Box shadow
|
|
14
|
+
* - `--popover-padding` - Content padding
|
|
8
15
|
*/
|
|
9
16
|
let {
|
|
10
17
|
open = $bindable(false),
|
|
11
18
|
anchor,
|
|
12
|
-
position = "block-end
|
|
19
|
+
position = "block-end",
|
|
20
|
+
align = "center",
|
|
13
21
|
fallbacks = ["flip-block", "flip-inline"],
|
|
14
22
|
mode = "auto",
|
|
15
23
|
offset,
|
|
16
24
|
width,
|
|
17
25
|
maxWidth,
|
|
18
26
|
maxHeight,
|
|
27
|
+
unstyled = false,
|
|
19
28
|
trigger,
|
|
20
29
|
children,
|
|
21
30
|
class: className = "",
|
|
@@ -26,13 +35,15 @@
|
|
|
26
35
|
open?: boolean;
|
|
27
36
|
/** Element to anchor to (if no trigger snippet provided) */
|
|
28
37
|
anchor?: HTMLElement | string;
|
|
29
|
-
/** CSS position-area value */
|
|
38
|
+
/** CSS position-area value for placement */
|
|
30
39
|
position?: string;
|
|
40
|
+
/** Alignment: "start", "center", "end", or "anchor-center" */
|
|
41
|
+
align?: "start" | "center" | "end" | "anchor-center";
|
|
31
42
|
/** CSS position-try-fallbacks values */
|
|
32
43
|
fallbacks?: string[];
|
|
33
44
|
/** Popover mode: "auto" (light dismiss) or "manual" */
|
|
34
45
|
mode?: "auto" | "manual";
|
|
35
|
-
/** Gap between anchor and popover */
|
|
46
|
+
/** Gap between anchor and popover (CSS margin value) */
|
|
36
47
|
offset?: string;
|
|
37
48
|
/** Width of the popover */
|
|
38
49
|
width?: string;
|
|
@@ -40,6 +51,8 @@
|
|
|
40
51
|
maxWidth?: string;
|
|
41
52
|
/** Max height of the popover */
|
|
42
53
|
maxHeight?: string;
|
|
54
|
+
/** Remove default styling (background, border, shadow) */
|
|
55
|
+
unstyled?: boolean;
|
|
43
56
|
/** Optional trigger snippet - receives attrs to spread on trigger element */
|
|
44
57
|
trigger?: Snippet<[{
|
|
45
58
|
toggle: () => void;
|
|
@@ -127,14 +140,6 @@
|
|
|
127
140
|
}
|
|
128
141
|
}
|
|
129
142
|
|
|
130
|
-
function show() {
|
|
131
|
-
popoverEl?.showPopover();
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
function hide() {
|
|
135
|
-
popoverEl?.hidePopover();
|
|
136
|
-
}
|
|
137
|
-
|
|
138
143
|
// Attrs to spread on trigger element
|
|
139
144
|
const triggerAttrs = $derived({
|
|
140
145
|
popovertarget: id,
|
|
@@ -148,12 +153,23 @@
|
|
|
148
153
|
style: `anchor-name: ${anchorName}`,
|
|
149
154
|
});
|
|
150
155
|
|
|
156
|
+
// Map align to CSS justify-self value
|
|
157
|
+
const justifySelf = $derived(
|
|
158
|
+
align === "center" ? "anchor-center" :
|
|
159
|
+
align === "anchor-center" ? "anchor-center" :
|
|
160
|
+
align === "start" ? "self-start" :
|
|
161
|
+
align === "end" ? "self-end" :
|
|
162
|
+
"anchor-center"
|
|
163
|
+
);
|
|
164
|
+
|
|
151
165
|
// CSS custom properties for the popover
|
|
152
166
|
const popoverStyle = $derived([
|
|
153
167
|
`position-anchor: ${anchorName}`,
|
|
154
168
|
`position-area: ${position}`,
|
|
169
|
+
`justify-self: ${justifySelf}`,
|
|
170
|
+
`position-try-order: most-block-size`,
|
|
155
171
|
fallbacks.length ? `position-try-fallbacks: ${fallbacks.join(", ")}` : "",
|
|
156
|
-
offset ?
|
|
172
|
+
offset ? `--popover-offset: ${offset}` : "",
|
|
157
173
|
width ? `--popover-width: ${width}` : "",
|
|
158
174
|
maxWidth ? `--popover-max-width: ${maxWidth}` : "",
|
|
159
175
|
maxHeight ? `--popover-max-height: ${maxHeight}` : "",
|
|
@@ -171,6 +187,7 @@
|
|
|
171
187
|
{id}
|
|
172
188
|
popover={mode}
|
|
173
189
|
class="Popover {className}"
|
|
190
|
+
class:unstyled
|
|
174
191
|
style={popoverStyle}
|
|
175
192
|
>
|
|
176
193
|
{@render children()}
|
|
@@ -182,19 +199,34 @@
|
|
|
182
199
|
}
|
|
183
200
|
|
|
184
201
|
.Popover {
|
|
185
|
-
/* Reset browser defaults */
|
|
186
|
-
margin: 0;
|
|
202
|
+
/* Reset browser popover defaults */
|
|
187
203
|
padding: 0;
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
204
|
+
|
|
205
|
+
/* Default surface styling - can be overridden via CSS custom properties */
|
|
206
|
+
background: var(--popover-background, var(--surface-background, var(--background-body)));
|
|
207
|
+
border: var(--popover-border, var(--surface-border, var(--border-size-thin) var(--border-style) var(--border-color)));
|
|
208
|
+
border-radius: var(--popover-border-radius, var(--surface-border-radius, var(--border-radius-base)));
|
|
209
|
+
box-shadow: var(--popover-shadow, var(--surface-shadow, 0 0.5rem 1rem var(--shadow-color)));
|
|
191
210
|
|
|
192
211
|
/* Sizing */
|
|
193
|
-
width: var(--popover-width,
|
|
194
|
-
max-width: var(--popover-max-width,
|
|
195
|
-
max-height: var(--popover-max-height,
|
|
212
|
+
width: var(--popover-width, max-content);
|
|
213
|
+
max-width: var(--popover-max-width, calc(100vw - 2rem));
|
|
214
|
+
max-height: var(--popover-max-height, calc(100vh - 2rem));
|
|
215
|
+
overflow: auto;
|
|
216
|
+
|
|
217
|
+
/* Offset from anchor */
|
|
218
|
+
margin-block: var(--popover-offset, 0.25rem);
|
|
219
|
+
margin-inline: 0;
|
|
196
220
|
|
|
197
|
-
/*
|
|
221
|
+
/* Ensure popover stays in viewport */
|
|
222
|
+
inset: auto;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
.Popover.unstyled {
|
|
226
|
+
background: transparent;
|
|
227
|
+
border: none;
|
|
228
|
+
border-radius: 0;
|
|
229
|
+
box-shadow: none;
|
|
198
230
|
}
|
|
199
231
|
|
|
200
232
|
/* Animation */
|
|
@@ -219,4 +251,3 @@
|
|
|
219
251
|
}
|
|
220
252
|
}
|
|
221
253
|
</style>
|
|
222
|
-
|
|
@@ -4,13 +4,15 @@ type $$ComponentProps = {
|
|
|
4
4
|
open?: boolean;
|
|
5
5
|
/** Element to anchor to (if no trigger snippet provided) */
|
|
6
6
|
anchor?: HTMLElement | string;
|
|
7
|
-
/** CSS position-area value */
|
|
7
|
+
/** CSS position-area value for placement */
|
|
8
8
|
position?: string;
|
|
9
|
+
/** Alignment: "start", "center", "end", or "anchor-center" */
|
|
10
|
+
align?: "start" | "center" | "end" | "anchor-center";
|
|
9
11
|
/** CSS position-try-fallbacks values */
|
|
10
12
|
fallbacks?: string[];
|
|
11
13
|
/** Popover mode: "auto" (light dismiss) or "manual" */
|
|
12
14
|
mode?: "auto" | "manual";
|
|
13
|
-
/** Gap between anchor and popover */
|
|
15
|
+
/** Gap between anchor and popover (CSS margin value) */
|
|
14
16
|
offset?: string;
|
|
15
17
|
/** Width of the popover */
|
|
16
18
|
width?: string;
|
|
@@ -18,6 +20,8 @@ type $$ComponentProps = {
|
|
|
18
20
|
maxWidth?: string;
|
|
19
21
|
/** Max height of the popover */
|
|
20
22
|
maxHeight?: string;
|
|
23
|
+
/** Remove default styling (background, border, shadow) */
|
|
24
|
+
unstyled?: boolean;
|
|
21
25
|
/** Optional trigger snippet - receives attrs to spread on trigger element */
|
|
22
26
|
trigger?: Snippet<[
|
|
23
27
|
{
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
8
|
* A styled toast/notification card.
|
|
9
|
+
* Uses the same surface styling system as Popover and Modal.
|
|
9
10
|
* Typically used via addToast() but can be used standalone.
|
|
10
11
|
*/
|
|
11
12
|
let {
|
|
@@ -76,14 +77,19 @@
|
|
|
76
77
|
position: relative;
|
|
77
78
|
display: grid;
|
|
78
79
|
grid-template-columns: 1fr;
|
|
79
|
-
gap: var(--
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
border
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
80
|
+
gap: var(--notification-gap, var(--space-sm));
|
|
81
|
+
|
|
82
|
+
/* Surface styling - consistent with Popover and Modal */
|
|
83
|
+
background: var(--notification-background-color, var(--background-body));
|
|
84
|
+
border: var(--notification-border-size) var(--notification-border-style) var(--notification-border-color);
|
|
85
|
+
border-radius: var(--notification-border-radius);
|
|
86
|
+
box-shadow: 0 0.25rem 1rem var(--notification-shadow-color);
|
|
87
|
+
|
|
88
|
+
padding-block: var(--notification-padding-block);
|
|
89
|
+
padding-inline: var(--notification-padding-inline);
|
|
90
|
+
min-width: var(--notification-min-width);
|
|
91
|
+
max-width: var(--notification-max-width);
|
|
92
|
+
|
|
87
93
|
animation: toast-enter 0.2s ease-out;
|
|
88
94
|
}
|
|
89
95
|
|
|
@@ -102,8 +108,8 @@
|
|
|
102
108
|
.Icon {
|
|
103
109
|
display: flex;
|
|
104
110
|
align-items: flex-start;
|
|
105
|
-
font-size: var(--
|
|
106
|
-
color: var(--
|
|
111
|
+
font-size: var(--notification-icon-size);
|
|
112
|
+
color: var(--text-color-p-subtle);
|
|
107
113
|
}
|
|
108
114
|
|
|
109
115
|
.Content {
|
|
@@ -113,14 +119,14 @@
|
|
|
113
119
|
}
|
|
114
120
|
|
|
115
121
|
.Title {
|
|
116
|
-
font-weight: var(--
|
|
117
|
-
color: var(--
|
|
122
|
+
font-weight: var(--notification-title-font-weight);
|
|
123
|
+
color: var(--notification-title-color);
|
|
118
124
|
line-height: 1.3;
|
|
119
125
|
}
|
|
120
126
|
|
|
121
127
|
.Text {
|
|
122
128
|
margin: 0;
|
|
123
|
-
color: var(--
|
|
129
|
+
color: var(--notification-text-color);
|
|
124
130
|
line-height: 1.4;
|
|
125
131
|
}
|
|
126
132
|
|
|
@@ -147,4 +153,3 @@
|
|
|
147
153
|
}
|
|
148
154
|
}
|
|
149
155
|
</style>
|
|
150
|
-
|
|
@@ -13,7 +13,6 @@ export { default as MenuDropdown } from './MenuDropdown.svelte';
|
|
|
13
13
|
export { default as MenuItem } from './MenuItem.svelte';
|
|
14
14
|
export { default as MenuItemContent } from './MenuItemContent.svelte';
|
|
15
15
|
export { default as Modal } from './Modal.svelte';
|
|
16
|
-
export { default as ModalContent } from './ModalContent.svelte';
|
|
17
16
|
export { default as PageContent } from './PageContent.svelte';
|
|
18
17
|
export { default as Popover } from './Popover.svelte';
|
|
19
18
|
export { default as Tag } from './Tag.svelte';
|
package/dist/components/index.js
CHANGED
|
@@ -13,7 +13,6 @@ export { default as MenuDropdown } from './MenuDropdown.svelte';
|
|
|
13
13
|
export { default as MenuItem } from './MenuItem.svelte';
|
|
14
14
|
export { default as MenuItemContent } from './MenuItemContent.svelte';
|
|
15
15
|
export { default as Modal } from './Modal.svelte';
|
|
16
|
-
export { default as ModalContent } from './ModalContent.svelte';
|
|
17
16
|
export { default as PageContent } from './PageContent.svelte';
|
|
18
17
|
export { default as Popover } from './Popover.svelte';
|
|
19
18
|
export { default as Tag } from './Tag.svelte';
|
|
@@ -1,8 +1,15 @@
|
|
|
1
1
|
import type { ModalOptions } from './ModalTypes.js';
|
|
2
2
|
/**
|
|
3
3
|
* Opens a modal programmatically using the native `<dialog>` element.
|
|
4
|
+
*
|
|
5
|
+
* The dialog provides:
|
|
6
|
+
* - Backdrop (::backdrop pseudo-element)
|
|
7
|
+
* - Focus trapping (native behavior)
|
|
8
|
+
* - ESC key handling (cancel event)
|
|
9
|
+
*
|
|
4
10
|
* @param opts - Modal options or a simple string for text content
|
|
5
11
|
* @returns An object with the modal id and a close function
|
|
12
|
+
*
|
|
6
13
|
* @example
|
|
7
14
|
* // Simple text modal
|
|
8
15
|
* const { close } = openModal('Hello world!');
|
|
@@ -18,8 +18,15 @@ function unlockScroll() {
|
|
|
18
18
|
}
|
|
19
19
|
/**
|
|
20
20
|
* Opens a modal programmatically using the native `<dialog>` element.
|
|
21
|
+
*
|
|
22
|
+
* The dialog provides:
|
|
23
|
+
* - Backdrop (::backdrop pseudo-element)
|
|
24
|
+
* - Focus trapping (native behavior)
|
|
25
|
+
* - ESC key handling (cancel event)
|
|
26
|
+
*
|
|
21
27
|
* @param opts - Modal options or a simple string for text content
|
|
22
28
|
* @returns An object with the modal id and a close function
|
|
29
|
+
*
|
|
23
30
|
* @example
|
|
24
31
|
* // Simple text modal
|
|
25
32
|
* const { close } = openModal('Hello world!');
|
|
@@ -83,16 +90,9 @@ export function openModal(opts) {
|
|
|
83
90
|
props: opts.props,
|
|
84
91
|
buttonsConfig,
|
|
85
92
|
title: opts.title,
|
|
86
|
-
showScrim: false, // Dialog handles backdrop
|
|
87
|
-
closeOnScrim: opts.closeOnScrim ?? true,
|
|
88
|
-
trapFocus: opts.trapFocus ?? true,
|
|
89
|
-
dismissOnEsc: opts.dismissOnEsc ?? true,
|
|
90
93
|
unstyled: opts.unstyled ?? false,
|
|
91
94
|
shape: opts.shape ?? 'rounded',
|
|
92
95
|
contained: opts.contained ?? true,
|
|
93
|
-
width: opts.width,
|
|
94
|
-
maxWidth: opts.maxWidth,
|
|
95
|
-
maxHeight: opts.maxHeight,
|
|
96
96
|
},
|
|
97
97
|
});
|
|
98
98
|
// Handle dialog events
|
package/dist/css/1-props.css
CHANGED
|
@@ -496,6 +496,30 @@
|
|
|
496
496
|
@property --modal-actions-padding-inline { syntax: "<length>"; inherits: true; initial-value: 16px; }
|
|
497
497
|
@property --modal-actions-padding-block { syntax: "<length>"; inherits: true; initial-value: 16px; }
|
|
498
498
|
|
|
499
|
+
/**
|
|
500
|
+
* Shared Surface System
|
|
501
|
+
* These provide consistent defaults across all overlay components (Popover, Modal, Menu, Toast)
|
|
502
|
+
*/
|
|
503
|
+
|
|
504
|
+
@property --surface-background { syntax: "<color>"; inherits: true; initial-value: #ffffff; }
|
|
505
|
+
@property --surface-border { syntax: "*"; inherits: true; initial-value: 1px solid #d1d5db; }
|
|
506
|
+
@property --surface-border-radius { syntax: "<length>"; inherits: true; initial-value: 8px; }
|
|
507
|
+
@property --surface-shadow { syntax: "*"; inherits: true; initial-value: 0 0.5rem 1rem rgba(0, 0, 0, 0.1); }
|
|
508
|
+
|
|
509
|
+
/**
|
|
510
|
+
* Popover Component
|
|
511
|
+
*/
|
|
512
|
+
|
|
513
|
+
@property --popover-background { syntax: "<color>"; inherits: true; initial-value: #ffffff; }
|
|
514
|
+
@property --popover-border { syntax: "*"; inherits: true; initial-value: 1px solid #d1d5db; }
|
|
515
|
+
@property --popover-border-radius { syntax: "<length>"; inherits: true; initial-value: 8px; }
|
|
516
|
+
@property --popover-shadow { syntax: "*"; inherits: true; initial-value: 0 0.5rem 1rem rgba(0, 0, 0, 0.1); }
|
|
517
|
+
@property --popover-padding { syntax: "<length>"; inherits: true; initial-value: 0; }
|
|
518
|
+
@property --popover-offset { syntax: "<length>"; inherits: true; initial-value: 0.25rem; }
|
|
519
|
+
@property --popover-width { syntax: "*"; inherits: true; initial-value: max-content; }
|
|
520
|
+
@property --popover-max-width { syntax: "<length>"; inherits: true; initial-value: calc(100vw - 2rem); }
|
|
521
|
+
@property --popover-max-height { syntax: "<length>"; inherits: true; initial-value: calc(100vh - 2rem); }
|
|
522
|
+
|
|
499
523
|
/**
|
|
500
524
|
* Overlay System
|
|
501
525
|
*/
|
|
@@ -238,11 +238,29 @@
|
|
|
238
238
|
--table-row-background-hover: color-mix(in srgb, var(--theme-surface-interactive) 60%, transparent);
|
|
239
239
|
--table-cell-color: var(--text-color-p);
|
|
240
240
|
|
|
241
|
+
/**
|
|
242
|
+
* Shared Surface (for Popover, Modal, Menu, Toast)
|
|
243
|
+
*/
|
|
244
|
+
|
|
245
|
+
--surface-background: var(--background-body);
|
|
246
|
+
--surface-border: var(--border-size-thin) var(--border-style) var(--border-color);
|
|
247
|
+
--surface-border-radius: var(--border-radius-base);
|
|
248
|
+
--surface-shadow: 0 0.5rem 1rem var(--shadow-color);
|
|
249
|
+
|
|
250
|
+
/**
|
|
251
|
+
* Popover
|
|
252
|
+
*/
|
|
253
|
+
|
|
254
|
+
--popover-background: var(--surface-background);
|
|
255
|
+
--popover-border: var(--surface-border);
|
|
256
|
+
--popover-border-radius: var(--surface-border-radius);
|
|
257
|
+
--popover-shadow: var(--surface-shadow);
|
|
258
|
+
|
|
241
259
|
/**
|
|
242
260
|
* Modals
|
|
243
261
|
*/
|
|
244
262
|
|
|
245
|
-
--modal-background: var(--background
|
|
263
|
+
--modal-background: var(--surface-background);
|
|
246
264
|
--modal-border-color: var(--border-color);
|
|
247
265
|
--modal-shadow-color: var(--shadow-color);
|
|
248
266
|
--modal-actions-background: var(--theme-surface-subtlest);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "lutra",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.36",
|
|
4
4
|
"scripts": {
|
|
5
5
|
"dev": "vite dev",
|
|
6
6
|
"bump-and-publish:patch": "pnpm version:patch && pnpm build && npm publish",
|
|
@@ -45,7 +45,7 @@
|
|
|
45
45
|
"@sveltejs/vite-plugin-svelte": "^6.2.1",
|
|
46
46
|
"@types/node": "^25.0.1",
|
|
47
47
|
"publint": "^0.3.16",
|
|
48
|
-
"svelte": "^5.
|
|
48
|
+
"svelte": "^5.46.0",
|
|
49
49
|
"svelte-check": "^4.3.4",
|
|
50
50
|
"typescript": "^5.9.3",
|
|
51
51
|
"vite": "^7.2.7"
|