lutra 0.1.32 → 0.1.33
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/MenuDropdown.svelte +5 -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 +96 -83
- package/dist/components/Modal.svelte.d.ts +16 -5
- package/dist/components/ModalContent.svelte +48 -7
- package/dist/components/ModalContent.svelte.d.ts +5 -0
- package/dist/components/ModalTypes.d.ts +4 -1
- package/dist/components/OverlayContainer.svelte +1 -1
- package/dist/components/OverlayLayer.svelte +13 -10
- package/dist/components/modals.svelte.d.ts +20 -0
- package/dist/components/modals.svelte.js +27 -38
- package/dist/components/overlays.svelte.d.ts +2 -0
- package/dist/css/1-props.css +10 -1
- 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/package.json +10 -10
- package/dist/util/StringOrComponent.svelte +0 -20
- package/dist/util/StringOrComponent.svelte.d.ts +0 -8
package/README.md
CHANGED
|
@@ -122,6 +122,10 @@
|
|
|
122
122
|
}
|
|
123
123
|
}
|
|
124
124
|
|
|
125
|
+
function handleSelect(item: Item, index: number) {
|
|
126
|
+
_open = false;
|
|
127
|
+
}
|
|
128
|
+
|
|
125
129
|
</script>
|
|
126
130
|
|
|
127
131
|
|
|
@@ -150,7 +154,7 @@
|
|
|
150
154
|
>
|
|
151
155
|
<ul>
|
|
152
156
|
{#each items as item, index}
|
|
153
|
-
<MenuItem {keyboardHasFocus} onmouseover={mouseover} item={item} {index} />
|
|
157
|
+
<MenuItem {keyboardHasFocus} onmouseover={mouseover} onselect={handleSelect} item={item} {index} />
|
|
154
158
|
{/each}
|
|
155
159
|
</ul>
|
|
156
160
|
</div>
|
|
@@ -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>
|
|
@@ -1,16 +1,19 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import type { Component, Snippet } from "svelte";
|
|
3
|
+
import type { RenderFn } from "../types.js";
|
|
3
4
|
import Icon from "./Icon.svelte";
|
|
4
5
|
|
|
5
6
|
let {
|
|
6
7
|
text,
|
|
7
8
|
snippet,
|
|
9
|
+
render,
|
|
8
10
|
component: Comp,
|
|
9
11
|
props,
|
|
10
12
|
icon
|
|
11
13
|
}: {
|
|
12
14
|
text?: string;
|
|
13
15
|
snippet?: Snippet;
|
|
16
|
+
render?: RenderFn;
|
|
14
17
|
component?: Component;
|
|
15
18
|
props?: any;
|
|
16
19
|
icon?: string | Component;
|
|
@@ -25,6 +28,8 @@
|
|
|
25
28
|
{/if}
|
|
26
29
|
{#if snippet}
|
|
27
30
|
{@render snippet()}
|
|
31
|
+
{:else if render}
|
|
32
|
+
{@render render()}
|
|
28
33
|
{/if}
|
|
29
34
|
{#if Comp}
|
|
30
35
|
<Comp {...props} />
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { StatusColorOrString } from "../util/color.js";
|
|
2
2
|
import type { Component, Snippet } from "svelte";
|
|
3
|
+
import type { RenderFn } from "../types.js";
|
|
3
4
|
export type MenuItem = {
|
|
4
5
|
/** Type of menu item to render. */
|
|
5
6
|
type: 'divider';
|
|
@@ -8,8 +9,10 @@ export type MenuItem = {
|
|
|
8
9
|
type: 'header';
|
|
9
10
|
/** Text label of the item to display to the user. */
|
|
10
11
|
text?: string;
|
|
11
|
-
/** Snippet to display. */
|
|
12
|
+
/** Snippet to display (for direct props). */
|
|
12
13
|
snippet?: Snippet;
|
|
14
|
+
/** Render function to display (for object literals - avoids Svelte 5 snippet typing issues). */
|
|
15
|
+
render?: RenderFn;
|
|
13
16
|
/** Component to display. */
|
|
14
17
|
component?: Component;
|
|
15
18
|
/** Icon to display. Pass either a Svelte Component or an URL. Use the Icon component to recolor your icons according to the user theme. */
|
|
@@ -28,8 +31,10 @@ export type MenuItem = {
|
|
|
28
31
|
type: 'item';
|
|
29
32
|
/** Text label of the item to display to the user. */
|
|
30
33
|
text?: string;
|
|
31
|
-
/** Snippet to display. */
|
|
34
|
+
/** Snippet to display (for direct props). */
|
|
32
35
|
snippet?: Snippet;
|
|
36
|
+
/** Render function to display (for object literals - avoids Svelte 5 snippet typing issues). */
|
|
37
|
+
render?: RenderFn;
|
|
33
38
|
/** Component to display. */
|
|
34
39
|
component?: Component;
|
|
35
40
|
/** Keyboard shortcut to display next to the item. */
|
|
@@ -1,27 +1,41 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
-
|
|
3
|
-
|
|
2
|
+
import ModalContent from "./ModalContent.svelte";
|
|
3
|
+
import Overlay from "./Overlay.svelte";
|
|
4
4
|
import { getContext, type Snippet } from "svelte";
|
|
5
5
|
import { attr } from "../util/attr.js";
|
|
6
|
+
import type { RenderFn } from "../types.js";
|
|
6
7
|
|
|
7
8
|
/**
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
* <
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
9
|
+
* A flexible modal component supporting multiple usage patterns:
|
|
10
|
+
*
|
|
11
|
+
* **Pattern A: Trigger-based**
|
|
12
|
+
* ```svelte
|
|
13
|
+
* <Modal {trigger} {content} />
|
|
14
|
+
* ```
|
|
15
|
+
*
|
|
16
|
+
* **Pattern B: Controlled state**
|
|
17
|
+
* ```svelte
|
|
18
|
+
* <Modal bind:open={showModal}>
|
|
19
|
+
* {#snippet content(close)}
|
|
20
|
+
* <p>Modal content</p>
|
|
21
|
+
* {/snippet}
|
|
22
|
+
* </Modal>
|
|
23
|
+
* ```
|
|
24
|
+
*
|
|
25
|
+
* **Pattern C: Auto-show (no trigger)**
|
|
26
|
+
* ```svelte
|
|
27
|
+
* {#if shouldShow}
|
|
28
|
+
* <Modal open onclose={() => shouldShow = false}>
|
|
29
|
+
* {#snippet content(close)}...{/snippet}
|
|
30
|
+
* </Modal>
|
|
31
|
+
* {/if}
|
|
32
|
+
* ```
|
|
21
33
|
*/
|
|
22
34
|
let {
|
|
35
|
+
open = $bindable(false),
|
|
23
36
|
contained,
|
|
24
37
|
content,
|
|
38
|
+
render,
|
|
25
39
|
buttons,
|
|
26
40
|
trigger,
|
|
27
41
|
title,
|
|
@@ -31,15 +45,22 @@
|
|
|
31
45
|
closeOnScrim = true,
|
|
32
46
|
trapFocus = true,
|
|
33
47
|
dismissOnEsc = true,
|
|
48
|
+
width,
|
|
34
49
|
maxWidth,
|
|
35
50
|
maxHeight,
|
|
51
|
+
onclose,
|
|
52
|
+
onopen,
|
|
36
53
|
}: {
|
|
54
|
+
/** Whether the modal is open (bindable for controlled usage) */
|
|
55
|
+
open?: boolean;
|
|
37
56
|
/** Whether the modal should be contained with a border */
|
|
38
57
|
contained?: boolean;
|
|
39
|
-
/**
|
|
40
|
-
content
|
|
41
|
-
/**
|
|
42
|
-
|
|
58
|
+
/** Snippet for the modal content */
|
|
59
|
+
content?: Snippet<[close: () => void]>;
|
|
60
|
+
/** Render function for modal content (for object literals) */
|
|
61
|
+
render?: RenderFn<[close: () => void]>;
|
|
62
|
+
/** Optional snippet containing the trigger element */
|
|
63
|
+
trigger?: Snippet<[attrs: (node: Element) => void]>;
|
|
43
64
|
/** Buttons to be displayed in the modal */
|
|
44
65
|
buttons?: Snippet<[close: () => void]>;
|
|
45
66
|
/** Optional title for the modal (improves a11y) */
|
|
@@ -56,97 +77,89 @@
|
|
|
56
77
|
trapFocus?: boolean;
|
|
57
78
|
/** Whether pressing Escape closes the modal */
|
|
58
79
|
dismissOnEsc?: boolean;
|
|
80
|
+
/** Width of the modal */
|
|
81
|
+
width?: string;
|
|
59
82
|
/** Maximum width of the modal */
|
|
60
83
|
maxWidth?: string;
|
|
61
84
|
/** Maximum height of the modal */
|
|
62
85
|
maxHeight?: string;
|
|
86
|
+
/** Callback when the modal closes */
|
|
87
|
+
onclose?: () => void;
|
|
88
|
+
/** Callback when the modal opens */
|
|
89
|
+
onopen?: () => void;
|
|
63
90
|
} = $props();
|
|
64
91
|
|
|
65
|
-
if(contained === undefined) {
|
|
92
|
+
if (contained === undefined) {
|
|
93
|
+
contained = getContext('lutra.modal.contained') ?? getContext('lutra.contained') ?? false;
|
|
94
|
+
}
|
|
66
95
|
|
|
67
|
-
const id = `
|
|
68
|
-
let
|
|
96
|
+
const id = `modal-${crypto.randomUUID()}`;
|
|
97
|
+
let wasOpen = false;
|
|
69
98
|
|
|
70
|
-
function closeModal() {
|
|
71
|
-
|
|
72
|
-
|
|
99
|
+
function closeModal() {
|
|
100
|
+
open = false;
|
|
101
|
+
onclose?.();
|
|
73
102
|
}
|
|
74
|
-
|
|
75
|
-
function toggleModal() {
|
|
76
|
-
|
|
103
|
+
|
|
104
|
+
function toggleModal() {
|
|
105
|
+
open = !open;
|
|
77
106
|
}
|
|
78
107
|
|
|
79
108
|
$effect(() => {
|
|
80
|
-
if(
|
|
109
|
+
if (open && !wasOpen) {
|
|
110
|
+
onopen?.();
|
|
81
111
|
document.documentElement.style.overflow = "hidden";
|
|
82
|
-
} else {
|
|
112
|
+
} else if (!open && wasOpen) {
|
|
83
113
|
document.documentElement.style.overflow = "";
|
|
84
114
|
}
|
|
115
|
+
wasOpen = open;
|
|
85
116
|
});
|
|
86
117
|
|
|
87
118
|
let attrs = $derived.by(() => {
|
|
88
119
|
return attr({
|
|
89
120
|
id: `trigger-${id}`,
|
|
90
|
-
popovertarget: id,
|
|
91
121
|
onclick: toggleModal,
|
|
92
|
-
|
|
122
|
+
"aria-haspopup": "dialog",
|
|
123
|
+
"aria-expanded": open,
|
|
124
|
+
"aria-controls": id,
|
|
125
|
+
});
|
|
93
126
|
});
|
|
94
|
-
|
|
95
127
|
</script>
|
|
96
128
|
|
|
97
|
-
|
|
98
|
-
<div class="
|
|
99
|
-
|
|
129
|
+
{#if trigger}
|
|
130
|
+
<div class="Modal">
|
|
131
|
+
<div class="Trigger">
|
|
132
|
+
{@render trigger(attrs)}
|
|
133
|
+
</div>
|
|
100
134
|
</div>
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
</
|
|
135
|
+
{/if}
|
|
136
|
+
|
|
137
|
+
{#if open}
|
|
138
|
+
<Overlay position="center" {id} z={200} layer="modals">
|
|
139
|
+
<ModalContent
|
|
140
|
+
{shape}
|
|
141
|
+
{contained}
|
|
142
|
+
{unstyled}
|
|
143
|
+
{showScrim}
|
|
144
|
+
{closeOnScrim}
|
|
145
|
+
{trapFocus}
|
|
146
|
+
{dismissOnEsc}
|
|
147
|
+
{width}
|
|
148
|
+
{maxWidth}
|
|
149
|
+
{maxHeight}
|
|
150
|
+
{title}
|
|
151
|
+
{buttons}
|
|
152
|
+
snippet={content}
|
|
153
|
+
{render}
|
|
154
|
+
close={closeModal}
|
|
155
|
+
/>
|
|
156
|
+
</Overlay>
|
|
157
|
+
{/if}
|
|
123
158
|
|
|
124
159
|
<style>
|
|
125
|
-
.Modal,
|
|
160
|
+
.Modal,
|
|
161
|
+
.Trigger {
|
|
126
162
|
position: relative;
|
|
127
163
|
display: inline-block;
|
|
128
164
|
}
|
|
129
|
-
|
|
130
|
-
.ModalContainer {
|
|
131
|
-
border: 0;
|
|
132
|
-
width: 100svw;
|
|
133
|
-
height: 100svh;
|
|
134
|
-
overflow-y: auto;
|
|
135
|
-
display: flex;
|
|
136
|
-
align-items: center;
|
|
137
|
-
justify-content: center;
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
[popover] {
|
|
141
|
-
animation: fadeIn 0.2s;
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
@keyframes fadeIn {
|
|
145
|
-
from {
|
|
146
|
-
opacity: 0;
|
|
147
|
-
}
|
|
148
|
-
to {
|
|
149
|
-
opacity: 1;
|
|
150
|
-
}
|
|
151
|
-
}
|
|
152
|
-
</style>
|
|
165
|
+
</style>
|
|
@@ -1,11 +1,16 @@
|
|
|
1
1
|
import { type Snippet } from "svelte";
|
|
2
|
+
import type { RenderFn } from "../types.js";
|
|
2
3
|
type $$ComponentProps = {
|
|
4
|
+
/** Whether the modal is open (bindable for controlled usage) */
|
|
5
|
+
open?: boolean;
|
|
3
6
|
/** Whether the modal should be contained with a border */
|
|
4
7
|
contained?: boolean;
|
|
5
|
-
/**
|
|
6
|
-
content
|
|
7
|
-
/**
|
|
8
|
-
|
|
8
|
+
/** Snippet for the modal content */
|
|
9
|
+
content?: Snippet<[close: () => void]>;
|
|
10
|
+
/** Render function for modal content (for object literals) */
|
|
11
|
+
render?: RenderFn<[close: () => void]>;
|
|
12
|
+
/** Optional snippet containing the trigger element */
|
|
13
|
+
trigger?: Snippet<[attrs: (node: Element) => void]>;
|
|
9
14
|
/** Buttons to be displayed in the modal */
|
|
10
15
|
buttons?: Snippet<[close: () => void]>;
|
|
11
16
|
/** Optional title for the modal (improves a11y) */
|
|
@@ -22,11 +27,17 @@ type $$ComponentProps = {
|
|
|
22
27
|
trapFocus?: boolean;
|
|
23
28
|
/** Whether pressing Escape closes the modal */
|
|
24
29
|
dismissOnEsc?: boolean;
|
|
30
|
+
/** Width of the modal */
|
|
31
|
+
width?: string;
|
|
25
32
|
/** Maximum width of the modal */
|
|
26
33
|
maxWidth?: string;
|
|
27
34
|
/** Maximum height of the modal */
|
|
28
35
|
maxHeight?: string;
|
|
36
|
+
/** Callback when the modal closes */
|
|
37
|
+
onclose?: () => void;
|
|
38
|
+
/** Callback when the modal opens */
|
|
39
|
+
onopen?: () => void;
|
|
29
40
|
};
|
|
30
|
-
declare const Modal: import("svelte").Component<$$ComponentProps, {}, "">;
|
|
41
|
+
declare const Modal: import("svelte").Component<$$ComponentProps, {}, "open">;
|
|
31
42
|
type Modal = ReturnType<typeof Modal>;
|
|
32
43
|
export default Modal;
|
|
@@ -1,14 +1,18 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import type { Component, Snippet } from 'svelte';
|
|
3
3
|
import { onMount, onDestroy } from 'svelte';
|
|
4
|
+
import type { ModalButton } from './ModalTypes.js';
|
|
5
|
+
import type { RenderFn } from '../types.js';
|
|
4
6
|
|
|
5
7
|
let {
|
|
6
8
|
children,
|
|
7
9
|
text,
|
|
8
10
|
snippet,
|
|
11
|
+
render,
|
|
9
12
|
component: Comp,
|
|
10
13
|
props,
|
|
11
14
|
buttons,
|
|
15
|
+
buttonsConfig,
|
|
12
16
|
close,
|
|
13
17
|
title,
|
|
14
18
|
shape = 'rounded',
|
|
@@ -18,15 +22,18 @@
|
|
|
18
22
|
closeOnScrim = true,
|
|
19
23
|
trapFocus = true,
|
|
20
24
|
dismissOnEsc = true,
|
|
25
|
+
width,
|
|
21
26
|
maxWidth,
|
|
22
27
|
maxHeight,
|
|
23
28
|
}: {
|
|
24
29
|
children?: Snippet<[close: () => void]>;
|
|
25
30
|
text?: string;
|
|
26
31
|
snippet?: Snippet<[close: () => void]>;
|
|
32
|
+
render?: RenderFn<[close: () => void]>;
|
|
27
33
|
component?: Component;
|
|
28
34
|
props?: any;
|
|
29
35
|
buttons?: Snippet<[close: () => void]>;
|
|
36
|
+
buttonsConfig?: ModalButton[];
|
|
30
37
|
close: () => void;
|
|
31
38
|
title?: string;
|
|
32
39
|
shape?: 'rounded' | 'sharp';
|
|
@@ -36,6 +43,7 @@
|
|
|
36
43
|
closeOnScrim?: boolean;
|
|
37
44
|
trapFocus?: boolean;
|
|
38
45
|
dismissOnEsc?: boolean;
|
|
46
|
+
width?: string;
|
|
39
47
|
maxWidth?: string;
|
|
40
48
|
maxHeight?: string;
|
|
41
49
|
} = $props();
|
|
@@ -43,6 +51,20 @@
|
|
|
43
51
|
let dialogEl: HTMLDivElement | null = $state(null);
|
|
44
52
|
let previousActiveElement: HTMLElement | null = null;
|
|
45
53
|
const titleId = title ? `modal-title-${crypto.randomUUID()}` : undefined;
|
|
54
|
+
let loading: Record<number, boolean> = $state({});
|
|
55
|
+
|
|
56
|
+
async function handleButtonClick(btn: ModalButton, index: number) {
|
|
57
|
+
if (!btn.onclick) return;
|
|
58
|
+
const result = btn.onclick(close);
|
|
59
|
+
if (result instanceof Promise) {
|
|
60
|
+
loading[index] = true;
|
|
61
|
+
try {
|
|
62
|
+
await result;
|
|
63
|
+
} finally {
|
|
64
|
+
loading[index] = false;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
46
68
|
|
|
47
69
|
onMount(() => {
|
|
48
70
|
previousActiveElement = document.activeElement as HTMLElement;
|
|
@@ -53,11 +75,14 @@
|
|
|
53
75
|
focusableElements[0].focus();
|
|
54
76
|
}
|
|
55
77
|
}
|
|
78
|
+
dialogEl?.focus();
|
|
79
|
+
window.addEventListener('keydown', handleKeydown);
|
|
56
80
|
});
|
|
57
81
|
|
|
58
82
|
onDestroy(() => {
|
|
59
83
|
// Restore focus on unmount
|
|
60
84
|
previousActiveElement?.focus();
|
|
85
|
+
window.removeEventListener('keydown', handleKeydown);
|
|
61
86
|
});
|
|
62
87
|
|
|
63
88
|
function getFocusableElements(): HTMLElement[] {
|
|
@@ -120,7 +145,7 @@
|
|
|
120
145
|
tabindex="-1"
|
|
121
146
|
bind:this={dialogEl}
|
|
122
147
|
onkeydown={handleKeydown}
|
|
123
|
-
style="--modal-max-width: {maxWidth}; --modal-max-height: {maxHeight};"
|
|
148
|
+
style="--modal-width: {width}; --modal-max-width: {maxWidth}; --modal-max-height: {maxHeight};"
|
|
124
149
|
>
|
|
125
150
|
<div class="ModalContentArea">
|
|
126
151
|
{#if title}
|
|
@@ -131,6 +156,8 @@
|
|
|
131
156
|
{/if}
|
|
132
157
|
{#if snippet}
|
|
133
158
|
{@render snippet(close)}
|
|
159
|
+
{:else if render}
|
|
160
|
+
{@render render(close)}
|
|
134
161
|
{/if}
|
|
135
162
|
{#if Comp}
|
|
136
163
|
<Comp {...props} {close} />
|
|
@@ -139,7 +166,17 @@
|
|
|
139
166
|
{@render children(close)}
|
|
140
167
|
{/if}
|
|
141
168
|
</div>
|
|
142
|
-
{#if
|
|
169
|
+
{#if buttonsConfig}
|
|
170
|
+
<div class="ModalActions">
|
|
171
|
+
{#each buttonsConfig as btn, i}
|
|
172
|
+
<button
|
|
173
|
+
class="button {btn.variant || 'default'}"
|
|
174
|
+
disabled={btn.disabled || loading[i]}
|
|
175
|
+
onclick={() => handleButtonClick(btn, i)}
|
|
176
|
+
>{btn.text}</button>
|
|
177
|
+
{/each}
|
|
178
|
+
</div>
|
|
179
|
+
{:else if buttons}
|
|
143
180
|
<div class="ModalActions">
|
|
144
181
|
{@render buttons(close)}
|
|
145
182
|
</div>
|
|
@@ -150,23 +187,25 @@
|
|
|
150
187
|
.ModalScrim {
|
|
151
188
|
background: var(--scrim-background);
|
|
152
189
|
backdrop-filter: var(--scrim-backdrop-filter);
|
|
153
|
-
position:
|
|
190
|
+
position: absolute;
|
|
154
191
|
inset: 0;
|
|
155
|
-
z-index:
|
|
192
|
+
z-index: 0;
|
|
156
193
|
}
|
|
157
194
|
|
|
158
195
|
.ModalContent {
|
|
159
196
|
display: grid;
|
|
160
197
|
grid-template-rows: 1fr auto;
|
|
161
198
|
gap: var(--modal-gap);
|
|
199
|
+
width: var(--modal-width, fit-content);
|
|
162
200
|
max-width: min(var(--modal-max-width, 40rem), calc(100svw - 2rem));
|
|
163
201
|
max-height: min(var(--modal-max-height, 80svh), calc(100svh - 2rem));
|
|
164
202
|
background: var(--modal-background);
|
|
165
|
-
border: var(--modal-border);
|
|
203
|
+
border: var(--modal-border-size) var(--modal-border-style) var(--modal-border-color);
|
|
166
204
|
border-radius: var(--modal-border-radius);
|
|
167
205
|
box-shadow: 0 0.5rem 1rem var(--modal-shadow-color);
|
|
168
206
|
overflow: hidden;
|
|
169
207
|
position: relative;
|
|
208
|
+
z-index: 1;
|
|
170
209
|
}
|
|
171
210
|
|
|
172
211
|
.ModalContent.sharp {
|
|
@@ -190,7 +229,8 @@
|
|
|
190
229
|
overflow: auto;
|
|
191
230
|
scrollbar-gutter: stable;
|
|
192
231
|
scrollbar-width: thin;
|
|
193
|
-
padding: var(--modal-padding);
|
|
232
|
+
padding-block: var(--modal-padding-block);
|
|
233
|
+
padding-inline: var(--modal-padding-inline);
|
|
194
234
|
text-wrap: pretty;
|
|
195
235
|
}
|
|
196
236
|
|
|
@@ -215,7 +255,8 @@
|
|
|
215
255
|
.ModalActions {
|
|
216
256
|
background: var(--modal-actions-background);
|
|
217
257
|
border-top: var(--modal-border-size) var(--modal-border-style) var(--modal-actions-border-color);
|
|
218
|
-
padding: var(--modal-actions-padding);
|
|
258
|
+
padding-block: var(--modal-actions-padding-block);
|
|
259
|
+
padding-inline: var(--modal-actions-padding-inline);
|
|
219
260
|
display: flex;
|
|
220
261
|
gap: var(--space-sm);
|
|
221
262
|
justify-content: flex-end;
|
|
@@ -1,11 +1,15 @@
|
|
|
1
1
|
import type { Component, Snippet } from 'svelte';
|
|
2
|
+
import type { ModalButton } from './ModalTypes.js';
|
|
3
|
+
import type { RenderFn } from '../types.js';
|
|
2
4
|
type $$ComponentProps = {
|
|
3
5
|
children?: Snippet<[close: () => void]>;
|
|
4
6
|
text?: string;
|
|
5
7
|
snippet?: Snippet<[close: () => void]>;
|
|
8
|
+
render?: RenderFn<[close: () => void]>;
|
|
6
9
|
component?: Component;
|
|
7
10
|
props?: any;
|
|
8
11
|
buttons?: Snippet<[close: () => void]>;
|
|
12
|
+
buttonsConfig?: ModalButton[];
|
|
9
13
|
close: () => void;
|
|
10
14
|
title?: string;
|
|
11
15
|
shape?: 'rounded' | 'sharp';
|
|
@@ -15,6 +19,7 @@ type $$ComponentProps = {
|
|
|
15
19
|
closeOnScrim?: boolean;
|
|
16
20
|
trapFocus?: boolean;
|
|
17
21
|
dismissOnEsc?: boolean;
|
|
22
|
+
width?: string;
|
|
18
23
|
maxWidth?: string;
|
|
19
24
|
maxHeight?: string;
|
|
20
25
|
};
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { Component, Snippet } from 'svelte';
|
|
2
2
|
import type { OverlayPosition } from './overlays.svelte.js';
|
|
3
|
+
import type { RenderFn } from '../types.js';
|
|
3
4
|
export type ModalButton = {
|
|
4
5
|
text: string;
|
|
5
6
|
variant?: 'action' | 'success' | 'danger' | 'ghost' | 'outline' | 'default';
|
|
@@ -10,8 +11,10 @@ export type ModalButton = {
|
|
|
10
11
|
export type ModalOptions = {
|
|
11
12
|
/** Text content to display */
|
|
12
13
|
text?: string;
|
|
13
|
-
/** Snippet to render */
|
|
14
|
+
/** Snippet to render (for direct props) */
|
|
14
15
|
snippet?: Snippet<[close: () => void]>;
|
|
16
|
+
/** Render function (for object literals - avoids Svelte 5 snippet typing issues) */
|
|
17
|
+
render?: RenderFn<[close: () => void]>;
|
|
15
18
|
/** Component to render */
|
|
16
19
|
component?: Component;
|
|
17
20
|
/** Props to pass to component */
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import { type OverlayItem, type OverlayPosition } from "./overlays.svelte.js";
|
|
3
3
|
import { slidefade } from "../util/transitions.js";
|
|
4
4
|
import { BROWSER } from "esm-env";
|
|
5
|
+
import RenderContent from "../util/RenderContent.svelte";
|
|
5
6
|
import { untrack } from "svelte";
|
|
6
7
|
|
|
7
8
|
let {
|
|
@@ -113,11 +114,13 @@
|
|
|
113
114
|
transition:slidefade|global={{ duration: 150, origin: originCache[item.id] || origins[index], noMargin: !!!item.anchor }}
|
|
114
115
|
style="--index: {index}; --z: {item.z}; --left: {positions[index].left}px; --top: {positions[index].top}px;"
|
|
115
116
|
>
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
{
|
|
120
|
-
|
|
117
|
+
<RenderContent
|
|
118
|
+
text={item.text}
|
|
119
|
+
snippet={item.snippet}
|
|
120
|
+
render={item.render}
|
|
121
|
+
component={item.component}
|
|
122
|
+
props={item.props}
|
|
123
|
+
/>
|
|
121
124
|
</div>
|
|
122
125
|
{/each}
|
|
123
126
|
</div>
|
|
@@ -128,7 +131,7 @@
|
|
|
128
131
|
position: absolute;
|
|
129
132
|
display: flex;
|
|
130
133
|
flex-direction: column-reverse;
|
|
131
|
-
gap:
|
|
134
|
+
gap: var(--overlay-gap);
|
|
132
135
|
}
|
|
133
136
|
.Layer.center {
|
|
134
137
|
left: 50%;
|
|
@@ -136,19 +139,19 @@
|
|
|
136
139
|
transform: translateX(-50%);
|
|
137
140
|
}
|
|
138
141
|
.Layer.top {
|
|
139
|
-
top: calc(
|
|
142
|
+
top: calc(var(--overlay-offset) + env(safe-area-inset-top));
|
|
140
143
|
bottom: unset;
|
|
141
144
|
}
|
|
142
145
|
.Layer.bottom {
|
|
143
146
|
top: unset;
|
|
144
|
-
bottom: calc(
|
|
147
|
+
bottom: calc(var(--overlay-offset) + env(safe-area-inset-bottom));
|
|
145
148
|
}
|
|
146
149
|
.Layer.right {
|
|
147
150
|
left: unset;
|
|
148
|
-
right: calc(
|
|
151
|
+
right: calc(var(--overlay-offset) + env(safe-area-inset-right));
|
|
149
152
|
}
|
|
150
153
|
.Layer.left {
|
|
151
|
-
left: calc(
|
|
154
|
+
left: calc(var(--overlay-offset) + env(safe-area-inset-left));
|
|
152
155
|
right: unset;
|
|
153
156
|
}
|
|
154
157
|
.Layer.center:not(.top):not(.bottom):not(.anchor) {
|
|
@@ -1,4 +1,24 @@
|
|
|
1
1
|
import type { ModalOptions } from './ModalTypes.js';
|
|
2
|
+
/**
|
|
3
|
+
* Opens a modal programmatically.
|
|
4
|
+
* @param opts - Modal options or a simple string for text content
|
|
5
|
+
* @returns An object with the modal id and a close function
|
|
6
|
+
* @example
|
|
7
|
+
* // Simple text modal
|
|
8
|
+
* const { close } = openModal('Hello world!');
|
|
9
|
+
*
|
|
10
|
+
* // Modal with options
|
|
11
|
+
* const { close } = openModal({
|
|
12
|
+
* title: 'Confirm',
|
|
13
|
+
* text: 'Are you sure?',
|
|
14
|
+
* buttons: 'ok-cancel',
|
|
15
|
+
* });
|
|
16
|
+
*
|
|
17
|
+
* // Modal with render function (for object literals)
|
|
18
|
+
* const { close } = openModal({
|
|
19
|
+
* render: (close) => renderMyContent(close),
|
|
20
|
+
* });
|
|
21
|
+
*/
|
|
2
22
|
export declare function openModal(opts: ModalOptions | string): {
|
|
3
23
|
id: string;
|
|
4
24
|
close: () => void;
|
|
@@ -18,6 +18,26 @@ function unlockScroll() {
|
|
|
18
18
|
activeElement = null;
|
|
19
19
|
}
|
|
20
20
|
}
|
|
21
|
+
/**
|
|
22
|
+
* Opens a modal programmatically.
|
|
23
|
+
* @param opts - Modal options or a simple string for text content
|
|
24
|
+
* @returns An object with the modal id and a close function
|
|
25
|
+
* @example
|
|
26
|
+
* // Simple text modal
|
|
27
|
+
* const { close } = openModal('Hello world!');
|
|
28
|
+
*
|
|
29
|
+
* // Modal with options
|
|
30
|
+
* const { close } = openModal({
|
|
31
|
+
* title: 'Confirm',
|
|
32
|
+
* text: 'Are you sure?',
|
|
33
|
+
* buttons: 'ok-cancel',
|
|
34
|
+
* });
|
|
35
|
+
*
|
|
36
|
+
* // Modal with render function (for object literals)
|
|
37
|
+
* const { close } = openModal({
|
|
38
|
+
* render: (close) => renderMyContent(close),
|
|
39
|
+
* });
|
|
40
|
+
*/
|
|
21
41
|
export function openModal(opts) {
|
|
22
42
|
const id = crypto.randomUUID();
|
|
23
43
|
if (typeof opts === 'string') {
|
|
@@ -31,17 +51,16 @@ export function openModal(opts) {
|
|
|
31
51
|
lockScroll();
|
|
32
52
|
opts.onOpen?.();
|
|
33
53
|
// Handle defaultButtons shortcut
|
|
34
|
-
let
|
|
54
|
+
let buttonsConfig;
|
|
35
55
|
if (opts.buttons === 'ok-cancel') {
|
|
36
|
-
|
|
56
|
+
buttonsConfig = [cancelButton, okButton];
|
|
37
57
|
}
|
|
38
58
|
else if (opts.buttons === 'none' || opts.buttons === undefined) {
|
|
39
|
-
|
|
59
|
+
buttonsConfig = undefined;
|
|
40
60
|
}
|
|
41
61
|
else {
|
|
42
|
-
|
|
62
|
+
buttonsConfig = opts.buttons;
|
|
43
63
|
}
|
|
44
|
-
const buttonsSnippet = buttons ? createButtonsSnippet(buttons, close) : undefined;
|
|
45
64
|
addOverlay({
|
|
46
65
|
id,
|
|
47
66
|
z: 200,
|
|
@@ -50,9 +69,10 @@ export function openModal(opts) {
|
|
|
50
69
|
close,
|
|
51
70
|
text: opts.text,
|
|
52
71
|
snippet: opts.snippet,
|
|
72
|
+
render: opts.render,
|
|
53
73
|
component: opts.component,
|
|
54
74
|
props: opts.props,
|
|
55
|
-
|
|
75
|
+
buttonsConfig,
|
|
56
76
|
title: opts.title,
|
|
57
77
|
showScrim: opts.showScrim ?? true,
|
|
58
78
|
closeOnScrim: opts.closeOnScrim ?? true,
|
|
@@ -61,6 +81,7 @@ export function openModal(opts) {
|
|
|
61
81
|
unstyled: opts.unstyled ?? false,
|
|
62
82
|
shape: opts.shape ?? 'rounded',
|
|
63
83
|
contained: opts.contained ?? true,
|
|
84
|
+
width: opts.width,
|
|
64
85
|
maxWidth: opts.maxWidth,
|
|
65
86
|
maxHeight: opts.maxHeight,
|
|
66
87
|
},
|
|
@@ -69,35 +90,3 @@ export function openModal(opts) {
|
|
|
69
90
|
});
|
|
70
91
|
return { id, close };
|
|
71
92
|
}
|
|
72
|
-
function createButtonsSnippet(buttons, close) {
|
|
73
|
-
// Create a snippet that renders the buttons
|
|
74
|
-
return (closeParam) => {
|
|
75
|
-
// We need to return actual DOM elements, not strings
|
|
76
|
-
// This will be rendered by Svelte's snippet system
|
|
77
|
-
const fragment = document.createDocumentFragment();
|
|
78
|
-
buttons.forEach((btn) => {
|
|
79
|
-
const button = document.createElement('button');
|
|
80
|
-
button.className = `button ${btn.variant || 'default'}`;
|
|
81
|
-
button.textContent = btn.text;
|
|
82
|
-
button.disabled = btn.disabled || btn.loading || false;
|
|
83
|
-
if (btn.onclick) {
|
|
84
|
-
button.addEventListener('click', async () => {
|
|
85
|
-
const result = btn.onclick?.(close);
|
|
86
|
-
if (result instanceof Promise) {
|
|
87
|
-
button.disabled = true;
|
|
88
|
-
button.classList.add('loading');
|
|
89
|
-
try {
|
|
90
|
-
await result;
|
|
91
|
-
}
|
|
92
|
-
finally {
|
|
93
|
-
button.disabled = false;
|
|
94
|
-
button.classList.remove('loading');
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
});
|
|
98
|
-
}
|
|
99
|
-
fragment.appendChild(button);
|
|
100
|
-
});
|
|
101
|
-
return fragment;
|
|
102
|
-
};
|
|
103
|
-
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { Component, Snippet } from "svelte";
|
|
2
|
+
import type { RenderFn } from "../types.js";
|
|
2
3
|
export type OverlayPosition = "top left" | "top center" | "top right" | "bottom left" | "bottom center" | "bottom right" | "center" | "anchor";
|
|
3
4
|
export type TransitionOpts = {
|
|
4
5
|
y?: number;
|
|
@@ -12,6 +13,7 @@ export type OverlayItem = {
|
|
|
12
13
|
z?: number;
|
|
13
14
|
text?: string;
|
|
14
15
|
snippet?: Snippet;
|
|
16
|
+
render?: RenderFn;
|
|
15
17
|
component?: Component;
|
|
16
18
|
props?: any;
|
|
17
19
|
layer?: string;
|
package/dist/css/1-props.css
CHANGED
|
@@ -475,6 +475,7 @@
|
|
|
475
475
|
|
|
476
476
|
@property --modal-padding-inline { syntax: "<length>"; inherits: true; initial-value: 24px; }
|
|
477
477
|
@property --modal-padding-block { syntax: "<length>"; inherits: true; initial-value: 24px; }
|
|
478
|
+
@property --modal-width { syntax: "<length>"; inherits: true; initial-value: fit-content; }
|
|
478
479
|
@property --modal-max-width { syntax: "<length>"; inherits: true; initial-value: 40rem; }
|
|
479
480
|
@property --modal-max-height { syntax: "<length>"; inherits: true; initial-value: 80svh; }
|
|
480
481
|
@property --modal-gap { syntax: "<length>"; inherits: true; initial-value: 0px; }
|
|
@@ -484,9 +485,17 @@
|
|
|
484
485
|
@property --modal-actions-padding-inline { syntax: "<length>"; inherits: true; initial-value: 16px; }
|
|
485
486
|
@property --modal-actions-padding-block { syntax: "<length>"; inherits: true; initial-value: 16px; }
|
|
486
487
|
|
|
488
|
+
/**
|
|
489
|
+
* Overlay System
|
|
490
|
+
*/
|
|
491
|
+
|
|
492
|
+
@property --overlay-z-index { syntax: "<integer>"; inherits: true; initial-value: 1000; }
|
|
493
|
+
@property --overlay-gap { syntax: "<length>"; inherits: true; initial-value: 0.75rem; }
|
|
494
|
+
@property --overlay-offset { syntax: "<length>"; inherits: true; initial-value: 1rem; }
|
|
495
|
+
|
|
487
496
|
/**
|
|
488
497
|
* Scrim/Backdrop (shared across overlays)
|
|
489
498
|
*/
|
|
490
499
|
|
|
491
500
|
@property --scrim-background { syntax: "<color>"; inherits: true; initial-value: rgba(0, 0, 0, 0.5); }
|
|
492
|
-
@property --scrim-backdrop-filter { syntax: "
|
|
501
|
+
@property --scrim-backdrop-filter { syntax: "*"; inherits: true; initial-value: blur(2px); }
|
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
package/dist/types.d.ts
CHANGED
|
@@ -1,3 +1,11 @@
|
|
|
1
|
+
import { type Snippet } from "svelte";
|
|
2
|
+
/**
|
|
3
|
+
* A render function that can be used in place of a Snippet when passing content
|
|
4
|
+
* through object literals. This works around Svelte 5's snippet typing limitations
|
|
5
|
+
* where inline snippet declarations don't type-check when passed through objects.
|
|
6
|
+
* @template T - Tuple of argument types passed to the render function
|
|
7
|
+
*/
|
|
8
|
+
export type RenderFn<T extends any[] = []> = (...args: T) => ReturnType<Snippet<T>>;
|
|
1
9
|
export interface LutraConfig {
|
|
2
10
|
/**
|
|
3
11
|
* The default theme to use.
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
<script lang="ts" generics="T = undefined">
|
|
2
|
+
import type { Component, Snippet } from "svelte";
|
|
3
|
+
import type { RenderFn } from "../types.js";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* A unified content rendering component that handles multiple content types.
|
|
7
|
+
* Supports text strings, Svelte snippets, render functions, and components.
|
|
8
|
+
*/
|
|
9
|
+
let {
|
|
10
|
+
text,
|
|
11
|
+
snippet,
|
|
12
|
+
render,
|
|
13
|
+
component: Comp,
|
|
14
|
+
props,
|
|
15
|
+
arg,
|
|
16
|
+
}: {
|
|
17
|
+
/** Plain text content to display */
|
|
18
|
+
text?: string;
|
|
19
|
+
/** Svelte snippet to render */
|
|
20
|
+
snippet?: T extends undefined ? Snippet<[]> : Snippet<[T]>;
|
|
21
|
+
/** Render function (for object literals - avoids Svelte 5 snippet typing issues) */
|
|
22
|
+
render?: T extends undefined ? RenderFn<[]> : RenderFn<[T]>;
|
|
23
|
+
/** Svelte component to instantiate */
|
|
24
|
+
component?: Component;
|
|
25
|
+
/** Props to pass to the component */
|
|
26
|
+
props?: Record<string, any>;
|
|
27
|
+
/** Argument to pass to snippet or render function */
|
|
28
|
+
arg?: T;
|
|
29
|
+
} = $props();
|
|
30
|
+
</script>
|
|
31
|
+
|
|
32
|
+
{#if text}
|
|
33
|
+
{text}
|
|
34
|
+
{:else if snippet}
|
|
35
|
+
{#if arg !== undefined}
|
|
36
|
+
{@render snippet(arg)}
|
|
37
|
+
{:else}
|
|
38
|
+
{@render (snippet as Snippet<[]>)()}
|
|
39
|
+
{/if}
|
|
40
|
+
{:else if render}
|
|
41
|
+
{#if arg !== undefined}
|
|
42
|
+
{@render render(arg)}
|
|
43
|
+
{:else}
|
|
44
|
+
{@render (render as RenderFn<[]>)()}
|
|
45
|
+
{/if}
|
|
46
|
+
{:else if Comp}
|
|
47
|
+
<Comp {...props} />
|
|
48
|
+
{/if}
|
|
49
|
+
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { Component, Snippet } from "svelte";
|
|
2
|
+
import type { RenderFn } from "../types.js";
|
|
3
|
+
declare class __sveltets_Render<T = undefined> {
|
|
4
|
+
props(): {
|
|
5
|
+
/** Plain text content to display */
|
|
6
|
+
text?: string;
|
|
7
|
+
/** Svelte snippet to render */
|
|
8
|
+
snippet?: (T extends undefined ? Snippet<[]> : Snippet<[T]>) | undefined;
|
|
9
|
+
/** Render function (for object literals - avoids Svelte 5 snippet typing issues) */
|
|
10
|
+
render?: (T extends undefined ? RenderFn<[]> : RenderFn<[T]>) | undefined;
|
|
11
|
+
/** Svelte component to instantiate */
|
|
12
|
+
component?: Component;
|
|
13
|
+
/** Props to pass to the component */
|
|
14
|
+
props?: Record<string, any>;
|
|
15
|
+
/** Argument to pass to snippet or render function */
|
|
16
|
+
arg?: T | undefined;
|
|
17
|
+
};
|
|
18
|
+
events(): {};
|
|
19
|
+
slots(): {};
|
|
20
|
+
bindings(): "";
|
|
21
|
+
exports(): {};
|
|
22
|
+
}
|
|
23
|
+
interface $$IsomorphicComponent {
|
|
24
|
+
new <T = undefined>(options: import('svelte').ComponentConstructorOptions<ReturnType<__sveltets_Render<T>['props']>>): import('svelte').SvelteComponent<ReturnType<__sveltets_Render<T>['props']>, ReturnType<__sveltets_Render<T>['events']>, ReturnType<__sveltets_Render<T>['slots']>> & {
|
|
25
|
+
$$bindings?: ReturnType<__sveltets_Render<T>['bindings']>;
|
|
26
|
+
} & ReturnType<__sveltets_Render<T>['exports']>;
|
|
27
|
+
<T = undefined>(internal: unknown, props: ReturnType<__sveltets_Render<T>['props']> & {}): ReturnType<__sveltets_Render<T>['exports']>;
|
|
28
|
+
z_$$bindings?: ReturnType<__sveltets_Render<any>['bindings']>;
|
|
29
|
+
}
|
|
30
|
+
declare const RenderContent: $$IsomorphicComponent;
|
|
31
|
+
type RenderContent<T = undefined> = InstanceType<typeof RenderContent<T>>;
|
|
32
|
+
export default RenderContent;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "lutra",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.33",
|
|
4
4
|
"scripts": {
|
|
5
5
|
"dev": "vite dev",
|
|
6
6
|
"bump-and-publish:patch": "pnpm version:patch && pnpm build && npm publish",
|
|
@@ -40,23 +40,23 @@
|
|
|
40
40
|
},
|
|
41
41
|
"devDependencies": {
|
|
42
42
|
"@sveltejs/adapter-auto": "^7.0.0",
|
|
43
|
-
"@sveltejs/kit": "^2.
|
|
44
|
-
"@sveltejs/package": "^2.5.
|
|
43
|
+
"@sveltejs/kit": "^2.49.2",
|
|
44
|
+
"@sveltejs/package": "^2.5.7",
|
|
45
45
|
"@sveltejs/vite-plugin-svelte": "^6.2.1",
|
|
46
|
-
"@types/node": "^
|
|
47
|
-
"publint": "^0.3.
|
|
48
|
-
"svelte": "^5.
|
|
49
|
-
"svelte-check": "^4.3.
|
|
46
|
+
"@types/node": "^25.0.1",
|
|
47
|
+
"publint": "^0.3.16",
|
|
48
|
+
"svelte": "^5.45.10",
|
|
49
|
+
"svelte-check": "^4.3.4",
|
|
50
50
|
"typescript": "^5.9.3",
|
|
51
|
-
"vite": "^7.
|
|
51
|
+
"vite": "^7.2.7"
|
|
52
52
|
},
|
|
53
53
|
"dependencies": {
|
|
54
54
|
"@auth70/bodyguard": "^1.7.1",
|
|
55
55
|
"blurhash": "^2.0.5",
|
|
56
56
|
"browser-image-compression": "^2.0.2",
|
|
57
57
|
"esm-env": "^1.2.2",
|
|
58
|
-
"marked": "
|
|
59
|
-
"zod": "^4.1.
|
|
58
|
+
"marked": "17.0.1",
|
|
59
|
+
"zod": "^4.1.13",
|
|
60
60
|
"zodex": "^4.0.1"
|
|
61
61
|
}
|
|
62
62
|
}
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
<script lang="ts">
|
|
2
|
-
import type { Component } from "svelte";
|
|
3
|
-
let {
|
|
4
|
-
content,
|
|
5
|
-
props,
|
|
6
|
-
}: {
|
|
7
|
-
content: string | Component | undefined;
|
|
8
|
-
props?: Record<string, any>;
|
|
9
|
-
} = $props();
|
|
10
|
-
|
|
11
|
-
let Content = content as Component;
|
|
12
|
-
</script>
|
|
13
|
-
|
|
14
|
-
{#if content}
|
|
15
|
-
{#if typeof content === 'string'}
|
|
16
|
-
{content}
|
|
17
|
-
{:else if Content}
|
|
18
|
-
<Content {...props} />
|
|
19
|
-
{/if}
|
|
20
|
-
{/if}
|
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
import type { Component } from "svelte";
|
|
2
|
-
type $$ComponentProps = {
|
|
3
|
-
content: string | Component | undefined;
|
|
4
|
-
props?: Record<string, any>;
|
|
5
|
-
};
|
|
6
|
-
declare const StringOrComponent: Component<$$ComponentProps, {}, "">;
|
|
7
|
-
type StringOrComponent = ReturnType<typeof StringOrComponent>;
|
|
8
|
-
export default StringOrComponent;
|