@spaethtech/svelte-ui 0.1.10 → 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 +180 -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 +79 -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 +51 -0
- package/dist/components/CardFooter.svelte.d.ts +16 -0
- package/dist/components/CardHeader.svelte +51 -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/ConfirmDialog.svelte +103 -0
- package/dist/components/ConfirmDialog.svelte.d.ts +33 -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 +151 -0
- package/dist/components/Dialog.svelte.d.ts +30 -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 +49 -22
- package/dist/index.js +46 -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/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 +97 -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,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,151 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { Snippet } from "svelte";
|
|
3
|
+
import type { HTMLAttributes } from "svelte/elements";
|
|
4
|
+
|
|
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). */
|
|
17
|
+
isOpen: boolean;
|
|
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. */
|
|
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;
|
|
28
|
+
children?: Snippet;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
let {
|
|
32
|
+
isOpen = $bindable(),
|
|
33
|
+
width,
|
|
34
|
+
height,
|
|
35
|
+
dismissible = true,
|
|
36
|
+
onClose,
|
|
37
|
+
class: cls = "",
|
|
38
|
+
children,
|
|
39
|
+
...rest
|
|
40
|
+
}: Props = $props();
|
|
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
|
+
|
|
48
|
+
// Move the overlay to <body> so it's a real top-level layer — never a sibling of the trigger (which
|
|
49
|
+
// would perturb surrounding layout) and never clipped by an ancestor's overflow/stacking context.
|
|
50
|
+
function portal(node: HTMLElement) {
|
|
51
|
+
document.body.appendChild(node);
|
|
52
|
+
return { destroy: () => node.remove() };
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Lock background scroll while open; pad by the scrollbar width so hiding it doesn't shift the page.
|
|
56
|
+
$effect(() => {
|
|
57
|
+
if (!isOpen) return;
|
|
58
|
+
const sbw = window.innerWidth - document.documentElement.clientWidth;
|
|
59
|
+
const prevOverflow = document.body.style.overflow;
|
|
60
|
+
const prevPad = document.body.style.paddingRight;
|
|
61
|
+
document.body.style.overflow = "hidden";
|
|
62
|
+
if (sbw > 0) document.body.style.paddingRight = `${sbw}px`;
|
|
63
|
+
return () => {
|
|
64
|
+
document.body.style.overflow = prevOverflow;
|
|
65
|
+
document.body.style.paddingRight = prevPad;
|
|
66
|
+
};
|
|
67
|
+
});
|
|
68
|
+
|
|
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?.();
|
|
95
|
+
isOpen = false;
|
|
96
|
+
}
|
|
97
|
+
function onBackdrop(event: MouseEvent) {
|
|
98
|
+
if (event.target === event.currentTarget) close();
|
|
99
|
+
}
|
|
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
|
+
}
|
|
126
|
+
}
|
|
127
|
+
</script>
|
|
128
|
+
|
|
129
|
+
<svelte:window on:keydown={onKeydown} />
|
|
130
|
+
|
|
131
|
+
{#if isOpen}
|
|
132
|
+
<!-- svelte-ignore a11y_click_events_have_key_events a11y_no_static_element_interactions -->
|
|
133
|
+
<div
|
|
134
|
+
use:portal
|
|
135
|
+
class="fixed inset-0 bg-black/50 flex items-center justify-center z-[9999] p-4"
|
|
136
|
+
onclick={onBackdrop}
|
|
137
|
+
role="presentation"
|
|
138
|
+
>
|
|
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>
|
|
150
|
+
</div>
|
|
151
|
+
{/if}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { Snippet } from "svelte";
|
|
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). */
|
|
15
|
+
isOpen: boolean;
|
|
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. */
|
|
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;
|
|
26
|
+
children?: Snippet;
|
|
27
|
+
}
|
|
28
|
+
declare const Dialog: import("svelte").Component<Props, {}, "isOpen">;
|
|
29
|
+
type Dialog = ReturnType<typeof Dialog>;
|
|
30
|
+
export default Dialog;
|
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
-
import Input from
|
|
3
|
-
import
|
|
4
|
-
import type { HTMLInputAttributes } from
|
|
2
|
+
import Input from "./Input.svelte";
|
|
3
|
+
import Email from "~icons/mdi/email-outline";
|
|
4
|
+
import type { HTMLInputAttributes } from "svelte/elements";
|
|
5
|
+
import type { Size } from "../types/sizes.js";
|
|
6
|
+
import type { Responsive } from "../types/responsive.js";
|
|
7
|
+
import type { Variant } from "../types/variants.js";
|
|
5
8
|
|
|
6
|
-
interface Props extends Omit<HTMLInputAttributes,
|
|
9
|
+
interface Props extends Omit<HTMLInputAttributes, "type" | "size"> {
|
|
7
10
|
value: string;
|
|
8
11
|
class?: string;
|
|
9
12
|
inputClass?: string;
|
|
@@ -11,6 +14,10 @@
|
|
|
11
14
|
valid?: boolean;
|
|
12
15
|
touched?: boolean;
|
|
13
16
|
element?: HTMLInputElement;
|
|
17
|
+
size?: Responsive<Size>;
|
|
18
|
+
/** Shared axes — forwarded to the underlying Input. */
|
|
19
|
+
variant?: Variant;
|
|
20
|
+
borderless?: boolean;
|
|
14
21
|
required?: boolean;
|
|
15
22
|
}
|
|
16
23
|
|
|
@@ -26,8 +33,9 @@
|
|
|
26
33
|
|
|
27
34
|
// Default email validation
|
|
28
35
|
const defaultEmailValidator = (value: string) => {
|
|
29
|
-
if (required && !value) return
|
|
30
|
-
if (value && !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value))
|
|
36
|
+
if (required && !value) return "Email is required";
|
|
37
|
+
if (value && !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value))
|
|
38
|
+
return "Please enter a valid email address";
|
|
31
39
|
return true;
|
|
32
40
|
};
|
|
33
41
|
|
|
@@ -41,7 +49,8 @@
|
|
|
41
49
|
bind:touched
|
|
42
50
|
bind:element
|
|
43
51
|
type="email"
|
|
44
|
-
icon={faEnvelope}
|
|
45
52
|
validate={emailValidator}
|
|
46
53
|
{...restProps}
|
|
47
|
-
|
|
54
|
+
>
|
|
55
|
+
{#snippet icon()}<Email />{/snippet}
|
|
56
|
+
</Input>
|