@spaethtech/svelte-ui 0.15.1-dev.67.fbf3875 → 0.15.1-dev.71.e357f8b
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/.claude/skills/svelte-ui/SKILL.md +17 -12
- package/dist/components/ButtonGroup/ButtonGroup.svelte +47 -2
- package/dist/components/ButtonGroup/ButtonGroup.svelte.d.ts +11 -1
- package/dist/components/Combobox/Combobox.svelte +62 -31
- package/dist/components/Combobox/Combobox.svelte.d.ts +1 -1
- package/dist/components/Menu.svelte +35 -27
- package/dist/components/Pagination/Pagination.svelte +58 -43
- package/dist/components/Pagination/Pagination.svelte.d.ts +7 -2
- package/dist/components/Rating.svelte +169 -30
- package/dist/components/Rating.svelte.d.ts +22 -4
- package/dist/components/Slider/Slider.svelte +5 -3
- package/dist/components/TokenInput/TokenInput.svelte +12 -6
- package/dist/data/table/types.d.ts +9 -3
- package/dist/index.d.ts +1 -4
- package/dist/index.js +0 -2
- package/docs/components.md +31 -26
- package/docs/usage.md +27 -13
- package/package.json +1 -1
- package/dist/components/DateRangePicker/DateRangePicker.svelte +0 -157
- package/dist/components/DateRangePicker/DateRangePicker.svelte.d.ts +0 -31
- package/dist/components/DateRangePicker/index.d.ts +0 -2
- package/dist/components/DateRangePicker/index.js +0 -1
|
@@ -1,47 +1,168 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
/**
|
|
3
|
+
* Rating — star rating in two modes:
|
|
4
|
+
* • Display (default): a read-only average. `average` is a 0–10 score (TMDB-style), shown as 5 stars
|
|
5
|
+
* with halves, plus an optional `count`.
|
|
6
|
+
* • Interactive (`interactive`): a form control — click / hover / arrow-key a `bind:value` (0..max
|
|
7
|
+
* stars), optional half-steps. `role="slider"` (same a11y pattern as our Slider).
|
|
8
|
+
* See usage docs.
|
|
9
|
+
*/
|
|
10
|
+
-->
|
|
1
11
|
<script lang="ts">
|
|
2
12
|
import { variantToken, type Variant } from "../types/variants.js";
|
|
3
13
|
import type { Size } from "../types/sizes.js";
|
|
4
14
|
|
|
5
15
|
export type RatingProps = {
|
|
6
|
-
|
|
7
|
-
|
|
16
|
+
/** Display mode: a 0–10 average score (TMDB-style), rendered as 5 stars (average / 2). */
|
|
17
|
+
average?: number;
|
|
18
|
+
/** Display mode: number of ratings, shown in parentheses. */
|
|
19
|
+
count?: number;
|
|
20
|
+
/** Interactive mode: the selected rating in stars (0..max), bindable. */
|
|
21
|
+
value?: number;
|
|
22
|
+
/** Enable selection (click / hover / keyboard). Off = read-only display. */
|
|
23
|
+
interactive?: boolean;
|
|
24
|
+
/** Interactive but not editable (shows `value`, no input). */
|
|
25
|
+
readonly?: boolean;
|
|
26
|
+
disabled?: boolean;
|
|
27
|
+
/** Number of stars in interactive mode (default 5). */
|
|
28
|
+
max?: number;
|
|
29
|
+
/** Allow half-star selection/display (interactive). */
|
|
30
|
+
allowHalf?: boolean;
|
|
31
|
+
/** Show the numeric value beside the stars (default: true in display mode, false interactive). */
|
|
32
|
+
showValue?: boolean;
|
|
33
|
+
/** Fired on change (interactive). */
|
|
34
|
+
onchange?: (value: number) => void;
|
|
8
35
|
/** Filled-star colour. Defaults to gold; a variant tints from its token (e.g. `warning`, `primary`). */
|
|
9
36
|
variant?: Variant;
|
|
10
|
-
/** Star size (sm/md/lg). */
|
|
11
37
|
size?: Size;
|
|
38
|
+
/** Accessible label for the interactive slider. */
|
|
39
|
+
label?: string;
|
|
12
40
|
};
|
|
13
41
|
|
|
14
|
-
let {
|
|
42
|
+
let {
|
|
43
|
+
average,
|
|
44
|
+
count,
|
|
45
|
+
value = $bindable(0),
|
|
46
|
+
interactive = false,
|
|
47
|
+
readonly = false,
|
|
48
|
+
disabled = false,
|
|
49
|
+
max = 5,
|
|
50
|
+
allowHalf = false,
|
|
51
|
+
showValue,
|
|
52
|
+
onchange,
|
|
53
|
+
variant,
|
|
54
|
+
size = "md",
|
|
55
|
+
label = "Rating",
|
|
56
|
+
}: RatingProps = $props();
|
|
15
57
|
|
|
16
58
|
const starColor = $derived(variant ? `var(${variantToken[variant]})` : "#ffd700");
|
|
17
59
|
const starSize: Record<Size, string> = { sm: "1rem", md: "1.2rem", lg: "1.5rem" };
|
|
18
60
|
|
|
19
|
-
|
|
20
|
-
const
|
|
61
|
+
const active = $derived(interactive && !readonly && !disabled);
|
|
62
|
+
const step = $derived(allowHalf ? 0.5 : 1);
|
|
21
63
|
|
|
22
|
-
//
|
|
23
|
-
const
|
|
64
|
+
// Display mode: sanitize the 0–10 average and convert to a 5-point scale.
|
|
65
|
+
const displayFive = $derived.by(() => {
|
|
66
|
+
const a = isNaN(average ?? NaN) || (average ?? 0) < 0 ? 0 : Math.min(average ?? 0, 10);
|
|
67
|
+
return a / 2;
|
|
68
|
+
});
|
|
24
69
|
|
|
25
|
-
//
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
const
|
|
70
|
+
// Hover preview (interactive) overrides `value` while pointing.
|
|
71
|
+
let hover = $state<number | null>(null);
|
|
72
|
+
|
|
73
|
+
const starCount = $derived(interactive ? max : 5);
|
|
74
|
+
// The numeric value the stars are painted from.
|
|
75
|
+
const paint = $derived(interactive ? (hover ?? value) : displayFive);
|
|
76
|
+
const readout = $derived(interactive ? value : displayFive);
|
|
77
|
+
const wantValue = $derived(showValue ?? !interactive);
|
|
78
|
+
|
|
79
|
+
// Fill state for the i-th star (1-based).
|
|
80
|
+
function fillOf(i: number): "full" | "half" | "empty" {
|
|
81
|
+
if (paint >= i) return "full";
|
|
82
|
+
if (paint >= i - 0.5) return "half";
|
|
83
|
+
return "empty";
|
|
84
|
+
}
|
|
85
|
+
// Half-step from the pointer's position within a star (left half = i-0.5).
|
|
86
|
+
function valueAt(i: number, e: MouseEvent): number {
|
|
87
|
+
if (!allowHalf) return i;
|
|
88
|
+
const r = (e.currentTarget as HTMLElement).getBoundingClientRect();
|
|
89
|
+
return e.clientX - r.left < r.width / 2 ? i - 0.5 : i;
|
|
90
|
+
}
|
|
91
|
+
function pick(i: number, e: MouseEvent) {
|
|
92
|
+
if (!active) return;
|
|
93
|
+
value = valueAt(i, e);
|
|
94
|
+
onchange?.(value);
|
|
95
|
+
}
|
|
96
|
+
function preview(i: number, e: MouseEvent) {
|
|
97
|
+
if (!active) return;
|
|
98
|
+
hover = valueAt(i, e);
|
|
99
|
+
}
|
|
100
|
+
function onkeydown(e: KeyboardEvent) {
|
|
101
|
+
if (!active) return;
|
|
102
|
+
let v = value;
|
|
103
|
+
switch (e.key) {
|
|
104
|
+
case "ArrowRight":
|
|
105
|
+
case "ArrowUp":
|
|
106
|
+
v = Math.min(max, value + step);
|
|
107
|
+
break;
|
|
108
|
+
case "ArrowLeft":
|
|
109
|
+
case "ArrowDown":
|
|
110
|
+
v = Math.max(0, value - step);
|
|
111
|
+
break;
|
|
112
|
+
case "Home":
|
|
113
|
+
v = 0;
|
|
114
|
+
break;
|
|
115
|
+
case "End":
|
|
116
|
+
v = max;
|
|
117
|
+
break;
|
|
118
|
+
default:
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
e.preventDefault();
|
|
122
|
+
value = v;
|
|
123
|
+
onchange?.(v);
|
|
124
|
+
}
|
|
29
125
|
</script>
|
|
30
126
|
|
|
31
|
-
<div
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
{
|
|
40
|
-
|
|
127
|
+
<div
|
|
128
|
+
class="rating select-none"
|
|
129
|
+
class:disabled
|
|
130
|
+
style="--star-color: {starColor}; --star-size: {starSize[size]}"
|
|
131
|
+
>
|
|
132
|
+
<!-- svelte-ignore a11y_no_noninteractive_tabindex -->
|
|
133
|
+
<div
|
|
134
|
+
class="stars"
|
|
135
|
+
role={active ? "slider" : undefined}
|
|
136
|
+
tabindex={active ? 0 : undefined}
|
|
137
|
+
aria-label={active ? label : undefined}
|
|
138
|
+
aria-valuemin={active ? 0 : undefined}
|
|
139
|
+
aria-valuemax={active ? max : undefined}
|
|
140
|
+
aria-valuenow={active ? value : undefined}
|
|
141
|
+
aria-valuetext={active ? `${value} of ${max} stars` : undefined}
|
|
142
|
+
aria-readonly={interactive && readonly ? true : undefined}
|
|
143
|
+
aria-disabled={disabled || undefined}
|
|
144
|
+
onkeydown={active ? onkeydown : undefined}
|
|
145
|
+
onpointerleave={active ? () => (hover = null) : undefined}
|
|
146
|
+
>
|
|
147
|
+
{#each Array(starCount) as _, idx (idx)}
|
|
148
|
+
{@const i = idx + 1}
|
|
149
|
+
{@const f = fillOf(i)}
|
|
150
|
+
<!-- svelte-ignore a11y_click_events_have_key_events a11y_no_static_element_interactions -->
|
|
151
|
+
<span
|
|
152
|
+
class="star {f} {active ? 'cursor-pointer' : ''}"
|
|
153
|
+
onpointermove={active ? (e) => preview(i, e) : undefined}
|
|
154
|
+
onclick={active ? (e) => pick(i, e) : undefined}
|
|
155
|
+
>
|
|
156
|
+
{f === "empty" ? "☆" : "★"}
|
|
157
|
+
</span>
|
|
41
158
|
{/each}
|
|
42
159
|
</div>
|
|
43
|
-
|
|
44
|
-
|
|
160
|
+
{#if wantValue}
|
|
161
|
+
<span class="rating-value">{readout.toFixed(1)}</span>
|
|
162
|
+
{/if}
|
|
163
|
+
{#if !interactive && count != null}
|
|
164
|
+
<span class="rating-count">({count})</span>
|
|
165
|
+
{/if}
|
|
45
166
|
</div>
|
|
46
167
|
|
|
47
168
|
<style>
|
|
@@ -50,30 +171,48 @@
|
|
|
50
171
|
align-items: center;
|
|
51
172
|
gap: 0.5rem;
|
|
52
173
|
}
|
|
174
|
+
.rating.disabled {
|
|
175
|
+
opacity: 0.5;
|
|
176
|
+
pointer-events: none;
|
|
177
|
+
}
|
|
53
178
|
|
|
54
179
|
.stars {
|
|
55
180
|
display: flex;
|
|
56
181
|
gap: 0.1rem;
|
|
57
182
|
}
|
|
183
|
+
.stars[role="slider"] {
|
|
184
|
+
border-radius: 4px;
|
|
185
|
+
}
|
|
186
|
+
.stars[role="slider"]:focus-visible {
|
|
187
|
+
outline: 2px solid color-mix(in srgb, var(--ui-color-text) 70%, transparent);
|
|
188
|
+
outline-offset: 2px;
|
|
189
|
+
}
|
|
58
190
|
|
|
59
191
|
.star {
|
|
60
192
|
font-size: var(--star-size, 1.2rem);
|
|
61
193
|
line-height: 1;
|
|
62
194
|
}
|
|
63
195
|
|
|
64
|
-
.star.
|
|
65
|
-
color: var(--star-color, #ffd700);
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
.star.half-filled {
|
|
196
|
+
.star.full {
|
|
69
197
|
color: var(--star-color, #ffd700);
|
|
70
|
-
opacity: 0.6;
|
|
71
198
|
}
|
|
72
199
|
|
|
73
200
|
.star.empty {
|
|
74
201
|
color: var(--ui-color-secondary);
|
|
75
202
|
}
|
|
76
203
|
|
|
204
|
+
/* Half star: a full ★ glyph clipped to a left-gold / right-muted gradient. */
|
|
205
|
+
.star.half {
|
|
206
|
+
background: linear-gradient(
|
|
207
|
+
90deg,
|
|
208
|
+
var(--star-color, #ffd700) 50%,
|
|
209
|
+
var(--ui-color-secondary) 50%
|
|
210
|
+
);
|
|
211
|
+
-webkit-background-clip: text;
|
|
212
|
+
background-clip: text;
|
|
213
|
+
color: transparent;
|
|
214
|
+
}
|
|
215
|
+
|
|
77
216
|
.rating-value {
|
|
78
217
|
font-weight: 600;
|
|
79
218
|
color: var(--ui-color-text);
|
|
@@ -1,13 +1,31 @@
|
|
|
1
1
|
import { type Variant } from "../types/variants.js";
|
|
2
2
|
import type { Size } from "../types/sizes.js";
|
|
3
3
|
export type RatingProps = {
|
|
4
|
-
|
|
5
|
-
|
|
4
|
+
/** Display mode: a 0–10 average score (TMDB-style), rendered as 5 stars (average / 2). */
|
|
5
|
+
average?: number;
|
|
6
|
+
/** Display mode: number of ratings, shown in parentheses. */
|
|
7
|
+
count?: number;
|
|
8
|
+
/** Interactive mode: the selected rating in stars (0..max), bindable. */
|
|
9
|
+
value?: number;
|
|
10
|
+
/** Enable selection (click / hover / keyboard). Off = read-only display. */
|
|
11
|
+
interactive?: boolean;
|
|
12
|
+
/** Interactive but not editable (shows `value`, no input). */
|
|
13
|
+
readonly?: boolean;
|
|
14
|
+
disabled?: boolean;
|
|
15
|
+
/** Number of stars in interactive mode (default 5). */
|
|
16
|
+
max?: number;
|
|
17
|
+
/** Allow half-star selection/display (interactive). */
|
|
18
|
+
allowHalf?: boolean;
|
|
19
|
+
/** Show the numeric value beside the stars (default: true in display mode, false interactive). */
|
|
20
|
+
showValue?: boolean;
|
|
21
|
+
/** Fired on change (interactive). */
|
|
22
|
+
onchange?: (value: number) => void;
|
|
6
23
|
/** Filled-star colour. Defaults to gold; a variant tints from its token (e.g. `warning`, `primary`). */
|
|
7
24
|
variant?: Variant;
|
|
8
|
-
/** Star size (sm/md/lg). */
|
|
9
25
|
size?: Size;
|
|
26
|
+
/** Accessible label for the interactive slider. */
|
|
27
|
+
label?: string;
|
|
10
28
|
};
|
|
11
|
-
declare const Rating: import("svelte").Component<RatingProps, {}, "">;
|
|
29
|
+
declare const Rating: import("svelte").Component<RatingProps, {}, "value">;
|
|
12
30
|
type Rating = ReturnType<typeof Rating>;
|
|
13
31
|
export default Rating;
|
|
@@ -166,14 +166,16 @@
|
|
|
166
166
|
|
|
167
167
|
<FieldChrome {label} {aside} {error} {description} {required} {controlId} {size} class={cls}>
|
|
168
168
|
{#snippet control({ describedBy })}
|
|
169
|
-
<div class="flex items-center gap-3">
|
|
169
|
+
<div class="flex items-center gap-3 select-none">
|
|
170
170
|
<!-- The track is the click-to-position surface; the thumbs below carry role="slider". -->
|
|
171
171
|
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
|
172
|
+
<!-- Disabled keeps pointer events (so the not-allowed cursor shows) but every handler no-ops on
|
|
173
|
+
`disabled`, so it can't be dragged/keyed. -->
|
|
172
174
|
<div
|
|
173
175
|
bind:this={trackEl}
|
|
174
176
|
onpointerdown={onTrackPointerDown}
|
|
175
177
|
class="relative flex-1 select-none rounded-full {disabled
|
|
176
|
-
? 'opacity-50
|
|
178
|
+
? 'opacity-50 cursor-not-allowed'
|
|
177
179
|
: 'cursor-pointer'} {responsiveClasses(size, trackH)}"
|
|
178
180
|
style="background-color: color-mix(in srgb, var({token}) 16%, transparent);"
|
|
179
181
|
>
|
|
@@ -198,7 +200,7 @@
|
|
|
198
200
|
aria-describedby={describedBy}
|
|
199
201
|
onkeydown={(e) => onThumbKeydown(e, i)}
|
|
200
202
|
class="absolute top-1/2 -translate-x-1/2 -translate-y-1/2 rounded-full border-2 shadow {disabled
|
|
201
|
-
? ''
|
|
203
|
+
? 'cursor-not-allowed'
|
|
202
204
|
: 'cursor-grab active:cursor-grabbing'} focus-visible:[outline:2px_solid_color-mix(in_srgb,var(--ui-color-text)_70%,transparent)] focus-visible:[outline-offset:2px] {responsiveClasses(
|
|
203
205
|
size,
|
|
204
206
|
thumbSz,
|
|
@@ -62,14 +62,19 @@
|
|
|
62
62
|
: `color-mix(in srgb, var(${token}) var(--ui-tint-border-hover), transparent)`,
|
|
63
63
|
);
|
|
64
64
|
|
|
65
|
+
// Vertical padding is kept small enough that a single token row (chip + 1px border box + padding)
|
|
66
|
+
// never exceeds the tier's `min-h`, so the field height is CONSTANT whether empty or holding tokens.
|
|
67
|
+
// Multi-row wrapping still grows the field; row spacing comes from the flex `gap`, not padding.
|
|
65
68
|
const fieldSize: Record<Size, string> = {
|
|
66
|
-
sm: "min-h-6 px-1.5 py-0
|
|
67
|
-
md: "min-h-8 px-2 py-
|
|
68
|
-
lg: "min-h-10 px-2.5 py-
|
|
69
|
+
sm: "min-h-6 px-1.5 py-0 gap-1 text-xs",
|
|
70
|
+
md: "min-h-8 px-2 py-[2px] gap-1.5 text-sm",
|
|
71
|
+
lg: "min-h-10 px-2.5 py-[2px] gap-2 text-base",
|
|
69
72
|
};
|
|
70
|
-
const
|
|
71
|
-
|
|
72
|
-
|
|
73
|
+
const baseSize = $derived<Size>(typeof size === "string" ? size : "md");
|
|
74
|
+
const chipSize = $derived<Size>(baseSize === "lg" ? "md" : "sm");
|
|
75
|
+
// A default `sm` chip is 24px — exactly the sm FIELD height, so it can't fit inside the border box.
|
|
76
|
+
// Only in the sm field, cap the chip to 20px (still fits the min-h with room to center).
|
|
77
|
+
const chipHeightClass = $derived(baseSize === "sm" ? "!h-5" : "");
|
|
73
78
|
|
|
74
79
|
let draft = $state("");
|
|
75
80
|
|
|
@@ -115,6 +120,7 @@
|
|
|
115
120
|
text={v}
|
|
116
121
|
variant={variant === "ghost" ? "secondary" : variant}
|
|
117
122
|
size={chipSize}
|
|
123
|
+
class={chipHeightClass}
|
|
118
124
|
removable
|
|
119
125
|
{disabled}
|
|
120
126
|
onremove={() => removeAt(i)}
|
|
@@ -64,10 +64,10 @@ export interface QueryResult<T> {
|
|
|
64
64
|
export type FetchFn<T> = (state: GridState) => QueryResult<FetchResult<T>>;
|
|
65
65
|
export type ParsedQuery = QueryNode | null;
|
|
66
66
|
/**
|
|
67
|
-
* A
|
|
68
|
-
*
|
|
67
|
+
* A menu action. Convention: always render ALL items and `disabled` the invalid ones. Icons are
|
|
68
|
+
* optional — when ANY item has an icon, icon-less items reserve the icon column so labels align.
|
|
69
69
|
*/
|
|
70
|
-
export type
|
|
70
|
+
export type MenuAction = {
|
|
71
71
|
label: string;
|
|
72
72
|
icon?: Component<{
|
|
73
73
|
class?: string;
|
|
@@ -77,3 +77,9 @@ export type MenuItem = {
|
|
|
77
77
|
disabled?: boolean;
|
|
78
78
|
danger?: boolean;
|
|
79
79
|
};
|
|
80
|
+
/** A non-interactive separator line between groups of actions. */
|
|
81
|
+
export type MenuSeparator = {
|
|
82
|
+
divider: true;
|
|
83
|
+
};
|
|
84
|
+
/** A menu entry — either an action or a separator (`{ divider: true }`). */
|
|
85
|
+
export type MenuItem = MenuAction | MenuSeparator;
|
package/dist/index.d.ts
CHANGED
|
@@ -7,7 +7,6 @@ export { EmptyState } from "./components/EmptyState/index.js";
|
|
|
7
7
|
export { Avatar, AvatarGroup } from "./components/Avatar/index.js";
|
|
8
8
|
export type { AvatarShape, AvatarStatus, AvatarItem } from "./components/Avatar/index.js";
|
|
9
9
|
export { default as Button } from "./components/Button.svelte";
|
|
10
|
-
export { default as ButtonDropdown } from "./components/ButtonDropdown.svelte";
|
|
11
10
|
export { default as Card } from "./components/Card.svelte";
|
|
12
11
|
export { default as CardHeader } from "./components/CardHeader.svelte";
|
|
13
12
|
export { default as CardBody } from "./components/CardBody.svelte";
|
|
@@ -43,9 +42,7 @@ export { default as DatePicker } from "./components/DatePicker.svelte";
|
|
|
43
42
|
export { default as TimeSpinner } from "./components/TimeSpinner.svelte";
|
|
44
43
|
export { default as TimePicker } from "./components/TimePicker.svelte";
|
|
45
44
|
export { default as TimeRangeInput } from "./components/TimeRangeInput.svelte";
|
|
46
|
-
export { DateRangePicker } from "./components/DateRangePicker/index.js";
|
|
47
45
|
export { FileUpload } from "./components/FileUpload/index.js";
|
|
48
|
-
export type { RangePreset } from "./components/DateRangePicker/index.js";
|
|
49
46
|
export { default as ThemeSelector } from "./components/ThemeSelector.svelte";
|
|
50
47
|
export { default as ThemeToggle } from "./components/ThemeToggle.svelte";
|
|
51
48
|
export { default as Popup } from "./components/Popup.svelte";
|
|
@@ -55,7 +52,7 @@ export { CommandPalette } from "./components/CommandPalette/index.js";
|
|
|
55
52
|
export type { Command } from "./components/CommandPalette/index.js";
|
|
56
53
|
export { Tree } from "./components/Tree/index.js";
|
|
57
54
|
export type { TreeNode } from "./components/Tree/index.js";
|
|
58
|
-
export type { MenuItem } from "./data/table/types.js";
|
|
55
|
+
export type { MenuItem, MenuAction, MenuSeparator } from "./data/table/types.js";
|
|
59
56
|
export { ButtonGroup } from "./components/ButtonGroup/index.js";
|
|
60
57
|
export type { ButtonGroupItem, ButtonGroupSelect } from "./components/ButtonGroup/index.js";
|
|
61
58
|
export { default as Checkbox } from "./components/Checkbox.svelte";
|
package/dist/index.js
CHANGED
|
@@ -7,7 +7,6 @@ export { Kbd } from "./components/Kbd/index.js";
|
|
|
7
7
|
export { EmptyState } from "./components/EmptyState/index.js";
|
|
8
8
|
export { Avatar, AvatarGroup } from "./components/Avatar/index.js";
|
|
9
9
|
export { default as Button } from "./components/Button.svelte";
|
|
10
|
-
export { default as ButtonDropdown } from "./components/ButtonDropdown.svelte";
|
|
11
10
|
export { default as Card } from "./components/Card.svelte";
|
|
12
11
|
export { default as CardHeader } from "./components/CardHeader.svelte";
|
|
13
12
|
export { default as CardBody } from "./components/CardBody.svelte";
|
|
@@ -44,7 +43,6 @@ export { default as DatePicker } from "./components/DatePicker.svelte";
|
|
|
44
43
|
export { default as TimeSpinner } from "./components/TimeSpinner.svelte";
|
|
45
44
|
export { default as TimePicker } from "./components/TimePicker.svelte";
|
|
46
45
|
export { default as TimeRangeInput } from "./components/TimeRangeInput.svelte";
|
|
47
|
-
export { DateRangePicker } from "./components/DateRangePicker/index.js";
|
|
48
46
|
export { FileUpload } from "./components/FileUpload/index.js";
|
|
49
47
|
// Theme Components
|
|
50
48
|
export { default as ThemeSelector } from "./components/ThemeSelector.svelte";
|
package/docs/components.md
CHANGED
|
@@ -122,20 +122,6 @@ Drag-and-drop + click file field with thumbnails, remove, and optional progress.
|
|
|
122
122
|
`<input type=file>`; labelled remove ×s
|
|
123
123
|
- **Composition**: `Button` + `Progress`; FieldChrome for label/error
|
|
124
124
|
|
|
125
|
-
### DateRangePicker
|
|
126
|
-
|
|
127
|
-
Start–end date range with a presets rail; composes `Calendar mode="range"` + `Input` + `Popup`.
|
|
128
|
-
|
|
129
|
-
- **Location**: `src/lib/components/DateRangePicker/DateRangePicker.svelte`
|
|
130
|
-
- **Axes**: `variant`, `size`
|
|
131
|
-
- **Props**: `start`/`end` (bindable ISO), `min`/`max`, `presets` (`RangePreset[]`; default set, `[]`
|
|
132
|
-
hides the rail), `format`, `placeholder`, plus FieldChrome (`label`/`aside`/`error`/`description`/
|
|
133
|
-
`id`/`required`), `class`
|
|
134
|
-
- **Behavior**: readonly `Input` trigger shows the formatted range; the popup pairs a presets column
|
|
135
|
-
(`Button` ghost) with the range calendar; a preset or the second calendar pick closes it
|
|
136
|
-
- **Note**: `DatePicker range` covers a bare range (object value, no presets); this is the
|
|
137
|
-
presets-first, `bind:start`/`bind:end` variant
|
|
138
|
-
|
|
139
125
|
### Slider
|
|
140
126
|
|
|
141
127
|
Draggable range input — a single value or a `[min, max]` pair.
|
|
@@ -224,8 +210,14 @@ the selected one fills with the group `variant`. Not a form field (controlled vi
|
|
|
224
210
|
`string[]`, multi), `orientation` (`'row' | 'col'`, default `'row'`), `disabled` (whole group),
|
|
225
211
|
`class`, `children` (a **presentational** default slot — hand-authored `<Button>`s get the frame but
|
|
226
212
|
you own their behaviour; mutually exclusive with `items`, which wins + DEV-warns).
|
|
227
|
-
- **`ButtonGroupItem`**: `{ value?, text?, icon? (Snippet), disabled?, variant?,
|
|
228
|
-
|
|
213
|
+
- **`ButtonGroupItem`**: `{ value?, text?, icon? (Snippet), disabled?, loading?, variant?, href?,
|
|
214
|
+
target?, menu? (MenuItem[]), menuOnly?, onclick? }`. `loading`/`href`/`target` forward to `Button`
|
|
215
|
+
(a disabled `href` item falls back to a plain disabled button — `<a>` has no `disabled`).
|
|
216
|
+
- **Split buttons & menu triggers**: an item with `menu` renders a **split button** (its action + a
|
|
217
|
+
trailing caret that opens the `Menu`). Add **`menuOnly`** and the whole item becomes a caret-only
|
|
218
|
+
**menu-trigger** (its click opens the menu; a bare one defaults to a chevron). Compose the two for a
|
|
219
|
+
standalone `[ action ][ ▾ ]` dropdown: a normal item + a `menuOnly` item — this **replaces the
|
|
220
|
+
removed `ButtonDropdown`**.
|
|
229
221
|
- **Selection / a11y**: `'single'` → `role="radiogroup"` + `aria-checked`, arrow-key roving;
|
|
230
222
|
`'multi'` → `aria-pressed`; `'none'` → `role="toolbar"`, `item.onclick` fires (no swap).
|
|
231
223
|
|
|
@@ -285,10 +277,15 @@ independent. Propagates `size`/`variant`/`disabled` to children.
|
|
|
285
277
|
|
|
286
278
|
### Rating
|
|
287
279
|
|
|
288
|
-
|
|
280
|
+
Star rating in two modes: a read-only display, or an interactive picker.
|
|
289
281
|
|
|
290
282
|
- **Location**: `src/lib/components/Rating.svelte`
|
|
291
|
-
- **
|
|
283
|
+
- **Display mode** (default): `average` (0–10 score, shown as 5 stars = average/2), `count` (votes).
|
|
284
|
+
- **Interactive mode**: `interactive` + `value` (bindable, 0..`max`). Click a star, hover to preview,
|
|
285
|
+
or focus and use ←/→ · ↑/↓ · Home/End. `allowHalf` enables half-star selection; `onchange`,
|
|
286
|
+
`readonly`, `disabled`. Rendered as a `role="slider"` (same a11y pattern as `Slider`).
|
|
287
|
+
- **Shared**: `variant` (filled-star tint; default gold), `size`, `showValue` (numeric readout —
|
|
288
|
+
defaults on in display mode, off interactive), `label` (slider a11y label).
|
|
292
289
|
|
|
293
290
|
## Specialized Input Components
|
|
294
291
|
|
|
@@ -399,13 +396,19 @@ dialog (operators, casts, dynamic `now()` dates, and a worked examples table). T
|
|
|
399
396
|
Standalone page navigator — the reusable extraction of `DataTable`'s footer pager.
|
|
400
397
|
|
|
401
398
|
- **Location**: `src/lib/components/Pagination/Pagination.svelte`
|
|
402
|
-
- **Axes**: `variant` (
|
|
399
|
+
- **Axes**: `variant` (tints the band **surface + button hover**, like DataTable's footer — NOT a
|
|
400
|
+
filled button; omit for a neutral band, `ghost` for a transparent one), `size` (scales band, Select,
|
|
401
|
+
buttons, and the page field)
|
|
403
402
|
- **Props**: `page` (bindable), `total`, `perPage` (bindable), `perPageOptions` (→ rows-per-page
|
|
404
|
-
`Select`), `
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
403
|
+
`Select` on the left), `showEdges` (first/last, default **true**), `showTotal` (range text, default
|
|
404
|
+
**true**), `borderless` (drop the band border/surface), `disabled`, `class`
|
|
405
|
+
- **Layout**: a tinted, bordered band (matches `DataTable`'s footer) — per-page `Select` left; range
|
|
406
|
+
text + a compact `First · Prev · [page]/N · Next · Last` stepper right. The page indicator is an
|
|
407
|
+
**editable `NumberInput`** (min 1, max N, clamped) — type a page + Enter to jump; ↑/↓ step it.
|
|
408
|
+
- **Behavior**: prev/next/first/last disable at the ends; choosing `perPage` resets to page 1; `page`
|
|
409
|
+
clamps to range; jump commits on change (Enter/blur)
|
|
410
|
+
- **Accessibility**: `<nav aria-label="Pagination">`; the jump field is `aria-label="Page number"`
|
|
411
|
+
- **Composition**: `Button` + `Select` + `NumberInput` only (no raw `<button>`/`<input>`)
|
|
409
412
|
|
|
410
413
|
## Overlays
|
|
411
414
|
|
|
@@ -468,8 +471,10 @@ never clipped by overflow/stacking contexts. Placement via the shared `anchored`
|
|
|
468
471
|
Keyboard-navigable action menu rendered inside a `Popup`.
|
|
469
472
|
|
|
470
473
|
- **Location**: `src/lib/components/Menu.svelte`
|
|
471
|
-
- **Props**: `anchor`, `open` (bindable), `items` (`MenuItem[]`
|
|
472
|
-
`
|
|
474
|
+
- **Props**: `anchor`, `open` (bindable), `items` (`MenuItem[]`), `side`, `align`, `boundary`, `size`,
|
|
475
|
+
`variant`
|
|
476
|
+
- **`MenuItem`**: an **action** `{ label, icon?, onclick?, href?, disabled?, danger? }` **or** a
|
|
477
|
+
**separator** `{ divider: true }` (a non-interactive rule between groups; skipped by keyboard nav).
|
|
473
478
|
- **Features**: renders all items always and disables the invalid ones; aligns labels when any
|
|
474
479
|
item has an icon
|
|
475
480
|
|
package/docs/usage.md
CHANGED
|
@@ -311,21 +311,17 @@ A drag-and-drop + click file field (thumbnails, remove, optional progress).
|
|
|
311
311
|
label="Attachments" onfiles={(f) => console.log(f)} />
|
|
312
312
|
```
|
|
313
313
|
|
|
314
|
-
###
|
|
314
|
+
### Date range
|
|
315
315
|
|
|
316
|
-
|
|
316
|
+
Use `DatePicker` with the `range` prop — its value is a `{ start, end }` object.
|
|
317
317
|
|
|
318
318
|
```svelte
|
|
319
319
|
<script>
|
|
320
|
-
import {
|
|
321
|
-
let
|
|
322
|
-
let end = $state("");
|
|
320
|
+
import { DatePicker } from "@spaethtech/svelte-ui";
|
|
321
|
+
let range = $state({ start: null, end: null });
|
|
323
322
|
</script>
|
|
324
323
|
|
|
325
|
-
<
|
|
326
|
-
|
|
327
|
-
<!-- custom presets, or presets={[]} for calendar-only -->
|
|
328
|
-
<DateRangePicker bind:start bind:end presets={[{ label: "Q1", start: "2026-01-01", end: "2026-03-31" }]} />
|
|
324
|
+
<DatePicker range bind:value={range} label="Reporting period" />
|
|
329
325
|
```
|
|
330
326
|
|
|
331
327
|
### Stepper
|
|
@@ -468,6 +464,19 @@ A joined row/column of buttons — an action toolbar or a controlled segmented s
|
|
|
468
464
|
<!-- Action toolbar with a split-button (Export has a `menu`) -->
|
|
469
465
|
<ButtonGroup items={actions} variant="secondary" />
|
|
470
466
|
|
|
467
|
+
<!-- Split-button dropdown [ action ][ ▾ ] — replaces the old ButtonDropdown.
|
|
468
|
+
A normal action item + a `menuOnly` caret item that opens a Menu (with a divider). -->
|
|
469
|
+
<ButtonGroup
|
|
470
|
+
variant="primary"
|
|
471
|
+
items={[
|
|
472
|
+
{ text: "Save", icon: saveIcon, loading: saving, onclick: doSave },
|
|
473
|
+
{ menuOnly: true, menu: [
|
|
474
|
+
{ label: "Save and close", onclick: saveAndClose },
|
|
475
|
+
{ divider: true },
|
|
476
|
+
{ label: "Discard", danger: true, onclick: discard },
|
|
477
|
+
] },
|
|
478
|
+
]} />
|
|
479
|
+
|
|
471
480
|
<!-- Vertical -->
|
|
472
481
|
<ButtonGroup items={alignItems} select="single" bind:value={align} orientation="col" />
|
|
473
482
|
|
|
@@ -740,7 +749,9 @@ A thin separating rule — horizontal (optionally labelled) or vertical (toolbar
|
|
|
740
749
|
|
|
741
750
|
### Pagination
|
|
742
751
|
|
|
743
|
-
A standalone page navigator
|
|
752
|
+
A standalone page navigator laid out like `DataTable`'s footer: a tinted band with an optional
|
|
753
|
+
per-page `Select`, a range total, and a `First · Prev · [page]/N · Next · Last` stepper whose page
|
|
754
|
+
indicator is an editable `NumberInput` (type a page + Enter to jump).
|
|
744
755
|
|
|
745
756
|
```svelte
|
|
746
757
|
<script>
|
|
@@ -749,10 +760,13 @@ A standalone page navigator (numbered buttons + ellipsis, prev/next, optional pe
|
|
|
749
760
|
let perPage = $state(10);
|
|
750
761
|
</script>
|
|
751
762
|
|
|
752
|
-
<Pagination bind:page bind:perPage total={247} perPageOptions={[10, 25, 50, 100]}
|
|
763
|
+
<Pagination bind:page bind:perPage total={247} perPageOptions={[10, 25, 50, 100]} />
|
|
764
|
+
|
|
765
|
+
<!-- Compact: just the stepper (no per-page Select) -->
|
|
766
|
+
<Pagination bind:page total={200} showTotal={false} />
|
|
753
767
|
|
|
754
|
-
<!--
|
|
755
|
-
<Pagination bind:page total={200}
|
|
768
|
+
<!-- variant tints the band surface + button hover (not the buttons); ghost = transparent band -->
|
|
769
|
+
<Pagination bind:page total={200} variant="primary" />
|
|
756
770
|
```
|
|
757
771
|
|
|
758
772
|
### Breadcrumbs
|