@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.
Files changed (146) hide show
  1. package/README.md +198 -101
  2. package/package.json +52 -34
  3. package/src/components/BreadCrumbs.svelte +82 -0
  4. package/src/components/Button.svelte +87 -0
  5. package/src/components/ButtonGroup.svelte +18 -0
  6. package/src/components/Card.svelte +61 -0
  7. package/src/components/Carousel.svelte +169 -0
  8. package/src/components/Code.svelte +185 -0
  9. package/src/components/Connector.svelte +46 -0
  10. package/src/components/FloatingAction.svelte +331 -0
  11. package/src/components/FloatingNavigation.svelte +228 -0
  12. package/src/components/ItemContent.svelte +24 -0
  13. package/src/components/List.svelte +476 -0
  14. package/src/components/Menu.svelte +421 -0
  15. package/src/components/MultiSelect.svelte +521 -0
  16. package/src/components/PaletteManager.svelte +354 -0
  17. package/src/components/Pill.svelte +78 -0
  18. package/src/components/ProgressBar.svelte +31 -0
  19. package/src/components/Range.svelte +325 -0
  20. package/src/components/Rating.svelte +91 -0
  21. package/src/components/Reveal.svelte +58 -0
  22. package/src/components/SearchFilter.svelte +80 -0
  23. package/src/components/Select.svelte +585 -0
  24. package/src/{Shine.svelte → components/Shine.svelte} +29 -21
  25. package/src/components/Stepper.svelte +169 -0
  26. package/src/components/Switch.svelte +75 -0
  27. package/src/components/Table.svelte +243 -0
  28. package/src/components/Tabs.svelte +268 -0
  29. package/src/components/Tilt.svelte +68 -0
  30. package/src/components/Timeline.svelte +61 -0
  31. package/src/components/Toggle.svelte +157 -0
  32. package/src/components/Toolbar.svelte +307 -0
  33. package/src/components/ToolbarGroup.svelte +17 -0
  34. package/src/components/Tree.svelte +613 -0
  35. package/src/components/index.ts +33 -0
  36. package/src/index.ts +41 -0
  37. package/src/types/button.ts +83 -0
  38. package/src/types/code.ts +46 -0
  39. package/src/types/floating-action.ts +118 -0
  40. package/src/types/floating-navigation.ts +68 -0
  41. package/src/types/index.ts +53 -0
  42. package/src/types/item-proxy.ts +358 -0
  43. package/src/types/list.ts +196 -0
  44. package/src/types/menu.ts +195 -0
  45. package/src/types/palette.ts +143 -0
  46. package/src/types/range.ts +51 -0
  47. package/src/types/search-filter.ts +67 -0
  48. package/src/types/select.ts +206 -0
  49. package/src/types/switch.ts +64 -0
  50. package/src/types/table.ts +210 -0
  51. package/src/types/tabs.ts +124 -0
  52. package/src/types/timeline.ts +51 -0
  53. package/src/types/toggle.ts +109 -0
  54. package/src/types/toolbar.ts +164 -0
  55. package/src/types/tree.ts +259 -0
  56. package/src/utils/palette.ts +582 -0
  57. package/src/utils/shiki.ts +122 -0
  58. package/dist/constants.d.ts +0 -2
  59. package/dist/index.d.ts +0 -41
  60. package/dist/lib/fields.d.ts +0 -16
  61. package/dist/lib/form.d.ts +0 -95
  62. package/dist/lib/index.d.ts +0 -6
  63. package/dist/lib/layout.d.ts +0 -7
  64. package/dist/lib/nested.d.ts +0 -48
  65. package/dist/lib/schema.d.ts +0 -7
  66. package/dist/lib/select.d.ts +0 -8
  67. package/dist/lib/tree.d.ts +0 -9
  68. package/dist/tree/List.spec.svelte.d.ts +0 -1
  69. package/dist/tree/Node.spec.svelte.d.ts +0 -1
  70. package/dist/tree/Root.spec.svelte.d.ts +0 -1
  71. package/dist/types.d.ts +0 -5
  72. package/dist/wrappers/index.d.ts +0 -3
  73. package/src/Accordion.svelte +0 -118
  74. package/src/BreadCrumbs.svelte +0 -32
  75. package/src/Button.svelte +0 -57
  76. package/src/Calendar.svelte +0 -93
  77. package/src/Card.svelte +0 -45
  78. package/src/Carousel.svelte +0 -49
  79. package/src/CheckBox.svelte +0 -56
  80. package/src/Connector.svelte +0 -40
  81. package/src/DropDown.svelte +0 -68
  82. package/src/DropSearch.svelte +0 -37
  83. package/src/Fillable.svelte +0 -19
  84. package/src/GraphPaper.svelte +0 -43
  85. package/src/Icon.svelte +0 -81
  86. package/src/Item.svelte +0 -25
  87. package/src/Link.svelte +0 -21
  88. package/src/List.svelte +0 -89
  89. package/src/ListBody.svelte +0 -43
  90. package/src/Message.svelte +0 -11
  91. package/src/MultiSelect.svelte +0 -48
  92. package/src/NestedList.svelte +0 -78
  93. package/src/NestedPaginator.svelte +0 -63
  94. package/src/Node.svelte +0 -76
  95. package/src/Overlay.svelte +0 -21
  96. package/src/PageNavigator.svelte +0 -94
  97. package/src/PickOne.svelte +0 -60
  98. package/src/Pill.svelte +0 -41
  99. package/src/ProgressBar.svelte +0 -21
  100. package/src/ProgressDots.svelte +0 -53
  101. package/src/RadioGroup.svelte +0 -52
  102. package/src/Range.svelte +0 -45
  103. package/src/RangeMinMax.svelte +0 -124
  104. package/src/RangeSlider.svelte +0 -79
  105. package/src/RangeTick.svelte +0 -28
  106. package/src/Rating.svelte +0 -95
  107. package/src/ResponsiveGrid.svelte +0 -88
  108. package/src/Scrollable.svelte +0 -7
  109. package/src/Select.svelte +0 -114
  110. package/src/Separator.svelte +0 -1
  111. package/src/Slider.svelte +0 -14
  112. package/src/SlidingColumns.svelte +0 -50
  113. package/src/Stage.svelte +0 -41
  114. package/src/Stepper.svelte +0 -66
  115. package/src/Summary.svelte +0 -22
  116. package/src/Switch.svelte +0 -106
  117. package/src/TableCell.svelte +0 -51
  118. package/src/TableHeaderCell.svelte +0 -54
  119. package/src/Tabs.svelte +0 -176
  120. package/src/Tilt.svelte +0 -66
  121. package/src/Toggle.svelte +0 -58
  122. package/src/ToggleThemeMode.svelte +0 -23
  123. package/src/Tree.svelte +0 -80
  124. package/src/TreeTable.svelte +0 -171
  125. package/src/ValidationReport.svelte +0 -23
  126. package/src/constants.js +0 -4
  127. package/src/index.js +0 -48
  128. package/src/lib/fields.js +0 -118
  129. package/src/lib/form.js +0 -72
  130. package/src/lib/index.js +0 -13
  131. package/src/lib/layout.js +0 -63
  132. package/src/lib/nested.js +0 -192
  133. package/src/lib/schema.js +0 -32
  134. package/src/lib/select.js +0 -38
  135. package/src/lib/tree.js +0 -22
  136. package/src/tree/List.spec.svelte.js +0 -84
  137. package/src/tree/List.svelte +0 -78
  138. package/src/tree/Node.spec.svelte.js +0 -104
  139. package/src/tree/Node.svelte +0 -80
  140. package/src/tree/Root.spec.svelte.js +0 -63
  141. package/src/tree/Root.svelte +0 -81
  142. package/src/types.js +0 -9
  143. package/src/wrappers/Category.svelte +0 -27
  144. package/src/wrappers/Section.svelte +0 -16
  145. package/src/wrappers/Wrapper.svelte +0 -12
  146. 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
+ &times;
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
+ >&times;</button>
75
+ </span>
76
+ {/if}
77
+ {/each}
78
+ </div>
79
+ {/if}
80
+ </div>