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
27 changes: 20 additions & 7 deletions packages/webpack-plugin/lib/runtime/components/react/mpx-view.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -172,13 +172,21 @@ const isPercent = (val: string | number | undefined): val is string => typeof va

const isBackgroundSizeKeyword = (val: string | number): boolean => typeof val === 'string' && /^cover|contain$/.test(val)

const isFullSizeGradient = ({ type, sizeList, backgroundPosition, linearInfo }: PreImageInfo): boolean => {
// 四边撑满仅适用于无偏移、无需计算对角方向的全尺寸渐变。
return __mpx_mode__ !== 'ios' && type === 'linear' && sizeList[0] === '100%' && sizeList[1] === '100%' &&
!(linearInfo?.direction && diagonalAngleMap[linearInfo.direction]) &&
(!backgroundPosition.length || (backgroundPosition[1] === 0 && backgroundPosition[3] === 0))
}

const isNeedLayout = (preImageInfo: PreImageInfo): boolean => {
const { sizeList, backgroundPosition, type } = preImageInfo
const [width, height] = sizeList
const bp = backgroundPosition

if (type === 'linear') {
return !(Number.isFinite(width) && Number.isFinite(height)) || isPercent(bp[1]) || isPercent(bp[3])
return !isFullSizeGradient(preImageInfo) &&
(!(Number.isFinite(width) && Number.isFinite(height)) || isPercent(bp[1]) || isPercent(bp[3]))
}

// 含有百分号,center 需计算布局
Expand Down Expand Up @@ -284,10 +292,7 @@ function backgroundSize (imageProps: ImageProps, preImageInfo: PreImageInfo, ima
const { width: layoutWidth, height: layoutHeight } = layoutInfo || {}
const { width: imageSizeWidth, height: imageSizeHeight } = imageSize || {}
const [width, height] = sizeList
let dimensions: {
width: NumberVal,
height: NumberVal
} | null = { width: 0, height: 0 }
let dimensions: ImageStyle | null = { width: 0, height: 0 }

// 枚举值
if (typeof width === 'string' && ['cover', 'contain'].includes(width)) {
Expand Down Expand Up @@ -317,7 +322,14 @@ function backgroundSize (imageProps: ImageProps, preImageInfo: PreImageInfo, ima
dimensions = calculateSize(width as number, imageSizeHeight / imageSizeWidth, layoutInfo?.width, true)
if (!dimensions) return
} else { // 数值类型 ImageStyle
if (type === 'linear' && (!layoutWidth || !layoutHeight) && (isPercent(width) || isPercent(height))) {
if (isFullSizeGradient(preImageInfo)) {
dimensions = StyleSheet.absoluteFillObject
} else if (__mpx_mode__ === 'ios' && type === 'linear') {
dimensions = {
width: calcPercent(width as NumberVal, layoutWidth || 0),
height: calcPercent(height as NumberVal, layoutHeight || 0)
}
} else if (type === 'linear' && (!layoutWidth || !layoutHeight) && (isPercent(width) || isPercent(height))) {
// ios 上 linear 组件只要重新触发渲染,在渲染过程中外层容器 width 或者 height 被设置为 0,通过设置 % 的方式会渲染不出来,即使后面再更新为正常宽高也渲染不出来
// 所以 hack 手动先将 linear 宽高也设置为 0,后面再更新为正确的数值或 %。
dimensions = {
Expand Down Expand Up @@ -756,7 +768,8 @@ function useWrapImage (imageStyle?: ExtendedViewStyle, innerStyle?: Record<strin
}
}

if (type === 'linear' && pending) return createElement(View, backgroundProps)
// iOS DRN 的渐变在零尺寸下挂载后,即使尺寸恢复也可能不再绘制。
if (type === 'linear' && (pending || (__mpx_mode__ === 'ios' && needLayout && !(layoutInfo && layoutInfo.width > 0 && layoutInfo.height > 0)))) return createElement(View, backgroundProps)
return createElement(View, backgroundProps,
type === 'linear'
? createElement(LinearGradient, extendObject({ useAngle: true }, imageProps as LinearImageProps))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -97,8 +97,85 @@ describe('mpx-view linear-gradient parser', () => {

act(() => renderer!.root.findAllByType('View')[1].props.onLayout(layoutEvent(200, 100)))
const gradient = renderer!.root.findByType('LinearGradient')
expect(gradient.props.style).toMatchObject({ width: '100%', height: '100%' })
expect(gradient.props.style).toMatchObject({ width: 200, height: 100 })
expect(gradient.props.angle).toBeCloseTo(26.565)
act(() => renderer!.unmount())
})
})

describe('mpx-view full-size gradient layout', () => {
beforeEach(() => { global.__mpx_mode__ = 'android' })
afterEach(() => { global.__mpx_mode__ = 'ios' })
const render = (style: Record<string, any> = {}) => React.createElement(MpxView, {
'enable-background': true,
style: Object.assign({ width: 200, height: 100, backgroundImage: 'linear-gradient(90deg, red, blue)' }, style)
})
const fillStyle = { position: 'absolute', left: 0, right: 0, top: 0, bottom: 0 }

test.each([
[undefined], [['auto']], [['cover']], [['contain']], [['100%', '100%']]
])('renders full-size gradient immediately for %j', (backgroundSize) => {
let renderer: ReactTestRenderer
act(() => { renderer = create(render(backgroundSize ? { backgroundSize } : {})) })
expect(renderer!.root.findByType('LinearGradient').props.style).toEqual(fillStyle)
act(() => renderer!.root.findAllByType('View')[1].props.onLayout(layoutEvent(200, 100)))
act(() => renderer!.update(render({ backgroundSize, backgroundImage: 'linear-gradient(90deg, green, blue)' })))
expect(renderer!.root.findByType('LinearGradient').props.style).toEqual(fillStyle)
act(() => renderer!.unmount())
})

test.each([
['left', 20, 'top', 0], ['right', 20, 'bottom', 10], ['left', -20, 'top', 0]
])('keeps full dimensions for offset %s %s %s %s', (x, dx, y, dy) => {
let renderer: ReactTestRenderer
act(() => { renderer = create(render({ backgroundPosition: [x, dx, y, dy] })) })
expect(renderer!.root.findAllByType('LinearGradient')).toHaveLength(0)
act(() => renderer!.root.findAllByType('View')[1].props.onLayout(layoutEvent(200, 100)))
expect(renderer!.root.findByType('LinearGradient').props.style).toEqual({ position: 'absolute', width: '100%', height: '100%', [x]: dx, [y]: dy })
act(() => renderer!.root.findAllByType('View')[1].props.onLayout(layoutEvent(0, 100)))
expect(renderer!.root.findByType('LinearGradient').props.style).toMatchObject({ width: 0, height: 0 })
act(() => renderer!.root.findAllByType('View')[1].props.onLayout(layoutEvent(200, 100)))
expect(renderer!.root.findByType('LinearGradient').props.style).toMatchObject({ width: '100%', height: '100%' })
act(() => renderer!.unmount())
})

test('switches between fill and layout-dependent styles using the cached layout', () => {
let renderer: ReactTestRenderer
act(() => { renderer = create(render()) })
expect(renderer!.root.findByType('LinearGradient').props.style).toEqual(fillStyle)
act(() => renderer!.root.findAllByType('View')[1].props.onLayout(layoutEvent(200, 100)))
act(() => renderer!.update(render({ backgroundSize: ['50%', '100%'], backgroundPosition: ['center', 'center'] })))
expect(renderer!.root.findByType('LinearGradient').props.style).toEqual({ position: 'absolute', width: '50%', height: '100%', left: 50, top: 0 })
act(() => renderer!.update(render()))
expect(renderer!.root.findByType('LinearGradient').props.style).toEqual(fillStyle)
act(() => renderer!.root.findAllByType('View')[1].props.onLayout(layoutEvent(0, 100)))
act(() => renderer!.update(render({ backgroundImage: 'linear-gradient(90deg, green, blue)' })))
expect(renderer!.root.findByType('LinearGradient').props.style).toEqual(fillStyle)
act(() => renderer!.root.findAllByType('View')[1].props.onLayout(layoutEvent(200, 100)))
act(() => renderer!.update(render({ backgroundPosition: ['center', 'center'] })))
expect(renderer!.root.findByType('LinearGradient').props.style).toEqual({ position: 'absolute', width: '100%', height: '100%', left: 0, top: 0 })
act(() => renderer!.unmount())
})
})

describe('iOS gradient visibility restoration', () => {
test('does not mount at zero size and remounts with numeric size after restoration', () => {
let renderer: ReactTestRenderer
act(() => {
renderer = create(React.createElement(MpxView, {
'enable-background': true,
style: { width: 100, height: 100, backgroundImage: 'linear-gradient(179deg, #fff 0%, #000 76%)', backgroundSize: ['100%', '100%'] }
}))
})
expect(renderer!.root.findAllByType('LinearGradient')).toHaveLength(0)
;[0, 98, 0, 98].forEach(height => {
act(() => renderer!.root.findAllByType('View')[1].props.onLayout(layoutEvent(98, height)))
if (height) {
expect(renderer!.root.findByType('LinearGradient').props.style).toMatchObject({ width: 98, height: 98 })
} else {
expect(renderer!.root.findAllByType('LinearGradient')).toHaveLength(0)
}
})
act(() => renderer!.unmount())
})
})
Loading