@hanzogui/native 2.0.0-rc.41-hanzoai.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +2 -0
- package/dist/cjs/PressBoundary.cjs +92 -0
- package/dist/cjs/PressBoundary.native.js +105 -0
- package/dist/cjs/PressBoundary.native.js.map +1 -0
- package/dist/cjs/burntState.cjs +46 -0
- package/dist/cjs/burntState.native.js +49 -0
- package/dist/cjs/burntState.native.js.map +1 -0
- package/dist/cjs/components.cjs +71 -0
- package/dist/cjs/components.native.js +77 -0
- package/dist/cjs/components.native.js.map +1 -0
- package/dist/cjs/gesture-handler.cjs +36 -0
- package/dist/cjs/gesture-handler.native.js +39 -0
- package/dist/cjs/gesture-handler.native.js.map +1 -0
- package/dist/cjs/gestureState.cjs +178 -0
- package/dist/cjs/gestureState.native.js +191 -0
- package/dist/cjs/gestureState.native.js.map +1 -0
- package/dist/cjs/globalState.cjs +45 -0
- package/dist/cjs/globalState.native.js +48 -0
- package/dist/cjs/globalState.native.js.map +1 -0
- package/dist/cjs/index.cjs +53 -0
- package/dist/cjs/index.native.js +56 -0
- package/dist/cjs/index.native.js.map +1 -0
- package/dist/cjs/keyboardControllerState.cjs +49 -0
- package/dist/cjs/keyboardControllerState.native.js +52 -0
- package/dist/cjs/keyboardControllerState.native.js.map +1 -0
- package/dist/cjs/linearGradientState.cjs +45 -0
- package/dist/cjs/linearGradientState.native.js +48 -0
- package/dist/cjs/linearGradientState.native.js.map +1 -0
- package/dist/cjs/nativeMenuContext.cjs +29 -0
- package/dist/cjs/nativeMenuContext.native.js +32 -0
- package/dist/cjs/nativeMenuContext.native.js.map +1 -0
- package/dist/cjs/portalState.cjs +45 -0
- package/dist/cjs/portalState.native.js +48 -0
- package/dist/cjs/portalState.native.js.map +1 -0
- package/dist/cjs/safeAreaState.cjs +73 -0
- package/dist/cjs/safeAreaState.native.js +76 -0
- package/dist/cjs/safeAreaState.native.js.map +1 -0
- package/dist/cjs/setup-burnt.cjs +17 -0
- package/dist/cjs/setup-burnt.native.js +20 -0
- package/dist/cjs/setup-burnt.native.js.map +1 -0
- package/dist/cjs/setup-expo-linear-gradient.cjs +16 -0
- package/dist/cjs/setup-expo-linear-gradient.native.js +19 -0
- package/dist/cjs/setup-expo-linear-gradient.native.js.map +1 -0
- package/dist/cjs/setup-gesture-handler.cjs +67 -0
- package/dist/cjs/setup-gesture-handler.native.js +70 -0
- package/dist/cjs/setup-gesture-handler.native.js.map +1 -0
- package/dist/cjs/setup-keyboard-controller.cjs +0 -0
- package/dist/cjs/setup-keyboard-controller.native.js +36 -0
- package/dist/cjs/setup-keyboard-controller.native.js.map +1 -0
- package/dist/cjs/setup-safe-area.cjs +3 -0
- package/dist/cjs/setup-safe-area.native.js +23 -0
- package/dist/cjs/setup-safe-area.native.js.map +1 -0
- package/dist/cjs/setup-teleport.cjs +18 -0
- package/dist/cjs/setup-teleport.native.js +21 -0
- package/dist/cjs/setup-teleport.native.js.map +1 -0
- package/dist/cjs/setup-worklets.cjs +21 -0
- package/dist/cjs/setup-worklets.native.js +24 -0
- package/dist/cjs/setup-worklets.native.js.map +1 -0
- package/dist/cjs/setup-zeego.cjs +22 -0
- package/dist/cjs/setup-zeego.native.js +25 -0
- package/dist/cjs/setup-zeego.native.js.map +1 -0
- package/dist/cjs/types.cjs +18 -0
- package/dist/cjs/types.native.js +21 -0
- package/dist/cjs/types.native.js.map +1 -0
- package/dist/cjs/workletsState.cjs +48 -0
- package/dist/cjs/workletsState.native.js +51 -0
- package/dist/cjs/workletsState.native.js.map +1 -0
- package/dist/cjs/zeegoState.cjs +46 -0
- package/dist/cjs/zeegoState.native.js +49 -0
- package/dist/cjs/zeegoState.native.js.map +1 -0
- package/dist/esm/PressBoundary.mjs +56 -0
- package/dist/esm/PressBoundary.mjs.map +1 -0
- package/dist/esm/PressBoundary.native.js +66 -0
- package/dist/esm/PressBoundary.native.js.map +1 -0
- package/dist/esm/burntState.mjs +21 -0
- package/dist/esm/burntState.mjs.map +1 -0
- package/dist/esm/burntState.native.js +21 -0
- package/dist/esm/burntState.native.js.map +1 -0
- package/dist/esm/components.mjs +44 -0
- package/dist/esm/components.mjs.map +1 -0
- package/dist/esm/components.native.js +47 -0
- package/dist/esm/components.native.js.map +1 -0
- package/dist/esm/gesture-handler.mjs +6 -0
- package/dist/esm/gesture-handler.mjs.map +1 -0
- package/dist/esm/gesture-handler.native.js +6 -0
- package/dist/esm/gesture-handler.native.js.map +1 -0
- package/dist/esm/gestureState.mjs +151 -0
- package/dist/esm/gestureState.mjs.map +1 -0
- package/dist/esm/gestureState.native.js +161 -0
- package/dist/esm/gestureState.native.js.map +1 -0
- package/dist/esm/globalState.mjs +20 -0
- package/dist/esm/globalState.mjs.map +1 -0
- package/dist/esm/globalState.native.js +20 -0
- package/dist/esm/globalState.native.js.map +1 -0
- package/dist/esm/index.js +13 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/index.mjs +13 -0
- package/dist/esm/index.mjs.map +1 -0
- package/dist/esm/index.native.js +13 -0
- package/dist/esm/index.native.js.map +1 -0
- package/dist/esm/keyboardControllerState.mjs +22 -0
- package/dist/esm/keyboardControllerState.mjs.map +1 -0
- package/dist/esm/keyboardControllerState.native.js +22 -0
- package/dist/esm/keyboardControllerState.native.js.map +1 -0
- package/dist/esm/linearGradientState.mjs +20 -0
- package/dist/esm/linearGradientState.mjs.map +1 -0
- package/dist/esm/linearGradientState.native.js +20 -0
- package/dist/esm/linearGradientState.native.js.map +1 -0
- package/dist/esm/nativeMenuContext.mjs +4 -0
- package/dist/esm/nativeMenuContext.mjs.map +1 -0
- package/dist/esm/nativeMenuContext.native.js +4 -0
- package/dist/esm/nativeMenuContext.native.js.map +1 -0
- package/dist/esm/portalState.mjs +20 -0
- package/dist/esm/portalState.mjs.map +1 -0
- package/dist/esm/portalState.native.js +20 -0
- package/dist/esm/portalState.native.js.map +1 -0
- package/dist/esm/safeAreaState.mjs +48 -0
- package/dist/esm/safeAreaState.mjs.map +1 -0
- package/dist/esm/safeAreaState.native.js +48 -0
- package/dist/esm/safeAreaState.native.js.map +1 -0
- package/dist/esm/setup-burnt.mjs +18 -0
- package/dist/esm/setup-burnt.mjs.map +1 -0
- package/dist/esm/setup-burnt.native.js +18 -0
- package/dist/esm/setup-burnt.native.js.map +1 -0
- package/dist/esm/setup-expo-linear-gradient.mjs +17 -0
- package/dist/esm/setup-expo-linear-gradient.mjs.map +1 -0
- package/dist/esm/setup-expo-linear-gradient.native.js +17 -0
- package/dist/esm/setup-expo-linear-gradient.native.js.map +1 -0
- package/dist/esm/setup-gesture-handler.mjs +41 -0
- package/dist/esm/setup-gesture-handler.mjs.map +1 -0
- package/dist/esm/setup-gesture-handler.native.js +41 -0
- package/dist/esm/setup-gesture-handler.native.js.map +1 -0
- package/dist/esm/setup-keyboard-controller.mjs +2 -0
- package/dist/esm/setup-keyboard-controller.mjs.map +1 -0
- package/dist/esm/setup-keyboard-controller.native.js +34 -0
- package/dist/esm/setup-keyboard-controller.native.js.map +1 -0
- package/dist/esm/setup-safe-area.mjs +4 -0
- package/dist/esm/setup-safe-area.mjs.map +1 -0
- package/dist/esm/setup-safe-area.native.js +21 -0
- package/dist/esm/setup-safe-area.native.js.map +1 -0
- package/dist/esm/setup-teleport.mjs +19 -0
- package/dist/esm/setup-teleport.mjs.map +1 -0
- package/dist/esm/setup-teleport.native.js +19 -0
- package/dist/esm/setup-teleport.native.js.map +1 -0
- package/dist/esm/setup-worklets.mjs +22 -0
- package/dist/esm/setup-worklets.mjs.map +1 -0
- package/dist/esm/setup-worklets.native.js +22 -0
- package/dist/esm/setup-worklets.native.js.map +1 -0
- package/dist/esm/setup-zeego.mjs +23 -0
- package/dist/esm/setup-zeego.mjs.map +1 -0
- package/dist/esm/setup-zeego.native.js +23 -0
- package/dist/esm/setup-zeego.native.js.map +1 -0
- package/dist/esm/types.mjs +2 -0
- package/dist/esm/types.mjs.map +1 -0
- package/dist/esm/types.native.js +2 -0
- package/dist/esm/types.native.js.map +1 -0
- package/dist/esm/workletsState.mjs +23 -0
- package/dist/esm/workletsState.mjs.map +1 -0
- package/dist/esm/workletsState.native.js +23 -0
- package/dist/esm/workletsState.native.js.map +1 -0
- package/dist/esm/zeegoState.mjs +21 -0
- package/dist/esm/zeegoState.mjs.map +1 -0
- package/dist/esm/zeegoState.native.js +21 -0
- package/dist/esm/zeegoState.native.js.map +1 -0
- package/gesture-handler/index.cjs +1 -0
- package/gesture-handler/index.js +1 -0
- package/gesture-handler/index.native.cjs +1 -0
- package/gesture-handler/index.native.js +1 -0
- package/gesture-handler.cjs +1 -0
- package/package.json +208 -0
- package/setup-burnt/index.cjs +2 -0
- package/setup-burnt/index.js +2 -0
- package/setup-burnt/index.native.cjs +2 -0
- package/setup-burnt/index.native.js +2 -0
- package/setup-burnt.cjs +1 -0
- package/setup-expo-linear-gradient/index.cjs +2 -0
- package/setup-expo-linear-gradient/index.js +2 -0
- package/setup-expo-linear-gradient/index.native.cjs +2 -0
- package/setup-expo-linear-gradient/index.native.js +2 -0
- package/setup-expo-linear-gradient.cjs +1 -0
- package/setup-gesture-handler/index.cjs +2 -0
- package/setup-gesture-handler/index.js +2 -0
- package/setup-gesture-handler/index.native.cjs +2 -0
- package/setup-gesture-handler/index.native.js +2 -0
- package/setup-gesture-handler.cjs +1 -0
- package/setup-keyboard-controller/index.cjs +2 -0
- package/setup-keyboard-controller/index.js +2 -0
- package/setup-keyboard-controller/index.native.cjs +2 -0
- package/setup-keyboard-controller/index.native.js +2 -0
- package/setup-keyboard-controller.cjs +1 -0
- package/setup-safe-area/index.cjs +2 -0
- package/setup-safe-area/index.js +2 -0
- package/setup-safe-area/index.native.cjs +2 -0
- package/setup-safe-area/index.native.js +2 -0
- package/setup-safe-area.cjs +1 -0
- package/setup-teleport/index.cjs +2 -0
- package/setup-teleport/index.js +2 -0
- package/setup-teleport/index.native.cjs +2 -0
- package/setup-teleport/index.native.js +2 -0
- package/setup-teleport.cjs +1 -0
- package/setup-worklets/index.cjs +2 -0
- package/setup-worklets/index.js +2 -0
- package/setup-worklets/index.native.cjs +2 -0
- package/setup-worklets/index.native.js +2 -0
- package/setup-worklets.cjs +1 -0
- package/setup-zeego/index.cjs +2 -0
- package/setup-zeego/index.js +2 -0
- package/setup-zeego/index.native.cjs +2 -0
- package/setup-zeego/index.native.js +2 -0
- package/setup-zeego.cjs +1 -0
- package/src/PressBoundary.tsx +81 -0
- package/src/burntState.ts +28 -0
- package/src/components.tsx +48 -0
- package/src/gesture-handler.ts +17 -0
- package/src/gestureState.ts +260 -0
- package/src/globalState.ts +32 -0
- package/src/index.ts +88 -0
- package/src/keyboardControllerState.ts +29 -0
- package/src/linearGradientState.ts +27 -0
- package/src/nativeMenuContext.ts +4 -0
- package/src/portalState.ts +27 -0
- package/src/safeAreaState.ts +50 -0
- package/src/setup-burnt.ts +38 -0
- package/src/setup-expo-linear-gradient.ts +36 -0
- package/src/setup-gesture-handler.ts +78 -0
- package/src/setup-keyboard-controller.ts +60 -0
- package/src/setup-keyboard-controller.web.ts +6 -0
- package/src/setup-safe-area.ts +56 -0
- package/src/setup-teleport.ts +36 -0
- package/src/setup-worklets.ts +49 -0
- package/src/setup-zeego.ts +42 -0
- package/src/types.ts +88 -0
- package/src/workletsState.ts +30 -0
- package/src/zeegoState.ts +28 -0
- package/tsconfig.json +12 -0
- package/types/PressBoundary.d.ts +14 -0
- package/types/PressBoundary.d.ts.map +11 -0
- package/types/burntState.d.ts +9 -0
- package/types/burntState.d.ts.map +11 -0
- package/types/components.d.ts +20 -0
- package/types/components.d.ts.map +11 -0
- package/types/gesture-handler.d.ts +8 -0
- package/types/gesture-handler.d.ts.map +11 -0
- package/types/gestureState.d.ts +29 -0
- package/types/gestureState.d.ts.map +11 -0
- package/types/globalState.d.ts +8 -0
- package/types/globalState.d.ts.map +11 -0
- package/types/index.d.ts +43 -0
- package/types/index.d.ts.map +11 -0
- package/types/keyboardControllerState.d.ts +7 -0
- package/types/keyboardControllerState.d.ts.map +11 -0
- package/types/linearGradientState.d.ts +9 -0
- package/types/linearGradientState.d.ts.map +11 -0
- package/types/nativeMenuContext.d.ts +4 -0
- package/types/nativeMenuContext.d.ts.map +11 -0
- package/types/portalState.d.ts +9 -0
- package/types/portalState.d.ts.map +11 -0
- package/types/safeAreaState.d.ts +13 -0
- package/types/safeAreaState.d.ts.map +11 -0
- package/types/setup-burnt.d.ts +3 -0
- package/types/setup-burnt.d.ts.map +11 -0
- package/types/setup-expo-linear-gradient.d.ts +3 -0
- package/types/setup-expo-linear-gradient.d.ts.map +11 -0
- package/types/setup-gesture-handler.d.ts +10 -0
- package/types/setup-gesture-handler.d.ts.map +11 -0
- package/types/setup-keyboard-controller.d.ts +3 -0
- package/types/setup-keyboard-controller.d.ts.map +11 -0
- package/types/setup-keyboard-controller.web.d.ts +2 -0
- package/types/setup-keyboard-controller.web.d.ts.map +11 -0
- package/types/setup-safe-area.d.ts +3 -0
- package/types/setup-safe-area.d.ts.map +11 -0
- package/types/setup-teleport.d.ts +3 -0
- package/types/setup-teleport.d.ts.map +11 -0
- package/types/setup-worklets.d.ts +3 -0
- package/types/setup-worklets.d.ts.map +11 -0
- package/types/setup-zeego.d.ts +3 -0
- package/types/setup-zeego.d.ts.map +11 -0
- package/types/types.d.ts +76 -0
- package/types/types.d.ts.map +11 -0
- package/types/workletsState.d.ts +9 -0
- package/types/workletsState.d.ts.map +11 -0
- package/types/zeegoState.d.ts +9 -0
- package/types/zeegoState.d.ts.map +11 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
Object.assign(module.exports, require('./dist/cjs/setup-worklets.cjs'))
|
package/setup-zeego.cjs
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
Object.assign(module.exports, require('./dist/cjs/setup-zeego.cjs'))
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
import { View, type ViewProps } from 'react-native'
|
|
3
|
+
import {
|
|
4
|
+
claimExternalPressOwnership,
|
|
5
|
+
releaseExternalPressOwnership,
|
|
6
|
+
type ExternalPressOwnershipToken,
|
|
7
|
+
} from './gestureState'
|
|
8
|
+
|
|
9
|
+
export interface PressBoundaryProps extends ViewProps {
|
|
10
|
+
enabled?: boolean
|
|
11
|
+
/**
|
|
12
|
+
* Alias for enabling the boundary. The behavior is limited to Hanzogui's
|
|
13
|
+
* shared press ownership and does not patch arbitrary RN bubbling.
|
|
14
|
+
*/
|
|
15
|
+
stopPropagation?: boolean
|
|
16
|
+
debugName?: string | null
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function composeFirst<T extends (...args: any[]) => void>(ours: T, theirs?: T) {
|
|
20
|
+
return (...args: Parameters<T>) => {
|
|
21
|
+
ours(...args)
|
|
22
|
+
theirs?.(...args)
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function composeLast<T extends (...args: any[]) => void>(theirs: T | undefined, ours: T) {
|
|
27
|
+
return (...args: Parameters<T>) => {
|
|
28
|
+
theirs?.(...args)
|
|
29
|
+
ours(...args)
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export const PressBoundary: React.ForwardRefExoticComponent<
|
|
34
|
+
PressBoundaryProps & React.RefAttributes<View>
|
|
35
|
+
> = React.forwardRef<View, PressBoundaryProps>(function PressBoundary(
|
|
36
|
+
{
|
|
37
|
+
enabled,
|
|
38
|
+
stopPropagation,
|
|
39
|
+
debugName,
|
|
40
|
+
onTouchStart,
|
|
41
|
+
onTouchEnd,
|
|
42
|
+
onTouchCancel,
|
|
43
|
+
onResponderGrant,
|
|
44
|
+
onResponderRelease,
|
|
45
|
+
onResponderTerminate,
|
|
46
|
+
...props
|
|
47
|
+
},
|
|
48
|
+
forwardedRef
|
|
49
|
+
) {
|
|
50
|
+
const tokenRef = React.useRef<ExternalPressOwnershipToken | null>(null)
|
|
51
|
+
const isEnabled = enabled ?? stopPropagation ?? true
|
|
52
|
+
|
|
53
|
+
const claim = React.useCallback((): void => {
|
|
54
|
+
if (!isEnabled) return
|
|
55
|
+
if (tokenRef.current) {
|
|
56
|
+
releaseExternalPressOwnership(tokenRef.current, debugName)
|
|
57
|
+
}
|
|
58
|
+
tokenRef.current = claimExternalPressOwnership(debugName)
|
|
59
|
+
}, [debugName, isEnabled])
|
|
60
|
+
|
|
61
|
+
const release = React.useCallback((): void => {
|
|
62
|
+
if (!tokenRef.current) return
|
|
63
|
+
releaseExternalPressOwnership(tokenRef.current, debugName)
|
|
64
|
+
tokenRef.current = null
|
|
65
|
+
}, [debugName])
|
|
66
|
+
|
|
67
|
+
React.useEffect(() => release, [release])
|
|
68
|
+
|
|
69
|
+
return (
|
|
70
|
+
<View
|
|
71
|
+
ref={forwardedRef}
|
|
72
|
+
{...props}
|
|
73
|
+
onTouchStart={composeFirst(claim, onTouchStart)}
|
|
74
|
+
onTouchEnd={composeLast(onTouchEnd, release)}
|
|
75
|
+
onTouchCancel={composeLast(onTouchCancel, release)}
|
|
76
|
+
onResponderGrant={composeFirst(claim, onResponderGrant)}
|
|
77
|
+
onResponderRelease={composeLast(onResponderRelease, release)}
|
|
78
|
+
onResponderTerminate={composeLast(onResponderTerminate, release)}
|
|
79
|
+
/>
|
|
80
|
+
)
|
|
81
|
+
})
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { createGlobalState } from './globalState'
|
|
2
|
+
import type { BurntState } from './types'
|
|
3
|
+
|
|
4
|
+
const state = createGlobalState<BurntState>(`burnt`, {
|
|
5
|
+
enabled: false,
|
|
6
|
+
toast: null,
|
|
7
|
+
dismissAllAlerts: null,
|
|
8
|
+
})
|
|
9
|
+
|
|
10
|
+
export interface BurntAccessor {
|
|
11
|
+
readonly isEnabled: boolean
|
|
12
|
+
readonly state: BurntState
|
|
13
|
+
set(newState: BurntState): void
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function getBurnt(): BurntAccessor {
|
|
17
|
+
return {
|
|
18
|
+
get isEnabled(): boolean {
|
|
19
|
+
return state.get().enabled
|
|
20
|
+
},
|
|
21
|
+
get state(): BurntState {
|
|
22
|
+
return state.get()
|
|
23
|
+
},
|
|
24
|
+
set(newState: BurntState): void {
|
|
25
|
+
state.set(newState)
|
|
26
|
+
},
|
|
27
|
+
}
|
|
28
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import type { ReactNode } from 'react'
|
|
2
|
+
import { getPortal } from './portalState'
|
|
3
|
+
import type {
|
|
4
|
+
NativePortalProps,
|
|
5
|
+
NativePortalHostProps,
|
|
6
|
+
NativePortalProviderProps,
|
|
7
|
+
} from './types'
|
|
8
|
+
|
|
9
|
+
export type { NativePortalProps, NativePortalHostProps, NativePortalProviderProps }
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Renders children into a teleport Portal when available.
|
|
13
|
+
* Returns null when teleport is not set up (allows fallback handling by caller).
|
|
14
|
+
*/
|
|
15
|
+
export function NativePortal({
|
|
16
|
+
hostName = 'root',
|
|
17
|
+
children,
|
|
18
|
+
}: NativePortalProps): ReactNode {
|
|
19
|
+
const state = getPortal().state
|
|
20
|
+
if (state.type !== 'teleport') return null
|
|
21
|
+
|
|
22
|
+
const { Portal } = (globalThis as any).__hanzogui_teleport
|
|
23
|
+
return <Portal hostName={hostName}>{children}</Portal>
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Renders a teleport PortalHost when available.
|
|
28
|
+
* Returns null when teleport is not set up.
|
|
29
|
+
*/
|
|
30
|
+
export function NativePortalHost({ name }: NativePortalHostProps): ReactNode {
|
|
31
|
+
const state = getPortal().state
|
|
32
|
+
if (state.type !== 'teleport') return null
|
|
33
|
+
|
|
34
|
+
const { PortalHost } = (globalThis as any).__hanzogui_teleport
|
|
35
|
+
return <PortalHost name={name} />
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Wraps children with teleport PortalProvider when available.
|
|
40
|
+
* Returns children as-is when teleport is not set up.
|
|
41
|
+
*/
|
|
42
|
+
export function NativePortalProvider({ children }: NativePortalProviderProps): ReactNode {
|
|
43
|
+
const state = getPortal().state
|
|
44
|
+
if (state.type !== 'teleport') return <>{children}</>
|
|
45
|
+
|
|
46
|
+
const { PortalProvider } = (globalThis as any).__hanzogui_teleport
|
|
47
|
+
return <PortalProvider>{children}</PortalProvider>
|
|
48
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export { getGestureHandler } from './gestureState'
|
|
2
|
+
export type {
|
|
3
|
+
ExternalPressOwnershipToken,
|
|
4
|
+
GestureHandlerAccessor,
|
|
5
|
+
PressGestureConfig,
|
|
6
|
+
} from './gestureState'
|
|
7
|
+
export {
|
|
8
|
+
claimExternalPressOwnership as unstable_claimExternalPressOwnership,
|
|
9
|
+
releaseExternalPressOwnership as unstable_releaseExternalPressOwnership,
|
|
10
|
+
} from './gestureState'
|
|
11
|
+
export { PressBoundary } from './PressBoundary'
|
|
12
|
+
export type { PressBoundaryProps } from './PressBoundary'
|
|
13
|
+
export {
|
|
14
|
+
getGestureHandlerConfig,
|
|
15
|
+
setupGestureHandler,
|
|
16
|
+
type GestureHandlerConfig,
|
|
17
|
+
} from './setup-gesture-handler'
|
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
import { createGlobalState } from './globalState'
|
|
2
|
+
import type { GestureState } from './types'
|
|
3
|
+
|
|
4
|
+
const state = createGlobalState<GestureState>(`gesture`, {
|
|
5
|
+
enabled: false,
|
|
6
|
+
Gesture: null,
|
|
7
|
+
GestureDetector: null,
|
|
8
|
+
ScrollView: null,
|
|
9
|
+
})
|
|
10
|
+
|
|
11
|
+
let pressGestureDebugId = 0
|
|
12
|
+
let externalPressDebugId = 0
|
|
13
|
+
|
|
14
|
+
type PressOwnerSource = 'internal' | 'external' | null
|
|
15
|
+
|
|
16
|
+
function getEventPointerId(e: any): number | null {
|
|
17
|
+
const pointerId =
|
|
18
|
+
e?.pointerId ??
|
|
19
|
+
e?.pointer?.id ??
|
|
20
|
+
e?.event?.pointerId ??
|
|
21
|
+
e?.event?.pointer?.id ??
|
|
22
|
+
e?.nativeEvent?.pointerId ??
|
|
23
|
+
e?.nativeEvent?.id ??
|
|
24
|
+
e?.event?.nativeEvent?.pointerId ??
|
|
25
|
+
e?.event?.nativeEvent?.id ??
|
|
26
|
+
null
|
|
27
|
+
|
|
28
|
+
return pointerId == null || Number.isNaN(pointerId) ? null : Number(pointerId)
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Global press coordination - ensures only innermost pressable fires press events,
|
|
33
|
+
* matching RN Pressable/responder system semantics where deepest component wins.
|
|
34
|
+
* Uses a grace period to allow child gestures to steal ownership from parent,
|
|
35
|
+
* since RNGH fires parent gestures before child gestures.
|
|
36
|
+
*/
|
|
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,
|
|
42
|
+
timestamp: 0,
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export interface Insets {
|
|
46
|
+
top?: number
|
|
47
|
+
left?: number
|
|
48
|
+
bottom?: number
|
|
49
|
+
right?: number
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export type PressGestureConfig = {
|
|
53
|
+
debugName?: string | null
|
|
54
|
+
onPressIn?: (e: any) => void
|
|
55
|
+
onPressOut?: (e: any) => void
|
|
56
|
+
onPress?: (e: any) => void
|
|
57
|
+
onLongPress?: (e: any) => void
|
|
58
|
+
delayLongPress?: number
|
|
59
|
+
hitSlop?: number | Insets | null
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export interface GestureHandlerAccessor {
|
|
63
|
+
readonly isEnabled: boolean
|
|
64
|
+
readonly state: GestureState
|
|
65
|
+
set(updates: Partial<GestureState>): void
|
|
66
|
+
disable(): void
|
|
67
|
+
createPressGesture(config: PressGestureConfig): any
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export type ExternalPressOwnershipToken = object
|
|
71
|
+
|
|
72
|
+
function resetPressOwner() {
|
|
73
|
+
pressState.owner = null
|
|
74
|
+
pressState.ownerId = null
|
|
75
|
+
pressState.ownerSource = null
|
|
76
|
+
pressState.ownerPointerId = null
|
|
77
|
+
pressState.timestamp = 0
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function resetStaleOwner(now: number, debugName?: string | null) {
|
|
81
|
+
if (now - pressState.timestamp > 2000) {
|
|
82
|
+
resetPressOwner()
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export function claimExternalPressOwnership(
|
|
87
|
+
debugName?: string | null
|
|
88
|
+
): ExternalPressOwnershipToken {
|
|
89
|
+
const now = Date.now()
|
|
90
|
+
resetStaleOwner(now, debugName)
|
|
91
|
+
|
|
92
|
+
const token = {}
|
|
93
|
+
const ownerId = ++externalPressDebugId
|
|
94
|
+
|
|
95
|
+
pressState.owner = token
|
|
96
|
+
pressState.ownerId = ownerId
|
|
97
|
+
pressState.ownerSource = 'external'
|
|
98
|
+
pressState.timestamp = now
|
|
99
|
+
|
|
100
|
+
return token
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export function releaseExternalPressOwnership(
|
|
104
|
+
token: ExternalPressOwnershipToken | null | undefined,
|
|
105
|
+
debugName?: string | null
|
|
106
|
+
): void {
|
|
107
|
+
if (!token || pressState.owner !== token) {
|
|
108
|
+
return
|
|
109
|
+
}
|
|
110
|
+
resetPressOwner()
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export function getGestureHandler(): GestureHandlerAccessor {
|
|
114
|
+
return {
|
|
115
|
+
get isEnabled(): boolean {
|
|
116
|
+
return state.get().enabled
|
|
117
|
+
},
|
|
118
|
+
get state(): GestureState {
|
|
119
|
+
return state.get()
|
|
120
|
+
},
|
|
121
|
+
set(updates: Partial<GestureState>): void {
|
|
122
|
+
Object.assign(state.get(), updates)
|
|
123
|
+
},
|
|
124
|
+
|
|
125
|
+
disable(): void {
|
|
126
|
+
state.get().enabled = false
|
|
127
|
+
},
|
|
128
|
+
|
|
129
|
+
createPressGesture(config: PressGestureConfig): any {
|
|
130
|
+
const { Gesture } = state.get()
|
|
131
|
+
if (!Gesture) return null
|
|
132
|
+
|
|
133
|
+
const longPressDuration = config.delayLongPress ?? 500
|
|
134
|
+
|
|
135
|
+
// unique token for this gesture instance - used to track ownership
|
|
136
|
+
const myToken = {}
|
|
137
|
+
const myDebugId = ++pressGestureDebugId
|
|
138
|
+
let didLongPress = false
|
|
139
|
+
let didPressIn = false
|
|
140
|
+
let pressInTimer: ReturnType<typeof setTimeout> | null = null
|
|
141
|
+
|
|
142
|
+
// Grace period for child gestures to steal ownership from parent.
|
|
143
|
+
// RNGH fires parent before child, but we want innermost to win.
|
|
144
|
+
// Claims typically span a few ms, 24ms gives enough room to observe
|
|
145
|
+
// ordering while debugging slower frames.
|
|
146
|
+
const GRACE_PERIOD_MS = process.env.TAMAGUI_RNGH_PRESS_DELAY
|
|
147
|
+
? +process.env.TAMAGUI_RNGH_PRESS_DELAY
|
|
148
|
+
: 24
|
|
149
|
+
|
|
150
|
+
const tryClaimOwnership = (e: any) => {
|
|
151
|
+
const now = Date.now()
|
|
152
|
+
resetStaleOwner(now, config.debugName)
|
|
153
|
+
|
|
154
|
+
const currentPointerId = getEventPointerId(e)
|
|
155
|
+
const isSameTouchPointer =
|
|
156
|
+
currentPointerId == null ||
|
|
157
|
+
pressState.ownerPointerId == null ||
|
|
158
|
+
pressState.ownerPointerId === currentPointerId
|
|
159
|
+
|
|
160
|
+
if (
|
|
161
|
+
pressState.owner === null ||
|
|
162
|
+
(pressState.ownerSource === 'internal' && isSameTouchPointer)
|
|
163
|
+
) {
|
|
164
|
+
pressState.owner = myToken
|
|
165
|
+
pressState.ownerId = myDebugId
|
|
166
|
+
pressState.ownerSource = 'internal'
|
|
167
|
+
pressState.ownerPointerId = currentPointerId
|
|
168
|
+
pressState.timestamp = now
|
|
169
|
+
}
|
|
170
|
+
return pressState.owner === myToken
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
const isOwner = () => pressState.owner === myToken
|
|
174
|
+
|
|
175
|
+
const releaseOwnership = () => {
|
|
176
|
+
if (pressInTimer) {
|
|
177
|
+
clearTimeout(pressInTimer)
|
|
178
|
+
pressInTimer = null
|
|
179
|
+
}
|
|
180
|
+
if (pressState.owner === myToken) {
|
|
181
|
+
resetPressOwner()
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
const firePressIn = (e: any) => {
|
|
186
|
+
if (!didPressIn && isOwner()) {
|
|
187
|
+
didPressIn = true
|
|
188
|
+
config.onPressIn?.(e)
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
const schedulePressIn = (e: any) => {
|
|
193
|
+
if (pressInTimer) {
|
|
194
|
+
clearTimeout(pressInTimer)
|
|
195
|
+
}
|
|
196
|
+
pressInTimer = setTimeout(() => {
|
|
197
|
+
pressInTimer = null
|
|
198
|
+
if (isOwner()) {
|
|
199
|
+
firePressIn(e)
|
|
200
|
+
}
|
|
201
|
+
}, GRACE_PERIOD_MS + 1)
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// Tap gesture for regular presses
|
|
205
|
+
// Use long maxDuration to not cancel during long presses
|
|
206
|
+
const tap = Gesture.Tap()
|
|
207
|
+
.runOnJS(true)
|
|
208
|
+
.maxDuration(10000) // allow very long presses
|
|
209
|
+
.onBegin((e: unknown) => {
|
|
210
|
+
didLongPress = false
|
|
211
|
+
didPressIn = false
|
|
212
|
+
tryClaimOwnership(e)
|
|
213
|
+
// Defer onPressIn until after the grace window so child pressables
|
|
214
|
+
// can steal ownership, but flush it on tap end for very fast taps.
|
|
215
|
+
schedulePressIn(e)
|
|
216
|
+
})
|
|
217
|
+
.onEnd((e: unknown) => {
|
|
218
|
+
if (isOwner() && !didLongPress) {
|
|
219
|
+
firePressIn(e)
|
|
220
|
+
config.onPress?.(e)
|
|
221
|
+
}
|
|
222
|
+
})
|
|
223
|
+
.onFinalize((e: unknown) => {
|
|
224
|
+
if (isOwner()) {
|
|
225
|
+
config.onPressOut?.(e)
|
|
226
|
+
releaseOwnership()
|
|
227
|
+
} else if (didPressIn) {
|
|
228
|
+
// we already fired onPressIn but lost ownership before finalize
|
|
229
|
+
// (e.g. finger dragged onto a sibling pressable and that one
|
|
230
|
+
// claimed ownership). fire onPressOut so callers can clear their
|
|
231
|
+
// press state — otherwise pressStyle stays stuck on this view.
|
|
232
|
+
didPressIn = false
|
|
233
|
+
config.onPressOut?.(e)
|
|
234
|
+
}
|
|
235
|
+
})
|
|
236
|
+
|
|
237
|
+
if (config.hitSlop) tap.hitSlop(config.hitSlop)
|
|
238
|
+
|
|
239
|
+
// if no long press handler, just use tap
|
|
240
|
+
if (!config.onLongPress) return tap
|
|
241
|
+
|
|
242
|
+
// LongPress gesture for long press handling
|
|
243
|
+
const longPress = Gesture.LongPress()
|
|
244
|
+
.runOnJS(true)
|
|
245
|
+
.minDuration(longPressDuration)
|
|
246
|
+
.onStart((e: any) => {
|
|
247
|
+
didLongPress = true
|
|
248
|
+
if (isOwner()) {
|
|
249
|
+
firePressIn(e)
|
|
250
|
+
config.onLongPress?.(e)
|
|
251
|
+
}
|
|
252
|
+
})
|
|
253
|
+
|
|
254
|
+
if (config.hitSlop) longPress.hitSlop(config.hitSlop)
|
|
255
|
+
|
|
256
|
+
// exclusive: longPress has priority, tap is fallback for quick presses
|
|
257
|
+
return Gesture.Exclusive(longPress, tap)
|
|
258
|
+
},
|
|
259
|
+
}
|
|
260
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
export function createGlobalState<T extends { enabled: boolean }>(
|
|
2
|
+
key: string,
|
|
3
|
+
defaultValue: T
|
|
4
|
+
): {
|
|
5
|
+
get: () => T
|
|
6
|
+
set: (next: T) => void
|
|
7
|
+
} {
|
|
8
|
+
const GLOBAL_KEY = `__hanzogui_${key}__`
|
|
9
|
+
|
|
10
|
+
type HanzoguiGlobal = typeof globalThis & {
|
|
11
|
+
[GLOBAL_KEY]?: T
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function getGlobalState(): T {
|
|
15
|
+
const g = globalThis as HanzoguiGlobal
|
|
16
|
+
if (!g[GLOBAL_KEY]) {
|
|
17
|
+
// reset on module load so reloadReactNative gets a clean state
|
|
18
|
+
// (globalThis persists across reloads but module scope re-evaluates)
|
|
19
|
+
g[GLOBAL_KEY] = defaultValue
|
|
20
|
+
}
|
|
21
|
+
return g[GLOBAL_KEY]!
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function setGlobalState(newState: T): void {
|
|
25
|
+
;(globalThis as HanzoguiGlobal)[GLOBAL_KEY] = newState
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
return {
|
|
29
|
+
get: getGlobalState,
|
|
30
|
+
set: setGlobalState,
|
|
31
|
+
}
|
|
32
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @hanzogui/native
|
|
3
|
+
*
|
|
4
|
+
* Native setup modules for Hanzogui. Import these at the top of your app entry point.
|
|
5
|
+
*
|
|
6
|
+
* @example
|
|
7
|
+
* ```tsx
|
|
8
|
+
* // In your app entry (index.js or App.tsx)
|
|
9
|
+
* import '@hanzogui/native/setup-teleport'
|
|
10
|
+
* import '@hanzogui/native/setup-gesture-handler'
|
|
11
|
+
* import '@hanzogui/native/setup-worklets'
|
|
12
|
+
* import '@hanzogui/native/setup-safe-area'
|
|
13
|
+
* import '@hanzogui/native/expo-linear-gradient'
|
|
14
|
+
* import '@hanzogui/native/setup-keyboard-controller'
|
|
15
|
+
*
|
|
16
|
+
* // Then use Hanzogui components normally
|
|
17
|
+
* // Sheet will automatically use native gestures when available
|
|
18
|
+
* // LinearGradient will use expo-linear-gradient when installed
|
|
19
|
+
* ```
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
// types
|
|
23
|
+
export type {
|
|
24
|
+
NativePortalState,
|
|
25
|
+
GestureState,
|
|
26
|
+
WorkletsState,
|
|
27
|
+
SafeAreaState,
|
|
28
|
+
SafeAreaInsets,
|
|
29
|
+
SafeAreaFrame,
|
|
30
|
+
SafeAreaMetrics,
|
|
31
|
+
LinearGradientState,
|
|
32
|
+
ZeegoState,
|
|
33
|
+
BurntState,
|
|
34
|
+
NativePortalProps,
|
|
35
|
+
NativePortalHostProps,
|
|
36
|
+
NativePortalProviderProps,
|
|
37
|
+
} from './types'
|
|
38
|
+
|
|
39
|
+
// portal
|
|
40
|
+
export { getPortal } from './portalState'
|
|
41
|
+
export type { PortalAccessor } from './portalState'
|
|
42
|
+
|
|
43
|
+
// gesture handler
|
|
44
|
+
export { getGestureHandler } from './gestureState'
|
|
45
|
+
export {
|
|
46
|
+
claimExternalPressOwnership as unstable_claimExternalPressOwnership,
|
|
47
|
+
releaseExternalPressOwnership as unstable_releaseExternalPressOwnership,
|
|
48
|
+
} from './gestureState'
|
|
49
|
+
export type {
|
|
50
|
+
ExternalPressOwnershipToken,
|
|
51
|
+
GestureHandlerAccessor,
|
|
52
|
+
PressGestureConfig,
|
|
53
|
+
} from './gestureState'
|
|
54
|
+
// NOTE: setupGestureHandler is exported from setup-gesture-handler.ts entry point,
|
|
55
|
+
// not here, to avoid bundler pulling in RNGH require during tree-shaking
|
|
56
|
+
export type { GestureHandlerConfig } from './setup-gesture-handler'
|
|
57
|
+
|
|
58
|
+
// worklets
|
|
59
|
+
export { getWorklets } from './workletsState'
|
|
60
|
+
export type { WorkletsAccessor } from './workletsState'
|
|
61
|
+
|
|
62
|
+
// safe area
|
|
63
|
+
export { getSafeArea } from './safeAreaState'
|
|
64
|
+
export type { SafeAreaAccessor } from './safeAreaState'
|
|
65
|
+
|
|
66
|
+
// linear gradient
|
|
67
|
+
export { getLinearGradient } from './linearGradientState'
|
|
68
|
+
export type { LinearGradientAccessor } from './linearGradientState'
|
|
69
|
+
|
|
70
|
+
// keyboard controller state exports (safe - no side effects)
|
|
71
|
+
export {
|
|
72
|
+
isKeyboardControllerEnabled,
|
|
73
|
+
getKeyboardControllerState,
|
|
74
|
+
setKeyboardControllerState,
|
|
75
|
+
} from './keyboardControllerState'
|
|
76
|
+
export type { KeyboardControllerState } from './keyboardControllerState'
|
|
77
|
+
|
|
78
|
+
// zeego (native menus)
|
|
79
|
+
export { getZeego } from './zeegoState'
|
|
80
|
+
export type { ZeegoAccessor } from './zeegoState'
|
|
81
|
+
export { NativeMenuContext } from './nativeMenuContext'
|
|
82
|
+
|
|
83
|
+
// burnt (native toasts)
|
|
84
|
+
export { getBurnt } from './burntState'
|
|
85
|
+
export type { BurntAccessor } from './burntState'
|
|
86
|
+
|
|
87
|
+
// components
|
|
88
|
+
export { NativePortal, NativePortalHost, NativePortalProvider } from './components'
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { createGlobalState } from './globalState'
|
|
2
|
+
import type { KeyboardControllerState } from './types'
|
|
3
|
+
|
|
4
|
+
export type { KeyboardControllerState }
|
|
5
|
+
|
|
6
|
+
const state = createGlobalState<KeyboardControllerState>(`keyboard_controller`, {
|
|
7
|
+
enabled: false,
|
|
8
|
+
KeyboardProvider: null,
|
|
9
|
+
KeyboardAwareScrollView: null,
|
|
10
|
+
useKeyboardHandler: null,
|
|
11
|
+
useReanimatedKeyboardAnimation: null,
|
|
12
|
+
KeyboardController: null,
|
|
13
|
+
KeyboardEvents: null,
|
|
14
|
+
KeyboardStickyView: null,
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
export function isKeyboardControllerEnabled(): boolean {
|
|
18
|
+
return state.get().enabled
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function getKeyboardControllerState(): KeyboardControllerState {
|
|
22
|
+
return state.get()
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function setKeyboardControllerState(
|
|
26
|
+
updates: Partial<KeyboardControllerState>
|
|
27
|
+
): void {
|
|
28
|
+
Object.assign(state.get(), updates)
|
|
29
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { createGlobalState } from './globalState'
|
|
2
|
+
import type { LinearGradientState } from './types'
|
|
3
|
+
|
|
4
|
+
const state = createGlobalState<LinearGradientState>(`linear_gradient`, {
|
|
5
|
+
enabled: false,
|
|
6
|
+
Component: null,
|
|
7
|
+
})
|
|
8
|
+
|
|
9
|
+
export interface LinearGradientAccessor {
|
|
10
|
+
readonly isEnabled: boolean
|
|
11
|
+
readonly state: LinearGradientState
|
|
12
|
+
set(newState: LinearGradientState): void
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function getLinearGradient(): LinearGradientAccessor {
|
|
16
|
+
return {
|
|
17
|
+
get isEnabled(): boolean {
|
|
18
|
+
return state.get().enabled
|
|
19
|
+
},
|
|
20
|
+
get state(): LinearGradientState {
|
|
21
|
+
return state.get()
|
|
22
|
+
},
|
|
23
|
+
set(newState: LinearGradientState): void {
|
|
24
|
+
state.set(newState)
|
|
25
|
+
},
|
|
26
|
+
}
|
|
27
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { createGlobalState } from './globalState'
|
|
2
|
+
import type { NativePortalState } from './types'
|
|
3
|
+
|
|
4
|
+
const state = createGlobalState<NativePortalState>(`portal`, {
|
|
5
|
+
enabled: false,
|
|
6
|
+
type: null,
|
|
7
|
+
})
|
|
8
|
+
|
|
9
|
+
export interface PortalAccessor {
|
|
10
|
+
readonly isEnabled: boolean
|
|
11
|
+
readonly state: NativePortalState
|
|
12
|
+
set(newState: NativePortalState): void
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function getPortal(): PortalAccessor {
|
|
16
|
+
return {
|
|
17
|
+
get isEnabled(): boolean {
|
|
18
|
+
return state.get().enabled
|
|
19
|
+
},
|
|
20
|
+
get state(): NativePortalState {
|
|
21
|
+
return state.get()
|
|
22
|
+
},
|
|
23
|
+
set(newState: NativePortalState): void {
|
|
24
|
+
state.set(newState)
|
|
25
|
+
},
|
|
26
|
+
}
|
|
27
|
+
}
|