@spaethtech/svelte-ui 0.1.10 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +166 -42
- package/dist/components/Alert.svelte +119 -0
- package/dist/components/Alert.svelte.d.ts +29 -0
- package/dist/components/Badge/Badge.svelte +142 -69
- package/dist/components/Badge/Badge.svelte.d.ts +26 -6
- package/dist/components/Badge/index.d.ts +1 -1
- package/dist/components/Badge/index.js +1 -1
- package/dist/components/Banner.svelte +133 -0
- package/dist/components/Banner.svelte.d.ts +31 -0
- package/dist/components/Button.svelte +240 -0
- package/dist/components/Button.svelte.d.ts +33 -0
- package/dist/components/ButtonDropdown.svelte +145 -0
- package/dist/components/ButtonDropdown.svelte.d.ts +31 -0
- package/dist/components/Calendar.svelte +259 -0
- package/dist/components/Calendar.svelte.d.ts +27 -0
- package/dist/components/Card.svelte +78 -0
- package/dist/components/Card.svelte.d.ts +17 -0
- package/dist/components/CardBody.svelte +41 -0
- package/dist/components/CardBody.svelte.d.ts +17 -0
- package/dist/components/CardFooter.svelte +49 -0
- package/dist/components/CardFooter.svelte.d.ts +16 -0
- package/dist/components/CardHeader.svelte +49 -0
- package/dist/components/CardHeader.svelte.d.ts +16 -0
- package/dist/components/Checkbox.svelte +131 -0
- package/dist/components/Checkbox.svelte.d.ts +16 -0
- package/dist/components/DataTable.svelte +518 -0
- package/dist/components/DataTable.svelte.d.ts +74 -0
- package/dist/components/DatePicker.svelte +312 -0
- package/dist/components/DatePicker.svelte.d.ts +32 -0
- package/dist/components/DateTimeInput.svelte +93 -0
- package/dist/components/DateTimeInput.svelte.d.ts +20 -0
- package/dist/components/Dialog.svelte +132 -0
- package/dist/components/Dialog.svelte.d.ts +24 -0
- package/dist/components/{EmailInput/EmailInput.svelte → EmailInput.svelte} +17 -8
- package/dist/components/EmailInput.svelte.d.ts +21 -0
- package/dist/components/Input.svelte +369 -0
- package/dist/components/Input.svelte.d.ts +35 -0
- package/dist/components/{List/List.svelte → List.svelte} +194 -133
- package/dist/components/{List/List.svelte.d.ts → List.svelte.d.ts} +9 -1
- package/dist/components/Menu.svelte +117 -0
- package/dist/components/Menu.svelte.d.ts +20 -0
- package/dist/components/NotesEditor.svelte +127 -0
- package/dist/components/NotesEditor.svelte.d.ts +17 -0
- package/dist/components/{NumberInput/NumberInput.svelte → NumberInput.svelte} +146 -105
- package/dist/components/{NumberInput/NumberInput.svelte.d.ts → NumberInput.svelte.d.ts} +11 -4
- package/dist/components/PasswordInput.svelte +52 -0
- package/dist/components/PasswordInput.svelte.d.ts +20 -0
- package/dist/components/Popup.svelte +140 -0
- package/dist/components/Popup.svelte.d.ts +31 -0
- package/dist/components/Query.svelte +284 -0
- package/dist/components/Query.svelte.d.ts +15 -0
- package/dist/components/{Rating/Rating.svelte → Rating.svelte} +19 -10
- package/dist/components/Rating.svelte.d.ts +13 -0
- package/dist/components/{SearchInput/SearchInput.svelte → SearchInput.svelte} +19 -8
- package/dist/components/{SearchInput/SearchInput.svelte.d.ts → SearchInput.svelte.d.ts} +9 -2
- package/dist/components/{Select/Select.svelte → Select.svelte} +48 -76
- package/dist/components/{Select/Select.svelte.d.ts → Select.svelte.d.ts} +11 -1
- package/dist/components/SideBarMenu/SideBarMenu.svelte +724 -0
- package/dist/components/SideBarMenu/SideBarMenu.svelte.d.ts +109 -0
- package/dist/components/SideBarMenu/index.d.ts +2 -0
- package/dist/components/SideBarMenu/index.js +1 -0
- package/dist/components/TabStrip/TabStrip.svelte +384 -0
- package/dist/components/TabStrip/TabStrip.svelte.d.ts +50 -0
- package/dist/components/TabStrip/index.d.ts +3 -0
- package/dist/components/TabStrip/index.js +2 -0
- package/dist/components/{TextArea/TextArea.svelte → TextArea.svelte} +143 -112
- package/dist/components/TextArea.svelte.d.ts +38 -0
- package/dist/components/ThemeSelector.svelte +81 -0
- package/dist/components/ThemeSelector.svelte.d.ts +10 -0
- package/dist/components/TimePicker.svelte +148 -0
- package/dist/components/TimePicker.svelte.d.ts +28 -0
- package/dist/components/TimeRangeInput.svelte +203 -0
- package/dist/components/TimeRangeInput.svelte.d.ts +29 -0
- package/dist/components/TimeSpinner.svelte +202 -0
- package/dist/components/TimeSpinner.svelte.d.ts +26 -0
- package/dist/components/Toast/Toaster.svelte +51 -0
- package/dist/components/Toast/Toaster.svelte.d.ts +12 -0
- package/dist/components/Toast/toast.svelte.d.ts +29 -0
- package/dist/components/Toast/toast.svelte.js +27 -0
- package/dist/components/Toggle.svelte +93 -0
- package/dist/components/Toggle.svelte.d.ts +15 -0
- package/dist/data/dataset.d.ts +42 -0
- package/dist/data/dataset.js +3 -0
- package/dist/data/index.d.ts +3 -0
- package/dist/data/index.js +3 -0
- package/dist/data/query/ast.d.ts +62 -0
- package/dist/data/query/ast.js +12 -0
- package/dist/data/query/index.d.ts +3 -0
- package/dist/data/query/index.js +3 -0
- package/dist/data/query/lexer.d.ts +33 -0
- package/dist/data/query/lexer.js +225 -0
- package/dist/data/query/parser.d.ts +11 -0
- package/dist/data/query/parser.js +252 -0
- package/dist/data/table/grid.svelte.d.ts +57 -0
- package/dist/data/table/grid.svelte.js +139 -0
- package/dist/data/table/index.d.ts +2 -0
- package/dist/data/table/index.js +2 -0
- package/dist/data/table/types.d.ts +79 -0
- package/dist/index.d.ts +48 -22
- package/dist/index.js +45 -21
- package/dist/positioning/anchored.d.ts +61 -0
- package/dist/positioning/anchored.js +122 -0
- package/dist/positioning/tooltip.d.ts +41 -0
- package/dist/positioning/tooltip.js +147 -0
- package/dist/theme.css +205 -0
- package/dist/types/breakpoints.d.ts +24 -0
- package/dist/types/breakpoints.js +13 -0
- package/dist/types/responsive.d.ts +32 -0
- package/dist/types/responsive.js +54 -0
- package/dist/types/sizes.d.ts +10 -3
- package/dist/types/time.d.ts +19 -0
- package/dist/types/time.js +10 -0
- package/dist/types/variants.d.ts +8 -1
- package/dist/types/variants.js +16 -1
- package/package.json +93 -63
- package/dist/components/Button/Button.svelte +0 -172
- package/dist/components/Button/Button.svelte.d.ts +0 -32
- package/dist/components/Button/index.d.ts +0 -1
- package/dist/components/Button/index.js +0 -1
- package/dist/components/Dialog/Dialog.svelte +0 -101
- package/dist/components/Dialog/Dialog.svelte.d.ts +0 -18
- package/dist/components/Dialog/index.d.ts +0 -1
- package/dist/components/Dialog/index.js +0 -1
- package/dist/components/ElementManager/ElementManager.svelte +0 -397
- package/dist/components/ElementManager/ElementManager.svelte.d.ts +0 -43
- package/dist/components/ElementManager/index.d.ts +0 -1
- package/dist/components/ElementManager/index.js +0 -1
- package/dist/components/EmailInput/EmailInput.svelte.d.ts +0 -14
- package/dist/components/EmailInput/index.d.ts +0 -1
- package/dist/components/EmailInput/index.js +0 -1
- package/dist/components/Icon/Icon.svelte +0 -74
- package/dist/components/Icon/Icon.svelte.d.ts +0 -13
- package/dist/components/Icon/index.d.ts +0 -1
- package/dist/components/Icon/index.js +0 -1
- package/dist/components/Input/Input.svelte +0 -268
- package/dist/components/Input/Input.svelte.d.ts +0 -18
- package/dist/components/Input/index.d.ts +0 -1
- package/dist/components/Input/index.js +0 -1
- package/dist/components/List/index.d.ts +0 -1
- package/dist/components/List/index.js +0 -1
- package/dist/components/ListItem/ListItem.svelte +0 -175
- package/dist/components/ListItem/ListItem.svelte.d.ts +0 -24
- package/dist/components/ListItem/index.d.ts +0 -2
- package/dist/components/ListItem/index.js +0 -2
- package/dist/components/ListView/ListView.svelte +0 -463
- package/dist/components/ListView/ListView.svelte.d.ts +0 -37
- package/dist/components/ListView/index.d.ts +0 -2
- package/dist/components/ListView/index.js +0 -2
- package/dist/components/NodeGraph/BaseNode.d.ts +0 -57
- package/dist/components/NodeGraph/BaseNode.js +0 -30
- package/dist/components/NodeGraph/Edge.svelte +0 -60
- package/dist/components/NodeGraph/Edge.svelte.d.ts +0 -16
- package/dist/components/NodeGraph/ExpressionEvaluator.d.ts +0 -82
- package/dist/components/NodeGraph/ExpressionEvaluator.js +0 -152
- package/dist/components/NodeGraph/Node.svelte +0 -100
- package/dist/components/NodeGraph/Node.svelte.d.ts +0 -10
- package/dist/components/NodeGraph/NodeGraph.svelte +0 -52
- package/dist/components/NodeGraph/NodeGraph.svelte.d.ts +0 -14
- package/dist/components/NodeGraph/NodeInstance.svelte.d.ts +0 -80
- package/dist/components/NodeGraph/NodeInstance.svelte.js +0 -241
- package/dist/components/NodeGraph/NodePort.svelte +0 -75
- package/dist/components/NodeGraph/NodePort.svelte.d.ts +0 -11
- package/dist/components/NodeGraph/decorators.d.ts +0 -81
- package/dist/components/NodeGraph/decorators.js +0 -148
- package/dist/components/NodeGraph/index.d.ts +0 -9
- package/dist/components/NodeGraph/index.js +0 -9
- package/dist/components/NodeGraph/types.d.ts +0 -94
- package/dist/components/NodeGraph/types.js +0 -33
- package/dist/components/NotesEditor/NotesEditor.svelte +0 -203
- package/dist/components/NotesEditor/NotesEditor.svelte.d.ts +0 -9
- package/dist/components/NotesEditor/index.d.ts +0 -1
- package/dist/components/NotesEditor/index.js +0 -1
- package/dist/components/NumberInput/index.d.ts +0 -1
- package/dist/components/NumberInput/index.js +0 -1
- package/dist/components/PasswordInput/PasswordInput.svelte +0 -41
- package/dist/components/PasswordInput/PasswordInput.svelte.d.ts +0 -13
- package/dist/components/PasswordInput/index.d.ts +0 -1
- package/dist/components/PasswordInput/index.js +0 -1
- package/dist/components/Popup/index.d.ts +0 -1
- package/dist/components/Popup/index.js +0 -1
- package/dist/components/Rating/Rating.svelte.d.ts +0 -7
- package/dist/components/Rating/index.d.ts +0 -1
- package/dist/components/Rating/index.js +0 -1
- package/dist/components/ScrollView/ScrollView.svelte +0 -285
- package/dist/components/ScrollView/ScrollView.svelte.d.ts +0 -19
- package/dist/components/ScrollView/index.d.ts +0 -1
- package/dist/components/ScrollView/index.js +0 -1
- package/dist/components/SearchInput/index.d.ts +0 -1
- package/dist/components/SearchInput/index.js +0 -1
- package/dist/components/Select/index.d.ts +0 -1
- package/dist/components/Select/index.js +0 -1
- package/dist/components/TextArea/TextArea.svelte.d.ts +0 -19
- package/dist/components/TextArea/index.d.ts +0 -1
- package/dist/components/TextArea/index.js +0 -1
- package/dist/components/ThemeSelector/ThemeSelector.svelte +0 -64
- package/dist/components/ThemeSelector/ThemeSelector.svelte.d.ts +0 -3
- package/dist/components/ThemeSelector/index.d.ts +0 -1
- package/dist/components/ThemeSelector/index.js +0 -1
- package/dist/components/TooltipHandler/TooltipHandler.svelte +0 -593
- package/dist/components/TooltipHandler/TooltipHandler.svelte.d.ts +0 -10
- package/dist/components/TooltipHandler/index.d.ts +0 -1
- package/dist/components/TooltipHandler/index.js +0 -1
- package/dist/styles/sizes.d.ts +0 -78
- package/dist/styles/sizes.js +0 -120
- package/dist/styles/variants.d.ts +0 -106
- package/dist/styles/variants.js +0 -194
- package/dist/types/ManagerTypes.d.ts +0 -42
- /package/dist/{types/ManagerTypes.js → data/table/types.js} +0 -0
|
@@ -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>
|
|
@@ -0,0 +1,21 @@
|
|
|
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"> {
|
|
6
|
+
value: string;
|
|
7
|
+
class?: string;
|
|
8
|
+
inputClass?: string;
|
|
9
|
+
validate?: (value: string) => boolean | string;
|
|
10
|
+
valid?: boolean;
|
|
11
|
+
touched?: boolean;
|
|
12
|
+
element?: HTMLInputElement;
|
|
13
|
+
size?: Responsive<Size>;
|
|
14
|
+
/** Shared axes — forwarded to the underlying Input. */
|
|
15
|
+
variant?: Variant;
|
|
16
|
+
borderless?: boolean;
|
|
17
|
+
required?: boolean;
|
|
18
|
+
}
|
|
19
|
+
declare const EmailInput: import("svelte").Component<Props, {}, "element" | "value" | "valid" | "touched">;
|
|
20
|
+
type EmailInput = ReturnType<typeof EmailInput>;
|
|
21
|
+
export default EmailInput;
|
|
@@ -0,0 +1,369 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { Snippet } from "svelte";
|
|
3
|
+
import type { HTMLInputAttributes } from "svelte/elements";
|
|
4
|
+
import type { Size } from "../types/sizes.js";
|
|
5
|
+
import { responsiveClasses, type Responsive } from "../types/responsive.js";
|
|
6
|
+
import type { Variant } from "../types/variants.js";
|
|
7
|
+
|
|
8
|
+
// Omit the native `size` attribute (character width, a number) — we repurpose `size` as the
|
|
9
|
+
// shared sm/md/lg axis; the native one is meaningless on our flex-based input anyway.
|
|
10
|
+
interface Props extends Omit<HTMLInputAttributes, "size"> {
|
|
11
|
+
/** Accepts number too so remote-form field spreads (`{...field.as(...)}`) type-check. */
|
|
12
|
+
value?: string | number;
|
|
13
|
+
/** Icon slot — consumer-rendered (e.g. an MDI `~icons/mdi/*` component). Styled by the lib. */
|
|
14
|
+
icon?: Snippet;
|
|
15
|
+
/** Trailing controls rendered inside the bordered wrapper, right edge (e.g. clear/help buttons).
|
|
16
|
+
Lets compound components (Query, Select…) reuse Input's frame instead of re-rolling it. */
|
|
17
|
+
actions?: Snippet;
|
|
18
|
+
iconPosition?: "left" | "right";
|
|
19
|
+
size?: Responsive<Size>;
|
|
20
|
+
/** Colour axis (shared with every component). Tints the field's border like Card: the default
|
|
21
|
+
* `secondary` is the neutral strong edge; other variants use that colour at the 30% tint-border,
|
|
22
|
+
* strengthening on hover/focus. A validation error still overrides with the error colour. */
|
|
23
|
+
variant?: Variant;
|
|
24
|
+
/** Drop the visible frame while keeping the 1px box + focus outline: the border goes transparent
|
|
25
|
+
* (the field's background) and the hover/focus border-darkening is suppressed. Defaults `false` —
|
|
26
|
+
* an Input reads as a bordered field. A validation error still shows its border. */
|
|
27
|
+
borderless?: boolean;
|
|
28
|
+
iconClickable?: boolean;
|
|
29
|
+
onIconClick?: () => void;
|
|
30
|
+
class?: string;
|
|
31
|
+
inputClass?: string;
|
|
32
|
+
validate?: (value: string) => boolean | string;
|
|
33
|
+
valid?: boolean;
|
|
34
|
+
touched?: boolean;
|
|
35
|
+
element?: HTMLInputElement;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
let {
|
|
39
|
+
value = $bindable(),
|
|
40
|
+
icon,
|
|
41
|
+
actions,
|
|
42
|
+
iconPosition = "right",
|
|
43
|
+
size = "md",
|
|
44
|
+
variant = "secondary",
|
|
45
|
+
borderless = false,
|
|
46
|
+
iconClickable = false,
|
|
47
|
+
onIconClick,
|
|
48
|
+
class: containerClass = "",
|
|
49
|
+
inputClass = "",
|
|
50
|
+
validate,
|
|
51
|
+
valid = $bindable(true),
|
|
52
|
+
touched = $bindable(false),
|
|
53
|
+
element = $bindable(),
|
|
54
|
+
type = "text",
|
|
55
|
+
disabled = false,
|
|
56
|
+
readonly = false,
|
|
57
|
+
// Pulled out of restProps so the wrapping handlers below can't be clobbered by the
|
|
58
|
+
// `{...restProps}` spread on the <input>; we still forward the consumer's callbacks.
|
|
59
|
+
oninput: oninputProp,
|
|
60
|
+
onfocus: onfocusProp,
|
|
61
|
+
...restProps
|
|
62
|
+
}: Props = $props();
|
|
63
|
+
|
|
64
|
+
let inputElement: HTMLInputElement;
|
|
65
|
+
|
|
66
|
+
// Sync element binding
|
|
67
|
+
$effect(() => {
|
|
68
|
+
element = inputElement;
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
// Validation logic
|
|
72
|
+
let validationResult = $state<boolean | string>(true);
|
|
73
|
+
|
|
74
|
+
function runValidation() {
|
|
75
|
+
if (!validate) {
|
|
76
|
+
valid = true;
|
|
77
|
+
validationResult = true;
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const result = validate(String(value ?? ""));
|
|
82
|
+
validationResult = result;
|
|
83
|
+
valid = result === true;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Run validation when value changes
|
|
87
|
+
$effect(() => {
|
|
88
|
+
runValidation();
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
// Size axis (see types/sizes.ts): control height, embedded icon-box, text, and svg size.
|
|
92
|
+
// Per-Size class maps → responsiveClasses() handles scalar OR { base, xs, … }. Icons: 14/16/20px.
|
|
93
|
+
const heightMap: Record<Size, string> = { sm: "h-6", md: "h-8", lg: "h-10" };
|
|
94
|
+
const iconBoxMap: Record<Size, string> = { sm: "w-6 h-6", md: "w-8 h-8", lg: "w-10 h-10" };
|
|
95
|
+
const textMap: Record<Size, string> = { sm: "text-xs", md: "text-sm", lg: "text-base" };
|
|
96
|
+
const svgMap: Record<Size, string> = { sm: "w-3.5 h-3.5", md: "w-4 h-4", lg: "w-5 h-5" };
|
|
97
|
+
// Border colour by variant — ONE calculation for EVERY variant (no `secondary`/`-strong` special
|
|
98
|
+
// case): the variant token at `--ui-tint-border` (30%) at rest, the same token at 55% on hover/focus,
|
|
99
|
+
// always mixed toward transparent (the tint language, like Alert/Badge/Card). Literal classes so
|
|
100
|
+
// Tailwind can generate them (incl. the hover/focus states). ghost/neutral resolve to --ui-color-text.
|
|
101
|
+
const borderVariantClass: Record<Variant, string> = {
|
|
102
|
+
secondary:
|
|
103
|
+
"[border-color:color-mix(in_srgb,var(--ui-color-secondary)_var(--ui-tint-border),transparent)] hover:[border-color:color-mix(in_srgb,var(--ui-color-secondary)_var(--ui-tint-border-hover),transparent)] focus-within:[border-color:color-mix(in_srgb,var(--ui-color-secondary)_var(--ui-tint-border-hover),transparent)]",
|
|
104
|
+
primary:
|
|
105
|
+
"[border-color:color-mix(in_srgb,var(--ui-color-primary)_var(--ui-tint-border),transparent)] hover:[border-color:color-mix(in_srgb,var(--ui-color-primary)_var(--ui-tint-border-hover),transparent)] focus-within:[border-color:color-mix(in_srgb,var(--ui-color-primary)_var(--ui-tint-border-hover),transparent)]",
|
|
106
|
+
danger:
|
|
107
|
+
"[border-color:color-mix(in_srgb,var(--ui-color-error)_var(--ui-tint-border),transparent)] hover:[border-color:color-mix(in_srgb,var(--ui-color-error)_var(--ui-tint-border-hover),transparent)] focus-within:[border-color:color-mix(in_srgb,var(--ui-color-error)_var(--ui-tint-border-hover),transparent)]",
|
|
108
|
+
info: "[border-color:color-mix(in_srgb,var(--ui-color-info)_var(--ui-tint-border),transparent)] hover:[border-color:color-mix(in_srgb,var(--ui-color-info)_var(--ui-tint-border-hover),transparent)] focus-within:[border-color:color-mix(in_srgb,var(--ui-color-info)_var(--ui-tint-border-hover),transparent)]",
|
|
109
|
+
success:
|
|
110
|
+
"[border-color:color-mix(in_srgb,var(--ui-color-success)_var(--ui-tint-border),transparent)] hover:[border-color:color-mix(in_srgb,var(--ui-color-success)_var(--ui-tint-border-hover),transparent)] focus-within:[border-color:color-mix(in_srgb,var(--ui-color-success)_var(--ui-tint-border-hover),transparent)]",
|
|
111
|
+
warning:
|
|
112
|
+
"[border-color:color-mix(in_srgb,var(--ui-color-warning)_var(--ui-tint-border),transparent)] hover:[border-color:color-mix(in_srgb,var(--ui-color-warning)_var(--ui-tint-border-hover),transparent)] focus-within:[border-color:color-mix(in_srgb,var(--ui-color-warning)_var(--ui-tint-border-hover),transparent)]",
|
|
113
|
+
neutral:
|
|
114
|
+
"[border-color:color-mix(in_srgb,var(--ui-color-text)_var(--ui-tint-border),transparent)] hover:[border-color:color-mix(in_srgb,var(--ui-color-text)_var(--ui-tint-border-hover),transparent)] focus-within:[border-color:color-mix(in_srgb,var(--ui-color-text)_var(--ui-tint-border-hover),transparent)]",
|
|
115
|
+
ghost:
|
|
116
|
+
"[border-color:color-mix(in_srgb,var(--ui-color-text)_var(--ui-tint-border),transparent)] hover:[border-color:color-mix(in_srgb,var(--ui-color-text)_var(--ui-tint-border-hover),transparent)] focus-within:[border-color:color-mix(in_srgb,var(--ui-color-text)_var(--ui-tint-border-hover),transparent)]",
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
// Placeholder colour tracks the variant too — muted, matching each variant's RESTING border colour
|
|
120
|
+
// (secondary = the neutral strong edge; others = their 30% tint-border). Literal classes so Tailwind
|
|
121
|
+
// emits them; applied regardless of `borderless`, so a frameless variant field still reads as coloured.
|
|
122
|
+
const placeholderVariantClass: Record<Variant, string> = {
|
|
123
|
+
secondary:
|
|
124
|
+
"placeholder:[color:color-mix(in_srgb,var(--ui-color-secondary)_var(--ui-tint-border),transparent)]",
|
|
125
|
+
primary:
|
|
126
|
+
"placeholder:[color:color-mix(in_srgb,var(--ui-color-primary)_var(--ui-tint-border),transparent)]",
|
|
127
|
+
danger:
|
|
128
|
+
"placeholder:[color:color-mix(in_srgb,var(--ui-color-error)_var(--ui-tint-border),transparent)]",
|
|
129
|
+
info: "placeholder:[color:color-mix(in_srgb,var(--ui-color-info)_var(--ui-tint-border),transparent)]",
|
|
130
|
+
success:
|
|
131
|
+
"placeholder:[color:color-mix(in_srgb,var(--ui-color-success)_var(--ui-tint-border),transparent)]",
|
|
132
|
+
warning:
|
|
133
|
+
"placeholder:[color:color-mix(in_srgb,var(--ui-color-warning)_var(--ui-tint-border),transparent)]",
|
|
134
|
+
neutral:
|
|
135
|
+
"placeholder:[color:color-mix(in_srgb,var(--ui-color-text)_var(--ui-tint-border),transparent)]",
|
|
136
|
+
ghost:
|
|
137
|
+
"placeholder:[color:color-mix(in_srgb,var(--ui-color-text)_var(--ui-tint-border),transparent)]",
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
const heightClass = $derived(responsiveClasses(size, heightMap));
|
|
141
|
+
const iconBoxClass = $derived(responsiveClasses(size, iconBoxMap));
|
|
142
|
+
const textClass = $derived(responsiveClasses(size, textMap));
|
|
143
|
+
const svgClass = $derived(responsiveClasses(size, svgMap));
|
|
144
|
+
|
|
145
|
+
// Computed classes
|
|
146
|
+
let containerClasses = $derived.by(() => {
|
|
147
|
+
const base = "relative w-full";
|
|
148
|
+
return `${base} ${containerClass}`.trim();
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
let wrapperClasses = $derived.by(() => {
|
|
152
|
+
const base = `w-full ${heightClass} flex items-center [border-radius:var(--ui-border-radius)]`;
|
|
153
|
+
|
|
154
|
+
// Calculate padding based on icon presence and position
|
|
155
|
+
let paddingClasses = "";
|
|
156
|
+
if (icon) {
|
|
157
|
+
// Icon present - only pad the side without icon
|
|
158
|
+
if (iconPosition === "left") {
|
|
159
|
+
paddingClasses = "pl-0 pr-2"; // Icon on left, pad right only
|
|
160
|
+
} else {
|
|
161
|
+
paddingClasses = "pl-2 pr-0"; // Icon on right, pad left only
|
|
162
|
+
}
|
|
163
|
+
} else {
|
|
164
|
+
// No icon - pad both sides
|
|
165
|
+
paddingClasses = "px-2";
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// Border colour comes from the variant map (the shared tint scheme — token @ --ui-tint-border at
|
|
169
|
+
// rest, @ --ui-tint-border-hover on hover/focus — same as Card/DataTable/TextArea, so every bordered
|
|
170
|
+
// surface stays uniform under any theme). `borderless` overrides it with transparent (the field's
|
|
171
|
+
// own background) — the 1px box stays, so the field never jumps, and the focus outline still fires.
|
|
172
|
+
const frameStyles = borderless ? "[border-color:transparent]" : borderVariantClass[variant];
|
|
173
|
+
const themeStyles = [
|
|
174
|
+
"bg-transparent",
|
|
175
|
+
"border", // 1px — matches --ui-border (List/TextArea); uniform across the library
|
|
176
|
+
paddingClasses,
|
|
177
|
+
frameStyles,
|
|
178
|
+
"focus-within:[outline:2px_solid_color-mix(in_srgb,currentColor_70%,transparent)]",
|
|
179
|
+
"focus-within:[outline-offset:0px]",
|
|
180
|
+
].join(" ");
|
|
181
|
+
|
|
182
|
+
// Validation state overrides
|
|
183
|
+
let validationStyles = "";
|
|
184
|
+
if (touched && !valid) {
|
|
185
|
+
validationStyles =
|
|
186
|
+
"[border-color:var(--ui-color-error)] focus-within:[ring-color:var(--ui-color-error)]";
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// Disabled state — borders are uniformly 1px now, so no padding compensation is needed.
|
|
190
|
+
// (readonly renders like a normal field: same 1px frame + hover/focus glow, e.g. Select's trigger.)
|
|
191
|
+
let disabledStyles = "";
|
|
192
|
+
if (disabled) {
|
|
193
|
+
disabledStyles =
|
|
194
|
+
"![background-color:color-mix(in_srgb,var(--ui-color-text)_5%,transparent)] [border-color:color-mix(in_srgb,var(--ui-color-secondary)_var(--ui-tint-border),transparent)] cursor-not-allowed";
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
return `${base} ${themeStyles} ${validationStyles} ${disabledStyles}`.trim();
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
let inputClasses = $derived.by(() => {
|
|
201
|
+
const base = `flex-1 h-full bg-transparent border-none outline-none ${textClass} leading-tight p-0 min-w-0`;
|
|
202
|
+
|
|
203
|
+
// Text uses the theme's text token; placeholder is a muted mix of
|
|
204
|
+
// it (matches DaisyUI's `base-content/50` convention). Not tied
|
|
205
|
+
// to --ui-color-secondary anymore so a theme's accent palette doesn't
|
|
206
|
+
// leak into every input's placeholder.
|
|
207
|
+
const themeStyles = [
|
|
208
|
+
"[color:var(--ui-color-text)]",
|
|
209
|
+
placeholderVariantClass[variant],
|
|
210
|
+
"disabled:bg-transparent",
|
|
211
|
+
"disabled:[color:color-mix(in_srgb,var(--ui-color-text)_40%,transparent)]",
|
|
212
|
+
"disabled:cursor-not-allowed",
|
|
213
|
+
].join(" ");
|
|
214
|
+
|
|
215
|
+
return `${base} ${themeStyles} ${inputClass}`.trim();
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
let iconClasses = $derived.by(() => {
|
|
219
|
+
const base = `${iconBoxClass} flex items-center justify-center flex-shrink-0 select-none`;
|
|
220
|
+
|
|
221
|
+
const interactive = iconClickable
|
|
222
|
+
? "cursor-pointer focus:outline-none transition-colors group"
|
|
223
|
+
: "pointer-events-none";
|
|
224
|
+
|
|
225
|
+
return `${base} ${interactive}`.trim();
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
// Event handlers
|
|
229
|
+
function handleInput(event: Event & { currentTarget: EventTarget & HTMLInputElement }) {
|
|
230
|
+
const target = event.target as HTMLInputElement;
|
|
231
|
+
value = target.value;
|
|
232
|
+
oninputProp?.(event);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
function handleFocus(event: FocusEvent & { currentTarget: EventTarget & HTMLInputElement }) {
|
|
236
|
+
touched = true;
|
|
237
|
+
onfocusProp?.(event);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
function handleIconKeydown(event: KeyboardEvent) {
|
|
241
|
+
if (event.key === "Enter" || event.key === " ") {
|
|
242
|
+
event.preventDefault();
|
|
243
|
+
onIconClick?.();
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
function handleIconMouseDown(event: MouseEvent) {
|
|
248
|
+
event.preventDefault(); // Prevent text selection on double-click
|
|
249
|
+
event.stopPropagation(); // Prevent event from bubbling
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
function handleIconSelectStart(event: Event) {
|
|
253
|
+
event.preventDefault(); // Prevent text selection
|
|
254
|
+
return false;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
function handleIconDoubleClick(event: MouseEvent) {
|
|
258
|
+
event.preventDefault();
|
|
259
|
+
event.stopPropagation();
|
|
260
|
+
return false;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
// Override browser validation
|
|
264
|
+
$effect(() => {
|
|
265
|
+
if (inputElement) {
|
|
266
|
+
inputElement.setCustomValidity("");
|
|
267
|
+
if (touched && !valid && typeof validationResult === "string") {
|
|
268
|
+
inputElement.setCustomValidity(validationResult);
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
});
|
|
272
|
+
</script>
|
|
273
|
+
|
|
274
|
+
<!-- Styled icon box: the consumer renders any icon component in the `icon` snippet; the lib controls
|
|
275
|
+
size + color here (MDI icons use currentColor and fill the box), so it works with any icon set. -->
|
|
276
|
+
{#snippet iconBox()}
|
|
277
|
+
<span
|
|
278
|
+
class="inline-flex {svgClass} items-center justify-center [color:var(--ui-color-secondary)] group-hover:[color:color-mix(in_srgb,var(--ui-color-secondary)_80%,black)] group-focus:[color:color-mix(in_srgb,currentColor_70%,transparent)] transition-colors [&_svg]:w-full [&_svg]:h-full"
|
|
279
|
+
>
|
|
280
|
+
{@render icon?.()}
|
|
281
|
+
</span>
|
|
282
|
+
{/snippet}
|
|
283
|
+
|
|
284
|
+
<div class={containerClasses}>
|
|
285
|
+
<div class={wrapperClasses} style="user-select: none;">
|
|
286
|
+
{#if icon && iconPosition === "left"}
|
|
287
|
+
{#if iconClickable}
|
|
288
|
+
<div
|
|
289
|
+
class={iconClasses}
|
|
290
|
+
onclick={onIconClick}
|
|
291
|
+
onkeydown={handleIconKeydown}
|
|
292
|
+
onmousedown={handleIconMouseDown}
|
|
293
|
+
ondblclick={handleIconDoubleClick}
|
|
294
|
+
onselectstart={handleIconSelectStart}
|
|
295
|
+
role="button"
|
|
296
|
+
tabindex="0"
|
|
297
|
+
style="user-select: none; -webkit-user-select: none; -moz-user-select: none; -ms-user-select: none;"
|
|
298
|
+
>
|
|
299
|
+
{@render iconBox()}
|
|
300
|
+
</div>
|
|
301
|
+
{:else}
|
|
302
|
+
<div class={iconClasses}>
|
|
303
|
+
{@render iconBox()}
|
|
304
|
+
</div>
|
|
305
|
+
{/if}
|
|
306
|
+
{/if}
|
|
307
|
+
|
|
308
|
+
<input
|
|
309
|
+
bind:this={inputElement}
|
|
310
|
+
{type}
|
|
311
|
+
{value}
|
|
312
|
+
{disabled}
|
|
313
|
+
{readonly}
|
|
314
|
+
oninput={handleInput}
|
|
315
|
+
onfocus={handleFocus}
|
|
316
|
+
{...restProps}
|
|
317
|
+
class="{inputClasses} ui-input"
|
|
318
|
+
style="border: none !important; user-select: text !important;"
|
|
319
|
+
/>
|
|
320
|
+
|
|
321
|
+
{#if icon && iconPosition === "right"}
|
|
322
|
+
{#if iconClickable}
|
|
323
|
+
<div
|
|
324
|
+
class={iconClasses}
|
|
325
|
+
onclick={onIconClick}
|
|
326
|
+
onkeydown={handleIconKeydown}
|
|
327
|
+
onmousedown={handleIconMouseDown}
|
|
328
|
+
ondblclick={handleIconDoubleClick}
|
|
329
|
+
onselectstart={handleIconSelectStart}
|
|
330
|
+
role="button"
|
|
331
|
+
tabindex="0"
|
|
332
|
+
style="user-select: none; -webkit-user-select: none; -moz-user-select: none; -ms-user-select: none;"
|
|
333
|
+
>
|
|
334
|
+
{@render iconBox()}
|
|
335
|
+
</div>
|
|
336
|
+
{:else}
|
|
337
|
+
<div class={iconClasses}>
|
|
338
|
+
{@render iconBox()}
|
|
339
|
+
</div>
|
|
340
|
+
{/if}
|
|
341
|
+
{/if}
|
|
342
|
+
|
|
343
|
+
{#if actions}
|
|
344
|
+
<div class="flex h-full shrink-0 items-center gap-1">
|
|
345
|
+
{@render actions()}
|
|
346
|
+
</div>
|
|
347
|
+
{/if}
|
|
348
|
+
</div>
|
|
349
|
+
</div>
|
|
350
|
+
|
|
351
|
+
<style>
|
|
352
|
+
/* Kill the UA focus ring/box-shadow without inline !important (which would also defeat the
|
|
353
|
+
autofill override below, since box-shadow and -webkit-box-shadow are the same property). */
|
|
354
|
+
.ui-input {
|
|
355
|
+
box-shadow: none !important;
|
|
356
|
+
}
|
|
357
|
+
/* Theme the browser autofill so it fills the whole field in our background colour (no inset blue
|
|
358
|
+
band) and uses our text colour, in both light and dark. More specific than `.ui-input`, so it
|
|
359
|
+
wins. The huge inset shadow paints over the UA-yellow/blue highlight. */
|
|
360
|
+
.ui-input:-webkit-autofill,
|
|
361
|
+
.ui-input:-webkit-autofill:hover,
|
|
362
|
+
.ui-input:-webkit-autofill:focus,
|
|
363
|
+
.ui-input:-webkit-autofill:active {
|
|
364
|
+
-webkit-box-shadow: 0 0 0 1000px var(--ui-color-background) inset !important;
|
|
365
|
+
-webkit-text-fill-color: var(--ui-color-text) !important;
|
|
366
|
+
caret-color: var(--ui-color-text);
|
|
367
|
+
transition: background-color 9999s ease-in-out 0s; /* belt-and-suspenders against the fade */
|
|
368
|
+
}
|
|
369
|
+
</style>
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { Snippet } from "svelte";
|
|
2
|
+
import type { HTMLInputAttributes } from "svelte/elements";
|
|
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
|
+
interface Props extends Omit<HTMLInputAttributes, "size"> {
|
|
7
|
+
/** Accepts number too so remote-form field spreads (`{...field.as(...)}`) type-check. */
|
|
8
|
+
value?: string | number;
|
|
9
|
+
/** Icon slot — consumer-rendered (e.g. an MDI `~icons/mdi/*` component). Styled by the lib. */
|
|
10
|
+
icon?: Snippet;
|
|
11
|
+
/** Trailing controls rendered inside the bordered wrapper, right edge (e.g. clear/help buttons).
|
|
12
|
+
Lets compound components (Query, Select…) reuse Input's frame instead of re-rolling it. */
|
|
13
|
+
actions?: Snippet;
|
|
14
|
+
iconPosition?: "left" | "right";
|
|
15
|
+
size?: Responsive<Size>;
|
|
16
|
+
/** Colour axis (shared with every component). Tints the field's border like Card: the default
|
|
17
|
+
* `secondary` is the neutral strong edge; other variants use that colour at the 30% tint-border,
|
|
18
|
+
* strengthening on hover/focus. A validation error still overrides with the error colour. */
|
|
19
|
+
variant?: Variant;
|
|
20
|
+
/** Drop the visible frame while keeping the 1px box + focus outline: the border goes transparent
|
|
21
|
+
* (the field's background) and the hover/focus border-darkening is suppressed. Defaults `false` —
|
|
22
|
+
* an Input reads as a bordered field. A validation error still shows its border. */
|
|
23
|
+
borderless?: boolean;
|
|
24
|
+
iconClickable?: boolean;
|
|
25
|
+
onIconClick?: () => void;
|
|
26
|
+
class?: string;
|
|
27
|
+
inputClass?: string;
|
|
28
|
+
validate?: (value: string) => boolean | string;
|
|
29
|
+
valid?: boolean;
|
|
30
|
+
touched?: boolean;
|
|
31
|
+
element?: HTMLInputElement;
|
|
32
|
+
}
|
|
33
|
+
declare const Input: import("svelte").Component<Props, {}, "element" | "value" | "valid" | "touched">;
|
|
34
|
+
type Input = ReturnType<typeof Input>;
|
|
35
|
+
export default Input;
|