Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 12 additions & 2 deletions packages/webpack-plugin/lib/runtime/components/react/context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,11 +45,15 @@ export interface IntersectionObserver {
}

export interface PortalContextValue {
mount: (children: React.ReactNode, key?: number | null, id?: number | null) => number | undefined
update: (key: number, children: React.ReactNode) => void
mount: (children: React.ReactNode, key?: number | null, id?: number | null, meta?: PortalMeta) => number | undefined
update: (key: number, children: React.ReactNode, meta?: PortalMeta) => void
unmount: (key: number) => void
}

export interface PortalMeta {
stackPath?: number[]
}

export interface ScrollViewContextValue {
gestureRef: React.RefObject<any> | null
scrollOffset: Animated.Value | null
Expand All @@ -75,6 +79,10 @@ export interface TextPassThroughContextValue {
pendingTextProps?: Record<string, any>
}

export interface FixedStackContextValue {
stackPath: number[]
}

export const MovableAreaContext = createContext({ width: 0, height: 0 })

export const FormContext = createContext<FormContextValue | null>(null)
Expand Down Expand Up @@ -105,4 +113,6 @@ export const PortalContext = createContext<PortalContextValue>(null as any)

export const StickyContext = createContext<StickyContextValue>({ registerStickyHeader: noop, unregisterStickyHeader: noop })

export const FixedStackContext = createContext<FixedStackContextValue | null>(null)

export const ProviderContext = createContext(null)
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,10 @@ import PortalHost, { portal } from './portal-host'

export type PortalProps = {
children?: ReactNode
stackPath?: number[]
}

const Portal = ({ children }: PortalProps): null => {
const Portal = ({ children, stackPath }: PortalProps): null => {
const manager = useContext(PortalContext)
const keyRef = useRef<any>(null)
const { pageId } = useContext(RouteContext) || {}
Expand All @@ -25,15 +26,15 @@ const Portal = ({ children }: PortalProps): null => {
}

useEffect(() => {
manager.update(keyRef.current, children)
}, [children])
manager.update(keyRef.current, children, { stackPath })
}, [children, stackPath])
useEffect(() => {
if (!manager) {
throw new Error(
'Looks like you forgot to wrap your root component with `PortalHost` component from `@mpxjs/webpack-plugin/lib/runtime/components/react/dist/mpx-portal/index`.\n\n'
)
}
keyRef.current = manager.mount(children, null, pageId)
keyRef.current = manager.mount(children, null, pageId, { stackPath })
return () => {
manager.unmount(keyRef.current)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,21 +7,22 @@ import {
} from 'react-native'
import PortalManager from './portal-manager'
import { PortalContext, RouteContext } from '../context'
import type { PortalMeta } from '../context'

type PortalHostProps = {
children: ReactNode,
pageId: number
}

interface PortalManagerContextValue {
mount: (key: number, children: React.ReactNode) => void
update: (key: number, children: React.ReactNode) => void
mount: (key: number, children: React.ReactNode, meta?: PortalMeta) => void
update: (key: number, children: React.ReactNode, meta?: PortalMeta) => void
unmount: (key: number) => void
}

export type Operation =
| { type: 'mount'; key: number; children: ReactNode }
| { type: 'update'; key: number; children: ReactNode }
| { type: 'mount'; key: number; children: ReactNode; meta?: PortalMeta }
| { type: 'update'; key: number; children: ReactNode; meta?: PortalMeta }
| { type: 'unmount'; key: number }

// events
Expand All @@ -39,18 +40,18 @@ const styles = StyleSheet.create({

class PortalGuard {
private nextKey = 10000
add = (e: ReactNode, id: number|null) => {
add = (e: ReactNode, id: number|null, meta?: PortalMeta) => {
const key = this.nextKey++
TopViewEventEmitter.emit(addType, e, key, id)
TopViewEventEmitter.emit(addType, e, key, id, meta)
return key
}

remove = (key: number) => {
TopViewEventEmitter.emit(removeType, key)
}

update = (key: number, e: ReactNode) => {
TopViewEventEmitter.emit(updateType, key, e)
update = (key: number, e: ReactNode, meta?: PortalMeta) => {
TopViewEventEmitter.emit(updateType, key, e, meta)
}
}
/**
Expand All @@ -61,15 +62,15 @@ export const portal = new PortalGuard()
const PortalHost = ({ children } :PortalHostProps): JSX.Element => {
const _nextKey = useRef(0)
const manager = useRef<PortalManagerContextValue | null>(null)
const queue = useRef<Array<{ type: string, key: number; children: ReactNode }>>([])
const queue = useRef<Array<{ type: string, key: number; children: ReactNode; meta?: PortalMeta }>>([])
const { pageId } = useContext(RouteContext) || {}
const mount = (children: ReactNode, _key?: number, id?: number|null) => {
const mount = (children: ReactNode, _key?: number, id?: number|null, meta?: PortalMeta) => {
if (id !== pageId) return
const key = _key || _nextKey.current++
if (manager.current) {
manager.current.mount(key, children)
manager.current.mount(key, children, meta)
} else {
queue.current.push({ type: 'mount', key, children })
queue.current.push({ type: 'mount', key, children, meta })
}
return key
}
Expand All @@ -82,11 +83,11 @@ const PortalHost = ({ children } :PortalHostProps): JSX.Element => {
}
}

const update = (key: number, children?: ReactNode) => {
const update = (key: number, children?: ReactNode, meta?: PortalMeta) => {
if (manager.current) {
manager.current.update(key, children)
manager.current.update(key, children, meta)
} else {
const operation = { type: 'mount', key, children }
const operation = { type: 'mount', key, children, meta }
const index = queue.current.findIndex((q) => q.type === 'mount' && q.key === key)
if (index > -1) {
queue.current[index] = operation
Expand All @@ -108,7 +109,7 @@ const PortalHost = ({ children } :PortalHostProps): JSX.Element => {
if (!operation) return
switch (operation.type) {
case 'mount':
manager.current.mount(operation.key, operation.children)
manager.current.mount(operation.key, operation.children, operation.meta)
break
case 'unmount':
manager.current.unmount(operation.key)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,31 +1,64 @@
import { useState, useCallback, forwardRef, ForwardedRef, useImperativeHandle, ReactNode, ReactElement, Fragment } from 'react'
import { useState, useCallback, useRef, forwardRef, ForwardedRef, useImperativeHandle, ReactNode, ReactElement, Fragment } from 'react'
import { View, StyleSheet } from 'react-native'
import type { PortalMeta } from '../context'

export type State = {
portals: Array<{
key: number
children: ReactNode
stackPath?: number[]
order: number
}>
}

type PortalManagerProps = {
}

type PortalItem = State['portals'][number]

const styles = StyleSheet.create({
portal: {
...StyleSheet.absoluteFillObject
}
})

const compareStackPath = (left: number[], right: number[]) => {
const maxLength = Math.max(left.length, right.length)
for (let i = 0; i < maxLength; i++) {
const leftValue = i < left.length ? left[i] : 0
const rightValue = i < right.length ? right[i] : 0
if (leftValue !== rightValue) {
return leftValue - rightValue
}
}
return 0
}

const comparePortalItems = (left: PortalItem, right: PortalItem) => {
if (left.stackPath && right.stackPath) {
return compareStackPath(left.stackPath, right.stackPath) || left.order - right.order
}
return left.order - right.order
}

const _PortalManager = forwardRef((props: PortalManagerProps, ref:ForwardedRef<unknown>): ReactElement => {
const [state, setState] = useState<State>({
portals: []
})
const orderRef = useRef(0)

const mount = useCallback((key: number, children: ReactNode) => {
const mount = useCallback((key: number, children: ReactNode, meta?: PortalMeta) => {
const order = orderRef.current++
setState((prevState) => ({
portals: [...prevState.portals, { key, children }]
portals: [...prevState.portals, { key, children, stackPath: meta?.stackPath, order }]
}))
}, [])

const update = useCallback((key: number, children: ReactNode) => {
const update = useCallback((key: number, children: ReactNode, meta?: PortalMeta) => {
setState((prevState) => ({
portals: prevState.portals.map((item) => {
if (item.key === key) {
return Object.assign({}, item, { children })
return Object.assign({}, item, { children }, meta ? { stackPath: meta.stackPath } : {})
}
return item
})
Expand All @@ -47,9 +80,11 @@ const _PortalManager = forwardRef((props: PortalManagerProps, ref:ForwardedRef<u

return (
<>
{state.portals.map(({ key, children }) => (
{[...state.portals].sort(comparePortalItems).map(({ key, children, stackPath }, index) => (
<Fragment key={key}>
{children}
{stackPath
? <View pointerEvents='box-none' style={[styles.portal, { zIndex: index }]}>{children}</View>
: children}
</Fragment>
))}
</>
Expand Down
24 changes: 21 additions & 3 deletions packages/webpack-plugin/lib/runtime/components/react/mpx-view.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
* ✔ hover-stay-time
*/
import { View, TextStyle, NativeSyntheticEvent, ViewProps, ImageStyle, StyleSheet, Image, LayoutChangeEvent } from 'react-native'
import { useRef, useState, useEffect, forwardRef, ReactNode, JSX, createElement } from 'react'
import { useRef, useState, useEffect, useContext, forwardRef, ReactNode, JSX, createElement } from 'react'
import useInnerProps from './getInnerListeners'
import Animated from 'react-native-reanimated'
import useAnimationHooks, { AnimationType } from './animationHooks/index'
Expand All @@ -17,6 +17,7 @@ import { error, isFunction } from '@mpxjs/utils'
import LinearGradient from 'react-native-linear-gradient'
import { GestureDetector, PanGesture } from 'react-native-gesture-handler'
import Portal from './mpx-portal'
import { FixedStackContext } from './context'

export interface _ViewProps extends ViewProps {
style?: ExtendedViewStyle
Expand Down Expand Up @@ -138,6 +139,21 @@ const normalizeStyle = (style: ExtendedViewStyle = {}) => {
return style
}

const getStyleZIndex = (style?: ExtendedViewStyle) => {
const zIndex = style?.zIndex as unknown
if (typeof zIndex === 'number') return zIndex
if (typeof zIndex === 'string' && zIndex.trim()) {
const parsed = Number(zIndex)
return Number.isFinite(parsed) ? parsed : 0
}
return 0
}

const getFixedStackPath = (style: ExtendedViewStyle, fixedStackContext: { stackPath: number[] } | null) => {
const localZIndex = getStyleZIndex(style)
return fixedStackContext ? [...fixedStackContext.stackPath, localZIndex] : [localZIndex]
}

const isPercent = (val: string | number | undefined): val is string => typeof val === 'string' && PERCENT_REGEX.test(val)

const isBackgroundSizeKeyword = (val: string | number): boolean => typeof val === 'string' && /^cover|contain$/.test(val)
Expand Down Expand Up @@ -750,7 +766,7 @@ const _View = forwardRef<HandlerRef<View, _ViewProps>, _ViewProps>((viewProps, r
parentWidth,
parentHeight
})

const fixedStackContext = useContext(FixedStackContext)
const { textStyle, backgroundStyle, innerStyle = {} } = splitStyle(normalStyle)
const textPassThrough = useTextPassThroughValue(textStyle, textProps)

Expand All @@ -772,6 +788,7 @@ const _View = forwardRef<HandlerRef<View, _ViewProps>, _ViewProps>((viewProps, r
} = useLayout({ props, hasSelfPercent, setWidth, setHeight, nodeRef })

const viewStyle = extendObject({}, innerStyle, layoutStyle)
const fixedStackPath = hasPositionFixed ? getFixedStackPath(viewStyle, fixedStackContext) : undefined
const transitionend = isFunction(catchtransitionend)
? catchtransitionend
: isFunction(bindtransitionend)
Expand Down Expand Up @@ -830,7 +847,8 @@ const _View = forwardRef<HandlerRef<View, _ViewProps>, _ViewProps>((viewProps, r
}

if (hasPositionFixed) {
finalComponent = createElement(Portal, null, finalComponent)
finalComponent = createElement(FixedStackContext.Provider, { value: { stackPath: fixedStackPath as number[] } }, finalComponent)
finalComponent = createElement(Portal, { stackPath: fixedStackPath }, finalComponent)
}
return finalComponent
})
Expand Down
Loading