@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.
- package/dist/cjs/Slider.cjs +442 -511
- package/dist/cjs/Slider.native.cjs +462 -0
- package/dist/cjs/Slider.native.js +416 -497
- package/dist/cjs/Slider.native.js.map +1 -1
- package/dist/cjs/SliderImpl.cjs +101 -129
- package/dist/cjs/SliderImpl.native.cjs +128 -0
- package/dist/cjs/SliderImpl.native.js +104 -146
- package/dist/cjs/SliderImpl.native.js.map +1 -1
- package/dist/cjs/SliderResponder.cjs +51 -0
- package/dist/cjs/SliderResponder.native.cjs +52 -0
- package/dist/cjs/SliderResponder.native.js +54 -0
- package/dist/cjs/SliderResponder.native.js.map +1 -0
- package/dist/cjs/constants.cjs +58 -45
- package/dist/cjs/constants.native.cjs +78 -0
- package/dist/cjs/constants.native.js +58 -44
- package/dist/cjs/constants.native.js.map +1 -1
- package/dist/cjs/helpers.cjs +58 -60
- package/dist/cjs/helpers.native.cjs +98 -0
- package/dist/cjs/helpers.native.js +65 -68
- package/dist/cjs/helpers.native.js.map +1 -1
- package/dist/cjs/index.cjs +15 -17
- package/dist/cjs/index.native.cjs +32 -0
- package/dist/cjs/index.native.js +15 -16
- package/dist/cjs/index.native.js.map +1 -1
- package/dist/cjs/types.cjs +9 -11
- package/dist/cjs/types.native.cjs +18 -0
- package/dist/cjs/types.native.js +9 -10
- package/dist/cjs/types.native.js.map +1 -1
- package/dist/esm/Slider.mjs +415 -474
- package/dist/esm/Slider.mjs.map +1 -1
- package/dist/esm/Slider.native.js +389 -463
- package/dist/esm/Slider.native.js.map +1 -1
- package/dist/esm/SliderImpl.mjs +84 -104
- package/dist/esm/SliderImpl.mjs.map +1 -1
- package/dist/esm/SliderImpl.native.js +88 -123
- package/dist/esm/SliderImpl.native.js.map +1 -1
- package/dist/esm/SliderResponder.mjs +26 -0
- package/dist/esm/SliderResponder.mjs.map +1 -0
- package/dist/esm/SliderResponder.native.js +31 -0
- package/dist/esm/SliderResponder.native.js.map +1 -0
- package/dist/esm/constants.mjs +38 -21
- package/dist/esm/constants.mjs.map +1 -1
- package/dist/esm/constants.native.js +38 -21
- package/dist/esm/constants.native.js.map +1 -1
- package/dist/esm/helpers.mjs +37 -36
- package/dist/esm/helpers.mjs.map +1 -1
- package/dist/esm/helpers.native.js +44 -45
- package/dist/esm/helpers.native.js.map +1 -1
- package/dist/esm/index.js +4 -3
- package/dist/esm/index.mjs +4 -3
- package/dist/esm/index.native.js +4 -3
- package/dist/esm/types.mjs +0 -2
- package/dist/esm/types.native.js +0 -2
- package/dist/jsx/Slider.mjs +415 -474
- package/dist/jsx/Slider.mjs.map +1 -1
- package/dist/jsx/Slider.native.cjs +462 -0
- package/dist/jsx/Slider.native.js +416 -497
- package/dist/jsx/Slider.native.js.map +1 -1
- package/dist/jsx/SliderImpl.mjs +84 -104
- package/dist/jsx/SliderImpl.mjs.map +1 -1
- package/dist/jsx/SliderImpl.native.cjs +128 -0
- package/dist/jsx/SliderImpl.native.js +104 -146
- package/dist/jsx/SliderImpl.native.js.map +1 -1
- package/dist/jsx/SliderResponder.mjs +26 -0
- package/dist/jsx/SliderResponder.mjs.map +1 -0
- package/dist/jsx/SliderResponder.native.cjs +52 -0
- package/dist/jsx/SliderResponder.native.js +54 -0
- package/dist/jsx/SliderResponder.native.js.map +1 -0
- package/dist/jsx/constants.mjs +38 -21
- package/dist/jsx/constants.mjs.map +1 -1
- package/dist/jsx/constants.native.cjs +78 -0
- package/dist/jsx/constants.native.js +58 -44
- package/dist/jsx/constants.native.js.map +1 -1
- package/dist/jsx/helpers.mjs +37 -36
- package/dist/jsx/helpers.mjs.map +1 -1
- package/dist/jsx/helpers.native.cjs +98 -0
- package/dist/jsx/helpers.native.js +65 -68
- package/dist/jsx/helpers.native.js.map +1 -1
- package/dist/jsx/index.js +4 -3
- package/dist/jsx/index.mjs +4 -3
- package/dist/jsx/index.native.cjs +32 -0
- package/dist/jsx/index.native.js +15 -16
- package/dist/jsx/index.native.js.map +1 -1
- package/dist/jsx/types.mjs +0 -2
- package/dist/jsx/types.native.cjs +18 -0
- package/dist/jsx/types.native.js +9 -10
- package/dist/jsx/types.native.js.map +1 -1
- package/package.json +19 -15
- package/src/Slider.tsx +168 -189
- package/src/SliderImpl.tsx +25 -32
- package/src/SliderResponder.native.tsx +25 -0
- package/src/SliderResponder.tsx +37 -0
- package/src/constants.tsx +16 -6
- package/src/types.ts +13 -4
- package/types/Slider.d.ts +229 -212
- package/types/Slider.d.ts.map +1 -1
- package/types/SliderImpl.d.ts +3 -6
- package/types/SliderImpl.d.ts.map +1 -1
- package/types/SliderResponder.d.ts +10 -0
- package/types/SliderResponder.d.ts.map +1 -0
- package/types/SliderResponder.native.d.ts +3 -0
- package/types/SliderResponder.native.d.ts.map +1 -0
- package/types/constants.d.ts +10 -10
- package/types/constants.d.ts.map +1 -1
- package/types/helpers.d.ts.map +1 -1
- package/types/types.d.ts +12 -4
- package/types/types.d.ts.map +1 -1
- package/dist/esm/index.js.map +0 -1
- package/dist/esm/index.mjs.map +0 -1
- package/dist/esm/index.native.js.map +0 -1
- package/dist/esm/types.mjs.map +0 -1
- package/dist/esm/types.native.js.map +0 -1
- package/dist/jsx/index.js.map +0 -1
- package/dist/jsx/index.mjs.map +0 -1
- 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
|
-
|
|
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
|
|
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
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
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 =
|
|
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(
|
|
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(
|
|
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
|
-
|
|
235
|
+
addActiveSliderMeasureListener(debouncedMeasure)
|
|
195
236
|
} else {
|
|
196
|
-
|
|
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
|
-
|
|
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 =
|
|
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(
|
|
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(
|
|
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
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
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
|
-
//
|
|
413
|
-
//
|
|
414
|
-
const
|
|
415
|
-
|
|
416
|
-
const
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
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
|
-
|
|
431
|
-
|
|
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
|
-
|
|
436
|
-
|
|
465
|
+
size: getThumbSize,
|
|
466
|
+
|
|
467
|
+
circular: {
|
|
468
|
+
true: {
|
|
469
|
+
borderRadius: 100_000,
|
|
470
|
+
padding: 0,
|
|
471
|
+
},
|
|
437
472
|
},
|
|
438
473
|
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
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
|
|
491
|
+
export type SliderThumbProps = GetProps<typeof SliderThumbFrame> & SliderThumbExtraProps
|
|
472
492
|
|
|
473
|
-
const SliderThumb =
|
|
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 ??
|
|
487
|
-
|
|
488
|
-
|
|
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 =
|
|
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
|
-
{
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
key={index}
|
|
716
|
-
|
|
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
|
package/src/SliderImpl.tsx
CHANGED
|
@@ -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
|
-
|
|
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: (
|
|
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
|
|
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="
|
|
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
|
-
<
|
|
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
|
-
</
|
|
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
|
+
}
|