@hanzogui/native 7.3.0 → 8.0.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 (156) hide show
  1. package/.turbo/turbo-build.log +2 -0
  2. package/dist/cjs/components.cjs +3 -3
  3. package/dist/cjs/components.native.js +3 -3
  4. package/dist/cjs/components.native.js.map +1 -1
  5. package/dist/cjs/gesture-handler.cjs +1 -0
  6. package/dist/cjs/gesture-handler.native.js +1 -0
  7. package/dist/cjs/gesture-handler.native.js.map +1 -1
  8. package/dist/cjs/gestureState.cjs +91 -20
  9. package/dist/cjs/gestureState.native.js +97 -20
  10. package/dist/cjs/gestureState.native.js.map +1 -1
  11. package/dist/cjs/globalState.cjs +1 -1
  12. package/dist/cjs/globalState.native.js +1 -1
  13. package/dist/cjs/globalState.native.js.map +1 -1
  14. package/dist/cjs/index.cjs +1 -0
  15. package/dist/cjs/index.native.js +1 -0
  16. package/dist/cjs/index.native.js.map +1 -1
  17. package/dist/cjs/setup-burnt.cjs +2 -2
  18. package/dist/cjs/setup-burnt.native.js +2 -2
  19. package/dist/cjs/setup-burnt.native.js.map +1 -1
  20. package/dist/cjs/setup-expo-linear-gradient.cjs +2 -2
  21. package/dist/cjs/setup-expo-linear-gradient.native.js +2 -2
  22. package/dist/cjs/setup-expo-linear-gradient.native.js.map +1 -1
  23. package/dist/cjs/setup-gesture-handler.cjs +15 -6
  24. package/dist/cjs/setup-gesture-handler.native.js +15 -6
  25. package/dist/cjs/setup-gesture-handler.native.js.map +1 -1
  26. package/dist/cjs/setup-keyboard-controller.native.js +2 -2
  27. package/dist/cjs/setup-safe-area.native.js +2 -2
  28. package/dist/cjs/setup-safe-area.native.js.map +1 -1
  29. package/dist/cjs/setup-teleport.cjs +3 -3
  30. package/dist/cjs/setup-teleport.native.js +3 -3
  31. package/dist/cjs/setup-teleport.native.js.map +1 -1
  32. package/dist/cjs/setup-worklets.cjs +2 -2
  33. package/dist/cjs/setup-worklets.native.js +2 -2
  34. package/dist/cjs/setup-worklets.native.js.map +1 -1
  35. package/dist/cjs/setup-zeego.cjs +2 -2
  36. package/dist/cjs/setup-zeego.native.js +2 -2
  37. package/dist/cjs/setup-zeego.native.js.map +1 -1
  38. package/dist/esm/components.mjs +3 -3
  39. package/dist/esm/components.mjs.map +1 -1
  40. package/dist/esm/components.native.js +3 -3
  41. package/dist/esm/components.native.js.map +1 -1
  42. package/dist/esm/gesture-handler.mjs +2 -2
  43. package/dist/esm/gesture-handler.mjs.map +1 -1
  44. package/dist/esm/gesture-handler.native.js +2 -2
  45. package/dist/esm/gesture-handler.native.js.map +1 -1
  46. package/dist/esm/gestureState.mjs +90 -21
  47. package/dist/esm/gestureState.mjs.map +1 -1
  48. package/dist/esm/gestureState.native.js +96 -21
  49. package/dist/esm/gestureState.native.js.map +1 -1
  50. package/dist/esm/globalState.mjs +1 -1
  51. package/dist/esm/globalState.mjs.map +1 -1
  52. package/dist/esm/globalState.native.js +1 -1
  53. package/dist/esm/globalState.native.js.map +1 -1
  54. package/dist/esm/index.js +2 -2
  55. package/dist/esm/index.js.map +1 -1
  56. package/dist/esm/index.mjs +2 -2
  57. package/dist/esm/index.mjs.map +1 -1
  58. package/dist/esm/index.native.js +2 -2
  59. package/dist/esm/index.native.js.map +1 -1
  60. package/dist/esm/setup-burnt.mjs +2 -2
  61. package/dist/esm/setup-burnt.mjs.map +1 -1
  62. package/dist/esm/setup-burnt.native.js +2 -2
  63. package/dist/esm/setup-burnt.native.js.map +1 -1
  64. package/dist/esm/setup-expo-linear-gradient.mjs +2 -2
  65. package/dist/esm/setup-expo-linear-gradient.mjs.map +1 -1
  66. package/dist/esm/setup-expo-linear-gradient.native.js +2 -2
  67. package/dist/esm/setup-expo-linear-gradient.native.js.map +1 -1
  68. package/dist/esm/setup-gesture-handler.mjs +16 -7
  69. package/dist/esm/setup-gesture-handler.mjs.map +1 -1
  70. package/dist/esm/setup-gesture-handler.native.js +16 -7
  71. package/dist/esm/setup-gesture-handler.native.js.map +1 -1
  72. package/dist/esm/setup-keyboard-controller.native.js +2 -2
  73. package/dist/esm/setup-safe-area.native.js +8 -2
  74. package/dist/esm/setup-safe-area.native.js.map +1 -1
  75. package/dist/esm/setup-teleport.mjs +3 -3
  76. package/dist/esm/setup-teleport.mjs.map +1 -1
  77. package/dist/esm/setup-teleport.native.js +3 -3
  78. package/dist/esm/setup-teleport.native.js.map +1 -1
  79. package/dist/esm/setup-worklets.mjs +2 -2
  80. package/dist/esm/setup-worklets.mjs.map +1 -1
  81. package/dist/esm/setup-worklets.native.js +2 -2
  82. package/dist/esm/setup-worklets.native.js.map +1 -1
  83. package/dist/esm/setup-zeego.mjs +2 -2
  84. package/dist/esm/setup-zeego.mjs.map +1 -1
  85. package/dist/esm/setup-zeego.native.js +2 -2
  86. package/dist/esm/setup-zeego.native.js.map +1 -1
  87. package/gesture-handler/index.cjs +1 -0
  88. package/gesture-handler/index.js +1 -0
  89. package/gesture-handler/index.native.cjs +1 -0
  90. package/gesture-handler/index.native.js +1 -0
  91. package/package.json +7 -68
  92. package/setup-burnt/index.cjs +2 -0
  93. package/setup-burnt/index.js +2 -0
  94. package/setup-burnt/index.native.cjs +2 -0
  95. package/setup-burnt/index.native.js +2 -0
  96. package/setup-expo-linear-gradient/index.cjs +2 -0
  97. package/setup-expo-linear-gradient/index.js +2 -0
  98. package/setup-expo-linear-gradient/index.native.cjs +2 -0
  99. package/setup-expo-linear-gradient/index.native.js +2 -0
  100. package/setup-gesture-handler/index.cjs +2 -0
  101. package/setup-gesture-handler/index.js +2 -0
  102. package/setup-gesture-handler/index.native.cjs +2 -0
  103. package/setup-gesture-handler/index.native.js +2 -0
  104. package/setup-keyboard-controller/index.cjs +2 -0
  105. package/setup-keyboard-controller/index.js +2 -0
  106. package/setup-keyboard-controller/index.native.cjs +2 -0
  107. package/setup-keyboard-controller/index.native.js +2 -0
  108. package/setup-safe-area/index.cjs +2 -0
  109. package/setup-safe-area/index.js +2 -0
  110. package/setup-safe-area/index.native.cjs +2 -0
  111. package/setup-safe-area/index.native.js +2 -0
  112. package/setup-teleport/index.cjs +2 -0
  113. package/setup-teleport/index.js +2 -0
  114. package/setup-teleport/index.native.cjs +2 -0
  115. package/setup-teleport/index.native.js +2 -0
  116. package/setup-worklets/index.cjs +2 -0
  117. package/setup-worklets/index.js +2 -0
  118. package/setup-worklets/index.native.cjs +2 -0
  119. package/setup-worklets/index.native.js +2 -0
  120. package/setup-zeego/index.cjs +2 -0
  121. package/setup-zeego/index.js +2 -0
  122. package/setup-zeego/index.native.cjs +2 -0
  123. package/setup-zeego/index.native.js +2 -0
  124. package/src/components.tsx +3 -3
  125. package/src/gesture-handler.ts +1 -0
  126. package/src/gestureState.ts +185 -28
  127. package/src/globalState.ts +1 -1
  128. package/src/index.ts +1 -0
  129. package/src/setup-burnt.ts +2 -2
  130. package/src/setup-expo-linear-gradient.ts +2 -2
  131. package/src/setup-gesture-handler.ts +20 -5
  132. package/src/setup-keyboard-controller.ts +2 -2
  133. package/src/setup-safe-area.ts +2 -2
  134. package/src/setup-teleport.ts +3 -3
  135. package/src/setup-worklets.ts +2 -2
  136. package/src/setup-zeego.ts +2 -2
  137. package/src/types.ts +4 -0
  138. package/tsconfig.json +12 -0
  139. package/types/components.d.ts.map +1 -1
  140. package/types/gesture-handler.d.ts +1 -1
  141. package/types/gesture-handler.d.ts.map +2 -2
  142. package/types/gestureState.d.ts +2 -0
  143. package/types/gestureState.d.ts.map +2 -2
  144. package/types/globalState.d.ts.map +1 -1
  145. package/types/index.d.ts +1 -1
  146. package/types/index.d.ts.map +2 -2
  147. package/types/setup-burnt.d.ts.map +1 -1
  148. package/types/setup-expo-linear-gradient.d.ts.map +1 -1
  149. package/types/setup-gesture-handler.d.ts.map +1 -1
  150. package/types/setup-keyboard-controller.d.ts.map +1 -1
  151. package/types/setup-safe-area.d.ts.map +1 -1
  152. package/types/setup-teleport.d.ts.map +1 -1
  153. package/types/setup-worklets.d.ts.map +1 -1
  154. package/types/setup-zeego.d.ts.map +1 -1
  155. package/types/types.d.ts +1 -0
  156. package/types/types.d.ts.map +2 -2
@@ -6,12 +6,102 @@ const state = createGlobalState<GestureState>(`gesture`, {
6
6
  Gesture: null,
7
7
  GestureDetector: null,
8
8
  ScrollView: null,
9
+ RootView: null,
9
10
  })
10
11
 
12
+ type GestureEnabledFreezeState = {
13
+ frozen: boolean
14
+ enabled: boolean
15
+ warned: boolean
16
+ }
17
+
18
+ const GESTURE_ENABLED_FREEZE_KEY = '__hanzogui_gesture_enabled_freeze__'
19
+
20
+ function getGestureEnabledFreezeState(): GestureEnabledFreezeState {
21
+ const g = globalThis as typeof globalThis & {
22
+ [GESTURE_ENABLED_FREEZE_KEY]?: GestureEnabledFreezeState
23
+ }
24
+
25
+ if (!g[GESTURE_ENABLED_FREEZE_KEY]) {
26
+ g[GESTURE_ENABLED_FREEZE_KEY] = {
27
+ frozen: false,
28
+ enabled: false,
29
+ warned: false,
30
+ }
31
+ }
32
+
33
+ return g[GESTURE_ENABLED_FREEZE_KEY]!
34
+ }
35
+
36
+ function warnGestureEnabledMutationIgnored(source: string) {
37
+ const freezeState = getGestureEnabledFreezeState()
38
+
39
+ if (freezeState.warned || process.env.NODE_ENV === 'production') {
40
+ return
41
+ }
42
+
43
+ freezeState.warned = true
44
+
45
+ console.warn(
46
+ `[Gui] Ignored ${source} because gesture handler press events were already ` +
47
+ `${freezeState.enabled ? 'enabled' : 'disabled'} when GuiProvider mounted. ` +
48
+ `Configure gesture handler mode before the first render.`
49
+ )
50
+ }
51
+
52
+ export function canChangeGestureHandlerEnabled(
53
+ nextEnabled: boolean,
54
+ source: string
55
+ ): boolean {
56
+ const freezeState = getGestureEnabledFreezeState()
57
+
58
+ if (!freezeState.frozen || freezeState.enabled === nextEnabled) {
59
+ return true
60
+ }
61
+
62
+ warnGestureEnabledMutationIgnored(source)
63
+ return false
64
+ }
65
+
11
66
  let pressGestureDebugId = 0
12
67
  let externalPressDebugId = 0
13
68
 
69
+ // distance in dp the finger may travel before a press is treated as a
70
+ // scroll/drag and cancelled. tracked in absolute (screen) coordinates by the
71
+ // Tap's onTouchesMove (see createPressGesture) so a view translating under a
72
+ // stationary finger - a sheet sliding on keyboard open/close, a sheet open
73
+ // animation - does not cancel the press; only genuine finger travel does.
74
+ // this also makes scroll-cancels-press work for any scroll container (a plain
75
+ // RN ScrollView/FlatList or an RNGH one), since the press cancels itself
76
+ // rather than relying on a parent RNGH gesture to steal it.
77
+ const PRESS_MOVE_CANCEL_DISTANCE = 12
78
+ const PRESS_MOVE_CANCEL_DISTANCE_SQ =
79
+ PRESS_MOVE_CANCEL_DISTANCE * PRESS_MOVE_CANCEL_DISTANCE
80
+
81
+ // minimal shapes of the RNGH gesture events createPressGesture reads. RNGH is
82
+ // injected at runtime (GestureState.Gesture is untyped), so this module keeps
83
+ // no type dependency on react-native-gesture-handler - these name just the
84
+ // fields the press gesture touches.
85
+ type GesturePoint = {
86
+ absoluteX: number
87
+ absoluteY: number
88
+ }
89
+ type GestureBeginEvent = Partial<GesturePoint>
90
+ type GestureTouchEvent = {
91
+ changedTouches?: GesturePoint[]
92
+ allTouches?: GesturePoint[]
93
+ }
94
+
14
95
  type PressOwnerSource = 'internal' | 'external' | null
96
+ type PressOwnershipState = {
97
+ owner: object | null
98
+ ownerId: number | null
99
+ ownerSource: PressOwnerSource
100
+ ownerPointerId: number | null
101
+ timestamp: number
102
+ }
103
+
104
+ const PRESS_STATE_KEY = '__hanzogui_press_state__'
15
105
 
16
106
  function getEventPointerId(e: any): number | null {
17
107
  const pointerId =
@@ -34,13 +124,17 @@ function getEventPointerId(e: any): number | null {
34
124
  * Uses a grace period to allow child gestures to steal ownership from parent,
35
125
  * since RNGH fires parent gestures before child gestures.
36
126
  */
37
- const pressState = {
38
- owner: null as object | null,
39
- ownerId: null as number | null,
40
- ownerSource: null as PressOwnerSource,
41
- ownerPointerId: null as number | null,
127
+ const pressState = ((
128
+ globalThis as typeof globalThis & {
129
+ [PRESS_STATE_KEY]?: PressOwnershipState
130
+ }
131
+ )[PRESS_STATE_KEY] ??= {
132
+ owner: null,
133
+ ownerId: null,
134
+ ownerSource: null,
135
+ ownerPointerId: null,
42
136
  timestamp: 0,
43
- }
137
+ })
44
138
 
45
139
  export interface Insets {
46
140
  top?: number
@@ -110,6 +204,11 @@ export function releaseExternalPressOwnership(
110
204
  resetPressOwner()
111
205
  }
112
206
 
207
+ export function hasExternalPressOwnership(): boolean {
208
+ resetStaleOwner(Date.now())
209
+ return pressState.ownerSource === 'external'
210
+ }
211
+
113
212
  export function getGestureHandler(): GestureHandlerAccessor {
114
213
  return {
115
214
  get isEnabled(): boolean {
@@ -119,10 +218,21 @@ export function getGestureHandler(): GestureHandlerAccessor {
119
218
  return state.get()
120
219
  },
121
220
  set(updates: Partial<GestureState>): void {
221
+ if (
222
+ updates.enabled !== undefined &&
223
+ !canChangeGestureHandlerEnabled(updates.enabled, 'getGestureHandler().set()')
224
+ ) {
225
+ return
226
+ }
227
+
122
228
  Object.assign(state.get(), updates)
123
229
  },
124
230
 
125
231
  disable(): void {
232
+ if (!canChangeGestureHandlerEnabled(false, 'getGestureHandler().disable()')) {
233
+ return
234
+ }
235
+
126
236
  state.get().enabled = false
127
237
  },
128
238
 
@@ -135,9 +245,22 @@ export function getGestureHandler(): GestureHandlerAccessor {
135
245
  // unique token for this gesture instance - used to track ownership
136
246
  const myToken = {}
137
247
  const myDebugId = ++pressGestureDebugId
138
- let didLongPress = false
139
- let didPressIn = false
140
- let pressInTimer: ReturnType<typeof setTimeout> | null = null
248
+ // mutable gesture state kept on an object so handler bodies that get
249
+ // workletized by react-native-worklets (>=0.7.4) see live values.
250
+ // primitive `let`s get frozen into the worklet's serialized __closure
251
+ // at factory time, so reassignments from workletized handlers never
252
+ // propagate out. object property mutation goes through the captured
253
+ // reference and is always observed.
254
+ const flags = {
255
+ didLongPress: false,
256
+ didPressIn: false,
257
+ pressInTimer: null as ReturnType<typeof setTimeout> | null,
258
+ // absolute (screen) coords of the press start, and whether the finger
259
+ // has since travelled far enough to be treated as a scroll/drag.
260
+ moveStartX: null as number | null,
261
+ moveStartY: 0,
262
+ cancelledByMove: false,
263
+ }
141
264
 
142
265
  // Grace period for child gestures to steal ownership from parent.
143
266
  // RNGH fires parent before child, but we want innermost to win.
@@ -173,9 +296,9 @@ export function getGestureHandler(): GestureHandlerAccessor {
173
296
  const isOwner = () => pressState.owner === myToken
174
297
 
175
298
  const releaseOwnership = () => {
176
- if (pressInTimer) {
177
- clearTimeout(pressInTimer)
178
- pressInTimer = null
299
+ if (flags.pressInTimer) {
300
+ clearTimeout(flags.pressInTimer)
301
+ flags.pressInTimer = null
179
302
  }
180
303
  if (pressState.owner === myToken) {
181
304
  resetPressOwner()
@@ -183,39 +306,73 @@ export function getGestureHandler(): GestureHandlerAccessor {
183
306
  }
184
307
 
185
308
  const firePressIn = (e: any) => {
186
- if (!didPressIn && isOwner()) {
187
- didPressIn = true
309
+ if (!flags.didPressIn && isOwner()) {
310
+ flags.didPressIn = true
188
311
  config.onPressIn?.(e)
189
312
  }
190
313
  }
191
314
 
192
315
  const schedulePressIn = (e: any) => {
193
- if (pressInTimer) {
194
- clearTimeout(pressInTimer)
316
+ if (flags.pressInTimer) {
317
+ clearTimeout(flags.pressInTimer)
195
318
  }
196
- pressInTimer = setTimeout(() => {
197
- pressInTimer = null
319
+ flags.pressInTimer = setTimeout(() => {
320
+ flags.pressInTimer = null
198
321
  if (isOwner()) {
199
322
  firePressIn(e)
200
323
  }
201
324
  }, GRACE_PERIOD_MS + 1)
202
325
  }
203
326
 
204
- // Tap gesture for regular presses
205
- // Use long maxDuration to not cancel during long presses
327
+ // Tap gesture for regular presses.
328
+ //
329
+ // maxDuration is long so long-presses aren't cut off. We deliberately do
330
+ // not use RNGH's .maxDistance(): it measures finger travel in the gesture
331
+ // view's own coordinate space, so a view that translates under a
332
+ // stationary finger (a sheet sliding on keyboard open/close, a sheet open
333
+ // animation) reads as finger movement and silently cancels the press -
334
+ // onPress never fires. Instead onTouchesMove tracks the finger in
335
+ // absolute (screen) coordinates and cancels only on genuine finger
336
+ // travel. That also makes scroll-cancels-press work for any scroll
337
+ // container, since the press cancels itself instead of depending on a
338
+ // parent RNGH gesture to steal the touch.
206
339
  const tap = Gesture.Tap()
207
340
  .runOnJS(true)
208
341
  .maxDuration(10000) // allow very long presses
209
- .onBegin((e: unknown) => {
210
- didLongPress = false
211
- didPressIn = false
342
+ .onBegin((e: GestureBeginEvent) => {
343
+ flags.didLongPress = false
344
+ flags.didPressIn = false
345
+ flags.cancelledByMove = false
346
+ flags.moveStartX = typeof e.absoluteX === 'number' ? e.absoluteX : null
347
+ flags.moveStartY = typeof e.absoluteY === 'number' ? e.absoluteY : 0
212
348
  tryClaimOwnership(e)
213
349
  // Defer onPressIn until after the grace window so child pressables
214
350
  // can steal ownership, but flush it on tap end for very fast taps.
215
351
  schedulePressIn(e)
216
352
  })
353
+ .onTouchesMove((e: GestureTouchEvent) => {
354
+ if (flags.cancelledByMove || flags.moveStartX === null) return
355
+ const touch = e.changedTouches?.[0] ?? e.allTouches?.[0]
356
+ if (!touch) return
357
+ const dx = touch.absoluteX - flags.moveStartX
358
+ const dy = touch.absoluteY - flags.moveStartY
359
+ if (dx * dx + dy * dy <= PRESS_MOVE_CANCEL_DISTANCE_SQ) return
360
+ // finger has travelled far enough to be a scroll/drag, not a tap.
361
+ // release the pressStyle now (mid-scroll, so it doesn't stay stuck)
362
+ // and make onEnd skip onPress on finger lift.
363
+ flags.cancelledByMove = true
364
+ if (flags.pressInTimer) {
365
+ clearTimeout(flags.pressInTimer)
366
+ flags.pressInTimer = null
367
+ }
368
+ if (flags.didPressIn) {
369
+ flags.didPressIn = false
370
+ config.onPressOut?.(e)
371
+ }
372
+ releaseOwnership()
373
+ })
217
374
  .onEnd((e: unknown) => {
218
- if (isOwner() && !didLongPress) {
375
+ if (isOwner() && !flags.didLongPress && !flags.cancelledByMove) {
219
376
  firePressIn(e)
220
377
  config.onPress?.(e)
221
378
  }
@@ -224,12 +381,12 @@ export function getGestureHandler(): GestureHandlerAccessor {
224
381
  if (isOwner()) {
225
382
  config.onPressOut?.(e)
226
383
  releaseOwnership()
227
- } else if (didPressIn) {
384
+ } else if (flags.didPressIn) {
228
385
  // we already fired onPressIn but lost ownership before finalize
229
386
  // (e.g. finger dragged onto a sibling pressable and that one
230
387
  // claimed ownership). fire onPressOut so callers can clear their
231
- // press state otherwise pressStyle stays stuck on this view.
232
- didPressIn = false
388
+ // press state - otherwise pressStyle stays stuck on this view.
389
+ flags.didPressIn = false
233
390
  config.onPressOut?.(e)
234
391
  }
235
392
  })
@@ -244,7 +401,7 @@ export function getGestureHandler(): GestureHandlerAccessor {
244
401
  .runOnJS(true)
245
402
  .minDuration(longPressDuration)
246
403
  .onStart((e: any) => {
247
- didLongPress = true
404
+ flags.didLongPress = true
248
405
  if (isOwner()) {
249
406
  firePressIn(e)
250
407
  config.onLongPress?.(e)
@@ -5,7 +5,7 @@ export function createGlobalState<T extends { enabled: boolean }>(
5
5
  get: () => T
6
6
  set: (next: T) => void
7
7
  } {
8
- const GLOBAL_KEY = `__gui_${key}__`
8
+ const GLOBAL_KEY = `__hanzogui_${key}__`
9
9
 
10
10
  type GuiGlobal = typeof globalThis & {
11
11
  [GLOBAL_KEY]?: T
package/src/index.ts CHANGED
@@ -44,6 +44,7 @@ export type { PortalAccessor } from './portalState'
44
44
  export { getGestureHandler } from './gestureState'
45
45
  export {
46
46
  claimExternalPressOwnership as unstable_claimExternalPressOwnership,
47
+ hasExternalPressOwnership as unstable_hasExternalPressOwnership,
47
48
  releaseExternalPressOwnership as unstable_releaseExternalPressOwnership,
48
49
  } from './gestureState'
49
50
  export type {
@@ -16,8 +16,8 @@ import { getBurnt } from './burntState'
16
16
 
17
17
  function setup(): void {
18
18
  const g = globalThis as any
19
- if (g.__gui_native_burnt_setup) return
20
- g.__gui_native_burnt_setup = true
19
+ if (g.__hanzogui_native_burnt_setup) return
20
+ g.__hanzogui_native_burnt_setup = true
21
21
 
22
22
  try {
23
23
  const Burnt = require('burnt') as typeof import('burnt')
@@ -16,8 +16,8 @@ import { getLinearGradient } from './linearGradientState'
16
16
 
17
17
  function setup(): void {
18
18
  const g = globalThis as any
19
- if (g.__gui_native_linear_gradient_setup) return
20
- g.__gui_native_linear_gradient_setup = true
19
+ if (g.__hanzogui_native_linear_gradient_setup) return
20
+ g.__hanzogui_native_linear_gradient_setup = true
21
21
 
22
22
  try {
23
23
  const expoLinearGradient = require('expo-linear-gradient')
@@ -17,7 +17,7 @@
17
17
  * for use with Sheet and other gesture-aware components.
18
18
  */
19
19
 
20
- import { getGestureHandler } from './gestureState'
20
+ import { canChangeGestureHandlerEnabled, getGestureHandler } from './gestureState'
21
21
 
22
22
  export interface GestureHandlerConfig {
23
23
  /** use RNGH for press events on Gui components (default: true) */
@@ -41,16 +41,29 @@ export function setupGestureHandler(config?: GestureHandlerConfig): void {
41
41
  // override config if provided
42
42
  if (config) {
43
43
  currentConfig = config
44
+
45
+ if (
46
+ config.pressEvents !== undefined &&
47
+ !canChangeGestureHandlerEnabled(
48
+ config.pressEvents !== false,
49
+ 'setupGestureHandler()'
50
+ )
51
+ ) {
52
+ currentConfig = {
53
+ ...currentConfig,
54
+ pressEvents: getGestureHandler().isEnabled,
55
+ }
56
+ }
44
57
  }
45
58
 
46
59
  // allow re-running setup to change config
47
- const isFirstRun = !g.__gui_native_gesture_setup_complete
48
- g.__gui_native_gesture_setup_complete = true
60
+ const isFirstRun = !g.__hanzogui_native_gesture_setup_complete
61
+ g.__hanzogui_native_gesture_setup_complete = true
49
62
 
50
63
  try {
51
64
  // dynamically require RNGH - it should already be imported by the app
52
65
  const rngh = require('react-native-gesture-handler')
53
- const { Gesture, GestureDetector, ScrollView } = rngh
66
+ const { Gesture, GestureDetector, ScrollView, GestureHandlerRootView } = rngh
54
67
 
55
68
  if (Gesture && GestureDetector) {
56
69
  // only enable if pressEvents is true
@@ -59,14 +72,16 @@ export function setupGestureHandler(config?: GestureHandlerConfig): void {
59
72
  Gesture,
60
73
  GestureDetector,
61
74
  ScrollView: ScrollView || null,
75
+ RootView: GestureHandlerRootView || null,
62
76
  })
63
77
 
64
78
  // sheet state - only enable if sheet is true
65
- g.__gui_sheet_gesture_state__ = {
79
+ g.__hanzogui_sheet_gesture_state__ = {
66
80
  enabled: currentConfig.sheet !== false,
67
81
  Gesture,
68
82
  GestureDetector,
69
83
  ScrollView: ScrollView || null,
84
+ RootView: GestureHandlerRootView || null,
70
85
  }
71
86
  }
72
87
  } catch {
@@ -21,10 +21,10 @@ import { setKeyboardControllerState } from './keyboardControllerState'
21
21
 
22
22
  function setup() {
23
23
  const g = globalThis as any
24
- if (g.__gui_native_keyboard_controller_setup_complete) {
24
+ if (g.__hanzogui_native_keyboard_controller_setup_complete) {
25
25
  return
26
26
  }
27
- g.__gui_native_keyboard_controller_setup_complete = true
27
+ g.__hanzogui_native_keyboard_controller_setup_complete = true
28
28
 
29
29
  try {
30
30
  // dynamically require keyboard-controller - it should already be imported by the app
@@ -31,10 +31,10 @@ function setup() {
31
31
  }
32
32
 
33
33
  const g = globalThis as any
34
- if (g.__gui_native_safe_area_setup_complete) {
34
+ if (g.__hanzogui_native_safe_area_setup_complete) {
35
35
  return
36
36
  }
37
- g.__gui_native_safe_area_setup_complete = true
37
+ g.__hanzogui_native_safe_area_setup_complete = true
38
38
 
39
39
  try {
40
40
  const safeAreaContext = require('react-native-safe-area-context')
@@ -16,14 +16,14 @@ import { getPortal } from './portalState'
16
16
 
17
17
  function setup(): void {
18
18
  const g = globalThis as any
19
- if (g.__gui_native_portal_setup) return
20
- g.__gui_native_portal_setup = true
19
+ if (g.__hanzogui_native_portal_setup) return
20
+ g.__hanzogui_native_portal_setup = true
21
21
 
22
22
  // try teleport first (preferred)
23
23
  try {
24
24
  const teleport = require('react-native-teleport')
25
25
  if (teleport?.Portal && teleport?.PortalHost && teleport?.PortalProvider) {
26
- g.__gui_teleport = teleport
26
+ g.__hanzogui_teleport = teleport
27
27
  getPortal().set({ enabled: true, type: 'teleport' })
28
28
  return
29
29
  }
@@ -22,10 +22,10 @@ import { getWorklets } from './workletsState'
22
22
 
23
23
  function setup() {
24
24
  const g = globalThis as any
25
- if (g.__gui_native_worklets_setup_complete) {
25
+ if (g.__hanzogui_native_worklets_setup_complete) {
26
26
  return
27
27
  }
28
- g.__gui_native_worklets_setup_complete = true
28
+ g.__hanzogui_native_worklets_setup_complete = true
29
29
 
30
30
  try {
31
31
  // dynamically require worklets-core
@@ -16,8 +16,8 @@ import { getZeego } from './zeegoState'
16
16
 
17
17
  function setup(): void {
18
18
  const g = globalThis as any
19
- if (g.__gui_native_zeego_setup) return
20
- g.__gui_native_zeego_setup = true
19
+ if (g.__hanzogui_native_zeego_setup) return
20
+ g.__hanzogui_native_zeego_setup = true
21
21
 
22
22
  try {
23
23
  const DropdownMenu = require('zeego/dropdown-menu')
package/src/types.ts CHANGED
@@ -10,6 +10,10 @@ export interface GestureState {
10
10
  Gesture: any
11
11
  GestureDetector: any
12
12
  ScrollView: any
13
+ // rngh's GestureHandlerRootView. native portals can render outside the app's
14
+ // root gesture view, so components that portal rngh gestures need to
15
+ // re-establish one around their content.
16
+ RootView: any
13
17
  }
14
18
 
15
19
  export interface WorkletsState {
package/tsconfig.json ADDED
@@ -0,0 +1,12 @@
1
+ {
2
+ "extends": "../../../tsconfig.json",
3
+ "compilerOptions": {
4
+ "composite": true,
5
+ "isolatedDeclarations": true
6
+ },
7
+ "exclude": [
8
+ "types",
9
+ "dist",
10
+ "**/__tests__"
11
+ ]
12
+ }
@@ -6,6 +6,6 @@
6
6
  ],
7
7
  "version": 3,
8
8
  "sourcesContent": [
9
- "import type { ReactNode } from 'react'\nimport { getPortal } from './portalState'\nimport type {\n NativePortalProps,\n NativePortalHostProps,\n NativePortalProviderProps,\n} from './types'\n\nexport type { NativePortalProps, NativePortalHostProps, NativePortalProviderProps }\n\n/**\n * Renders children into a teleport Portal when available.\n * Returns null when teleport is not set up (allows fallback handling by caller).\n */\nexport function NativePortal({\n hostName = 'root',\n children,\n}: NativePortalProps): ReactNode {\n const state = getPortal().state\n if (state.type !== 'teleport') return null\n\n const { Portal } = (globalThis as any).__gui_teleport\n return <Portal hostName={hostName}>{children}</Portal>\n}\n\n/**\n * Renders a teleport PortalHost when available.\n * Returns null when teleport is not set up.\n */\nexport function NativePortalHost({ name }: NativePortalHostProps): ReactNode {\n const state = getPortal().state\n if (state.type !== 'teleport') return null\n\n const { PortalHost } = (globalThis as any).__gui_teleport\n return <PortalHost name={name} />\n}\n\n/**\n * Wraps children with teleport PortalProvider when available.\n * Returns children as-is when teleport is not set up.\n */\nexport function NativePortalProvider({ children }: NativePortalProviderProps): ReactNode {\n const state = getPortal().state\n if (state.type !== 'teleport') return <>{children}</>\n\n const { PortalProvider } = (globalThis as any).__gui_teleport\n return <PortalProvider>{children}</PortalProvider>\n}\n"
9
+ "import type { ReactNode } from 'react'\nimport { getPortal } from './portalState'\nimport type {\n NativePortalProps,\n NativePortalHostProps,\n NativePortalProviderProps,\n} from './types'\n\nexport type { NativePortalProps, NativePortalHostProps, NativePortalProviderProps }\n\n/**\n * Renders children into a teleport Portal when available.\n * Returns null when teleport is not set up (allows fallback handling by caller).\n */\nexport function NativePortal({\n hostName = 'root',\n children,\n}: NativePortalProps): ReactNode {\n const state = getPortal().state\n if (state.type !== 'teleport') return null\n\n const { Portal } = (globalThis as any).__hanzogui_teleport\n return <Portal hostName={hostName}>{children}</Portal>\n}\n\n/**\n * Renders a teleport PortalHost when available.\n * Returns null when teleport is not set up.\n */\nexport function NativePortalHost({ name }: NativePortalHostProps): ReactNode {\n const state = getPortal().state\n if (state.type !== 'teleport') return null\n\n const { PortalHost } = (globalThis as any).__hanzogui_teleport\n return <PortalHost name={name} />\n}\n\n/**\n * Wraps children with teleport PortalProvider when available.\n * Returns children as-is when teleport is not set up.\n */\nexport function NativePortalProvider({ children }: NativePortalProviderProps): ReactNode {\n const state = getPortal().state\n if (state.type !== 'teleport') return <>{children}</>\n\n const { PortalProvider } = (globalThis as any).__hanzogui_teleport\n return <PortalProvider>{children}</PortalProvider>\n}\n"
10
10
  ]
11
11
  }
@@ -1,6 +1,6 @@
1
1
  export { getGestureHandler } from "./gestureState";
2
2
  export type { ExternalPressOwnershipToken, GestureHandlerAccessor, PressGestureConfig } from "./gestureState";
3
- export { claimExternalPressOwnership as unstable_claimExternalPressOwnership, releaseExternalPressOwnership as unstable_releaseExternalPressOwnership } from "./gestureState";
3
+ export { claimExternalPressOwnership as unstable_claimExternalPressOwnership, hasExternalPressOwnership as unstable_hasExternalPressOwnership, releaseExternalPressOwnership as unstable_releaseExternalPressOwnership } from "./gestureState";
4
4
  export { PressBoundary } from "./PressBoundary";
5
5
  export type { PressBoundaryProps } from "./PressBoundary";
6
6
  export { getGestureHandlerConfig, setupGestureHandler, type GestureHandlerConfig } from "./setup-gesture-handler";
@@ -1,11 +1,11 @@
1
1
  {
2
- "mappings": "AAAA,SAAS,yBAAyB;AAClC,cACE,6BACA,wBACA,0BACK;AACP,SACE,+BAA+B,sCAC/B,iCAAiC,8CAC5B;AACP,SAAS,qBAAqB;AAC9B,cAAc,0BAA0B;AACxC,SACE,yBACA,0BACK,4BACA",
2
+ "mappings": "AAAA,SAAS,yBAAyB;AAClC,cACE,6BACA,wBACA,0BACK;AACP,SACE,+BAA+B,sCAC/B,6BAA6B,oCAC7B,iCAAiC,8CAC5B;AACP,SAAS,qBAAqB;AAC9B,cAAc,0BAA0B;AACxC,SACE,yBACA,0BACK,4BACA",
3
3
  "names": [],
4
4
  "sources": [
5
5
  "src/gesture-handler.ts"
6
6
  ],
7
7
  "version": 3,
8
8
  "sourcesContent": [
9
- "export { getGestureHandler } from './gestureState'\nexport type {\n ExternalPressOwnershipToken,\n GestureHandlerAccessor,\n PressGestureConfig,\n} from './gestureState'\nexport {\n claimExternalPressOwnership as unstable_claimExternalPressOwnership,\n releaseExternalPressOwnership as unstable_releaseExternalPressOwnership,\n} from './gestureState'\nexport { PressBoundary } from './PressBoundary'\nexport type { PressBoundaryProps } from './PressBoundary'\nexport {\n getGestureHandlerConfig,\n setupGestureHandler,\n type GestureHandlerConfig,\n} from './setup-gesture-handler'\n"
9
+ "export { getGestureHandler } from './gestureState'\nexport type {\n ExternalPressOwnershipToken,\n GestureHandlerAccessor,\n PressGestureConfig,\n} from './gestureState'\nexport {\n claimExternalPressOwnership as unstable_claimExternalPressOwnership,\n hasExternalPressOwnership as unstable_hasExternalPressOwnership,\n releaseExternalPressOwnership as unstable_releaseExternalPressOwnership,\n} from './gestureState'\nexport { PressBoundary } from './PressBoundary'\nexport type { PressBoundaryProps } from './PressBoundary'\nexport {\n getGestureHandlerConfig,\n setupGestureHandler,\n type GestureHandlerConfig,\n} from './setup-gesture-handler'\n"
10
10
  ]
11
11
  }
@@ -1,4 +1,5 @@
1
1
  import type { GestureState } from "./types";
2
+ export declare function canChangeGestureHandlerEnabled(nextEnabled: boolean, source: string): boolean;
2
3
  export interface Insets {
3
4
  top?: number;
4
5
  left?: number;
@@ -24,6 +25,7 @@ export interface GestureHandlerAccessor {
24
25
  export type ExternalPressOwnershipToken = object;
25
26
  export declare function claimExternalPressOwnership(debugName?: string | null): ExternalPressOwnershipToken;
26
27
  export declare function releaseExternalPressOwnership(token: ExternalPressOwnershipToken | null | undefined, debugName?: string | null): void;
28
+ export declare function hasExternalPressOwnership(): boolean;
27
29
  export declare function getGestureHandler(): GestureHandlerAccessor;
28
30
 
29
31
  //# sourceMappingURL=gestureState.d.ts.map
@@ -1,11 +1,11 @@
1
1
  {
2
- "mappings": "AACA,cAAc,oBAAoB;AA2ClC,iBAAiB,OAAO;CACtB;CACA;CACA;CACA;;AAGF,YAAY,qBAAqB;CAC/B;CACA,aAAa;CACb,cAAc;CACd,WAAW;CACX,eAAe;CACf;CACA,mBAAmB;;AAGrB,iBAAiB,uBAAuB;UAC7B;UACA,OAAO;CAChB,IAAI,SAAS,QAAQ;CACrB;CACA,mBAAmB,QAAQ;;AAG7B,YAAY;AAgBZ,OAAO,iBAAS,4BACd,4BACC;AAeH,OAAO,iBAAS,8BACd,OAAO,gDACP;AAQF,OAAO,iBAAS,qBAAqB",
2
+ "mappings": "AACA,cAAc,oBAAoB;AAkDlC,OAAO,iBAAS,+BACd,sBACA;AAqFF,iBAAiB,OAAO;CACtB;CACA;CACA;CACA;;AAGF,YAAY,qBAAqB;CAC/B;CACA,aAAa;CACb,cAAc;CACd,WAAW;CACX,eAAe;CACf;CACA,mBAAmB;;AAGrB,iBAAiB,uBAAuB;UAC7B;UACA,OAAO;CAChB,IAAI,SAAS,QAAQ;CACrB;CACA,mBAAmB,QAAQ;;AAG7B,YAAY;AAgBZ,OAAO,iBAAS,4BACd,4BACC;AAeH,OAAO,iBAAS,8BACd,OAAO,gDACP;AAQF,OAAO,iBAAS;AAKhB,OAAO,iBAAS,qBAAqB",
3
3
  "names": [],
4
4
  "sources": [
5
5
  "src/gestureState.ts"
6
6
  ],
7
7
  "version": 3,
8
8
  "sourcesContent": [
9
- "import { createGlobalState } from './globalState'\nimport type { GestureState } from './types'\n\nconst state = createGlobalState<GestureState>(`gesture`, {\n enabled: false,\n Gesture: null,\n GestureDetector: null,\n ScrollView: null,\n})\n\nlet pressGestureDebugId = 0\nlet externalPressDebugId = 0\n\ntype PressOwnerSource = 'internal' | 'external' | null\n\nfunction getEventPointerId(e: any): number | null {\n const pointerId =\n e?.pointerId ??\n e?.pointer?.id ??\n e?.event?.pointerId ??\n e?.event?.pointer?.id ??\n e?.nativeEvent?.pointerId ??\n e?.nativeEvent?.id ??\n e?.event?.nativeEvent?.pointerId ??\n e?.event?.nativeEvent?.id ??\n null\n\n return pointerId == null || Number.isNaN(pointerId) ? null : Number(pointerId)\n}\n\n/**\n * Global press coordination - ensures only innermost pressable fires press events,\n * matching RN Pressable/responder system semantics where deepest component wins.\n * Uses a grace period to allow child gestures to steal ownership from parent,\n * since RNGH fires parent gestures before child gestures.\n */\nconst pressState = {\n owner: null as object | null,\n ownerId: null as number | null,\n ownerSource: null as PressOwnerSource,\n ownerPointerId: null as number | null,\n timestamp: 0,\n}\n\nexport interface Insets {\n top?: number\n left?: number\n bottom?: number\n right?: number\n}\n\nexport type PressGestureConfig = {\n debugName?: string | null\n onPressIn?: (e: any) => void\n onPressOut?: (e: any) => void\n onPress?: (e: any) => void\n onLongPress?: (e: any) => void\n delayLongPress?: number\n hitSlop?: number | Insets | null\n}\n\nexport interface GestureHandlerAccessor {\n readonly isEnabled: boolean\n readonly state: GestureState\n set(updates: Partial<GestureState>): void\n disable(): void\n createPressGesture(config: PressGestureConfig): any\n}\n\nexport type ExternalPressOwnershipToken = object\n\nfunction resetPressOwner() {\n pressState.owner = null\n pressState.ownerId = null\n pressState.ownerSource = null\n pressState.ownerPointerId = null\n pressState.timestamp = 0\n}\n\nfunction resetStaleOwner(now: number, debugName?: string | null) {\n if (now - pressState.timestamp > 2000) {\n resetPressOwner()\n }\n}\n\nexport function claimExternalPressOwnership(\n debugName?: string | null\n): ExternalPressOwnershipToken {\n const now = Date.now()\n resetStaleOwner(now, debugName)\n\n const token = {}\n const ownerId = ++externalPressDebugId\n\n pressState.owner = token\n pressState.ownerId = ownerId\n pressState.ownerSource = 'external'\n pressState.timestamp = now\n\n return token\n}\n\nexport function releaseExternalPressOwnership(\n token: ExternalPressOwnershipToken | null | undefined,\n debugName?: string | null\n): void {\n if (!token || pressState.owner !== token) {\n return\n }\n resetPressOwner()\n}\n\nexport function getGestureHandler(): GestureHandlerAccessor {\n return {\n get isEnabled(): boolean {\n return state.get().enabled\n },\n get state(): GestureState {\n return state.get()\n },\n set(updates: Partial<GestureState>): void {\n Object.assign(state.get(), updates)\n },\n\n disable(): void {\n state.get().enabled = false\n },\n\n createPressGesture(config: PressGestureConfig): any {\n const { Gesture } = state.get()\n if (!Gesture) return null\n\n const longPressDuration = config.delayLongPress ?? 500\n\n // unique token for this gesture instance - used to track ownership\n const myToken = {}\n const myDebugId = ++pressGestureDebugId\n let didLongPress = false\n let didPressIn = false\n let pressInTimer: ReturnType<typeof setTimeout> | null = null\n\n // Grace period for child gestures to steal ownership from parent.\n // RNGH fires parent before child, but we want innermost to win.\n // Claims typically span a few ms, 24ms gives enough room to observe\n // ordering while debugging slower frames.\n const GRACE_PERIOD_MS = process.env.GUI_RNGH_PRESS_DELAY\n ? +process.env.GUI_RNGH_PRESS_DELAY\n : 24\n\n const tryClaimOwnership = (e: any) => {\n const now = Date.now()\n resetStaleOwner(now, config.debugName)\n\n const currentPointerId = getEventPointerId(e)\n const isSameTouchPointer =\n currentPointerId == null ||\n pressState.ownerPointerId == null ||\n pressState.ownerPointerId === currentPointerId\n\n if (\n pressState.owner === null ||\n (pressState.ownerSource === 'internal' && isSameTouchPointer)\n ) {\n pressState.owner = myToken\n pressState.ownerId = myDebugId\n pressState.ownerSource = 'internal'\n pressState.ownerPointerId = currentPointerId\n pressState.timestamp = now\n }\n return pressState.owner === myToken\n }\n\n const isOwner = () => pressState.owner === myToken\n\n const releaseOwnership = () => {\n if (pressInTimer) {\n clearTimeout(pressInTimer)\n pressInTimer = null\n }\n if (pressState.owner === myToken) {\n resetPressOwner()\n }\n }\n\n const firePressIn = (e: any) => {\n if (!didPressIn && isOwner()) {\n didPressIn = true\n config.onPressIn?.(e)\n }\n }\n\n const schedulePressIn = (e: any) => {\n if (pressInTimer) {\n clearTimeout(pressInTimer)\n }\n pressInTimer = setTimeout(() => {\n pressInTimer = null\n if (isOwner()) {\n firePressIn(e)\n }\n }, GRACE_PERIOD_MS + 1)\n }\n\n // Tap gesture for regular presses\n // Use long maxDuration to not cancel during long presses\n const tap = Gesture.Tap()\n .runOnJS(true)\n .maxDuration(10000) // allow very long presses\n .onBegin((e: unknown) => {\n didLongPress = false\n didPressIn = false\n tryClaimOwnership(e)\n // Defer onPressIn until after the grace window so child pressables\n // can steal ownership, but flush it on tap end for very fast taps.\n schedulePressIn(e)\n })\n .onEnd((e: unknown) => {\n if (isOwner() && !didLongPress) {\n firePressIn(e)\n config.onPress?.(e)\n }\n })\n .onFinalize((e: unknown) => {\n if (isOwner()) {\n config.onPressOut?.(e)\n releaseOwnership()\n } else if (didPressIn) {\n // we already fired onPressIn but lost ownership before finalize\n // (e.g. finger dragged onto a sibling pressable and that one\n // claimed ownership). fire onPressOut so callers can clear their\n // press state — otherwise pressStyle stays stuck on this view.\n didPressIn = false\n config.onPressOut?.(e)\n }\n })\n\n if (config.hitSlop) tap.hitSlop(config.hitSlop)\n\n // if no long press handler, just use tap\n if (!config.onLongPress) return tap\n\n // LongPress gesture for long press handling\n const longPress = Gesture.LongPress()\n .runOnJS(true)\n .minDuration(longPressDuration)\n .onStart((e: any) => {\n didLongPress = true\n if (isOwner()) {\n firePressIn(e)\n config.onLongPress?.(e)\n }\n })\n\n if (config.hitSlop) longPress.hitSlop(config.hitSlop)\n\n // exclusive: longPress has priority, tap is fallback for quick presses\n return Gesture.Exclusive(longPress, tap)\n },\n }\n}\n"
9
+ "import { createGlobalState } from './globalState'\nimport type { GestureState } from './types'\n\nconst state = createGlobalState<GestureState>(`gesture`, {\n enabled: false,\n Gesture: null,\n GestureDetector: null,\n ScrollView: null,\n RootView: null,\n})\n\ntype GestureEnabledFreezeState = {\n frozen: boolean\n enabled: boolean\n warned: boolean\n}\n\nconst GESTURE_ENABLED_FREEZE_KEY = '__hanzogui_gesture_enabled_freeze__'\n\nfunction getGestureEnabledFreezeState(): GestureEnabledFreezeState {\n const g = globalThis as typeof globalThis & {\n [GESTURE_ENABLED_FREEZE_KEY]?: GestureEnabledFreezeState\n }\n\n if (!g[GESTURE_ENABLED_FREEZE_KEY]) {\n g[GESTURE_ENABLED_FREEZE_KEY] = {\n frozen: false,\n enabled: false,\n warned: false,\n }\n }\n\n return g[GESTURE_ENABLED_FREEZE_KEY]!\n}\n\nfunction warnGestureEnabledMutationIgnored(source: string) {\n const freezeState = getGestureEnabledFreezeState()\n\n if (freezeState.warned || process.env.NODE_ENV === 'production') {\n return\n }\n\n freezeState.warned = true\n\n console.warn(\n `[Gui] Ignored ${source} because gesture handler press events were already ` +\n `${freezeState.enabled ? 'enabled' : 'disabled'} when GuiProvider mounted. ` +\n `Configure gesture handler mode before the first render.`\n )\n}\n\nexport function canChangeGestureHandlerEnabled(\n nextEnabled: boolean,\n source: string\n): boolean {\n const freezeState = getGestureEnabledFreezeState()\n\n if (!freezeState.frozen || freezeState.enabled === nextEnabled) {\n return true\n }\n\n warnGestureEnabledMutationIgnored(source)\n return false\n}\n\nlet pressGestureDebugId = 0\nlet externalPressDebugId = 0\n\n// distance in dp the finger may travel before a press is treated as a\n// scroll/drag and cancelled. tracked in absolute (screen) coordinates by the\n// Tap's onTouchesMove (see createPressGesture) so a view translating under a\n// stationary finger - a sheet sliding on keyboard open/close, a sheet open\n// animation - does not cancel the press; only genuine finger travel does.\n// this also makes scroll-cancels-press work for any scroll container (a plain\n// RN ScrollView/FlatList or an RNGH one), since the press cancels itself\n// rather than relying on a parent RNGH gesture to steal it.\nconst PRESS_MOVE_CANCEL_DISTANCE = 12\nconst PRESS_MOVE_CANCEL_DISTANCE_SQ =\n PRESS_MOVE_CANCEL_DISTANCE * PRESS_MOVE_CANCEL_DISTANCE\n\n// minimal shapes of the RNGH gesture events createPressGesture reads. RNGH is\n// injected at runtime (GestureState.Gesture is untyped), so this module keeps\n// no type dependency on react-native-gesture-handler - these name just the\n// fields the press gesture touches.\ntype GesturePoint = {\n absoluteX: number\n absoluteY: number\n}\ntype GestureBeginEvent = Partial<GesturePoint>\ntype GestureTouchEvent = {\n changedTouches?: GesturePoint[]\n allTouches?: GesturePoint[]\n}\n\ntype PressOwnerSource = 'internal' | 'external' | null\ntype PressOwnershipState = {\n owner: object | null\n ownerId: number | null\n ownerSource: PressOwnerSource\n ownerPointerId: number | null\n timestamp: number\n}\n\nconst PRESS_STATE_KEY = '__hanzogui_press_state__'\n\nfunction getEventPointerId(e: any): number | null {\n const pointerId =\n e?.pointerId ??\n e?.pointer?.id ??\n e?.event?.pointerId ??\n e?.event?.pointer?.id ??\n e?.nativeEvent?.pointerId ??\n e?.nativeEvent?.id ??\n e?.event?.nativeEvent?.pointerId ??\n e?.event?.nativeEvent?.id ??\n null\n\n return pointerId == null || Number.isNaN(pointerId) ? null : Number(pointerId)\n}\n\n/**\n * Global press coordination - ensures only innermost pressable fires press events,\n * matching RN Pressable/responder system semantics where deepest component wins.\n * Uses a grace period to allow child gestures to steal ownership from parent,\n * since RNGH fires parent gestures before child gestures.\n */\nconst pressState = ((\n globalThis as typeof globalThis & {\n [PRESS_STATE_KEY]?: PressOwnershipState\n }\n)[PRESS_STATE_KEY] ??= {\n owner: null,\n ownerId: null,\n ownerSource: null,\n ownerPointerId: null,\n timestamp: 0,\n})\n\nexport interface Insets {\n top?: number\n left?: number\n bottom?: number\n right?: number\n}\n\nexport type PressGestureConfig = {\n debugName?: string | null\n onPressIn?: (e: any) => void\n onPressOut?: (e: any) => void\n onPress?: (e: any) => void\n onLongPress?: (e: any) => void\n delayLongPress?: number\n hitSlop?: number | Insets | null\n}\n\nexport interface GestureHandlerAccessor {\n readonly isEnabled: boolean\n readonly state: GestureState\n set(updates: Partial<GestureState>): void\n disable(): void\n createPressGesture(config: PressGestureConfig): any\n}\n\nexport type ExternalPressOwnershipToken = object\n\nfunction resetPressOwner() {\n pressState.owner = null\n pressState.ownerId = null\n pressState.ownerSource = null\n pressState.ownerPointerId = null\n pressState.timestamp = 0\n}\n\nfunction resetStaleOwner(now: number, debugName?: string | null) {\n if (now - pressState.timestamp > 2000) {\n resetPressOwner()\n }\n}\n\nexport function claimExternalPressOwnership(\n debugName?: string | null\n): ExternalPressOwnershipToken {\n const now = Date.now()\n resetStaleOwner(now, debugName)\n\n const token = {}\n const ownerId = ++externalPressDebugId\n\n pressState.owner = token\n pressState.ownerId = ownerId\n pressState.ownerSource = 'external'\n pressState.timestamp = now\n\n return token\n}\n\nexport function releaseExternalPressOwnership(\n token: ExternalPressOwnershipToken | null | undefined,\n debugName?: string | null\n): void {\n if (!token || pressState.owner !== token) {\n return\n }\n resetPressOwner()\n}\n\nexport function hasExternalPressOwnership(): boolean {\n resetStaleOwner(Date.now())\n return pressState.ownerSource === 'external'\n}\n\nexport function getGestureHandler(): GestureHandlerAccessor {\n return {\n get isEnabled(): boolean {\n return state.get().enabled\n },\n get state(): GestureState {\n return state.get()\n },\n set(updates: Partial<GestureState>): void {\n if (\n updates.enabled !== undefined &&\n !canChangeGestureHandlerEnabled(updates.enabled, 'getGestureHandler().set()')\n ) {\n return\n }\n\n Object.assign(state.get(), updates)\n },\n\n disable(): void {\n if (!canChangeGestureHandlerEnabled(false, 'getGestureHandler().disable()')) {\n return\n }\n\n state.get().enabled = false\n },\n\n createPressGesture(config: PressGestureConfig): any {\n const { Gesture } = state.get()\n if (!Gesture) return null\n\n const longPressDuration = config.delayLongPress ?? 500\n\n // unique token for this gesture instance - used to track ownership\n const myToken = {}\n const myDebugId = ++pressGestureDebugId\n // mutable gesture state kept on an object so handler bodies that get\n // workletized by react-native-worklets (>=0.7.4) see live values.\n // primitive `let`s get frozen into the worklet's serialized __closure\n // at factory time, so reassignments from workletized handlers never\n // propagate out. object property mutation goes through the captured\n // reference and is always observed.\n const flags = {\n didLongPress: false,\n didPressIn: false,\n pressInTimer: null as ReturnType<typeof setTimeout> | null,\n // absolute (screen) coords of the press start, and whether the finger\n // has since travelled far enough to be treated as a scroll/drag.\n moveStartX: null as number | null,\n moveStartY: 0,\n cancelledByMove: false,\n }\n\n // Grace period for child gestures to steal ownership from parent.\n // RNGH fires parent before child, but we want innermost to win.\n // Claims typically span a few ms, 24ms gives enough room to observe\n // ordering while debugging slower frames.\n const GRACE_PERIOD_MS = process.env.GUI_RNGH_PRESS_DELAY\n ? +process.env.GUI_RNGH_PRESS_DELAY\n : 24\n\n const tryClaimOwnership = (e: any) => {\n const now = Date.now()\n resetStaleOwner(now, config.debugName)\n\n const currentPointerId = getEventPointerId(e)\n const isSameTouchPointer =\n currentPointerId == null ||\n pressState.ownerPointerId == null ||\n pressState.ownerPointerId === currentPointerId\n\n if (\n pressState.owner === null ||\n (pressState.ownerSource === 'internal' && isSameTouchPointer)\n ) {\n pressState.owner = myToken\n pressState.ownerId = myDebugId\n pressState.ownerSource = 'internal'\n pressState.ownerPointerId = currentPointerId\n pressState.timestamp = now\n }\n return pressState.owner === myToken\n }\n\n const isOwner = () => pressState.owner === myToken\n\n const releaseOwnership = () => {\n if (flags.pressInTimer) {\n clearTimeout(flags.pressInTimer)\n flags.pressInTimer = null\n }\n if (pressState.owner === myToken) {\n resetPressOwner()\n }\n }\n\n const firePressIn = (e: any) => {\n if (!flags.didPressIn && isOwner()) {\n flags.didPressIn = true\n config.onPressIn?.(e)\n }\n }\n\n const schedulePressIn = (e: any) => {\n if (flags.pressInTimer) {\n clearTimeout(flags.pressInTimer)\n }\n flags.pressInTimer = setTimeout(() => {\n flags.pressInTimer = null\n if (isOwner()) {\n firePressIn(e)\n }\n }, GRACE_PERIOD_MS + 1)\n }\n\n // Tap gesture for regular presses.\n //\n // maxDuration is long so long-presses aren't cut off. We deliberately do\n // not use RNGH's .maxDistance(): it measures finger travel in the gesture\n // view's own coordinate space, so a view that translates under a\n // stationary finger (a sheet sliding on keyboard open/close, a sheet open\n // animation) reads as finger movement and silently cancels the press -\n // onPress never fires. Instead onTouchesMove tracks the finger in\n // absolute (screen) coordinates and cancels only on genuine finger\n // travel. That also makes scroll-cancels-press work for any scroll\n // container, since the press cancels itself instead of depending on a\n // parent RNGH gesture to steal the touch.\n const tap = Gesture.Tap()\n .runOnJS(true)\n .maxDuration(10000) // allow very long presses\n .onBegin((e: GestureBeginEvent) => {\n flags.didLongPress = false\n flags.didPressIn = false\n flags.cancelledByMove = false\n flags.moveStartX = typeof e.absoluteX === 'number' ? e.absoluteX : null\n flags.moveStartY = typeof e.absoluteY === 'number' ? e.absoluteY : 0\n tryClaimOwnership(e)\n // Defer onPressIn until after the grace window so child pressables\n // can steal ownership, but flush it on tap end for very fast taps.\n schedulePressIn(e)\n })\n .onTouchesMove((e: GestureTouchEvent) => {\n if (flags.cancelledByMove || flags.moveStartX === null) return\n const touch = e.changedTouches?.[0] ?? e.allTouches?.[0]\n if (!touch) return\n const dx = touch.absoluteX - flags.moveStartX\n const dy = touch.absoluteY - flags.moveStartY\n if (dx * dx + dy * dy <= PRESS_MOVE_CANCEL_DISTANCE_SQ) return\n // finger has travelled far enough to be a scroll/drag, not a tap.\n // release the pressStyle now (mid-scroll, so it doesn't stay stuck)\n // and make onEnd skip onPress on finger lift.\n flags.cancelledByMove = true\n if (flags.pressInTimer) {\n clearTimeout(flags.pressInTimer)\n flags.pressInTimer = null\n }\n if (flags.didPressIn) {\n flags.didPressIn = false\n config.onPressOut?.(e)\n }\n releaseOwnership()\n })\n .onEnd((e: unknown) => {\n if (isOwner() && !flags.didLongPress && !flags.cancelledByMove) {\n firePressIn(e)\n config.onPress?.(e)\n }\n })\n .onFinalize((e: unknown) => {\n if (isOwner()) {\n config.onPressOut?.(e)\n releaseOwnership()\n } else if (flags.didPressIn) {\n // we already fired onPressIn but lost ownership before finalize\n // (e.g. finger dragged onto a sibling pressable and that one\n // claimed ownership). fire onPressOut so callers can clear their\n // press state - otherwise pressStyle stays stuck on this view.\n flags.didPressIn = false\n config.onPressOut?.(e)\n }\n })\n\n if (config.hitSlop) tap.hitSlop(config.hitSlop)\n\n // if no long press handler, just use tap\n if (!config.onLongPress) return tap\n\n // LongPress gesture for long press handling\n const longPress = Gesture.LongPress()\n .runOnJS(true)\n .minDuration(longPressDuration)\n .onStart((e: any) => {\n flags.didLongPress = true\n if (isOwner()) {\n firePressIn(e)\n config.onLongPress?.(e)\n }\n })\n\n if (config.hitSlop) longPress.hitSlop(config.hitSlop)\n\n // exclusive: longPress has priority, tap is fallback for quick presses\n return Gesture.Exclusive(longPress, tap)\n },\n }\n}\n"
10
10
  ]
11
11
  }
@@ -6,6 +6,6 @@
6
6
  ],
7
7
  "version": 3,
8
8
  "sourcesContent": [
9
- "export function createGlobalState<T extends { enabled: boolean }>(\n key: string,\n defaultValue: T\n): {\n get: () => T\n set: (next: T) => void\n} {\n const GLOBAL_KEY = `__gui_${key}__`\n\n type GuiGlobal = typeof globalThis & {\n [GLOBAL_KEY]?: T\n }\n\n function getGlobalState(): T {\n const g = globalThis as GuiGlobal\n if (!g[GLOBAL_KEY]) {\n // reset on module load so reloadReactNative gets a clean state\n // (globalThis persists across reloads but module scope re-evaluates)\n g[GLOBAL_KEY] = defaultValue\n }\n return g[GLOBAL_KEY]!\n }\n\n function setGlobalState(newState: T): void {\n ;(globalThis as GuiGlobal)[GLOBAL_KEY] = newState\n }\n\n return {\n get: getGlobalState,\n set: setGlobalState,\n }\n}\n"
9
+ "export function createGlobalState<T extends { enabled: boolean }>(\n key: string,\n defaultValue: T\n): {\n get: () => T\n set: (next: T) => void\n} {\n const GLOBAL_KEY = `__hanzogui_${key}__`\n\n type GuiGlobal = typeof globalThis & {\n [GLOBAL_KEY]?: T\n }\n\n function getGlobalState(): T {\n const g = globalThis as GuiGlobal\n if (!g[GLOBAL_KEY]) {\n // reset on module load so reloadReactNative gets a clean state\n // (globalThis persists across reloads but module scope re-evaluates)\n g[GLOBAL_KEY] = defaultValue\n }\n return g[GLOBAL_KEY]!\n }\n\n function setGlobalState(newState: T): void {\n ;(globalThis as GuiGlobal)[GLOBAL_KEY] = newState\n }\n\n return {\n get: getGlobalState,\n set: setGlobalState,\n }\n}\n"
10
10
  ]
11
11
  }
package/types/index.d.ts CHANGED
@@ -22,7 +22,7 @@ export type { NativePortalState, GestureState, WorkletsState, SafeAreaState, Saf
22
22
  export { getPortal } from "./portalState";
23
23
  export type { PortalAccessor } from "./portalState";
24
24
  export { getGestureHandler } from "./gestureState";
25
- export { claimExternalPressOwnership as unstable_claimExternalPressOwnership, releaseExternalPressOwnership as unstable_releaseExternalPressOwnership } from "./gestureState";
25
+ export { claimExternalPressOwnership as unstable_claimExternalPressOwnership, hasExternalPressOwnership as unstable_hasExternalPressOwnership, releaseExternalPressOwnership as unstable_releaseExternalPressOwnership } from "./gestureState";
26
26
  export type { ExternalPressOwnershipToken, GestureHandlerAccessor, PressGestureConfig } from "./gestureState";
27
27
  export type { GestureHandlerConfig } from "./setup-gesture-handler";
28
28
  export { getWorklets } from "./workletsState";