svelte-comp 1.3.3 → 1.3.6
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/LICENSE.md +21 -21
- package/README.md +101 -100
- package/dist/App.svelte +507 -507
- package/dist/Container.svelte +59 -59
- package/dist/app.css +234 -235
- package/dist/app.d.ts +10 -0
- package/dist/lib/Accordion.svelte +155 -155
- package/dist/lib/Badge.svelte +44 -44
- package/dist/lib/Button.svelte +185 -170
- package/dist/lib/Calendar.svelte +384 -384
- package/dist/lib/Card.svelte +103 -103
- package/dist/lib/Carousel.svelte +293 -293
- package/dist/lib/Carousel.svelte.d.ts +1 -1
- package/dist/lib/CheckBox.svelte +210 -210
- package/dist/lib/CodeView.svelte +308 -307
- package/dist/lib/ColorPicker.svelte +159 -159
- package/dist/lib/ContextMenu.svelte +328 -322
- package/dist/lib/DatePicker.svelte +246 -246
- package/dist/lib/Dialog.svelte +233 -233
- package/dist/lib/Field.svelte +299 -299
- package/dist/lib/FilePicker.svelte +295 -240
- package/dist/lib/FilePicker.svelte.d.ts +6 -1
- package/dist/lib/Form.svelte +438 -438
- package/dist/lib/Hamburger.svelte +217 -217
- package/dist/lib/InstallPWA.svelte +94 -94
- package/dist/lib/Menu.svelte +623 -623
- package/dist/lib/NoticeBase.svelte +140 -140
- package/dist/lib/PaginatedCard.svelte +73 -73
- package/dist/lib/Pagination.svelte +119 -119
- package/dist/lib/PrimaryColorSelect.svelte +111 -111
- package/dist/lib/ProgressBar.svelte +141 -141
- package/dist/lib/ProgressCircle.svelte +190 -190
- package/dist/lib/Radio.svelte +189 -189
- package/dist/lib/SearchInput.svelte +104 -104
- package/dist/lib/Select.svelte +524 -524
- package/dist/lib/Slider.svelte +253 -253
- package/dist/lib/Splitter.svelte +159 -150
- package/dist/lib/Switch.svelte +168 -167
- package/dist/lib/Table.svelte +299 -299
- package/dist/lib/Tabs.svelte +213 -213
- package/dist/lib/ThemeToggle.svelte +128 -127
- package/dist/lib/TimePicker.svelte +312 -312
- package/dist/lib/TimePickerNew.svelte +634 -0
- package/dist/lib/TimePickerNew.svelte.d.ts +49 -0
- package/dist/lib/Toast.svelte +123 -123
- package/dist/lib/Tooltip.svelte +110 -110
- package/dist/lib/Topbar.svelte +107 -107
- package/dist/lib/__tests__/Accordion.test.d.ts +1 -0
- package/dist/lib/__tests__/Accordion.test.js +171 -0
- package/dist/lib/__tests__/Badge.test.d.ts +1 -0
- package/dist/lib/__tests__/Badge.test.js +41 -0
- package/dist/lib/__tests__/Button.test.d.ts +1 -0
- package/dist/lib/__tests__/Button.test.js +269 -0
- package/dist/lib/__tests__/Calendar.test.d.ts +1 -0
- package/dist/lib/__tests__/Calendar.test.js +171 -0
- package/dist/lib/__tests__/Card.test.d.ts +1 -0
- package/dist/lib/__tests__/Card.test.js +148 -0
- package/dist/lib/__tests__/Carousel.test.d.ts +1 -0
- package/dist/lib/__tests__/Carousel.test.js +439 -0
- package/dist/lib/__tests__/CheckBox.test.d.ts +1 -0
- package/dist/lib/__tests__/CheckBox.test.js +152 -0
- package/dist/lib/__tests__/CodeView.test.d.ts +1 -0
- package/dist/lib/__tests__/CodeView.test.js +157 -0
- package/dist/lib/__tests__/ColorPicker.test.d.ts +1 -0
- package/dist/lib/__tests__/ColorPicker.test.js +93 -0
- package/dist/lib/__tests__/ContextMenu.test.d.ts +1 -0
- package/dist/lib/__tests__/ContextMenu.test.js +67 -0
- package/dist/lib/__tests__/DatePicker.test.d.ts +1 -0
- package/dist/lib/__tests__/DatePicker.test.js +108 -0
- package/dist/lib/__tests__/Dialog.test.d.ts +1 -0
- package/dist/lib/__tests__/Dialog.test.js +183 -0
- package/dist/lib/__tests__/Field.test.d.ts +1 -0
- package/dist/lib/__tests__/Field.test.js +190 -0
- package/dist/lib/__tests__/FilePicker.test.d.ts +1 -0
- package/dist/lib/__tests__/FilePicker.test.js +179 -0
- package/dist/lib/__tests__/Form.integration.test.d.ts +1 -0
- package/dist/lib/__tests__/Form.integration.test.js +158 -0
- package/dist/lib/__tests__/Form.test.d.ts +1 -0
- package/dist/lib/__tests__/Form.test.js +463 -0
- package/dist/lib/__tests__/Hamburger.test.d.ts +1 -0
- package/dist/lib/__tests__/Hamburger.test.js +161 -0
- package/dist/lib/__tests__/InstallPWA.test.d.ts +1 -0
- package/dist/lib/__tests__/InstallPWA.test.js +15 -0
- package/dist/lib/__tests__/Menu.test.d.ts +1 -0
- package/dist/lib/__tests__/Menu.test.js +285 -0
- package/dist/lib/__tests__/NoticeBase.test.d.ts +1 -0
- package/dist/lib/__tests__/NoticeBase.test.js +60 -0
- package/dist/lib/__tests__/PaginatedCard.test.d.ts +1 -0
- package/dist/lib/__tests__/PaginatedCard.test.js +89 -0
- package/dist/lib/__tests__/Pagination.test.d.ts +1 -0
- package/dist/lib/__tests__/Pagination.test.js +168 -0
- package/dist/lib/__tests__/PrimaryColorSelect.test.d.ts +1 -0
- package/dist/lib/__tests__/PrimaryColorSelect.test.js +92 -0
- package/dist/lib/__tests__/ProgressBar.test.d.ts +1 -0
- package/dist/lib/__tests__/ProgressBar.test.js +69 -0
- package/dist/lib/__tests__/ProgressCircle.test.d.ts +1 -0
- package/dist/lib/__tests__/ProgressCircle.test.js +71 -0
- package/dist/lib/__tests__/Radio.test.d.ts +1 -0
- package/dist/lib/__tests__/Radio.test.js +127 -0
- package/dist/lib/__tests__/SearchInput.test.d.ts +1 -0
- package/dist/lib/__tests__/SearchInput.test.js +80 -0
- package/dist/lib/__tests__/Select.test.d.ts +1 -0
- package/dist/lib/__tests__/Select.test.js +408 -0
- package/dist/lib/__tests__/Slider.test.d.ts +1 -0
- package/dist/lib/__tests__/Slider.test.js +213 -0
- package/dist/lib/__tests__/Splitter.test.d.ts +1 -0
- package/dist/lib/__tests__/Splitter.test.js +87 -0
- package/dist/lib/__tests__/Switch.test.d.ts +1 -0
- package/dist/lib/__tests__/Switch.test.js +97 -0
- package/dist/lib/__tests__/Table.test.d.ts +1 -0
- package/dist/lib/__tests__/Table.test.js +349 -0
- package/dist/lib/__tests__/Tabs.test.d.ts +1 -0
- package/dist/lib/__tests__/Tabs.test.js +262 -0
- package/dist/lib/__tests__/ThemeToggle.test.d.ts +1 -0
- package/dist/lib/__tests__/ThemeToggle.test.js +84 -0
- package/dist/lib/__tests__/TimePicker.test.d.ts +1 -0
- package/dist/lib/__tests__/TimePicker.test.js +146 -0
- package/dist/lib/__tests__/TimePickerNew.test.d.ts +1 -0
- package/dist/lib/__tests__/TimePickerNew.test.js +322 -0
- package/dist/lib/__tests__/Toast.test.d.ts +1 -0
- package/dist/lib/__tests__/Toast.test.js +135 -0
- package/dist/lib/__tests__/Tooltip.test.d.ts +1 -0
- package/dist/lib/__tests__/Tooltip.test.js +171 -0
- package/dist/lib/__tests__/Topbar.test.d.ts +1 -0
- package/dist/lib/__tests__/Topbar.test.js +25 -0
- package/dist/lib/__tests__/setupLangContext.d.ts +1 -0
- package/dist/lib/__tests__/setupLangContext.js +65 -0
- package/dist/lib/__tests__/storage.test.d.ts +1 -0
- package/dist/lib/__tests__/storage.test.js +124 -0
- package/dist/lib/__tests__/utils.test.d.ts +1 -0
- package/dist/lib/__tests__/utils.test.js +11 -0
- package/dist/lib/index.d.ts +1 -0
- package/dist/lib/index.js +1 -0
- package/dist/lib/lang.d.ts +4 -0
- package/dist/lib/lang.js +4 -0
- package/dist/styles.css +234 -232
- package/dist/utils/index.js +15 -4
- package/package.json +52 -52
package/dist/lib/Slider.svelte
CHANGED
|
@@ -1,253 +1,253 @@
|
|
|
1
|
-
<!-- src/lib/Slider.svelte -->
|
|
2
|
-
<script lang="ts">
|
|
3
|
-
/**
|
|
4
|
-
* @component Slider
|
|
5
|
-
* @description A customizable slider component for selecting a value from a range.
|
|
6
|
-
*
|
|
7
|
-
* @prop value {number} - The current value (bindable)
|
|
8
|
-
* @default 0
|
|
9
|
-
*
|
|
10
|
-
* @prop min {number} - Minimum value
|
|
11
|
-
* @default 0
|
|
12
|
-
*
|
|
13
|
-
* @prop max {number} - Maximum value
|
|
14
|
-
* @default 100
|
|
15
|
-
*
|
|
16
|
-
* @prop step {number} - Step size
|
|
17
|
-
* @default 1
|
|
18
|
-
*
|
|
19
|
-
* @prop sz {SizeKey} - Slider size
|
|
20
|
-
* @options xs|sm|md|lg|xl
|
|
21
|
-
* @default md
|
|
22
|
-
*
|
|
23
|
-
* @prop variant {ComponentVariant} - Color variant
|
|
24
|
-
* @options default|neutral
|
|
25
|
-
* @default default
|
|
26
|
-
*
|
|
27
|
-
* @prop disabled {boolean} - Disable the slider
|
|
28
|
-
* @default false
|
|
29
|
-
*
|
|
30
|
-
* @prop showValue {boolean} - Show the current value
|
|
31
|
-
* @default false
|
|
32
|
-
*
|
|
33
|
-
* @prop onInput {(value: number) => void} - Fires on value change
|
|
34
|
-
*
|
|
35
|
-
* @prop class {string} - Custom wrapper classes
|
|
36
|
-
* @default ""
|
|
37
|
-
*
|
|
38
|
-
* @note Works with both keyboard and mouse.
|
|
39
|
-
* @note Size and variant control appearance.
|
|
40
|
-
* @note Uses proper ARIA attributes.
|
|
41
|
-
*/
|
|
42
|
-
import type { HTMLAttributes } from "svelte/elements";
|
|
43
|
-
import type { SizeKey, ComponentVariant } from "./types";
|
|
44
|
-
import { TEXT } from "./types";
|
|
45
|
-
import { cx, clamp } from "../utils";
|
|
46
|
-
|
|
47
|
-
type Props = HTMLAttributes<HTMLDivElement> & {
|
|
48
|
-
value?: number;
|
|
49
|
-
min?: number;
|
|
50
|
-
max?: number;
|
|
51
|
-
step?: number;
|
|
52
|
-
sz?: SizeKey;
|
|
53
|
-
variant?: ComponentVariant;
|
|
54
|
-
disabled?: boolean;
|
|
55
|
-
showValue?: boolean;
|
|
56
|
-
onInput?: (value: number) => void;
|
|
57
|
-
class?: string;
|
|
58
|
-
};
|
|
59
|
-
|
|
60
|
-
let {
|
|
61
|
-
value = $bindable(0),
|
|
62
|
-
min = 0,
|
|
63
|
-
max = 100,
|
|
64
|
-
step = 1,
|
|
65
|
-
sz = "md",
|
|
66
|
-
variant = "default",
|
|
67
|
-
disabled = false,
|
|
68
|
-
showValue = false,
|
|
69
|
-
onInput,
|
|
70
|
-
class: externalClass = "",
|
|
71
|
-
...rest
|
|
72
|
-
}: Props = $props();
|
|
73
|
-
|
|
74
|
-
const sizeTrack = $derived(
|
|
75
|
-
{
|
|
76
|
-
xs: "h-[4px]",
|
|
77
|
-
sm: "h-[5px]",
|
|
78
|
-
md: "h-[6px]",
|
|
79
|
-
lg: "h-[7px]",
|
|
80
|
-
xl: "h-[8px]",
|
|
81
|
-
}[sz]
|
|
82
|
-
);
|
|
83
|
-
|
|
84
|
-
const sizeThumb = $derived(
|
|
85
|
-
{
|
|
86
|
-
xs: "[&::-webkit-slider-thumb]:w-3 [&::-webkit-slider-thumb]:h-3 [&::-moz-range-thumb]:w-3 [&::-moz-range-thumb]:h-3",
|
|
87
|
-
sm: "[&::-webkit-slider-thumb]:w-4 [&::-webkit-slider-thumb]:h-4 [&::-moz-range-thumb]:w-4 [&::-moz-range-thumb]:h-4",
|
|
88
|
-
md: "[&::-webkit-slider-thumb]:w-5 [&::-webkit-slider-thumb]:h-5 [&::-moz-range-thumb]:w-5 [&::-moz-range-thumb]:h-5",
|
|
89
|
-
lg: "[&::-webkit-slider-thumb]:w-6 [&::-webkit-slider-thumb]:h-6 [&::-moz-range-thumb]:w-6 [&::-moz-range-thumb]:h-6",
|
|
90
|
-
xl: "[&::-webkit-slider-thumb]:w-7 [&::-webkit-slider-thumb]:h-7 [&::-moz-range-thumb]:w-7 [&::-moz-range-thumb]:h-7",
|
|
91
|
-
}[sz]
|
|
92
|
-
);
|
|
93
|
-
|
|
94
|
-
const bar = $derived(
|
|
95
|
-
variant === "neutral"
|
|
96
|
-
? "bg-[var(--color-bg-secondary)]"
|
|
97
|
-
: "bg-[var(--color-bg-primary)]"
|
|
98
|
-
);
|
|
99
|
-
|
|
100
|
-
const thumb = $derived(
|
|
101
|
-
variant === "neutral"
|
|
102
|
-
? "[&::-webkit-slider-thumb]:bg-[var(--color-bg-secondary)] [&::-moz-range-thumb]:bg-[var(--color-bg-secondary)]"
|
|
103
|
-
: "[&::-webkit-slider-thumb]:bg-[var(--color-bg-primary)] [&::-moz-range-thumb]:bg-[var(--color-bg-primary)]"
|
|
104
|
-
);
|
|
105
|
-
|
|
106
|
-
const pct = $derived(
|
|
107
|
-
clamp(((value - min) / Math.max(1e-12, max - min)) * 100, 0, 100)
|
|
108
|
-
);
|
|
109
|
-
|
|
110
|
-
const rootClass = $derived(
|
|
111
|
-
cx(
|
|
112
|
-
"relative w-full data-[disabled=true]:opacity-[var(--opacity-disabled)]",
|
|
113
|
-
disabled ? "cursor-not-allowed" : "cursor-pointer",
|
|
114
|
-
externalClass
|
|
115
|
-
)
|
|
116
|
-
);
|
|
117
|
-
|
|
118
|
-
const trackClass = $derived(
|
|
119
|
-
cx(
|
|
120
|
-
"absolute top-1/2 -translate-y-1/2 w-full bg-[var(--color-bg-muted)] rounded overflow-hidden",
|
|
121
|
-
sizeTrack
|
|
122
|
-
)
|
|
123
|
-
);
|
|
124
|
-
|
|
125
|
-
const rangeBase =
|
|
126
|
-
"relative z-10 w-full h-10 bg-transparent appearance-none cursor-pointer disabled:cursor-not-allowed " +
|
|
127
|
-
"[&::-webkit-slider-thumb]:appearance-none " +
|
|
128
|
-
"[&::-webkit-slider-thumb]:rounded-[var(--radius-full)] " +
|
|
129
|
-
"[&::-webkit-slider-thumb]:border-2 [&::-webkit-slider-thumb]:border-[var(--color-bg-surface)] " +
|
|
130
|
-
"[&::-webkit-slider-thumb]:shadow-[var(--shadow-color)] " +
|
|
131
|
-
"[&::-moz-range-thumb]:rounded-[var(--radius-full)] " +
|
|
132
|
-
"[&::-moz-range-thumb]:border-2 [&::-moz-range-thumb]:border-[var(--color-bg-surface)] " +
|
|
133
|
-
"[&::-moz-range-thumb]:shadow-[var(--shadow-color)] " +
|
|
134
|
-
"hover:[&::-webkit-slider-thumb]:scale-110 active:[&::-webkit-slider-thumb]:scale-125 " +
|
|
135
|
-
"hover:[&::-moz-range-thumb]:scale-110 active:[&::-moz-range-thumb]:scale-125 " +
|
|
136
|
-
"focus:outline-none focus-visible:outline-none " +
|
|
137
|
-
"focus-visible:[&::-webkit-slider-thumb]:shadow-[0_0_0_3px_var(--border-color-focus)] " +
|
|
138
|
-
"focus-visible:[&::-moz-range-thumb]:shadow-[0_0_0_3px_var(--border-color-focus)] " +
|
|
139
|
-
"disabled:hover:[&::-webkit-slider-thumb]:scale-100 disabled:active:[&::-webkit-slider-thumb]:scale-100 " +
|
|
140
|
-
"disabled:hover:[&::-moz-range-thumb]:scale-100 disabled:active:[&::-moz-range-thumb]:scale-100";
|
|
141
|
-
|
|
142
|
-
const rangeClass = $derived(cx(rangeBase, sizeThumb, thumb));
|
|
143
|
-
|
|
144
|
-
function snap(v: number) {
|
|
145
|
-
const s = step || 1;
|
|
146
|
-
const ticks = Math.round((v - min) / s);
|
|
147
|
-
const snapped = min + ticks * s;
|
|
148
|
-
return clamp(Number(snapped.toFixed(6)), min, max);
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
let wrap: HTMLDivElement;
|
|
152
|
-
|
|
153
|
-
function setFromClientX(x: number) {
|
|
154
|
-
const r = wrap.getBoundingClientRect();
|
|
155
|
-
const ratio = clamp((x - r.left) / r.width, 0, 1);
|
|
156
|
-
value = snap(min + ratio * (max - min));
|
|
157
|
-
onInput?.(value);
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
function onTrackClick(e: MouseEvent) {
|
|
161
|
-
if (disabled) return;
|
|
162
|
-
setFromClientX(e.clientX);
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
function onRangeInput(e: Event) {
|
|
166
|
-
const t = e.currentTarget as HTMLInputElement;
|
|
167
|
-
value = snap(Number(t.value));
|
|
168
|
-
onInput?.(value);
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
function onKey(e: KeyboardEvent) {
|
|
172
|
-
if (disabled) return;
|
|
173
|
-
const span = max - min;
|
|
174
|
-
const big = Math.max(step || 1, Math.round(span / 10));
|
|
175
|
-
let v
|
|
176
|
-
|
|
177
|
-
switch (e.key) {
|
|
178
|
-
case "ArrowLeft":
|
|
179
|
-
case "ArrowDown":
|
|
180
|
-
v = value - (step || 1);
|
|
181
|
-
break;
|
|
182
|
-
case "ArrowRight":
|
|
183
|
-
case "ArrowUp":
|
|
184
|
-
v = value + (step || 1);
|
|
185
|
-
break;
|
|
186
|
-
case "PageDown":
|
|
187
|
-
v = value - big;
|
|
188
|
-
break;
|
|
189
|
-
case "PageUp":
|
|
190
|
-
v = value + big;
|
|
191
|
-
break;
|
|
192
|
-
case "Home":
|
|
193
|
-
v = min;
|
|
194
|
-
break;
|
|
195
|
-
case "End":
|
|
196
|
-
v = max;
|
|
197
|
-
break;
|
|
198
|
-
default:
|
|
199
|
-
return;
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
value = snap(v);
|
|
203
|
-
onInput?.(value);
|
|
204
|
-
e.preventDefault();
|
|
205
|
-
}
|
|
206
|
-
</script>
|
|
207
|
-
|
|
208
|
-
<div
|
|
209
|
-
bind:this={wrap}
|
|
210
|
-
class={rootClass}
|
|
211
|
-
onclick={onTrackClick}
|
|
212
|
-
onfocusin={(e) => (rest as unknown as { onfocus?: (e: FocusEvent) => void }).onfocus?.(e as FocusEvent)}
|
|
213
|
-
onfocusout={(e) => (rest as unknown as { onblur?: (e: FocusEvent) => void }).onblur?.(e as FocusEvent)}
|
|
214
|
-
data-disabled={disabled ? "true" : undefined}
|
|
215
|
-
{...rest}
|
|
216
|
-
>
|
|
217
|
-
<div class="relative flex items-center w-full h-10">
|
|
218
|
-
<div class={trackClass}>
|
|
219
|
-
<div
|
|
220
|
-
class={cx("w-full h-full transition-[width]", bar)}
|
|
221
|
-
style={`width:${pct}%`}
|
|
222
|
-
></div>
|
|
223
|
-
</div>
|
|
224
|
-
|
|
225
|
-
<input
|
|
226
|
-
type="range"
|
|
227
|
-
class={rangeClass}
|
|
228
|
-
{min}
|
|
229
|
-
{max}
|
|
230
|
-
{step}
|
|
231
|
-
{value}
|
|
232
|
-
oninput={onRangeInput}
|
|
233
|
-
onkeydown={onKey}
|
|
234
|
-
{disabled}
|
|
235
|
-
aria-label="slider"
|
|
236
|
-
aria-valuemin={min}
|
|
237
|
-
aria-valuemax={max}
|
|
238
|
-
aria-valuenow={value}
|
|
239
|
-
/>
|
|
240
|
-
</div>
|
|
241
|
-
|
|
242
|
-
{#if showValue}
|
|
243
|
-
<div class="flex justify-end mt-1">
|
|
244
|
-
<div
|
|
245
|
-
class="font-[var(--font-mono)] text-[var(--color-text-muted)] select-none {TEXT[
|
|
246
|
-
sz
|
|
247
|
-
]}"
|
|
248
|
-
>
|
|
249
|
-
{value}
|
|
250
|
-
</div>
|
|
251
|
-
</div>
|
|
252
|
-
{/if}
|
|
253
|
-
</div>
|
|
1
|
+
<!-- src/lib/Slider.svelte -->
|
|
2
|
+
<script lang="ts">
|
|
3
|
+
/**
|
|
4
|
+
* @component Slider
|
|
5
|
+
* @description A customizable slider component for selecting a value from a range.
|
|
6
|
+
*
|
|
7
|
+
* @prop value {number} - The current value (bindable)
|
|
8
|
+
* @default 0
|
|
9
|
+
*
|
|
10
|
+
* @prop min {number} - Minimum value
|
|
11
|
+
* @default 0
|
|
12
|
+
*
|
|
13
|
+
* @prop max {number} - Maximum value
|
|
14
|
+
* @default 100
|
|
15
|
+
*
|
|
16
|
+
* @prop step {number} - Step size
|
|
17
|
+
* @default 1
|
|
18
|
+
*
|
|
19
|
+
* @prop sz {SizeKey} - Slider size
|
|
20
|
+
* @options xs|sm|md|lg|xl
|
|
21
|
+
* @default md
|
|
22
|
+
*
|
|
23
|
+
* @prop variant {ComponentVariant} - Color variant
|
|
24
|
+
* @options default|neutral
|
|
25
|
+
* @default default
|
|
26
|
+
*
|
|
27
|
+
* @prop disabled {boolean} - Disable the slider
|
|
28
|
+
* @default false
|
|
29
|
+
*
|
|
30
|
+
* @prop showValue {boolean} - Show the current value
|
|
31
|
+
* @default false
|
|
32
|
+
*
|
|
33
|
+
* @prop onInput {(value: number) => void} - Fires on value change
|
|
34
|
+
*
|
|
35
|
+
* @prop class {string} - Custom wrapper classes
|
|
36
|
+
* @default ""
|
|
37
|
+
*
|
|
38
|
+
* @note Works with both keyboard and mouse.
|
|
39
|
+
* @note Size and variant control appearance.
|
|
40
|
+
* @note Uses proper ARIA attributes.
|
|
41
|
+
*/
|
|
42
|
+
import type { HTMLAttributes } from "svelte/elements";
|
|
43
|
+
import type { SizeKey, ComponentVariant } from "./types";
|
|
44
|
+
import { TEXT } from "./types";
|
|
45
|
+
import { cx, clamp } from "../utils";
|
|
46
|
+
|
|
47
|
+
type Props = HTMLAttributes<HTMLDivElement> & {
|
|
48
|
+
value?: number;
|
|
49
|
+
min?: number;
|
|
50
|
+
max?: number;
|
|
51
|
+
step?: number;
|
|
52
|
+
sz?: SizeKey;
|
|
53
|
+
variant?: ComponentVariant;
|
|
54
|
+
disabled?: boolean;
|
|
55
|
+
showValue?: boolean;
|
|
56
|
+
onInput?: (value: number) => void;
|
|
57
|
+
class?: string;
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
let {
|
|
61
|
+
value = $bindable(0),
|
|
62
|
+
min = 0,
|
|
63
|
+
max = 100,
|
|
64
|
+
step = 1,
|
|
65
|
+
sz = "md",
|
|
66
|
+
variant = "default",
|
|
67
|
+
disabled = false,
|
|
68
|
+
showValue = false,
|
|
69
|
+
onInput,
|
|
70
|
+
class: externalClass = "",
|
|
71
|
+
...rest
|
|
72
|
+
}: Props = $props();
|
|
73
|
+
|
|
74
|
+
const sizeTrack = $derived(
|
|
75
|
+
{
|
|
76
|
+
xs: "h-[4px]",
|
|
77
|
+
sm: "h-[5px]",
|
|
78
|
+
md: "h-[6px]",
|
|
79
|
+
lg: "h-[7px]",
|
|
80
|
+
xl: "h-[8px]",
|
|
81
|
+
}[sz]
|
|
82
|
+
);
|
|
83
|
+
|
|
84
|
+
const sizeThumb = $derived(
|
|
85
|
+
{
|
|
86
|
+
xs: "[&::-webkit-slider-thumb]:w-3 [&::-webkit-slider-thumb]:h-3 [&::-moz-range-thumb]:w-3 [&::-moz-range-thumb]:h-3",
|
|
87
|
+
sm: "[&::-webkit-slider-thumb]:w-4 [&::-webkit-slider-thumb]:h-4 [&::-moz-range-thumb]:w-4 [&::-moz-range-thumb]:h-4",
|
|
88
|
+
md: "[&::-webkit-slider-thumb]:w-5 [&::-webkit-slider-thumb]:h-5 [&::-moz-range-thumb]:w-5 [&::-moz-range-thumb]:h-5",
|
|
89
|
+
lg: "[&::-webkit-slider-thumb]:w-6 [&::-webkit-slider-thumb]:h-6 [&::-moz-range-thumb]:w-6 [&::-moz-range-thumb]:h-6",
|
|
90
|
+
xl: "[&::-webkit-slider-thumb]:w-7 [&::-webkit-slider-thumb]:h-7 [&::-moz-range-thumb]:w-7 [&::-moz-range-thumb]:h-7",
|
|
91
|
+
}[sz]
|
|
92
|
+
);
|
|
93
|
+
|
|
94
|
+
const bar = $derived(
|
|
95
|
+
variant === "neutral"
|
|
96
|
+
? "bg-[var(--color-bg-secondary)]"
|
|
97
|
+
: "bg-[var(--color-bg-primary)]"
|
|
98
|
+
);
|
|
99
|
+
|
|
100
|
+
const thumb = $derived(
|
|
101
|
+
variant === "neutral"
|
|
102
|
+
? "[&::-webkit-slider-thumb]:bg-[var(--color-bg-secondary)] [&::-moz-range-thumb]:bg-[var(--color-bg-secondary)]"
|
|
103
|
+
: "[&::-webkit-slider-thumb]:bg-[var(--color-bg-primary)] [&::-moz-range-thumb]:bg-[var(--color-bg-primary)]"
|
|
104
|
+
);
|
|
105
|
+
|
|
106
|
+
const pct = $derived(
|
|
107
|
+
clamp(((value - min) / Math.max(1e-12, max - min)) * 100, 0, 100)
|
|
108
|
+
);
|
|
109
|
+
|
|
110
|
+
const rootClass = $derived(
|
|
111
|
+
cx(
|
|
112
|
+
"relative w-full data-[disabled=true]:opacity-[var(--opacity-disabled)]",
|
|
113
|
+
disabled ? "cursor-not-allowed" : "cursor-pointer",
|
|
114
|
+
externalClass
|
|
115
|
+
)
|
|
116
|
+
);
|
|
117
|
+
|
|
118
|
+
const trackClass = $derived(
|
|
119
|
+
cx(
|
|
120
|
+
"absolute top-1/2 -translate-y-1/2 w-full bg-[var(--color-bg-muted)] rounded overflow-hidden",
|
|
121
|
+
sizeTrack
|
|
122
|
+
)
|
|
123
|
+
);
|
|
124
|
+
|
|
125
|
+
const rangeBase =
|
|
126
|
+
"relative z-10 w-full h-10 bg-transparent appearance-none cursor-pointer disabled:cursor-not-allowed " +
|
|
127
|
+
"[&::-webkit-slider-thumb]:appearance-none " +
|
|
128
|
+
"[&::-webkit-slider-thumb]:rounded-[var(--radius-full)] " +
|
|
129
|
+
"[&::-webkit-slider-thumb]:border-2 [&::-webkit-slider-thumb]:border-[var(--color-bg-surface)] " +
|
|
130
|
+
"[&::-webkit-slider-thumb]:shadow-[var(--shadow-color)] " +
|
|
131
|
+
"[&::-moz-range-thumb]:rounded-[var(--radius-full)] " +
|
|
132
|
+
"[&::-moz-range-thumb]:border-2 [&::-moz-range-thumb]:border-[var(--color-bg-surface)] " +
|
|
133
|
+
"[&::-moz-range-thumb]:shadow-[var(--shadow-color)] " +
|
|
134
|
+
"hover:[&::-webkit-slider-thumb]:scale-110 active:[&::-webkit-slider-thumb]:scale-125 " +
|
|
135
|
+
"hover:[&::-moz-range-thumb]:scale-110 active:[&::-moz-range-thumb]:scale-125 " +
|
|
136
|
+
"focus:outline-none focus-visible:outline-none " +
|
|
137
|
+
"focus-visible:[&::-webkit-slider-thumb]:shadow-[0_0_0_3px_var(--border-color-focus)] " +
|
|
138
|
+
"focus-visible:[&::-moz-range-thumb]:shadow-[0_0_0_3px_var(--border-color-focus)] " +
|
|
139
|
+
"disabled:hover:[&::-webkit-slider-thumb]:scale-100 disabled:active:[&::-webkit-slider-thumb]:scale-100 " +
|
|
140
|
+
"disabled:hover:[&::-moz-range-thumb]:scale-100 disabled:active:[&::-moz-range-thumb]:scale-100";
|
|
141
|
+
|
|
142
|
+
const rangeClass = $derived(cx(rangeBase, sizeThumb, thumb));
|
|
143
|
+
|
|
144
|
+
function snap(v: number) {
|
|
145
|
+
const s = step || 1;
|
|
146
|
+
const ticks = Math.round((v - min) / s);
|
|
147
|
+
const snapped = min + ticks * s;
|
|
148
|
+
return clamp(Number(snapped.toFixed(6)), min, max);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
let wrap: HTMLDivElement;
|
|
152
|
+
|
|
153
|
+
function setFromClientX(x: number) {
|
|
154
|
+
const r = wrap.getBoundingClientRect();
|
|
155
|
+
const ratio = clamp((x - r.left) / r.width, 0, 1);
|
|
156
|
+
value = snap(min + ratio * (max - min));
|
|
157
|
+
onInput?.(value);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function onTrackClick(e: MouseEvent) {
|
|
161
|
+
if (disabled) return;
|
|
162
|
+
setFromClientX(e.clientX);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
function onRangeInput(e: Event) {
|
|
166
|
+
const t = e.currentTarget as HTMLInputElement;
|
|
167
|
+
value = snap(Number(t.value));
|
|
168
|
+
onInput?.(value);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
function onKey(e: KeyboardEvent) {
|
|
172
|
+
if (disabled) return;
|
|
173
|
+
const span = max - min;
|
|
174
|
+
const big = Math.max(step || 1, Math.round(span / 10));
|
|
175
|
+
let v: number;
|
|
176
|
+
|
|
177
|
+
switch (e.key) {
|
|
178
|
+
case "ArrowLeft":
|
|
179
|
+
case "ArrowDown":
|
|
180
|
+
v = value - (step || 1);
|
|
181
|
+
break;
|
|
182
|
+
case "ArrowRight":
|
|
183
|
+
case "ArrowUp":
|
|
184
|
+
v = value + (step || 1);
|
|
185
|
+
break;
|
|
186
|
+
case "PageDown":
|
|
187
|
+
v = value - big;
|
|
188
|
+
break;
|
|
189
|
+
case "PageUp":
|
|
190
|
+
v = value + big;
|
|
191
|
+
break;
|
|
192
|
+
case "Home":
|
|
193
|
+
v = min;
|
|
194
|
+
break;
|
|
195
|
+
case "End":
|
|
196
|
+
v = max;
|
|
197
|
+
break;
|
|
198
|
+
default:
|
|
199
|
+
return;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
value = snap(v);
|
|
203
|
+
onInput?.(value);
|
|
204
|
+
e.preventDefault();
|
|
205
|
+
}
|
|
206
|
+
</script>
|
|
207
|
+
|
|
208
|
+
<div
|
|
209
|
+
bind:this={wrap}
|
|
210
|
+
class={rootClass}
|
|
211
|
+
onclick={onTrackClick}
|
|
212
|
+
onfocusin={(e) => (rest as unknown as { onfocus?: (e: FocusEvent) => void }).onfocus?.(e as FocusEvent)}
|
|
213
|
+
onfocusout={(e) => (rest as unknown as { onblur?: (e: FocusEvent) => void }).onblur?.(e as FocusEvent)}
|
|
214
|
+
data-disabled={disabled ? "true" : undefined}
|
|
215
|
+
{...rest}
|
|
216
|
+
>
|
|
217
|
+
<div class="relative flex items-center w-full h-10">
|
|
218
|
+
<div class={trackClass}>
|
|
219
|
+
<div
|
|
220
|
+
class={cx("w-full h-full transition-[width]", bar)}
|
|
221
|
+
style={`width:${pct}%`}
|
|
222
|
+
></div>
|
|
223
|
+
</div>
|
|
224
|
+
|
|
225
|
+
<input
|
|
226
|
+
type="range"
|
|
227
|
+
class={rangeClass}
|
|
228
|
+
{min}
|
|
229
|
+
{max}
|
|
230
|
+
{step}
|
|
231
|
+
{value}
|
|
232
|
+
oninput={onRangeInput}
|
|
233
|
+
onkeydown={onKey}
|
|
234
|
+
{disabled}
|
|
235
|
+
aria-label="slider"
|
|
236
|
+
aria-valuemin={min}
|
|
237
|
+
aria-valuemax={max}
|
|
238
|
+
aria-valuenow={value}
|
|
239
|
+
/>
|
|
240
|
+
</div>
|
|
241
|
+
|
|
242
|
+
{#if showValue}
|
|
243
|
+
<div class="flex justify-end mt-1">
|
|
244
|
+
<div
|
|
245
|
+
class="font-[var(--font-mono)] text-[var(--color-text-muted)] select-none {TEXT[
|
|
246
|
+
sz
|
|
247
|
+
]}"
|
|
248
|
+
>
|
|
249
|
+
{value}
|
|
250
|
+
</div>
|
|
251
|
+
</div>
|
|
252
|
+
{/if}
|
|
253
|
+
</div>
|