svelte-comp 1.2.5 → 1.2.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.
Files changed (84) hide show
  1. package/README.md +1 -1
  2. package/package.json +1 -1
  3. package/dist/App.svelte +0 -551
  4. package/dist/App.svelte.d.ts +0 -3
  5. package/dist/Container.svelte +0 -60
  6. package/dist/Container.svelte.d.ts +0 -12
  7. package/dist/app.css +0 -235
  8. package/dist/index.d.ts +0 -5
  9. package/dist/index.js +0 -6
  10. package/dist/lang.d.ts +0 -1081
  11. package/dist/lang.js +0 -1096
  12. package/dist/lib/Accordion.svelte +0 -155
  13. package/dist/lib/Accordion.svelte.d.ts +0 -40
  14. package/dist/lib/Button.svelte +0 -170
  15. package/dist/lib/Button.svelte.d.ts +0 -53
  16. package/dist/lib/Card.svelte +0 -103
  17. package/dist/lib/Card.svelte.d.ts +0 -42
  18. package/dist/lib/Carousel.svelte +0 -293
  19. package/dist/lib/Carousel.svelte.d.ts +0 -13
  20. package/dist/lib/CheckBox.svelte +0 -210
  21. package/dist/lib/CheckBox.svelte.d.ts +0 -53
  22. package/dist/lib/CodeView.svelte +0 -307
  23. package/dist/lib/CodeView.svelte.d.ts +0 -64
  24. package/dist/lib/ColorPicker.svelte +0 -161
  25. package/dist/lib/ColorPicker.svelte.d.ts +0 -40
  26. package/dist/lib/DatePicker.svelte +0 -170
  27. package/dist/lib/DatePicker.svelte.d.ts +0 -53
  28. package/dist/lib/Dialog.svelte +0 -235
  29. package/dist/lib/Dialog.svelte.d.ts +0 -58
  30. package/dist/lib/Field.svelte +0 -299
  31. package/dist/lib/Field.svelte.d.ts +0 -8
  32. package/dist/lib/FilePicker.svelte +0 -241
  33. package/dist/lib/FilePicker.svelte.d.ts +0 -52
  34. package/dist/lib/Form.svelte +0 -438
  35. package/dist/lib/Form.svelte.d.ts +0 -20
  36. package/dist/lib/Hamburger.svelte +0 -211
  37. package/dist/lib/Hamburger.svelte.d.ts +0 -52
  38. package/dist/lib/Menu.svelte +0 -623
  39. package/dist/lib/Menu.svelte.d.ts +0 -33
  40. package/dist/lib/PaginatedCard.svelte +0 -73
  41. package/dist/lib/PaginatedCard.svelte.d.ts +0 -11
  42. package/dist/lib/Pagination.svelte +0 -119
  43. package/dist/lib/Pagination.svelte.d.ts +0 -9
  44. package/dist/lib/PrimaryColorSelect.svelte +0 -113
  45. package/dist/lib/PrimaryColorSelect.svelte.d.ts +0 -9
  46. package/dist/lib/ProgressBar.svelte +0 -141
  47. package/dist/lib/ProgressBar.svelte.d.ts +0 -48
  48. package/dist/lib/ProgressCircle.svelte +0 -192
  49. package/dist/lib/ProgressCircle.svelte.d.ts +0 -39
  50. package/dist/lib/Radio.svelte +0 -189
  51. package/dist/lib/Radio.svelte.d.ts +0 -55
  52. package/dist/lib/SearchInput.svelte +0 -106
  53. package/dist/lib/SearchInput.svelte.d.ts +0 -13
  54. package/dist/lib/Select.svelte +0 -524
  55. package/dist/lib/Select.svelte.d.ts +0 -21
  56. package/dist/lib/Slider.svelte +0 -253
  57. package/dist/lib/Slider.svelte.d.ts +0 -56
  58. package/dist/lib/Splitter.svelte +0 -150
  59. package/dist/lib/Splitter.svelte.d.ts +0 -43
  60. package/dist/lib/Switch.svelte +0 -167
  61. package/dist/lib/Switch.svelte.d.ts +0 -42
  62. package/dist/lib/Table.svelte +0 -299
  63. package/dist/lib/Table.svelte.d.ts +0 -17
  64. package/dist/lib/Tabs.svelte +0 -213
  65. package/dist/lib/Tabs.svelte.d.ts +0 -48
  66. package/dist/lib/ThemeToggle.svelte +0 -127
  67. package/dist/lib/ThemeToggle.svelte.d.ts +0 -32
  68. package/dist/lib/TimePicker.svelte +0 -269
  69. package/dist/lib/TimePicker.svelte.d.ts +0 -48
  70. package/dist/lib/Toast.svelte +0 -226
  71. package/dist/lib/Toast.svelte.d.ts +0 -14
  72. package/dist/lib/Tooltip.svelte +0 -110
  73. package/dist/lib/Tooltip.svelte.d.ts +0 -40
  74. package/dist/lib/index.d.ts +0 -32
  75. package/dist/lib/index.js +0 -33
  76. package/dist/lib/lang.d.ts +0 -158
  77. package/dist/lib/lang.js +0 -150
  78. package/dist/lib/types/index.d.ts +0 -111
  79. package/dist/lib/types/index.js +0 -26
  80. package/dist/main.d.ts +0 -3
  81. package/dist/main.js +0 -7
  82. package/dist/styles.css +0 -232
  83. package/dist/utils/index.d.ts +0 -34
  84. package/dist/utils/index.js +0 -268
@@ -1,253 +0,0 @@
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 = value;
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,56 +0,0 @@
1
- /**
2
- * @component Slider
3
- * @description A customizable slider component for selecting a value from a range.
4
- *
5
- * @prop value {number} - The current value (bindable)
6
- * @default 0
7
- *
8
- * @prop min {number} - Minimum value
9
- * @default 0
10
- *
11
- * @prop max {number} - Maximum value
12
- * @default 100
13
- *
14
- * @prop step {number} - Step size
15
- * @default 1
16
- *
17
- * @prop sz {SizeKey} - Slider size
18
- * @options xs|sm|md|lg|xl
19
- * @default md
20
- *
21
- * @prop variant {ComponentVariant} - Color variant
22
- * @options default|neutral
23
- * @default default
24
- *
25
- * @prop disabled {boolean} - Disable the slider
26
- * @default false
27
- *
28
- * @prop showValue {boolean} - Show the current value
29
- * @default false
30
- *
31
- * @prop onInput {(value: number) => void} - Fires on value change
32
- *
33
- * @prop class {string} - Custom wrapper classes
34
- * @default ""
35
- *
36
- * @note Works with both keyboard and mouse.
37
- * @note Size and variant control appearance.
38
- * @note Uses proper ARIA attributes.
39
- */
40
- import type { HTMLAttributes } from "svelte/elements";
41
- import type { SizeKey, ComponentVariant } from "./types";
42
- type Props = HTMLAttributes<HTMLDivElement> & {
43
- value?: number;
44
- min?: number;
45
- max?: number;
46
- step?: number;
47
- sz?: SizeKey;
48
- variant?: ComponentVariant;
49
- disabled?: boolean;
50
- showValue?: boolean;
51
- onInput?: (value: number) => void;
52
- class?: string;
53
- };
54
- declare const Slider: import("svelte").Component<Props, {}, "value">;
55
- type Slider = ReturnType<typeof Slider>;
56
- export default Slider;
@@ -1,150 +0,0 @@
1
- <!-- src/lib/Splitter.svelte -->
2
- <script lang="ts">
3
- /**
4
- * @component Splitter
5
- * @description Resizable split panel container with horizontal or vertical orientation.
6
- *
7
- * @prop direction {'horizontal' | 'vertical'} - Split orientation
8
- * @options horizontal|vertical
9
- * @default horizontal
10
- *
11
- * @prop initialSize {number} - Initial size of the first panel as percentage
12
- * @default 30
13
- *
14
- * @prop dividerSize {number} - Width/height of the divider handle in pixels
15
- * @default 4
16
- *
17
- * @prop minSize {number} - Minimum size of the first panel as percentage
18
- * @default 10
19
- *
20
- * @prop maxSize {number} - Maximum size of the first panel as percentage
21
- * @default 90
22
- *
23
- * @prop first {Snippet} - Content for the first panel
24
- *
25
- * @prop second {Snippet} - Content for the second panel
26
- *
27
- * @note Uses pointer events for smooth dragging with proper event delegation
28
- * @note Responsive - automatically adjusts to container resize
29
- * @note Accessible with proper cursor hints and hover states
30
- * @note No wrapper elements - panels render directly for clean DOM structure
31
- * @note Supports any content type through snippet rendering
32
- */
33
-
34
- import type { Snippet } from "svelte";
35
-
36
- type Props = {
37
- direction?: "horizontal" | "vertical";
38
- initialSize?: number;
39
- dividerSize?: number;
40
- minSize?: number;
41
- maxSize?: number;
42
- first?: Snippet;
43
- second?: Snippet;
44
- };
45
-
46
- let {
47
- direction = "horizontal",
48
- initialSize = 30,
49
- dividerSize = 4,
50
- minSize = 10,
51
- maxSize = 90,
52
- first,
53
- second,
54
- }: Props = $props();
55
-
56
- let container: HTMLElement;
57
-
58
- let size = $derived(initialSize);
59
- let isDragging = $state(false);
60
- let startSize = 0;
61
- let startPos = 0;
62
- let containerSize = 0;
63
-
64
- function getContainerSize(): number {
65
- if (!container) return 0;
66
- return direction === "horizontal"
67
- ? container.offsetWidth
68
- : container.offsetHeight;
69
- }
70
-
71
- function startDrag(e: PointerEvent): void {
72
- isDragging = true;
73
- startSize = size;
74
- startPos = direction === "horizontal" ? e.clientX : e.clientY;
75
- containerSize = getContainerSize();
76
-
77
- document.addEventListener("pointermove", onDrag);
78
- document.addEventListener("pointerup", stopDrag);
79
- document.addEventListener("pointercancel", stopDrag);
80
-
81
- e.preventDefault();
82
- e.stopPropagation();
83
- }
84
-
85
- function onDrag(e: PointerEvent): void {
86
- if (!isDragging) return;
87
-
88
- const currentPos = direction === "horizontal" ? e.clientX : e.clientY;
89
- const delta = currentPos - startPos;
90
- const deltaPercent = (delta / containerSize) * 100;
91
- const newSize = startSize + deltaPercent;
92
-
93
- size = Math.max(minSize, Math.min(maxSize, newSize));
94
-
95
- e.preventDefault();
96
- e.stopPropagation();
97
- }
98
-
99
- function stopDrag(): void {
100
- isDragging = false;
101
- document.removeEventListener("pointermove", onDrag);
102
- document.removeEventListener("pointerup", stopDrag);
103
- document.removeEventListener("pointercancel", stopDrag);
104
- }
105
-
106
- $effect(() => {
107
- const onResize = () => {
108
- containerSize = getContainerSize();
109
- };
110
- window.addEventListener("resize", onResize);
111
- return () => window.removeEventListener("resize", onResize);
112
- });
113
- </script>
114
-
115
- <div
116
- bind:this={container}
117
- class="w-full h-full overflow-hidden"
118
- class:flex={direction === "horizontal"}
119
- class:flex-col={direction === "vertical"}
120
- >
121
- {#if direction === "horizontal"}
122
- <div class="overflow-auto min-w-0 min-h-0" style="width: {size}%">
123
- {@render first?.()}
124
- </div>
125
-
126
- <div
127
- class="touch-none select-none z-10 cursor-col-resize bg-[var(--color-bg-muted)] hover:bg-[var(--color-bg-hover)] transition-colors"
128
- style="width: {dividerSize}px"
129
- onpointerdown={startDrag}
130
- ></div>
131
-
132
- <div class="overflow-auto min-w-0 min-h-0 flex-1">
133
- {@render second?.()}
134
- </div>
135
- {:else}
136
- <div class="overflow-auto min-w-0 min-h-0" style="height: {size}%">
137
- {@render first?.()}
138
- </div>
139
-
140
- <div
141
- class="touch-none select-none z-10 cursor-row-resize bg-[var(--color-bg-muted)] hover:bg-[var(--color-bg-hover)] transition-colors"
142
- style="height: {dividerSize}px"
143
- onpointerdown={startDrag}
144
- ></div>
145
-
146
- <div class="overflow-auto min-w-0 min-h-0" style="height: calc(100% - {size}% - {dividerSize}px)">
147
- {@render second?.()}
148
- </div>
149
- {/if}
150
- </div>
@@ -1,43 +0,0 @@
1
- /**
2
- * @component Splitter
3
- * @description Resizable split panel container with horizontal or vertical orientation.
4
- *
5
- * @prop direction {'horizontal' | 'vertical'} - Split orientation
6
- * @options horizontal|vertical
7
- * @default horizontal
8
- *
9
- * @prop initialSize {number} - Initial size of the first panel as percentage
10
- * @default 30
11
- *
12
- * @prop dividerSize {number} - Width/height of the divider handle in pixels
13
- * @default 4
14
- *
15
- * @prop minSize {number} - Minimum size of the first panel as percentage
16
- * @default 10
17
- *
18
- * @prop maxSize {number} - Maximum size of the first panel as percentage
19
- * @default 90
20
- *
21
- * @prop first {Snippet} - Content for the first panel
22
- *
23
- * @prop second {Snippet} - Content for the second panel
24
- *
25
- * @note Uses pointer events for smooth dragging with proper event delegation
26
- * @note Responsive - automatically adjusts to container resize
27
- * @note Accessible with proper cursor hints and hover states
28
- * @note No wrapper elements - panels render directly for clean DOM structure
29
- * @note Supports any content type through snippet rendering
30
- */
31
- import type { Snippet } from "svelte";
32
- type Props = {
33
- direction?: "horizontal" | "vertical";
34
- initialSize?: number;
35
- dividerSize?: number;
36
- minSize?: number;
37
- maxSize?: number;
38
- first?: Snippet;
39
- second?: Snippet;
40
- };
41
- declare const Splitter: import("svelte").Component<Props, {}, "">;
42
- type Splitter = ReturnType<typeof Splitter>;
43
- export default Splitter;
@@ -1,167 +0,0 @@
1
- <!-- src/lib/Switch.svelte -->
2
- <script lang="ts">
3
- /**
4
- * @component Switch
5
- * @description A compact toggle switch component built on top of a native `<input type="checkbox">`. Supports optional labels around the control and works naturally with `bind:checked`.
6
- *
7
- * @prop sz {SizeKey} - Size preset for the control
8
- * @options xs|sm|md|lg|xl
9
- * @default md
10
- *
11
- * @prop checked {boolean} - Current state (bindable)
12
- * @default false
13
- *
14
- * @prop leftLabel {string} - Optional label rendered on the left side
15
- *
16
- * @prop rightLabel {string} - Optional label rendered on the right side
17
- *
18
- * @prop topLabel {string} - Optional label placed above the switch
19
- *
20
- * @prop onChange {(v: boolean) => void} - Fired on toggle with the new value
21
- *
22
- * @prop class {string} - External wrapper classes
23
- * @default ""
24
- *
25
- * @note Built over a real checkbox so browser accessibility comes for free: keyboard (Space/Enter), focus ring, and screen reader semantics.
26
- * @note Labels do not affect the actual checkbox hitbox, but the whole area is clickable if wrapped correctly.
27
- * @note Size preset adjusts track width, knob size, and spacing.
28
- * @note Reflects `disabled` by dimming visuals and removing pointer events.
29
- * @note The component keeps no internal state besides the bound `checked` value, so it's predictable in forms and controlled UI.
30
- */
31
- import type { HTMLInputAttributes } from "svelte/elements";
32
- import type { SizeKey } from "./types";
33
- import { TEXT } from "./types";
34
- import { cx, uid } from "../utils";
35
-
36
- type Props = HTMLInputAttributes & {
37
- sz?: SizeKey;
38
- checked?: boolean;
39
- leftLabel?: string;
40
- rightLabel?: string;
41
- topLabel?: string;
42
- onChange?: (v: boolean) => void;
43
- class?: string;
44
- };
45
-
46
- let {
47
- sz = "md",
48
- checked = $bindable(false),
49
- leftLabel,
50
- rightLabel,
51
- topLabel,
52
- onChange,
53
- class: externalClass = "",
54
- ...rest
55
- }: Props = $props();
56
-
57
- const inputId = $derived(rest.id ?? uid("sw-"));
58
-
59
- const track = {
60
- xs: "w-8 h-4",
61
- sm: "w-10 h-5",
62
- md: "w-12 h-6",
63
- lg: "w-14 h-7",
64
- xl: "w-16 h-8",
65
- } as const;
66
-
67
- const knob = {
68
- xs: "h-[14px] w-[14px]",
69
- sm: "h-[17px] w-[17px]",
70
- md: "h-5 w-5",
71
- lg: "h-6 w-6",
72
- xl: "h-7 w-7",
73
- } as const;
74
-
75
- const pad = {
76
- xs: "p-[1px]",
77
- sm: "p-[1.5px]",
78
- md: "p-[2px]",
79
- lg: "p-[2px]",
80
- xl: "p-[2px]",
81
- } as const;
82
-
83
- const isDisabled = $derived(
84
- "disabled" in rest ? Boolean(rest.disabled) : false
85
- );
86
-
87
- function toggle() {
88
- if (isDisabled) return;
89
- checked = !checked;
90
- onChange?.(checked);
91
- }
92
-
93
- const justifyClass = $derived(checked ? "justify-end" : "justify-start");
94
-
95
- const trackClass = $derived(
96
- cx(
97
- "relative inline-flex items-center rounded-full border transition focus:outline-none focus:ring-2 focus:ring-[var(--border-color-focus)]",
98
- track[sz],
99
- pad[sz],
100
- checked
101
- ? "bg-[var(--color-bg-primary)] border-[var(--color-bg-primary)]"
102
- : "bg-[var(--color-bg-muted)] border-[var(--border-color-default)]",
103
- isDisabled
104
- ? "opacity-[var(--opacity-disabled)] cursor-not-allowed"
105
- : "cursor-pointer",
106
- justifyClass
107
- )
108
- );
109
-
110
- const rootClass = $derived(
111
- cx("inline-flex flex-col items-center gap-1 select-none", externalClass)
112
- );
113
-
114
- const knobClass = $derived(
115
- cx(
116
- knob[sz],
117
- "rounded-full bg-white border border-[var(--border-color-default)] shadow-sm"
118
- )
119
- );
120
- </script>
121
-
122
- <label class={rootClass} for={inputId}>
123
- {#if topLabel}
124
- <span class="text-[var(--color-text-muted)] {TEXT[sz]}">
125
- {topLabel}
126
- </span>
127
- {/if}
128
-
129
- <div class="inline-flex items-center gap-2">
130
- {#if leftLabel}
131
- <span class="text-[var(--color-text-muted)] {TEXT[sz]}">
132
- {leftLabel}
133
- </span>
134
- {/if}
135
-
136
- <input
137
- id={inputId}
138
- type="checkbox"
139
- {checked}
140
- {...rest}
141
- class="sr-only"
142
- aria-checked={checked}
143
- aria-invalid={rest["aria-invalid"] || undefined}
144
- aria-describedby={rest["aria-describedby"]}
145
- onchange={() => toggle()}
146
- />
147
-
148
- <button
149
- type="button"
150
- class={trackClass}
151
- onclick={toggle}
152
- aria-pressed={checked}
153
- aria-label={typeof rest["aria-label"] === "string"
154
- ? rest["aria-label"]
155
- : "Switch"}
156
- disabled={isDisabled}
157
- >
158
- <span class={knobClass}></span>
159
- </button>
160
-
161
- {#if rightLabel}
162
- <span class="text-[var(--color-text-muted)] {TEXT[sz]}">
163
- {rightLabel}
164
- </span>
165
- {/if}
166
- </div>
167
- </label>