@spaethtech/svelte-ui 0.1.10 → 0.3.1-dev.3.e16df24
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 +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 +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,51 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
/**
|
|
3
|
+
* Renders the toast store's items in a fixed, top-layer stack. Mount ONCE at the app root; push with
|
|
4
|
+
* `toast.*` from anywhere. Each toast is an Alert (filled) honouring its variant + size.
|
|
5
|
+
*/
|
|
6
|
+
import Alert from "../Alert.svelte";
|
|
7
|
+
import IconClose from "~icons/mdi/close";
|
|
8
|
+
import { getToasts, dismissToast } from "./toast.svelte.js";
|
|
9
|
+
import type { Size } from "../../types/sizes.js";
|
|
10
|
+
import type { Responsive } from "../../types/responsive.js";
|
|
11
|
+
import type { Variant } from "../../types/variants.js";
|
|
12
|
+
|
|
13
|
+
let {
|
|
14
|
+
position = "bottom-right",
|
|
15
|
+
variant,
|
|
16
|
+
size = "md",
|
|
17
|
+
}: {
|
|
18
|
+
position?: "bottom-right" | "bottom-left" | "top-right" | "top-left";
|
|
19
|
+
/** Default variant/size for toasts that don't set their own (each toast can still override). */
|
|
20
|
+
variant?: Variant;
|
|
21
|
+
size?: Responsive<Size>;
|
|
22
|
+
} = $props();
|
|
23
|
+
const items = getToasts();
|
|
24
|
+
|
|
25
|
+
const posClass = {
|
|
26
|
+
"bottom-right": "bottom-4 right-4 items-end",
|
|
27
|
+
"bottom-left": "bottom-4 left-4 items-start",
|
|
28
|
+
"top-right": "top-4 right-4 items-end",
|
|
29
|
+
"top-left": "top-4 left-4 items-start",
|
|
30
|
+
}[position];
|
|
31
|
+
</script>
|
|
32
|
+
|
|
33
|
+
{#if items.length}
|
|
34
|
+
<div class="pointer-events-none fixed z-[100] flex max-w-[90vw] flex-col gap-2 {posClass}">
|
|
35
|
+
{#each items as t (t.id)}
|
|
36
|
+
<div
|
|
37
|
+
class="pointer-events-auto flex min-w-56 items-start gap-2 rounded shadow-lg [background-color:var(--ui-color-background)]"
|
|
38
|
+
>
|
|
39
|
+
<Alert variant={t.variant ?? variant} size={t.size ?? size} filled class="grow"
|
|
40
|
+
>{t.message}</Alert
|
|
41
|
+
>
|
|
42
|
+
<button
|
|
43
|
+
type="button"
|
|
44
|
+
aria-label="Dismiss"
|
|
45
|
+
class="shrink-0 self-center pr-2 opacity-60 hover:opacity-100"
|
|
46
|
+
onclick={() => dismissToast(t.id)}><IconClose class="size-4" /></button
|
|
47
|
+
>
|
|
48
|
+
</div>
|
|
49
|
+
{/each}
|
|
50
|
+
</div>
|
|
51
|
+
{/if}
|
|
@@ -0,0 +1,12 @@
|
|
|
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
|
+
type $$ComponentProps = {
|
|
5
|
+
position?: "bottom-right" | "bottom-left" | "top-right" | "top-left";
|
|
6
|
+
/** Default variant/size for toasts that don't set their own (each toast can still override). */
|
|
7
|
+
variant?: Variant;
|
|
8
|
+
size?: Responsive<Size>;
|
|
9
|
+
};
|
|
10
|
+
declare const Toaster: import("svelte").Component<$$ComponentProps, {}, "">;
|
|
11
|
+
type Toaster = ReturnType<typeof Toaster>;
|
|
12
|
+
export default Toaster;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { Size } from "../../types/sizes.js";
|
|
2
|
+
/**
|
|
3
|
+
* Toast store — module-level runes state so toasts live OUTSIDE any page. Push from anywhere with
|
|
4
|
+
* `toast.danger('…')` etc.; a single <Toaster /> mounted at the app root renders them. Honours the
|
|
5
|
+
* shared Variant (status subset) + Size axes. Auto-dismisses after `timeout` ms (0 = sticky).
|
|
6
|
+
*/
|
|
7
|
+
export type ToastVariant = "danger" | "success" | "warning" | "info" | "primary";
|
|
8
|
+
export type ToastItem = {
|
|
9
|
+
id: number;
|
|
10
|
+
variant: ToastVariant;
|
|
11
|
+
message: string;
|
|
12
|
+
size: Size;
|
|
13
|
+
timeout: number;
|
|
14
|
+
};
|
|
15
|
+
export type ToastOptions = {
|
|
16
|
+
timeout?: number;
|
|
17
|
+
size?: Size;
|
|
18
|
+
};
|
|
19
|
+
/** The live toast list (read in <Toaster>; mutations are reactive). */
|
|
20
|
+
export declare function getToasts(): ToastItem[];
|
|
21
|
+
export declare function dismissToast(id: number): void;
|
|
22
|
+
export declare const toast: {
|
|
23
|
+
show: (variant: ToastVariant, message: string, opts?: ToastOptions) => number;
|
|
24
|
+
danger: (message: string, opts?: ToastOptions) => number;
|
|
25
|
+
success: (message: string, opts?: ToastOptions) => number;
|
|
26
|
+
warning: (message: string, opts?: ToastOptions) => number;
|
|
27
|
+
info: (message: string, opts?: ToastOptions) => number;
|
|
28
|
+
dismiss: typeof dismissToast;
|
|
29
|
+
};
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
const items = $state([]);
|
|
2
|
+
let seq = 0;
|
|
3
|
+
/** The live toast list (read in <Toaster>; mutations are reactive). */
|
|
4
|
+
export function getToasts() {
|
|
5
|
+
return items;
|
|
6
|
+
}
|
|
7
|
+
export function dismissToast(id) {
|
|
8
|
+
const i = items.findIndex((t) => t.id === id);
|
|
9
|
+
if (i >= 0)
|
|
10
|
+
items.splice(i, 1);
|
|
11
|
+
}
|
|
12
|
+
function push(variant, message, opts = {}) {
|
|
13
|
+
const id = ++seq;
|
|
14
|
+
const timeout = opts.timeout ?? 4000;
|
|
15
|
+
items.push({ id, variant, message, size: opts.size ?? "md", timeout });
|
|
16
|
+
if (timeout > 0 && typeof window !== "undefined")
|
|
17
|
+
window.setTimeout(() => dismissToast(id), timeout);
|
|
18
|
+
return id;
|
|
19
|
+
}
|
|
20
|
+
export const toast = {
|
|
21
|
+
show: (variant, message, opts) => push(variant, message, opts),
|
|
22
|
+
danger: (message, opts) => push("danger", message, opts),
|
|
23
|
+
success: (message, opts) => push("success", message, opts),
|
|
24
|
+
warning: (message, opts) => push("warning", message, opts),
|
|
25
|
+
info: (message, opts) => push("info", message, opts),
|
|
26
|
+
dismiss: dismissToast,
|
|
27
|
+
};
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
/**
|
|
3
|
+
* Toggle — a sliding on/off switch (à la a physical switch). Use this, NOT a Checkbox, for binary
|
|
4
|
+
* state: enabled/disabled, active/inactive, on/off. A real `<input type=checkbox>` drives it — laid
|
|
5
|
+
* INVISIBLY over the whole control, so clicking anywhere toggles it (naturally clickable +
|
|
6
|
+
* testable + accessible + form-friendly; `bind:checked` works, and `onchange` fires a real event).
|
|
7
|
+
* Honours the shared {@link Variant} (the "on" colour) + {@link Size} axes. Self-contained: no external
|
|
8
|
+
* label needed — pass an `aria-label` when there's no adjacent text.
|
|
9
|
+
*
|
|
10
|
+
* ALIGNMENT: the visible switch is deliberately shorter than a text control, but the Toggle occupies a
|
|
11
|
+
* wrapper sized to the standard control HEIGHT for its `size` (sm 24 / md 32 / lg 40px — same as Input),
|
|
12
|
+
* with the switch centred inside. So a Toggle dropped next to `size`-matched Inputs/Selects lines up on
|
|
13
|
+
* the same baseline (and the full-height wrapper is the click target), while the switch itself stays
|
|
14
|
+
* switch-sized rather than ballooning to the input height.
|
|
15
|
+
*/
|
|
16
|
+
-->
|
|
17
|
+
<script lang="ts">
|
|
18
|
+
import type { HTMLInputAttributes } from "svelte/elements";
|
|
19
|
+
import type { Size } from "../types/sizes.js";
|
|
20
|
+
import { responsiveClasses, resolveScalar, type Responsive } from "../types/responsive.js";
|
|
21
|
+
import { variantToken, type Variant } from "../types/variants.js";
|
|
22
|
+
|
|
23
|
+
let {
|
|
24
|
+
checked = $bindable(false),
|
|
25
|
+
disabled = false,
|
|
26
|
+
size = "md",
|
|
27
|
+
variant = "primary",
|
|
28
|
+
class: cls = "",
|
|
29
|
+
...rest
|
|
30
|
+
}: {
|
|
31
|
+
checked?: boolean;
|
|
32
|
+
disabled?: boolean;
|
|
33
|
+
size?: Responsive<Size>;
|
|
34
|
+
/** The colour of the "on" state (default primary). */
|
|
35
|
+
variant?: Variant;
|
|
36
|
+
class?: string;
|
|
37
|
+
} & Omit<HTMLInputAttributes, "size" | "type" | "class"> = $props();
|
|
38
|
+
|
|
39
|
+
// Per size: track w/h + knob size are CLASSES (responsive); knob travel is an inline-style transform
|
|
40
|
+
// (JS value → keys off the base tier). Knob sits 2px in, travels W − K − 4px.
|
|
41
|
+
const trackMap: Record<Size, string> = { sm: "h-4 w-7", md: "h-5 w-9", lg: "h-6 w-11" };
|
|
42
|
+
const knobMap: Record<Size, string> = { sm: "size-3", md: "size-4", lg: "size-5" };
|
|
43
|
+
const onMap: Record<Size, string> = {
|
|
44
|
+
sm: "translateX(12px)",
|
|
45
|
+
md: "translateX(16px)",
|
|
46
|
+
lg: "translateX(20px)",
|
|
47
|
+
};
|
|
48
|
+
const heightMap: Record<Size, string> = { sm: "h-6", md: "h-8", lg: "h-10" };
|
|
49
|
+
|
|
50
|
+
const trackCls = $derived(responsiveClasses(size, trackMap));
|
|
51
|
+
const knobCls = $derived(responsiveClasses(size, knobMap));
|
|
52
|
+
// Wrapper height = the standard control height for this size (matches Input's h-6/h-8/h-10), so the
|
|
53
|
+
// switch centres on the same baseline as sibling inputs without growing the switch itself.
|
|
54
|
+
const heightClass = $derived(responsiveClasses(size, heightMap));
|
|
55
|
+
const trackStyle = $derived(
|
|
56
|
+
`background-color: ${checked ? `var(${variantToken[variant]})` : "color-mix(in srgb, var(--ui-color-text) 22%, transparent)"}`,
|
|
57
|
+
);
|
|
58
|
+
// Knob colour = the INVERTED text colour (`--ui-color-background`) with a hint of the variant mixed
|
|
59
|
+
// in — ONE rule for every variant, both themes. Light: background is white → a near-white knob (as
|
|
60
|
+
// before). Dark: background is dark → the knob flips dark, and since the dark theme's accent fills
|
|
61
|
+
// are brighter, a dark knob still reads. The 20% variant tint keeps the knob on-theme; it never
|
|
62
|
+
// approaches the track colour (80% background), so it stays contrasting even on a neutral track.
|
|
63
|
+
const knobColor = $derived(
|
|
64
|
+
`color-mix(in srgb, var(--ui-color-background) 80%, var(${variantToken[variant]}))`,
|
|
65
|
+
);
|
|
66
|
+
const knobStyle = $derived(
|
|
67
|
+
`transform: ${checked ? onMap[resolveScalar(size, "md")] : "translateX(2px)"}; background-color: ${knobColor}`,
|
|
68
|
+
);
|
|
69
|
+
</script>
|
|
70
|
+
|
|
71
|
+
<label
|
|
72
|
+
class="relative inline-flex shrink-0 items-center {heightClass} {disabled
|
|
73
|
+
? 'cursor-not-allowed opacity-50'
|
|
74
|
+
: 'cursor-pointer'} {cls}"
|
|
75
|
+
>
|
|
76
|
+
<!-- Real checkbox, invisible but covering the whole control so a click anywhere toggles it. -->
|
|
77
|
+
<input
|
|
78
|
+
type="checkbox"
|
|
79
|
+
bind:checked
|
|
80
|
+
{disabled}
|
|
81
|
+
class="peer absolute inset-0 z-10 m-0 cursor-pointer opacity-0 disabled:cursor-not-allowed"
|
|
82
|
+
{...rest}
|
|
83
|
+
/>
|
|
84
|
+
<span
|
|
85
|
+
class="relative inline-flex items-center rounded-full transition-colors duration-150 peer-focus-visible:outline peer-focus-visible:outline-2 peer-focus-visible:outline-offset-2 peer-focus-visible:[outline-color:var(--ui-color-primary)] {trackCls}"
|
|
86
|
+
style={trackStyle}
|
|
87
|
+
>
|
|
88
|
+
<span
|
|
89
|
+
class="inline-block rounded-full shadow transition-transform duration-150 {knobCls}"
|
|
90
|
+
style={knobStyle}
|
|
91
|
+
></span>
|
|
92
|
+
</span>
|
|
93
|
+
</label>
|
|
@@ -0,0 +1,15 @@
|
|
|
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 $$ComponentProps = {
|
|
6
|
+
checked?: boolean;
|
|
7
|
+
disabled?: boolean;
|
|
8
|
+
size?: Responsive<Size>;
|
|
9
|
+
/** The colour of the "on" state (default primary). */
|
|
10
|
+
variant?: Variant;
|
|
11
|
+
class?: string;
|
|
12
|
+
} & Omit<HTMLInputAttributes, "size" | "type" | "class">;
|
|
13
|
+
declare const Toggle: import("svelte").Component<$$ComponentProps, {}, "checked">;
|
|
14
|
+
type Toggle = ReturnType<typeof Toggle>;
|
|
15
|
+
export default Toggle;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
export type FilterDef = {
|
|
2
|
+
kind: "text";
|
|
3
|
+
} | {
|
|
4
|
+
kind: "number";
|
|
5
|
+
} | {
|
|
6
|
+
kind: "date";
|
|
7
|
+
} | {
|
|
8
|
+
kind: "boolean";
|
|
9
|
+
} | {
|
|
10
|
+
kind: "enum";
|
|
11
|
+
options: {
|
|
12
|
+
value: string;
|
|
13
|
+
label?: string;
|
|
14
|
+
}[];
|
|
15
|
+
};
|
|
16
|
+
/** A column the query language can filter on (drives the `$` popup + enum-value autocomplete). */
|
|
17
|
+
export type QueryColumn = {
|
|
18
|
+
id: string;
|
|
19
|
+
label?: string;
|
|
20
|
+
filter?: FilterDef;
|
|
21
|
+
};
|
|
22
|
+
/**
|
|
23
|
+
* The minimal surface <Query> needs to drive *any* dataset: the filterable columns + the
|
|
24
|
+
* draft/applied query state. A DataGrid implements this and adds rows/sort/paging/selection.
|
|
25
|
+
*/
|
|
26
|
+
export interface DataSet {
|
|
27
|
+
readonly queryColumns: QueryColumn[];
|
|
28
|
+
/** Draft query text (mid-edit). */
|
|
29
|
+
query: string;
|
|
30
|
+
/** Committed/applied query text. */
|
|
31
|
+
readonly applied: string;
|
|
32
|
+
/** Live parse error for the draft, or null. */
|
|
33
|
+
readonly error: string | null;
|
|
34
|
+
/** Draft differs from applied. */
|
|
35
|
+
readonly dirty: boolean;
|
|
36
|
+
/** Type -> validate, but don't apply/fetch. */
|
|
37
|
+
setDraft(raw: string): void;
|
|
38
|
+
/** Apply the current draft (Enter). */
|
|
39
|
+
commit(): void;
|
|
40
|
+
/** Set + apply immediately (clear / URL seed). */
|
|
41
|
+
setQuery(raw: string): void;
|
|
42
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
export type CompareOp = "eq" | "ne" | "gt" | "gte" | "lt" | "lte" | "contains" | "ncontains" | "starts" | "ends";
|
|
2
|
+
export type Scalar = string | number | boolean;
|
|
3
|
+
/** Explicit value/column cast on a comparison, e.g. `$ip |inet >= 10.0.0.0 |inet`. Storage-agnostic:
|
|
4
|
+
* the query layer only carries the marker; the consumer (`toConditions`) decides the SQL (e.g.
|
|
5
|
+
* Postgres `::inet`). `number`/`string`/`bool` also retype the literal at parse time. */
|
|
6
|
+
export type Cast = "number" | "string" | "bool" | "date" | "inet";
|
|
7
|
+
/** Membership mode: `|=` matches ANY of the operand, `&=` matches ALL (e.g. array contains-all). */
|
|
8
|
+
export type MemberMode = "any" | "all";
|
|
9
|
+
/** One end of an interval. `inclusive` reflects the bracket: `[`/`]` inclusive, `(`/`)` exclusive. */
|
|
10
|
+
export type Bound = {
|
|
11
|
+
value: Scalar;
|
|
12
|
+
inclusive: boolean;
|
|
13
|
+
};
|
|
14
|
+
/** `null` bound = open/unbounded on that side (e.g. `[20..)` -> upper is null). */
|
|
15
|
+
export type Interval = {
|
|
16
|
+
lower: Bound | null;
|
|
17
|
+
upper: Bound | null;
|
|
18
|
+
};
|
|
19
|
+
export type QueryNode = {
|
|
20
|
+
kind: "and";
|
|
21
|
+
children: QueryNode[];
|
|
22
|
+
} | {
|
|
23
|
+
kind: "or";
|
|
24
|
+
children: QueryNode[];
|
|
25
|
+
} | {
|
|
26
|
+
kind: "not";
|
|
27
|
+
child: QueryNode;
|
|
28
|
+
} | {
|
|
29
|
+
kind: "cmp";
|
|
30
|
+
col: string;
|
|
31
|
+
op: CompareOp;
|
|
32
|
+
value: Scalar;
|
|
33
|
+
cast?: Cast;
|
|
34
|
+
} | {
|
|
35
|
+
kind: "match";
|
|
36
|
+
col: string;
|
|
37
|
+
negated: boolean;
|
|
38
|
+
source: string;
|
|
39
|
+
flags: string;
|
|
40
|
+
} | {
|
|
41
|
+
kind: "in";
|
|
42
|
+
col: string;
|
|
43
|
+
mode: MemberMode;
|
|
44
|
+
values: Scalar[];
|
|
45
|
+
cast?: Cast;
|
|
46
|
+
} | {
|
|
47
|
+
kind: "range";
|
|
48
|
+
col: string;
|
|
49
|
+
mode: MemberMode;
|
|
50
|
+
interval: Interval;
|
|
51
|
+
cast?: Cast;
|
|
52
|
+
} | {
|
|
53
|
+
kind: "search";
|
|
54
|
+
text: string;
|
|
55
|
+
};
|
|
56
|
+
export declare class QueryError extends Error {
|
|
57
|
+
/** 0-based character offset into the source where the problem is. */
|
|
58
|
+
position: number;
|
|
59
|
+
constructor(message: string,
|
|
60
|
+
/** 0-based character offset into the source where the problem is. */
|
|
61
|
+
position: number);
|
|
62
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
// AST for the data-table query language. Framework-agnostic — no Svelte/Drizzle/SvelteKit deps,
|
|
2
|
+
// so it runs in the browser (Query input), on the server (toConditions), and in unit tests.
|
|
3
|
+
export class QueryError extends Error {
|
|
4
|
+
position;
|
|
5
|
+
constructor(message,
|
|
6
|
+
/** 0-based character offset into the source where the problem is. */
|
|
7
|
+
position) {
|
|
8
|
+
super(message);
|
|
9
|
+
this.position = position;
|
|
10
|
+
this.name = "QueryError";
|
|
11
|
+
}
|
|
12
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { type CompareOp, type MemberMode } from "./ast";
|
|
2
|
+
export type Token = {
|
|
3
|
+
type: "dollar" | "and" | "or" | "not" | "lparen" | "rparen" | "lbracket" | "rbracket" | "comma" | "dotdot" | "eof";
|
|
4
|
+
start: number;
|
|
5
|
+
end: number;
|
|
6
|
+
} | {
|
|
7
|
+
type: "op";
|
|
8
|
+
op: CompareOp;
|
|
9
|
+
start: number;
|
|
10
|
+
end: number;
|
|
11
|
+
} | {
|
|
12
|
+
type: "member";
|
|
13
|
+
mode: MemberMode;
|
|
14
|
+
start: number;
|
|
15
|
+
end: number;
|
|
16
|
+
} | {
|
|
17
|
+
type: "cast";
|
|
18
|
+
name: string;
|
|
19
|
+
start: number;
|
|
20
|
+
end: number;
|
|
21
|
+
} | {
|
|
22
|
+
type: "word" | "string";
|
|
23
|
+
value: string;
|
|
24
|
+
start: number;
|
|
25
|
+
end: number;
|
|
26
|
+
} | {
|
|
27
|
+
type: "regex";
|
|
28
|
+
source: string;
|
|
29
|
+
flags: string;
|
|
30
|
+
start: number;
|
|
31
|
+
end: number;
|
|
32
|
+
};
|
|
33
|
+
export declare function lex(input: string): Token[];
|
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
import { QueryError } from "./ast";
|
|
2
|
+
const isSpace = (c) => c === " " || c === "\t" || c === "\n" || c === "\r" || c === "\f" || c === "\v";
|
|
3
|
+
// Structural characters that break a bare token. Everything else (letters, digits, and ordinary
|
|
4
|
+
// punctuation like @ . / : % # + - _ * ?) is allowed unquoted. `/` is allowed *inside* a word
|
|
5
|
+
// (cat/sub) but starts a regex when it leads a token; a word also breaks at `..`.
|
|
6
|
+
const BREAK = new Set(['"', "(", ")", "[", "]", ",", "$", "^", "~", "!", "=", ">", "<", "&", "|"]);
|
|
7
|
+
const isWordChar = (c) => !isSpace(c) && !BREAK.has(c);
|
|
8
|
+
export function lex(input) {
|
|
9
|
+
const tokens = [];
|
|
10
|
+
const n = input.length;
|
|
11
|
+
let i = 0;
|
|
12
|
+
while (i < n) {
|
|
13
|
+
const c = input[i];
|
|
14
|
+
if (isSpace(c)) {
|
|
15
|
+
i++;
|
|
16
|
+
continue;
|
|
17
|
+
}
|
|
18
|
+
const start = i;
|
|
19
|
+
const next = input[i + 1];
|
|
20
|
+
switch (c) {
|
|
21
|
+
case "(":
|
|
22
|
+
i++;
|
|
23
|
+
tokens.push({ type: "lparen", start, end: i });
|
|
24
|
+
continue;
|
|
25
|
+
case ")":
|
|
26
|
+
i++;
|
|
27
|
+
tokens.push({ type: "rparen", start, end: i });
|
|
28
|
+
continue;
|
|
29
|
+
case "[":
|
|
30
|
+
i++;
|
|
31
|
+
tokens.push({ type: "lbracket", start, end: i });
|
|
32
|
+
continue;
|
|
33
|
+
case "]":
|
|
34
|
+
i++;
|
|
35
|
+
tokens.push({ type: "rbracket", start, end: i });
|
|
36
|
+
continue;
|
|
37
|
+
case ",":
|
|
38
|
+
i++;
|
|
39
|
+
tokens.push({ type: "comma", start, end: i });
|
|
40
|
+
continue;
|
|
41
|
+
case '"': {
|
|
42
|
+
i++;
|
|
43
|
+
let s = "";
|
|
44
|
+
while (i < n && input[i] !== '"') {
|
|
45
|
+
if (input[i] === "\\" && i + 1 < n) {
|
|
46
|
+
i++;
|
|
47
|
+
s += input[i];
|
|
48
|
+
i++;
|
|
49
|
+
}
|
|
50
|
+
else {
|
|
51
|
+
s += input[i];
|
|
52
|
+
i++;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
if (i >= n)
|
|
56
|
+
throw new QueryError("Unterminated string", start);
|
|
57
|
+
i++;
|
|
58
|
+
tokens.push({ type: "string", value: s, start, end: i });
|
|
59
|
+
continue;
|
|
60
|
+
}
|
|
61
|
+
case "/": {
|
|
62
|
+
// regex literal: /source/flags (escapes preserved in source)
|
|
63
|
+
i++;
|
|
64
|
+
let src = "";
|
|
65
|
+
while (i < n && input[i] !== "/") {
|
|
66
|
+
if (input[i] === "\\" && i + 1 < n) {
|
|
67
|
+
src += input[i] + input[i + 1];
|
|
68
|
+
i += 2;
|
|
69
|
+
}
|
|
70
|
+
else {
|
|
71
|
+
src += input[i];
|
|
72
|
+
i++;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
if (i >= n)
|
|
76
|
+
throw new QueryError("Unterminated regex", start);
|
|
77
|
+
i++; // closing /
|
|
78
|
+
let flags = "";
|
|
79
|
+
while (i < n && /[a-zA-Z]/.test(input[i])) {
|
|
80
|
+
flags += input[i];
|
|
81
|
+
i++;
|
|
82
|
+
}
|
|
83
|
+
tokens.push({ type: "regex", source: src, flags, start, end: i });
|
|
84
|
+
continue;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
if (c === "&") {
|
|
88
|
+
if (next === "&") {
|
|
89
|
+
i += 2;
|
|
90
|
+
tokens.push({ type: "and", start, end: i });
|
|
91
|
+
}
|
|
92
|
+
else if (next === "=") {
|
|
93
|
+
i += 2;
|
|
94
|
+
tokens.push({ type: "member", mode: "all", start, end: i });
|
|
95
|
+
}
|
|
96
|
+
else
|
|
97
|
+
throw new QueryError("Expected '&&' or '&='", i);
|
|
98
|
+
continue;
|
|
99
|
+
}
|
|
100
|
+
if (c === "|") {
|
|
101
|
+
if (next === "|") {
|
|
102
|
+
i += 2;
|
|
103
|
+
tokens.push({ type: "or", start, end: i });
|
|
104
|
+
}
|
|
105
|
+
else if (next === "=") {
|
|
106
|
+
i += 2;
|
|
107
|
+
tokens.push({ type: "member", mode: "any", start, end: i });
|
|
108
|
+
}
|
|
109
|
+
else if (next && /[a-zA-Z]/.test(next)) {
|
|
110
|
+
// `|word` is a cast/transformer (|inet, |number, …); validated in the parser.
|
|
111
|
+
i++;
|
|
112
|
+
let name = "";
|
|
113
|
+
while (i < n && /[a-zA-Z]/.test(input[i])) {
|
|
114
|
+
name += input[i];
|
|
115
|
+
i++;
|
|
116
|
+
}
|
|
117
|
+
tokens.push({ type: "cast", name, start, end: i });
|
|
118
|
+
}
|
|
119
|
+
else
|
|
120
|
+
throw new QueryError("Expected '||', '|=', or a cast (e.g. |inet)", i);
|
|
121
|
+
continue;
|
|
122
|
+
}
|
|
123
|
+
if (c === "!") {
|
|
124
|
+
if (next === "=") {
|
|
125
|
+
i += 2;
|
|
126
|
+
tokens.push({ type: "op", op: "ne", start, end: i });
|
|
127
|
+
}
|
|
128
|
+
else if (next === "~") {
|
|
129
|
+
i += 2;
|
|
130
|
+
tokens.push({ type: "op", op: "ncontains", start, end: i });
|
|
131
|
+
}
|
|
132
|
+
else {
|
|
133
|
+
i++;
|
|
134
|
+
tokens.push({ type: "not", start, end: i });
|
|
135
|
+
}
|
|
136
|
+
continue;
|
|
137
|
+
}
|
|
138
|
+
if (c === "=") {
|
|
139
|
+
if (next === "=") {
|
|
140
|
+
i += 2;
|
|
141
|
+
tokens.push({ type: "op", op: "eq", start, end: i });
|
|
142
|
+
}
|
|
143
|
+
else
|
|
144
|
+
throw new QueryError("Expected '=='", i);
|
|
145
|
+
continue;
|
|
146
|
+
}
|
|
147
|
+
if (c === ">") {
|
|
148
|
+
if (next === "=") {
|
|
149
|
+
i += 2;
|
|
150
|
+
tokens.push({ type: "op", op: "gte", start, end: i });
|
|
151
|
+
}
|
|
152
|
+
else {
|
|
153
|
+
i++;
|
|
154
|
+
tokens.push({ type: "op", op: "gt", start, end: i });
|
|
155
|
+
}
|
|
156
|
+
continue;
|
|
157
|
+
}
|
|
158
|
+
if (c === "<") {
|
|
159
|
+
if (next === "=") {
|
|
160
|
+
i += 2;
|
|
161
|
+
tokens.push({ type: "op", op: "lte", start, end: i });
|
|
162
|
+
}
|
|
163
|
+
else {
|
|
164
|
+
i++;
|
|
165
|
+
tokens.push({ type: "op", op: "lt", start, end: i });
|
|
166
|
+
}
|
|
167
|
+
continue;
|
|
168
|
+
}
|
|
169
|
+
if (c === "~") {
|
|
170
|
+
if (next === "=") {
|
|
171
|
+
i += 2;
|
|
172
|
+
tokens.push({ type: "op", op: "contains", start, end: i });
|
|
173
|
+
}
|
|
174
|
+
else
|
|
175
|
+
throw new QueryError("Expected '~='", i);
|
|
176
|
+
continue;
|
|
177
|
+
}
|
|
178
|
+
if (c === "^") {
|
|
179
|
+
if (next === "=") {
|
|
180
|
+
i += 2;
|
|
181
|
+
tokens.push({ type: "op", op: "starts", start, end: i });
|
|
182
|
+
}
|
|
183
|
+
else
|
|
184
|
+
throw new QueryError("Expected '^='", i);
|
|
185
|
+
continue;
|
|
186
|
+
}
|
|
187
|
+
if (c === "$") {
|
|
188
|
+
// `$=` is the ends-with operator; otherwise `$` is the clause sigil.
|
|
189
|
+
if (next === "=") {
|
|
190
|
+
i += 2;
|
|
191
|
+
tokens.push({ type: "op", op: "ends", start, end: i });
|
|
192
|
+
}
|
|
193
|
+
else {
|
|
194
|
+
i++;
|
|
195
|
+
tokens.push({ type: "dollar", start, end: i });
|
|
196
|
+
}
|
|
197
|
+
continue;
|
|
198
|
+
}
|
|
199
|
+
if (c === "." && next === ".") {
|
|
200
|
+
i += 2;
|
|
201
|
+
tokens.push({ type: "dotdot", start, end: i });
|
|
202
|
+
continue;
|
|
203
|
+
}
|
|
204
|
+
// a single '.' is an ordinary word char (.com, 99.5) — falls through to the word branch.
|
|
205
|
+
if (isWordChar(c)) {
|
|
206
|
+
let w = "";
|
|
207
|
+
while (i < n) {
|
|
208
|
+
const ch = input[i];
|
|
209
|
+
if (ch === "." && input[i + 1] === ".")
|
|
210
|
+
break; // '..' is the range operator
|
|
211
|
+
if (isWordChar(ch)) {
|
|
212
|
+
w += ch;
|
|
213
|
+
i++;
|
|
214
|
+
}
|
|
215
|
+
else
|
|
216
|
+
break;
|
|
217
|
+
}
|
|
218
|
+
tokens.push({ type: "word", value: w, start, end: i });
|
|
219
|
+
continue;
|
|
220
|
+
}
|
|
221
|
+
throw new QueryError(`Unexpected character '${c}'`, i);
|
|
222
|
+
}
|
|
223
|
+
tokens.push({ type: "eof", start: i, end: i });
|
|
224
|
+
return tokens;
|
|
225
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { type QueryNode } from "./ast";
|
|
2
|
+
export declare function parseQuery(input: string): QueryNode | null;
|
|
3
|
+
export type ParseResult = {
|
|
4
|
+
ok: true;
|
|
5
|
+
ast: QueryNode | null;
|
|
6
|
+
} | {
|
|
7
|
+
ok: false;
|
|
8
|
+
error: string;
|
|
9
|
+
position: number;
|
|
10
|
+
};
|
|
11
|
+
export declare function tryParseQuery(input: string): ParseResult;
|