@tachui/forms 0.7.1-alpha → 0.8.1-alpha

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 (90) hide show
  1. package/README.md +87 -272
  2. package/dist/DatePicker-D5nRFTUm.js +475 -0
  3. package/dist/DatePicker-D5nRFTUm.js.map +1 -0
  4. package/dist/Select-yZyKooXk.js +945 -0
  5. package/dist/Select-yZyKooXk.js.map +1 -0
  6. package/dist/Slider-0-oal5YR.js +644 -0
  7. package/dist/Slider-0-oal5YR.js.map +1 -0
  8. package/dist/TextField-hX15dY3U.js +509 -0
  9. package/dist/TextField-hX15dY3U.js.map +1 -0
  10. package/dist/components/advanced/Slider.d.ts +190 -0
  11. package/dist/components/advanced/Slider.d.ts.map +1 -0
  12. package/dist/components/advanced/Stepper.d.ts +161 -0
  13. package/dist/components/advanced/Stepper.d.ts.map +1 -0
  14. package/dist/components/advanced/index.d.ts +15 -0
  15. package/dist/components/advanced/index.d.ts.map +1 -0
  16. package/dist/components/advanced/index.js +6 -0
  17. package/dist/components/advanced/index.js.map +1 -0
  18. package/dist/components/date-picker/DatePicker.d.ts +126 -0
  19. package/dist/components/date-picker/DatePicker.d.ts.map +1 -0
  20. package/dist/components/date-picker/index.d.ts +14 -0
  21. package/dist/components/date-picker/index.d.ts.map +1 -0
  22. package/dist/components/date-picker/index.js +5 -0
  23. package/dist/components/date-picker/index.js.map +1 -0
  24. package/dist/components/form-container/index.d.ts +58 -0
  25. package/dist/components/form-container/index.d.ts.map +1 -0
  26. package/dist/components/selection/Checkbox.d.ts.map +1 -0
  27. package/dist/components/selection/Radio.d.ts.map +1 -0
  28. package/dist/components/selection/Select.d.ts.map +1 -0
  29. package/dist/components/selection/index.d.ts +68 -0
  30. package/dist/components/selection/index.d.ts.map +1 -0
  31. package/dist/components/selection/index.js +12 -0
  32. package/dist/components/selection/index.js.map +1 -0
  33. package/dist/components/text-input/TextField.d.ts.map +1 -0
  34. package/dist/components/text-input/index.d.ts +8 -0
  35. package/dist/components/text-input/index.d.ts.map +1 -0
  36. package/dist/components/text-input/index.js +18 -0
  37. package/dist/components/text-input/index.js.map +1 -0
  38. package/dist/{state/index.js → index-D3WfkqVv.js} +15 -8
  39. package/dist/index-D3WfkqVv.js.map +1 -0
  40. package/dist/index.d.ts +10 -15
  41. package/dist/index.d.ts.map +1 -1
  42. package/dist/index.js +198 -376
  43. package/dist/index.js.map +1 -0
  44. package/dist/state/index.d.ts.map +1 -1
  45. package/dist/types/index.d.ts.map +1 -1
  46. package/dist/utils/index.d.ts +19 -0
  47. package/dist/utils/index.d.ts.map +1 -0
  48. package/dist/validation/component-validation.d.ts +11 -2
  49. package/dist/validation/component-validation.d.ts.map +1 -1
  50. package/dist/validation/index.d.ts.map +1 -1
  51. package/dist/validation/index.js +282 -191
  52. package/dist/validation/index.js.map +1 -0
  53. package/package.json +53 -39
  54. package/src/components/advanced/Slider.ts +722 -0
  55. package/src/components/advanced/Stepper.ts +715 -0
  56. package/src/components/advanced/index.ts +20 -0
  57. package/src/components/date-picker/DatePicker.ts +925 -0
  58. package/src/components/date-picker/index.ts +20 -0
  59. package/src/components/form-container/index.ts +266 -0
  60. package/src/components/selection/Checkbox.ts +478 -0
  61. package/src/components/selection/Radio.ts +470 -0
  62. package/src/components/selection/Select.ts +620 -0
  63. package/src/components/selection/index.ts +81 -0
  64. package/src/components/text-input/TextField.ts +728 -0
  65. package/src/components/text-input/index.ts +35 -0
  66. package/src/index.ts +48 -0
  67. package/src/state/index.ts +544 -0
  68. package/src/types/index.ts +579 -0
  69. package/src/utils/formatters.ts +184 -0
  70. package/src/utils/index.ts +57 -0
  71. package/src/validation/component-validation.ts +429 -0
  72. package/src/validation/index.ts +641 -0
  73. package/dist/TextField-CGBM3x7K.js +0 -1799
  74. package/dist/components/Form.d.ts +0 -76
  75. package/dist/components/Form.d.ts.map +0 -1
  76. package/dist/components/index.d.ts +0 -9
  77. package/dist/components/index.d.ts.map +0 -1
  78. package/dist/components/index.js +0 -28
  79. package/dist/components/input/Checkbox.d.ts.map +0 -1
  80. package/dist/components/input/Radio.d.ts.map +0 -1
  81. package/dist/components/input/Select.d.ts.map +0 -1
  82. package/dist/components/input/TextField.d.ts.map +0 -1
  83. package/dist/components/input/index.d.ts +0 -11
  84. package/dist/components/input/index.d.ts.map +0 -1
  85. package/dist/utils/validators.d.ts +0 -101
  86. package/dist/utils/validators.d.ts.map +0 -1
  87. /package/dist/components/{input → selection}/Checkbox.d.ts +0 -0
  88. /package/dist/components/{input → selection}/Radio.d.ts +0 -0
  89. /package/dist/components/{input → selection}/Select.d.ts +0 -0
  90. /package/dist/components/{input → text-input}/TextField.d.ts +0 -0
@@ -0,0 +1,722 @@
1
+ /**
2
+ * Enhanced Slider Component (Phase 6.4.4)
3
+ *
4
+ * SwiftUI-inspired Slider component with range selection, formatting,
5
+ * and precise value control for numeric input.
6
+ */
7
+
8
+ import type { ModifiableComponent, ModifierBuilder } from '@tachui/core'
9
+ import { createEffect, createSignal, isSignal } from '@tachui/core'
10
+ import type { Signal } from '@tachui/core'
11
+ import { h, text } from '@tachui/core'
12
+ import type { ComponentInstance, ComponentProps } from '@tachui/core'
13
+ import { withModifiers } from '@tachui/core'
14
+
15
+ /**
16
+ * Slider component properties
17
+ */
18
+ export interface SliderProps extends ComponentProps {
19
+ // Value binding
20
+ value: number | Signal<number>
21
+ onValueChange?: (value: number) => void
22
+
23
+ // Range
24
+ min?: number
25
+ max?: number
26
+ step?: number
27
+
28
+ // Formatting
29
+ formatter?: (value: number) => string
30
+ minimumValueLabel?: string
31
+ maximumValueLabel?: string
32
+
33
+ // Appearance
34
+ variant?: 'default' | 'filled' | 'minimal'
35
+ trackColor?: string
36
+ thumbColor?: string
37
+ activeTrackColor?: string
38
+ size?: 'small' | 'medium' | 'large'
39
+
40
+ // Behavior
41
+ disabled?: boolean | Signal<boolean>
42
+ showValue?: boolean
43
+ showLabels?: boolean
44
+
45
+ // Advanced features
46
+ marks?: SliderMark[]
47
+ range?: boolean
48
+ vertical?: boolean
49
+
50
+ // Accessibility
51
+ accessibilityLabel?: string
52
+ accessibilityHint?: string
53
+ accessibilityValueDescription?: (value: number) => string
54
+ }
55
+
56
+ /**
57
+ * Slider mark configuration
58
+ */
59
+ export interface SliderMark {
60
+ value: number
61
+ label?: string
62
+ color?: string
63
+ }
64
+
65
+ /**
66
+ * Enhanced Slider component class
67
+ */
68
+ export class EnhancedSlider implements ComponentInstance<SliderProps> {
69
+ public readonly type = 'component' as const
70
+ public readonly id: string
71
+ public mounted = false
72
+ public cleanup: (() => void)[] = []
73
+ private sliderElement: HTMLInputElement | null = null
74
+ private isDragging: () => boolean
75
+ private setIsDragging: (dragging: boolean) => boolean
76
+
77
+ constructor(public props: SliderProps) {
78
+ this.id = `slider-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`
79
+
80
+ // Initialize internal state
81
+ const [dragging, setDragging] = createSignal(false)
82
+ this.isDragging = dragging
83
+ this.setIsDragging = setDragging
84
+
85
+ // Handle value changes reactively
86
+ createEffect(() => {
87
+ const currentValue = this.getValue()
88
+ if (this.sliderElement && !this.isDragging()) {
89
+ this.sliderElement.value = String(currentValue)
90
+ this.updateTrackFill()
91
+ }
92
+ })
93
+ }
94
+
95
+ /**
96
+ * Get current value
97
+ */
98
+ private getValue(): number {
99
+ const { value } = this.props
100
+ if (isSignal(value)) {
101
+ return (value as () => number)()
102
+ }
103
+ return value as number
104
+ }
105
+
106
+ /**
107
+ * Check if slider is disabled
108
+ */
109
+ private isDisabled(): boolean {
110
+ const { disabled } = this.props
111
+ if (typeof disabled === 'boolean') return disabled
112
+ if (isSignal(disabled)) return (disabled as () => boolean)()
113
+ return false
114
+ }
115
+
116
+ /**
117
+ * Format value for display
118
+ */
119
+ private formatValue(value: number): string {
120
+ const { formatter } = this.props
121
+ if (formatter) {
122
+ return formatter(value)
123
+ }
124
+
125
+ // Default formatting based on step
126
+ const step = this.props.step || 1
127
+ if (step < 1) {
128
+ const decimals = String(step).split('.')[1]?.length || 0
129
+ return value.toFixed(decimals)
130
+ }
131
+
132
+ return String(Math.round(value))
133
+ }
134
+
135
+ /**
136
+ * Handle value change
137
+ */
138
+ private handleValueChange = (newValue: number) => {
139
+ const { min = 0, max = 100, step = 1 } = this.props
140
+
141
+ // Clamp value to range
142
+ let clampedValue = Math.max(min, Math.min(max, newValue))
143
+
144
+ // Snap to step
145
+ if (step > 0) {
146
+ clampedValue = Math.round((clampedValue - min) / step) * step + min
147
+ }
148
+
149
+ // Update value
150
+ if (this.props.onValueChange) {
151
+ this.props.onValueChange(clampedValue)
152
+ }
153
+
154
+ this.updateTrackFill()
155
+ }
156
+
157
+ /**
158
+ * Update track fill visual
159
+ */
160
+ private updateTrackFill() {
161
+ if (!this.sliderElement) return
162
+
163
+ const { min = 0, max = 100 } = this.props
164
+ const value = this.getValue()
165
+ const percentage = ((value - min) / (max - min)) * 100
166
+
167
+ // Update CSS custom property for track fill
168
+ this.sliderElement.style.setProperty('--slider-progress', `${percentage}%`)
169
+ }
170
+
171
+ /**
172
+ * Handle input events
173
+ */
174
+ private handleInput = (event: Event) => {
175
+ const target = event.target as HTMLInputElement
176
+ const value = parseFloat(target.value)
177
+
178
+ if (!Number.isNaN(value)) {
179
+ this.handleValueChange(value)
180
+ }
181
+ }
182
+
183
+ /**
184
+ * Handle mouse/touch start
185
+ */
186
+ private handleStart = () => {
187
+ if (!this.isDisabled()) {
188
+ this.setIsDragging(true)
189
+ }
190
+ }
191
+
192
+ /**
193
+ * Handle mouse/touch end
194
+ */
195
+ private handleEnd = () => {
196
+ this.setIsDragging(false)
197
+ }
198
+
199
+ /**
200
+ * Get slider size styles
201
+ */
202
+ private getSizeStyles() {
203
+ const { size = 'medium' } = this.props
204
+
205
+ switch (size) {
206
+ case 'small':
207
+ return {
208
+ height: '4px',
209
+ thumbSize: '16px',
210
+ }
211
+ case 'large':
212
+ return {
213
+ height: '8px',
214
+ thumbSize: '24px',
215
+ }
216
+ default:
217
+ return {
218
+ height: '6px',
219
+ thumbSize: '20px',
220
+ }
221
+ }
222
+ }
223
+
224
+ /**
225
+ * Get slider variant styles
226
+ */
227
+ private getVariantStyles() {
228
+ const {
229
+ variant = 'default',
230
+ trackColor = '#e2e8f0',
231
+ activeTrackColor = '#007AFF',
232
+ thumbColor = '#ffffff',
233
+ } = this.props
234
+ const sizeStyles = this.getSizeStyles()
235
+
236
+ const baseStyles = {
237
+ '--slider-track-color': trackColor,
238
+ '--slider-active-track-color': activeTrackColor,
239
+ '--slider-thumb-color': thumbColor,
240
+ '--slider-height': sizeStyles.height,
241
+ '--slider-thumb-size': sizeStyles.thumbSize,
242
+ }
243
+
244
+ switch (variant) {
245
+ case 'filled':
246
+ return {
247
+ ...baseStyles,
248
+ '--slider-track-color': '#f1f5f9',
249
+ '--slider-active-track-color': activeTrackColor,
250
+ backgroundColor: '#f8fafc',
251
+ borderRadius: '12px',
252
+ padding: '8px',
253
+ }
254
+
255
+ case 'minimal':
256
+ return {
257
+ ...baseStyles,
258
+ '--slider-track-color': 'transparent',
259
+ '--slider-active-track-color': activeTrackColor,
260
+ border: `1px solid ${trackColor}`,
261
+ }
262
+ default:
263
+ return baseStyles
264
+ }
265
+ }
266
+
267
+ /**
268
+ * Render slider marks
269
+ */
270
+ private renderMarks() {
271
+ const { marks, min = 0, max = 100 } = this.props
272
+
273
+ if (!marks || marks.length === 0) {
274
+ return []
275
+ }
276
+
277
+ return marks.map(mark => {
278
+ const percentage = ((mark.value - min) / (max - min)) * 100
279
+
280
+ return h(
281
+ 'div',
282
+ {
283
+ key: String(mark.value),
284
+ style: {
285
+ position: 'absolute',
286
+ left: `${percentage}%`,
287
+ top: '50%',
288
+ transform: 'translate(-50%, -50%)',
289
+ display: 'flex',
290
+ flexDirection: 'column',
291
+ alignItems: 'center',
292
+ pointerEvents: 'none',
293
+ },
294
+ },
295
+ // Mark indicator
296
+ h('div', {
297
+ style: {
298
+ width: '2px',
299
+ height: '12px',
300
+ backgroundColor: mark.color || '#666',
301
+ marginBottom: '4px',
302
+ },
303
+ }),
304
+
305
+ // Mark label
306
+ ...(mark.label
307
+ ? [
308
+ h(
309
+ 'span',
310
+ {
311
+ style: {
312
+ fontSize: '12px',
313
+ color: '#666',
314
+ whiteSpace: 'nowrap',
315
+ },
316
+ },
317
+ text(mark.label)
318
+ ),
319
+ ]
320
+ : [])
321
+ )
322
+ })
323
+ }
324
+
325
+ /**
326
+ * Render value labels
327
+ */
328
+ private renderLabels() {
329
+ const {
330
+ showLabels,
331
+ min = 0,
332
+ max = 100,
333
+ minimumValueLabel,
334
+ maximumValueLabel,
335
+ } = this.props
336
+
337
+ if (!showLabels) return []
338
+
339
+ return [
340
+ h(
341
+ 'div',
342
+ {
343
+ style: {
344
+ display: 'flex',
345
+ justifyContent: 'space-between',
346
+ marginTop: '8px',
347
+ fontSize: '12px',
348
+ color: '#666',
349
+ },
350
+ },
351
+ h('span', {}, text(minimumValueLabel || this.formatValue(min))),
352
+ h('span', {}, text(maximumValueLabel || this.formatValue(max)))
353
+ ),
354
+ ]
355
+ }
356
+
357
+ /**
358
+ * Render value display
359
+ */
360
+ private renderValueDisplay() {
361
+ const { showValue } = this.props
362
+
363
+ if (!showValue) return []
364
+
365
+ const currentValue = this.getValue()
366
+
367
+ return [
368
+ h(
369
+ 'div',
370
+ {
371
+ style: {
372
+ display: 'flex',
373
+ justifyContent: 'center',
374
+ marginBottom: '8px',
375
+ fontSize: '14px',
376
+ fontWeight: '600',
377
+ color: '#1a1a1a',
378
+ },
379
+ },
380
+ text(this.formatValue(currentValue))
381
+ ),
382
+ ]
383
+ }
384
+
385
+ render() {
386
+ const {
387
+ min = 0,
388
+ max = 100,
389
+ step = 1,
390
+ vertical = false,
391
+ accessibilityLabel,
392
+ accessibilityHint,
393
+ accessibilityValueDescription,
394
+ } = this.props
395
+
396
+ const currentValue = this.getValue()
397
+ const variantStyles = this.getVariantStyles()
398
+ const isDisabled = this.isDisabled()
399
+
400
+ // Slider CSS styles
401
+ const sliderStyles = {
402
+ appearance: 'none' as const,
403
+ width: vertical ? '6px' : '100%',
404
+ height: vertical ? '200px' : 'var(--slider-height)',
405
+ background: `linear-gradient(to ${vertical ? 'top' : 'right'}, var(--slider-active-track-color) 0%, var(--slider-active-track-color) var(--slider-progress, 0%), var(--slider-track-color) var(--slider-progress, 0%), var(--slider-track-color) 100%)`,
406
+ borderRadius: 'calc(var(--slider-height) / 2)',
407
+ outline: 'none',
408
+ cursor: isDisabled ? 'not-allowed' : 'pointer',
409
+ opacity: isDisabled ? 0.5 : 1,
410
+ transition: 'all 0.2s ease',
411
+
412
+ // Webkit styles
413
+ WebkitAppearance: 'none' as const,
414
+
415
+ // Thumb styles
416
+ '&::-webkit-slider-thumb': {
417
+ appearance: 'none',
418
+ width: 'var(--slider-thumb-size)',
419
+ height: 'var(--slider-thumb-size)',
420
+ borderRadius: '50%',
421
+ background: 'var(--slider-thumb-color)',
422
+ border: '2px solid var(--slider-active-track-color)',
423
+ cursor: isDisabled ? 'not-allowed' : 'grab',
424
+ boxShadow: '0 2px 4px rgba(0,0,0,0.1)',
425
+ transition: 'all 0.2s ease',
426
+ },
427
+
428
+ '&::-webkit-slider-thumb:hover': !isDisabled
429
+ ? {
430
+ transform: 'scale(1.1)',
431
+ boxShadow: '0 4px 8px rgba(0,0,0,0.15)',
432
+ }
433
+ : {},
434
+
435
+ '&::-webkit-slider-thumb:active': !isDisabled
436
+ ? {
437
+ cursor: 'grabbing',
438
+ transform: 'scale(1.05)',
439
+ }
440
+ : {},
441
+
442
+ // Firefox styles
443
+ '&::-moz-range-thumb': {
444
+ width: 'var(--slider-thumb-size)',
445
+ height: 'var(--slider-thumb-size)',
446
+ borderRadius: '50%',
447
+ background: 'var(--slider-thumb-color)',
448
+ border: '2px solid var(--slider-active-track-color)',
449
+ cursor: isDisabled ? 'not-allowed' : 'pointer',
450
+ boxShadow: '0 2px 4px rgba(0,0,0,0.1)',
451
+ },
452
+
453
+ '&::-moz-range-track': {
454
+ height: 'var(--slider-height)',
455
+ borderRadius: 'calc(var(--slider-height) / 2)',
456
+ background: 'var(--slider-track-color)',
457
+ border: 'none',
458
+ },
459
+ }
460
+
461
+ return h(
462
+ 'div',
463
+ {
464
+ style: {
465
+ ...variantStyles,
466
+ position: 'relative',
467
+ display: 'flex',
468
+ flexDirection: vertical ? 'row' : 'column',
469
+ alignItems: vertical ? 'center' : 'stretch',
470
+ gap: vertical ? '12px' : '0',
471
+ },
472
+ 'aria-label': accessibilityLabel,
473
+ 'aria-describedby': accessibilityHint ? `${this.id}-hint` : undefined,
474
+ },
475
+ // Value display
476
+ ...this.renderValueDisplay(),
477
+
478
+ // Slider container
479
+ h(
480
+ 'div',
481
+ {
482
+ style: {
483
+ position: 'relative',
484
+ width: vertical ? 'auto' : '100%',
485
+ height: vertical ? '200px' : 'auto',
486
+ },
487
+ },
488
+ // Slider input
489
+ h('input', {
490
+ ref: (el: HTMLInputElement) => {
491
+ this.sliderElement = el
492
+
493
+ if (el && !this.mounted) {
494
+ // Set up event listeners
495
+ el.addEventListener('input', this.handleInput)
496
+ el.addEventListener('mousedown', this.handleStart)
497
+ el.addEventListener('mouseup', this.handleEnd)
498
+ el.addEventListener('touchstart', this.handleStart)
499
+ el.addEventListener('touchend', this.handleEnd)
500
+
501
+ this.cleanup.push(() => {
502
+ el.removeEventListener('input', this.handleInput)
503
+ el.removeEventListener('mousedown', this.handleStart)
504
+ el.removeEventListener('mouseup', this.handleEnd)
505
+ el.removeEventListener('touchstart', this.handleStart)
506
+ el.removeEventListener('touchend', this.handleEnd)
507
+ })
508
+
509
+ // Initial setup
510
+ this.updateTrackFill()
511
+ this.mounted = true
512
+ }
513
+ },
514
+ type: 'range',
515
+ min: String(min),
516
+ max: String(max),
517
+ step: String(step),
518
+ value: String(currentValue),
519
+ disabled: isDisabled,
520
+ orient: vertical ? 'vertical' : undefined,
521
+ style: sliderStyles,
522
+ 'aria-label': accessibilityLabel,
523
+ 'aria-valuemin': min,
524
+ 'aria-valuemax': max,
525
+ 'aria-valuenow': currentValue,
526
+ 'aria-valuetext': accessibilityValueDescription
527
+ ? accessibilityValueDescription(currentValue)
528
+ : this.formatValue(currentValue),
529
+ }),
530
+
531
+ // Slider marks
532
+ ...this.renderMarks()
533
+ ),
534
+
535
+ // Labels
536
+ ...this.renderLabels(),
537
+
538
+ // Accessibility hint
539
+ ...(accessibilityHint
540
+ ? [
541
+ h(
542
+ 'div',
543
+ {
544
+ id: `${this.id}-hint`,
545
+ style: {
546
+ fontSize: '12px',
547
+ color: '#666',
548
+ marginTop: '4px',
549
+ },
550
+ },
551
+ text(accessibilityHint)
552
+ ),
553
+ ]
554
+ : [])
555
+ )
556
+ }
557
+ }
558
+
559
+ /**
560
+ * Slider component function
561
+ */
562
+ export function Slider(
563
+ value: number | Signal<number>,
564
+ props: Omit<SliderProps, 'value'> = {}
565
+ ): ModifiableComponent<SliderProps> & {
566
+ modifier: ModifierBuilder<ModifiableComponent<SliderProps>>
567
+ } {
568
+ const sliderProps: SliderProps = { ...props, value }
569
+ const component = new EnhancedSlider(sliderProps)
570
+ return withModifiers(component)
571
+ }
572
+
573
+ /**
574
+ * Slider style variants
575
+ */
576
+ export const SliderStyles = {
577
+ /**
578
+ * Default slider (default)
579
+ */
580
+ Default(
581
+ value: number | Signal<number>,
582
+ props: Omit<SliderProps, 'value' | 'variant'> = {}
583
+ ) {
584
+ return Slider(value, { ...props, variant: 'default' })
585
+ },
586
+
587
+ /**
588
+ * Filled slider with background
589
+ */
590
+ Filled(
591
+ value: number | Signal<number>,
592
+ props: Omit<SliderProps, 'value' | 'variant'> = {}
593
+ ) {
594
+ return Slider(value, { ...props, variant: 'filled' })
595
+ },
596
+
597
+ /**
598
+ * Minimal slider with minimal styling
599
+ */
600
+ Minimal(
601
+ value: number | Signal<number>,
602
+ props: Omit<SliderProps, 'value' | 'variant'> = {}
603
+ ) {
604
+ return Slider(value, { ...props, variant: 'minimal' })
605
+ },
606
+
607
+ /**
608
+ * Vertical slider
609
+ */
610
+ Vertical(
611
+ value: number | Signal<number>,
612
+ props: Omit<SliderProps, 'value' | 'vertical'> = {}
613
+ ) {
614
+ return Slider(value, { ...props, vertical: true })
615
+ },
616
+
617
+ /**
618
+ * Range slider with marks
619
+ */
620
+ WithMarks(
621
+ value: number | Signal<number>,
622
+ marks: SliderMark[],
623
+ props: Omit<SliderProps, 'value' | 'marks'> = {}
624
+ ) {
625
+ return Slider(value, { ...props, marks })
626
+ },
627
+
628
+ /**
629
+ * Slider with value display
630
+ */
631
+ WithValue(
632
+ value: number | Signal<number>,
633
+ props: Omit<SliderProps, 'value' | 'showValue'> = {}
634
+ ) {
635
+ return Slider(value, { ...props, showValue: true })
636
+ },
637
+
638
+ /**
639
+ * Slider with labels
640
+ */
641
+ WithLabels(
642
+ value: number | Signal<number>,
643
+ minimumLabel: string,
644
+ maximumLabel: string,
645
+ props: Omit<
646
+ SliderProps,
647
+ 'value' | 'showLabels' | 'minimumValueLabel' | 'maximumValueLabel'
648
+ > = {}
649
+ ) {
650
+ return Slider(value, {
651
+ ...props,
652
+ showLabels: true,
653
+ minimumValueLabel: minimumLabel,
654
+ maximumValueLabel: maximumLabel,
655
+ })
656
+ },
657
+ }
658
+
659
+ /**
660
+ * Slider utilities
661
+ */
662
+ export const SliderUtils = {
663
+ /**
664
+ * Create marks for common ranges
665
+ */
666
+ createMarks(min: number, max: number, step: number): SliderMark[] {
667
+ const marks: SliderMark[] = []
668
+
669
+ for (let value = min; value <= max; value += step) {
670
+ marks.push({
671
+ value,
672
+ label: String(value),
673
+ })
674
+ }
675
+
676
+ return marks
677
+ },
678
+
679
+ /**
680
+ * Create percentage marks
681
+ */
682
+ createPercentageMarks(): SliderMark[] {
683
+ return [
684
+ { value: 0, label: '0%' },
685
+ { value: 25, label: '25%' },
686
+ { value: 50, label: '50%' },
687
+ { value: 75, label: '75%' },
688
+ { value: 100, label: '100%' },
689
+ ]
690
+ },
691
+
692
+ /**
693
+ * Create custom formatter
694
+ */
695
+ createFormatter(
696
+ unit: string,
697
+ decimals: number = 0
698
+ ): (value: number) => string {
699
+ return (value: number) => `${value.toFixed(decimals)}${unit}`
700
+ },
701
+
702
+ /**
703
+ * Snap value to step
704
+ */
705
+ snapToStep(value: number, min: number, step: number): number {
706
+ return Math.round((value - min) / step) * step + min
707
+ },
708
+
709
+ /**
710
+ * Convert slider value to different scale
711
+ */
712
+ mapToScale(
713
+ value: number,
714
+ fromMin: number,
715
+ fromMax: number,
716
+ toMin: number,
717
+ toMax: number
718
+ ): number {
719
+ const percentage = (value - fromMin) / (fromMax - fromMin)
720
+ return toMin + percentage * (toMax - toMin)
721
+ },
722
+ }