@onekeyfe/react-native-segment-slider 6.7.0-alpha.396 → 6.7.0-alpha.397

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -2,6 +2,8 @@
2
2
 
3
3
  SegmentSlider view for React Native
4
4
 
5
+ See [the behavioral contract](docs/SPEC.md) for platform behavior and acceptance.
6
+
5
7
  ## Installation
6
8
 
7
9
  ```sh
@@ -13,18 +13,20 @@ import com.facebook.react.uimanager.ThemedReactContext
13
13
  /**
14
14
  * Native segmented slider. Draws the track, fill bar, segment marks, thumb and
15
15
  * value bubble in [View.onDraw] and handles the drag in [View.onTouchEvent], so
16
- * a drag never crosses the JS bridge per frame. Mirrors the web variant
17
- * (`composite/SegmentSlider/index.tsx`) pixel-for-pixel.
16
+ * a drag never crosses the JS bridge per frame. Follows the web variant's
17
+ * semantics, with extra endpoint space for the enlarged drag thumb.
18
18
  */
19
19
  @DoNotStrip
20
20
  class HybridSegmentSlider(val context: ThemedReactContext) : HybridSegmentSliderSpec() {
21
21
 
22
22
  private val density = context.resources.displayMetrics.density
23
23
 
24
- // Geometry (dp), identical to the web constants.
24
+ // Geometry (dp).
25
25
  private val thumbSizePx = 16f * density
26
+ private val activeThumbScale = 1.15f
26
27
  private val markSizePx = 10f * density
27
- private val insetPx = thumbSizePx / 2f
28
+ // Keep the enlarged thumb and node halo inside the horizontal view bounds.
29
+ private val insetPx = 12f * density
28
30
  private val haloRingPx = 4f * density // translucent halo width beyond each mark
29
31
 
30
32
  // Paints.
@@ -95,7 +97,7 @@ class HybridSegmentSlider(val context: ThemedReactContext) : HybridSegmentSlider
95
97
  touchStartX = event.x
96
98
  lastTouchX = event.x
97
99
  onSlideStart?.invoke()
98
- animateThumbScale(1.15f)
100
+ animateThumbScale(activeThumbScale)
99
101
  // Tap-snap mode: defer the value commit until we know this is a tap or
100
102
  // a drag, so a tap never jumps to the raw touch point first.
101
103
  if (!snapTapsActive) commitDragValue(valueFromX(event.x))
@@ -255,7 +257,7 @@ class HybridSegmentSlider(val context: ThemedReactContext) : HybridSegmentSlider
255
257
  }
256
258
 
257
259
  // MARK: - Geometry helpers
258
- private val trackWidth: Float get() = (view.width - thumbSizePx).coerceAtLeast(0f)
260
+ private val trackWidth: Float get() = (view.width - insetPx * 2f).coerceAtLeast(0f)
259
261
  private val rangeSafe: Double get() = (max - min).let { if (it == 0.0) 1.0 else it }
260
262
  private val markCount: Int get() = if (segments >= 1) segments.toInt() + 1 else 0
261
263
 
@@ -418,12 +420,12 @@ class HybridSegmentSlider(val context: ThemedReactContext) : HybridSegmentSlider
418
420
  val padH = 8f * density
419
421
  val bw = textW + padH * 2
420
422
  val bh = 20f * density
421
- val left = thumbX - bw / 2f
423
+ val left = (thumbX - bw / 2f).coerceIn(0f, (w - bw).coerceAtLeast(0f))
422
424
  val top = trackTop - 8f * density - bh
423
425
  rect.set(left, top, left + bw, top + bh)
424
426
  canvas.drawRoundRect(rect, 4f * density, 4f * density, bubblePaint)
425
427
  val baseline = top + bh / 2f - (bubbleTextPaint.descent() + bubbleTextPaint.ascent()) / 2f
426
- canvas.drawText(text, thumbX, baseline, bubbleTextPaint)
428
+ canvas.drawText(text, left + bw / 2f, baseline, bubbleTextPaint)
427
429
  }
428
430
  }
429
431
 
@@ -3,15 +3,17 @@ import UIKit
3
3
 
4
4
  /// Native segmented slider. Renders the track, fill bar, segment marks, thumb
5
5
  /// and value bubble as CALayers and handles the drag gesture itself, so a drag
6
- /// never crosses the JS bridge per frame. Mirrors the web variant
7
- /// (`composite/SegmentSlider/index.tsx`) pixel-for-pixel.
6
+ /// never crosses the JS bridge per frame. Follows the web variant's semantics,
7
+ /// with extra endpoint space for the enlarged iOS drag thumb.
8
8
  final class HybridSegmentSlider: HybridSegmentSliderSpec {
9
9
 
10
- // MARK: - Geometry (points), identical to the web constants
10
+ // MARK: - Geometry (points)
11
11
  private let thumbSize: CGFloat = 16
12
+ private let activeThumbScale: CGFloat = 1.15
12
13
  private let markSize: CGFloat = 10
13
14
  private let haloRing: CGFloat = 4 // translucent halo width beyond each mark
14
- private var inset: CGFloat { thumbSize / 2 }
15
+ // Include the enlarged thumb radius and room for its shadow at either end.
16
+ private var inset: CGFloat { ceil(thumbSize * activeThumbScale / 2) + 2 }
15
17
 
16
18
  // MARK: - HybridView
17
19
  var view: UIView = SegmentSliderUIView()
@@ -173,7 +175,7 @@ final class HybridSegmentSlider: HybridSegmentSliderSpec {
173
175
  }
174
176
 
175
177
  // MARK: - Geometry helpers
176
- private var trackWidth: CGFloat { Swift.max(view.bounds.width - thumbSize, 0) }
178
+ private var trackWidth: CGFloat { Swift.max(view.bounds.width - inset * 2, 0) }
177
179
  private var trackLeft: CGFloat { inset }
178
180
  private var range: Double { let r = max - min; return r == 0 ? 1 : r }
179
181
  private var markCount: Int { segments >= 1 ? Int(segments) + 1 : 0 }
@@ -317,7 +319,9 @@ final class HybridSegmentSlider: HybridSegmentSliderSpec {
317
319
  let bw = ceil(textW) + padH * 2
318
320
  let bh: CGFloat = 20
319
321
  let by = trackTop - 8 - bh
320
- bubbleLayer.frame = CGRect(x: thumbX - bw / 2, y: by, width: bw, height: bh)
322
+ // Keep endpoint bubbles inside horizontally clipping ancestors.
323
+ let bx = Swift.max(bounds.minX, Swift.min(thumbX - bw / 2, bounds.maxX - bw))
324
+ bubbleLayer.frame = CGRect(x: bx, y: by, width: bw, height: bh)
321
325
  bubbleTextLayer.frame = CGRect(x: 0, y: 2, width: bw, height: 16)
322
326
  }
323
327
 
@@ -385,7 +389,7 @@ final class HybridSegmentSlider: HybridSegmentSliderSpec {
385
389
  touchStartTime = CACurrentMediaTime()
386
390
  haptic.prepare()
387
391
  onSlideStart?()
388
- animateThumbScale(1.15)
392
+ animateThumbScale(activeThumbScale)
389
393
  setBubbleVisible(true)
390
394
  // Tap-snap mode: defer any value change until we know this is a tap or a
391
395
  // drag, so a tap never jumps to the raw touch point first.
@@ -8,12 +8,13 @@ import type { HybridView, HybridViewMethods, HybridViewProps } from 'react-nativ
8
8
  * (`packages/components/src/composite/SegmentSlider/index.tsx`) which avoids
9
9
  * React state per frame by mutating the DOM imperatively.
10
10
  *
11
- * Geometry contract (points), matching the web layout 1:1:
11
+ * Geometry contract (points):
12
12
  * THUMB_SIZE = 16, MARK_SIZE = 10, hit/container height = 24
13
- * track is inset by THUMB_SIZE/2 on each end → usable width W' = W - 16
13
+ * END_INSET = 12 on iOS and Android (room for the enlarged thumb and halo)
14
+ * usable track width W' = max(W - 2 * END_INSET, 0)
14
15
  * pct = (clamp(value,min,max) - min) / (max - min)
15
- * thumb x = 8 + pct * W'
16
- * mark i x = 8 + (i / segments) * W' (i in 0..segments)
16
+ * thumb x = END_INSET + pct * W'
17
+ * mark i x = END_INSET + (i / segments) * W' (i in 0..segments)
17
18
  *
18
19
  * Colors are resolved from the Tamagui theme in the JS wrapper and passed in as
19
20
  * hex / rgba() strings so the native side never depends on the theme system.
@@ -1 +1 @@
1
- {"version":3,"file":"SegmentSlider.nitro.d.ts","sourceRoot":"","sources":["../../../src/SegmentSlider.nitro.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,UAAU,EACV,iBAAiB,EACjB,eAAe,EAChB,MAAM,4BAA4B,CAAC;AAEpC;;;;;;;;;;;;;;;;;;GAkBG;AACH,MAAM,WAAW,kBAAmB,SAAQ,eAAe;IACzD;;;;;;;;OAQG;IACH,YAAY,EAAE,MAAM,CAAC;IACrB,4CAA4C;IAC5C,GAAG,EAAE,MAAM,CAAC;IACZ,qBAAqB;IACrB,GAAG,EAAE,MAAM,CAAC;IACZ,4EAA4E;IAC5E,QAAQ,EAAE,MAAM,CAAC;IACjB,8CAA8C;IAC9C,YAAY,EAAE,MAAM,CAAC;IACrB,0DAA0D;IAC1D,QAAQ,EAAE,OAAO,CAAC;IAClB,6EAA6E;IAC7E,UAAU,EAAE,OAAO,CAAC;IACpB,4EAA4E;IAC5E,YAAY,EAAE,OAAO,CAAC;IACtB;;;;;OAKG;IACH,gBAAgB,EAAE,OAAO,CAAC;IAG1B,mDAAmD;IACnD,SAAS,EAAE,MAAM,CAAC;IAClB,iDAAiD;IACjD,UAAU,EAAE,MAAM,CAAC;IACnB,uCAAuC;IACvC,UAAU,EAAE,MAAM,CAAC;IACnB,mDAAmD;IACnD,gBAAgB,EAAE,MAAM,CAAC;IACzB,6DAA6D;IAC7D,eAAe,EAAE,MAAM,CAAC;IACxB,+CAA+C;IAC/C,iBAAiB,EAAE,MAAM,CAAC;IAC1B,uDAAuD;IACvD,eAAe,EAAE,MAAM,CAAC;IACxB,qDAAqD;IACrD,WAAW,EAAE,MAAM,CAAC;IACpB,wCAAwC;IACxC,eAAe,EAAE,MAAM,CAAC;IAExB,oEAAoE;IACpE,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACnC,yCAAyC;IACzC,YAAY,CAAC,EAAE,MAAM,IAAI,CAAC;IAC1B,uCAAuC;IACvC,eAAe,CAAC,EAAE,MAAM,IAAI,CAAC;CAC9B;AAED,MAAM,WAAW,oBAAqB,SAAQ,iBAAiB;IAC7D;;;;;;OAMG;IACH,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;CAC/B;AAED,MAAM,MAAM,aAAa,GAAG,UAAU,CAAC,kBAAkB,EAAE,oBAAoB,CAAC,CAAC"}
1
+ {"version":3,"file":"SegmentSlider.nitro.d.ts","sourceRoot":"","sources":["../../../src/SegmentSlider.nitro.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,UAAU,EACV,iBAAiB,EACjB,eAAe,EAChB,MAAM,4BAA4B,CAAC;AAEpC;;;;;;;;;;;;;;;;;;;GAmBG;AACH,MAAM,WAAW,kBAAmB,SAAQ,eAAe;IACzD;;;;;;;;OAQG;IACH,YAAY,EAAE,MAAM,CAAC;IACrB,4CAA4C;IAC5C,GAAG,EAAE,MAAM,CAAC;IACZ,qBAAqB;IACrB,GAAG,EAAE,MAAM,CAAC;IACZ,4EAA4E;IAC5E,QAAQ,EAAE,MAAM,CAAC;IACjB,8CAA8C;IAC9C,YAAY,EAAE,MAAM,CAAC;IACrB,0DAA0D;IAC1D,QAAQ,EAAE,OAAO,CAAC;IAClB,6EAA6E;IAC7E,UAAU,EAAE,OAAO,CAAC;IACpB,4EAA4E;IAC5E,YAAY,EAAE,OAAO,CAAC;IACtB;;;;;OAKG;IACH,gBAAgB,EAAE,OAAO,CAAC;IAG1B,mDAAmD;IACnD,SAAS,EAAE,MAAM,CAAC;IAClB,iDAAiD;IACjD,UAAU,EAAE,MAAM,CAAC;IACnB,uCAAuC;IACvC,UAAU,EAAE,MAAM,CAAC;IACnB,mDAAmD;IACnD,gBAAgB,EAAE,MAAM,CAAC;IACzB,6DAA6D;IAC7D,eAAe,EAAE,MAAM,CAAC;IACxB,+CAA+C;IAC/C,iBAAiB,EAAE,MAAM,CAAC;IAC1B,uDAAuD;IACvD,eAAe,EAAE,MAAM,CAAC;IACxB,qDAAqD;IACrD,WAAW,EAAE,MAAM,CAAC;IACpB,wCAAwC;IACxC,eAAe,EAAE,MAAM,CAAC;IAExB,oEAAoE;IACpE,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACnC,yCAAyC;IACzC,YAAY,CAAC,EAAE,MAAM,IAAI,CAAC;IAC1B,uCAAuC;IACvC,eAAe,CAAC,EAAE,MAAM,IAAI,CAAC;CAC9B;AAED,MAAM,WAAW,oBAAqB,SAAQ,iBAAiB;IAC7D;;;;;;OAMG;IACH,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;CAC/B;AAED,MAAM,MAAM,aAAa,GAAG,UAAU,CACpC,kBAAkB,EAClB,oBAAoB,CACrB,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@onekeyfe/react-native-segment-slider",
3
- "version": "6.7.0-alpha.396",
3
+ "version": "6.7.0-alpha.397",
4
4
  "description": "react-native-segment-slider",
5
5
  "main": "./lib/module/index.js",
6
6
  "types": "./lib/typescript/src/index.d.ts",
@@ -13,12 +13,13 @@ import type {
13
13
  * (`packages/components/src/composite/SegmentSlider/index.tsx`) which avoids
14
14
  * React state per frame by mutating the DOM imperatively.
15
15
  *
16
- * Geometry contract (points), matching the web layout 1:1:
16
+ * Geometry contract (points):
17
17
  * THUMB_SIZE = 16, MARK_SIZE = 10, hit/container height = 24
18
- * track is inset by THUMB_SIZE/2 on each end → usable width W' = W - 16
18
+ * END_INSET = 12 on iOS and Android (room for the enlarged thumb and halo)
19
+ * usable track width W' = max(W - 2 * END_INSET, 0)
19
20
  * pct = (clamp(value,min,max) - min) / (max - min)
20
- * thumb x = 8 + pct * W'
21
- * mark i x = 8 + (i / segments) * W' (i in 0..segments)
21
+ * thumb x = END_INSET + pct * W'
22
+ * mark i x = END_INSET + (i / segments) * W' (i in 0..segments)
22
23
  *
23
24
  * Colors are resolved from the Tamagui theme in the JS wrapper and passed in as
24
25
  * hex / rgba() strings so the native side never depends on the theme system.
@@ -95,4 +96,7 @@ export interface SegmentSliderMethods extends HybridViewMethods {
95
96
  setValue(value: number): void;
96
97
  }
97
98
 
98
- export type SegmentSlider = HybridView<SegmentSliderProps, SegmentSliderMethods>;
99
+ export type SegmentSlider = HybridView<
100
+ SegmentSliderProps,
101
+ SegmentSliderMethods
102
+ >;