@rokkit/ui 1.0.0-next.125 → 1.0.0-next.127
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 +198 -101
- package/package.json +52 -34
- package/src/components/BreadCrumbs.svelte +82 -0
- package/src/components/Button.svelte +87 -0
- package/src/components/ButtonGroup.svelte +18 -0
- package/src/components/Card.svelte +61 -0
- package/src/components/Carousel.svelte +169 -0
- package/src/components/Code.svelte +185 -0
- package/src/components/Connector.svelte +46 -0
- package/src/components/FloatingAction.svelte +331 -0
- package/src/components/FloatingNavigation.svelte +228 -0
- package/src/components/ItemContent.svelte +24 -0
- package/src/components/List.svelte +476 -0
- package/src/components/Menu.svelte +421 -0
- package/src/components/MultiSelect.svelte +521 -0
- package/src/components/PaletteManager.svelte +354 -0
- package/src/components/Pill.svelte +78 -0
- package/src/components/ProgressBar.svelte +31 -0
- package/src/components/Range.svelte +325 -0
- package/src/components/Rating.svelte +91 -0
- package/src/components/Reveal.svelte +58 -0
- package/src/components/SearchFilter.svelte +80 -0
- package/src/components/Select.svelte +585 -0
- package/src/{Shine.svelte → components/Shine.svelte} +29 -21
- package/src/components/Stepper.svelte +169 -0
- package/src/components/Switch.svelte +75 -0
- package/src/components/Table.svelte +243 -0
- package/src/components/Tabs.svelte +268 -0
- package/src/components/Tilt.svelte +68 -0
- package/src/components/Timeline.svelte +61 -0
- package/src/components/Toggle.svelte +157 -0
- package/src/components/Toolbar.svelte +307 -0
- package/src/components/ToolbarGroup.svelte +17 -0
- package/src/components/Tree.svelte +613 -0
- package/src/components/index.ts +33 -0
- package/src/index.ts +41 -0
- package/src/types/button.ts +83 -0
- package/src/types/code.ts +46 -0
- package/src/types/floating-action.ts +118 -0
- package/src/types/floating-navigation.ts +68 -0
- package/src/types/index.ts +53 -0
- package/src/types/item-proxy.ts +358 -0
- package/src/types/list.ts +196 -0
- package/src/types/menu.ts +195 -0
- package/src/types/palette.ts +143 -0
- package/src/types/range.ts +51 -0
- package/src/types/search-filter.ts +67 -0
- package/src/types/select.ts +206 -0
- package/src/types/switch.ts +64 -0
- package/src/types/table.ts +210 -0
- package/src/types/tabs.ts +124 -0
- package/src/types/timeline.ts +51 -0
- package/src/types/toggle.ts +109 -0
- package/src/types/toolbar.ts +164 -0
- package/src/types/tree.ts +259 -0
- package/src/utils/palette.ts +582 -0
- package/src/utils/shiki.ts +122 -0
- package/dist/constants.d.ts +0 -2
- package/dist/index.d.ts +0 -41
- package/dist/lib/fields.d.ts +0 -16
- package/dist/lib/form.d.ts +0 -95
- package/dist/lib/index.d.ts +0 -6
- package/dist/lib/layout.d.ts +0 -7
- package/dist/lib/nested.d.ts +0 -48
- package/dist/lib/schema.d.ts +0 -7
- package/dist/lib/select.d.ts +0 -8
- package/dist/lib/tree.d.ts +0 -9
- package/dist/tree/List.spec.svelte.d.ts +0 -1
- package/dist/tree/Node.spec.svelte.d.ts +0 -1
- package/dist/tree/Root.spec.svelte.d.ts +0 -1
- package/dist/types.d.ts +0 -5
- package/dist/wrappers/index.d.ts +0 -3
- package/src/Accordion.svelte +0 -118
- package/src/BreadCrumbs.svelte +0 -32
- package/src/Button.svelte +0 -57
- package/src/Calendar.svelte +0 -93
- package/src/Card.svelte +0 -45
- package/src/Carousel.svelte +0 -49
- package/src/CheckBox.svelte +0 -56
- package/src/Connector.svelte +0 -40
- package/src/DropDown.svelte +0 -68
- package/src/DropSearch.svelte +0 -37
- package/src/Fillable.svelte +0 -19
- package/src/GraphPaper.svelte +0 -43
- package/src/Icon.svelte +0 -81
- package/src/Item.svelte +0 -25
- package/src/Link.svelte +0 -21
- package/src/List.svelte +0 -89
- package/src/ListBody.svelte +0 -43
- package/src/Message.svelte +0 -11
- package/src/MultiSelect.svelte +0 -48
- package/src/NestedList.svelte +0 -78
- package/src/NestedPaginator.svelte +0 -63
- package/src/Node.svelte +0 -76
- package/src/Overlay.svelte +0 -21
- package/src/PageNavigator.svelte +0 -94
- package/src/PickOne.svelte +0 -60
- package/src/Pill.svelte +0 -41
- package/src/ProgressBar.svelte +0 -21
- package/src/ProgressDots.svelte +0 -53
- package/src/RadioGroup.svelte +0 -52
- package/src/Range.svelte +0 -45
- package/src/RangeMinMax.svelte +0 -124
- package/src/RangeSlider.svelte +0 -79
- package/src/RangeTick.svelte +0 -28
- package/src/Rating.svelte +0 -95
- package/src/ResponsiveGrid.svelte +0 -88
- package/src/Scrollable.svelte +0 -7
- package/src/Select.svelte +0 -114
- package/src/Separator.svelte +0 -1
- package/src/Slider.svelte +0 -14
- package/src/SlidingColumns.svelte +0 -50
- package/src/Stage.svelte +0 -41
- package/src/Stepper.svelte +0 -66
- package/src/Summary.svelte +0 -22
- package/src/Switch.svelte +0 -106
- package/src/TableCell.svelte +0 -51
- package/src/TableHeaderCell.svelte +0 -54
- package/src/Tabs.svelte +0 -176
- package/src/Tilt.svelte +0 -66
- package/src/Toggle.svelte +0 -58
- package/src/ToggleThemeMode.svelte +0 -23
- package/src/Tree.svelte +0 -80
- package/src/TreeTable.svelte +0 -171
- package/src/ValidationReport.svelte +0 -23
- package/src/constants.js +0 -4
- package/src/index.js +0 -48
- package/src/lib/fields.js +0 -118
- package/src/lib/form.js +0 -72
- package/src/lib/index.js +0 -13
- package/src/lib/layout.js +0 -63
- package/src/lib/nested.js +0 -192
- package/src/lib/schema.js +0 -32
- package/src/lib/select.js +0 -38
- package/src/lib/tree.js +0 -22
- package/src/tree/List.spec.svelte.js +0 -84
- package/src/tree/List.svelte +0 -78
- package/src/tree/Node.spec.svelte.js +0 -104
- package/src/tree/Node.svelte +0 -80
- package/src/tree/Root.spec.svelte.js +0 -63
- package/src/tree/Root.svelte +0 -81
- package/src/types.js +0 -9
- package/src/wrappers/Category.svelte +0 -27
- package/src/wrappers/Section.svelte +0 -16
- package/src/wrappers/Wrapper.svelte +0 -12
- package/src/wrappers/index.js +0 -3
|
@@ -0,0 +1,325 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { RangeProps } from '../types/range.js'
|
|
3
|
+
import { generateTicks } from '@rokkit/core'
|
|
4
|
+
import { pannable } from '@rokkit/actions'
|
|
5
|
+
|
|
6
|
+
let {
|
|
7
|
+
value = $bindable(50),
|
|
8
|
+
lower = $bindable(0),
|
|
9
|
+
upper = $bindable(100),
|
|
10
|
+
min = 0,
|
|
11
|
+
max = 100,
|
|
12
|
+
step = 1,
|
|
13
|
+
range: rangeMode = false,
|
|
14
|
+
ticks = 0,
|
|
15
|
+
labelSkip = 0,
|
|
16
|
+
disabled = false,
|
|
17
|
+
onchange,
|
|
18
|
+
class: className = ''
|
|
19
|
+
}: RangeProps = $props()
|
|
20
|
+
|
|
21
|
+
// ─── Pixel state ────────────────────────────────────────────────
|
|
22
|
+
let trackWidth = $state(0)
|
|
23
|
+
|
|
24
|
+
// ─── Helpers ────────────────────────────────────────────────────
|
|
25
|
+
|
|
26
|
+
function inverseLerp(val: number, lo: number, hi: number): number {
|
|
27
|
+
if (hi === lo) return 0
|
|
28
|
+
return (val - lo) / (hi - lo)
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function valueToPercent(val: number): number {
|
|
32
|
+
return inverseLerp(val, min, max) * 100
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function pixelToValue(px: number): number {
|
|
36
|
+
if (trackWidth === 0) return min
|
|
37
|
+
return min + (px / trackWidth) * (max - min)
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function clamp(val: number, lo: number, hi: number): number {
|
|
41
|
+
return Math.min(Math.max(val, lo), hi)
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function snapToStep(val: number): number {
|
|
45
|
+
if (step <= 0) return val
|
|
46
|
+
return Math.round((val - min) / step) * step + min
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function fireChange() {
|
|
50
|
+
if (rangeMode) {
|
|
51
|
+
onchange?.([lower, upper])
|
|
52
|
+
} else {
|
|
53
|
+
onchange?.(value)
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// ─── Derived positions (percentages) ────────────────────────────
|
|
58
|
+
|
|
59
|
+
let selectedLeft = $derived(rangeMode ? valueToPercent(lower) : 0)
|
|
60
|
+
let selectedWidth = $derived(
|
|
61
|
+
rangeMode
|
|
62
|
+
? valueToPercent(upper) - valueToPercent(lower)
|
|
63
|
+
: valueToPercent(value)
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
// ─── Ticks ──────────────────────────────────────────────────────
|
|
67
|
+
|
|
68
|
+
let tickStep = $derived(ticks > 0 ? Math.max(1, Math.round((max - min) / ticks)) : 0)
|
|
69
|
+
let tickItems = $derived(
|
|
70
|
+
ticks > 0 ? generateTicks(min, max, tickStep, labelSkip > 0 ? labelSkip + 1 : 1) : []
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
// ─── Thumb drag — upper (or single) ─────────────────────────────
|
|
74
|
+
|
|
75
|
+
let slidingUpper = $state(false)
|
|
76
|
+
|
|
77
|
+
function handleUpperPanStart() {
|
|
78
|
+
if (disabled) return
|
|
79
|
+
slidingUpper = true
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function handleUpperPanMove(event: CustomEvent) {
|
|
83
|
+
if (disabled || trackWidth === 0) return
|
|
84
|
+
const dx = event.detail.dx as number
|
|
85
|
+
const currentPx = inverseLerp(rangeMode ? upper : value, min, max) * trackWidth
|
|
86
|
+
const newPx = clamp(currentPx + dx, rangeMode ? inverseLerp(lower, min, max) * trackWidth : 0, trackWidth)
|
|
87
|
+
const raw = pixelToValue(newPx)
|
|
88
|
+
const snapped = snapToStep(clamp(raw, rangeMode ? lower : min, max))
|
|
89
|
+
if (rangeMode) {
|
|
90
|
+
upper = snapped
|
|
91
|
+
} else {
|
|
92
|
+
value = snapped
|
|
93
|
+
}
|
|
94
|
+
fireChange()
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function handleUpperPanEnd() {
|
|
98
|
+
slidingUpper = false
|
|
99
|
+
// Final snap
|
|
100
|
+
if (rangeMode) {
|
|
101
|
+
upper = snapToStep(clamp(upper, lower, max))
|
|
102
|
+
} else {
|
|
103
|
+
value = snapToStep(clamp(value, min, max))
|
|
104
|
+
}
|
|
105
|
+
fireChange()
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function handleUpperKeyDown(event: KeyboardEvent) {
|
|
109
|
+
if (disabled) return
|
|
110
|
+
const increment = step > 0 ? step : (max - min) / 10
|
|
111
|
+
|
|
112
|
+
if (event.key === 'ArrowRight' || event.key === 'ArrowUp') {
|
|
113
|
+
event.preventDefault()
|
|
114
|
+
if (rangeMode) {
|
|
115
|
+
upper = clamp(snapToStep(upper + increment), lower, max)
|
|
116
|
+
} else {
|
|
117
|
+
value = clamp(snapToStep(value + increment), min, max)
|
|
118
|
+
}
|
|
119
|
+
fireChange()
|
|
120
|
+
} else if (event.key === 'ArrowLeft' || event.key === 'ArrowDown') {
|
|
121
|
+
event.preventDefault()
|
|
122
|
+
if (rangeMode) {
|
|
123
|
+
upper = clamp(snapToStep(upper - increment), lower, max)
|
|
124
|
+
} else {
|
|
125
|
+
value = clamp(snapToStep(value - increment), min, max)
|
|
126
|
+
}
|
|
127
|
+
fireChange()
|
|
128
|
+
} else if (event.key === 'Home') {
|
|
129
|
+
event.preventDefault()
|
|
130
|
+
if (rangeMode) {
|
|
131
|
+
upper = lower
|
|
132
|
+
} else {
|
|
133
|
+
value = min
|
|
134
|
+
}
|
|
135
|
+
fireChange()
|
|
136
|
+
} else if (event.key === 'End') {
|
|
137
|
+
event.preventDefault()
|
|
138
|
+
if (rangeMode) {
|
|
139
|
+
upper = max
|
|
140
|
+
} else {
|
|
141
|
+
value = max
|
|
142
|
+
}
|
|
143
|
+
fireChange()
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// ─── Thumb drag — lower (range mode only) ───────────────────────
|
|
148
|
+
|
|
149
|
+
let slidingLower = $state(false)
|
|
150
|
+
|
|
151
|
+
function handleLowerPanStart() {
|
|
152
|
+
if (disabled) return
|
|
153
|
+
slidingLower = true
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function handleLowerPanMove(event: CustomEvent) {
|
|
157
|
+
if (disabled || trackWidth === 0) return
|
|
158
|
+
const dx = event.detail.dx as number
|
|
159
|
+
const currentPx = inverseLerp(lower, min, max) * trackWidth
|
|
160
|
+
const newPx = clamp(currentPx + dx, 0, inverseLerp(upper, min, max) * trackWidth)
|
|
161
|
+
const raw = pixelToValue(newPx)
|
|
162
|
+
lower = snapToStep(clamp(raw, min, upper))
|
|
163
|
+
fireChange()
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function handleLowerPanEnd() {
|
|
167
|
+
slidingLower = false
|
|
168
|
+
lower = snapToStep(clamp(lower, min, upper))
|
|
169
|
+
fireChange()
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
function handleLowerKeyDown(event: KeyboardEvent) {
|
|
173
|
+
if (disabled) return
|
|
174
|
+
const increment = step > 0 ? step : (max - min) / 10
|
|
175
|
+
|
|
176
|
+
if (event.key === 'ArrowRight' || event.key === 'ArrowUp') {
|
|
177
|
+
event.preventDefault()
|
|
178
|
+
lower = clamp(snapToStep(lower + increment), min, upper)
|
|
179
|
+
fireChange()
|
|
180
|
+
} else if (event.key === 'ArrowLeft' || event.key === 'ArrowDown') {
|
|
181
|
+
event.preventDefault()
|
|
182
|
+
lower = clamp(snapToStep(lower - increment), min, upper)
|
|
183
|
+
fireChange()
|
|
184
|
+
} else if (event.key === 'Home') {
|
|
185
|
+
event.preventDefault()
|
|
186
|
+
lower = min
|
|
187
|
+
fireChange()
|
|
188
|
+
} else if (event.key === 'End') {
|
|
189
|
+
event.preventDefault()
|
|
190
|
+
lower = upper
|
|
191
|
+
fireChange()
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// ─── Track click ────────────────────────────────────────────────
|
|
196
|
+
|
|
197
|
+
function handleTrackClick(event: MouseEvent) {
|
|
198
|
+
if (disabled) return
|
|
199
|
+
const target = event.currentTarget as HTMLElement
|
|
200
|
+
const rect = target.getBoundingClientRect()
|
|
201
|
+
const px = event.clientX - rect.left
|
|
202
|
+
const raw = pixelToValue(px)
|
|
203
|
+
const snapped = snapToStep(clamp(raw, min, max))
|
|
204
|
+
|
|
205
|
+
if (rangeMode) {
|
|
206
|
+
// Click closer to lower → adjust lower, else adjust upper
|
|
207
|
+
const distLower = Math.abs(snapped - lower)
|
|
208
|
+
const distUpper = Math.abs(snapped - upper)
|
|
209
|
+
if (distLower < distUpper) {
|
|
210
|
+
lower = clamp(snapped, min, upper)
|
|
211
|
+
} else {
|
|
212
|
+
upper = clamp(snapped, lower, max)
|
|
213
|
+
}
|
|
214
|
+
} else {
|
|
215
|
+
value = snapped
|
|
216
|
+
}
|
|
217
|
+
fireChange()
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// ─── Tick click ─────────────────────────────────────────────────
|
|
221
|
+
|
|
222
|
+
function handleTickClick(tickValue: number) {
|
|
223
|
+
if (disabled) return
|
|
224
|
+
const snapped = snapToStep(clamp(tickValue, min, max))
|
|
225
|
+
|
|
226
|
+
if (rangeMode) {
|
|
227
|
+
const distLower = Math.abs(snapped - lower)
|
|
228
|
+
const distUpper = Math.abs(snapped - upper)
|
|
229
|
+
if (distLower < distUpper) {
|
|
230
|
+
lower = clamp(snapped, min, upper)
|
|
231
|
+
} else {
|
|
232
|
+
upper = clamp(snapped, lower, max)
|
|
233
|
+
}
|
|
234
|
+
} else {
|
|
235
|
+
value = snapped
|
|
236
|
+
}
|
|
237
|
+
fireChange()
|
|
238
|
+
}
|
|
239
|
+
</script>
|
|
240
|
+
|
|
241
|
+
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
|
242
|
+
<div
|
|
243
|
+
data-range
|
|
244
|
+
data-disabled={disabled || undefined}
|
|
245
|
+
class={className || undefined}
|
|
246
|
+
>
|
|
247
|
+
<!-- svelte-ignore a11y_click_events_have_key_events -->
|
|
248
|
+
<div data-range-track onclick={handleTrackClick}>
|
|
249
|
+
<div data-range-bar bind:clientWidth={trackWidth}></div>
|
|
250
|
+
<div
|
|
251
|
+
data-range-selected
|
|
252
|
+
style:left="{selectedLeft}%"
|
|
253
|
+
style:width="{selectedWidth}%"
|
|
254
|
+
></div>
|
|
255
|
+
|
|
256
|
+
{#if rangeMode}
|
|
257
|
+
<!-- Lower thumb -->
|
|
258
|
+
<div
|
|
259
|
+
data-range-thumb
|
|
260
|
+
data-thumb="lower"
|
|
261
|
+
data-sliding={slidingLower || undefined}
|
|
262
|
+
style:left="{valueToPercent(lower)}%"
|
|
263
|
+
tabindex={disabled ? -1 : 0}
|
|
264
|
+
use:pannable
|
|
265
|
+
onpanstart={handleLowerPanStart}
|
|
266
|
+
onpanmove={handleLowerPanMove}
|
|
267
|
+
onpanend={handleLowerPanEnd}
|
|
268
|
+
onkeydown={handleLowerKeyDown}
|
|
269
|
+
onfocus={() => { if (!disabled) slidingLower = true }}
|
|
270
|
+
onblur={() => { slidingLower = false }}
|
|
271
|
+
role="slider"
|
|
272
|
+
aria-valuenow={lower}
|
|
273
|
+
aria-valuemin={min}
|
|
274
|
+
aria-valuemax={upper}
|
|
275
|
+
aria-disabled={disabled || undefined}
|
|
276
|
+
aria-label="Lower bound"
|
|
277
|
+
></div>
|
|
278
|
+
{/if}
|
|
279
|
+
|
|
280
|
+
<!-- Upper thumb (or single thumb) -->
|
|
281
|
+
<div
|
|
282
|
+
data-range-thumb
|
|
283
|
+
data-thumb={rangeMode ? 'upper' : 'value'}
|
|
284
|
+
data-sliding={slidingUpper || undefined}
|
|
285
|
+
style:left="{valueToPercent(rangeMode ? upper : value)}%"
|
|
286
|
+
tabindex={disabled ? -1 : 0}
|
|
287
|
+
use:pannable
|
|
288
|
+
onpanstart={handleUpperPanStart}
|
|
289
|
+
onpanmove={handleUpperPanMove}
|
|
290
|
+
onpanend={handleUpperPanEnd}
|
|
291
|
+
onkeydown={handleUpperKeyDown}
|
|
292
|
+
onfocus={() => { if (!disabled) slidingUpper = true }}
|
|
293
|
+
onblur={() => { slidingUpper = false }}
|
|
294
|
+
role="slider"
|
|
295
|
+
aria-valuenow={rangeMode ? upper : value}
|
|
296
|
+
aria-valuemin={rangeMode ? lower : min}
|
|
297
|
+
aria-valuemax={max}
|
|
298
|
+
aria-disabled={disabled || undefined}
|
|
299
|
+
aria-label={rangeMode ? 'Upper bound' : 'Value'}
|
|
300
|
+
></div>
|
|
301
|
+
</div>
|
|
302
|
+
|
|
303
|
+
{#if tickItems.length > 0}
|
|
304
|
+
<div data-range-ticks style:--count={tickItems.length - 1}>
|
|
305
|
+
{#each tickItems as tick (tick.value)}
|
|
306
|
+
<!-- svelte-ignore a11y_click_events_have_key_events -->
|
|
307
|
+
<div
|
|
308
|
+
data-range-tick
|
|
309
|
+
data-disabled={disabled || undefined}
|
|
310
|
+
role="option"
|
|
311
|
+
aria-disabled={disabled || undefined}
|
|
312
|
+
tabindex={disabled ? -1 : 0}
|
|
313
|
+
onclick={() => handleTickClick(tick.value)}
|
|
314
|
+
>
|
|
315
|
+
<div data-tick-bar></div>
|
|
316
|
+
{#if tick.label !== ''}
|
|
317
|
+
<span data-tick-label>{tick.label}</span>
|
|
318
|
+
{:else}
|
|
319
|
+
<span data-tick-label></span>
|
|
320
|
+
{/if}
|
|
321
|
+
</div>
|
|
322
|
+
{/each}
|
|
323
|
+
</div>
|
|
324
|
+
{/if}
|
|
325
|
+
</div>
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
interface RatingProps {
|
|
3
|
+
/** Current rating value (bindable) */
|
|
4
|
+
value?: number
|
|
5
|
+
/** Maximum number of stars (default: 5) */
|
|
6
|
+
max?: number
|
|
7
|
+
/** Disabled state */
|
|
8
|
+
disabled?: boolean
|
|
9
|
+
/** CSS class for filled star icon */
|
|
10
|
+
filledIcon?: string
|
|
11
|
+
/** CSS class for empty star icon */
|
|
12
|
+
emptyIcon?: string
|
|
13
|
+
/** Called when value changes */
|
|
14
|
+
onchange?: (value: number) => void
|
|
15
|
+
/** Additional CSS class */
|
|
16
|
+
class?: string
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
let {
|
|
20
|
+
value = $bindable(0),
|
|
21
|
+
max = 5,
|
|
22
|
+
disabled = false,
|
|
23
|
+
filledIcon = 'i-lucide:star',
|
|
24
|
+
emptyIcon = 'i-lucide:star',
|
|
25
|
+
onchange,
|
|
26
|
+
class: className = ''
|
|
27
|
+
}: RatingProps = $props()
|
|
28
|
+
|
|
29
|
+
let hoverIndex = $state(-1)
|
|
30
|
+
|
|
31
|
+
function handleClick(index: number) {
|
|
32
|
+
if (disabled) return
|
|
33
|
+
// Clicking same star toggles to 0, otherwise set to index + 1
|
|
34
|
+
const newValue = value === index + 1 ? 0 : index + 1
|
|
35
|
+
value = newValue
|
|
36
|
+
onchange?.(newValue)
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function handleKeyDown(event: KeyboardEvent) {
|
|
40
|
+
if (disabled) return
|
|
41
|
+
|
|
42
|
+
if (event.key === 'ArrowRight' || event.key === 'ArrowUp') {
|
|
43
|
+
event.preventDefault()
|
|
44
|
+
const newValue = Math.min(value + 1, max)
|
|
45
|
+
value = newValue
|
|
46
|
+
onchange?.(newValue)
|
|
47
|
+
} else if (event.key === 'ArrowLeft' || event.key === 'ArrowDown') {
|
|
48
|
+
event.preventDefault()
|
|
49
|
+
const newValue = Math.max(value - 1, 0)
|
|
50
|
+
value = newValue
|
|
51
|
+
onchange?.(newValue)
|
|
52
|
+
} else {
|
|
53
|
+
const digit = parseInt(event.key, 10)
|
|
54
|
+
if (!isNaN(digit) && digit >= 0 && digit <= max) {
|
|
55
|
+
event.preventDefault()
|
|
56
|
+
value = digit
|
|
57
|
+
onchange?.(digit)
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
</script>
|
|
62
|
+
|
|
63
|
+
<!-- svelte-ignore a11y_no_noninteractive_tabindex -->
|
|
64
|
+
<div
|
|
65
|
+
data-rating
|
|
66
|
+
data-rating-disabled={disabled || undefined}
|
|
67
|
+
class={className || undefined}
|
|
68
|
+
role="radiogroup"
|
|
69
|
+
aria-label="Rating"
|
|
70
|
+
aria-disabled={disabled || undefined}
|
|
71
|
+
tabindex={disabled ? undefined : 0}
|
|
72
|
+
onkeydown={handleKeyDown}
|
|
73
|
+
>
|
|
74
|
+
{#each Array(max) as _, index (index)}
|
|
75
|
+
{@const filled = index < value}
|
|
76
|
+
<!-- svelte-ignore a11y_click_events_have_key_events -->
|
|
77
|
+
<span
|
|
78
|
+
data-rating-item
|
|
79
|
+
data-filled={filled || undefined}
|
|
80
|
+
data-hovering={index <= hoverIndex || undefined}
|
|
81
|
+
role="radio"
|
|
82
|
+
aria-checked={filled}
|
|
83
|
+
aria-label="Rate {index + 1} of {max}"
|
|
84
|
+
onclick={() => handleClick(index)}
|
|
85
|
+
onmouseenter={() => { if (!disabled) hoverIndex = index }}
|
|
86
|
+
onmouseleave={() => { hoverIndex = -1 }}
|
|
87
|
+
>
|
|
88
|
+
<span class={filled ? filledIcon : emptyIcon} aria-hidden="true"></span>
|
|
89
|
+
</span>
|
|
90
|
+
{/each}
|
|
91
|
+
</div>
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { Snippet } from 'svelte'
|
|
3
|
+
import { reveal } from '@rokkit/actions'
|
|
4
|
+
|
|
5
|
+
interface RevealProps {
|
|
6
|
+
/** Slide direction (default: 'up') */
|
|
7
|
+
direction?: 'up' | 'down' | 'left' | 'right' | 'none'
|
|
8
|
+
/** Slide distance CSS value (default: '1.5rem') */
|
|
9
|
+
distance?: string
|
|
10
|
+
/** Transition duration in ms (default: 600) */
|
|
11
|
+
duration?: number
|
|
12
|
+
/** Delay before animation starts in ms (default: 0) */
|
|
13
|
+
delay?: number
|
|
14
|
+
/** Delay increment per direct child in ms for stagger (default: 0) */
|
|
15
|
+
stagger?: number
|
|
16
|
+
/** Only animate once (default: true) */
|
|
17
|
+
once?: boolean
|
|
18
|
+
/** IntersectionObserver threshold 0–1 (default: 0.1) */
|
|
19
|
+
threshold?: number
|
|
20
|
+
/** CSS easing function */
|
|
21
|
+
easing?: string
|
|
22
|
+
/** Additional CSS class */
|
|
23
|
+
class?: string
|
|
24
|
+
children?: Snippet
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const {
|
|
28
|
+
direction = 'up',
|
|
29
|
+
distance = '1.5rem',
|
|
30
|
+
duration = 600,
|
|
31
|
+
delay = 0,
|
|
32
|
+
stagger = 0,
|
|
33
|
+
once = true,
|
|
34
|
+
threshold = 0.1,
|
|
35
|
+
easing = 'cubic-bezier(0.4, 0, 0.2, 1)',
|
|
36
|
+
class: className = '',
|
|
37
|
+
children
|
|
38
|
+
}: RevealProps = $props()
|
|
39
|
+
|
|
40
|
+
let el: HTMLDivElement | null = $state(null)
|
|
41
|
+
|
|
42
|
+
function handleReveal(e: CustomEvent<{ visible: boolean }>) {
|
|
43
|
+
if (!e.detail.visible || stagger <= 0 || !el) return
|
|
44
|
+
const children = el.children
|
|
45
|
+
for (let i = 0; i < children.length; i++) {
|
|
46
|
+
;(children[i] as HTMLElement).style.transitionDelay = `${delay + i * stagger}ms`
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
</script>
|
|
50
|
+
|
|
51
|
+
<div
|
|
52
|
+
bind:this={el}
|
|
53
|
+
class={className || undefined}
|
|
54
|
+
use:reveal={{ direction, distance, duration, delay: stagger > 0 ? 0 : delay, once, threshold, easing }}
|
|
55
|
+
onreveal={handleReveal}
|
|
56
|
+
>
|
|
57
|
+
{@render children?.()}
|
|
58
|
+
</div>
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { SearchFilterProps, FilterObject } from '../types/search-filter.js'
|
|
3
|
+
import { parseFilters } from '@rokkit/data'
|
|
4
|
+
|
|
5
|
+
let {
|
|
6
|
+
filters = $bindable([]),
|
|
7
|
+
debounce: delay = 300,
|
|
8
|
+
placeholder = 'Search...',
|
|
9
|
+
columns: _columns,
|
|
10
|
+
onfilter,
|
|
11
|
+
class: className = '',
|
|
12
|
+
size = 'md',
|
|
13
|
+
tag: tagSnippet
|
|
14
|
+
}: SearchFilterProps = $props()
|
|
15
|
+
|
|
16
|
+
let inputText = $state('')
|
|
17
|
+
let timer: ReturnType<typeof setTimeout> | undefined
|
|
18
|
+
|
|
19
|
+
function handleInput() {
|
|
20
|
+
clearTimeout(timer)
|
|
21
|
+
timer = setTimeout(() => {
|
|
22
|
+
filters = parseFilters(inputText)
|
|
23
|
+
onfilter?.(filters)
|
|
24
|
+
}, delay)
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function removeFilter(index: number) {
|
|
28
|
+
filters = filters.filter((_: FilterObject, i: number) => i !== index)
|
|
29
|
+
onfilter?.(filters)
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function clear() {
|
|
33
|
+
inputText = ''
|
|
34
|
+
filters = []
|
|
35
|
+
onfilter?.(filters)
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function formatFilter(filter: FilterObject): string {
|
|
39
|
+
const col = filter.column ? `${filter.column} ${filter.operator} ` : ''
|
|
40
|
+
const val = filter.value instanceof RegExp ? filter.value.source : String(filter.value)
|
|
41
|
+
return `${col}${val}`
|
|
42
|
+
}
|
|
43
|
+
</script>
|
|
44
|
+
|
|
45
|
+
<div data-search-filter data-size={size} class={className || undefined}>
|
|
46
|
+
<div data-search-input-wrapper>
|
|
47
|
+
<input
|
|
48
|
+
type="search"
|
|
49
|
+
data-search-input
|
|
50
|
+
bind:value={inputText}
|
|
51
|
+
oninput={handleInput}
|
|
52
|
+
{placeholder}
|
|
53
|
+
/>
|
|
54
|
+
{#if inputText}
|
|
55
|
+
<button data-search-clear onclick={clear} aria-label="Clear search" type="button">
|
|
56
|
+
×
|
|
57
|
+
</button>
|
|
58
|
+
{/if}
|
|
59
|
+
</div>
|
|
60
|
+
|
|
61
|
+
{#if filters.length > 0}
|
|
62
|
+
<div data-search-tags>
|
|
63
|
+
{#each filters as filter, i}
|
|
64
|
+
{#if tagSnippet}
|
|
65
|
+
{@render tagSnippet(filter, () => removeFilter(i))}
|
|
66
|
+
{:else}
|
|
67
|
+
<span data-search-tag>
|
|
68
|
+
<span data-search-tag-text>{formatFilter(filter)}</span>
|
|
69
|
+
<button
|
|
70
|
+
data-search-tag-remove
|
|
71
|
+
onclick={() => removeFilter(i)}
|
|
72
|
+
aria-label="Remove filter"
|
|
73
|
+
type="button"
|
|
74
|
+
>×</button>
|
|
75
|
+
</span>
|
|
76
|
+
{/if}
|
|
77
|
+
{/each}
|
|
78
|
+
</div>
|
|
79
|
+
{/if}
|
|
80
|
+
</div>
|