@metamask/design-system-react-native 0.37.0 → 0.38.0

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 (124) hide show
  1. package/CHANGELOG.md +24 -1
  2. package/dist/components/BannerBase/BannerBase.cjs +4 -3
  3. package/dist/components/BannerBase/BannerBase.cjs.map +1 -1
  4. package/dist/components/BannerBase/BannerBase.d.cts.map +1 -1
  5. package/dist/components/BannerBase/BannerBase.d.mts.map +1 -1
  6. package/dist/components/BannerBase/BannerBase.mjs +4 -3
  7. package/dist/components/BannerBase/BannerBase.mjs.map +1 -1
  8. package/dist/components/BoxRow/BoxRow.types.cjs.map +1 -1
  9. package/dist/components/BoxRow/BoxRow.types.d.cts +4 -3
  10. package/dist/components/BoxRow/BoxRow.types.d.cts.map +1 -1
  11. package/dist/components/BoxRow/BoxRow.types.d.mts +4 -3
  12. package/dist/components/BoxRow/BoxRow.types.d.mts.map +1 -1
  13. package/dist/components/BoxRow/BoxRow.types.mjs.map +1 -1
  14. package/dist/components/Content/Content.cjs +1 -1
  15. package/dist/components/Content/Content.cjs.map +1 -1
  16. package/dist/components/Content/Content.mjs +1 -1
  17. package/dist/components/Content/Content.mjs.map +1 -1
  18. package/dist/components/Content/Content.types.cjs.map +1 -1
  19. package/dist/components/Content/Content.types.d.cts +10 -6
  20. package/dist/components/Content/Content.types.d.cts.map +1 -1
  21. package/dist/components/Content/Content.types.d.mts +10 -6
  22. package/dist/components/Content/Content.types.d.mts.map +1 -1
  23. package/dist/components/Content/Content.types.mjs.map +1 -1
  24. package/dist/components/HelpText/HelpText.cjs +28 -0
  25. package/dist/components/HelpText/HelpText.cjs.map +1 -0
  26. package/dist/components/HelpText/HelpText.constants.cjs +11 -0
  27. package/dist/components/HelpText/HelpText.constants.cjs.map +1 -0
  28. package/dist/components/HelpText/HelpText.constants.d.cts +3 -0
  29. package/dist/components/HelpText/HelpText.constants.d.cts.map +1 -0
  30. package/dist/components/HelpText/HelpText.constants.d.mts +3 -0
  31. package/dist/components/HelpText/HelpText.constants.d.mts.map +1 -0
  32. package/dist/components/HelpText/HelpText.constants.mjs +8 -0
  33. package/dist/components/HelpText/HelpText.constants.mjs.map +1 -0
  34. package/dist/components/HelpText/HelpText.d.cts +4 -0
  35. package/dist/components/HelpText/HelpText.d.cts.map +1 -0
  36. package/dist/components/HelpText/HelpText.d.mts +4 -0
  37. package/dist/components/HelpText/HelpText.d.mts.map +1 -0
  38. package/dist/components/HelpText/HelpText.mjs +28 -0
  39. package/dist/components/HelpText/HelpText.mjs.map +1 -0
  40. package/dist/components/HelpText/HelpText.types.cjs +3 -0
  41. package/dist/components/HelpText/HelpText.types.cjs.map +1 -0
  42. package/dist/components/HelpText/HelpText.types.d.cts +4 -0
  43. package/dist/components/HelpText/HelpText.types.d.cts.map +1 -0
  44. package/dist/components/HelpText/HelpText.types.d.mts +4 -0
  45. package/dist/components/HelpText/HelpText.types.d.mts.map +1 -0
  46. package/dist/components/HelpText/HelpText.types.mjs +2 -0
  47. package/dist/components/HelpText/HelpText.types.mjs.map +1 -0
  48. package/dist/components/HelpText/index.cjs +8 -0
  49. package/dist/components/HelpText/index.cjs.map +1 -0
  50. package/dist/components/HelpText/index.d.cts +4 -0
  51. package/dist/components/HelpText/index.d.cts.map +1 -0
  52. package/dist/components/HelpText/index.d.mts +4 -0
  53. package/dist/components/HelpText/index.d.mts.map +1 -0
  54. package/dist/components/HelpText/index.mjs +3 -0
  55. package/dist/components/HelpText/index.mjs.map +1 -0
  56. package/dist/components/KeyValueRow/KeyValueRow.cjs +1 -1
  57. package/dist/components/KeyValueRow/KeyValueRow.cjs.map +1 -1
  58. package/dist/components/KeyValueRow/KeyValueRow.d.cts.map +1 -1
  59. package/dist/components/KeyValueRow/KeyValueRow.d.mts.map +1 -1
  60. package/dist/components/KeyValueRow/KeyValueRow.mjs +1 -1
  61. package/dist/components/KeyValueRow/KeyValueRow.mjs.map +1 -1
  62. package/dist/components/KeyValueRow/KeyValueRow.types.cjs.map +1 -1
  63. package/dist/components/KeyValueRow/KeyValueRow.types.d.cts +6 -2
  64. package/dist/components/KeyValueRow/KeyValueRow.types.d.cts.map +1 -1
  65. package/dist/components/KeyValueRow/KeyValueRow.types.d.mts +6 -2
  66. package/dist/components/KeyValueRow/KeyValueRow.types.d.mts.map +1 -1
  67. package/dist/components/KeyValueRow/KeyValueRow.types.mjs.map +1 -1
  68. package/dist/components/KeyValueSelect/KeyValueSelect.cjs +55 -0
  69. package/dist/components/KeyValueSelect/KeyValueSelect.cjs.map +1 -0
  70. package/dist/components/KeyValueSelect/KeyValueSelect.d.cts +11 -0
  71. package/dist/components/KeyValueSelect/KeyValueSelect.d.cts.map +1 -0
  72. package/dist/components/KeyValueSelect/KeyValueSelect.d.mts +11 -0
  73. package/dist/components/KeyValueSelect/KeyValueSelect.d.mts.map +1 -0
  74. package/dist/components/KeyValueSelect/KeyValueSelect.mjs +36 -0
  75. package/dist/components/KeyValueSelect/KeyValueSelect.mjs.map +1 -0
  76. package/dist/components/KeyValueSelect/KeyValueSelect.types.cjs +3 -0
  77. package/dist/components/KeyValueSelect/KeyValueSelect.types.cjs.map +1 -0
  78. package/dist/components/KeyValueSelect/KeyValueSelect.types.d.cts +47 -0
  79. package/dist/components/KeyValueSelect/KeyValueSelect.types.d.cts.map +1 -0
  80. package/dist/components/KeyValueSelect/KeyValueSelect.types.d.mts +47 -0
  81. package/dist/components/KeyValueSelect/KeyValueSelect.types.d.mts.map +1 -0
  82. package/dist/components/KeyValueSelect/KeyValueSelect.types.mjs +2 -0
  83. package/dist/components/KeyValueSelect/KeyValueSelect.types.mjs.map +1 -0
  84. package/dist/components/KeyValueSelect/index.cjs +8 -0
  85. package/dist/components/KeyValueSelect/index.cjs.map +1 -0
  86. package/dist/components/KeyValueSelect/index.d.cts +4 -0
  87. package/dist/components/KeyValueSelect/index.d.cts.map +1 -0
  88. package/dist/components/KeyValueSelect/index.d.mts +4 -0
  89. package/dist/components/KeyValueSelect/index.d.mts.map +1 -0
  90. package/dist/components/KeyValueSelect/index.mjs +3 -0
  91. package/dist/components/KeyValueSelect/index.mjs.map +1 -0
  92. package/dist/components/Slider/useSliderGesture.cjs +126 -10
  93. package/dist/components/Slider/useSliderGesture.cjs.map +1 -1
  94. package/dist/components/Slider/useSliderGesture.d.cts +11 -1
  95. package/dist/components/Slider/useSliderGesture.d.cts.map +1 -1
  96. package/dist/components/Slider/useSliderGesture.d.mts +11 -1
  97. package/dist/components/Slider/useSliderGesture.d.mts.map +1 -1
  98. package/dist/components/Slider/useSliderGesture.mjs +126 -10
  99. package/dist/components/Slider/useSliderGesture.mjs.map +1 -1
  100. package/dist/components/TitleHub/TitleHub.types.cjs.map +1 -1
  101. package/dist/components/TitleHub/TitleHub.types.d.cts +5 -2
  102. package/dist/components/TitleHub/TitleHub.types.d.cts.map +1 -1
  103. package/dist/components/TitleHub/TitleHub.types.d.mts +5 -2
  104. package/dist/components/TitleHub/TitleHub.types.d.mts.map +1 -1
  105. package/dist/components/TitleHub/TitleHub.types.mjs.map +1 -1
  106. package/dist/components/index.cjs +8 -2
  107. package/dist/components/index.cjs.map +1 -1
  108. package/dist/components/index.d.cts +4 -0
  109. package/dist/components/index.d.cts.map +1 -1
  110. package/dist/components/index.d.mts +4 -0
  111. package/dist/components/index.d.mts.map +1 -1
  112. package/dist/components/index.mjs +2 -0
  113. package/dist/components/index.mjs.map +1 -1
  114. package/dist/components/temp-components/TextOrChildren/TextOrChildren.cjs +2 -2
  115. package/dist/components/temp-components/TextOrChildren/TextOrChildren.cjs.map +1 -1
  116. package/dist/components/temp-components/TextOrChildren/TextOrChildren.mjs +2 -2
  117. package/dist/components/temp-components/TextOrChildren/TextOrChildren.mjs.map +1 -1
  118. package/dist/components/temp-components/TextOrChildren/TextOrChildren.types.cjs.map +1 -1
  119. package/dist/components/temp-components/TextOrChildren/TextOrChildren.types.d.cts +4 -3
  120. package/dist/components/temp-components/TextOrChildren/TextOrChildren.types.d.cts.map +1 -1
  121. package/dist/components/temp-components/TextOrChildren/TextOrChildren.types.d.mts +4 -3
  122. package/dist/components/temp-components/TextOrChildren/TextOrChildren.types.d.mts.map +1 -1
  123. package/dist/components/temp-components/TextOrChildren/TextOrChildren.types.mjs.map +1 -1
  124. package/package.json +3 -3
@@ -0,0 +1 @@
1
+ {"version":3,"file":"KeyValueSelect.types.mjs","sourceRoot":"","sources":["../../../src/components/KeyValueSelect/KeyValueSelect.types.ts"],"names":[],"mappings":"","sourcesContent":["import type {\n KeyValueSelectPropsShared,\n KeyValueSelectSelectButtonPropsShared,\n} from '@metamask/design-system-shared';\nimport type { PressableProps } from 'react-native';\n\nimport type { ButtonIconProps } from '../ButtonIcon/ButtonIcon.types';\nimport type { IconProps } from '../Icon/Icon.types';\nimport type { KeyValueRowProps } from '../KeyValueRow/KeyValueRow.types';\nimport type { TextProps } from '../Text/Text.types';\n\n/**\n * SelectButton-only props for KeyValueSelect (RN).\n * Size is always Md and variant is always Secondary.\n */\nexport type KeyValueSelectSelectButtonProps =\n KeyValueSelectSelectButtonPropsShared & {\n /**\n * Optional props passed to the SelectButton trailing arrow Icon when a trailing\n * arrow is shown (excluding `name`, which is derived from the resolved direction).\n */\n endArrowDirectionIconProps?: Partial<Omit<IconProps, 'name'>>;\n };\n\n/**\n * KeyValueRow shell / ViewProps escape hatch.\n * Owned KeyValueSelect content props are omitted so the public API stays the source of truth.\n */\nexport type KeyValueSelectKeyValueRowProps = Omit<\n KeyValueRowProps,\n | 'keyLabel'\n | 'keyStartAccessory'\n | 'keyEndAccessory'\n | 'keyTextProps'\n | 'keyEndButtonIconProps'\n | 'value'\n | 'valueStartAccessory'\n | 'valueEndAccessory'\n | 'valueTextProps'\n | 'valueEndButtonIconProps'\n | 'variant'\n>;\n\n/**\n * KeyValueSelect component props.\n *\n * Pressable KeyValueRow with a SelectButton value. Value-side KeyValueRow props\n * map onto SelectButton; SelectButton-only options use `selectButtonProps`.\n * Use `keyValueRowProps` for inner KeyValueRow shell props (e.g. `testID`, `twClassName`).\n */\nexport type KeyValueSelectProps = Omit<\n KeyValueSelectPropsShared,\n 'selectButtonProps'\n> &\n Omit<PressableProps, 'children' | 'disabled'> & {\n /** Optional props for the key Text when key is a string. */\n keyTextProps?: Partial<Omit<TextProps, 'children'>>;\n /** When set, renders a ButtonIcon as the key endAccessory (takes precedence over keyEndAccessory). */\n keyEndButtonIconProps?: Partial<ButtonIconProps>;\n /** Optional props for the SelectButton label Text. Mapped from KeyValueRow `valueTextProps`. */\n valueTextProps?: Partial<Omit<TextProps, 'children'>>;\n /** SelectButton-only props (placeholder and caret controls). */\n selectButtonProps: KeyValueSelectSelectButtonProps;\n /**\n * Optional props forwarded to the inner KeyValueRow (e.g. `testID`, `twClassName`, `style`,\n * accessibility props). Owned content props are set by KeyValueSelect and are not available here.\n */\n keyValueRowProps?: KeyValueSelectKeyValueRowProps;\n /** Optional Tailwind class names applied to the outer Pressable. */\n twClassName?: string;\n };\n"]}
@@ -0,0 +1,8 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.KeyValueSelect = exports.KeyValueSelectVariant = void 0;
4
+ var design_system_shared_1 = require("@metamask/design-system-shared");
5
+ Object.defineProperty(exports, "KeyValueSelectVariant", { enumerable: true, get: function () { return design_system_shared_1.KeyValueSelectVariant; } });
6
+ var KeyValueSelect_1 = require("./KeyValueSelect.cjs");
7
+ Object.defineProperty(exports, "KeyValueSelect", { enumerable: true, get: function () { return KeyValueSelect_1.KeyValueSelect; } });
8
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.cjs","sourceRoot":"","sources":["../../../src/components/KeyValueSelect/index.ts"],"names":[],"mappings":";;;AAAA,uEAAuE;AAA9D,6HAAA,qBAAqB,OAAA;AAC9B,uDAAkD;AAAzC,gHAAA,cAAc,OAAA","sourcesContent":["export { KeyValueSelectVariant } from '@metamask/design-system-shared';\nexport { KeyValueSelect } from './KeyValueSelect';\nexport type {\n KeyValueSelectKeyValueRowProps,\n KeyValueSelectProps,\n KeyValueSelectSelectButtonProps,\n} from './KeyValueSelect.types';\n"]}
@@ -0,0 +1,4 @@
1
+ export { KeyValueSelectVariant } from "@metamask/design-system-shared";
2
+ export { KeyValueSelect } from "./KeyValueSelect.cjs";
3
+ export type { KeyValueSelectKeyValueRowProps, KeyValueSelectProps, KeyValueSelectSelectButtonProps, } from "./KeyValueSelect.types.cjs";
4
+ //# sourceMappingURL=index.d.cts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.cts","sourceRoot":"","sources":["../../../src/components/KeyValueSelect/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,qBAAqB,EAAE,uCAAuC;AACvE,OAAO,EAAE,cAAc,EAAE,6BAAyB;AAClD,YAAY,EACV,8BAA8B,EAC9B,mBAAmB,EACnB,+BAA+B,GAChC,mCAA+B"}
@@ -0,0 +1,4 @@
1
+ export { KeyValueSelectVariant } from "@metamask/design-system-shared";
2
+ export { KeyValueSelect } from "./KeyValueSelect.mjs";
3
+ export type { KeyValueSelectKeyValueRowProps, KeyValueSelectProps, KeyValueSelectSelectButtonProps, } from "./KeyValueSelect.types.mjs";
4
+ //# sourceMappingURL=index.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.mts","sourceRoot":"","sources":["../../../src/components/KeyValueSelect/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,qBAAqB,EAAE,uCAAuC;AACvE,OAAO,EAAE,cAAc,EAAE,6BAAyB;AAClD,YAAY,EACV,8BAA8B,EAC9B,mBAAmB,EACnB,+BAA+B,GAChC,mCAA+B"}
@@ -0,0 +1,3 @@
1
+ export { KeyValueSelectVariant } from "@metamask/design-system-shared";
2
+ export { KeyValueSelect } from "./KeyValueSelect.mjs";
3
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.mjs","sourceRoot":"","sources":["../../../src/components/KeyValueSelect/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,qBAAqB,EAAE,uCAAuC;AACvE,OAAO,EAAE,cAAc,EAAE,6BAAyB","sourcesContent":["export { KeyValueSelectVariant } from '@metamask/design-system-shared';\nexport { KeyValueSelect } from './KeyValueSelect';\nexport type {\n KeyValueSelectKeyValueRowProps,\n KeyValueSelectProps,\n KeyValueSelectSelectButtonProps,\n} from './KeyValueSelect.types';\n"]}
@@ -6,6 +6,13 @@ const react_native_gesture_handler_1 = require("react-native-gesture-handler");
6
6
  const react_native_reanimated_1 = require("react-native-reanimated");
7
7
  const Slider_constants_1 = require("./Slider.constants.cjs");
8
8
  const Slider_utilities_1 = require("./Slider.utilities.cjs");
9
+ /**
10
+ * Cap on remembered local updates used to spot late `value` echoes.
11
+ * Pan `onUpdate` records every intermediate position, so a fast drag can
12
+ * produce many entries; 256 is large enough for a long scrub without
13
+ * growing unbounded.
14
+ */
15
+ const RECENT_COMMIT_HISTORY_LIMIT = 256;
9
16
  /**
10
17
  * Owns Slider gesture handling, animated thumb/fill position, and range-label taps.
11
18
  *
@@ -13,7 +20,17 @@ const Slider_utilities_1 = require("./Slider.utilities.cjs");
13
20
  * touch X → clamp to track width → trackPercent → domain value → callbacks
14
21
  *
15
22
  * Flow when `value` prop changes (JS thread → UI thread via useAnimatedReaction):
16
- * domain value → trackPercent → translateX (skipped while dragging)
23
+ * domain value → trackPercent → translateX (skipped while dragging).
24
+ *
25
+ * Stale controlled self-echoes (gesture → onValueChange → parent state → value
26
+ * prop) are suppressed with a monotonically increasing commit generation — the
27
+ * same sequence principle as React Native TextInput's `mostRecentEventCount`,
28
+ * kept local so the public API stays a plain `value` number. Direct commits
29
+ * record `{ value, generation }`; incoming props that match a recent emit are
30
+ * acknowledged and must not rewind thumb position or the haptic baseline
31
+ * (including late echoes that arrive after a newer commit was already acked).
32
+ * Props that do not match any recent emit are treated as external updates and
33
+ * applied immediately.
17
34
  *
18
35
  * `mapValueToTrackPercent` / `mapTrackPercentToValue` must be worklets when provided.
19
36
  *
@@ -29,8 +46,73 @@ function useSliderGesture(params) {
29
46
  const thumbScale = (0, react_native_reanimated_1.useSharedValue)(1);
30
47
  const isDragging = (0, react_native_reanimated_1.useSharedValue)(false);
31
48
  const propValue = (0, react_native_reanimated_1.useSharedValue)(value);
49
+ // True while commitGeneration > ackedGeneration — UI thread skips prop→thumb.
50
+ const hasInflightCommits = (0, react_native_reanimated_1.useSharedValue)(false);
32
51
  const previousTrackPercentRef = (0, react_1.useRef)((0, Slider_utilities_1.getTrackPercentFromValue)(value, minimumValue, maximumValue, mapValueToTrackPercent));
33
52
  const isDraggingRef = (0, react_1.useRef)(false);
53
+ const commitGenerationRef = (0, react_1.useRef)(0);
54
+ const ackedGenerationRef = (0, react_1.useRef)(0);
55
+ const lastEmittedValueRef = (0, react_1.useRef)(null);
56
+ const lastEmittedGenerationRef = (0, react_1.useRef)(0);
57
+ const recentCommitsRef = (0, react_1.useRef)([]);
58
+ // --- Commit generation tracking (sequence ack, not a time heuristic) ---
59
+ const recordDirectCommit = (0, react_1.useCallback)((domainValue) => {
60
+ commitGenerationRef.current += 1;
61
+ const generation = commitGenerationRef.current;
62
+ lastEmittedValueRef.current = domainValue;
63
+ lastEmittedGenerationRef.current = generation;
64
+ recentCommitsRef.current.push({ value: domainValue, generation });
65
+ if (recentCommitsRef.current.length > RECENT_COMMIT_HISTORY_LIMIT) {
66
+ recentCommitsRef.current.shift();
67
+ }
68
+ hasInflightCommits.value = true;
69
+ }, [hasInflightCommits]);
70
+ /**
71
+ * Reconciles an incoming controlled `value` against recent local commits.
72
+ *
73
+ * @param incoming - Next `value` prop.
74
+ * @returns Whether to sync `propValue` / haptic baseline from the prop.
75
+ */
76
+ const reconcilePropValue = (0, react_1.useCallback)((incoming) => {
77
+ const recent = recentCommitsRef.current;
78
+ let match;
79
+ for (let index = recent.length - 1; index >= 0; index -= 1) {
80
+ if (recent[index].value === incoming) {
81
+ match = recent[index];
82
+ break;
83
+ }
84
+ }
85
+ if (match) {
86
+ // Late echo of an already-acked commit — do not write propValue or the
87
+ // reaction will snap the thumb backward after a fast pan.
88
+ if (match.generation <= ackedGenerationRef.current) {
89
+ return { syncPropValue: false, syncBaseline: false };
90
+ }
91
+ // If the value we get back is the latest one we sent up through
92
+ // onValueChange, we treat the parent as fully caught up and stop
93
+ // waiting on any older updates still in flight (those often get
94
+ // batched away during a fast pan anyway). If it's an older value
95
+ // instead, we only count that one as handled and keep waiting for
96
+ // the newer ones.
97
+ if (incoming === lastEmittedValueRef.current) {
98
+ ackedGenerationRef.current = lastEmittedGenerationRef.current;
99
+ }
100
+ else {
101
+ ackedGenerationRef.current = match.generation;
102
+ }
103
+ hasInflightCommits.value =
104
+ ackedGenerationRef.current < commitGenerationRef.current;
105
+ // Sync propValue only when catching up to the latest emit (correct
106
+ // position). Intermediate echoes must not overwrite propValue.
107
+ const isLatestAck = ackedGenerationRef.current === lastEmittedGenerationRef.current;
108
+ return { syncPropValue: isLatestAck, syncBaseline: false };
109
+ }
110
+ // Not a recent local emit — external update or parent transform.
111
+ ackedGenerationRef.current = commitGenerationRef.current;
112
+ lastEmittedValueRef.current = null;
113
+ hasInflightCommits.value = false;
114
+ return { syncPropValue: true, syncBaseline: true };
115
+ }, [hasInflightCommits]);
34
116
  // --- Position sync (domain value → thumb pixels) ---
35
117
  const syncPositionFromValue = (0, react_1.useCallback)((nextValue, width = sliderWidth.value) => {
36
118
  const trackPercent = (0, Slider_utilities_1.getTrackPercentFromValue)(nextValue, minimumValue, maximumValue, mapValueToTrackPercent);
@@ -44,15 +126,40 @@ function useSliderGesture(params) {
44
126
  ]);
45
127
  const handleLayout = (0, react_1.useCallback)((event) => {
46
128
  const { width } = event.nativeEvent.layout;
129
+ const previousWidth = sliderWidth.value;
47
130
  sliderWidth.value = width;
48
- syncPositionFromValue(value, width);
49
- }, [sliderWidth, syncPositionFromValue, value]);
131
+ // After the first layout, never re-apply the raw `value` prop. A layout
132
+ // pass can land while props still hold a lagged stale controlled echo
133
+ // that useEffect intentionally kept out of `propValue`; syncing from
134
+ // that echo would snap the thumb backward. Remap the current thumb
135
+ // across the (possibly new) width instead.
136
+ if (previousWidth > 0) {
137
+ translateX.value = (0, Slider_utilities_1.trackPercentToPosition)((0, Slider_utilities_1.positionToTrackPercent)(translateX.value, previousWidth), width);
138
+ return;
139
+ }
140
+ // First layout: `propValue` is initialized from `value` and only updated
141
+ // when reconcilePropValue allows it.
142
+ syncPositionFromValue(propValue.value, width);
143
+ }, [propValue, sliderWidth, syncPositionFromValue, translateX]);
50
144
  (0, react_1.useEffect)(() => {
51
- propValue.value = value;
52
- if (!isDraggingRef.current) {
53
- previousTrackPercentRef.current = (0, Slider_utilities_1.getTrackPercentFromValue)(value, minimumValue, maximumValue, mapValueToTrackPercent);
145
+ const { syncPropValue, syncBaseline } = reconcilePropValue(value);
146
+ // Never push stale echoes into propValue — the animated reaction would
147
+ // otherwise move the thumb even when syncBaseline is false.
148
+ if (syncPropValue) {
149
+ propValue.value = value;
150
+ }
151
+ if (isDraggingRef.current || !syncBaseline) {
152
+ return;
54
153
  }
55
- }, [mapValueToTrackPercent, maximumValue, minimumValue, propValue, value]);
154
+ previousTrackPercentRef.current = (0, Slider_utilities_1.getTrackPercentFromValue)(value, minimumValue, maximumValue, mapValueToTrackPercent);
155
+ }, [
156
+ mapValueToTrackPercent,
157
+ maximumValue,
158
+ minimumValue,
159
+ propValue,
160
+ reconcilePropValue,
161
+ value,
162
+ ]);
56
163
  (0, react_native_reanimated_1.useAnimatedReaction)(() => propValue.value, (currentValue, previousValue) => {
57
164
  if (isDragging.value) {
58
165
  return;
@@ -60,13 +167,22 @@ function useSliderGesture(params) {
60
167
  if (previousValue !== null && currentValue === previousValue) {
61
168
  return;
62
169
  }
170
+ // Skip while newer local commits are unacked. Stale echoes are kept out
171
+ // of propValue on the JS thread; this is a second guard for races.
172
+ if (hasInflightCommits.value) {
173
+ return;
174
+ }
63
175
  const trackPercent = (0, Slider_utilities_1.resolveValueToTrackPercent)(currentValue, minimumValue, maximumValue, mapValueToTrackPercent);
64
176
  translateX.value = (0, Slider_utilities_1.trackPercentToPosition)(trackPercent, sliderWidth.value);
65
177
  }, [mapValueToTrackPercent, maximumValue, minimumValue]);
66
178
  // --- JS-thread bridges (stable refs for runOnJS from worklets) ---
179
+ // Called from pan onUpdate as well as tap/end — each move is recorded on
180
+ // purpose so a lagged older `value` after a fast drag can still be ignored
181
+ // (see RECENT_COMMIT_HISTORY_LIMIT).
67
182
  const emitValueChange = (0, react_1.useCallback)((nextValue) => {
183
+ recordDirectCommit(nextValue);
68
184
  onValueChange(nextValue);
69
- }, [onValueChange]);
185
+ }, [onValueChange, recordDirectCommit]);
70
186
  const emitDragEnd = (0, react_1.useCallback)((nextValue) => {
71
187
  onDragEnd?.(nextValue);
72
188
  }, [onDragEnd]);
@@ -224,19 +340,19 @@ function useSliderGesture(params) {
224
340
  return;
225
341
  }
226
342
  const newValue = (0, Slider_utilities_1.getMarkValue)(mark, minimumValue, maximumValue);
227
- onValueChange(newValue);
343
+ emitValueChange(newValue);
228
344
  const trackPercent = (0, Slider_utilities_1.getTrackPercentFromValue)(newValue, minimumValue, maximumValue, mapValueToTrackPercent);
229
345
  checkThresholdCrossing(trackPercent);
230
346
  onDragEnd?.(newValue);
231
347
  syncPositionFromValue(newValue);
232
348
  }, [
233
349
  checkThresholdCrossing,
350
+ emitValueChange,
234
351
  isDisabled,
235
352
  mapValueToTrackPercent,
236
353
  maximumValue,
237
354
  minimumValue,
238
355
  onDragEnd,
239
- onValueChange,
240
356
  syncPositionFromValue,
241
357
  marks,
242
358
  ]);
@@ -1 +1 @@
1
- {"version":3,"file":"useSliderGesture.cjs","sourceRoot":"","sources":["../../../src/components/Slider/useSliderGesture.ts"],"names":[],"mappings":"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","sourcesContent":["import { useCallback, useEffect, useMemo, useRef } from 'react';\nimport { Gesture } from 'react-native-gesture-handler';\nimport {\n Easing,\n runOnJS,\n useAnimatedReaction,\n useAnimatedStyle,\n useDerivedValue,\n useSharedValue,\n withTiming,\n} from 'react-native-reanimated';\n\nimport {\n THUMB_GRIP_ANIMATION_DURATION,\n THUMB_GRIP_SCALE,\n} from './Slider.constants';\nimport type {\n UseSliderGestureParams,\n UseSliderGestureResult,\n} from './Slider.types';\nimport {\n clampGesturePosition,\n getMarkHapticThresholds,\n getMarkValue,\n getTrackPercentFromValue,\n interpolateMarkColor,\n positionToTrackPercent,\n resolveTrackPercentToValue,\n resolveValueToTrackPercent,\n trackPercentToPosition,\n} from './Slider.utilities';\n\n/**\n * Owns Slider gesture handling, animated thumb/fill position, and range-label taps.\n *\n * Flow during drag (UI thread):\n * touch X → clamp to track width → trackPercent → domain value → callbacks\n *\n * Flow when `value` prop changes (JS thread → UI thread via useAnimatedReaction):\n * domain value → trackPercent → translateX (skipped while dragging)\n *\n * `mapValueToTrackPercent` / `mapTrackPercentToValue` must be worklets when provided.\n *\n * @param params - Gesture configuration and callbacks.\n * @returns Gesture state and handlers for Slider.\n */\nexport function useSliderGesture(\n params: UseSliderGestureParams,\n): UseSliderGestureResult {\n const {\n value,\n minimumValue = 0,\n maximumValue = 100,\n step = 1,\n isDisabled = false,\n onValueChange,\n onDragEnd,\n onGrip,\n onMark,\n marks,\n fillColorStops,\n thumbColorStops,\n hasThemedColors,\n mapValueToTrackPercent,\n mapTrackPercentToValue,\n } = params;\n\n const hapticThresholds = useMemo(\n () => getMarkHapticThresholds(marks),\n [marks],\n );\n\n // --- Animated state ---\n\n const sliderWidth = useSharedValue(0);\n const translateX = useSharedValue(0);\n const thumbScale = useSharedValue(1);\n const isDragging = useSharedValue(false);\n const propValue = useSharedValue(value);\n const previousTrackPercentRef = useRef(\n getTrackPercentFromValue(\n value,\n minimumValue,\n maximumValue,\n mapValueToTrackPercent,\n ),\n );\n const isDraggingRef = useRef(false);\n\n // --- Position sync (domain value → thumb pixels) ---\n\n const syncPositionFromValue = useCallback(\n (nextValue: number, width = sliderWidth.value) => {\n const trackPercent = getTrackPercentFromValue(\n nextValue,\n minimumValue,\n maximumValue,\n mapValueToTrackPercent,\n );\n translateX.value = trackPercentToPosition(trackPercent, width);\n },\n [\n mapValueToTrackPercent,\n maximumValue,\n minimumValue,\n sliderWidth,\n translateX,\n ],\n );\n\n const handleLayout = useCallback(\n (event: { nativeEvent: { layout: { width: number } } }) => {\n const { width } = event.nativeEvent.layout;\n sliderWidth.value = width;\n syncPositionFromValue(value, width);\n },\n [sliderWidth, syncPositionFromValue, value],\n );\n\n useEffect(() => {\n propValue.value = value;\n if (!isDraggingRef.current) {\n previousTrackPercentRef.current = getTrackPercentFromValue(\n value,\n minimumValue,\n maximumValue,\n mapValueToTrackPercent,\n );\n }\n }, [mapValueToTrackPercent, maximumValue, minimumValue, propValue, value]);\n\n useAnimatedReaction(\n () => propValue.value,\n (currentValue, previousValue) => {\n if (isDragging.value) {\n return;\n }\n\n if (previousValue !== null && currentValue === previousValue) {\n return;\n }\n\n const trackPercent = resolveValueToTrackPercent(\n currentValue,\n minimumValue,\n maximumValue,\n mapValueToTrackPercent,\n );\n translateX.value = trackPercentToPosition(\n trackPercent,\n sliderWidth.value,\n );\n },\n [mapValueToTrackPercent, maximumValue, minimumValue],\n );\n\n // --- JS-thread bridges (stable refs for runOnJS from worklets) ---\n\n const emitValueChange = useCallback(\n (nextValue: number) => {\n onValueChange(nextValue);\n },\n [onValueChange],\n );\n\n const emitDragEnd = useCallback(\n (nextValue: number) => {\n onDragEnd?.(nextValue);\n },\n [onDragEnd],\n );\n\n const triggerGrip = useCallback(() => {\n if (!isDisabled) {\n onGrip?.();\n }\n }, [isDisabled, onGrip]);\n\n const setDragging = useCallback((dragging: boolean) => {\n isDraggingRef.current = dragging;\n }, []);\n\n const checkThresholdCrossing = useCallback(\n (newTrackPercent: number) => {\n const prevTrackPercent = previousTrackPercentRef.current;\n\n for (const threshold of hapticThresholds) {\n if (\n (prevTrackPercent < threshold && newTrackPercent >= threshold) ||\n (prevTrackPercent > threshold && newTrackPercent <= threshold)\n ) {\n onMark?.();\n break;\n }\n }\n\n previousTrackPercentRef.current = newTrackPercent;\n },\n [hapticThresholds, onMark],\n );\n\n // --- Animated styles (thumb + fill width follow translateX) ---\n\n const activeFillColor = useDerivedValue(() => {\n if (!hasThemedColors) {\n return undefined;\n }\n\n return interpolateMarkColor(\n positionToTrackPercent(translateX.value, sliderWidth.value),\n fillColorStops,\n );\n });\n\n const activeThumbColor = useDerivedValue(() => {\n if (!hasThemedColors) {\n return undefined;\n }\n\n return interpolateMarkColor(\n positionToTrackPercent(translateX.value, sliderWidth.value),\n thumbColorStops,\n );\n });\n\n const progressStyle = useAnimatedStyle(\n () => ({\n width: translateX.value,\n ...(hasThemedColors && {\n backgroundColor: activeFillColor.value,\n }),\n }),\n [fillColorStops, hasThemedColors],\n );\n\n const thumbStyle = useAnimatedStyle(\n () => ({\n transform: [\n { translateX: translateX.value },\n { scale: thumbScale.value },\n ],\n ...(hasThemedColors && {\n backgroundColor: activeThumbColor.value,\n }),\n }),\n [hasThemedColors, thumbColorStops],\n );\n\n // --- Pan + tap gestures ---\n\n const gesture = useMemo(() => {\n // Gesture callbacks need explicit 'worklet' directives because this package\n // ships pre-built dist compiled by ts-bridge. The consumer's Reanimated Babel\n // plugin does not auto-detect the compiled namespaced Gesture form.\n\n const gripEasing = Easing.bezier(0.3, 0.8, 0.3, 1);\n\n const positionToDomainValue = (position: number) => {\n 'worklet';\n\n const trackPercent = positionToTrackPercent(position, sliderWidth.value);\n const domainValue = resolveTrackPercentToValue(\n trackPercent,\n minimumValue,\n maximumValue,\n step,\n mapTrackPercentToValue,\n );\n\n return { trackPercent, domainValue };\n };\n\n const snapThumbToValue = (domainValue: number) => {\n 'worklet';\n\n const snappedTrackPercent = resolveValueToTrackPercent(\n domainValue,\n minimumValue,\n maximumValue,\n mapValueToTrackPercent,\n );\n translateX.value = trackPercentToPosition(\n snappedTrackPercent,\n sliderWidth.value,\n );\n };\n\n // Finalize a gesture: emit callbacks, then re-sync thumb to stepped value.\n const commitAtPosition = (\n position: number,\n options: { checkThreshold: boolean; triggerGrip: boolean },\n ) => {\n 'worklet';\n\n translateX.value = position;\n const { trackPercent, domainValue } = positionToDomainValue(position);\n\n runOnJS(emitValueChange)(domainValue);\n if (options.checkThreshold) {\n runOnJS(checkThresholdCrossing)(trackPercent);\n }\n runOnJS(emitDragEnd)(domainValue);\n if (options.triggerGrip) {\n runOnJS(triggerGrip)();\n }\n snapThumbToValue(domainValue);\n };\n\n const panGesture = Gesture.Pan()\n .enabled(!isDisabled)\n .onStart(() => {\n 'worklet';\n\n isDragging.value = true;\n thumbScale.value = withTiming(THUMB_GRIP_SCALE, {\n duration: THUMB_GRIP_ANIMATION_DURATION,\n easing: gripEasing,\n });\n runOnJS(setDragging)(true);\n runOnJS(triggerGrip)();\n })\n .onUpdate((event) => {\n 'worklet';\n\n const position = clampGesturePosition(event.x, sliderWidth.value);\n translateX.value = position;\n const { trackPercent, domainValue } = positionToDomainValue(position);\n\n runOnJS(emitValueChange)(domainValue);\n runOnJS(checkThresholdCrossing)(trackPercent);\n })\n .onEnd(() => {\n 'worklet';\n\n // Thresholds already checked during onUpdate; grip fires again on release.\n commitAtPosition(translateX.value, {\n checkThreshold: false,\n triggerGrip: true,\n });\n })\n .onFinalize(() => {\n 'worklet';\n\n isDragging.value = false;\n thumbScale.value = withTiming(1, {\n duration: THUMB_GRIP_ANIMATION_DURATION,\n easing: gripEasing,\n });\n runOnJS(setDragging)(false);\n });\n\n const tapGesture = Gesture.Tap()\n .enabled(!isDisabled)\n .onEnd((event) => {\n 'worklet';\n\n const position = clampGesturePosition(event.x, sliderWidth.value);\n commitAtPosition(position, {\n checkThreshold: true,\n triggerGrip: false,\n });\n });\n\n return Gesture.Simultaneous(tapGesture, panGesture);\n }, [\n checkThresholdCrossing,\n emitDragEnd,\n emitValueChange,\n isDisabled,\n isDragging,\n mapTrackPercentToValue,\n mapValueToTrackPercent,\n maximumValue,\n minimumValue,\n setDragging,\n sliderWidth,\n step,\n thumbScale,\n translateX,\n triggerGrip,\n ]);\n\n // --- Range label tap (JS thread) ---\n\n const handlePressStep = useCallback(\n (rangeStep: number) => {\n if (isDisabled) {\n return;\n }\n\n const mark = marks.find((entry) => entry.step === rangeStep);\n if (!mark) {\n return;\n }\n\n const newValue = getMarkValue(mark, minimumValue, maximumValue);\n onValueChange(newValue);\n const trackPercent = getTrackPercentFromValue(\n newValue,\n minimumValue,\n maximumValue,\n mapValueToTrackPercent,\n );\n checkThresholdCrossing(trackPercent);\n onDragEnd?.(newValue);\n syncPositionFromValue(newValue);\n },\n [\n checkThresholdCrossing,\n isDisabled,\n mapValueToTrackPercent,\n maximumValue,\n minimumValue,\n onDragEnd,\n onValueChange,\n syncPositionFromValue,\n marks,\n ],\n );\n\n return {\n handleLayout,\n progressStyle,\n thumbStyle,\n gesture,\n handlePressStep,\n };\n}\n"]}
1
+ {"version":3,"file":"useSliderGesture.cjs","sourceRoot":"","sources":["../../../src/components/Slider/useSliderGesture.ts"],"names":[],"mappings":"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","sourcesContent":["import { useCallback, useEffect, useMemo, useRef } from 'react';\nimport { Gesture } from 'react-native-gesture-handler';\nimport {\n Easing,\n runOnJS,\n useAnimatedReaction,\n useAnimatedStyle,\n useDerivedValue,\n useSharedValue,\n withTiming,\n} from 'react-native-reanimated';\n\nimport {\n THUMB_GRIP_ANIMATION_DURATION,\n THUMB_GRIP_SCALE,\n} from './Slider.constants';\nimport type {\n UseSliderGestureParams,\n UseSliderGestureResult,\n} from './Slider.types';\nimport {\n clampGesturePosition,\n getMarkHapticThresholds,\n getMarkValue,\n getTrackPercentFromValue,\n interpolateMarkColor,\n positionToTrackPercent,\n resolveTrackPercentToValue,\n resolveValueToTrackPercent,\n trackPercentToPosition,\n} from './Slider.utilities';\n\n/**\n * Cap on remembered local updates used to spot late `value` echoes.\n * Pan `onUpdate` records every intermediate position, so a fast drag can\n * produce many entries; 256 is large enough for a long scrub without\n * growing unbounded.\n */\nconst RECENT_COMMIT_HISTORY_LIMIT = 256;\n\ntype CommitEntry = {\n value: number;\n generation: number;\n};\n\ntype PropReconciliation = {\n /** Write `value` into `propValue` for the UI-thread reaction. */\n syncPropValue: boolean;\n /** Update haptic baseline from the prop (external updates only). */\n syncBaseline: boolean;\n};\n\n/**\n * Owns Slider gesture handling, animated thumb/fill position, and range-label taps.\n *\n * Flow during drag (UI thread):\n * touch X → clamp to track width → trackPercent → domain value → callbacks\n *\n * Flow when `value` prop changes (JS thread → UI thread via useAnimatedReaction):\n * domain value → trackPercent → translateX (skipped while dragging).\n *\n * Stale controlled self-echoes (gesture → onValueChange → parent state → value\n * prop) are suppressed with a monotonically increasing commit generation — the\n * same sequence principle as React Native TextInput's `mostRecentEventCount`,\n * kept local so the public API stays a plain `value` number. Direct commits\n * record `{ value, generation }`; incoming props that match a recent emit are\n * acknowledged and must not rewind thumb position or the haptic baseline\n * (including late echoes that arrive after a newer commit was already acked).\n * Props that do not match any recent emit are treated as external updates and\n * applied immediately.\n *\n * `mapValueToTrackPercent` / `mapTrackPercentToValue` must be worklets when provided.\n *\n * @param params - Gesture configuration and callbacks.\n * @returns Gesture state and handlers for Slider.\n */\nexport function useSliderGesture(\n params: UseSliderGestureParams,\n): UseSliderGestureResult {\n const {\n value,\n minimumValue = 0,\n maximumValue = 100,\n step = 1,\n isDisabled = false,\n onValueChange,\n onDragEnd,\n onGrip,\n onMark,\n marks,\n fillColorStops,\n thumbColorStops,\n hasThemedColors,\n mapValueToTrackPercent,\n mapTrackPercentToValue,\n } = params;\n\n const hapticThresholds = useMemo(\n () => getMarkHapticThresholds(marks),\n [marks],\n );\n\n // --- Animated state ---\n\n const sliderWidth = useSharedValue(0);\n const translateX = useSharedValue(0);\n const thumbScale = useSharedValue(1);\n const isDragging = useSharedValue(false);\n const propValue = useSharedValue(value);\n // True while commitGeneration > ackedGeneration — UI thread skips prop→thumb.\n const hasInflightCommits = useSharedValue(false);\n const previousTrackPercentRef = useRef(\n getTrackPercentFromValue(\n value,\n minimumValue,\n maximumValue,\n mapValueToTrackPercent,\n ),\n );\n const isDraggingRef = useRef(false);\n const commitGenerationRef = useRef(0);\n const ackedGenerationRef = useRef(0);\n const lastEmittedValueRef = useRef<number | null>(null);\n const lastEmittedGenerationRef = useRef(0);\n const recentCommitsRef = useRef<CommitEntry[]>([]);\n\n // --- Commit generation tracking (sequence ack, not a time heuristic) ---\n\n const recordDirectCommit = useCallback(\n (domainValue: number) => {\n commitGenerationRef.current += 1;\n const generation = commitGenerationRef.current;\n lastEmittedValueRef.current = domainValue;\n lastEmittedGenerationRef.current = generation;\n recentCommitsRef.current.push({ value: domainValue, generation });\n if (recentCommitsRef.current.length > RECENT_COMMIT_HISTORY_LIMIT) {\n recentCommitsRef.current.shift();\n }\n hasInflightCommits.value = true;\n },\n [hasInflightCommits],\n );\n\n /**\n * Reconciles an incoming controlled `value` against recent local commits.\n *\n * @param incoming - Next `value` prop.\n * @returns Whether to sync `propValue` / haptic baseline from the prop.\n */\n const reconcilePropValue = useCallback(\n (incoming: number): PropReconciliation => {\n const recent = recentCommitsRef.current;\n let match: CommitEntry | undefined;\n for (let index = recent.length - 1; index >= 0; index -= 1) {\n if (recent[index].value === incoming) {\n match = recent[index];\n break;\n }\n }\n\n if (match) {\n // Late echo of an already-acked commit — do not write propValue or the\n // reaction will snap the thumb backward after a fast pan.\n if (match.generation <= ackedGenerationRef.current) {\n return { syncPropValue: false, syncBaseline: false };\n }\n\n // If the value we get back is the latest one we sent up through\n // onValueChange, we treat the parent as fully caught up and stop\n // waiting on any older updates still in flight (those often get\n // batched away during a fast pan anyway). If it's an older value\n // instead, we only count that one as handled and keep waiting for\n // the newer ones.\n if (incoming === lastEmittedValueRef.current) {\n ackedGenerationRef.current = lastEmittedGenerationRef.current;\n } else {\n ackedGenerationRef.current = match.generation;\n }\n\n hasInflightCommits.value =\n ackedGenerationRef.current < commitGenerationRef.current;\n\n // Sync propValue only when catching up to the latest emit (correct\n // position). Intermediate echoes must not overwrite propValue.\n const isLatestAck =\n ackedGenerationRef.current === lastEmittedGenerationRef.current;\n return { syncPropValue: isLatestAck, syncBaseline: false };\n }\n\n // Not a recent local emit — external update or parent transform.\n ackedGenerationRef.current = commitGenerationRef.current;\n lastEmittedValueRef.current = null;\n hasInflightCommits.value = false;\n return { syncPropValue: true, syncBaseline: true };\n },\n [hasInflightCommits],\n );\n\n // --- Position sync (domain value → thumb pixels) ---\n\n const syncPositionFromValue = useCallback(\n (nextValue: number, width = sliderWidth.value) => {\n const trackPercent = getTrackPercentFromValue(\n nextValue,\n minimumValue,\n maximumValue,\n mapValueToTrackPercent,\n );\n translateX.value = trackPercentToPosition(trackPercent, width);\n },\n [\n mapValueToTrackPercent,\n maximumValue,\n minimumValue,\n sliderWidth,\n translateX,\n ],\n );\n\n const handleLayout = useCallback(\n (event: { nativeEvent: { layout: { width: number } } }) => {\n const { width } = event.nativeEvent.layout;\n const previousWidth = sliderWidth.value;\n sliderWidth.value = width;\n\n // After the first layout, never re-apply the raw `value` prop. A layout\n // pass can land while props still hold a lagged stale controlled echo\n // that useEffect intentionally kept out of `propValue`; syncing from\n // that echo would snap the thumb backward. Remap the current thumb\n // across the (possibly new) width instead.\n if (previousWidth > 0) {\n translateX.value = trackPercentToPosition(\n positionToTrackPercent(translateX.value, previousWidth),\n width,\n );\n return;\n }\n\n // First layout: `propValue` is initialized from `value` and only updated\n // when reconcilePropValue allows it.\n syncPositionFromValue(propValue.value, width);\n },\n [propValue, sliderWidth, syncPositionFromValue, translateX],\n );\n\n useEffect(() => {\n const { syncPropValue, syncBaseline } = reconcilePropValue(value);\n\n // Never push stale echoes into propValue — the animated reaction would\n // otherwise move the thumb even when syncBaseline is false.\n if (syncPropValue) {\n propValue.value = value;\n }\n\n if (isDraggingRef.current || !syncBaseline) {\n return;\n }\n\n previousTrackPercentRef.current = getTrackPercentFromValue(\n value,\n minimumValue,\n maximumValue,\n mapValueToTrackPercent,\n );\n }, [\n mapValueToTrackPercent,\n maximumValue,\n minimumValue,\n propValue,\n reconcilePropValue,\n value,\n ]);\n\n useAnimatedReaction(\n () => propValue.value,\n (currentValue, previousValue) => {\n if (isDragging.value) {\n return;\n }\n\n if (previousValue !== null && currentValue === previousValue) {\n return;\n }\n\n // Skip while newer local commits are unacked. Stale echoes are kept out\n // of propValue on the JS thread; this is a second guard for races.\n if (hasInflightCommits.value) {\n return;\n }\n\n const trackPercent = resolveValueToTrackPercent(\n currentValue,\n minimumValue,\n maximumValue,\n mapValueToTrackPercent,\n );\n translateX.value = trackPercentToPosition(\n trackPercent,\n sliderWidth.value,\n );\n },\n [mapValueToTrackPercent, maximumValue, minimumValue],\n );\n\n // --- JS-thread bridges (stable refs for runOnJS from worklets) ---\n\n // Called from pan onUpdate as well as tap/end — each move is recorded on\n // purpose so a lagged older `value` after a fast drag can still be ignored\n // (see RECENT_COMMIT_HISTORY_LIMIT).\n const emitValueChange = useCallback(\n (nextValue: number) => {\n recordDirectCommit(nextValue);\n onValueChange(nextValue);\n },\n [onValueChange, recordDirectCommit],\n );\n\n const emitDragEnd = useCallback(\n (nextValue: number) => {\n onDragEnd?.(nextValue);\n },\n [onDragEnd],\n );\n\n const triggerGrip = useCallback(() => {\n if (!isDisabled) {\n onGrip?.();\n }\n }, [isDisabled, onGrip]);\n\n const setDragging = useCallback((dragging: boolean) => {\n isDraggingRef.current = dragging;\n }, []);\n\n const checkThresholdCrossing = useCallback(\n (newTrackPercent: number) => {\n const prevTrackPercent = previousTrackPercentRef.current;\n\n for (const threshold of hapticThresholds) {\n if (\n (prevTrackPercent < threshold && newTrackPercent >= threshold) ||\n (prevTrackPercent > threshold && newTrackPercent <= threshold)\n ) {\n onMark?.();\n break;\n }\n }\n\n previousTrackPercentRef.current = newTrackPercent;\n },\n [hapticThresholds, onMark],\n );\n\n // --- Animated styles (thumb + fill width follow translateX) ---\n\n const activeFillColor = useDerivedValue(() => {\n if (!hasThemedColors) {\n return undefined;\n }\n\n return interpolateMarkColor(\n positionToTrackPercent(translateX.value, sliderWidth.value),\n fillColorStops,\n );\n });\n\n const activeThumbColor = useDerivedValue(() => {\n if (!hasThemedColors) {\n return undefined;\n }\n\n return interpolateMarkColor(\n positionToTrackPercent(translateX.value, sliderWidth.value),\n thumbColorStops,\n );\n });\n\n const progressStyle = useAnimatedStyle(\n () => ({\n width: translateX.value,\n ...(hasThemedColors && {\n backgroundColor: activeFillColor.value,\n }),\n }),\n [fillColorStops, hasThemedColors],\n );\n\n const thumbStyle = useAnimatedStyle(\n () => ({\n transform: [\n { translateX: translateX.value },\n { scale: thumbScale.value },\n ],\n ...(hasThemedColors && {\n backgroundColor: activeThumbColor.value,\n }),\n }),\n [hasThemedColors, thumbColorStops],\n );\n\n // --- Pan + tap gestures ---\n\n const gesture = useMemo(() => {\n // Gesture callbacks need explicit 'worklet' directives because this package\n // ships pre-built dist compiled by ts-bridge. The consumer's Reanimated Babel\n // plugin does not auto-detect the compiled namespaced Gesture form.\n\n const gripEasing = Easing.bezier(0.3, 0.8, 0.3, 1);\n\n const positionToDomainValue = (position: number) => {\n 'worklet';\n\n const trackPercent = positionToTrackPercent(position, sliderWidth.value);\n const domainValue = resolveTrackPercentToValue(\n trackPercent,\n minimumValue,\n maximumValue,\n step,\n mapTrackPercentToValue,\n );\n\n return { trackPercent, domainValue };\n };\n\n const snapThumbToValue = (domainValue: number) => {\n 'worklet';\n\n const snappedTrackPercent = resolveValueToTrackPercent(\n domainValue,\n minimumValue,\n maximumValue,\n mapValueToTrackPercent,\n );\n translateX.value = trackPercentToPosition(\n snappedTrackPercent,\n sliderWidth.value,\n );\n };\n\n // Finalize a gesture: emit callbacks, then re-sync thumb to stepped value.\n const commitAtPosition = (\n position: number,\n options: { checkThreshold: boolean; triggerGrip: boolean },\n ) => {\n 'worklet';\n\n translateX.value = position;\n const { trackPercent, domainValue } = positionToDomainValue(position);\n\n runOnJS(emitValueChange)(domainValue);\n if (options.checkThreshold) {\n runOnJS(checkThresholdCrossing)(trackPercent);\n }\n runOnJS(emitDragEnd)(domainValue);\n if (options.triggerGrip) {\n runOnJS(triggerGrip)();\n }\n snapThumbToValue(domainValue);\n };\n\n const panGesture = Gesture.Pan()\n .enabled(!isDisabled)\n .onStart(() => {\n 'worklet';\n\n isDragging.value = true;\n thumbScale.value = withTiming(THUMB_GRIP_SCALE, {\n duration: THUMB_GRIP_ANIMATION_DURATION,\n easing: gripEasing,\n });\n runOnJS(setDragging)(true);\n runOnJS(triggerGrip)();\n })\n .onUpdate((event) => {\n 'worklet';\n\n const position = clampGesturePosition(event.x, sliderWidth.value);\n translateX.value = position;\n const { trackPercent, domainValue } = positionToDomainValue(position);\n\n runOnJS(emitValueChange)(domainValue);\n runOnJS(checkThresholdCrossing)(trackPercent);\n })\n .onEnd(() => {\n 'worklet';\n\n // Thresholds already checked during onUpdate; grip fires again on release.\n commitAtPosition(translateX.value, {\n checkThreshold: false,\n triggerGrip: true,\n });\n })\n .onFinalize(() => {\n 'worklet';\n\n isDragging.value = false;\n thumbScale.value = withTiming(1, {\n duration: THUMB_GRIP_ANIMATION_DURATION,\n easing: gripEasing,\n });\n runOnJS(setDragging)(false);\n });\n\n const tapGesture = Gesture.Tap()\n .enabled(!isDisabled)\n .onEnd((event) => {\n 'worklet';\n\n const position = clampGesturePosition(event.x, sliderWidth.value);\n commitAtPosition(position, {\n checkThreshold: true,\n triggerGrip: false,\n });\n });\n\n return Gesture.Simultaneous(tapGesture, panGesture);\n }, [\n checkThresholdCrossing,\n emitDragEnd,\n emitValueChange,\n isDisabled,\n isDragging,\n mapTrackPercentToValue,\n mapValueToTrackPercent,\n maximumValue,\n minimumValue,\n setDragging,\n sliderWidth,\n step,\n thumbScale,\n translateX,\n triggerGrip,\n ]);\n\n // --- Range label tap (JS thread) ---\n\n const handlePressStep = useCallback(\n (rangeStep: number) => {\n if (isDisabled) {\n return;\n }\n\n const mark = marks.find((entry) => entry.step === rangeStep);\n if (!mark) {\n return;\n }\n\n const newValue = getMarkValue(mark, minimumValue, maximumValue);\n emitValueChange(newValue);\n const trackPercent = getTrackPercentFromValue(\n newValue,\n minimumValue,\n maximumValue,\n mapValueToTrackPercent,\n );\n checkThresholdCrossing(trackPercent);\n onDragEnd?.(newValue);\n syncPositionFromValue(newValue);\n },\n [\n checkThresholdCrossing,\n emitValueChange,\n isDisabled,\n mapValueToTrackPercent,\n maximumValue,\n minimumValue,\n onDragEnd,\n syncPositionFromValue,\n marks,\n ],\n );\n\n return {\n handleLayout,\n progressStyle,\n thumbStyle,\n gesture,\n handlePressStep,\n };\n}\n"]}
@@ -6,7 +6,17 @@ import type { UseSliderGestureParams, UseSliderGestureResult } from "./Slider.ty
6
6
  * touch X → clamp to track width → trackPercent → domain value → callbacks
7
7
  *
8
8
  * Flow when `value` prop changes (JS thread → UI thread via useAnimatedReaction):
9
- * domain value → trackPercent → translateX (skipped while dragging)
9
+ * domain value → trackPercent → translateX (skipped while dragging).
10
+ *
11
+ * Stale controlled self-echoes (gesture → onValueChange → parent state → value
12
+ * prop) are suppressed with a monotonically increasing commit generation — the
13
+ * same sequence principle as React Native TextInput's `mostRecentEventCount`,
14
+ * kept local so the public API stays a plain `value` number. Direct commits
15
+ * record `{ value, generation }`; incoming props that match a recent emit are
16
+ * acknowledged and must not rewind thumb position or the haptic baseline
17
+ * (including late echoes that arrive after a newer commit was already acked).
18
+ * Props that do not match any recent emit are treated as external updates and
19
+ * applied immediately.
10
20
  *
11
21
  * `mapValueToTrackPercent` / `mapTrackPercentToValue` must be worklets when provided.
12
22
  *
@@ -1 +1 @@
1
- {"version":3,"file":"useSliderGesture.d.cts","sourceRoot":"","sources":["../../../src/components/Slider/useSliderGesture.ts"],"names":[],"mappings":"AAgBA,OAAO,KAAK,EACV,sBAAsB,EACtB,sBAAsB,EACvB,2BAAuB;AAaxB;;;;;;;;;;;;;GAaG;AACH,wBAAgB,gBAAgB,CAC9B,MAAM,EAAE,sBAAsB,GAC7B,sBAAsB,CA2XxB"}
1
+ {"version":3,"file":"useSliderGesture.d.cts","sourceRoot":"","sources":["../../../src/components/Slider/useSliderGesture.ts"],"names":[],"mappings":"AAgBA,OAAO,KAAK,EACV,sBAAsB,EACtB,sBAAsB,EACvB,2BAAuB;AAiCxB;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,wBAAgB,gBAAgB,CAC9B,MAAM,EAAE,sBAAsB,GAC7B,sBAAsB,CAqfxB"}
@@ -6,7 +6,17 @@ import type { UseSliderGestureParams, UseSliderGestureResult } from "./Slider.ty
6
6
  * touch X → clamp to track width → trackPercent → domain value → callbacks
7
7
  *
8
8
  * Flow when `value` prop changes (JS thread → UI thread via useAnimatedReaction):
9
- * domain value → trackPercent → translateX (skipped while dragging)
9
+ * domain value → trackPercent → translateX (skipped while dragging).
10
+ *
11
+ * Stale controlled self-echoes (gesture → onValueChange → parent state → value
12
+ * prop) are suppressed with a monotonically increasing commit generation — the
13
+ * same sequence principle as React Native TextInput's `mostRecentEventCount`,
14
+ * kept local so the public API stays a plain `value` number. Direct commits
15
+ * record `{ value, generation }`; incoming props that match a recent emit are
16
+ * acknowledged and must not rewind thumb position or the haptic baseline
17
+ * (including late echoes that arrive after a newer commit was already acked).
18
+ * Props that do not match any recent emit are treated as external updates and
19
+ * applied immediately.
10
20
  *
11
21
  * `mapValueToTrackPercent` / `mapTrackPercentToValue` must be worklets when provided.
12
22
  *
@@ -1 +1 @@
1
- {"version":3,"file":"useSliderGesture.d.mts","sourceRoot":"","sources":["../../../src/components/Slider/useSliderGesture.ts"],"names":[],"mappings":"AAgBA,OAAO,KAAK,EACV,sBAAsB,EACtB,sBAAsB,EACvB,2BAAuB;AAaxB;;;;;;;;;;;;;GAaG;AACH,wBAAgB,gBAAgB,CAC9B,MAAM,EAAE,sBAAsB,GAC7B,sBAAsB,CA2XxB"}
1
+ {"version":3,"file":"useSliderGesture.d.mts","sourceRoot":"","sources":["../../../src/components/Slider/useSliderGesture.ts"],"names":[],"mappings":"AAgBA,OAAO,KAAK,EACV,sBAAsB,EACtB,sBAAsB,EACvB,2BAAuB;AAiCxB;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,wBAAgB,gBAAgB,CAC9B,MAAM,EAAE,sBAAsB,GAC7B,sBAAsB,CAqfxB"}
@@ -3,6 +3,13 @@ import { Gesture } from "react-native-gesture-handler";
3
3
  import { Easing, runOnJS, useAnimatedReaction, useAnimatedStyle, useDerivedValue, useSharedValue, withTiming } from "react-native-reanimated";
4
4
  import { THUMB_GRIP_ANIMATION_DURATION, THUMB_GRIP_SCALE } from "./Slider.constants.mjs";
5
5
  import { clampGesturePosition, getMarkHapticThresholds, getMarkValue, getTrackPercentFromValue, interpolateMarkColor, positionToTrackPercent, resolveTrackPercentToValue, resolveValueToTrackPercent, trackPercentToPosition } from "./Slider.utilities.mjs";
6
+ /**
7
+ * Cap on remembered local updates used to spot late `value` echoes.
8
+ * Pan `onUpdate` records every intermediate position, so a fast drag can
9
+ * produce many entries; 256 is large enough for a long scrub without
10
+ * growing unbounded.
11
+ */
12
+ const RECENT_COMMIT_HISTORY_LIMIT = 256;
6
13
  /**
7
14
  * Owns Slider gesture handling, animated thumb/fill position, and range-label taps.
8
15
  *
@@ -10,7 +17,17 @@ import { clampGesturePosition, getMarkHapticThresholds, getMarkValue, getTrackPe
10
17
  * touch X → clamp to track width → trackPercent → domain value → callbacks
11
18
  *
12
19
  * Flow when `value` prop changes (JS thread → UI thread via useAnimatedReaction):
13
- * domain value → trackPercent → translateX (skipped while dragging)
20
+ * domain value → trackPercent → translateX (skipped while dragging).
21
+ *
22
+ * Stale controlled self-echoes (gesture → onValueChange → parent state → value
23
+ * prop) are suppressed with a monotonically increasing commit generation — the
24
+ * same sequence principle as React Native TextInput's `mostRecentEventCount`,
25
+ * kept local so the public API stays a plain `value` number. Direct commits
26
+ * record `{ value, generation }`; incoming props that match a recent emit are
27
+ * acknowledged and must not rewind thumb position or the haptic baseline
28
+ * (including late echoes that arrive after a newer commit was already acked).
29
+ * Props that do not match any recent emit are treated as external updates and
30
+ * applied immediately.
14
31
  *
15
32
  * `mapValueToTrackPercent` / `mapTrackPercentToValue` must be worklets when provided.
16
33
  *
@@ -26,8 +43,73 @@ export function useSliderGesture(params) {
26
43
  const thumbScale = useSharedValue(1);
27
44
  const isDragging = useSharedValue(false);
28
45
  const propValue = useSharedValue(value);
46
+ // True while commitGeneration > ackedGeneration — UI thread skips prop→thumb.
47
+ const hasInflightCommits = useSharedValue(false);
29
48
  const previousTrackPercentRef = useRef(getTrackPercentFromValue(value, minimumValue, maximumValue, mapValueToTrackPercent));
30
49
  const isDraggingRef = useRef(false);
50
+ const commitGenerationRef = useRef(0);
51
+ const ackedGenerationRef = useRef(0);
52
+ const lastEmittedValueRef = useRef(null);
53
+ const lastEmittedGenerationRef = useRef(0);
54
+ const recentCommitsRef = useRef([]);
55
+ // --- Commit generation tracking (sequence ack, not a time heuristic) ---
56
+ const recordDirectCommit = useCallback((domainValue) => {
57
+ commitGenerationRef.current += 1;
58
+ const generation = commitGenerationRef.current;
59
+ lastEmittedValueRef.current = domainValue;
60
+ lastEmittedGenerationRef.current = generation;
61
+ recentCommitsRef.current.push({ value: domainValue, generation });
62
+ if (recentCommitsRef.current.length > RECENT_COMMIT_HISTORY_LIMIT) {
63
+ recentCommitsRef.current.shift();
64
+ }
65
+ hasInflightCommits.value = true;
66
+ }, [hasInflightCommits]);
67
+ /**
68
+ * Reconciles an incoming controlled `value` against recent local commits.
69
+ *
70
+ * @param incoming - Next `value` prop.
71
+ * @returns Whether to sync `propValue` / haptic baseline from the prop.
72
+ */
73
+ const reconcilePropValue = useCallback((incoming) => {
74
+ const recent = recentCommitsRef.current;
75
+ let match;
76
+ for (let index = recent.length - 1; index >= 0; index -= 1) {
77
+ if (recent[index].value === incoming) {
78
+ match = recent[index];
79
+ break;
80
+ }
81
+ }
82
+ if (match) {
83
+ // Late echo of an already-acked commit — do not write propValue or the
84
+ // reaction will snap the thumb backward after a fast pan.
85
+ if (match.generation <= ackedGenerationRef.current) {
86
+ return { syncPropValue: false, syncBaseline: false };
87
+ }
88
+ // If the value we get back is the latest one we sent up through
89
+ // onValueChange, we treat the parent as fully caught up and stop
90
+ // waiting on any older updates still in flight (those often get
91
+ // batched away during a fast pan anyway). If it's an older value
92
+ // instead, we only count that one as handled and keep waiting for
93
+ // the newer ones.
94
+ if (incoming === lastEmittedValueRef.current) {
95
+ ackedGenerationRef.current = lastEmittedGenerationRef.current;
96
+ }
97
+ else {
98
+ ackedGenerationRef.current = match.generation;
99
+ }
100
+ hasInflightCommits.value =
101
+ ackedGenerationRef.current < commitGenerationRef.current;
102
+ // Sync propValue only when catching up to the latest emit (correct
103
+ // position). Intermediate echoes must not overwrite propValue.
104
+ const isLatestAck = ackedGenerationRef.current === lastEmittedGenerationRef.current;
105
+ return { syncPropValue: isLatestAck, syncBaseline: false };
106
+ }
107
+ // Not a recent local emit — external update or parent transform.
108
+ ackedGenerationRef.current = commitGenerationRef.current;
109
+ lastEmittedValueRef.current = null;
110
+ hasInflightCommits.value = false;
111
+ return { syncPropValue: true, syncBaseline: true };
112
+ }, [hasInflightCommits]);
31
113
  // --- Position sync (domain value → thumb pixels) ---
32
114
  const syncPositionFromValue = useCallback((nextValue, width = sliderWidth.value) => {
33
115
  const trackPercent = getTrackPercentFromValue(nextValue, minimumValue, maximumValue, mapValueToTrackPercent);
@@ -41,15 +123,40 @@ export function useSliderGesture(params) {
41
123
  ]);
42
124
  const handleLayout = useCallback((event) => {
43
125
  const { width } = event.nativeEvent.layout;
126
+ const previousWidth = sliderWidth.value;
44
127
  sliderWidth.value = width;
45
- syncPositionFromValue(value, width);
46
- }, [sliderWidth, syncPositionFromValue, value]);
128
+ // After the first layout, never re-apply the raw `value` prop. A layout
129
+ // pass can land while props still hold a lagged stale controlled echo
130
+ // that useEffect intentionally kept out of `propValue`; syncing from
131
+ // that echo would snap the thumb backward. Remap the current thumb
132
+ // across the (possibly new) width instead.
133
+ if (previousWidth > 0) {
134
+ translateX.value = trackPercentToPosition(positionToTrackPercent(translateX.value, previousWidth), width);
135
+ return;
136
+ }
137
+ // First layout: `propValue` is initialized from `value` and only updated
138
+ // when reconcilePropValue allows it.
139
+ syncPositionFromValue(propValue.value, width);
140
+ }, [propValue, sliderWidth, syncPositionFromValue, translateX]);
47
141
  useEffect(() => {
48
- propValue.value = value;
49
- if (!isDraggingRef.current) {
50
- previousTrackPercentRef.current = getTrackPercentFromValue(value, minimumValue, maximumValue, mapValueToTrackPercent);
142
+ const { syncPropValue, syncBaseline } = reconcilePropValue(value);
143
+ // Never push stale echoes into propValue — the animated reaction would
144
+ // otherwise move the thumb even when syncBaseline is false.
145
+ if (syncPropValue) {
146
+ propValue.value = value;
147
+ }
148
+ if (isDraggingRef.current || !syncBaseline) {
149
+ return;
51
150
  }
52
- }, [mapValueToTrackPercent, maximumValue, minimumValue, propValue, value]);
151
+ previousTrackPercentRef.current = getTrackPercentFromValue(value, minimumValue, maximumValue, mapValueToTrackPercent);
152
+ }, [
153
+ mapValueToTrackPercent,
154
+ maximumValue,
155
+ minimumValue,
156
+ propValue,
157
+ reconcilePropValue,
158
+ value,
159
+ ]);
53
160
  useAnimatedReaction(() => propValue.value, (currentValue, previousValue) => {
54
161
  if (isDragging.value) {
55
162
  return;
@@ -57,13 +164,22 @@ export function useSliderGesture(params) {
57
164
  if (previousValue !== null && currentValue === previousValue) {
58
165
  return;
59
166
  }
167
+ // Skip while newer local commits are unacked. Stale echoes are kept out
168
+ // of propValue on the JS thread; this is a second guard for races.
169
+ if (hasInflightCommits.value) {
170
+ return;
171
+ }
60
172
  const trackPercent = resolveValueToTrackPercent(currentValue, minimumValue, maximumValue, mapValueToTrackPercent);
61
173
  translateX.value = trackPercentToPosition(trackPercent, sliderWidth.value);
62
174
  }, [mapValueToTrackPercent, maximumValue, minimumValue]);
63
175
  // --- JS-thread bridges (stable refs for runOnJS from worklets) ---
176
+ // Called from pan onUpdate as well as tap/end — each move is recorded on
177
+ // purpose so a lagged older `value` after a fast drag can still be ignored
178
+ // (see RECENT_COMMIT_HISTORY_LIMIT).
64
179
  const emitValueChange = useCallback((nextValue) => {
180
+ recordDirectCommit(nextValue);
65
181
  onValueChange(nextValue);
66
- }, [onValueChange]);
182
+ }, [onValueChange, recordDirectCommit]);
67
183
  const emitDragEnd = useCallback((nextValue) => {
68
184
  onDragEnd?.(nextValue);
69
185
  }, [onDragEnd]);
@@ -221,19 +337,19 @@ export function useSliderGesture(params) {
221
337
  return;
222
338
  }
223
339
  const newValue = getMarkValue(mark, minimumValue, maximumValue);
224
- onValueChange(newValue);
340
+ emitValueChange(newValue);
225
341
  const trackPercent = getTrackPercentFromValue(newValue, minimumValue, maximumValue, mapValueToTrackPercent);
226
342
  checkThresholdCrossing(trackPercent);
227
343
  onDragEnd?.(newValue);
228
344
  syncPositionFromValue(newValue);
229
345
  }, [
230
346
  checkThresholdCrossing,
347
+ emitValueChange,
231
348
  isDisabled,
232
349
  mapValueToTrackPercent,
233
350
  maximumValue,
234
351
  minimumValue,
235
352
  onDragEnd,
236
- onValueChange,
237
353
  syncPositionFromValue,
238
354
  marks,
239
355
  ]);