mirror of
https://github.com/hanzoai/gui.git
synced 2026-10-25 02:31:41 +00:00
This repo could never sync. There was NO merge-base with tamagui — 372 commits
here against 15,791 there, unrelated histories — because it began as a
copy-paste rebrand with a flattened layout. Every "sync" since was a hand
re-application that drifted, which is why the tree sat on 7.3.1 while upstream
shipped fixes we then rediscovered by hand.
This commit's PARENT IS TAMAGUI. A future sync is `git merge tama/main` then
`node scripts/rebrand.mjs` — a real merge, because the history is now shared.
174 packages: 166 upstream, our 8 restored (chrome, shell, telemetry, recipes,
get, font-geist-{sans,mono}, @hanzo/booker), bento-get gained. 170/170 build.
Our patches to files UPSTREAM ALSO OWNS are carried, which the package-level
delta missed and which was where the value actually was:
- useMedia's server snapshot (`initState ?? getMedia()`). Upstream still
returns undefined there, so every Tamagui Dialog kills a prerender — the
exact crash hanzo.app spent a day on.
- data-slot on web host nodes, absent upstream entirely.
Both applied ONTO upstream's newer files (it rewrote useMedia +150/-63 and
createComponent +160/-55), marked `PATCH (ours, carried across the transplant)`.
Seven traps are encoded in scripts/rebrand.mjs, each found by a gate:
skip itself and lockfiles; special-case the double-branded names in BOTH scoped
and bare form; map path strings, not just paths; drop a level for the flattened
group; check disk rather than a stale index.
Rust build artifacts are NOT committed. An earlier draft of this commit swept
apps/desktop/src-tauri/target/debug in via `git add -A` — 3,859 files including
a 272MB .a, over GitHub's 100MB cap, so the push could never have landed. They
are gitignored now.
Co-Authored-By: Hanzo Dev <[email protected]>
4.7 KiB
4.7 KiB
Plan: Conditional RNGH Press Handling
Goal
Use RNGH for press events when @hanzogui/native/setup-gesture-handler has been called, otherwise fall back to RN's usePressability. Avoid re-parenting by tracking hasEverHadPressEvents in stateRef.
Key Points
runOnJS(true)is required on Tap/LongPress gestures for React callbacks to workGesture.Exclusive(longPress, tap)ensures only one fires based on timing- Inline require
usePressabilityonly when RNGH is not enabled (avoids RN internal dep when not needed) - Track
hasEverHadPressEventsin stateRef to maintain consistent tree structure
Changes
1. pkgs/core/web/src/types.tsx
Add to GuiComponentStateRef (around line 535):
export type GuiComponentStateRef = {
// ... existing fields ...
hasEverHadPressEvents?: boolean // <-- add this
}
2. pkgs/core/web/src/eventHandling.native.ts
import React from 'react'
import { composeEventHandlers } from '@hanzogui/helpers'
import { getGestureHandler } from '@hanzogui/native'
import type { GuiComponentStateRef } from './types'
// web events not used on native
export function getWebEvents() {
return {}
}
const dontComposePressabilityKeys: Record<string, boolean> = {
onBlur: true,
onFocus: true,
}
export function usePressHandling(
events: any,
viewProps: any,
stateRef: { current: GuiComponentStateRef }
) {
const hasPressEvents =
events?.onPress || events?.onPressIn || events?.onPressOut || events?.onLongPress
const gh = getGestureHandler()
// track if we ever had press events to avoid re-parenting
if (hasPressEvents) {
stateRef.current.hasEverHadPressEvents = true
}
if (gh.isEnabled && hasPressEvents) {
// RNGH path - return gesture for wrapping
return gh.createPressGesture({
onPressIn: events.onPressIn,
onPressOut: events.onPressOut,
onPress: events.onPress,
onLongPress: events.onLongPress,
delayLongPress: events.delayLongPress,
hitSlop: viewProps.hitSlop,
})
}
if (hasPressEvents) {
// fallback - inline require usePressability only when needed
const usePressability =
require('react-native/Libraries/Pressability/usePressability').default
const pressability = usePressability(events)
if (pressability) {
if (viewProps.hitSlop) {
events.hitSlop = viewProps.hitSlop
}
for (const key in pressability) {
const og = viewProps[key]
const val = pressability[key]
viewProps[key] =
og && !dontComposePressabilityKeys[key] ? composeEventHandlers(og, val) : val
}
}
}
return null
}
export function wrapWithGestureDetector(
content: any,
gesture: any,
stateRef: { current: GuiComponentStateRef }
) {
const gh = getGestureHandler()
const { GestureDetector, Gesture } = gh.state
// avoid re-parenting: if we ever had press events, always wrap
const shouldWrap = stateRef.current.hasEverHadPressEvents
if (!GestureDetector || !shouldWrap) {
return content
}
// use actual gesture or no-op Manual gesture to maintain tree structure
const gestureToUse = gesture || Gesture?.Manual()
if (!gestureToUse) {
return content
}
return React.createElement(GestureDetector, { gesture: gestureToUse }, content)
}
How It Works
-
User Setup (optional):
// app entry import '@hanzogui/native/setup-gesture-handler' import { GestureHandlerRootView } from 'react-native-gesture-handler' -
At Runtime:
usePressHandlingchecksgetGestureHandler().isEnabled- If RNGH enabled: creates press gesture via
createPressGesture(), returns it - If RNGH not enabled: inline requires
usePressability, mutates viewProps, returns null
-
Wrapping:
wrapWithGestureDetectorcheckshasEverHadPressEvents- If component ever had press events, wraps with GestureDetector
- Uses actual gesture or
Gesture.Manual()no-op to maintain tree structure - If never had press events, returns content unwrapped
Gesture Callback Flow
| User Action | Callback | RNGH Event |
|---|---|---|
| Touch down | onPressIn |
Tap's onBegin |
| Quick release (< 500ms) | onPress + onPressOut |
Tap's onEnd + onFinalize |
| Hold 500ms+ | onLongPress + onPressOut |
LongPress's onStart + onFinalize |
Testing
- Run existing Detox tests with RNGH enabled
- Run
PressStyleNative.noRngh.test.tsto verify fallback path - Test press style changes work with both paths