@spaethtech/svelte-ui 0.1.10 → 0.3.0
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 +166 -42
- package/dist/components/Alert.svelte +119 -0
- package/dist/components/Alert.svelte.d.ts +29 -0
- package/dist/components/Badge/Badge.svelte +142 -69
- package/dist/components/Badge/Badge.svelte.d.ts +26 -6
- package/dist/components/Badge/index.d.ts +1 -1
- package/dist/components/Badge/index.js +1 -1
- package/dist/components/Banner.svelte +133 -0
- package/dist/components/Banner.svelte.d.ts +31 -0
- package/dist/components/Button.svelte +240 -0
- package/dist/components/Button.svelte.d.ts +33 -0
- package/dist/components/ButtonDropdown.svelte +145 -0
- package/dist/components/ButtonDropdown.svelte.d.ts +31 -0
- package/dist/components/Calendar.svelte +259 -0
- package/dist/components/Calendar.svelte.d.ts +27 -0
- package/dist/components/Card.svelte +78 -0
- package/dist/components/Card.svelte.d.ts +17 -0
- package/dist/components/CardBody.svelte +41 -0
- package/dist/components/CardBody.svelte.d.ts +17 -0
- package/dist/components/CardFooter.svelte +49 -0
- package/dist/components/CardFooter.svelte.d.ts +16 -0
- package/dist/components/CardHeader.svelte +49 -0
- package/dist/components/CardHeader.svelte.d.ts +16 -0
- package/dist/components/Checkbox.svelte +131 -0
- package/dist/components/Checkbox.svelte.d.ts +16 -0
- package/dist/components/DataTable.svelte +518 -0
- package/dist/components/DataTable.svelte.d.ts +74 -0
- package/dist/components/DatePicker.svelte +312 -0
- package/dist/components/DatePicker.svelte.d.ts +32 -0
- package/dist/components/DateTimeInput.svelte +93 -0
- package/dist/components/DateTimeInput.svelte.d.ts +20 -0
- package/dist/components/Dialog.svelte +132 -0
- package/dist/components/Dialog.svelte.d.ts +24 -0
- package/dist/components/{EmailInput/EmailInput.svelte → EmailInput.svelte} +17 -8
- package/dist/components/EmailInput.svelte.d.ts +21 -0
- package/dist/components/Input.svelte +369 -0
- package/dist/components/Input.svelte.d.ts +35 -0
- package/dist/components/{List/List.svelte → List.svelte} +194 -133
- package/dist/components/{List/List.svelte.d.ts → List.svelte.d.ts} +9 -1
- package/dist/components/Menu.svelte +117 -0
- package/dist/components/Menu.svelte.d.ts +20 -0
- package/dist/components/NotesEditor.svelte +127 -0
- package/dist/components/NotesEditor.svelte.d.ts +17 -0
- package/dist/components/{NumberInput/NumberInput.svelte → NumberInput.svelte} +146 -105
- package/dist/components/{NumberInput/NumberInput.svelte.d.ts → NumberInput.svelte.d.ts} +11 -4
- package/dist/components/PasswordInput.svelte +52 -0
- package/dist/components/PasswordInput.svelte.d.ts +20 -0
- package/dist/components/Popup.svelte +140 -0
- package/dist/components/Popup.svelte.d.ts +31 -0
- package/dist/components/Query.svelte +284 -0
- package/dist/components/Query.svelte.d.ts +15 -0
- package/dist/components/{Rating/Rating.svelte → Rating.svelte} +19 -10
- package/dist/components/Rating.svelte.d.ts +13 -0
- package/dist/components/{SearchInput/SearchInput.svelte → SearchInput.svelte} +19 -8
- package/dist/components/{SearchInput/SearchInput.svelte.d.ts → SearchInput.svelte.d.ts} +9 -2
- package/dist/components/{Select/Select.svelte → Select.svelte} +48 -76
- package/dist/components/{Select/Select.svelte.d.ts → Select.svelte.d.ts} +11 -1
- package/dist/components/SideBarMenu/SideBarMenu.svelte +724 -0
- package/dist/components/SideBarMenu/SideBarMenu.svelte.d.ts +109 -0
- package/dist/components/SideBarMenu/index.d.ts +2 -0
- package/dist/components/SideBarMenu/index.js +1 -0
- package/dist/components/TabStrip/TabStrip.svelte +384 -0
- package/dist/components/TabStrip/TabStrip.svelte.d.ts +50 -0
- package/dist/components/TabStrip/index.d.ts +3 -0
- package/dist/components/TabStrip/index.js +2 -0
- package/dist/components/{TextArea/TextArea.svelte → TextArea.svelte} +143 -112
- package/dist/components/TextArea.svelte.d.ts +38 -0
- package/dist/components/ThemeSelector.svelte +81 -0
- package/dist/components/ThemeSelector.svelte.d.ts +10 -0
- package/dist/components/TimePicker.svelte +148 -0
- package/dist/components/TimePicker.svelte.d.ts +28 -0
- package/dist/components/TimeRangeInput.svelte +203 -0
- package/dist/components/TimeRangeInput.svelte.d.ts +29 -0
- package/dist/components/TimeSpinner.svelte +202 -0
- package/dist/components/TimeSpinner.svelte.d.ts +26 -0
- package/dist/components/Toast/Toaster.svelte +51 -0
- package/dist/components/Toast/Toaster.svelte.d.ts +12 -0
- package/dist/components/Toast/toast.svelte.d.ts +29 -0
- package/dist/components/Toast/toast.svelte.js +27 -0
- package/dist/components/Toggle.svelte +93 -0
- package/dist/components/Toggle.svelte.d.ts +15 -0
- package/dist/data/dataset.d.ts +42 -0
- package/dist/data/dataset.js +3 -0
- package/dist/data/index.d.ts +3 -0
- package/dist/data/index.js +3 -0
- package/dist/data/query/ast.d.ts +62 -0
- package/dist/data/query/ast.js +12 -0
- package/dist/data/query/index.d.ts +3 -0
- package/dist/data/query/index.js +3 -0
- package/dist/data/query/lexer.d.ts +33 -0
- package/dist/data/query/lexer.js +225 -0
- package/dist/data/query/parser.d.ts +11 -0
- package/dist/data/query/parser.js +252 -0
- package/dist/data/table/grid.svelte.d.ts +57 -0
- package/dist/data/table/grid.svelte.js +139 -0
- package/dist/data/table/index.d.ts +2 -0
- package/dist/data/table/index.js +2 -0
- package/dist/data/table/types.d.ts +79 -0
- package/dist/index.d.ts +48 -22
- package/dist/index.js +45 -21
- package/dist/positioning/anchored.d.ts +61 -0
- package/dist/positioning/anchored.js +122 -0
- package/dist/positioning/tooltip.d.ts +41 -0
- package/dist/positioning/tooltip.js +147 -0
- package/dist/theme.css +205 -0
- package/dist/types/breakpoints.d.ts +24 -0
- package/dist/types/breakpoints.js +13 -0
- package/dist/types/responsive.d.ts +32 -0
- package/dist/types/responsive.js +54 -0
- package/dist/types/sizes.d.ts +10 -3
- package/dist/types/time.d.ts +19 -0
- package/dist/types/time.js +10 -0
- package/dist/types/variants.d.ts +8 -1
- package/dist/types/variants.js +16 -1
- package/package.json +93 -63
- package/dist/components/Button/Button.svelte +0 -172
- package/dist/components/Button/Button.svelte.d.ts +0 -32
- package/dist/components/Button/index.d.ts +0 -1
- package/dist/components/Button/index.js +0 -1
- package/dist/components/Dialog/Dialog.svelte +0 -101
- package/dist/components/Dialog/Dialog.svelte.d.ts +0 -18
- package/dist/components/Dialog/index.d.ts +0 -1
- package/dist/components/Dialog/index.js +0 -1
- package/dist/components/ElementManager/ElementManager.svelte +0 -397
- package/dist/components/ElementManager/ElementManager.svelte.d.ts +0 -43
- package/dist/components/ElementManager/index.d.ts +0 -1
- package/dist/components/ElementManager/index.js +0 -1
- package/dist/components/EmailInput/EmailInput.svelte.d.ts +0 -14
- package/dist/components/EmailInput/index.d.ts +0 -1
- package/dist/components/EmailInput/index.js +0 -1
- package/dist/components/Icon/Icon.svelte +0 -74
- package/dist/components/Icon/Icon.svelte.d.ts +0 -13
- package/dist/components/Icon/index.d.ts +0 -1
- package/dist/components/Icon/index.js +0 -1
- package/dist/components/Input/Input.svelte +0 -268
- package/dist/components/Input/Input.svelte.d.ts +0 -18
- package/dist/components/Input/index.d.ts +0 -1
- package/dist/components/Input/index.js +0 -1
- package/dist/components/List/index.d.ts +0 -1
- package/dist/components/List/index.js +0 -1
- package/dist/components/ListItem/ListItem.svelte +0 -175
- package/dist/components/ListItem/ListItem.svelte.d.ts +0 -24
- package/dist/components/ListItem/index.d.ts +0 -2
- package/dist/components/ListItem/index.js +0 -2
- package/dist/components/ListView/ListView.svelte +0 -463
- package/dist/components/ListView/ListView.svelte.d.ts +0 -37
- package/dist/components/ListView/index.d.ts +0 -2
- package/dist/components/ListView/index.js +0 -2
- package/dist/components/NodeGraph/BaseNode.d.ts +0 -57
- package/dist/components/NodeGraph/BaseNode.js +0 -30
- package/dist/components/NodeGraph/Edge.svelte +0 -60
- package/dist/components/NodeGraph/Edge.svelte.d.ts +0 -16
- package/dist/components/NodeGraph/ExpressionEvaluator.d.ts +0 -82
- package/dist/components/NodeGraph/ExpressionEvaluator.js +0 -152
- package/dist/components/NodeGraph/Node.svelte +0 -100
- package/dist/components/NodeGraph/Node.svelte.d.ts +0 -10
- package/dist/components/NodeGraph/NodeGraph.svelte +0 -52
- package/dist/components/NodeGraph/NodeGraph.svelte.d.ts +0 -14
- package/dist/components/NodeGraph/NodeInstance.svelte.d.ts +0 -80
- package/dist/components/NodeGraph/NodeInstance.svelte.js +0 -241
- package/dist/components/NodeGraph/NodePort.svelte +0 -75
- package/dist/components/NodeGraph/NodePort.svelte.d.ts +0 -11
- package/dist/components/NodeGraph/decorators.d.ts +0 -81
- package/dist/components/NodeGraph/decorators.js +0 -148
- package/dist/components/NodeGraph/index.d.ts +0 -9
- package/dist/components/NodeGraph/index.js +0 -9
- package/dist/components/NodeGraph/types.d.ts +0 -94
- package/dist/components/NodeGraph/types.js +0 -33
- package/dist/components/NotesEditor/NotesEditor.svelte +0 -203
- package/dist/components/NotesEditor/NotesEditor.svelte.d.ts +0 -9
- package/dist/components/NotesEditor/index.d.ts +0 -1
- package/dist/components/NotesEditor/index.js +0 -1
- package/dist/components/NumberInput/index.d.ts +0 -1
- package/dist/components/NumberInput/index.js +0 -1
- package/dist/components/PasswordInput/PasswordInput.svelte +0 -41
- package/dist/components/PasswordInput/PasswordInput.svelte.d.ts +0 -13
- package/dist/components/PasswordInput/index.d.ts +0 -1
- package/dist/components/PasswordInput/index.js +0 -1
- package/dist/components/Popup/index.d.ts +0 -1
- package/dist/components/Popup/index.js +0 -1
- package/dist/components/Rating/Rating.svelte.d.ts +0 -7
- package/dist/components/Rating/index.d.ts +0 -1
- package/dist/components/Rating/index.js +0 -1
- package/dist/components/ScrollView/ScrollView.svelte +0 -285
- package/dist/components/ScrollView/ScrollView.svelte.d.ts +0 -19
- package/dist/components/ScrollView/index.d.ts +0 -1
- package/dist/components/ScrollView/index.js +0 -1
- package/dist/components/SearchInput/index.d.ts +0 -1
- package/dist/components/SearchInput/index.js +0 -1
- package/dist/components/Select/index.d.ts +0 -1
- package/dist/components/Select/index.js +0 -1
- package/dist/components/TextArea/TextArea.svelte.d.ts +0 -19
- package/dist/components/TextArea/index.d.ts +0 -1
- package/dist/components/TextArea/index.js +0 -1
- package/dist/components/ThemeSelector/ThemeSelector.svelte +0 -64
- package/dist/components/ThemeSelector/ThemeSelector.svelte.d.ts +0 -3
- package/dist/components/ThemeSelector/index.d.ts +0 -1
- package/dist/components/ThemeSelector/index.js +0 -1
- package/dist/components/TooltipHandler/TooltipHandler.svelte +0 -593
- package/dist/components/TooltipHandler/TooltipHandler.svelte.d.ts +0 -10
- package/dist/components/TooltipHandler/index.d.ts +0 -1
- package/dist/components/TooltipHandler/index.js +0 -1
- package/dist/styles/sizes.d.ts +0 -78
- package/dist/styles/sizes.js +0 -120
- package/dist/styles/variants.d.ts +0 -106
- package/dist/styles/variants.js +0 -194
- package/dist/types/ManagerTypes.d.ts +0 -42
- /package/dist/{types/ManagerTypes.js → data/table/types.js} +0 -0
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import type { Snippet } from "svelte";
|
|
2
|
+
import type { MenuItem, DataGrid } from "../data/table/index.js";
|
|
3
|
+
import type { Size } from "../types/sizes.js";
|
|
4
|
+
import { type Responsive } from "../types/responsive.js";
|
|
5
|
+
import { type Variant } from "../types/variants.js";
|
|
6
|
+
declare function $$render<T>(): {
|
|
7
|
+
props: {
|
|
8
|
+
grid: DataGrid<T>;
|
|
9
|
+
selectable?: boolean;
|
|
10
|
+
expandable?: boolean;
|
|
11
|
+
/** When true, each row gets up/down chevron buttons in the lead
|
|
12
|
+
* column and fires `onReorder(row, 'up'|'down')` when clicked.
|
|
13
|
+
* The consumer owns the actual swap (typically updating the
|
|
14
|
+
* `sortOrder` field of two rows). Buttons auto-disable at the
|
|
15
|
+
* ends of the visible page — with server pagination, this
|
|
16
|
+
* reorders WITHIN the visible page only. */
|
|
17
|
+
reorderable?: boolean;
|
|
18
|
+
expanded?: Snippet<[T]>;
|
|
19
|
+
rowActions?: (row: T) => MenuItem[];
|
|
20
|
+
bulkActions?: (selected: ReadonlySet<string>) => MenuItem[];
|
|
21
|
+
onReorder?: (row: T, direction: "up" | "down") => void;
|
|
22
|
+
/** Overall density tier. Scales the whole table uniformly:
|
|
23
|
+
* font-size (--ui-text-*), row/footer padding, inter-column
|
|
24
|
+
* gap, lead/tail control squares (--ui-height-*), and
|
|
25
|
+
* propagates to every inner Button / Checkbox / Select.
|
|
26
|
+
* Header text inherits the outer font-size — no separate
|
|
27
|
+
* axis. Default `'md'` matches the rest of svelte-ui. */
|
|
28
|
+
size?: Responsive<Size>;
|
|
29
|
+
/** Every header + body cell wrapper gets Tailwind's `truncate`
|
|
30
|
+
* (white-space:nowrap, overflow:hidden, text-overflow:ellipsis).
|
|
31
|
+
* Simple text cells render with ellipsis automatically. Compound
|
|
32
|
+
* cells (inline-flex wrappers, badges, buttons) clip at the
|
|
33
|
+
* right edge — to also get ellipsis on the inner text, the
|
|
34
|
+
* snippet's text span needs its own `truncate` class.
|
|
35
|
+
*
|
|
36
|
+
* Defaults to `true` — the "wrong" default is columns visually
|
|
37
|
+
* bleeding into their neighbours when a value doesn't fit its
|
|
38
|
+
* configured span. Pass `truncate={false}` if the consumer
|
|
39
|
+
* wants wrap-on-overflow (rare — usually you'd fix the span
|
|
40
|
+
* allocation instead). */
|
|
41
|
+
truncate?: boolean;
|
|
42
|
+
/** Drop the visible frame while keeping each section's 1px box: every section's border colour
|
|
43
|
+
* becomes its own background, so it vanishes per-section on a matching surface. Defaults `false`. */
|
|
44
|
+
borderless?: boolean;
|
|
45
|
+
/** Tints the whole table's interactive/section surfaces (row hover,
|
|
46
|
+
* header/footer bands) with a semantic colour instead of the neutral
|
|
47
|
+
* default. Points --ui-accent at the variant's token and re-mixes the
|
|
48
|
+
* --ui-color-hover/surface/active tints via the `.ui-accent` class.
|
|
49
|
+
* `ghost` additionally drops ALL section fills (bands + body) for a bare,
|
|
50
|
+
* see-through table. Omit for the default grey tint. */
|
|
51
|
+
variant?: Variant;
|
|
52
|
+
};
|
|
53
|
+
exports: {};
|
|
54
|
+
bindings: "";
|
|
55
|
+
slots: {};
|
|
56
|
+
events: {};
|
|
57
|
+
};
|
|
58
|
+
declare class __sveltets_Render<T> {
|
|
59
|
+
props(): ReturnType<typeof $$render<T>>['props'];
|
|
60
|
+
events(): ReturnType<typeof $$render<T>>['events'];
|
|
61
|
+
slots(): ReturnType<typeof $$render<T>>['slots'];
|
|
62
|
+
bindings(): "";
|
|
63
|
+
exports(): {};
|
|
64
|
+
}
|
|
65
|
+
interface $$IsomorphicComponent {
|
|
66
|
+
new <T>(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']>> & {
|
|
67
|
+
$$bindings?: ReturnType<__sveltets_Render<T>['bindings']>;
|
|
68
|
+
} & ReturnType<__sveltets_Render<T>['exports']>;
|
|
69
|
+
<T>(internal: unknown, props: ReturnType<__sveltets_Render<T>['props']> & {}): ReturnType<__sveltets_Render<T>['exports']>;
|
|
70
|
+
z_$$bindings?: ReturnType<__sveltets_Render<any>['bindings']>;
|
|
71
|
+
}
|
|
72
|
+
declare const DataTable: $$IsomorphicComponent;
|
|
73
|
+
type DataTable<T> = InstanceType<typeof DataTable<T>>;
|
|
74
|
+
export default DataTable;
|
|
@@ -0,0 +1,312 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
/**
|
|
3
|
+
* DatePicker — a themed date field (single date, datetime, or date RANGE) that follows the library
|
|
4
|
+
* look end to end: an {@link Input}-framed trigger + a {@link Popup} (top layer) holding a
|
|
5
|
+
* {@link Calendar}, with a compact {@link TimeSpinner} tucked underneath in `time` mode. A hidden
|
|
6
|
+
* input carries the ISO value for forms (single mode).
|
|
7
|
+
*
|
|
8
|
+
* <DatePicker bind:value={dateStr} /> single value: 'YYYY-MM-DD'
|
|
9
|
+
* <DatePicker time bind:value={dtStr} /> datetime value: 'YYYY-MM-DDTHH:MM'
|
|
10
|
+
* <DatePicker range bind:value={{start,end}} /> range value: { start, end }
|
|
11
|
+
*
|
|
12
|
+
* `variant` accents the selection/actions; `size` scales the field, calendar, spinner, and popup.
|
|
13
|
+
*/
|
|
14
|
+
-->
|
|
15
|
+
<script lang="ts">
|
|
16
|
+
import type { Size } from "../types/sizes.js";
|
|
17
|
+
import { responsiveClasses, type Responsive } from "../types/responsive.js";
|
|
18
|
+
import { variantToken, type Variant } from "../types/variants.js";
|
|
19
|
+
import { resolveStep, type StepSpec } from "../types/time.js";
|
|
20
|
+
import Input from "./Input.svelte";
|
|
21
|
+
import Popup from "./Popup.svelte";
|
|
22
|
+
import Calendar from "./Calendar.svelte";
|
|
23
|
+
import TimeSpinner from "./TimeSpinner.svelte";
|
|
24
|
+
import IconCalendar from "~icons/mdi/calendar-outline";
|
|
25
|
+
import IconClose from "~icons/mdi/close";
|
|
26
|
+
|
|
27
|
+
type RangeValue = { start: string | null; end: string | null };
|
|
28
|
+
interface Props {
|
|
29
|
+
value?: string | RangeValue | null;
|
|
30
|
+
range?: boolean;
|
|
31
|
+
time?: boolean;
|
|
32
|
+
/** `time` mode: column interval — a number = minute step, or `{ hour, minute, second }`. Default 1. */
|
|
33
|
+
step?: StepSpec;
|
|
34
|
+
/** `time` mode: add a seconds column; the time part becomes `HH:MM:SS`. Default false. */
|
|
35
|
+
seconds?: boolean;
|
|
36
|
+
/** `time` mode: show the AM/PM meridiem (12h). `false` = 24h. Default true; supply from app config. */
|
|
37
|
+
meridiem?: boolean;
|
|
38
|
+
min?: string;
|
|
39
|
+
max?: string;
|
|
40
|
+
weekStart?: 0 | 1;
|
|
41
|
+
locale?: string;
|
|
42
|
+
placeholder?: string;
|
|
43
|
+
disabled?: boolean;
|
|
44
|
+
size?: Responsive<Size>;
|
|
45
|
+
variant?: Variant;
|
|
46
|
+
class?: string;
|
|
47
|
+
name?: string;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
let {
|
|
51
|
+
value = $bindable(),
|
|
52
|
+
range = false,
|
|
53
|
+
time = false,
|
|
54
|
+
step = 1,
|
|
55
|
+
seconds = false,
|
|
56
|
+
meridiem = true,
|
|
57
|
+
min,
|
|
58
|
+
max,
|
|
59
|
+
weekStart,
|
|
60
|
+
locale = typeof navigator !== "undefined" ? navigator.language : "en-US",
|
|
61
|
+
placeholder = range ? "Select dates" : time ? "Select date & time" : "Select date",
|
|
62
|
+
disabled = false,
|
|
63
|
+
size = "md",
|
|
64
|
+
variant = "primary",
|
|
65
|
+
class: cls = "",
|
|
66
|
+
name,
|
|
67
|
+
}: Props = $props();
|
|
68
|
+
|
|
69
|
+
const accent = $derived(`var(${variantToken[variant]})`);
|
|
70
|
+
const pad = $derived(responsiveClasses(size, { sm: "p-1.5", md: "p-2", lg: "p-3" }));
|
|
71
|
+
const footText = $derived(
|
|
72
|
+
responsiveClasses(size, { sm: "text-xs", md: "text-sm", lg: "text-base" }),
|
|
73
|
+
);
|
|
74
|
+
const subtle = "[color:color-mix(in_srgb,var(--ui-color-text)_60%,transparent)]";
|
|
75
|
+
|
|
76
|
+
const dateOf = (s: string | null | undefined) => (s ? s.split("T")[0] : "");
|
|
77
|
+
const timeOf = (s: string | null | undefined) =>
|
|
78
|
+
s && s.includes("T") ? s.split("T")[1].slice(0, seconds ? 8 : 5) : "";
|
|
79
|
+
const midnight = $derived(seconds ? "00:00:00" : "00:00");
|
|
80
|
+
const compose = (d: string, t: string) => (time ? `${d}T${t || midnight}` : d);
|
|
81
|
+
const todayDate = () => {
|
|
82
|
+
const d = new Date();
|
|
83
|
+
return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, "0")}-${String(d.getDate()).padStart(2, "0")}`;
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
const rv = $derived(
|
|
87
|
+
range ? ((value as RangeValue) ?? { start: null, end: null }) : { start: null, end: null },
|
|
88
|
+
);
|
|
89
|
+
const singleStr = $derived(range ? "" : ((value as string | null) ?? ""));
|
|
90
|
+
|
|
91
|
+
let open = $state(false);
|
|
92
|
+
let anchorEl = $state<HTMLDivElement>();
|
|
93
|
+
|
|
94
|
+
// ── single ──
|
|
95
|
+
function pickSingleDate(iso: string) {
|
|
96
|
+
value = compose(iso, timeOf(singleStr));
|
|
97
|
+
if (!time) open = false;
|
|
98
|
+
}
|
|
99
|
+
function setSingleTime(t: string) {
|
|
100
|
+
value = compose(dateOf(singleStr) || todayDate(), t);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// ── range ──
|
|
104
|
+
function onRange(r: { start: string; end: string }) {
|
|
105
|
+
const s = r.start ? compose(r.start, timeOf(rv.start) || (time ? midnight : "")) : null;
|
|
106
|
+
const e = r.end
|
|
107
|
+
? compose(r.end, timeOf(rv.end) || (time ? (seconds ? "23:59:59" : "23:59") : ""))
|
|
108
|
+
: null;
|
|
109
|
+
value = { start: s, end: e };
|
|
110
|
+
if (!time && e) open = false;
|
|
111
|
+
}
|
|
112
|
+
const setStartTime = (t: string) =>
|
|
113
|
+
rv.start && (value = { ...rv, start: `${dateOf(rv.start)}T${t}` });
|
|
114
|
+
const setEndTime = (t: string) => rv.end && (value = { ...rv, end: `${dateOf(rv.end)}T${t}` });
|
|
115
|
+
|
|
116
|
+
function goToday() {
|
|
117
|
+
const t = todayDate();
|
|
118
|
+
if (range) value = { start: compose(t, time ? midnight : ""), end: null };
|
|
119
|
+
else pickSingleDate(t);
|
|
120
|
+
}
|
|
121
|
+
// time mode: jump to today's date AND the current time (minutes snapped to `step`; real seconds).
|
|
122
|
+
function goNow() {
|
|
123
|
+
const d = new Date();
|
|
124
|
+
const ms = resolveStep(step).minute;
|
|
125
|
+
const mins = Math.round((d.getHours() * 60 + d.getMinutes()) / ms) * ms;
|
|
126
|
+
const hhmm = `${String(Math.floor(mins / 60) % 24).padStart(2, "0")}:${String(mins % 60).padStart(2, "0")}`;
|
|
127
|
+
const t = seconds ? `${hhmm}:${String(d.getSeconds()).padStart(2, "0")}` : hhmm;
|
|
128
|
+
const day = todayDate();
|
|
129
|
+
if (range) value = { start: `${day}T${t}`, end: null };
|
|
130
|
+
else value = compose(day, t);
|
|
131
|
+
if (!range) open = false; // a full date+time is a complete single selection
|
|
132
|
+
}
|
|
133
|
+
function clear() {
|
|
134
|
+
value = range ? { start: null, end: null } : null;
|
|
135
|
+
open = false;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// ── trigger label ──
|
|
139
|
+
const fmtDate = (s: string) => {
|
|
140
|
+
if (!s) return "";
|
|
141
|
+
const [y, m, d] = dateOf(s).split("-").map(Number);
|
|
142
|
+
return new Intl.DateTimeFormat(locale, {
|
|
143
|
+
month: "short",
|
|
144
|
+
day: "numeric",
|
|
145
|
+
year: "numeric",
|
|
146
|
+
}).format(new Date(y, m - 1, d));
|
|
147
|
+
};
|
|
148
|
+
const fmtTime = (s: string) => {
|
|
149
|
+
const t = timeOf(s);
|
|
150
|
+
if (!t) return "";
|
|
151
|
+
const [h, m, sec] = t.split(":").map(Number);
|
|
152
|
+
return new Intl.DateTimeFormat(locale, {
|
|
153
|
+
hour: "numeric",
|
|
154
|
+
minute: "2-digit",
|
|
155
|
+
second: seconds ? "2-digit" : undefined,
|
|
156
|
+
hour12: meridiem,
|
|
157
|
+
}).format(new Date(2000, 0, 1, h, m, sec || 0));
|
|
158
|
+
};
|
|
159
|
+
const fmt = (s: string) => (s ? (time ? `${fmtDate(s)}, ${fmtTime(s)}` : fmtDate(s)) : "");
|
|
160
|
+
const label = $derived.by(() => {
|
|
161
|
+
if (range)
|
|
162
|
+
return rv.start ? `${fmt(rv.start ?? "")}${rv.end ? ` – ${fmt(rv.end ?? "")}` : " – …"}` : "";
|
|
163
|
+
return fmt(singleStr);
|
|
164
|
+
});
|
|
165
|
+
const hasValue = $derived(range ? !!(rv.start || rv.end) : !!singleStr);
|
|
166
|
+
|
|
167
|
+
function toggle() {
|
|
168
|
+
if (!disabled) open = !open;
|
|
169
|
+
}
|
|
170
|
+
function onKey(e: KeyboardEvent) {
|
|
171
|
+
if (disabled) return;
|
|
172
|
+
if (e.key === "ArrowDown" || e.key === "Enter" || e.key === " ") {
|
|
173
|
+
e.preventDefault();
|
|
174
|
+
open = true;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
</script>
|
|
178
|
+
|
|
179
|
+
<div bind:this={anchorEl} class="w-full {cls}">
|
|
180
|
+
<Input
|
|
181
|
+
value={label}
|
|
182
|
+
readonly
|
|
183
|
+
{size}
|
|
184
|
+
{disabled}
|
|
185
|
+
{placeholder}
|
|
186
|
+
onclick={toggle}
|
|
187
|
+
onkeydown={onKey}
|
|
188
|
+
inputClass="cursor-pointer"
|
|
189
|
+
actions={trailing}
|
|
190
|
+
/>
|
|
191
|
+
{#if name && !range}<input type="hidden" {name} value={singleStr} />{/if}
|
|
192
|
+
</div>
|
|
193
|
+
|
|
194
|
+
{#snippet trailing()}
|
|
195
|
+
{#if hasValue && !disabled}
|
|
196
|
+
<button
|
|
197
|
+
type="button"
|
|
198
|
+
aria-label="Clear"
|
|
199
|
+
tabindex="-1"
|
|
200
|
+
class="grid size-5 place-items-center rounded {subtle} hover:[background-color:color-mix(in_srgb,var(--ui-color-text)_10%,transparent)]"
|
|
201
|
+
onclick={(e) => {
|
|
202
|
+
e.stopPropagation();
|
|
203
|
+
clear();
|
|
204
|
+
}}><IconClose class="size-3.5" /></button
|
|
205
|
+
>
|
|
206
|
+
{/if}
|
|
207
|
+
<button
|
|
208
|
+
type="button"
|
|
209
|
+
tabindex="-1"
|
|
210
|
+
aria-label="Open calendar"
|
|
211
|
+
{disabled}
|
|
212
|
+
class="inline-flex items-center [color:var(--ui-color-secondary)] disabled:opacity-50"
|
|
213
|
+
onclick={toggle}><IconCalendar /></button
|
|
214
|
+
>
|
|
215
|
+
{/snippet}
|
|
216
|
+
|
|
217
|
+
<Popup anchor={anchorEl} bind:open offset={6}>
|
|
218
|
+
<div
|
|
219
|
+
class="rounded-[var(--ui-border-radius)] border {pad} shadow-lg [border-color:color-mix(in_srgb,var(--ui-color-secondary)_var(--ui-tint-border),transparent)] [background-color:var(--ui-color-background)] [color:var(--ui-color-text)]"
|
|
220
|
+
style="--accent: {accent}"
|
|
221
|
+
>
|
|
222
|
+
{#if range}
|
|
223
|
+
<Calendar
|
|
224
|
+
mode="range"
|
|
225
|
+
start={dateOf(rv.start)}
|
|
226
|
+
end={dateOf(rv.end)}
|
|
227
|
+
{min}
|
|
228
|
+
{max}
|
|
229
|
+
{weekStart}
|
|
230
|
+
{locale}
|
|
231
|
+
{size}
|
|
232
|
+
{variant}
|
|
233
|
+
onrange={onRange}
|
|
234
|
+
/>
|
|
235
|
+
{#if time}
|
|
236
|
+
<div class="mt-2 flex flex-col gap-2 border-t pt-2 [border-color:var(--ui-border-color)]">
|
|
237
|
+
<div class="flex items-center gap-3">
|
|
238
|
+
<span class="w-10 shrink-0 text-xs {subtle}">Start</span><TimeSpinner
|
|
239
|
+
value={timeOf(rv.start)}
|
|
240
|
+
{step}
|
|
241
|
+
{seconds}
|
|
242
|
+
{meridiem}
|
|
243
|
+
{size}
|
|
244
|
+
{variant}
|
|
245
|
+
{locale}
|
|
246
|
+
onchange={setStartTime}
|
|
247
|
+
/>
|
|
248
|
+
</div>
|
|
249
|
+
<div class="flex items-center gap-3">
|
|
250
|
+
<span class="w-10 shrink-0 text-xs {subtle}">End</span><TimeSpinner
|
|
251
|
+
value={timeOf(rv.end)}
|
|
252
|
+
{step}
|
|
253
|
+
{seconds}
|
|
254
|
+
{meridiem}
|
|
255
|
+
{size}
|
|
256
|
+
{variant}
|
|
257
|
+
{locale}
|
|
258
|
+
onchange={setEndTime}
|
|
259
|
+
/>
|
|
260
|
+
</div>
|
|
261
|
+
</div>
|
|
262
|
+
{/if}
|
|
263
|
+
{:else}
|
|
264
|
+
<Calendar
|
|
265
|
+
mode="single"
|
|
266
|
+
value={dateOf(singleStr)}
|
|
267
|
+
{min}
|
|
268
|
+
{max}
|
|
269
|
+
{weekStart}
|
|
270
|
+
{locale}
|
|
271
|
+
{size}
|
|
272
|
+
{variant}
|
|
273
|
+
onselect={pickSingleDate}
|
|
274
|
+
/>
|
|
275
|
+
{#if time}
|
|
276
|
+
<div class="mt-2 flex justify-center border-t pt-2 [border-color:var(--ui-border-color)]">
|
|
277
|
+
<TimeSpinner
|
|
278
|
+
value={timeOf(singleStr)}
|
|
279
|
+
{step}
|
|
280
|
+
{seconds}
|
|
281
|
+
{meridiem}
|
|
282
|
+
{size}
|
|
283
|
+
{variant}
|
|
284
|
+
{locale}
|
|
285
|
+
onchange={setSingleTime}
|
|
286
|
+
/>
|
|
287
|
+
</div>
|
|
288
|
+
{/if}
|
|
289
|
+
{/if}
|
|
290
|
+
<div
|
|
291
|
+
class="mt-2 flex items-center justify-between border-t pt-2 {footText} [border-color:var(--ui-border-color)]"
|
|
292
|
+
>
|
|
293
|
+
<div class="flex items-center gap-1">
|
|
294
|
+
<button
|
|
295
|
+
type="button"
|
|
296
|
+
class="rounded px-2 py-1 [color:var(--accent)] hover:underline"
|
|
297
|
+
onclick={goToday}>Today</button
|
|
298
|
+
>
|
|
299
|
+
{#if time}<span class={subtle}>/</span><button
|
|
300
|
+
type="button"
|
|
301
|
+
class="rounded px-2 py-1 [color:var(--accent)] hover:underline"
|
|
302
|
+
onclick={goNow}>Now</button
|
|
303
|
+
>{/if}
|
|
304
|
+
</div>
|
|
305
|
+
<button
|
|
306
|
+
type="button"
|
|
307
|
+
class="rounded px-2 py-1 {subtle} hover:underline"
|
|
308
|
+
onclick={() => (open = false)}>Done</button
|
|
309
|
+
>
|
|
310
|
+
</div>
|
|
311
|
+
</div>
|
|
312
|
+
</Popup>
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { Size } from "../types/sizes.js";
|
|
2
|
+
import { type Responsive } from "../types/responsive.js";
|
|
3
|
+
import { type Variant } from "../types/variants.js";
|
|
4
|
+
import { type StepSpec } from "../types/time.js";
|
|
5
|
+
type RangeValue = {
|
|
6
|
+
start: string | null;
|
|
7
|
+
end: string | null;
|
|
8
|
+
};
|
|
9
|
+
interface Props {
|
|
10
|
+
value?: string | RangeValue | null;
|
|
11
|
+
range?: boolean;
|
|
12
|
+
time?: boolean;
|
|
13
|
+
/** `time` mode: column interval — a number = minute step, or `{ hour, minute, second }`. Default 1. */
|
|
14
|
+
step?: StepSpec;
|
|
15
|
+
/** `time` mode: add a seconds column; the time part becomes `HH:MM:SS`. Default false. */
|
|
16
|
+
seconds?: boolean;
|
|
17
|
+
/** `time` mode: show the AM/PM meridiem (12h). `false` = 24h. Default true; supply from app config. */
|
|
18
|
+
meridiem?: boolean;
|
|
19
|
+
min?: string;
|
|
20
|
+
max?: string;
|
|
21
|
+
weekStart?: 0 | 1;
|
|
22
|
+
locale?: string;
|
|
23
|
+
placeholder?: string;
|
|
24
|
+
disabled?: boolean;
|
|
25
|
+
size?: Responsive<Size>;
|
|
26
|
+
variant?: Variant;
|
|
27
|
+
class?: string;
|
|
28
|
+
name?: string;
|
|
29
|
+
}
|
|
30
|
+
declare const DatePicker: import("svelte").Component<Props, {}, "value">;
|
|
31
|
+
type DatePicker = ReturnType<typeof DatePicker>;
|
|
32
|
+
export default DatePicker;
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
/**
|
|
3
|
+
* DateTimeInput — the themed native date/time picker. Wraps {@link Input} so it inherits the exact
|
|
4
|
+
* frame, `size` height (sm 24 / md 32 / lg 40px), border, hover/focus glow and disabled styling as
|
|
5
|
+
* every other control — a raw `<input type="date|datetime-local">` never matches those, which is why
|
|
6
|
+
* bare date inputs were the one sanctioned exception. Use this instead.
|
|
7
|
+
*
|
|
8
|
+
* `type` picks the native picker: `datetime-local` (default), `date`, `time`, `month`, `week`. The
|
|
9
|
+
* value is the native string the browser emits (e.g. `2026-07-01T13:00` / `2026-07-01`), so it drops
|
|
10
|
+
* straight into a form field. We render our own trailing calendar/clock icon (in the library accent,
|
|
11
|
+
* matching Input's icons) and hide the UA indicator; clicking it opens the native picker via
|
|
12
|
+
* `showPicker()`. The picker popup + edit fields follow light/dark automatically through the theme's
|
|
13
|
+
* `color-scheme`.
|
|
14
|
+
*/
|
|
15
|
+
-->
|
|
16
|
+
<script lang="ts">
|
|
17
|
+
import Input from "./Input.svelte";
|
|
18
|
+
import IconCalendar from "~icons/mdi/calendar-outline";
|
|
19
|
+
import IconClock from "~icons/mdi/clock-outline";
|
|
20
|
+
import type { HTMLInputAttributes } from "svelte/elements";
|
|
21
|
+
import type { Size } from "../types/sizes.js";
|
|
22
|
+
import type { Responsive } from "../types/responsive.js";
|
|
23
|
+
import { variantToken, type Variant } from "../types/variants.js";
|
|
24
|
+
|
|
25
|
+
type DateType = "datetime-local" | "date" | "time" | "month" | "week";
|
|
26
|
+
|
|
27
|
+
interface Props extends Omit<HTMLInputAttributes, "type" | "value" | "size"> {
|
|
28
|
+
value?: string;
|
|
29
|
+
/** Which native picker to render. Default `datetime-local`. */
|
|
30
|
+
type?: DateType;
|
|
31
|
+
size?: Responsive<Size>;
|
|
32
|
+
/** Accent for the native picker highlights (via CSS `accent-color`). Default primary. */
|
|
33
|
+
variant?: Variant;
|
|
34
|
+
class?: string;
|
|
35
|
+
inputClass?: string;
|
|
36
|
+
element?: HTMLInputElement;
|
|
37
|
+
disabled?: boolean;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
let {
|
|
41
|
+
value = $bindable(""),
|
|
42
|
+
type = "datetime-local",
|
|
43
|
+
size = "md",
|
|
44
|
+
variant = "primary",
|
|
45
|
+
class: cls = "",
|
|
46
|
+
inputClass = "",
|
|
47
|
+
element = $bindable(),
|
|
48
|
+
disabled = false,
|
|
49
|
+
...rest
|
|
50
|
+
}: Props = $props();
|
|
51
|
+
|
|
52
|
+
// `accent-color` themes the native picker's own highlights (selected day, spin buttons) in
|
|
53
|
+
// supporting browsers — the one lever we have on the browser-owned popup.
|
|
54
|
+
const accentClass = $derived(`[accent-color:var(${variantToken[variant]})]`);
|
|
55
|
+
|
|
56
|
+
// Our trailing icon opens the native picker (a real user gesture). Guard for disabled + browsers
|
|
57
|
+
// without showPicker (fall back to focusing, which still lets the user type / use the UA indicator).
|
|
58
|
+
function openPicker() {
|
|
59
|
+
if (disabled) return;
|
|
60
|
+
try {
|
|
61
|
+
element?.showPicker?.();
|
|
62
|
+
} catch {
|
|
63
|
+
element?.focus();
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
</script>
|
|
67
|
+
|
|
68
|
+
<Input
|
|
69
|
+
bind:value
|
|
70
|
+
bind:element
|
|
71
|
+
{type}
|
|
72
|
+
{size}
|
|
73
|
+
{disabled}
|
|
74
|
+
class={cls}
|
|
75
|
+
inputClass="ui-datetime {accentClass} {inputClass}"
|
|
76
|
+
icon={pickerIcon}
|
|
77
|
+
iconPosition="right"
|
|
78
|
+
iconClickable
|
|
79
|
+
onIconClick={openPicker}
|
|
80
|
+
{...rest}
|
|
81
|
+
/>
|
|
82
|
+
|
|
83
|
+
{#snippet pickerIcon()}
|
|
84
|
+
{#if type === "time"}<IconClock />{:else}<IconCalendar />{/if}
|
|
85
|
+
{/snippet}
|
|
86
|
+
|
|
87
|
+
<style>
|
|
88
|
+
/* We draw our own calendar/clock icon (opens the picker), so hide the browser's native indicator to
|
|
89
|
+
avoid a double glyph. Value text + edit fields still theme via Input's --ui-color-text + color-scheme. */
|
|
90
|
+
:global(.ui-datetime::-webkit-calendar-picker-indicator) {
|
|
91
|
+
display: none;
|
|
92
|
+
}
|
|
93
|
+
</style>
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { HTMLInputAttributes } from "svelte/elements";
|
|
2
|
+
import type { Size } from "../types/sizes.js";
|
|
3
|
+
import type { Responsive } from "../types/responsive.js";
|
|
4
|
+
import { type Variant } from "../types/variants.js";
|
|
5
|
+
type DateType = "datetime-local" | "date" | "time" | "month" | "week";
|
|
6
|
+
interface Props extends Omit<HTMLInputAttributes, "type" | "value" | "size"> {
|
|
7
|
+
value?: string;
|
|
8
|
+
/** Which native picker to render. Default `datetime-local`. */
|
|
9
|
+
type?: DateType;
|
|
10
|
+
size?: Responsive<Size>;
|
|
11
|
+
/** Accent for the native picker highlights (via CSS `accent-color`). Default primary. */
|
|
12
|
+
variant?: Variant;
|
|
13
|
+
class?: string;
|
|
14
|
+
inputClass?: string;
|
|
15
|
+
element?: HTMLInputElement;
|
|
16
|
+
disabled?: boolean;
|
|
17
|
+
}
|
|
18
|
+
declare const DateTimeInput: import("svelte").Component<Props, {}, "element" | "value">;
|
|
19
|
+
type DateTimeInput = ReturnType<typeof DateTimeInput>;
|
|
20
|
+
export default DateTimeInput;
|
|
@@ -0,0 +1,132 @@
|
|
|
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
|
+
import type { Snippet } from "svelte";
|
|
7
|
+
import type { Size } from "../types/sizes.js";
|
|
8
|
+
import type { Responsive } from "../types/responsive.js";
|
|
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 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. */
|
|
25
|
+
dismissible?: boolean;
|
|
26
|
+
children?: Snippet;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
let {
|
|
30
|
+
isOpen = $bindable(),
|
|
31
|
+
title = "Dialog",
|
|
32
|
+
message,
|
|
33
|
+
confirmText = "Confirm",
|
|
34
|
+
cancelText = "Cancel",
|
|
35
|
+
onConfirm,
|
|
36
|
+
onCancel,
|
|
37
|
+
variant = "normal",
|
|
38
|
+
size = "md",
|
|
39
|
+
showConfirm = true,
|
|
40
|
+
showCancel = true,
|
|
41
|
+
dismissible = true,
|
|
42
|
+
children,
|
|
43
|
+
}: Props = $props();
|
|
44
|
+
|
|
45
|
+
// 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, e.g. flip a space-y `:last-child`) and never clipped by an
|
|
47
|
+
// ancestor's overflow/stacking context. This is what makes it an actual modal, not an inline box.
|
|
48
|
+
function portal(node: HTMLElement) {
|
|
49
|
+
document.body.appendChild(node);
|
|
50
|
+
return { destroy: () => node.remove() };
|
|
51
|
+
}
|
|
52
|
+
|
|
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
|
+
$effect(() => {
|
|
56
|
+
if (!isOpen) return;
|
|
57
|
+
const sbw = window.innerWidth - document.documentElement.clientWidth;
|
|
58
|
+
const prevOverflow = document.body.style.overflow;
|
|
59
|
+
const prevPad = document.body.style.paddingRight;
|
|
60
|
+
document.body.style.overflow = "hidden";
|
|
61
|
+
if (sbw > 0) document.body.style.paddingRight = `${sbw}px`;
|
|
62
|
+
return () => {
|
|
63
|
+
document.body.style.overflow = prevOverflow;
|
|
64
|
+
document.body.style.paddingRight = prevPad;
|
|
65
|
+
};
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
function handleConfirm() {
|
|
69
|
+
onConfirm?.();
|
|
70
|
+
isOpen = false;
|
|
71
|
+
}
|
|
72
|
+
function handleCancel() {
|
|
73
|
+
onCancel?.();
|
|
74
|
+
isOpen = false;
|
|
75
|
+
}
|
|
76
|
+
// Backdrop click / Escape only dismiss when allowed.
|
|
77
|
+
function dismiss() {
|
|
78
|
+
if (dismissible) handleCancel();
|
|
79
|
+
}
|
|
80
|
+
function handleBackdropClick(event: MouseEvent) {
|
|
81
|
+
if (event.target === event.currentTarget) dismiss();
|
|
82
|
+
}
|
|
83
|
+
function handleKeydown(event: KeyboardEvent) {
|
|
84
|
+
if (isOpen && event.key === "Escape") dismiss();
|
|
85
|
+
}
|
|
86
|
+
</script>
|
|
87
|
+
|
|
88
|
+
<svelte:window on:keydown={handleKeydown} />
|
|
89
|
+
|
|
90
|
+
{#if isOpen}
|
|
91
|
+
<div
|
|
92
|
+
use:portal
|
|
93
|
+
class="fixed inset-0 bg-black/50 flex items-center justify-center z-[9999]"
|
|
94
|
+
onclick={handleBackdropClick}
|
|
95
|
+
onkeydown={handleKeydown}
|
|
96
|
+
role="dialog"
|
|
97
|
+
aria-modal="true"
|
|
98
|
+
tabindex="-1"
|
|
99
|
+
>
|
|
100
|
+
<!-- The panel IS a Card — border + --ui-border-radius + padding + background come from Card, so a
|
|
101
|
+
Dialog stays consistent with every other surface; CardHeader/CardFooter give the title + action
|
|
102
|
+
rows their (inset) separators and spacing. `size` flows to all three. -->
|
|
103
|
+
<Card {size} class="max-w-md w-full mx-4 shadow-xl z-[10000]">
|
|
104
|
+
<CardHeader {size}>
|
|
105
|
+
<h3 class="text-lg font-medium [color:var(--ui-color-text)]">{title}</h3>
|
|
106
|
+
</CardHeader>
|
|
107
|
+
|
|
108
|
+
{#if children}
|
|
109
|
+
{@render children()}
|
|
110
|
+
{:else if message}
|
|
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>
|
|
131
|
+
</div>
|
|
132
|
+
{/if}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { Snippet } from "svelte";
|
|
2
|
+
import type { Size } from "../types/sizes.js";
|
|
3
|
+
import type { Responsive } from "../types/responsive.js";
|
|
4
|
+
interface Props {
|
|
5
|
+
isOpen: boolean;
|
|
6
|
+
title?: string;
|
|
7
|
+
message?: string;
|
|
8
|
+
confirmText?: string;
|
|
9
|
+
cancelText?: string;
|
|
10
|
+
onConfirm?: () => void;
|
|
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. */
|
|
19
|
+
dismissible?: boolean;
|
|
20
|
+
children?: Snippet;
|
|
21
|
+
}
|
|
22
|
+
declare const Dialog: import("svelte").Component<Props, {}, "isOpen">;
|
|
23
|
+
type Dialog = ReturnType<typeof Dialog>;
|
|
24
|
+
export default Dialog;
|