@gryt/ui-native 0.6.0 → 0.7.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.
@@ -1 +1 @@
1
- {"version":3,"file":"Drawer.d.ts","sourceRoot":"","sources":["../../../src/components/Drawer/Drawer.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAiB,KAAK,SAAS,EAA2C,MAAM,OAAO,CAAC;AAC/F,OAAO,EAKL,KAAK,SAAS,EACd,KAAK,SAAS,EACf,MAAM,cAAc,CAAC;AAWtB,OAAO,EAAgB,KAAK,cAAc,EAAE,MAAM,4BAA4B,CAAC;AAG/E,MAAM,MAAM,UAAU,GAAG,MAAM,GAAG,OAAO,GAAG,QAAQ,CAAC;AAgBrD,MAAM,WAAW,eAAgB,SAAQ,cAAc;IACrD,QAAQ,CAAC,EAAE,SAAS,CAAC;CACtB;AAED,iBAAS,IAAI,CAAC,EAAE,QAAQ,EAAE,GAAG,SAAS,EAAE,EAAE,eAAe,+BAKxD;AAED,iBAAS,OAAO,CAAC,EACf,QAAQ,EACR,KAAK,EACN,EAAE;IACD,QAAQ,CAAC,EAAE,SAAS,CAAC;IACrB,KAAK,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;CAC9B,+BAWA;AAED,iBAAS,MAAM,CAAC,EAAE,QAAQ,EAAE,EAAE;IAAE,QAAQ,CAAC,EAAE,SAAS,CAAA;CAAE,+BAErD;AAED,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,EAAE,SAAS,CAAC;IACrB,IAAI,CAAC,EAAE,UAAU,CAAC;IAClB,sCAAsC;IACtC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,KAAK,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;CAC9B;AAgBD,iBAAS,KAAK,CAAC,EACb,QAAQ,EACR,IAAa,EACb,IAAU,EACV,WAAkB,EAClB,KAAK,EACN,EAAE,gBAAgB,+BAiSlB;AAED,iBAAS,KAAK,CAAC,EACb,QAAQ,EACR,KAAK,EACN,EAAE;IACD,QAAQ,CAAC,EAAE,SAAS,CAAC;IACrB,KAAK,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;CAC9B,+BAWA;AAED,eAAO,MAAM,MAAM;;;;;;CAA0C,CAAC"}
1
+ {"version":3,"file":"Drawer.d.ts","sourceRoot":"","sources":["../../../src/components/Drawer/Drawer.tsx"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,SAAS,EAKf,MAAM,OAAO,CAAC;AACf,OAAO,EAIL,KAAK,SAAS,EACd,KAAK,SAAS,EACf,MAAM,cAAc,CAAC;AAYtB,OAAO,EAAgB,KAAK,cAAc,EAAE,MAAM,4BAA4B,CAAC;AAG/E,MAAM,MAAM,UAAU,GAAG,MAAM,GAAG,OAAO,GAAG,QAAQ,CAAC;AAgBrD,MAAM,WAAW,eAAgB,SAAQ,cAAc;IACrD,QAAQ,CAAC,EAAE,SAAS,CAAC;CACtB;AAED,iBAAS,IAAI,CAAC,EAAE,QAAQ,EAAE,GAAG,SAAS,EAAE,EAAE,eAAe,+BAKxD;AAED,iBAAS,OAAO,CAAC,EACf,QAAQ,EACR,KAAK,EACN,EAAE;IACD,QAAQ,CAAC,EAAE,SAAS,CAAC;IACrB,KAAK,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;CAC9B,+BAWA;AAED,iBAAS,MAAM,CAAC,EAAE,QAAQ,EAAE,EAAE;IAAE,QAAQ,CAAC,EAAE,SAAS,CAAA;CAAE,+BAErD;AAED,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,EAAE,SAAS,CAAC;IACrB,IAAI,CAAC,EAAE,UAAU,CAAC;IAClB,sCAAsC;IACtC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,KAAK,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;CAC9B;AAgBD,iBAAS,KAAK,CAAC,EACb,QAAQ,EACR,IAAa,EACb,IAAU,EACV,WAAkB,EAClB,KAAK,EACN,EAAE,gBAAgB,+BA0UlB;AAED,iBAAS,KAAK,CAAC,EACb,QAAQ,EACR,KAAK,EACN,EAAE;IACD,QAAQ,CAAC,EAAE,SAAS,CAAC;IACrB,KAAK,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;CAC9B,+BAWA;AAED,eAAO,MAAM,MAAM;;;;;;CAA0C,CAAC"}
@@ -1,6 +1,7 @@
1
1
  import { jsx as _jsx, Fragment as _Fragment } from "react/jsx-runtime";
2
- import { createContext, useContext, useEffect, useRef, useState } from "react";
3
- import { Dimensions, PanResponder, Modal, Pressable } from "react-native";
2
+ import { createContext, useContext, useEffect, useMemo, useState } from "react";
3
+ import { Dimensions, Modal, Pressable } from "react-native";
4
+ import { Gesture, GestureDetector } from "react-native-gesture-handler";
4
5
  import Animated, { runOnJS, useAnimatedStyle, useSharedValue } from "react-native-reanimated";
5
6
  import { useSafeAreaInsets } from "react-native-safe-area-context";
6
7
  import { useReducedMotion } from "../../hooks/useReducedMotion.js";
@@ -79,7 +80,11 @@ function Popup({ children, side = "left", size = 0.8, dismissible = true, style
79
80
  const panelExtent = extent + bleed;
80
81
  // Measured against the bleed panel, so the panel's *visible* edge lands
81
82
  // exactly off-screen rather than the overhang doing it.
82
- const hidden = side === "right" ? panelExtent : side === "left" ? -panelExtent : panelExtent;
83
+ const hidden = side === "right"
84
+ ? panelExtent
85
+ : side === "left"
86
+ ? -panelExtent
87
+ : panelExtent;
83
88
  /**
84
89
  * 700ms on the *tight* curve, and this changed on both platforms at once.
85
90
  *
@@ -150,141 +155,172 @@ function Popup({ children, side = "left", size = 0.8, dismissible = true, style
150
155
  };
151
156
  });
152
157
  /**
153
- * The scrim thins as the panel is dragged away, and does not fade in or out.
158
+ * The scrim fades with the panel, and thins further as it is dragged away.
154
159
  *
155
- * A drawer opens by sliding and nothing about it fades — that is the whole
156
- * distinction from a dialog, and a scrim that fades up alongside the panel
157
- * puts a second, slower animation on top of the one that matters. It is up
158
- * when the drawer is up.
160
+ * Two separate rules, both the web's:
159
161
  *
160
- * The drag term stays, and it is a different thing: pushing the panel off
161
- * lightens the scrim in proportion, so a half-gone drawer does not sit under
162
- * a full-strength one. The web's own rule, in its own words.
162
+ * ```
163
+ * "[opacity:calc(1-var(--drawer-swipe-progress,0))]",
164
+ * "transition-opacity duration-(--gryt-dur-spring-soft) ease-spring-tight",
165
+ * "data-starting-style:opacity-0 data-ending-style:opacity-0",
166
+ * ```
163
167
  *
164
- * The Modal unmounts once the panel has finished travelling, which is what
165
- * takes the scrim with it. Earlier this was a flat colour that vanished with
166
- * the Modal *before* the panel had moved, so a drawer animating out sat under
167
- * a full-strength scrim for 700ms and then blinked off that was the bug,
168
- * and keeping the scrim up for exactly as long as the panel is on screen is
169
- * the fix rather than fading it.
168
+ * The first is the drag term. The other two are the fade: the backdrop
169
+ * starts and ends at zero, over the same duration and easing as the panel's
170
+ * slide. `progress` already runs on `travel`, which is `easeSpringTight` at
171
+ * `springSoft` so multiplying by it is the same curve, not a new one.
172
+ *
173
+ * GRYT-408 took the fade out, on the grounds that "the web's Popup declares
174
+ * `transition-transform` and nothing else". That is true, and it is about
175
+ * `Drawer.Popup` — the panel, which still only translates. The backdrop is a
176
+ * different element and says the opposite. Without the fade the scrim was at
177
+ * full strength before the panel was on screen and stayed there while it slid
178
+ * away, then blinked off with the Modal.
170
179
  */
171
180
  const scrimStyle = useAnimatedStyle(() => {
172
181
  const dragged = extent > 0 ? Math.min(1, Math.abs(drag.value) / extent) : 0;
173
- return { opacity: 1 - dragged };
182
+ return { opacity: progress.value * (1 - dragged) };
174
183
  });
175
184
  /**
176
185
  * Swipe to dismiss, which the web has had from Base UI's drawer primitive
177
186
  * and this did not have at all (GRYT-395). A drawer you cannot push back is
178
187
  * the thing a drawer is for.
179
188
  *
180
- * `PanResponder` rather than react-native-gesture-handler: the Slider proved
181
- * this path works from this package's prebuilt output today, and an attempt
182
- * at gesture-handler from here failed for reasons still not understood
183
- * (GRYT-393). This is not the place to retry that.
189
+ * `react-native-gesture-handler` rather than `PanResponder`. That was the
190
+ * other way round because gesture callbacks from this package's prebuilt
191
+ * output were measured doing nothing (GRYT-393) the babel plugin was not
192
+ * reaching `node_modules`, so nothing became a worklet. Measured again on
193
+ * 2026-08-21 with a probe built inside this package: both worklet and
194
+ * `runOnJS` callbacks report correctly. Whatever it was, it is fixed, and
195
+ * `PanResponder` was only ever the fallback.
196
+ *
197
+ * The reason to move is what the axis constraints below buy. `PanResponder`
198
+ * cannot negotiate with a native scroll recogniser, so the old version kept
199
+ * the drag with `onPanResponderTerminationRequest: () => false` — which
200
+ * works by never handing the gesture back, and stops a `ScrollView` inside
201
+ * the drawer scrolling at all. Here the pan simply fails on a cross-axis
202
+ * drag and the scrollable gets it, which is the same rule the web relies on
203
+ * the browser for.
204
+ *
205
+ * The callbacks stay worklets. Running them on the JS thread would be
206
+ * simpler and would give up the whole point: the panel tracks the finger on
207
+ * the UI thread, with nothing to be blocked by a busy bridge.
184
208
  */
185
- const gesture = useRef({ extent, side, vertical, dismissible, setOpen });
186
- useEffect(() => {
187
- gesture.current = { extent, side, vertical, dismissible, setOpen };
188
- }, [extent, side, vertical, dismissible, setOpen]);
189
- /* eslint-disable react-hooks/refs */
190
- const [responder] = useState(() => PanResponder.create({
191
- // Claimed on move, not on start. Claiming on start would swallow every
192
- // tap on the panel's own content buttons inside a drawer would stop
193
- // working, which is a far worse bug than not being able to swipe.
194
- onStartShouldSetPanResponder: () => false,
195
- onMoveShouldSetPanResponder: (_e, g) => {
196
- if (!gesture.current.dismissible)
197
- return false;
198
- // Only once the drag is clearly along the axis the panel closes on,
199
- // and clearly a drag rather than a slow press. Anything else belongs
200
- // to whatever is inside.
201
- const along = gesture.current.vertical ? g.dy : g.dx;
202
- const across = gesture.current.vertical ? g.dx : g.dy;
203
- return Math.abs(along) > 8 && Math.abs(along) > Math.abs(across);
204
- },
205
- // Once it is ours it stays ours, for the same reason the Slider needs
206
- // this: the default is to grant, and a ScrollView inside the drawer
207
- // would take the drag back mid-swipe.
208
- onPanResponderTerminationRequest: () => false,
209
- onPanResponderMove: (_e, g) => {
210
- const { vertical: v, side: sd } = gesture.current;
211
- const raw = v ? g.dy : g.dx;
209
+ const pan = useMemo(() => {
210
+ const closingSign = side === "left" ? -1 : 1;
211
+ const gesture = Gesture.Pan().enabled(dismissible);
212
+ /* Claim only a drag heading the way the panel closes, and only once it is
213
+ * clearly a drag rather than a slow press — 8pt, the same threshold the
214
+ * old responder used. `failOffset` on the other axis is the half that
215
+ * `PanResponder` had no answer for: a finger moving across the panel is
216
+ * somebody scrolling its contents, and this hands that over rather than
217
+ * swallowing it. */
218
+ if (vertical) {
219
+ gesture.activeOffsetY(8).failOffsetX([-12, 12]);
220
+ }
221
+ else {
222
+ gesture.activeOffsetX(closingSign * 8).failOffsetY([-12, 12]);
223
+ }
224
+ return gesture
225
+ .onUpdate((event) => {
226
+ "worklet";
227
+ const raw = vertical ? event.translationY : event.translationX;
212
228
  // Clamped to the closing direction. Dragging a left drawer rightwards
213
229
  // should do nothing, not tear it off its edge.
214
- const closing = sd === "left" ? Math.min(0, raw) : Math.max(0, raw);
215
- // eslint-disable-next-line react-hooks/immutability
216
- drag.value = closing;
217
- },
218
- onPanResponderRelease: (_e, g) => {
219
- const { vertical: v, side: sd, extent: ex } = gesture.current;
220
- const moved = Math.abs(v ? g.dy : g.dx);
221
- const speed = Math.abs(v ? g.vy : g.vx);
222
- // Half the panel, or a flick. The velocity term is what makes a short
223
- // sharp swipe work — without it you have to drag the whole way, which
224
- // is the difference between a drawer and a slow puzzle.
225
- const dismiss = moved > ex / 2 || speed > 0.5;
226
- if (dismiss) {
227
- gesture.current.setOpen(false);
228
- // Left where it is: the close animation runs from here, and resetting
229
- // it first would snap the panel back before it left.
230
+ drag.value = closingSign < 0 ? Math.min(0, raw) : Math.max(0, raw);
231
+ })
232
+ .onEnd((event) => {
233
+ "worklet";
234
+ const moved = Math.abs(vertical ? event.translationY : event.translationX);
235
+ const speed = Math.abs(vertical ? event.velocityY : event.velocityX);
236
+ /* Half the panel, or a flick. The velocity term is what makes a short
237
+ * sharp swipe work without it you have to drag the whole way, which
238
+ * is the difference between a drawer and a slow puzzle.
239
+ *
240
+ * Gesture-handler reports velocity in points per second where
241
+ * `PanResponder` reported points per millisecond, so the old `0.5`
242
+ * became 500. Same gesture, different unit — worth stating, because
243
+ * carrying the number across unchanged would have made a flick need to
244
+ * be a thousand times faster and looked like the velocity term simply
245
+ * not working.
246
+ */
247
+ if (moved > extent / 2 || speed > 500) {
248
+ // Left where it is: the close animation runs from here, and snapping
249
+ // it back first would show the panel returning before it left.
250
+ runOnJS(setOpen)(false);
230
251
  return;
231
252
  }
232
- // eslint-disable-next-line react-hooks/immutability
233
253
  drag.value = travelTo(0, { duration: durations.springSoft });
234
- },
235
- onPanResponderTerminate: () => {
254
+ })
255
+ .onFinalize((_event, success) => {
256
+ "worklet";
257
+ // A gesture cancelled by the system — an incoming call, a parent
258
+ // taking over — leaves the panel wherever the finger was.
259
+ if (!success)
260
+ drag.value = travelTo(0, { duration: durations.springSoft });
261
+ });
262
+ }, [dismissible, drag, extent, setOpen, side, vertical]);
263
+ /**
264
+ * Any leftover drag goes back as the panel opens, and is cleared once it is
265
+ * gone.
266
+ *
267
+ * Two moments, deliberately. Clearing on `open` going false was wrong: that
268
+ * happens the instant a swipe dismisses, so the panel jumped back to fully
269
+ * open and then slid out — exactly what the release handler above says must
270
+ * not happen (GRYT-429). `mounted` is the moment the panel is actually off
271
+ * screen, where a hard reset costs nothing to look at.
272
+ */
273
+ useEffect(() => {
274
+ if (open) {
236
275
  // eslint-disable-next-line react-hooks/immutability
237
276
  drag.value = travelTo(0, { duration: durations.springSoft });
238
277
  }
239
- }));
240
- /* eslint-enable react-hooks/refs */
241
- // Cleared once the panel is closed, so the next open does not start from
242
- // wherever the last swipe left it.
278
+ }, [open, drag]);
243
279
  useEffect(() => {
244
- if (!open) {
280
+ if (!mounted) {
245
281
  // eslint-disable-next-line react-hooks/immutability
246
282
  drag.value = 0;
247
283
  }
248
- }, [open, drag]);
249
- return (_jsx(Modal, { visible: mounted, transparent: true, animationType: "none", onRequestClose: dismissible ? () => setOpen(false) : undefined, children: _jsx(AnimatedPressable, { onPress: dismissible ? () => setOpen(false) : undefined, style: [{ flex: 1, backgroundColor: "rgba(0,0,0,0.6)" }, scrimStyle], children: _jsx(Animated.View, { accessibilityViewIsModal: true, ...responder.panHandlers, style: [
250
- {
251
- position: "absolute",
252
- // The overhang hangs *off* the edge the panel comes from, which
253
- // is the only direction that helps. It used to be added to the
254
- // width instead, so the panel grew inwards — 64pt more of the
255
- // screen covered, and the seam it was meant to hide still there,
256
- // because the gap opens on the entering edge and that is the edge
257
- // the extra material was not on.
258
- top: side === "bottom" ? undefined : 0,
259
- bottom: vertical ? -bleed : 0,
260
- left: side === "right" ? undefined : side === "left" ? -bleed : 0,
261
- right: side === "left" ? undefined : side === "right" ? -bleed : 0,
262
- width: vertical ? "100%" : panelExtent,
263
- height: vertical ? panelExtent : "100%",
264
- // Puts the content back where it would have been without the
265
- // overhang, so `size` still means what it says.
266
- paddingLeft: side === "left" ? bleed : 0,
267
- paddingRight: side === "right" ? bleed : 0,
268
- paddingBottom: vertical ? bleed : 0,
269
- backgroundColor: theme.color.surface,
270
- borderColor: theme.color.border,
271
- borderRightWidth: side === "left" ? 1 : 0,
272
- borderLeftWidth: side === "right" ? 1 : 0,
273
- borderTopWidth: vertical ? 1 : 0,
274
- borderTopLeftRadius: vertical ? theme.radius.lg : 0,
275
- borderTopRightRadius: vertical ? theme.radius.lg : 0
276
- },
277
- panelStyle
278
- ], children: _jsx(Pressable, { onPress: () => { }, style: [
284
+ }, [mounted, drag]);
285
+ return (_jsx(Modal, { visible: mounted, transparent: true, animationType: "none", onRequestClose: dismissible ? () => setOpen(false) : undefined, children: _jsx(AnimatedPressable, { onPress: dismissible ? () => setOpen(false) : undefined, style: [{ flex: 1, backgroundColor: "rgba(0,0,0,0.6)" }, scrimStyle], children: _jsx(GestureDetector, { gesture: pan, children: _jsx(Animated.View, { accessibilityViewIsModal: true, style: [
279
286
  {
280
- flex: 1,
281
- // Before the caller's style, so a drawer that wants to run
282
- // under the island a full-bleed image, say still can.
283
- paddingTop: vertical ? 0 : insets.top,
284
- paddingBottom: insets.bottom
287
+ position: "absolute",
288
+ // The overhang hangs *off* the edge the panel comes from, which
289
+ // is the only direction that helps. It used to be added to the
290
+ // width instead, so the panel grew inwards — 64pt more of the
291
+ // screen covered, and the seam it was meant to hide still there,
292
+ // because the gap opens on the entering edge and that is the edge
293
+ // the extra material was not on.
294
+ top: side === "bottom" ? undefined : 0,
295
+ bottom: vertical ? -bleed : 0,
296
+ left: side === "right" ? undefined : side === "left" ? -bleed : 0,
297
+ right: side === "left" ? undefined : side === "right" ? -bleed : 0,
298
+ width: vertical ? "100%" : panelExtent,
299
+ height: vertical ? panelExtent : "100%",
300
+ // Puts the content back where it would have been without the
301
+ // overhang, so `size` still means what it says.
302
+ paddingLeft: side === "left" ? bleed : 0,
303
+ paddingRight: side === "right" ? bleed : 0,
304
+ paddingBottom: vertical ? bleed : 0,
305
+ backgroundColor: theme.color.surface,
306
+ borderColor: theme.color.border,
307
+ borderRightWidth: side === "left" ? 1 : 0,
308
+ borderLeftWidth: side === "right" ? 1 : 0,
309
+ borderTopWidth: vertical ? 1 : 0,
310
+ borderTopLeftRadius: vertical ? theme.radius.lg : 0,
311
+ borderTopRightRadius: vertical ? theme.radius.lg : 0
285
312
  },
286
- style
287
- ], children: children }) }) }) }));
313
+ panelStyle
314
+ ], children: _jsx(Pressable, { onPress: () => { }, style: [
315
+ {
316
+ flex: 1,
317
+ // Before the caller's style, so a drawer that wants to run
318
+ // under the island — a full-bleed image, say — still can.
319
+ paddingTop: vertical ? 0 : insets.top,
320
+ paddingBottom: insets.bottom
321
+ },
322
+ style
323
+ ], children: children }) }) }) }) }));
288
324
  }
289
325
  function Close({ children, style }) {
290
326
  const { setOpen } = useDrawer("Close");
@@ -15,10 +15,15 @@ export interface ScrollAreaProps extends Omit<ScrollViewProps, "style"> {
15
15
  * device. So the component stays for call-site parity and does almost nothing,
16
16
  * which is the honest version.
17
17
  *
18
- * Its one job was defaulting the indicator off, since the web version hides the
19
- * native scrollbar too. It has a second now: it publishes a drag lock, so a
20
- * control inside it can hold it still while it is being dragged. See
21
- * internal/dragLock a Slider is unusable in a scrolling screen without it.
18
+ * Its one job is defaulting the indicator off, since the web version hides the
19
+ * native scrollbar too.
20
+ *
21
+ * It used to have a second: publishing a drag lock that a Slider could claim to
22
+ * switch this list off for the duration of a drag. That existed because
23
+ * `PanResponder` could not tell the native scroll recogniser anything, so the
24
+ * only way to stop the list moving under a slider was to disable it. The Slider
25
+ * declares `activeOffsetX` now and the two gestures settle it between them, so
26
+ * there is nothing to hold still and nothing to leak if a drag is interrupted.
22
27
  */
23
- export declare function ScrollArea({ children, horizontal, style, contentStyle, showsVerticalScrollIndicator, showsHorizontalScrollIndicator, scrollEnabled, ...rest }: ScrollAreaProps): import("react").JSX.Element;
28
+ export declare function ScrollArea({ children, horizontal, style, contentStyle, showsVerticalScrollIndicator, showsHorizontalScrollIndicator, ...rest }: ScrollAreaProps): import("react").JSX.Element;
24
29
  //# sourceMappingURL=ScrollArea.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"ScrollArea.d.ts","sourceRoot":"","sources":["../../../src/components/ScrollArea/ScrollArea.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAkC,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AACvE,OAAO,EAAc,KAAK,eAAe,EAAE,KAAK,SAAS,EAAE,KAAK,SAAS,EAAE,MAAM,cAAc,CAAC;AAIhG,MAAM,WAAW,eAAgB,SAAQ,IAAI,CAAC,eAAe,EAAE,OAAO,CAAC;IACrE,QAAQ,CAAC,EAAE,SAAS,CAAC;IACrB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,KAAK,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;IAC7B,YAAY,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;CACrC;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,UAAU,CAAC,EACzB,QAAQ,EACR,UAAkB,EAClB,KAAK,EACL,YAAY,EACZ,4BAAoC,EACpC,8BAAsC,EACtC,aAAa,EACb,GAAG,IAAI,EACR,EAAE,eAAe,+BAgCjB"}
1
+ {"version":3,"file":"ScrollArea.d.ts","sourceRoot":"","sources":["../../../src/components/ScrollArea/ScrollArea.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AACvC,OAAO,EAAc,KAAK,eAAe,EAAE,KAAK,SAAS,EAAE,KAAK,SAAS,EAAE,MAAM,cAAc,CAAC;AAEhG,MAAM,WAAW,eAAgB,SAAQ,IAAI,CAAC,eAAe,EAAE,OAAO,CAAC;IACrE,QAAQ,CAAC,EAAE,SAAS,CAAC;IACrB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,KAAK,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;IAC7B,YAAY,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;CACrC;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,UAAU,CAAC,EACzB,QAAQ,EACR,UAAkB,EAClB,KAAK,EACL,YAAY,EACZ,4BAAoC,EACpC,8BAAsC,EACtC,GAAG,IAAI,EACR,EAAE,eAAe,+BAajB"}
@@ -1,7 +1,5 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
- import { useCallback, useMemo, useState } from "react";
3
2
  import { ScrollView } from "react-native";
4
- import { DragLockContext } from "../internal/dragLock.js";
5
3
  /**
6
4
  * A thin pass over ScrollView.
7
5
  *
@@ -11,22 +9,16 @@ import { DragLockContext } from "../internal/dragLock.js";
11
9
  * device. So the component stays for call-site parity and does almost nothing,
12
10
  * which is the honest version.
13
11
  *
14
- * Its one job was defaulting the indicator off, since the web version hides the
15
- * native scrollbar too. It has a second now: it publishes a drag lock, so a
16
- * control inside it can hold it still while it is being dragged. See
17
- * internal/dragLock a Slider is unusable in a scrolling screen without it.
12
+ * Its one job is defaulting the indicator off, since the web version hides the
13
+ * native scrollbar too.
14
+ *
15
+ * It used to have a second: publishing a drag lock that a Slider could claim to
16
+ * switch this list off for the duration of a drag. That existed because
17
+ * `PanResponder` could not tell the native scroll recogniser anything, so the
18
+ * only way to stop the list moving under a slider was to disable it. The Slider
19
+ * declares `activeOffsetX` now and the two gestures settle it between them, so
20
+ * there is nothing to hold still and nothing to leak if a drag is interrupted.
18
21
  */
19
- export function ScrollArea({ children, horizontal = false, style, contentStyle, showsVerticalScrollIndicator = false, showsHorizontalScrollIndicator = false, scrollEnabled, ...rest }) {
20
- const [locks, setLocks] = useState(0);
21
- // Counted rather than a boolean. Two controls can overlap in principle — a
22
- // drag interrupted by a second one starting — and the first to finish would
23
- // otherwise unlock the list while the second is still going.
24
- const lock = useCallback(() => setLocks((n) => n + 1), []);
25
- const unlock = useCallback(() => setLocks((n) => Math.max(0, n - 1)), []);
26
- const dragLock = useMemo(() => ({ locked: locks > 0, lock, unlock }), [locks, lock, unlock]);
27
- return (_jsx(DragLockContext.Provider, { value: dragLock, children: _jsx(ScrollView, { horizontal: horizontal, style: style, contentContainerStyle: contentStyle, showsVerticalScrollIndicator: showsVerticalScrollIndicator, showsHorizontalScrollIndicator: showsHorizontalScrollIndicator,
28
- // Only overridden while something is actually dragging, so a caller
29
- // that passes `scrollEnabled={false}` still gets a list that never
30
- // scrolls.
31
- scrollEnabled: scrollEnabled === false ? false : locks === 0, ...rest, children: children }) }));
22
+ export function ScrollArea({ children, horizontal = false, style, contentStyle, showsVerticalScrollIndicator = false, showsHorizontalScrollIndicator = false, ...rest }) {
23
+ return (_jsx(ScrollView, { horizontal: horizontal, style: style, contentContainerStyle: contentStyle, showsVerticalScrollIndicator: showsVerticalScrollIndicator, showsHorizontalScrollIndicator: showsHorizontalScrollIndicator, ...rest, children: children }));
32
24
  }
@@ -18,12 +18,23 @@ export interface SliderProps {
18
18
  /**
19
19
  * Dragging, on a surface where a drag might belong to something else.
20
20
  *
21
- * The web gets pointer capture: once the thumb is grabbed, every move belongs to
22
- * the slider until release. React Native has no equivalent, so this claims the
23
- * gesture through PanResponder and holds it, which is what stops a horizontal
24
- * drag being read as a scroll by whatever list the slider is sitting in.
21
+ * The web gets pointer capture: once the thumb is grabbed, every move belongs
22
+ * to the slider until release. React Native has no equivalent, and volume
23
+ * sliders live in settings lists, so a horizontal drag has to be told apart
24
+ * from the scroll it is sitting in.
25
25
  *
26
- * Volume sliders live in scroll views, so this matters more here than it looks.
26
+ * `activeOffsetX` is that distinction, declared rather than fought over. The
27
+ * pan claims the gesture once the finger has clearly gone sideways, and a
28
+ * vertical drag never activates it at all, so the scroll view keeps it. That
29
+ * replaces `onPanResponderTerminationRequest: () => false` plus the `DragLock`
30
+ * that had to switch the scroll view off, because refusing to hand back a JS
31
+ * responder says nothing to the native recogniser that actually does the
32
+ * scrolling on iOS.
33
+ *
34
+ * Both gestures run on the JS thread. The Drawer's stay worklets because they
35
+ * only move a shared value; every callback here has to reach React state and
36
+ * the consumer's `onValueChange`, so the hop is happening either way and
37
+ * pretending otherwise would only add a way to get it wrong.
27
38
  */
28
- export declare function Slider({ value: controlled, defaultValue, onValueChange, onValueCommit, min, max, step, disabled, tone, style, accessibilityLabel, }: SliderProps): import("react").JSX.Element;
39
+ export declare function Slider({ value: controlled, defaultValue, onValueChange, onValueCommit, min, max, step, disabled, tone, style, accessibilityLabel }: SliderProps): import("react").JSX.Element;
29
40
  //# sourceMappingURL=Slider.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"Slider.d.ts","sourceRoot":"","sources":["../../../src/components/Slider/Slider.tsx"],"names":[],"mappings":"AACA,OAAO,EAIL,KAAK,SAAS,EACd,KAAK,SAAS,EACf,MAAM,cAAc,CAAC;AAMtB,OAAO,EAAsB,KAAK,aAAa,EAAE,MAAM,aAAa,CAAC;AAIrE,MAAM,MAAM,UAAU,GAAG,OAAO,CAAC,aAAa,EAAE,SAAS,GAAG,WAAW,GAAG,SAAS,GAAG,QAAQ,CAAC,CAAC;AAEhG,MAAM,WAAW,WAAW;IAC1B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACxC,+DAA+D;IAC/D,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACxC,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,IAAI,CAAC,EAAE,UAAU,CAAC;IAClB,KAAK,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;IAC7B,kBAAkB,CAAC,EAAE,MAAM,CAAC;CAC7B;AAKD;;;;;;;;;GASG;AACH,wBAAgB,MAAM,CAAC,EACrB,KAAK,EAAE,UAAU,EACjB,YAAgB,EAChB,aAAa,EACb,aAAa,EACb,GAAO,EACP,GAAS,EACT,IAAQ,EACR,QAAQ,EACR,IAAgB,EAChB,KAAK,EACL,kBAAkB,GACnB,EAAE,WAAW,+BAuKb"}
1
+ {"version":3,"file":"Slider.d.ts","sourceRoot":"","sources":["../../../src/components/Slider/Slider.tsx"],"names":[],"mappings":"AACA,OAAO,EAGL,KAAK,SAAS,EACd,KAAK,SAAS,EACf,MAAM,cAAc,CAAC;AAUtB,OAAO,EAAsB,KAAK,aAAa,EAAE,MAAM,aAAa,CAAC;AAGrE,MAAM,MAAM,UAAU,GAAG,OAAO,CAC9B,aAAa,EACb,SAAS,GAAG,WAAW,GAAG,SAAS,GAAG,QAAQ,CAC/C,CAAC;AAEF,MAAM,WAAW,WAAW;IAC1B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACxC,+DAA+D;IAC/D,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACxC,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,IAAI,CAAC,EAAE,UAAU,CAAC;IAClB,KAAK,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;IAC7B,kBAAkB,CAAC,EAAE,MAAM,CAAC;CAC7B;AAKD;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,MAAM,CAAC,EACrB,KAAK,EAAE,UAAU,EACjB,YAAgB,EAChB,aAAa,EACb,aAAa,EACb,GAAO,EACP,GAAS,EACT,IAAQ,EACR,QAAQ,EACR,IAAgB,EAChB,KAAK,EACL,kBAAkB,EACnB,EAAE,WAAW,+BA4Jb"}
@@ -1,29 +1,37 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { useEffect, useRef, useState } from "react";
3
- import { PanResponder, View, } from "react-native";
2
+ import { useEffect, useMemo, useRef, useState } from "react";
3
+ import { View } from "react-native";
4
+ import { Gesture, GestureDetector } from "react-native-gesture-handler";
4
5
  import Animated, { useAnimatedStyle, useSharedValue } from "react-native-reanimated";
5
6
  import { grytScaleSteps } from "@gryt/theme";
6
7
  import { springy } from "../../motion/index.js";
7
8
  import { toneRamp, useTheme } from "../../theme/index.js";
8
- import { useDragLock } from "../internal/dragLock.js";
9
9
  import { valueAt } from "./sliderValue.js";
10
10
  const TRACK_HEIGHT = 4;
11
11
  const THUMB = 20;
12
12
  /**
13
13
  * Dragging, on a surface where a drag might belong to something else.
14
14
  *
15
- * The web gets pointer capture: once the thumb is grabbed, every move belongs to
16
- * the slider until release. React Native has no equivalent, so this claims the
17
- * gesture through PanResponder and holds it, which is what stops a horizontal
18
- * drag being read as a scroll by whatever list the slider is sitting in.
15
+ * The web gets pointer capture: once the thumb is grabbed, every move belongs
16
+ * to the slider until release. React Native has no equivalent, and volume
17
+ * sliders live in settings lists, so a horizontal drag has to be told apart
18
+ * from the scroll it is sitting in.
19
19
  *
20
- * Volume sliders live in scroll views, so this matters more here than it looks.
20
+ * `activeOffsetX` is that distinction, declared rather than fought over. The
21
+ * pan claims the gesture once the finger has clearly gone sideways, and a
22
+ * vertical drag never activates it at all, so the scroll view keeps it. That
23
+ * replaces `onPanResponderTerminationRequest: () => false` plus the `DragLock`
24
+ * that had to switch the scroll view off, because refusing to hand back a JS
25
+ * responder says nothing to the native recogniser that actually does the
26
+ * scrolling on iOS.
27
+ *
28
+ * Both gestures run on the JS thread. The Drawer's stay worklets because they
29
+ * only move a shared value; every callback here has to reach React state and
30
+ * the consumer's `onValueChange`, so the hop is happening either way and
31
+ * pretending otherwise would only add a way to get it wrong.
21
32
  */
22
- export function Slider({ value: controlled, defaultValue = 0, onValueChange, onValueCommit, min = 0, max = 100, step = 1, disabled, tone = "primary", style, accessibilityLabel, }) {
33
+ export function Slider({ value: controlled, defaultValue = 0, onValueChange, onValueCommit, min = 0, max = 100, step = 1, disabled, tone = "primary", style, accessibilityLabel }) {
23
34
  const theme = useTheme();
24
- // Held for the duration of a drag, so the list this is sitting in stays put.
25
- // A no-op outside a ScrollView from this package — see useDragLock.
26
- const dragLock = useDragLock();
27
35
  const [uncontrolled, setUncontrolled] = useState(defaultValue);
28
36
  const value = controlled ?? uncontrolled;
29
37
  const [width, setWidth] = useState(0);
@@ -35,11 +43,6 @@ export function Slider({ value: controlled, defaultValue = 0, onValueChange, onV
35
43
  // fire from touch events, which is always after an effect has run.
36
44
  const state = useRef({ width, value, min, max, step, disabled });
37
45
  const emit = useRef({ onValueChange, onValueCommit, controlled });
38
- // Same reason as the others: the responder callbacks are created once, and
39
- // the lock's identity changes whenever the count does.
40
- const lockRef = useRef(dragLock);
41
- /** Where the current gesture started, in track coordinates. */
42
- const origin = useRef(0);
43
46
  /**
44
47
  * `active:scale-[0.94]` on the web. `hover:scale-[1.12]` has no touch
45
48
  * equivalent, so only the press half ports.
@@ -54,9 +57,6 @@ export function Slider({ value: controlled, defaultValue = 0, onValueChange, onV
54
57
  useEffect(() => {
55
58
  emit.current = { onValueChange, onValueCommit, controlled };
56
59
  }, [onValueChange, onValueCommit, controlled]);
57
- useEffect(() => {
58
- lockRef.current = dragLock;
59
- }, [dragLock]);
60
60
  const valueFromX = (x) => {
61
61
  const s = state.current;
62
62
  // Before layout there is no position to read, so hold what we have rather
@@ -70,90 +70,89 @@ export function Slider({ value: controlled, defaultValue = 0, onValueChange, onV
70
70
  setUncontrolled(next);
71
71
  emit.current.onValueChange?.(next);
72
72
  };
73
- // useState with an initialiser rather than useRef(...).current, which counts
74
- // as reading a ref during render. Created once either way.
75
- //
76
- // The rule is disabled for the block below because it reads what the closures
77
- // mention, not what runs. Every state.current here executes inside a touch
78
- // callback, long after render, and none of it decides what is drawn the
79
- // rendered position comes from `value` and `width` directly. The thing the
80
- // rule protects against, a component rendering from a ref and not updating,
81
- // cannot happen here.
82
- /* eslint-disable react-hooks/refs */
83
- const [responder] = useState(() => PanResponder.create({
84
- onStartShouldSetPanResponder: () => !state.current.disabled,
85
- // Claimed on move as well, so a drag that begins as a scroll still lands
86
- // here once it is clearly horizontal.
87
- onMoveShouldSetPanResponder: () => !state.current.disabled,
88
- onPanResponderGrant: (e) => {
89
- lockRef.current.lock();
90
- // Where the finger landed, kept for the drag to offset from.
91
- origin.current = e.nativeEvent.locationX;
73
+ /**
74
+ * Tap to seek, drag to scrub, and nothing at all if the finger goes down the
75
+ * page.
76
+ *
77
+ * Two gestures rather than one because they want opposite thresholds. A tap
78
+ * has to work with no movement; a drag must not claim anything until it is
79
+ * clearly sideways, or every attempt to scroll past a slider moves it. `Race`
80
+ * lets whichever qualifies win, and only one ever does.
81
+ */
82
+ const gesture = useMemo(() => {
83
+ const seek = (x) => {
84
+ const s = state.current;
85
+ if (s.disabled)
86
+ return;
87
+ // Absolute position in the track, not accumulated translation.
88
+ //
89
+ // The old version anchored on where the gesture started and added
90
+ // `gesture.dx`, which is the distance from the *start* rather than from
91
+ // the last event — so offsetting the live value by it added the whole
92
+ // travel again every move and the thumb accelerated away from the
93
+ // finger. Reading the position directly cannot express that bug.
94
+ apply(valueFromX(x));
95
+ };
96
+ const pan = Gesture.Pan()
97
+ .runOnJS(true)
98
+ .enabled(!disabled)
99
+ // Sideways, and clearly so. Below this the scroll view keeps the gesture,
100
+ // which is the whole reason the lock is gone.
101
+ .activeOffsetX([-4, 4])
102
+ .onBegin(() => {
103
+ // On touch down rather than on activation: the press feedback should
104
+ // answer the finger, not wait to find out where it is going.
92
105
  thumbScale.value = springy(grytScaleSteps.sliderThumb.press);
93
- apply(valueFromX(e.nativeEvent.locationX));
94
- },
95
- // `gesture.dx` is the distance from where the gesture *started*, not from
96
- // the previous event. Offsetting the live value by it therefore adds the
97
- // whole travel again on every move, and the thumb accelerates away from
98
- // the finger — 200px wide, 0-100, dragging to x=50 then 60 then 70 gave
99
- // 25, then 55, then 90. Tapping was always fine, because grant uses
100
- // locationX directly, which is what made it look like a rendering
101
- // problem rather than an arithmetic one.
102
- //
103
- // Anchoring on the grant position is also what makes the drag continue
104
- // from the finger: grant has already seeked the thumb there.
105
- onPanResponderMove: (_e, gesture) => {
106
- apply(valueFromX(origin.current + gesture.dx));
107
- },
108
- // The fix for GRYT-390's "the slider still lets me scroll the page".
109
- //
110
- // The comment above says this "claims the gesture and holds it". It did
111
- // not: `onPanResponderTerminationRequest` defaults to *granting*, so the
112
- // enclosing ScrollView asked for the responder as soon as the finger
113
- // moved and got it. Every drag inside a scrolling screen scrolled the
114
- // screen, which is every drag — volume sliders live in settings lists.
115
- //
116
- // Saying no is the whole fix. `onShouldBlockNativeResponder` stops the
117
- // native scroll view taking over underneath on Android, where the JS
118
- // answer alone is not enough.
119
- onPanResponderTerminationRequest: () => false,
120
- // A refused termination can still happen — the system can take the
121
- // responder away regardless, on a call or a notification. Without this
122
- // the lock leaks and the screen never scrolls again, which is a far
123
- // worse bug than the one being fixed.
124
- onPanResponderTerminate: () => {
125
- lockRef.current.unlock();
126
- thumbScale.value = springy(1);
127
- },
128
- onShouldBlockNativeResponder: () => true,
129
- onPanResponderRelease: () => {
130
- lockRef.current.unlock();
106
+ })
107
+ .onStart((event) => seek(event.x))
108
+ .onUpdate((event) => seek(event.x))
109
+ .onEnd(() => emit.current.onValueCommit?.(state.current.value))
110
+ .onFinalize(() => {
131
111
  thumbScale.value = springy(1);
112
+ });
113
+ const tap = Gesture.Tap()
114
+ .runOnJS(true)
115
+ .enabled(!disabled)
116
+ .onEnd((event, success) => {
117
+ if (!success)
118
+ return;
119
+ seek(event.x);
132
120
  emit.current.onValueCommit?.(state.current.value);
133
- },
134
- }));
135
- /* eslint-enable react-hooks/refs */
121
+ });
122
+ return Gesture.Race(pan, tap);
123
+ // `apply` and `valueFromX` read refs that are written in effects, so they
124
+ // do not need to be dependencies — and listing them would rebuild the
125
+ // gesture on every render.
126
+ // eslint-disable-next-line react-hooks/exhaustive-deps
127
+ }, [disabled, thumbScale]);
136
128
  const ratio = (value - min) / (max - min || 1);
137
- return (_jsxs(View, { accessibilityRole: "adjustable", accessibilityLabel: accessibilityLabel, accessibilityValue: { min, max, now: value }, onLayout: (e) => setWidth(e.nativeEvent.layout.width), style: [{ height: THUMB, justifyContent: "center", opacity: disabled ? 0.5 : 1 }, style], ...responder.panHandlers, children: [_jsx(View, { style: {
138
- height: TRACK_HEIGHT,
139
- borderRadius: TRACK_HEIGHT / 2,
140
- backgroundColor: theme.scales.neutral[4],
141
- }, children: _jsx(View, { style: {
142
- width: `${ratio * 100}%`,
143
- height: "100%",
129
+ return (_jsx(GestureDetector, { gesture: gesture, children: _jsxs(View, { accessibilityRole: "adjustable", accessibilityLabel: accessibilityLabel, accessibilityValue: { min, max, now: value }, onLayout: (e) => setWidth(e.nativeEvent.layout.width), style: [
130
+ {
131
+ height: THUMB,
132
+ justifyContent: "center",
133
+ opacity: disabled ? 0.5 : 1
134
+ },
135
+ style
136
+ ], children: [_jsx(View, { style: {
137
+ height: TRACK_HEIGHT,
144
138
  borderRadius: TRACK_HEIGHT / 2,
145
- backgroundColor: ramp[8],
146
- } }) }), _jsx(Animated.View, { pointerEvents: "none", style: [
147
- {
148
- position: "absolute",
149
- left: Math.max(0, ratio * width - THUMB / 2),
150
- width: THUMB,
151
- height: THUMB,
152
- borderRadius: THUMB / 2,
153
- backgroundColor: ramp[8],
154
- borderWidth: 2,
155
- borderColor: theme.color.bg,
156
- },
157
- thumbStyle,
158
- ] })] }));
139
+ backgroundColor: theme.scales.neutral[4]
140
+ }, children: _jsx(View, { style: {
141
+ width: `${ratio * 100}%`,
142
+ height: "100%",
143
+ borderRadius: TRACK_HEIGHT / 2,
144
+ backgroundColor: ramp[8]
145
+ } }) }), _jsx(Animated.View, { pointerEvents: "none", style: [
146
+ {
147
+ position: "absolute",
148
+ left: Math.max(0, ratio * width - THUMB / 2),
149
+ width: THUMB,
150
+ height: THUMB,
151
+ borderRadius: THUMB / 2,
152
+ backgroundColor: ramp[8],
153
+ borderWidth: 2,
154
+ borderColor: theme.color.bg
155
+ },
156
+ thumbStyle
157
+ ] })] }) }));
159
158
  }
package/dist/index.d.ts CHANGED
@@ -31,7 +31,6 @@ export { NumberField, type NumberFieldProps } from "./components/NumberField/Num
31
31
  export { OtpField, type OtpFieldProps } from "./components/OtpField/OtpField.js";
32
32
  export { ScrollArea, type ScrollAreaProps } from "./components/ScrollArea/ScrollArea.js";
33
33
  export { Sheet, type SheetCloseProps, type SheetContentProps, type SheetProps, SheetProvider, type SheetProviderProps, type SheetTitleProps, type SheetTriggerProps, } from "./components/Sheet/Sheet.js";
34
- export { useDragLock, type DragLock } from "./components/internal/dragLock.js";
35
34
  export { ToastProvider, useToast, type ToastOptions, type ToastSeverity, } from "./components/Toast/Toast.js";
36
35
  export { Toolbar, ToolbarSeparator, type ToolbarProps, type ToolbarSeparatorProps, } from "./components/Toolbar/Toolbar.js";
37
36
  export { AnchoredPopup, type AnchoredPopupProps } from "./overlay/AnchoredPopup.js";
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
package/dist/index.js CHANGED
@@ -31,7 +31,6 @@ export { NumberField } from "./components/NumberField/NumberField.js";
31
31
  export { OtpField } from "./components/OtpField/OtpField.js";
32
32
  export { ScrollArea } from "./components/ScrollArea/ScrollArea.js";
33
33
  export { Sheet, SheetProvider, } from "./components/Sheet/Sheet.js";
34
- export { useDragLock } from "./components/internal/dragLock.js";
35
34
  export { ToastProvider, useToast, } from "./components/Toast/Toast.js";
36
35
  export { Toolbar, ToolbarSeparator, } from "./components/Toolbar/Toolbar.js";
37
36
  export { AnchoredPopup } from "./overlay/AnchoredPopup.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gryt/ui-native",
3
- "version": "0.6.0",
3
+ "version": "0.7.0",
4
4
  "type": "module",
5
5
  "license": "MIT",
6
6
  "description": "Gryt's design system on React Native. Same tokens as @gryt/ui, a different renderer.",
@@ -1,43 +0,0 @@
1
- /**
2
- * A control holding a scrolling ancestor still while it is being dragged.
3
- *
4
- * This exists because of one bug, and the obvious fixes all fail in ways that
5
- * look like they worked, so it is worth writing down.
6
- *
7
- * Dragging a `Slider` inside a scrolling screen scrolled the screen. The first
8
- * fix — `onPanResponderTerminationRequest: () => false` — was necessary and not
9
- * sufficient: it stops the ScrollView taking the responder away, so the thumb
10
- * follows your finger now, and the page still scrolls underneath. On iOS the
11
- * scroll is driven by a native `UIPanGestureRecognizer` that is not part of
12
- * React Native's JS responder system, so refusing to hand over the responder
13
- * answers a question the native recogniser never asked.
14
- * `onShouldBlockNativeResponder` is the knob for that, and it is Android-only.
15
- *
16
- * What works is `scrollEnabled` — and the control cannot reach the scroll view
17
- * to set it, since they are separated by however much app markup. So the scroll
18
- * view publishes a lock, the control claims it, and neither has to know where
19
- * the other is.
20
- *
21
- * `react-native-gesture-handler` solves this properly, by negotiating with the
22
- * native recogniser rather than switching it off. That is the better fix and it
23
- * is blocked on GRYT-393: this package ships prebuilt JavaScript, Reanimated's
24
- * babel plugin does not run over `node_modules`, and gesture callbacks are
25
- * therefore never worklets. Measured on a device, not assumed. When that is
26
- * solved, this should probably go.
27
- */
28
- export interface DragLock {
29
- locked: boolean;
30
- lock: () => void;
31
- unlock: () => void;
32
- }
33
- export declare const DragLockContext: import("react").Context<DragLock | null>;
34
- /**
35
- * Claimed by a control for the duration of a drag.
36
- *
37
- * No-ops outside a `ScrollArea`, which is the normal case for a control that is
38
- * not in a scrolling screen. A control must not require the provider: a slider
39
- * in a fixed panel has nothing to lock, and throwing there would be inventing a
40
- * dependency on a component it does not need.
41
- */
42
- export declare function useDragLock(): DragLock;
43
- //# sourceMappingURL=dragLock.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"dragLock.d.ts","sourceRoot":"","sources":["../../../src/components/internal/dragLock.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,MAAM,WAAW,QAAQ;IACvB,MAAM,EAAE,OAAO,CAAC;IAChB,IAAI,EAAE,MAAM,IAAI,CAAC;IACjB,MAAM,EAAE,MAAM,IAAI,CAAC;CACpB;AAED,eAAO,MAAM,eAAe,0CAAuC,CAAC;AAQpE;;;;;;;GAOG;AACH,wBAAgB,WAAW,IAAI,QAAQ,CAEtC"}
@@ -1,18 +0,0 @@
1
- import { createContext, useContext } from "react";
2
- export const DragLockContext = createContext(null);
3
- const NO_LOCK = {
4
- locked: false,
5
- lock: () => { },
6
- unlock: () => { },
7
- };
8
- /**
9
- * Claimed by a control for the duration of a drag.
10
- *
11
- * No-ops outside a `ScrollArea`, which is the normal case for a control that is
12
- * not in a scrolling screen. A control must not require the provider: a slider
13
- * in a fixed panel has nothing to lock, and throwing there would be inventing a
14
- * dependency on a component it does not need.
15
- */
16
- export function useDragLock() {
17
- return useContext(DragLockContext) ?? NO_LOCK;
18
- }