@tamagui/slider 2.7.7 → 3.0.0-beta.1093.1

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 (115) hide show
  1. package/dist/cjs/Slider.cjs +442 -511
  2. package/dist/cjs/Slider.native.cjs +462 -0
  3. package/dist/cjs/Slider.native.js +416 -497
  4. package/dist/cjs/Slider.native.js.map +1 -1
  5. package/dist/cjs/SliderImpl.cjs +101 -129
  6. package/dist/cjs/SliderImpl.native.cjs +128 -0
  7. package/dist/cjs/SliderImpl.native.js +104 -146
  8. package/dist/cjs/SliderImpl.native.js.map +1 -1
  9. package/dist/cjs/SliderResponder.cjs +51 -0
  10. package/dist/cjs/SliderResponder.native.cjs +52 -0
  11. package/dist/cjs/SliderResponder.native.js +54 -0
  12. package/dist/cjs/SliderResponder.native.js.map +1 -0
  13. package/dist/cjs/constants.cjs +58 -45
  14. package/dist/cjs/constants.native.cjs +78 -0
  15. package/dist/cjs/constants.native.js +58 -44
  16. package/dist/cjs/constants.native.js.map +1 -1
  17. package/dist/cjs/helpers.cjs +58 -60
  18. package/dist/cjs/helpers.native.cjs +98 -0
  19. package/dist/cjs/helpers.native.js +65 -68
  20. package/dist/cjs/helpers.native.js.map +1 -1
  21. package/dist/cjs/index.cjs +15 -17
  22. package/dist/cjs/index.native.cjs +32 -0
  23. package/dist/cjs/index.native.js +15 -16
  24. package/dist/cjs/index.native.js.map +1 -1
  25. package/dist/cjs/types.cjs +9 -11
  26. package/dist/cjs/types.native.cjs +18 -0
  27. package/dist/cjs/types.native.js +9 -10
  28. package/dist/cjs/types.native.js.map +1 -1
  29. package/dist/esm/Slider.mjs +415 -474
  30. package/dist/esm/Slider.mjs.map +1 -1
  31. package/dist/esm/Slider.native.js +389 -463
  32. package/dist/esm/Slider.native.js.map +1 -1
  33. package/dist/esm/SliderImpl.mjs +84 -104
  34. package/dist/esm/SliderImpl.mjs.map +1 -1
  35. package/dist/esm/SliderImpl.native.js +88 -123
  36. package/dist/esm/SliderImpl.native.js.map +1 -1
  37. package/dist/esm/SliderResponder.mjs +26 -0
  38. package/dist/esm/SliderResponder.mjs.map +1 -0
  39. package/dist/esm/SliderResponder.native.js +31 -0
  40. package/dist/esm/SliderResponder.native.js.map +1 -0
  41. package/dist/esm/constants.mjs +38 -21
  42. package/dist/esm/constants.mjs.map +1 -1
  43. package/dist/esm/constants.native.js +38 -21
  44. package/dist/esm/constants.native.js.map +1 -1
  45. package/dist/esm/helpers.mjs +37 -36
  46. package/dist/esm/helpers.mjs.map +1 -1
  47. package/dist/esm/helpers.native.js +44 -45
  48. package/dist/esm/helpers.native.js.map +1 -1
  49. package/dist/esm/index.js +4 -3
  50. package/dist/esm/index.mjs +4 -3
  51. package/dist/esm/index.native.js +4 -3
  52. package/dist/esm/types.mjs +0 -2
  53. package/dist/esm/types.native.js +0 -2
  54. package/dist/jsx/Slider.mjs +415 -474
  55. package/dist/jsx/Slider.mjs.map +1 -1
  56. package/dist/jsx/Slider.native.cjs +462 -0
  57. package/dist/jsx/Slider.native.js +416 -497
  58. package/dist/jsx/Slider.native.js.map +1 -1
  59. package/dist/jsx/SliderImpl.mjs +84 -104
  60. package/dist/jsx/SliderImpl.mjs.map +1 -1
  61. package/dist/jsx/SliderImpl.native.cjs +128 -0
  62. package/dist/jsx/SliderImpl.native.js +104 -146
  63. package/dist/jsx/SliderImpl.native.js.map +1 -1
  64. package/dist/jsx/SliderResponder.mjs +26 -0
  65. package/dist/jsx/SliderResponder.mjs.map +1 -0
  66. package/dist/jsx/SliderResponder.native.cjs +52 -0
  67. package/dist/jsx/SliderResponder.native.js +54 -0
  68. package/dist/jsx/SliderResponder.native.js.map +1 -0
  69. package/dist/jsx/constants.mjs +38 -21
  70. package/dist/jsx/constants.mjs.map +1 -1
  71. package/dist/jsx/constants.native.cjs +78 -0
  72. package/dist/jsx/constants.native.js +58 -44
  73. package/dist/jsx/constants.native.js.map +1 -1
  74. package/dist/jsx/helpers.mjs +37 -36
  75. package/dist/jsx/helpers.mjs.map +1 -1
  76. package/dist/jsx/helpers.native.cjs +98 -0
  77. package/dist/jsx/helpers.native.js +65 -68
  78. package/dist/jsx/helpers.native.js.map +1 -1
  79. package/dist/jsx/index.js +4 -3
  80. package/dist/jsx/index.mjs +4 -3
  81. package/dist/jsx/index.native.cjs +32 -0
  82. package/dist/jsx/index.native.js +15 -16
  83. package/dist/jsx/index.native.js.map +1 -1
  84. package/dist/jsx/types.mjs +0 -2
  85. package/dist/jsx/types.native.cjs +18 -0
  86. package/dist/jsx/types.native.js +9 -10
  87. package/dist/jsx/types.native.js.map +1 -1
  88. package/package.json +19 -15
  89. package/src/Slider.tsx +168 -189
  90. package/src/SliderImpl.tsx +25 -32
  91. package/src/SliderResponder.native.tsx +25 -0
  92. package/src/SliderResponder.tsx +37 -0
  93. package/src/constants.tsx +16 -6
  94. package/src/types.ts +13 -4
  95. package/types/Slider.d.ts +229 -212
  96. package/types/Slider.d.ts.map +1 -1
  97. package/types/SliderImpl.d.ts +3 -6
  98. package/types/SliderImpl.d.ts.map +1 -1
  99. package/types/SliderResponder.d.ts +10 -0
  100. package/types/SliderResponder.d.ts.map +1 -0
  101. package/types/SliderResponder.native.d.ts +3 -0
  102. package/types/SliderResponder.native.d.ts.map +1 -0
  103. package/types/constants.d.ts +10 -10
  104. package/types/constants.d.ts.map +1 -1
  105. package/types/helpers.d.ts.map +1 -1
  106. package/types/types.d.ts +12 -4
  107. package/types/types.d.ts.map +1 -1
  108. package/dist/esm/index.js.map +0 -1
  109. package/dist/esm/index.mjs.map +0 -1
  110. package/dist/esm/index.native.js.map +0 -1
  111. package/dist/esm/types.mjs.map +0 -1
  112. package/dist/esm/types.native.js.map +0 -1
  113. package/dist/jsx/index.js.map +0 -1
  114. package/dist/jsx/index.mjs.map +0 -1
  115. package/dist/jsx/types.mjs.map +0 -1
package/src/Slider.tsx CHANGED
@@ -5,24 +5,25 @@ import { isIos, isWeb } from '@tamagui/constants'
5
5
  import type {
6
6
  GestureReponderEvent,
7
7
  GetProps,
8
+ SizeResolverEnv,
8
9
  SizeTokens,
9
10
  TamaguiElement,
10
11
  } from '@tamagui/core'
11
12
  import {
12
- getTokens,
13
+ createStyledHOC,
14
+ createRefComponent,
13
15
  getVariableValue,
16
+ resolveSize,
14
17
  styled,
15
18
  useConfiguration,
16
19
  useCreateShallowSetState,
17
20
  } from '@tamagui/core'
18
- import { getSize } from '@tamagui/get-token'
19
21
  import { clamp, composeEventHandlers, withStaticProperties } from '@tamagui/helpers'
20
- import type { SizableStackProps } from '@tamagui/stacks'
21
- import { ThemeableStack } from '@tamagui/stacks'
22
+ import { YStack } from '@tamagui/stacks'
22
23
  import { useControllableState } from '@tamagui/use-controllable-state'
23
24
  import { useDirection } from '@tamagui/use-direction'
24
25
  import * as React from 'react'
25
- import type { View } from 'react-native'
26
+ import type { View } from '@tamagui/react-native-types'
26
27
 
27
28
  import {
28
29
  ARROW_KEYS,
@@ -56,27 +57,63 @@ import type {
56
57
  } from './types'
57
58
 
58
59
  const activeSliderMeasureListeners = new Set<Function>()
60
+ let activeSliderMeasureInterval: ReturnType<typeof setInterval> | undefined
59
61
 
60
62
  // run an interval on web as using translate can move things at any moment
61
63
  // without triggering layout or intersection observers
64
+ function startSliderMeasureInterval() {
65
+ if (
66
+ process.env.TAMAGUI_TARGET !== 'web' ||
67
+ activeSliderMeasureInterval !== undefined ||
68
+ process.env.TAMAGUI_DISABLE_SLIDER_INTERVAL
69
+ ) {
70
+ return
71
+ }
62
72
 
63
- if (process.env.TAMAGUI_TARGET === 'web') {
64
- if (!process.env.TAMAGUI_DISABLE_SLIDER_INTERVAL) {
65
- setInterval?.(
66
- () => {
67
- activeSliderMeasureListeners.forEach((cb) => cb())
68
- },
69
- // really doesn't need to be super often
70
- 1000
71
- )
73
+ activeSliderMeasureInterval = setInterval(() => {
74
+ activeSliderMeasureListeners.forEach((cb) => cb())
75
+ }, 1000)
76
+ }
77
+
78
+ function addActiveSliderMeasureListener(listener: Function) {
79
+ activeSliderMeasureListeners.add(listener)
80
+ startSliderMeasureInterval()
81
+ }
82
+
83
+ function removeActiveSliderMeasureListener(listener: Function) {
84
+ activeSliderMeasureListeners.delete(listener)
85
+ if (!activeSliderMeasureListeners.size && activeSliderMeasureInterval !== undefined) {
86
+ clearInterval(activeSliderMeasureInterval)
87
+ activeSliderMeasureInterval = undefined
72
88
  }
73
89
  }
74
90
 
91
+ // on web a responder event's pageX/pageY come straight off the DOM event and are
92
+ // document-relative, while `measure` reports the track through getBoundingClientRect
93
+ // and is viewport-relative. subtracting the cached offset therefore mixes origins and
94
+ // the thumb lags the cursor by exactly the page scroll (#4146). locationX/locationY
95
+ // are `client - track.getBoundingClientRect()`, read fresh on every event, so they
96
+ // need no offset at all - which is why onSlideStart was already correct. that also
97
+ // closes the window where the cached offset is stale: nothing re-measures on scroll,
98
+ // only on resize, an IntersectionObserver threshold crossing, and a 1s interval.
99
+ // native measures the track and the touch against the screen, so it keeps the offset.
100
+ const getTrackPosition = (
101
+ event: GestureReponderEvent,
102
+ offset: number,
103
+ orientation: 'horizontal' | 'vertical'
104
+ ) => {
105
+ const { locationX, locationY, pageX, pageY } = event.nativeEvent
106
+ if (isWeb) {
107
+ return orientation === 'horizontal' ? locationX : locationY
108
+ }
109
+ return (orientation === 'horizontal' ? pageX : pageY) - offset
110
+ }
111
+
75
112
  /* -------------------------------------------------------------------------------------------------
76
113
  * SliderHorizontal
77
114
  * -----------------------------------------------------------------------------------------------*/
78
115
 
79
- const SliderHorizontal = React.forwardRef<View, SliderHorizontalProps>(
116
+ const SliderHorizontal = createRefComponent<View, SliderHorizontalProps>(
80
117
  (props: ScopedProps<SliderHorizontalProps>, forwardedRef) => {
81
118
  const {
82
119
  min,
@@ -136,13 +173,17 @@ const SliderHorizontal = React.forwardRef<View, SliderHorizontalProps>(
136
173
  }
137
174
  }}
138
175
  onSlideMove={(event) => {
139
- const value = getValueFromPointer(event.nativeEvent.pageX - state.offset)
176
+ const value = getValueFromPointer(
177
+ getTrackPosition(event, state.offset, 'horizontal')
178
+ )
140
179
  if (value) {
141
180
  onSlideMove?.(value, event)
142
181
  }
143
182
  }}
144
183
  onSlideEnd={(event) => {
145
- const value = getValueFromPointer(event.nativeEvent.pageX - state.offset)
184
+ const value = getValueFromPointer(
185
+ getTrackPosition(event, state.offset, 'horizontal')
186
+ )
146
187
  if (value) {
147
188
  onSlideEnd?.(event, value)
148
189
  }
@@ -191,9 +232,9 @@ function useSliderMeasure(sliderRef: React.RefObject<View | null>, measure: () =
191
232
  (entries) => {
192
233
  debouncedMeasure()
193
234
  if (entries?.[0].isIntersecting) {
194
- activeSliderMeasureListeners.add(debouncedMeasure)
235
+ addActiveSliderMeasureListener(debouncedMeasure)
195
236
  } else {
196
- activeSliderMeasureListeners.delete(debouncedMeasure)
237
+ removeActiveSliderMeasureListener(debouncedMeasure)
197
238
  }
198
239
  },
199
240
  {
@@ -206,7 +247,7 @@ function useSliderMeasure(sliderRef: React.RefObject<View | null>, measure: () =
206
247
  io.observe(node)
207
248
 
208
249
  return () => {
209
- activeSliderMeasureListeners.delete(debouncedMeasure)
250
+ removeActiveSliderMeasureListener(debouncedMeasure)
210
251
  io.disconnect()
211
252
  }
212
253
  }, [])
@@ -216,7 +257,7 @@ function useSliderMeasure(sliderRef: React.RefObject<View | null>, measure: () =
216
257
  * SliderVertical
217
258
  * -----------------------------------------------------------------------------------------------*/
218
259
 
219
- const SliderVertical = React.forwardRef<View, SliderVerticalProps>(
260
+ const SliderVertical = createRefComponent<View, SliderVerticalProps>(
220
261
  (props: ScopedProps<SliderVerticalProps>, forwardedRef) => {
221
262
  const {
222
263
  min,
@@ -276,13 +317,17 @@ const SliderVertical = React.forwardRef<View, SliderVerticalProps>(
276
317
  }
277
318
  }}
278
319
  onSlideMove={(event) => {
279
- const value = getValueFromPointer(event.nativeEvent.pageY - state.offset)
320
+ const value = getValueFromPointer(
321
+ getTrackPosition(event, state.offset, 'vertical')
322
+ )
280
323
  if (value) {
281
324
  onSlideMove?.(value, event)
282
325
  }
283
326
  }}
284
327
  onSlideEnd={(event) => {
285
- const value = getValueFromPointer(event.nativeEvent.pageY - state.offset)
328
+ const value = getValueFromPointer(
329
+ getTrackPosition(event, state.offset, 'vertical')
330
+ )
286
331
  onSlideEnd?.(event, value)
287
332
  }}
288
333
  onStepKeyDown={(event) => {
@@ -301,28 +346,20 @@ const SliderVertical = React.forwardRef<View, SliderVerticalProps>(
301
346
 
302
347
  type SliderTrackElement = TamaguiElement
303
348
 
349
+ // Unstyled track frame: fill + clip only. Track color and radius live in the
350
+ // tamagui skin (code/ui/tamagui/src/components/Slider.tsx).
304
351
  export const SliderTrackFrame = styled(SliderFrame, {
305
- name: 'Slider',
306
-
307
- variants: {
308
- unstyled: {
309
- false: {
310
- height: '100%',
311
- width: '100%',
312
- backgroundColor: '$background',
313
- position: 'relative',
314
- borderRadius: 100_000,
315
- overflow: 'hidden',
316
- },
317
- },
318
- } as const,
319
-
320
- defaultVariants: {
321
- unstyled: process.env.TAMAGUI_HEADLESS === '1',
322
- },
352
+ displayName: 'Slider',
353
+ height: '100%',
354
+ width: '100%',
355
+ position: 'relative',
356
+ overflow: 'hidden',
323
357
  })
324
358
 
325
- const SliderTrack = React.forwardRef<SliderTrackElement, SliderTrackProps>(
359
+ // createStyledHOC (not createRefComponent) so the tamagui skin can layer track
360
+ // color/radius via styled(Slider.Track, { ... }); the render body is unchanged.
361
+ const SliderTrack = createStyledHOC(
362
+ SliderTrackFrame,
326
363
  function SliderTrack(props: ScopedProps<SliderTrackProps>, forwardedRef) {
327
364
  const { __scopeSlider, ...trackProps } = props
328
365
  const context = useSliderContext(__scopeSlider)
@@ -331,7 +368,7 @@ const SliderTrack = React.forwardRef<SliderTrackElement, SliderTrackProps>(
331
368
  data-disabled={context.disabled ? '' : undefined}
332
369
  data-orientation={context.orientation}
333
370
  orientation={context.orientation}
334
- size={context.size}
371
+ size={context.size ?? undefined}
335
372
  {...trackProps}
336
373
  ref={forwardedRef}
337
374
  />
@@ -343,134 +380,118 @@ const SliderTrack = React.forwardRef<SliderTrackElement, SliderTrackProps>(
343
380
  * SliderActive
344
381
  * -----------------------------------------------------------------------------------------------*/
345
382
 
383
+ // Unstyled active-range frame: positioning only. Fill color and radius live in
384
+ // the tamagui skin.
346
385
  export const SliderActiveFrame = styled(SliderFrame, {
347
- name: 'SliderActive',
386
+ displayName: 'SliderActive',
348
387
  position: 'absolute',
349
388
  pointerEvents: 'box-none',
350
-
351
- variants: {
352
- unstyled: {
353
- false: {
354
- backgroundColor: '$background',
355
- borderRadius: 100_000,
356
- },
357
- },
358
- } as const,
359
-
360
- defaultVariants: {
361
- unstyled: process.env.TAMAGUI_HEADLESS === '1',
362
- },
363
389
  })
364
390
 
365
391
  type SliderActiveProps = GetProps<typeof SliderActiveFrame>
366
392
 
367
- const SliderActive = React.forwardRef<View, SliderActiveProps>(function SliderActive(
368
- props: ScopedProps<SliderActiveProps>,
369
- forwardedRef
370
- ) {
371
- const { __scopeSlider, ...rangeProps } = props
372
- const context = useSliderContext(__scopeSlider)
373
- const orientation = useSliderOrientationContext(__scopeSlider)
374
- const ref = React.useRef<View>(null)
375
- const composedRefs = useComposedRefs(forwardedRef, ref)
376
- const valuesCount = context.values.length
377
- const percentages = context.values.map((value) =>
378
- convertValueToPercentage(value, context.min, context.max)
379
- )
380
- const offsetStart = valuesCount > 1 ? Math.min(...percentages) : 0
381
- const offsetEnd = 100 - Math.max(...percentages)
382
-
383
- return (
384
- <SliderActiveFrame
385
- orientation={context.orientation}
386
- data-orientation={context.orientation}
387
- data-disabled={context.disabled ? '' : undefined}
388
- size={context.size}
389
- animateOnly={['left', 'top', 'right', 'bottom']}
390
- {...rangeProps}
391
- ref={composedRefs}
392
- {...{
393
- [orientation.startEdge]: `${offsetStart}%`,
394
- [orientation.endEdge]: `${offsetEnd}%`,
395
- }}
396
- {...(orientation.sizeProp === 'width'
397
- ? {
398
- height: '100%',
399
- }
400
- : {
401
- left: 0,
402
- right: 0,
403
- })}
404
- />
405
- )
406
- })
393
+ // createStyledHOC so the tamagui skin can layer active-range color/radius via
394
+ // styled(Slider.TrackActive, { ... }); the render body is unchanged.
395
+ const SliderActive = createStyledHOC(
396
+ SliderActiveFrame,
397
+ function SliderActive(props: ScopedProps<SliderActiveProps>, forwardedRef) {
398
+ const { __scopeSlider, ...rangeProps } = props
399
+ const context = useSliderContext(__scopeSlider)
400
+ const orientation = useSliderOrientationContext(__scopeSlider)
401
+ const ref = React.useRef<View>(null)
402
+ const composedRefs = useComposedRefs(forwardedRef, ref)
403
+ const valuesCount = context.values.length
404
+ const percentages = context.values.map((value) =>
405
+ convertValueToPercentage(value, context.min, context.max)
406
+ )
407
+ const offsetStart = valuesCount > 1 ? Math.min(...percentages) : 0
408
+ const offsetEnd = 100 - Math.max(...percentages)
409
+
410
+ return (
411
+ <SliderActiveFrame
412
+ orientation={context.orientation}
413
+ data-orientation={context.orientation}
414
+ data-disabled={context.disabled ? '' : undefined}
415
+ size={context.size ?? undefined}
416
+ {...rangeProps}
417
+ ref={composedRefs}
418
+ {...{
419
+ [orientation.startEdge]: `${offsetStart}%`,
420
+ [orientation.endEdge]: `${offsetEnd}%`,
421
+ }}
422
+ {...(orientation.sizeProp === 'width'
423
+ ? {
424
+ height: '100%',
425
+ }
426
+ : {
427
+ left: 0,
428
+ right: 0,
429
+ })}
430
+ />
431
+ )
432
+ }
433
+ )
407
434
 
408
435
  /* -------------------------------------------------------------------------------------------------
409
436
  * SliderThumb
410
437
  * -----------------------------------------------------------------------------------------------*/
411
438
 
412
- // TODO make this customizable through tamagui
413
- // so we can accurately use it for estimatedSize below
414
- const getThumbSize = (val?: SizeTokens | number) => {
415
- const tokens = getTokens()
416
- const size =
417
- typeof val === 'number'
418
- ? val
419
- : getSize(tokens.size[val as any] as any, {
420
- shift: -1,
421
- })
439
+ // the thumb is a line-height tall circle: 20px at md. without a styled env
440
+ // (the SSR estimate below) it measures against the config's default font
441
+ const thumbSize = (val: SizeTokens | number | true, env?: SizeResolverEnv) => {
442
+ if (typeof val === 'number') return val
443
+ const { text } = resolveSize(val, env)
444
+ return getVariableValue(text.lineHeight ?? text.fontSize) as number
445
+ }
446
+
447
+ const getThumbSize = styled.dynamic<SizeTokens | number | true>((val, env) => {
448
+ const size = thumbSize(val, env)
422
449
  return {
423
450
  width: size,
424
451
  height: size,
425
452
  minWidth: size,
426
453
  minHeight: size,
427
454
  }
428
- }
455
+ })
429
456
 
430
- export const SliderThumbFrame = styled(ThemeableStack, {
431
- name: 'SliderThumb',
457
+ // Unstyled thumb frame: positioning + the size mechanism (hit-target
458
+ // dimensions) + the circular shape modifier (opt-in, off by default). Border,
459
+ // background, and hover/press/focus color styling live in the tamagui skin.
460
+ export const SliderThumbFrame = styled(YStack, {
461
+ displayName: 'SliderThumb',
462
+ position: 'absolute',
432
463
 
433
464
  variants: {
434
- size: {
435
- '...size': getThumbSize,
436
- ':number': getThumbSize,
465
+ size: getThumbSize,
466
+
467
+ circular: {
468
+ true: {
469
+ borderRadius: 100_000,
470
+ padding: 0,
471
+ },
437
472
  },
438
473
 
439
- unstyled: {
440
- false: {
441
- position: 'absolute',
442
- borderWidth: 2,
443
- borderColor: '$borderColor',
444
- backgroundColor: '$background',
445
- pressStyle: {
446
- backgroundColor: '$backgroundPress',
447
- borderColor: '$borderColorPress',
448
- },
449
- hoverStyle: {
450
- backgroundColor: '$backgroundHover',
451
- borderColor: '$borderColorHover',
452
- },
453
- focusVisibleStyle: {
454
- outlineStyle: 'solid',
455
- outlineWidth: 2,
456
- outlineColor: '$outlineColor',
457
- },
474
+ // opt-in v2-compat elevate (formerly from ThemeableStack), off by default.
475
+ // On the frame (the animated node), not a skin wrapper — a variant block on a
476
+ // wrapper breaks the native animation driver's interpolation.
477
+ elevate: {
478
+ true: {
479
+ shadowColor: 'shadow-color',
480
+ shadowRadius: 4,
481
+ shadowOffset: { width: 0, height: 2 },
458
482
  },
459
483
  },
460
484
  } as const,
461
-
462
- defaultVariants: {
463
- unstyled: process.env.TAMAGUI_HEADLESS === '1',
464
- },
465
485
  })
466
486
 
467
487
  export interface SliderThumbExtraProps {
468
488
  index?: number
469
489
  }
470
490
 
471
- export interface SliderThumbProps extends SizableStackProps, SliderThumbExtraProps {}
491
+ export type SliderThumbProps = GetProps<typeof SliderThumbFrame> & SliderThumbExtraProps
472
492
 
473
- const SliderThumb = SliderThumbFrame.styleable<SliderThumbExtraProps>(
493
+ const SliderThumb = createStyledHOC(
494
+ SliderThumbFrame,
474
495
  function SliderThumb(props: ScopedProps<SliderThumbProps>, forwardedRef) {
475
496
  const { __scopeSlider, index = 0, circular, size: sizeProp, ...thumbProps } = props
476
497
  const context = useSliderContext(__scopeSlider)
@@ -483,12 +504,9 @@ const SliderThumb = SliderThumbFrame.styleable<SliderThumbExtraProps>(
483
504
  const percent =
484
505
  value === undefined ? 0 : convertValueToPercentage(value, context.min, context.max)
485
506
  const label = getLabel(index, context.values.length)
486
- const sizeIn = sizeProp ?? context.size ?? '$true'
487
- const [size, setSize] = React.useState(() => {
488
- // for SSR
489
- const estimatedSize = getVariableValue(getThumbSize(sizeIn).width) as number
490
- return estimatedSize
491
- })
507
+ const sizeIn = (sizeProp ?? context.size ?? true) as SizeTokens | number | true
508
+ // for SSR
509
+ const [size, setSize] = React.useState(() => thumbSize(sizeIn))
492
510
 
493
511
  const thumbInBoundsOffset = size
494
512
  ? getThumbInBoundsOffset(size, percent, orientation.direction)
@@ -536,7 +554,6 @@ const SliderThumb = SliderThumbFrame.styleable<SliderThumbExtraProps>(
536
554
  data-orientation={context.orientation}
537
555
  data-disabled={context.disabled ? '' : undefined}
538
556
  tabIndex={context.disabled ? undefined : 0}
539
- animateOnly={['transform', 'left', 'top', 'right', 'bottom']}
540
557
  {...positionalStyles}
541
558
  {...{
542
559
  [orientation.startEdge]: `${percent}%`,
@@ -571,7 +588,7 @@ const SliderThumb = SliderThumbFrame.styleable<SliderThumbExtraProps>(
571
588
  * Slider
572
589
  * -----------------------------------------------------------------------------------------------*/
573
590
 
574
- const SliderComponent = React.forwardRef(
591
+ const SliderComponent = createRefComponent(
575
592
  (props: ScopedProps<SliderProps>, forwardedRef) => {
576
593
  const {
577
594
  name,
@@ -709,14 +726,11 @@ const SliderComponent = React.forwardRef(
709
726
  }
710
727
  }}
711
728
  />
712
- {/* {isFormControl &&
713
- values.map((value, index) => (
714
- <BubbleInput
715
- key={index}
716
- name={name ? name + (values.length > 1 ? '[]' : '') : undefined}
717
- value={value}
718
- />
719
- ))} */}
729
+ {isWeb &&
730
+ name &&
731
+ values.map((value, index) => (
732
+ <input key={index} type="hidden" name={name} value={value} />
733
+ ))}
720
734
  </SliderProvider>
721
735
  )
722
736
  }
@@ -730,41 +744,6 @@ const Slider = withStaticProperties(SliderComponent, {
730
744
 
731
745
  Slider.displayName = SLIDER_NAME
732
746
 
733
- /* -----------------------------------------------------------------------------------------------*/
734
-
735
- // // TODO
736
- // const BubbleInput = (props: any) => {
737
- // const { value, ...inputProps } = props
738
- // const ref = React.useRef<HTMLInputElement>(null)
739
- // const prevValue = usePrevious(value)
740
-
741
- // // Bubble value change to parents (e.g form change event)
742
- // React.useEffect(() => {
743
- // const input = ref.current!
744
- // const inputProto = window.HTMLInputElement.prototype
745
- // const descriptor = Object.getOwnPropertyDescriptor(inputProto, 'value') as PropertyDescriptor
746
- // const setValue = descriptor.set
747
- // if (prevValue !== value && setValue) {
748
- // const event = new Event('input', { bubbles: true })
749
- // setValue.call(input, value)
750
- // input.dispatchEvent(event)
751
- // }
752
- // }, [prevValue, value])
753
-
754
- // /**
755
- // * We purposefully do not use `type="hidden"` here otherwise forms that
756
- // * wrap it will not be able to access its value via the FormData API.
757
- // *
758
- // * We purposefully do not add the `value` attribute here to allow the value
759
- // * to be set programatically and bubble to any parent form `onChange` event.
760
- // * Adding the `value` will cause React to consider the programatic
761
- // * dispatch a duplicate and it will get swallowed.
762
- // */
763
- // return <input style={{ display: 'none' }} {...inputProps} ref={ref} defaultValue={value} />
764
- // }
765
-
766
- /* -----------------------------------------------------------------------------------------------*/
767
-
768
747
  const Track = SliderTrack
769
748
  const Range = SliderActive
770
749
  const Thumb = SliderThumb
@@ -1,3 +1,4 @@
1
+ import { createRefComponent } from '@tamagui/core'
1
2
  /* -------------------------------------------------------------------------------------------------
2
3
  * SliderImpl
3
4
  * -----------------------------------------------------------------------------------------------*/
@@ -8,12 +9,12 @@ import { getVariableValue, styled } from '@tamagui/core'
8
9
  import { getSize } from '@tamagui/get-token'
9
10
  import { YStack } from '@tamagui/stacks'
10
11
  import * as React from 'react'
11
- import { View } from 'react-native'
12
12
 
13
13
  import { ARROW_KEYS, PAGE_KEYS, SLIDER_NAME, useSliderContext } from './constants'
14
+ import { SliderResponder } from './SliderResponder'
14
15
  import type { ScopedProps, SliderImplProps } from './types'
15
16
 
16
- export const SliderFrame = styled(YStack, {
17
+ const SliderFrameBase = styled(YStack, {
17
18
  position: 'relative',
18
19
 
19
20
  variants: {
@@ -22,29 +23,28 @@ export const SliderFrame = styled(YStack, {
22
23
  vertical: {},
23
24
  },
24
25
 
25
- size: (val, extras) => {
26
- if (!val) {
27
- return
28
- }
29
- const orientation = extras.props['orientation']
30
- const size = Math.round(getVariableValue(getSize(val)) / 6)
31
- if (orientation === 'horizontal') {
32
- return {
33
- height: size,
34
- borderRadius: size,
35
- justifyContent: 'center',
36
- }
37
- }
38
- return {
39
- width: size,
40
- borderRadius: size,
41
- alignItems: 'center',
42
- }
43
- },
26
+ size: styled.dynamic<SliderImplProps['size']>(),
44
27
  } as const,
45
28
  })
46
29
 
47
- export const SliderImpl = React.forwardRef<View, SliderImplProps>(
30
+ export const SliderFrame = SliderFrameBase.resolve((props) => {
31
+ if (!props.size) return
32
+ const size = Math.round(getVariableValue(getSize(props.size as any)) / 6)
33
+ if (props.orientation === 'horizontal') {
34
+ return {
35
+ height: size,
36
+ borderRadius: size,
37
+ justifyContent: 'center',
38
+ }
39
+ }
40
+ return {
41
+ width: size,
42
+ borderRadius: size,
43
+ alignItems: 'center',
44
+ }
45
+ })
46
+
47
+ export const SliderImpl = createRefComponent<TamaguiElement, SliderImplProps>(
48
48
  (props: ScopedProps<SliderImplProps>, forwardedRef) => {
49
49
  const {
50
50
  __scopeSlider,
@@ -104,10 +104,8 @@ export const SliderImpl = React.forwardRef<View, SliderImplProps>(
104
104
  )
105
105
 
106
106
  return (
107
- // wrap with plain RN View for responder events - tamagui views no longer handle responder events on web
108
-
109
107
  <SliderFrame
110
- size="$4"
108
+ size="4"
111
109
  ref={forwardedRef as any}
112
110
  {...sliderProps}
113
111
  data-orientation={sliderProps.orientation}
@@ -129,18 +127,13 @@ export const SliderImpl = React.forwardRef<View, SliderImplProps>(
129
127
  },
130
128
  })}
131
129
  >
132
- <View
133
- onMoveShouldSetResponderCapture={() => true}
134
- onMoveShouldSetResponder={() => true}
135
- onStartShouldSetResponder={() => true}
136
- onResponderTerminationRequest={() => false}
130
+ <SliderResponder
137
131
  onResponderGrant={handleResponderGrant}
138
132
  onResponderMove={handleResponderMove}
139
133
  onResponderRelease={handleResponderRelease}
140
- style={{ inset: 0, position: 'absolute' }}
141
134
  >
142
135
  {children}
143
- </View>
136
+ </SliderResponder>
144
137
  </SliderFrame>
145
138
  )
146
139
  }
@@ -0,0 +1,25 @@
1
+ import { View } from 'react-native'
2
+
3
+ import type { SliderResponderProps } from './types'
4
+
5
+ export const SliderResponder = ({
6
+ onResponderGrant,
7
+ onResponderMove,
8
+ onResponderRelease,
9
+ children,
10
+ }: SliderResponderProps) => {
11
+ return (
12
+ <View
13
+ onMoveShouldSetResponderCapture={() => true}
14
+ onMoveShouldSetResponder={() => true}
15
+ onStartShouldSetResponder={() => true}
16
+ onResponderTerminationRequest={() => false}
17
+ onResponderGrant={onResponderGrant}
18
+ onResponderMove={onResponderMove}
19
+ onResponderRelease={onResponderRelease}
20
+ style={{ inset: 0, position: 'absolute' }}
21
+ >
22
+ {children}
23
+ </View>
24
+ )
25
+ }