@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,140 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
/**
|
|
3
|
+
* Popup — an interactive, click/programmatically-opened popover anchored to a trigger.
|
|
4
|
+
*
|
|
5
|
+
* Uses the Popover API (browser top layer, so it's never clipped by overflow/stacking contexts)
|
|
6
|
+
* for show/hide, and the shared `anchored` engine for placement (flip up/down vs the boundary —
|
|
7
|
+
* viewport by default, or a container). Dismisses on Escape and outside pointerdown.
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* ```svelte
|
|
11
|
+
* <button bind:this={btn} onclick={() => open = !open}>Menu</button>
|
|
12
|
+
* <Popup {anchor} bind:open>
|
|
13
|
+
* <div class="...your content...">…</div>
|
|
14
|
+
* </Popup>
|
|
15
|
+
* ```
|
|
16
|
+
*/
|
|
17
|
+
-->
|
|
18
|
+
<script lang="ts">
|
|
19
|
+
import type { Snippet } from "svelte";
|
|
20
|
+
import {
|
|
21
|
+
anchored,
|
|
22
|
+
type Side,
|
|
23
|
+
type Align,
|
|
24
|
+
type Boundary,
|
|
25
|
+
type Placement,
|
|
26
|
+
} from "../positioning/anchored.js";
|
|
27
|
+
import type { Size } from "../types/sizes.js";
|
|
28
|
+
import { responsiveClasses, type Responsive } from "../types/responsive.js";
|
|
29
|
+
import { variantToken, type Variant } from "../types/variants.js";
|
|
30
|
+
|
|
31
|
+
let {
|
|
32
|
+
anchor,
|
|
33
|
+
open = $bindable(false),
|
|
34
|
+
side = "bottom",
|
|
35
|
+
align = "start",
|
|
36
|
+
boundary = "viewport",
|
|
37
|
+
offset = 4,
|
|
38
|
+
matchWidth = false,
|
|
39
|
+
surface = false,
|
|
40
|
+
variant,
|
|
41
|
+
size = "md",
|
|
42
|
+
borderless = false,
|
|
43
|
+
onclose,
|
|
44
|
+
children,
|
|
45
|
+
}: {
|
|
46
|
+
/** The trigger element to position against. */
|
|
47
|
+
anchor: HTMLElement | undefined;
|
|
48
|
+
open?: boolean;
|
|
49
|
+
side?: Side;
|
|
50
|
+
align?: Align;
|
|
51
|
+
/** Default the viewport; pass a container element or CSS selector to bound flipping to it. */
|
|
52
|
+
boundary?: Boundary;
|
|
53
|
+
offset?: number;
|
|
54
|
+
matchWidth?: boolean;
|
|
55
|
+
/** Render a themed floating PANEL around the children (background, border, padding, shadow) instead
|
|
56
|
+
* of the default bare positioner. `variant`/`size`/`borderless` configure that panel. Consumers
|
|
57
|
+
* that supply their own surface (Menu, date/time pickers) leave this `false`. */
|
|
58
|
+
surface?: boolean;
|
|
59
|
+
/** Panel border colour (when `surface`) — tinted by the variant token, like Card. */
|
|
60
|
+
variant?: Variant;
|
|
61
|
+
/** Panel padding tier (when `surface`). */
|
|
62
|
+
size?: Responsive<Size>;
|
|
63
|
+
/** Drop the panel's visible border (border = its own background). */
|
|
64
|
+
borderless?: boolean;
|
|
65
|
+
onclose?: () => void;
|
|
66
|
+
children: Snippet;
|
|
67
|
+
} = $props();
|
|
68
|
+
|
|
69
|
+
// Themed-panel styling (only used when `surface`). Border tints from the variant token; `borderless`
|
|
70
|
+
// paints it the panel's background. Padding scales with `size`.
|
|
71
|
+
const padMap: Record<Size, string> = { sm: "p-1", md: "p-1.5", lg: "p-2" };
|
|
72
|
+
const accentToken = $derived(variant ? variantToken[variant] : "--ui-color-secondary");
|
|
73
|
+
const panelBorder = $derived(
|
|
74
|
+
borderless
|
|
75
|
+
? "var(--ui-color-background)"
|
|
76
|
+
: `color-mix(in srgb, var(${accentToken}) var(--ui-tint-border), transparent)`,
|
|
77
|
+
);
|
|
78
|
+
const surfaceClass = $derived(
|
|
79
|
+
surface
|
|
80
|
+
? `rounded-md border shadow-lg ${responsiveClasses(size, padMap)} [background-color:var(--ui-color-background)] [color:var(--ui-color-text)]`
|
|
81
|
+
: "bg-transparent p-0 border-0",
|
|
82
|
+
);
|
|
83
|
+
|
|
84
|
+
let el = $state<HTMLElement>();
|
|
85
|
+
let placed = $state<Placement | null>(null);
|
|
86
|
+
|
|
87
|
+
function close() {
|
|
88
|
+
open = false;
|
|
89
|
+
onclose?.();
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Drive the Popover API (top layer) from `open`.
|
|
93
|
+
$effect(() => {
|
|
94
|
+
if (!el) return;
|
|
95
|
+
const isOpen = el.matches(":popover-open");
|
|
96
|
+
if (open && !isOpen) el.showPopover();
|
|
97
|
+
else if (!open && isOpen) el.hidePopover();
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
// Dismiss on Escape / outside pointerdown while open.
|
|
101
|
+
$effect(() => {
|
|
102
|
+
if (!open) return;
|
|
103
|
+
const onKey = (e: KeyboardEvent) => {
|
|
104
|
+
if (e.key === "Escape") {
|
|
105
|
+
e.preventDefault();
|
|
106
|
+
close();
|
|
107
|
+
}
|
|
108
|
+
};
|
|
109
|
+
const onPointer = (e: PointerEvent) => {
|
|
110
|
+
const t = e.target as Node;
|
|
111
|
+
if (el?.contains(t) || anchor?.contains(t)) return;
|
|
112
|
+
close();
|
|
113
|
+
};
|
|
114
|
+
document.addEventListener("keydown", onKey, true);
|
|
115
|
+
document.addEventListener("pointerdown", onPointer, true);
|
|
116
|
+
return () => {
|
|
117
|
+
document.removeEventListener("keydown", onKey, true);
|
|
118
|
+
document.removeEventListener("pointerdown", onPointer, true);
|
|
119
|
+
};
|
|
120
|
+
});
|
|
121
|
+
</script>
|
|
122
|
+
|
|
123
|
+
<div
|
|
124
|
+
bind:this={el}
|
|
125
|
+
popover="manual"
|
|
126
|
+
class="m-0 overflow-visible {surfaceClass}"
|
|
127
|
+
style={surface ? `border-color: ${panelBorder};` : ""}
|
|
128
|
+
use:anchored={{
|
|
129
|
+
anchor,
|
|
130
|
+
enabled: open,
|
|
131
|
+
side,
|
|
132
|
+
align,
|
|
133
|
+
boundary,
|
|
134
|
+
offset,
|
|
135
|
+
matchWidth,
|
|
136
|
+
onplaced: (p) => (placed = p),
|
|
137
|
+
}}
|
|
138
|
+
>
|
|
139
|
+
{@render children()}
|
|
140
|
+
</div>
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { Snippet } from "svelte";
|
|
2
|
+
import { type Side, type Align, type Boundary } from "../positioning/anchored.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
|
+
type $$ComponentProps = {
|
|
7
|
+
/** The trigger element to position against. */
|
|
8
|
+
anchor: HTMLElement | undefined;
|
|
9
|
+
open?: boolean;
|
|
10
|
+
side?: Side;
|
|
11
|
+
align?: Align;
|
|
12
|
+
/** Default the viewport; pass a container element or CSS selector to bound flipping to it. */
|
|
13
|
+
boundary?: Boundary;
|
|
14
|
+
offset?: number;
|
|
15
|
+
matchWidth?: boolean;
|
|
16
|
+
/** Render a themed floating PANEL around the children (background, border, padding, shadow) instead
|
|
17
|
+
* of the default bare positioner. `variant`/`size`/`borderless` configure that panel. Consumers
|
|
18
|
+
* that supply their own surface (Menu, date/time pickers) leave this `false`. */
|
|
19
|
+
surface?: boolean;
|
|
20
|
+
/** Panel border colour (when `surface`) — tinted by the variant token, like Card. */
|
|
21
|
+
variant?: Variant;
|
|
22
|
+
/** Panel padding tier (when `surface`). */
|
|
23
|
+
size?: Responsive<Size>;
|
|
24
|
+
/** Drop the panel's visible border (border = its own background). */
|
|
25
|
+
borderless?: boolean;
|
|
26
|
+
onclose?: () => void;
|
|
27
|
+
children: Snippet;
|
|
28
|
+
};
|
|
29
|
+
declare const Popup: import("svelte").Component<$$ComponentProps, {}, "open">;
|
|
30
|
+
type Popup = ReturnType<typeof Popup>;
|
|
31
|
+
export default Popup;
|
|
@@ -0,0 +1,284 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
/**
|
|
3
|
+
* Query — a filter bar over a DataSet (svelte-ui/data): a monospace input with `$` column +
|
|
4
|
+
* enum-value autocomplete (in a Popup, so it flips vs the viewport), Enter-to-apply, and a help
|
|
5
|
+
* dialog. URL sync is intentionally NOT built in — observe `dataset.applied` and sync it yourself
|
|
6
|
+
* (it's framework-specific; e.g. SvelteKit's replaceState).
|
|
7
|
+
*/
|
|
8
|
+
-->
|
|
9
|
+
<script lang="ts">
|
|
10
|
+
import { tick } from "svelte";
|
|
11
|
+
import IconSearch from "~icons/mdi/magnify";
|
|
12
|
+
import IconClose from "~icons/mdi/close";
|
|
13
|
+
import IconHelp from "~icons/mdi/help-circle-outline";
|
|
14
|
+
import Input from "./Input.svelte";
|
|
15
|
+
import Button from "./Button.svelte";
|
|
16
|
+
import Popup from "./Popup.svelte";
|
|
17
|
+
import Dialog from "./Dialog.svelte";
|
|
18
|
+
import type { DataSet } from "../data/dataset.js";
|
|
19
|
+
import type { Size } from "../types/sizes.js";
|
|
20
|
+
import type { Responsive } from "../types/responsive.js";
|
|
21
|
+
import type { Variant } from "../types/variants.js";
|
|
22
|
+
|
|
23
|
+
let {
|
|
24
|
+
dataset,
|
|
25
|
+
placeholder = "Filter… e.g. $status == active && $price |= [20..50)",
|
|
26
|
+
size = "md",
|
|
27
|
+
variant = "secondary",
|
|
28
|
+
borderless = false,
|
|
29
|
+
}: {
|
|
30
|
+
dataset: DataSet;
|
|
31
|
+
placeholder?: string;
|
|
32
|
+
/** Shared axes — forwarded to the underlying filter Input. */
|
|
33
|
+
size?: Responsive<Size>;
|
|
34
|
+
variant?: Variant;
|
|
35
|
+
borderless?: boolean;
|
|
36
|
+
} = $props();
|
|
37
|
+
|
|
38
|
+
let inputEl = $state<HTMLInputElement>();
|
|
39
|
+
let wrapperEl = $state<HTMLElement>();
|
|
40
|
+
let helpOpen = $state(false);
|
|
41
|
+
let caret = $state(0);
|
|
42
|
+
let focused = $state(false);
|
|
43
|
+
let open = $state(false);
|
|
44
|
+
let active = $state(0);
|
|
45
|
+
|
|
46
|
+
type Suggestion = { value: string; label: string };
|
|
47
|
+
|
|
48
|
+
const filterCols = $derived(dataset.queryColumns.filter((c) => c.filter));
|
|
49
|
+
const TOKEN = /[A-Za-z0-9_.$:-]/;
|
|
50
|
+
const VALUE_OP =
|
|
51
|
+
/\$([A-Za-z0-9_]+)\s*(?:==|!=|>=|<=|>|<|~=|!~|\^=|\$=|\|=|&=)\s*\[?\s*(?:[^[\],]+,\s*)*$/;
|
|
52
|
+
|
|
53
|
+
function detect(text: string, pos: number) {
|
|
54
|
+
let i = pos;
|
|
55
|
+
while (i > 0 && TOKEN.test(text[i - 1])) i--;
|
|
56
|
+
const token = text.slice(i, pos);
|
|
57
|
+
if (token.startsWith("$"))
|
|
58
|
+
return { type: "column" as const, start: i, partial: token.slice(1) };
|
|
59
|
+
const m = text.slice(0, i).match(VALUE_OP);
|
|
60
|
+
if (m) return { type: "value" as const, start: i, partial: token, col: m[1] };
|
|
61
|
+
return { type: "none" as const, start: pos, partial: "" };
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const ctx = $derived(detect(dataset.query, caret));
|
|
65
|
+
const suggestions = $derived.by<Suggestion[]>(() => {
|
|
66
|
+
if (ctx.type === "column") {
|
|
67
|
+
const p = ctx.partial.toLowerCase();
|
|
68
|
+
return filterCols
|
|
69
|
+
.filter((c) => c.id.toLowerCase().startsWith(p))
|
|
70
|
+
.map((c) => ({ value: c.id, label: c.label ?? c.id }));
|
|
71
|
+
}
|
|
72
|
+
if (ctx.type === "value") {
|
|
73
|
+
const col = dataset.queryColumns.find((c) => c.id === ctx.col);
|
|
74
|
+
if (col?.filter?.kind === "enum") {
|
|
75
|
+
const p = ctx.partial.toLowerCase();
|
|
76
|
+
return col.filter.options
|
|
77
|
+
.filter((o) => o.value.toLowerCase().startsWith(p))
|
|
78
|
+
.map((o) => ({ value: o.value, label: o.label ?? o.value }));
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
return [];
|
|
82
|
+
});
|
|
83
|
+
const showPopup = $derived(open && focused && suggestions.length > 0);
|
|
84
|
+
|
|
85
|
+
const syncCaret = () => (caret = inputEl?.selectionStart ?? dataset.query.length);
|
|
86
|
+
|
|
87
|
+
function oninput(e: Event & { currentTarget: HTMLInputElement }) {
|
|
88
|
+
dataset.setDraft(e.currentTarget.value);
|
|
89
|
+
caret = e.currentTarget.selectionStart ?? 0;
|
|
90
|
+
active = 0;
|
|
91
|
+
open = true;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
async function accept(s: Suggestion) {
|
|
95
|
+
const insert = ctx.type === "column" ? `$${s.value} ` : `${s.value} `;
|
|
96
|
+
const next = dataset.query.slice(0, ctx.start) + insert + dataset.query.slice(caret);
|
|
97
|
+
const pos = ctx.start + insert.length;
|
|
98
|
+
dataset.setDraft(next);
|
|
99
|
+
open = false;
|
|
100
|
+
await tick();
|
|
101
|
+
if (inputEl) {
|
|
102
|
+
inputEl.focus();
|
|
103
|
+
inputEl.selectionStart = inputEl.selectionEnd = pos;
|
|
104
|
+
caret = pos;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function onkeydown(e: KeyboardEvent) {
|
|
109
|
+
if (showPopup) {
|
|
110
|
+
if (e.key === "ArrowDown")
|
|
111
|
+
return (e.preventDefault(), void (active = (active + 1) % suggestions.length));
|
|
112
|
+
if (e.key === "ArrowUp")
|
|
113
|
+
return (
|
|
114
|
+
e.preventDefault(),
|
|
115
|
+
void (active = (active - 1 + suggestions.length) % suggestions.length)
|
|
116
|
+
);
|
|
117
|
+
if (e.key === "Enter" || e.key === "Tab")
|
|
118
|
+
return (e.preventDefault(), accept(suggestions[active]));
|
|
119
|
+
if (e.key === "Escape") return (e.preventDefault(), void (open = false));
|
|
120
|
+
} else if (e.key === "Enter") {
|
|
121
|
+
e.preventDefault();
|
|
122
|
+
dataset.commit();
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function clear() {
|
|
127
|
+
dataset.setQuery("");
|
|
128
|
+
inputEl?.focus();
|
|
129
|
+
caret = 0;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const bc = "border-[color-mix(in_srgb,var(--ui-color-secondary)_30%,transparent)]";
|
|
133
|
+
</script>
|
|
134
|
+
|
|
135
|
+
<div class="w-full [color:var(--ui-color-text)]">
|
|
136
|
+
<!-- Built on the core <Input> so the frame/border/hover/focus glow + themed autofill are shared,
|
|
137
|
+
not re-rolled. Search is the left icon; clear + help live in the `actions` slot; the parse
|
|
138
|
+
error reuses Input's own validation (danger) styling. -->
|
|
139
|
+
<div bind:this={wrapperEl} class="w-full">
|
|
140
|
+
<Input
|
|
141
|
+
bind:element={inputEl}
|
|
142
|
+
value={dataset.query}
|
|
143
|
+
{size}
|
|
144
|
+
{variant}
|
|
145
|
+
{borderless}
|
|
146
|
+
iconPosition="left"
|
|
147
|
+
inputClass="font-mono"
|
|
148
|
+
spellcheck={false}
|
|
149
|
+
autocapitalize="none"
|
|
150
|
+
autocomplete="off"
|
|
151
|
+
{placeholder}
|
|
152
|
+
validate={() => dataset.error || true}
|
|
153
|
+
touched={!!dataset.error}
|
|
154
|
+
{oninput}
|
|
155
|
+
{onkeydown}
|
|
156
|
+
onkeyup={syncCaret}
|
|
157
|
+
onclick={syncCaret}
|
|
158
|
+
onfocus={() => {
|
|
159
|
+
focused = true;
|
|
160
|
+
open = true;
|
|
161
|
+
}}
|
|
162
|
+
onblur={() => {
|
|
163
|
+
focused = false;
|
|
164
|
+
setTimeout(() => (open = false), 120);
|
|
165
|
+
}}
|
|
166
|
+
>
|
|
167
|
+
{#snippet icon()}<IconSearch class="opacity-60" />{/snippet}
|
|
168
|
+
{#snippet actions()}
|
|
169
|
+
{#if dataset.query}
|
|
170
|
+
<Button variant="ghost" size="sm" aria-label="Clear" title="Clear" onclick={clear}
|
|
171
|
+
>{#snippet icon()}<IconClose />{/snippet}</Button
|
|
172
|
+
>
|
|
173
|
+
{/if}
|
|
174
|
+
<Button
|
|
175
|
+
variant="ghost"
|
|
176
|
+
size="sm"
|
|
177
|
+
aria-label="Query help"
|
|
178
|
+
title="Query help"
|
|
179
|
+
onclick={() => (helpOpen = true)}>{#snippet icon()}<IconHelp />{/snippet}</Button
|
|
180
|
+
>
|
|
181
|
+
{/snippet}
|
|
182
|
+
</Input>
|
|
183
|
+
</div>
|
|
184
|
+
|
|
185
|
+
{#if dataset.error}
|
|
186
|
+
<p class="mt-1 text-xs [color:var(--ui-color-error)]">{dataset.error}</p>
|
|
187
|
+
{:else if dataset.dirty}
|
|
188
|
+
<p class="mt-1 text-xs [color:color-mix(in_srgb,var(--ui-color-text)_50%,transparent)]">
|
|
189
|
+
Press <kbd class="rounded border px-1 {bc}">Enter</kbd> to apply
|
|
190
|
+
</p>
|
|
191
|
+
{/if}
|
|
192
|
+
</div>
|
|
193
|
+
|
|
194
|
+
<Popup
|
|
195
|
+
anchor={wrapperEl}
|
|
196
|
+
open={showPopup}
|
|
197
|
+
onclose={() => (open = false)}
|
|
198
|
+
side="bottom"
|
|
199
|
+
align="start"
|
|
200
|
+
matchWidth
|
|
201
|
+
>
|
|
202
|
+
<ul
|
|
203
|
+
class="mt-1 max-h-72 overflow-y-auto rounded-md border p-1 shadow-lg [background-color:var(--ui-color-background)] {bc}"
|
|
204
|
+
>
|
|
205
|
+
{#each suggestions as s, i (s.value)}
|
|
206
|
+
<li>
|
|
207
|
+
<button
|
|
208
|
+
type="button"
|
|
209
|
+
class="flex w-full items-center justify-between gap-3 rounded px-2 py-1.5 text-left text-sm [color:var(--ui-color-text)] {i ===
|
|
210
|
+
active
|
|
211
|
+
? '[background-color:color-mix(in_srgb,var(--ui-color-text)_10%,transparent)]'
|
|
212
|
+
: ''}"
|
|
213
|
+
onmouseenter={() => (active = i)}
|
|
214
|
+
onmousedown={(e) => {
|
|
215
|
+
e.preventDefault();
|
|
216
|
+
accept(s);
|
|
217
|
+
}}
|
|
218
|
+
>
|
|
219
|
+
<span class="font-mono">{ctx.type === "column" ? "$" : ""}{s.value}</span>
|
|
220
|
+
{#if s.label !== s.value}<span
|
|
221
|
+
class="text-xs [color:color-mix(in_srgb,var(--ui-color-text)_60%,transparent)]"
|
|
222
|
+
>{s.label}</span
|
|
223
|
+
>{/if}
|
|
224
|
+
</button>
|
|
225
|
+
</li>
|
|
226
|
+
{/each}
|
|
227
|
+
</ul>
|
|
228
|
+
</Popup>
|
|
229
|
+
|
|
230
|
+
<Dialog bind:isOpen={helpOpen} title="Query syntax" showConfirm={false} showCancel={false}>
|
|
231
|
+
<div class="max-h-[70vh] space-y-4 overflow-y-auto text-sm [color:var(--ui-color-text)]">
|
|
232
|
+
<p>
|
|
233
|
+
A filter is one or more conditions, <code class="font-mono">$column operator value</code>.
|
|
234
|
+
Whitespace is insignificant; combine with <code>&&</code>, <code>||</code>, <code>!</code>,
|
|
235
|
+
<code>()</code>.
|
|
236
|
+
</p>
|
|
237
|
+
<div>
|
|
238
|
+
<h4 class="mb-1 font-semibold">Operators</h4>
|
|
239
|
+
<table class="w-full text-left">
|
|
240
|
+
<tbody class="[&_td]:py-0.5 [&_td:first-child]:pr-4 [&_td:first-child]:font-mono">
|
|
241
|
+
<tr><td>== !=</td><td>equals / not equals</td></tr>
|
|
242
|
+
<tr><td>{"> >= < <="}</td><td>greater / less than (numbers & dates)</td></tr>
|
|
243
|
+
<tr><td>~= !~</td><td>contains / not — or regex if value is <code>/…/</code></td></tr>
|
|
244
|
+
<tr><td>^= $=</td><td>starts / ends with</td></tr>
|
|
245
|
+
<tr><td>|=</td><td>any of a set <code>[a, b]</code> or range <code>[1..9)</code></td></tr>
|
|
246
|
+
<tr><td>&=</td><td>all of a set (multi-value columns)</td></tr>
|
|
247
|
+
</tbody>
|
|
248
|
+
</table>
|
|
249
|
+
</div>
|
|
250
|
+
<div>
|
|
251
|
+
<h4 class="mb-1 font-semibold">Casts</h4>
|
|
252
|
+
<p class="mb-1 text-xs [color:color-mix(in_srgb,var(--ui-color-text)_70%,transparent)]">
|
|
253
|
+
A <code class="font-mono">|type</code> after a column or value forces its type for strict comparison
|
|
254
|
+
— one cast per clause; put it on the column and/or the literals.
|
|
255
|
+
</p>
|
|
256
|
+
<table class="w-full text-left">
|
|
257
|
+
<tbody class="[&_td]:py-0.5 [&_td:first-child]:pr-4 [&_td:first-child]:font-mono">
|
|
258
|
+
<tr
|
|
259
|
+
><td>|number |string |bool</td><td
|
|
260
|
+
>force the literal's type (e.g. <code>007|string</code>)</td
|
|
261
|
+
></tr
|
|
262
|
+
>
|
|
263
|
+
<tr><td>|date</td><td>compare as a date</td></tr>
|
|
264
|
+
<tr><td>|inet</td><td>compare as an IP address (IPv4 / IPv6)</td></tr>
|
|
265
|
+
</tbody>
|
|
266
|
+
</table>
|
|
267
|
+
</div>
|
|
268
|
+
<div>
|
|
269
|
+
<h4 class="mb-1 font-semibold">Examples</h4>
|
|
270
|
+
<pre
|
|
271
|
+
class="overflow-x-auto rounded p-2 font-mono text-xs [background-color:color-mix(in_srgb,var(--ui-color-text)_6%,transparent)]"><code
|
|
272
|
+
>$status == active
|
|
273
|
+
$email ~= /@gmail\.com$/i
|
|
274
|
+
$price |= [20..50) && $role |= [admin, ops]
|
|
275
|
+
$ip|inet |= [10.0.0.0|inet..10.255.255.255|inet]
|
|
276
|
+
"lemon balm" || $featured == true</code
|
|
277
|
+
></pre>
|
|
278
|
+
</div>
|
|
279
|
+
<p class="text-xs [color:color-mix(in_srgb,var(--ui-color-text)_60%,transparent)]">
|
|
280
|
+
Tip: type <kbd class="rounded border px-1 {bc}">$</kbd> for columns; enum columns suggest
|
|
281
|
+
values. Applies on <kbd class="rounded border px-1 {bc}">Enter</kbd>.
|
|
282
|
+
</p>
|
|
283
|
+
</div>
|
|
284
|
+
</Dialog>
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { DataSet } from "../data/dataset.js";
|
|
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
|
+
dataset: DataSet;
|
|
7
|
+
placeholder?: string;
|
|
8
|
+
/** Shared axes — forwarded to the underlying filter Input. */
|
|
9
|
+
size?: Responsive<Size>;
|
|
10
|
+
variant?: Variant;
|
|
11
|
+
borderless?: boolean;
|
|
12
|
+
};
|
|
13
|
+
declare const Query: import("svelte").Component<$$ComponentProps, {}, "">;
|
|
14
|
+
type Query = ReturnType<typeof Query>;
|
|
15
|
+
export default Query;
|
|
@@ -1,14 +1,23 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
+
import { variantToken, type Variant } from "../types/variants.js";
|
|
3
|
+
import type { Size } from "../types/sizes.js";
|
|
4
|
+
|
|
2
5
|
export type RatingProps = {
|
|
3
6
|
average: number;
|
|
4
7
|
count: number;
|
|
8
|
+
/** Filled-star colour. Defaults to gold; a variant tints from its token (e.g. `warning`, `primary`). */
|
|
9
|
+
variant?: Variant;
|
|
10
|
+
/** Star size (sm/md/lg). */
|
|
11
|
+
size?: Size;
|
|
5
12
|
};
|
|
6
13
|
|
|
7
|
-
let { average, count }: RatingProps = $props();
|
|
14
|
+
let { average, count, variant, size = "md" }: RatingProps = $props();
|
|
15
|
+
|
|
16
|
+
const starColor = $derived(variant ? `var(${variantToken[variant]})` : "#ffd700");
|
|
17
|
+
const starSize: Record<Size, string> = { sm: "1rem", md: "1.2rem", lg: "1.5rem" };
|
|
8
18
|
|
|
9
19
|
// Validate and sanitize the average rating
|
|
10
|
-
const validAverage =
|
|
11
|
-
isNaN(average) || average < 0 ? 0 : Math.min(average, 10);
|
|
20
|
+
const validAverage = isNaN(average) || average < 0 ? 0 : Math.min(average, 10);
|
|
12
21
|
|
|
13
22
|
// Convert TMDB 10-point scale to 5-star scale
|
|
14
23
|
const fiveStarRating = validAverage / 2;
|
|
@@ -19,7 +28,7 @@
|
|
|
19
28
|
const emptyStars = Math.max(0, 5 - filledStars - (hasHalfStar ? 1 : 0));
|
|
20
29
|
</script>
|
|
21
30
|
|
|
22
|
-
<div class="rating select-none">
|
|
31
|
+
<div class="rating select-none" style="--star-color: {starColor}; --star-size: {starSize[size]}">
|
|
23
32
|
<div class="stars">
|
|
24
33
|
{#each Array(filledStars) as _, i}
|
|
25
34
|
<span class="star filled">★</span>
|
|
@@ -48,30 +57,30 @@
|
|
|
48
57
|
}
|
|
49
58
|
|
|
50
59
|
.star {
|
|
51
|
-
font-size: 1.2rem;
|
|
60
|
+
font-size: var(--star-size, 1.2rem);
|
|
52
61
|
line-height: 1;
|
|
53
62
|
}
|
|
54
63
|
|
|
55
64
|
.star.filled {
|
|
56
|
-
color: #ffd700;
|
|
65
|
+
color: var(--star-color, #ffd700);
|
|
57
66
|
}
|
|
58
67
|
|
|
59
68
|
.star.half-filled {
|
|
60
|
-
color: #ffd700;
|
|
69
|
+
color: var(--star-color, #ffd700);
|
|
61
70
|
opacity: 0.6;
|
|
62
71
|
}
|
|
63
72
|
|
|
64
73
|
.star.empty {
|
|
65
|
-
color: var(--color-secondary);
|
|
74
|
+
color: var(--ui-color-secondary);
|
|
66
75
|
}
|
|
67
76
|
|
|
68
77
|
.rating-value {
|
|
69
78
|
font-weight: 600;
|
|
70
|
-
color: var(--color-text);
|
|
79
|
+
color: var(--ui-color-text);
|
|
71
80
|
}
|
|
72
81
|
|
|
73
82
|
.rating-count {
|
|
74
83
|
font-size: 0.9rem;
|
|
75
|
-
color: var(--color-secondary);
|
|
84
|
+
color: var(--ui-color-secondary);
|
|
76
85
|
}
|
|
77
86
|
</style>
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { type Variant } from "../types/variants.js";
|
|
2
|
+
import type { Size } from "../types/sizes.js";
|
|
3
|
+
export type RatingProps = {
|
|
4
|
+
average: number;
|
|
5
|
+
count: number;
|
|
6
|
+
/** Filled-star colour. Defaults to gold; a variant tints from its token (e.g. `warning`, `primary`). */
|
|
7
|
+
variant?: Variant;
|
|
8
|
+
/** Star size (sm/md/lg). */
|
|
9
|
+
size?: Size;
|
|
10
|
+
};
|
|
11
|
+
declare const Rating: import("svelte").Component<RatingProps, {}, "">;
|
|
12
|
+
type Rating = ReturnType<typeof Rating>;
|
|
13
|
+
export default Rating;
|
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
-
import Input from
|
|
3
|
-
import
|
|
4
|
-
import
|
|
2
|
+
import Input from "./Input.svelte";
|
|
3
|
+
import Magnify from "~icons/mdi/magnify";
|
|
4
|
+
import Close from "~icons/mdi/close";
|
|
5
|
+
import type { HTMLInputAttributes } from "svelte/elements";
|
|
6
|
+
import type { Size } from "../types/sizes.js";
|
|
7
|
+
import type { Responsive } from "../types/responsive.js";
|
|
8
|
+
import type { Variant } from "../types/variants.js";
|
|
5
9
|
|
|
6
|
-
interface Props extends Omit<HTMLInputAttributes,
|
|
10
|
+
interface Props extends Omit<HTMLInputAttributes, "type" | "size"> {
|
|
7
11
|
value: string;
|
|
8
12
|
class?: string;
|
|
9
13
|
inputClass?: string;
|
|
@@ -11,6 +15,10 @@
|
|
|
11
15
|
valid?: boolean;
|
|
12
16
|
touched?: boolean;
|
|
13
17
|
element?: HTMLInputElement;
|
|
18
|
+
size?: Responsive<Size>;
|
|
19
|
+
/** Shared axes — forwarded to the underlying Input. */
|
|
20
|
+
variant?: Variant;
|
|
21
|
+
borderless?: boolean;
|
|
14
22
|
onClear?: () => void;
|
|
15
23
|
clearable?: boolean;
|
|
16
24
|
}
|
|
@@ -22,12 +30,12 @@
|
|
|
22
30
|
element = $bindable(),
|
|
23
31
|
onClear,
|
|
24
32
|
clearable = true,
|
|
25
|
-
placeholder =
|
|
33
|
+
placeholder = "Search...",
|
|
26
34
|
...restProps
|
|
27
35
|
}: Props = $props();
|
|
28
36
|
|
|
29
37
|
function handleClear() {
|
|
30
|
-
value =
|
|
38
|
+
value = "";
|
|
31
39
|
onClear?.();
|
|
32
40
|
}
|
|
33
41
|
|
|
@@ -42,9 +50,12 @@
|
|
|
42
50
|
bind:element
|
|
43
51
|
type="search"
|
|
44
52
|
inputClass="[&::-webkit-search-cancel-button]:appearance-none [&::-webkit-search-decoration]:appearance-none"
|
|
45
|
-
icon={showClearIcon ? faTimes : faSearch}
|
|
46
53
|
iconClickable={showClearIcon}
|
|
47
54
|
onIconClick={showClearIcon ? handleClear : undefined}
|
|
48
55
|
{placeholder}
|
|
49
56
|
{...restProps}
|
|
50
|
-
|
|
57
|
+
>
|
|
58
|
+
{#snippet icon()}
|
|
59
|
+
{#if showClearIcon}<Close />{:else}<Magnify />{/if}
|
|
60
|
+
{/snippet}
|
|
61
|
+
</Input>
|
|
@@ -1,5 +1,8 @@
|
|
|
1
|
-
import type { HTMLInputAttributes } from
|
|
2
|
-
|
|
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
|
+
interface Props extends Omit<HTMLInputAttributes, "type" | "size"> {
|
|
3
6
|
value: string;
|
|
4
7
|
class?: string;
|
|
5
8
|
inputClass?: string;
|
|
@@ -7,6 +10,10 @@ interface Props extends Omit<HTMLInputAttributes, 'type'> {
|
|
|
7
10
|
valid?: boolean;
|
|
8
11
|
touched?: boolean;
|
|
9
12
|
element?: HTMLInputElement;
|
|
13
|
+
size?: Responsive<Size>;
|
|
14
|
+
/** Shared axes — forwarded to the underlying Input. */
|
|
15
|
+
variant?: Variant;
|
|
16
|
+
borderless?: boolean;
|
|
10
17
|
onClear?: () => void;
|
|
11
18
|
clearable?: boolean;
|
|
12
19
|
}
|