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
20 changes: 7 additions & 13 deletions packages/webpack-plugin/lib/runtime/components/react/mpx-view.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -173,12 +173,13 @@ 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 isNeedLayout = (preImageInfo: PreImageInfo): boolean => {
const { sizeList, backgroundPosition, type } = preImageInfo
const { sizeList, backgroundPosition, type, linearInfo } = preImageInfo
const [width, height] = sizeList
const bp = backgroundPosition

if (type === 'linear') {
Comment thread
dos1in marked this conversation as resolved.
return !(Number.isFinite(width) && Number.isFinite(height)) || isPercent(bp[1]) || isPercent(bp[3])
return ((__mpx_mode__ === 'ios' || (linearInfo?.direction && diagonalAngleMap[linearInfo.direction])) &&
!(Number.isFinite(width) && Number.isFinite(height))) || isPercent(bp[1]) || isPercent(bp[3])
}

// 含有百分号,center 需计算布局
Expand Down Expand Up @@ -284,10 +285,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,13 +315,9 @@ 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))) {
Comment thread
dos1in marked this conversation as resolved.
// ios 上 linear 组件只要重新触发渲染,在渲染过程中外层容器 width 或者 height 被设置为 0,通过设置 % 的方式会渲染不出来,即使后面再更新为正常宽高也渲染不出来
// 所以 hack 手动先将 linear 宽高也设置为 0,后面再更新为正确的数值或 %。
dimensions = {
width: 0,
height: 0
} as { width: NumberVal, height: NumberVal }
if (__mpx_mode__ === 'ios' && type === 'linear' && (!layoutWidth || !layoutHeight) && (isPercent(width) || isPercent(height))) {
// iOS 百分比渐变在零尺寸布局下先归零,布局恢复后再使用百分比。
dimensions = { width: 0, height: 0 }
} else {
dimensions = {
width: isPercent(width) ? width : +width,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -102,3 +102,92 @@ describe('mpx-view linear-gradient parser', () => {
act(() => renderer!.unmount())
})
})

describe.each(['android', 'harmony'])('%s gradient layout', (platform) => {
beforeEach(() => { global.__mpx_mode__ = platform })
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', width: '100%', height: '100%', left: 0, top: 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.findByType('LinearGradient').props.style).toEqual({ position: 'absolute', width: '100%', height: '100%', [x]: dx, [y]: dy })
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: '100%', height: '100%' })
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('keeps layout for diagonal angles but renders partial percentages immediately', () => {
let renderer: ReactTestRenderer
act(() => { renderer = create(renderGradient(['100%', '100%'])) })
expect(renderer!.root.findAllByType('LinearGradient')).toHaveLength(0)
act(() => renderer!.root.findAllByType('View')[1].props.onLayout(layoutEvent(200, 100)))
expect(renderer!.root.findByType('LinearGradient').props.angle).toBeCloseTo(26.565)
act(() => renderer!.unmount())
act(() => { renderer = create(render({ backgroundSize: ['50%', '75%'] })) })
expect(renderer!.root.findByType('LinearGradient').props.style).toMatchObject({ width: '50%', height: '75%' })
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('waits for layout then keeps the gradient mounted with zero or percentage dimensions', () => {
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: '100%', height: '100%' })
} else {
expect(renderer!.root.findByType('LinearGradient').props.style).toMatchObject({ width: 0, height: 0 })
}
})
act(() => renderer!.unmount())
})
})
Loading