@spaethtech/svelte-ui 0.3.0 → 0.3.1-dev.10.ff03a8d
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/.claude/skills/svelte-ui/SKILL.md +118 -0
- package/.claude/skills/themes/SKILL.md +303 -0
- package/CLAUDE.md +248 -0
- package/README.md +15 -1
- package/dist/components/Card.svelte +2 -1
- package/dist/components/CardFooter.svelte +3 -1
- package/dist/components/CardHeader.svelte +3 -1
- package/dist/components/ConfirmDialog.svelte +103 -0
- package/dist/components/ConfirmDialog.svelte.d.ts +33 -0
- package/dist/components/Dialog.svelte +105 -86
- package/dist/components/Dialog.svelte.d.ts +22 -16
- package/dist/components/Query.svelte +3 -3
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/docs/components.md +255 -0
- package/docs/examples.md +323 -0
- package/docs/paradigm.md +240 -0
- package/docs/themes.md +170 -0
- package/docs/usage.md +450 -0
- package/package.json +6 -2
package/README.md
CHANGED
|
@@ -159,9 +159,23 @@ CI verifies the tag matches `package.json`, type-checks, then runs `npm publish`
|
|
|
159
159
|
script builds `dist/` fresh — never commit `dist/`). Versions are **immutable**: a number can't be
|
|
160
160
|
republished, so don't tag until the CHANGELOG/version are right.
|
|
161
161
|
|
|
162
|
+
### Dev / canary builds
|
|
163
|
+
|
|
164
|
+
Every push to `main` also publishes a prerelease under the **`dev`** dist-tag (same workflow, no git
|
|
165
|
+
tag needed) — version `<next patch>-dev.<run>.<sha>`. It never touches `latest`, and caret ranges
|
|
166
|
+
(`^0.3.0`) skip prereleases, so it's invisible unless a consumer opts in:
|
|
167
|
+
|
|
168
|
+
```bash
|
|
169
|
+
npm install @spaethtech/svelte-ui@dev # newest main build; re-run / npm update to refresh
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
Use it to test unreleased work in a consumer before cutting a real release. (Publishing on every push
|
|
173
|
+
does accumulate prerelease versions on the registry — that's expected.)
|
|
174
|
+
|
|
162
175
|
**One-time setup (already done once, listed for reference):** on npmjs.com → the package → Settings →
|
|
163
176
|
add a **Trusted Publisher** for GitHub Actions (org `spaethtech`, repo `svelte-ui`, workflow
|
|
164
|
-
`publish.yml
|
|
177
|
+
`publish.yml`, "Allow npm publish"). Both the release and dev paths live in that one workflow file, so
|
|
178
|
+
the single trusted-publisher entry covers both.
|
|
165
179
|
|
|
166
180
|
## Known issues
|
|
167
181
|
|
|
@@ -7,7 +7,8 @@
|
|
|
7
7
|
* Border + radius come from the shared tint tokens (the same scheme Input/DataTable use, so surfaces
|
|
8
8
|
* match): the OUTER frame is the variant token at `--ui-tint-border` (rest) / `--ui-tint-border-hover`
|
|
9
9
|
* (hover) — one calc for every variant, no `secondary` special case — while inset Header/Footer
|
|
10
|
-
* separators stay soft (`--ui-border-color`)
|
|
10
|
+
* separators stay soft (`--ui-border-color`), matching the library's other internal dividers.
|
|
11
|
+
* `variant` sets the border colour; `size` the padding.
|
|
11
12
|
*
|
|
12
13
|
* Background comes from `--ui-color-background` so the card reads as a raised panel over the page
|
|
13
14
|
* body (matches Input, Dialog, tooltip surface tokens). Themes remap this token to their own
|
|
@@ -30,7 +30,9 @@
|
|
|
30
30
|
} & Omit<HTMLAttributes<HTMLDivElement>, "class"> = $props();
|
|
31
31
|
|
|
32
32
|
const space: Record<Size, string> = { sm: "pt-2 mt-2", md: "pt-3 mt-3", lg: "pt-4 mt-4" };
|
|
33
|
-
//
|
|
33
|
+
// The separator is a SOFT internal divider, consistent with the other section splits/footers in the
|
|
34
|
+
// library (DatePicker/TimePicker/TimeRangeInput) — deliberately lighter than the Card frame + fields.
|
|
35
|
+
// Default (secondary) → the shared `--ui-border-color`; any other variant → that accent's tint.
|
|
34
36
|
const sepColor = $derived(
|
|
35
37
|
variant === "secondary"
|
|
36
38
|
? "var(--ui-border-color)"
|
|
@@ -30,7 +30,9 @@
|
|
|
30
30
|
} & Omit<HTMLAttributes<HTMLDivElement>, "class"> = $props();
|
|
31
31
|
|
|
32
32
|
const space: Record<Size, string> = { sm: "pb-2 mb-2", md: "pb-3 mb-3", lg: "pb-4 mb-4" };
|
|
33
|
-
//
|
|
33
|
+
// The separator is a SOFT internal divider, consistent with the other section splits/footers in the
|
|
34
|
+
// library (DatePicker/TimePicker/TimeRangeInput) — deliberately lighter than the Card frame + fields.
|
|
35
|
+
// Default (secondary) → the shared `--ui-border-color`; any other variant → that accent's tint.
|
|
34
36
|
const sepColor = $derived(
|
|
35
37
|
variant === "secondary"
|
|
36
38
|
? "var(--ui-border-color)"
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import Dialog from "./Dialog.svelte";
|
|
3
|
+
import Card from "./Card.svelte";
|
|
4
|
+
import CardHeader from "./CardHeader.svelte";
|
|
5
|
+
import CardFooter from "./CardFooter.svelte";
|
|
6
|
+
import Button from "./Button.svelte";
|
|
7
|
+
import type { Snippet } from "svelte";
|
|
8
|
+
import type { Size } from "../types/sizes.js";
|
|
9
|
+
import type { Responsive } from "../types/responsive.js";
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* ConfirmDialog — the common title + message + Confirm/Cancel modal, built on the `Dialog` shell.
|
|
13
|
+
* It composes a `Card` (`CardHeader` title, body, `CardFooter` actions) inside `Dialog`, so it stays
|
|
14
|
+
* consistent with every other surface. Pass `children` to replace the message body with custom
|
|
15
|
+
* content (the header + footer stay). For a fully custom modal, use `Dialog` directly.
|
|
16
|
+
*/
|
|
17
|
+
interface Props {
|
|
18
|
+
isOpen: boolean;
|
|
19
|
+
title?: string;
|
|
20
|
+
message?: string;
|
|
21
|
+
confirmText?: string;
|
|
22
|
+
cancelText?: string;
|
|
23
|
+
onConfirm?: () => void;
|
|
24
|
+
onCancel?: () => void;
|
|
25
|
+
variant?: "danger" | "normal";
|
|
26
|
+
/** Density tier — forwarded to the Card / CardHeader / CardFooter. */
|
|
27
|
+
size?: Responsive<Size>;
|
|
28
|
+
showConfirm?: boolean;
|
|
29
|
+
showCancel?: boolean;
|
|
30
|
+
/** Allow dismissing by backdrop / Escape (default true). */
|
|
31
|
+
dismissible?: boolean;
|
|
32
|
+
/** Forwarded to the Dialog shell — any CSS value (default: content-sized, capped at 32rem). */
|
|
33
|
+
width?: string;
|
|
34
|
+
height?: string;
|
|
35
|
+
/** Custom body between header and footer (replaces `message`). */
|
|
36
|
+
children?: Snippet;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
let {
|
|
40
|
+
isOpen = $bindable(),
|
|
41
|
+
title = "Dialog",
|
|
42
|
+
message,
|
|
43
|
+
confirmText = "Confirm",
|
|
44
|
+
cancelText = "Cancel",
|
|
45
|
+
onConfirm,
|
|
46
|
+
onCancel,
|
|
47
|
+
variant = "normal",
|
|
48
|
+
size = "md",
|
|
49
|
+
showConfirm = true,
|
|
50
|
+
showCancel = true,
|
|
51
|
+
dismissible = true,
|
|
52
|
+
width,
|
|
53
|
+
height,
|
|
54
|
+
children,
|
|
55
|
+
}: Props = $props();
|
|
56
|
+
|
|
57
|
+
function handleConfirm() {
|
|
58
|
+
onConfirm?.();
|
|
59
|
+
isOpen = false;
|
|
60
|
+
}
|
|
61
|
+
function handleCancel() {
|
|
62
|
+
onCancel?.();
|
|
63
|
+
isOpen = false;
|
|
64
|
+
}
|
|
65
|
+
</script>
|
|
66
|
+
|
|
67
|
+
<Dialog
|
|
68
|
+
bind:isOpen
|
|
69
|
+
{dismissible}
|
|
70
|
+
{width}
|
|
71
|
+
{height}
|
|
72
|
+
onClose={onCancel}
|
|
73
|
+
aria-label={title}
|
|
74
|
+
>
|
|
75
|
+
<Card {size} class="w-full shadow-xl">
|
|
76
|
+
<CardHeader {size}>
|
|
77
|
+
<h3 class="text-lg font-medium [color:var(--ui-color-text)]">{title}</h3>
|
|
78
|
+
</CardHeader>
|
|
79
|
+
|
|
80
|
+
{#if children}
|
|
81
|
+
{@render children()}
|
|
82
|
+
{:else if message}
|
|
83
|
+
<p class="text-sm [color:color-mix(in_srgb,var(--ui-color-text)_70%,transparent)]">
|
|
84
|
+
{message}
|
|
85
|
+
</p>
|
|
86
|
+
{/if}
|
|
87
|
+
|
|
88
|
+
{#if showConfirm || showCancel}
|
|
89
|
+
<CardFooter {size}>
|
|
90
|
+
{#if showCancel}
|
|
91
|
+
<Button text={cancelText} onclick={handleCancel} variant="secondary" />
|
|
92
|
+
{/if}
|
|
93
|
+
{#if showConfirm}
|
|
94
|
+
<Button
|
|
95
|
+
text={confirmText}
|
|
96
|
+
onclick={handleConfirm}
|
|
97
|
+
variant={variant === "danger" ? "danger" : "primary"}
|
|
98
|
+
/>
|
|
99
|
+
{/if}
|
|
100
|
+
</CardFooter>
|
|
101
|
+
{/if}
|
|
102
|
+
</Card>
|
|
103
|
+
</Dialog>
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { Snippet } from "svelte";
|
|
2
|
+
import type { Size } from "../types/sizes.js";
|
|
3
|
+
import type { Responsive } from "../types/responsive.js";
|
|
4
|
+
/**
|
|
5
|
+
* ConfirmDialog — the common title + message + Confirm/Cancel modal, built on the `Dialog` shell.
|
|
6
|
+
* It composes a `Card` (`CardHeader` title, body, `CardFooter` actions) inside `Dialog`, so it stays
|
|
7
|
+
* consistent with every other surface. Pass `children` to replace the message body with custom
|
|
8
|
+
* content (the header + footer stay). For a fully custom modal, use `Dialog` directly.
|
|
9
|
+
*/
|
|
10
|
+
interface Props {
|
|
11
|
+
isOpen: boolean;
|
|
12
|
+
title?: string;
|
|
13
|
+
message?: string;
|
|
14
|
+
confirmText?: string;
|
|
15
|
+
cancelText?: string;
|
|
16
|
+
onConfirm?: () => void;
|
|
17
|
+
onCancel?: () => void;
|
|
18
|
+
variant?: "danger" | "normal";
|
|
19
|
+
/** Density tier — forwarded to the Card / CardHeader / CardFooter. */
|
|
20
|
+
size?: Responsive<Size>;
|
|
21
|
+
showConfirm?: boolean;
|
|
22
|
+
showCancel?: boolean;
|
|
23
|
+
/** Allow dismissing by backdrop / Escape (default true). */
|
|
24
|
+
dismissible?: boolean;
|
|
25
|
+
/** Forwarded to the Dialog shell — any CSS value (default: content-sized, capped at 32rem). */
|
|
26
|
+
width?: string;
|
|
27
|
+
height?: string;
|
|
28
|
+
/** Custom body between header and footer (replaces `message`). */
|
|
29
|
+
children?: Snippet;
|
|
30
|
+
}
|
|
31
|
+
declare const ConfirmDialog: import("svelte").Component<Props, {}, "isOpen">;
|
|
32
|
+
type ConfirmDialog = ReturnType<typeof ConfirmDialog>;
|
|
33
|
+
export default ConfirmDialog;
|
|
@@ -1,57 +1,58 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
-
import Button from "./Button.svelte";
|
|
3
|
-
import Card from "./Card.svelte";
|
|
4
|
-
import CardHeader from "./CardHeader.svelte";
|
|
5
|
-
import CardFooter from "./CardFooter.svelte";
|
|
6
2
|
import type { Snippet } from "svelte";
|
|
7
|
-
import type {
|
|
8
|
-
import type { Responsive } from "../types/responsive.js";
|
|
3
|
+
import type { HTMLAttributes } from "svelte/elements";
|
|
9
4
|
|
|
10
|
-
|
|
5
|
+
/**
|
|
6
|
+
* Dialog — a PURE modal shell (layout container). It owns only the modal mechanics: portals itself
|
|
7
|
+
* to <body>, locks background scroll, renders a dismissable backdrop, traps focus, and exposes the
|
|
8
|
+
* ARIA dialog role. It imposes NO content structure — put a `Card` (or anything) inside via
|
|
9
|
+
* `children`. For the common title + message + Confirm/Cancel pattern, use `ConfirmDialog`, which is
|
|
10
|
+
* built on this.
|
|
11
|
+
*
|
|
12
|
+
* `width` / `height` accept any CSS length: `"80%"`, `"80vw"`, `"60vh"`, `"400px"`, … (both default
|
|
13
|
+
* to content size, capped to the viewport minus a 1rem inset so the panel never runs off-screen).
|
|
14
|
+
*/
|
|
15
|
+
interface Props extends Omit<HTMLAttributes<HTMLDivElement>, "class"> {
|
|
16
|
+
/** Whether the dialog is shown (bindable). */
|
|
11
17
|
isOpen: boolean;
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
onCancel?: () => void;
|
|
18
|
-
variant?: "danger" | "normal";
|
|
19
|
-
/** Density tier — forwarded to the underlying Card / CardHeader / CardFooter. */
|
|
20
|
-
size?: Responsive<Size>;
|
|
21
|
-
showConfirm?: boolean;
|
|
22
|
-
showCancel?: boolean;
|
|
23
|
-
/** Allow dismissing by clicking the backdrop OR pressing Escape (default true). Set `false` for a
|
|
24
|
-
* must-decide dialog (e.g. a destructive confirm) so only the buttons close it. */
|
|
18
|
+
/** Panel width — any CSS value (`80%`, `80vw`, `400px`). Default: auto, capped at `min(32rem, …)`. */
|
|
19
|
+
width?: string;
|
|
20
|
+
/** Panel height — any CSS value (`60%`, `60vh`, `300px`). Default: auto (content height). */
|
|
21
|
+
height?: string;
|
|
22
|
+
/** Allow dismissing via backdrop click / Escape (default true). Set false for a must-decide modal. */
|
|
25
23
|
dismissible?: boolean;
|
|
24
|
+
/** Called when dismissed via backdrop / Escape (isOpen is also set false). */
|
|
25
|
+
onClose?: () => void;
|
|
26
|
+
/** Extra classes on the panel wrapper. */
|
|
27
|
+
class?: string;
|
|
26
28
|
children?: Snippet;
|
|
27
29
|
}
|
|
28
30
|
|
|
29
31
|
let {
|
|
30
32
|
isOpen = $bindable(),
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
confirmText = "Confirm",
|
|
34
|
-
cancelText = "Cancel",
|
|
35
|
-
onConfirm,
|
|
36
|
-
onCancel,
|
|
37
|
-
variant = "normal",
|
|
38
|
-
size = "md",
|
|
39
|
-
showConfirm = true,
|
|
40
|
-
showCancel = true,
|
|
33
|
+
width,
|
|
34
|
+
height,
|
|
41
35
|
dismissible = true,
|
|
36
|
+
onClose,
|
|
37
|
+
class: cls = "",
|
|
42
38
|
children,
|
|
39
|
+
...rest
|
|
43
40
|
}: Props = $props();
|
|
44
41
|
|
|
42
|
+
const FOCUSABLE =
|
|
43
|
+
'a[href], button:not([disabled]), textarea:not([disabled]), input:not([disabled]), select:not([disabled]), [tabindex]:not([tabindex="-1"])';
|
|
44
|
+
|
|
45
|
+
let panelEl: HTMLElement | undefined = $state();
|
|
46
|
+
let previouslyFocused: HTMLElement | null = null;
|
|
47
|
+
|
|
45
48
|
// Move the overlay to <body> so it's a real top-level layer — never a sibling of the trigger (which
|
|
46
|
-
// would perturb surrounding layout
|
|
47
|
-
// ancestor's overflow/stacking context. This is what makes it an actual modal, not an inline box.
|
|
49
|
+
// would perturb surrounding layout) and never clipped by an ancestor's overflow/stacking context.
|
|
48
50
|
function portal(node: HTMLElement) {
|
|
49
51
|
document.body.appendChild(node);
|
|
50
52
|
return { destroy: () => node.remove() };
|
|
51
53
|
}
|
|
52
54
|
|
|
53
|
-
// Lock background scroll while open; pad by the scrollbar width so hiding it doesn't shift the page
|
|
54
|
-
// sideways. Restore exactly what was there on close/destroy.
|
|
55
|
+
// Lock background scroll while open; pad by the scrollbar width so hiding it doesn't shift the page.
|
|
55
56
|
$effect(() => {
|
|
56
57
|
if (!isOpen) return;
|
|
57
58
|
const sbw = window.innerWidth - document.documentElement.clientWidth;
|
|
@@ -65,68 +66,86 @@
|
|
|
65
66
|
};
|
|
66
67
|
});
|
|
67
68
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
69
|
+
// Focus management: on open, remember what was focused and move focus into the panel; on close,
|
|
70
|
+
// restore it. (Tab-wrapping is handled in onKeydown.)
|
|
71
|
+
$effect(() => {
|
|
72
|
+
if (!isOpen) return;
|
|
73
|
+
previouslyFocused = document.activeElement as HTMLElement | null;
|
|
74
|
+
queueMicrotask(() => {
|
|
75
|
+
const first = panelEl?.querySelector<HTMLElement>(FOCUSABLE);
|
|
76
|
+
(first ?? panelEl)?.focus();
|
|
77
|
+
});
|
|
78
|
+
return () => previouslyFocused?.focus?.();
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
const panelStyle = $derived(
|
|
82
|
+
[
|
|
83
|
+
width ? `width:${width}` : "width:auto",
|
|
84
|
+
`max-width:${width ? "calc(100vw - 2rem)" : "min(32rem, calc(100vw - 2rem))"}`,
|
|
85
|
+
height ? `height:${height}` : null,
|
|
86
|
+
"max-height:calc(100vh - 2rem)",
|
|
87
|
+
]
|
|
88
|
+
.filter(Boolean)
|
|
89
|
+
.join(";"),
|
|
90
|
+
);
|
|
91
|
+
|
|
92
|
+
function close() {
|
|
93
|
+
if (!dismissible) return;
|
|
94
|
+
onClose?.();
|
|
74
95
|
isOpen = false;
|
|
75
96
|
}
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
if (dismissible) handleCancel();
|
|
79
|
-
}
|
|
80
|
-
function handleBackdropClick(event: MouseEvent) {
|
|
81
|
-
if (event.target === event.currentTarget) dismiss();
|
|
97
|
+
function onBackdrop(event: MouseEvent) {
|
|
98
|
+
if (event.target === event.currentTarget) close();
|
|
82
99
|
}
|
|
83
|
-
function
|
|
84
|
-
if (isOpen
|
|
100
|
+
function onKeydown(event: KeyboardEvent) {
|
|
101
|
+
if (!isOpen) return;
|
|
102
|
+
if (event.key === "Escape") {
|
|
103
|
+
close();
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
if (event.key === "Tab" && panelEl) {
|
|
107
|
+
const items = [...panelEl.querySelectorAll<HTMLElement>(FOCUSABLE)].filter(
|
|
108
|
+
(el) => el.offsetParent !== null,
|
|
109
|
+
);
|
|
110
|
+
if (items.length === 0) {
|
|
111
|
+
event.preventDefault();
|
|
112
|
+
panelEl.focus();
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
const first = items[0];
|
|
116
|
+
const last = items[items.length - 1];
|
|
117
|
+
const active = document.activeElement;
|
|
118
|
+
if (event.shiftKey && (active === first || active === panelEl)) {
|
|
119
|
+
event.preventDefault();
|
|
120
|
+
last.focus();
|
|
121
|
+
} else if (!event.shiftKey && active === last) {
|
|
122
|
+
event.preventDefault();
|
|
123
|
+
first.focus();
|
|
124
|
+
}
|
|
125
|
+
}
|
|
85
126
|
}
|
|
86
127
|
</script>
|
|
87
128
|
|
|
88
|
-
<svelte:window on:keydown={
|
|
129
|
+
<svelte:window on:keydown={onKeydown} />
|
|
89
130
|
|
|
90
131
|
{#if isOpen}
|
|
132
|
+
<!-- svelte-ignore a11y_click_events_have_key_events a11y_no_static_element_interactions -->
|
|
91
133
|
<div
|
|
92
134
|
use:portal
|
|
93
|
-
class="fixed inset-0 bg-black/50 flex items-center justify-center z-[9999]"
|
|
94
|
-
onclick={
|
|
95
|
-
|
|
96
|
-
role="dialog"
|
|
97
|
-
aria-modal="true"
|
|
98
|
-
tabindex="-1"
|
|
135
|
+
class="fixed inset-0 bg-black/50 flex items-center justify-center z-[9999] p-4"
|
|
136
|
+
onclick={onBackdrop}
|
|
137
|
+
role="presentation"
|
|
99
138
|
>
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
<p class="text-sm [color:color-mix(in_srgb,var(--ui-color-text)_70%,transparent)]">
|
|
112
|
-
{message}
|
|
113
|
-
</p>
|
|
114
|
-
{/if}
|
|
115
|
-
|
|
116
|
-
{#if showConfirm || showCancel}
|
|
117
|
-
<CardFooter {size}>
|
|
118
|
-
{#if showCancel}
|
|
119
|
-
<Button text={cancelText} onclick={handleCancel} variant="secondary" />
|
|
120
|
-
{/if}
|
|
121
|
-
{#if showConfirm}
|
|
122
|
-
<Button
|
|
123
|
-
text={confirmText}
|
|
124
|
-
onclick={handleConfirm}
|
|
125
|
-
variant={variant === "danger" ? "danger" : "primary"}
|
|
126
|
-
/>
|
|
127
|
-
{/if}
|
|
128
|
-
</CardFooter>
|
|
129
|
-
{/if}
|
|
130
|
-
</Card>
|
|
139
|
+
<div
|
|
140
|
+
bind:this={panelEl}
|
|
141
|
+
class="overflow-auto z-[10000] {cls}"
|
|
142
|
+
style={panelStyle}
|
|
143
|
+
role="dialog"
|
|
144
|
+
aria-modal="true"
|
|
145
|
+
tabindex="-1"
|
|
146
|
+
{...rest}
|
|
147
|
+
>
|
|
148
|
+
{@render children?.()}
|
|
149
|
+
</div>
|
|
131
150
|
</div>
|
|
132
151
|
{/if}
|
|
@@ -1,22 +1,28 @@
|
|
|
1
1
|
import type { Snippet } from "svelte";
|
|
2
|
-
import type {
|
|
3
|
-
|
|
4
|
-
|
|
2
|
+
import type { HTMLAttributes } from "svelte/elements";
|
|
3
|
+
/**
|
|
4
|
+
* Dialog — a PURE modal shell (layout container). It owns only the modal mechanics: portals itself
|
|
5
|
+
* to <body>, locks background scroll, renders a dismissable backdrop, traps focus, and exposes the
|
|
6
|
+
* ARIA dialog role. It imposes NO content structure — put a `Card` (or anything) inside via
|
|
7
|
+
* `children`. For the common title + message + Confirm/Cancel pattern, use `ConfirmDialog`, which is
|
|
8
|
+
* built on this.
|
|
9
|
+
*
|
|
10
|
+
* `width` / `height` accept any CSS length: `"80%"`, `"80vw"`, `"60vh"`, `"400px"`, … (both default
|
|
11
|
+
* to content size, capped to the viewport minus a 1rem inset so the panel never runs off-screen).
|
|
12
|
+
*/
|
|
13
|
+
interface Props extends Omit<HTMLAttributes<HTMLDivElement>, "class"> {
|
|
14
|
+
/** Whether the dialog is shown (bindable). */
|
|
5
15
|
isOpen: boolean;
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
onCancel?: () => void;
|
|
12
|
-
variant?: "danger" | "normal";
|
|
13
|
-
/** Density tier — forwarded to the underlying Card / CardHeader / CardFooter. */
|
|
14
|
-
size?: Responsive<Size>;
|
|
15
|
-
showConfirm?: boolean;
|
|
16
|
-
showCancel?: boolean;
|
|
17
|
-
/** Allow dismissing by clicking the backdrop OR pressing Escape (default true). Set `false` for a
|
|
18
|
-
* must-decide dialog (e.g. a destructive confirm) so only the buttons close it. */
|
|
16
|
+
/** Panel width — any CSS value (`80%`, `80vw`, `400px`). Default: auto, capped at `min(32rem, …)`. */
|
|
17
|
+
width?: string;
|
|
18
|
+
/** Panel height — any CSS value (`60%`, `60vh`, `300px`). Default: auto (content height). */
|
|
19
|
+
height?: string;
|
|
20
|
+
/** Allow dismissing via backdrop click / Escape (default true). Set false for a must-decide modal. */
|
|
19
21
|
dismissible?: boolean;
|
|
22
|
+
/** Called when dismissed via backdrop / Escape (isOpen is also set false). */
|
|
23
|
+
onClose?: () => void;
|
|
24
|
+
/** Extra classes on the panel wrapper. */
|
|
25
|
+
class?: string;
|
|
20
26
|
children?: Snippet;
|
|
21
27
|
}
|
|
22
28
|
declare const Dialog: import("svelte").Component<Props, {}, "isOpen">;
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
import Input from "./Input.svelte";
|
|
15
15
|
import Button from "./Button.svelte";
|
|
16
16
|
import Popup from "./Popup.svelte";
|
|
17
|
-
import
|
|
17
|
+
import ConfirmDialog from "./ConfirmDialog.svelte";
|
|
18
18
|
import type { DataSet } from "../data/dataset.js";
|
|
19
19
|
import type { Size } from "../types/sizes.js";
|
|
20
20
|
import type { Responsive } from "../types/responsive.js";
|
|
@@ -227,7 +227,7 @@
|
|
|
227
227
|
</ul>
|
|
228
228
|
</Popup>
|
|
229
229
|
|
|
230
|
-
<
|
|
230
|
+
<ConfirmDialog bind:isOpen={helpOpen} title="Query syntax" showConfirm={false} showCancel={false}>
|
|
231
231
|
<div class="max-h-[70vh] space-y-4 overflow-y-auto text-sm [color:var(--ui-color-text)]">
|
|
232
232
|
<p>
|
|
233
233
|
A filter is one or more conditions, <code class="font-mono">$column operator value</code>.
|
|
@@ -281,4 +281,4 @@ $ip|inet |= [10.0.0.0|inet..10.255.255.255|inet]
|
|
|
281
281
|
values. Applies on <kbd class="rounded border px-1 {bc}">Enter</kbd>.
|
|
282
282
|
</p>
|
|
283
283
|
</div>
|
|
284
|
-
</
|
|
284
|
+
</ConfirmDialog>
|
package/dist/index.d.ts
CHANGED
|
@@ -10,6 +10,7 @@ export { default as CardFooter } from "./components/CardFooter.svelte";
|
|
|
10
10
|
export { default as Toaster } from "./components/Toast/Toaster.svelte";
|
|
11
11
|
export { toast, getToasts, dismissToast, type ToastVariant, type ToastItem, } from "./components/Toast/toast.svelte.js";
|
|
12
12
|
export { default as Dialog } from "./components/Dialog.svelte";
|
|
13
|
+
export { default as ConfirmDialog } from "./components/ConfirmDialog.svelte";
|
|
13
14
|
export { default as Input } from "./components/Input.svelte";
|
|
14
15
|
export { default as NotesEditor } from "./components/NotesEditor.svelte";
|
|
15
16
|
export { default as Rating } from "./components/Rating.svelte";
|
package/dist/index.js
CHANGED
|
@@ -12,6 +12,7 @@ export { default as CardFooter } from "./components/CardFooter.svelte";
|
|
|
12
12
|
export { default as Toaster } from "./components/Toast/Toaster.svelte";
|
|
13
13
|
export { toast, getToasts, dismissToast, } from "./components/Toast/toast.svelte.js";
|
|
14
14
|
export { default as Dialog } from "./components/Dialog.svelte";
|
|
15
|
+
export { default as ConfirmDialog } from "./components/ConfirmDialog.svelte";
|
|
15
16
|
export { default as Input } from "./components/Input.svelte";
|
|
16
17
|
export { default as NotesEditor } from "./components/NotesEditor.svelte";
|
|
17
18
|
export { default as Rating } from "./components/Rating.svelte";
|