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
2 changes: 2 additions & 0 deletions .agents/skills/mpx2rn/references/rn-template-reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -1111,6 +1111,8 @@ movable-view 的可移动区域。

从底部弹起的滚动选择器。

`picker` 节点上的文本样式(如 `color``font-size` 等)会透传给触发区域的子节点,用于对齐小程序中父节点文本样式影响子内容的表现。

#### 属性

| 属性名 | 类型 | 默认值 | 说明 |
Expand Down
2 changes: 2 additions & 0 deletions docs-vitepress/guide/rn/component.md
Original file line number Diff line number Diff line change
Expand Up @@ -503,6 +503,8 @@ movable-view的可移动区域。

从底部弹起的滚动选择器。

`picker` 节点上的文本样式(如 `color``font-size` 等)会透传给触发区域的子节点,用于对齐小程序中父节点文本样式影响子内容的表现。

属性

| 属性名 | 类型 | 默认值 | 说明 |
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,6 @@ module.exports = function ({ print }) {
const ttPropLog = print({ platform: 'bytedance', tag: TAG_NAME, isError: false })
const baiduPropLog = print({ platform: 'baidu', tag: TAG_NAME, isError: false })
const qaPropLog = print({ platform: 'qa', tag: TAG_NAME, isError: false })
const iosPropLog = print({ platform: 'ios', tag: TAG_NAME, isError: false })
const androidPropLog = print({ platform: 'android', tag: TAG_NAME, isError: false })
const harmonyPropLog = print({ platform: 'harmony', tag: TAG_NAME, isError: false })
const ksPropLog = print({ platform: 'ks', tag: TAG_NAME, isError: false })

return {
Expand Down Expand Up @@ -48,9 +45,6 @@ module.exports = function ({ print }) {
ali: aliPropLog,
jd: jdPropLog,
qa: qaPropLog,
ios: iosPropLog,
android: androidPropLog,
harmony: harmonyPropLog,
ks: ksPropLog
}
],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,11 @@ import PickerMultiSelector from './multiSelector'
import PickerTime from './time'
import PickerDate from './date'
import PickerRegion from './region'
import Portal from '../mpx-portal'
import { FormContext, FormFieldValue, RouteContext } from '../context'
import useNodesRef, { HandlerRef } from '../useNodesRef'
import useInnerProps, { getCustomEvent } from '../getInnerListeners'
import { extendObject, useLayout } from '../utils'
import { extendObject, splitProps, splitStyle, useLayout, useTextPassThrough, useTransformStyle, wrapChildren } from '../utils'
import { createPopupManager } from '../mpx-popup'
import { EventType, LanguageCode, PickerMode, PickerProps } from './type'

Expand Down Expand Up @@ -78,13 +79,13 @@ const styles = StyleSheet.create({
}
})

const pickerModalMap: Record<PickerMode, React.ComponentType<PickerProps>> = {
const pickerModalMap = {
[PickerMode.SELECTOR]: PickerSelector,
[PickerMode.MULTI_SELECTOR]: PickerMultiSelector,
[PickerMode.TIME]: PickerTime,
[PickerMode.DATE]: PickerDate,
[PickerMode.REGION]: PickerRegion
}
} satisfies Record<PickerMode, React.ComponentType<any>>

const getDefaultValue = (mode: PickerMode) => {
switch (mode) {
Expand All @@ -111,16 +112,23 @@ const buttonTextMap: Record<LanguageCode, { cancel: string; confirm: string }> =
}

const Picker = forwardRef<HandlerRef<View, PickerProps>, PickerProps>(
(props: PickerProps, ref): React.JSX.Element => {
(pickerProps: PickerProps, ref): React.JSX.Element => {
const { textProps, innerProps: splitInnerProps } = splitProps(pickerProps)
const props = (splitInnerProps || {}) as PickerProps
const {
mode,
value,
range = null,
range = [],
children,
disabled,
bindcancel,
bindchange,
'header-text': headerText = ''
'header-text': headerText = '',
style,
'enable-var': enableVar,
'enable-text-pass-through': enableTextPassThrough,
'parent-width': parentWidth,
'parent-height': parentHeight
} = props

const { pageId } = useContext(RouteContext) || {}
Expand All @@ -129,12 +137,31 @@ const Picker = forwardRef<HandlerRef<View, PickerProps>, PickerProps>(
pickerValue.current = Array.isArray(value) ? value.slice() : value
const nodeRef = useRef<View>(null)
const pickerRef = useRef<any>(null)
const { open, show, hide, remove } = useRef(createPopupManager()).current
const { open, show, hide, remove, update } = useRef(createPopupManager()).current
const {
normalStyle,
hasVarDec,
varContextRef,
hasSelfPercent,
hasPositionFixed,
setWidth,
setHeight
} = useTransformStyle(style, { enableVar, parentWidth, parentHeight })
const { textStyle, backgroundStyle, innerStyle = {} } = splitStyle(normalStyle)
const textPassThrough = useTextPassThrough(textStyle, textProps, { enableTextPassThrough })

useNodesRef<View, PickerProps>(props, ref, nodeRef)
const { layoutRef, layoutProps } = useLayout({
if (backgroundStyle) {
warn('Picker does not support background image-related styles!')
}

useNodesRef<View, PickerProps>(props, ref, nodeRef, {
style: normalStyle
})
const { layoutRef, layoutProps, layoutStyle } = useLayout({
props,
hasSelfPercent: false,
hasSelfPercent,
setWidth,
setHeight,
nodeRef
})

Expand All @@ -143,7 +170,8 @@ const Picker = forwardRef<HandlerRef<View, PickerProps>, PickerProps>(
{},
props,
{
ref: nodeRef
ref: nodeRef,
style: extendObject({}, innerStyle, layoutStyle)
},
layoutProps
),
Expand Down Expand Up @@ -228,25 +256,32 @@ const Picker = forwardRef<HandlerRef<View, PickerProps>, PickerProps>(
hide()
}

const specificProps = extendObject({}, innerProps, {
mode,
children,
bindchange: onChange,
bindcolumnchange: onColumnChange,
getRange: () => range
})
const getContentHeight = () => headerText ? 350 : 310

const renderPickerContent = () => {
const getPickerContent = () => {
if (disabled) {
return null
}
const _mode = mode ?? PickerMode.SELECTOR
if (!(_mode in pickerModalMap)) {
return warn(`[Mpx runtime warn]: Unsupported <picker> mode: ${mode}`)
}
const modalProps = props as any
const specificProps = {
mode: _mode,
range,
'range-key': modalProps['range-key'],
start: modalProps.start,
end: modalProps.end,
fields: modalProps.fields,
level: modalProps.level,
'custom-item': modalProps['custom-item'],
bindchange: onChange,
bindcolumnchange: onColumnChange
} as PickerProps
const _value: any = value
const PickerModal = pickerModalMap[_mode]
const renderPickerModal = (
const PickerModal = pickerModalMap[_mode] as React.ComponentType<PickerProps>
return (
<>
{headerText && (
<View style={[styles.header]}>
Expand All @@ -270,22 +305,51 @@ const Picker = forwardRef<HandlerRef<View, PickerProps>, PickerProps>(
</View>
</>
)
const contentHeight = headerText ? 350 : 310
open(renderPickerModal, pageId, { contentHeight })
}

const openPickerContent = () => {
const renderPickerModal = getPickerContent()
if (!renderPickerModal) {
return
}
open(renderPickerModal, pageId, { contentHeight: getContentHeight() })
}

useEffect(() => {
renderPickerContent()
openPickerContent()
return () => {
remove()
}
}, [])

return createElement(
const showPicker = () => {
const renderPickerModal = getPickerContent()
if (!renderPickerModal) {
return
}
update(renderPickerModal, { contentHeight: getContentHeight() })
show()
}

const finalComponent = createElement(
TouchableWithoutFeedback,
{ onPress: show },
createElement(View, innerProps, children)
{ onPress: showPicker },
createElement(
View,
innerProps,
wrapChildren(children, {
hasVarDec,
varContext: varContextRef.current,
textPassThrough
})
)
)

if (hasPositionFixed) {
return createElement(Portal, null, finalComponent)
}

return finalComponent
}
)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ const styles = StyleSheet.create({
})

const formatRangeFun = (range: RangeItem[], rangeKey = '') =>
rangeKey ? range.map((item: Obj) => item[rangeKey]) : range
rangeKey ? range.map((item) => (item as Obj)[rangeKey]) : range

const formatValueFn = (value: number | number[]) => {
return Array.isArray(value) ? value : [value]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ const styles = StyleSheet.create({
})

const formatRangeFun = (range: RangeItem[], rangeKey = '') =>
rangeKey ? range.map((item: Obj) => item[rangeKey]) : range
rangeKey ? range.map((item) => (item as Obj)[rangeKey]) : range

const formatValueFn = (value: number | number[] = 0) => {
const _value = Array.isArray(value) ? value[0] : value
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,10 @@ export interface BasePickerProps {
/** 作为表单组件时的名称 */
name?: string
style?: Record<string, any>
'enable-var'?: boolean
'enable-text-pass-through'?: boolean
'parent-width'?: number
'parent-height'?: number
children?: React.ReactNode
range?: RangeItem[]
ref?: any
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@ export interface IUsePopupOptions {
type?: PopupType
}

type PopupOptions = {
contentHeight?: number
}

/**
* 根据 type 返回对应的弹窗壳子组件
*/
Expand Down Expand Up @@ -44,7 +48,7 @@ const createPopupManager = (options: IUsePopupOptions = {}) => {
const open = (
childComponent: React.ReactNode,
pageId: number | undefined,
options?: { contentHeight?: number }
options?: PopupOptions
) => {
if (!isOpen && pageId != null) {
isOpen = true
Expand All @@ -57,9 +61,9 @@ const createPopupManager = (options: IUsePopupOptions = {}) => {
}
}

const update = (updatedChild: ReactElement | null) => {
const update = (updatedChild: ReactElement | null, options?: PopupOptions) => {
if (popupKey !== null && child !== null && updatedChild !== null) {
child = cloneElement(child, { children: updatedChild })
child = cloneElement(child, Object.assign({ children: updatedChild }, options))
Portal.update(popupKey, child)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,8 +59,9 @@ const PopupBase = (props: PopupBaseProps = {}) => {
contentHeight = MOVEOUT_HEIGHT,
visible = false
} = props
const moveoutHeight = contentHeight + bottom
const fade = useSharedValue<number>(MASK_OFF)
const slide = useSharedValue<number>(contentHeight)
const slide = useSharedValue<number>(moveoutHeight)

const animatedStylesMask = useAnimatedStyle(() => ({
opacity: fade.value
Expand All @@ -87,7 +88,7 @@ const PopupBase = (props: PopupBaseProps = {}) => {
duration: 300
})
slide.value = withTiming(
contentHeight,
moveoutHeight,
{
easing: Easing.inOut(Easing.poly(3)),
duration: 300
Expand Down
22 changes: 22 additions & 0 deletions packages/webpack-plugin/test/platform/wx/template/picker.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
const { compileTemplate, warnFn, errorFn } = require('../../util')

describe('picker template transform', function () {
afterEach(() => {
warnFn.mockClear()
errorFn.mockClear()
})

it('should support header-text in react native modes', function () {
const input = '<picker header-text="{{ headerTextTest }}"></picker>'
const modes = ['ios', 'android', 'harmony']

modes.forEach((mode) => {
compileTemplate(input, { srcMode: 'wx', mode })
})

expect(warnFn.mock.calls.map(args => args[0])).not.toEqual(expect.arrayContaining([
expect.stringContaining('<picker> does not support [header-text] property')
]))
expect(errorFn).not.toHaveBeenCalled()
})
})
Loading
Loading