@mpxjs/core 2.9.64 → 2.9.66

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/@types/index.d.ts CHANGED
@@ -114,7 +114,7 @@ interface Context {
114
114
  refs: ObjectOf<WechatMiniprogram.NodesRef & ComponentIns<{}, {}, {}, {}, []>>
115
115
  asyncRefs: ObjectOf<Promise<WechatMiniprogram.NodesRef & ComponentIns<{}, {}, {}, {}, []>>>
116
116
 
117
- forceUpdate (params?: object, callback?: () => void): void
117
+ forceUpdate (params?: object, options?: object | (() => void), callback?: () => void): void
118
118
 
119
119
  selectComponent: ReplaceWxComponentIns['selectComponent']
120
120
  selectAllComponents: ReplaceWxComponentIns['selectAllComponents']
@@ -183,7 +183,7 @@ export interface MpxComponentIns {
183
183
 
184
184
  $watch (expr: string | (() => any), handler: WatchHandler | WatchOptWithHandler, options?: WatchOpt): () => void
185
185
 
186
- $forceUpdate (params?: object, callback?: () => void): void
186
+ $forceUpdate (params?: object, options?: object | (() => void), callback?: () => void): void
187
187
 
188
188
  $nextTick (fn: () => void): void
189
189
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mpxjs/core",
3
- "version": "2.9.64",
3
+ "version": "2.9.66",
4
4
  "description": "mpx runtime core",
5
5
  "keywords": [
6
6
  "miniprogram",
@@ -19,7 +19,7 @@
19
19
  ],
20
20
  "main": "src/index.js",
21
21
  "dependencies": {
22
- "@mpxjs/utils": "^2.9.64",
22
+ "@mpxjs/utils": "^2.9.65",
23
23
  "lodash": "^4.1.1",
24
24
  "miniprogram-api-typings": "^3.10.0"
25
25
  },
@@ -32,6 +32,7 @@
32
32
  "react": "*",
33
33
  "react-native": "*",
34
34
  "react-native-gesture-handler": "^2.19.0",
35
+ "react-native-linear-gradient": "^2.8.3",
35
36
  "react-native-safe-area-context": "^4.10.1",
36
37
  "react-native-webview": "^13.10.5",
37
38
  "vue": "^2.7.10",
@@ -75,6 +76,9 @@
75
76
  },
76
77
  "react-native-gesture-handler": {
77
78
  "optional": true
79
+ },
80
+ "react-native-linear-gradient": {
81
+ "optional": true
78
82
  }
79
83
  },
80
84
  "publishConfig": {
@@ -93,5 +97,5 @@
93
97
  "url": "https://github.com/didi/mpx/issues"
94
98
  },
95
99
  "sideEffects": false,
96
- "gitHead": "803334dc0e600f219d514c27461aa7663b7a6653"
100
+ "gitHead": "ff9eb06a3be28538870823cebf813ed56f39bbd7"
97
101
  }
package/src/core/proxy.js CHANGED
@@ -106,6 +106,7 @@ export default class MpxProxy {
106
106
  this.uid = uid++
107
107
  this.name = options.name || ''
108
108
  this.options = options
109
+ this.ignoreReactivePattern = this.options.options?.ignoreReactivePattern
109
110
  // beforeCreate -> created -> mounted -> unmounted
110
111
  this.state = BEFORECREATE
111
112
  this.ignoreProxyMap = makeMap(Mpx.config.ignoreProxyWhiteList)
@@ -135,6 +136,21 @@ export default class MpxProxy {
135
136
  this.initApi()
136
137
  }
137
138
 
139
+ processIgnoreReactive (obj) {
140
+ if (this.ignoreReactivePattern && isObject(obj)) {
141
+ Object.keys(obj).forEach((key) => {
142
+ if (this.ignoreReactivePattern.test(key)) {
143
+ Object.defineProperty(obj, key, {
144
+ enumerable: true,
145
+ // set configurable to false to skip defineReactive
146
+ configurable: false
147
+ })
148
+ }
149
+ })
150
+ }
151
+ return obj
152
+ }
153
+
138
154
  created () {
139
155
  if (__mpx_dynamic_runtime__) {
140
156
  // 缓存上下文,在 destoryed 阶段删除
@@ -208,6 +224,11 @@ export default class MpxProxy {
208
224
  if (this.update) this.update.active = false
209
225
  this.callHook(UNMOUNTED)
210
226
  this.state = UNMOUNTED
227
+ if (this._intersectionObservers) {
228
+ this._intersectionObservers.forEach((observer) => {
229
+ observer.disconnect()
230
+ })
231
+ }
211
232
  }
212
233
 
213
234
  isUnmounted () {
@@ -249,7 +270,7 @@ export default class MpxProxy {
249
270
  } else {
250
271
  this.props = diffAndCloneA(this.target.__getProps(this.options)).clone
251
272
  }
252
- reactive(this.props)
273
+ reactive(this.processIgnoreReactive(this.props))
253
274
  proxy(this.target, this.props, undefined, false, this.createProxyConflictHandler('props'))
254
275
  }
255
276
 
@@ -287,7 +308,7 @@ export default class MpxProxy {
287
308
  if (isFunction(dataFn)) {
288
309
  Object.assign(this.data, callWithErrorHandling(dataFn.bind(this.target), this, 'data function'))
289
310
  }
290
- reactive(this.data)
311
+ reactive(this.processIgnoreReactive(this.data))
291
312
  proxy(this.target, this.data, undefined, false, this.createProxyConflictHandler('data'))
292
313
  this.collectLocalKeys(this.data)
293
314
  }
@@ -419,7 +440,7 @@ export default class MpxProxy {
419
440
  if (hasOwn(renderData, key)) {
420
441
  const data = renderData[key]
421
442
  const firstKey = getFirstKey(key)
422
- if (!this.localKeysMap[firstKey]) {
443
+ if (!this.localKeysMap[firstKey] || (this.ignoreReactivePattern && this.ignoreReactivePattern.test(firstKey))) {
423
444
  continue
424
445
  }
425
446
  // 外部clone,用于只需要clone的场景
@@ -14,23 +14,21 @@ function refreshMs () {
14
14
  }
15
15
  }
16
16
 
17
- let loading = null
18
-
19
17
  function showLoading (vm) {
20
18
  const { backgroundColor = 'transparent', backgroundTextStyle = 'dark' } = vm.$options.__mpxPageConfig
21
- loading = document.createElement('div')
22
- loading.className = 'pull-down-loading'
23
- loading.style.cssText = `background-color: ${backgroundColor};`
19
+ vm.__mpxloading = document.createElement('div')
20
+ vm.__mpxloading.className = 'pull-down-loading'
21
+ vm.__mpxloading.style.cssText = `background-color: ${backgroundColor};`
24
22
  const dot = document.createElement('div')
25
23
  dot.className = `dot-flashing ${backgroundTextStyle}`
26
- loading.append(dot)
27
- vm.$el.prepend(loading)
24
+ vm.__mpxloading.append(dot)
25
+ vm.$el.prepend(vm.__mpxloading)
28
26
  }
29
27
 
30
28
  function hideLoading (vm) {
31
- if (loading) {
32
- vm.$el.removeChild(loading)
33
- loading = null
29
+ if (vm.__mpxloading) {
30
+ vm.$el.removeChild(vm.__mpxloading)
31
+ vm.__mpxloading = null
34
32
  }
35
33
  }
36
34
 
@@ -1,4 +1,4 @@
1
- import { isObject, isArray, dash2hump, isFunction, cached } from '@mpxjs/utils'
1
+ import { isObject, isArray, dash2hump, isFunction, cached, getFocusedNavigation } from '@mpxjs/utils'
2
2
  import { Dimensions, StyleSheet } from 'react-native'
3
3
 
4
4
  function rpx (value) {
@@ -12,16 +12,30 @@ function vw (value) {
12
12
  return value * width / 100
13
13
  }
14
14
  function vh (value) {
15
- const { height } = Dimensions.get('screen')
15
+ const navigation = getFocusedNavigation()
16
+ const height = navigation?.layout?.height || Dimensions.get('screen').height
16
17
  return value * height / 100
17
18
  }
18
19
 
19
- global.__unit = {
20
+ const unit = {
20
21
  rpx,
21
22
  vw,
22
23
  vh
23
24
  }
24
- global.__hairlineWidth = StyleSheet.hairlineWidth
25
+
26
+ function formatValue (value) {
27
+ let matched
28
+ if ((matched = numberRegExp.exec(value))) {
29
+ value = +matched[1]
30
+ } else if ((matched = unitRegExp.exec(value))) {
31
+ value = unit[matched[2]](+matched[1])
32
+ } else if (hairlineRegExp.test(value)) {
33
+ value = StyleSheet.hairlineWidth
34
+ }
35
+ return value
36
+ }
37
+
38
+ global.__formatValue = formatValue
25
39
 
26
40
  const escapeReg = /[()[\]{}#!.:,%'"+$]/g
27
41
  const escapeMap = {
@@ -97,7 +111,8 @@ const numberRegExp = /^\s*(-?\d+(\.\d+)?)(px)?\s*$/
97
111
  const hairlineRegExp = /^\s*hairlineWidth\s*$/
98
112
  const varRegExp = /^--/
99
113
 
100
- const parseStyleText = cached((cssText = '') => {
114
+ const parseStyleText = cached((cssText) => {
115
+ if (typeof cssText !== 'string') return cssText
101
116
  const res = {}
102
117
  const arr = cssText.split(listDelimiter)
103
118
  for (let i = 0; i < arr.length; i++) {
@@ -134,19 +149,9 @@ function mergeObjectArray (arr) {
134
149
  }
135
150
 
136
151
  function transformStyleObj (styleObj) {
137
- const keys = Object.keys(styleObj)
138
152
  const transformed = {}
139
- keys.forEach((prop) => {
140
- let value = styleObj[prop]
141
- let matched
142
- if ((matched = numberRegExp.exec(value))) {
143
- value = +matched[1]
144
- } else if ((matched = unitRegExp.exec(value))) {
145
- value = global.__unit[matched[2]](+matched[1])
146
- } else if (hairlineRegExp.test(value)) {
147
- value = StyleSheet.hairlineWidth
148
- }
149
- transformed[prop] = value
153
+ Object.keys(styleObj).forEach((prop) => {
154
+ transformed[prop] = formatValue(styleObj[prop])
150
155
  })
151
156
  return transformed
152
157
  }
@@ -1,4 +1,3 @@
1
-
2
1
  export {
3
2
  watchEffect,
4
3
  watchSyncEffect,
@@ -7,6 +7,7 @@ if (__mpx_mode__ === 'web') {
7
7
  builtInKeys = [
8
8
  'proto',
9
9
  'mixins',
10
+ 'initData',
10
11
  'mpxCustomKeysForBlend',
11
12
  'mpxConvertMode',
12
13
  'mpxFileResource',
@@ -20,6 +21,7 @@ if (__mpx_mode__ === 'web') {
20
21
  'dataFn',
21
22
  'proto',
22
23
  'mixins',
24
+ 'initData',
23
25
  'watch',
24
26
  'computed',
25
27
  'mpxCustomKeysForBlend',
@@ -1,14 +1,15 @@
1
- import { useEffect, useLayoutEffect, useSyncExternalStore, useRef, useMemo, createElement, memo, forwardRef, useImperativeHandle, useContext, createContext, Fragment, cloneElement } from 'react'
1
+ import { useEffect, useLayoutEffect, useSyncExternalStore, useRef, useMemo, useCallback, createElement, memo, forwardRef, useImperativeHandle, useContext, createContext, Fragment, cloneElement } from 'react'
2
2
  import * as ReactNative from 'react-native'
3
3
  import { ReactiveEffect } from '../../../observer/effect'
4
4
  import { watch } from '../../../observer/watch'
5
5
  import { reactive, set, del } from '../../../observer/reactive'
6
- import { hasOwn, isFunction, noop, isObject, error, getByPath, collectDataset, hump2dash } from '@mpxjs/utils'
6
+ import { hasOwn, isFunction, noop, isObject, getByPath, collectDataset, hump2dash } from '@mpxjs/utils'
7
7
  import MpxProxy from '../../../core/proxy'
8
8
  import { BEFOREUPDATE, ONLOAD, UPDATED, ONSHOW, ONHIDE, ONRESIZE, REACTHOOKSEXEC } from '../../../core/innerLifecycle'
9
9
  import mergeOptions from '../../../core/mergeOptions'
10
10
  import { queueJob } from '../../../observer/scheduler'
11
- import { createSelectorQuery } from '@mpxjs/api-proxy'
11
+ import { createSelectorQuery, createIntersectionObserver } from '@mpxjs/api-proxy'
12
+ import { IntersectionObserverContext } from '@mpxjs/webpack-plugin/lib/runtime/components/react/dist/context'
12
13
 
13
14
  function getSystemInfo () {
14
15
  const window = ReactNative.Dimensions.get('window')
@@ -40,13 +41,18 @@ function createEffect (proxy, components) {
40
41
  }
41
42
  update.id = proxy.uid
42
43
  const getComponent = (tagName) => {
44
+ if (!tagName) return null
43
45
  if (tagName === 'block') return Fragment
44
46
  return components[tagName] || getByPath(ReactNative, tagName)
45
47
  }
48
+ const innerCreateElement = (type, ...rest) => {
49
+ if (!type) return null
50
+ return createElement(type, ...rest)
51
+ }
46
52
  proxy.effect = new ReactiveEffect(() => {
47
53
  // reset instance
48
54
  proxy.target.__resetInstance()
49
- return proxy.target.__injectedRender(createElement, getComponent)
55
+ return proxy.target.__injectedRender(innerCreateElement, getComponent)
50
56
  }, () => queueJob(update), proxy.scope)
51
57
  }
52
58
 
@@ -63,7 +69,7 @@ function getRootProps (props) {
63
69
  return rootProps
64
70
  }
65
71
 
66
- function createInstance ({ propsRef, type, rawOptions, currentInject, validProps, components, pageId }) {
72
+ function createInstance ({ propsRef, type, rawOptions, currentInject, validProps, components, pageId, intersectionCtx }) {
67
73
  const instance = Object.create({
68
74
  setData (data, callback) {
69
75
  return this.__mpxProxy.forceUpdate(data, { sync: true }, callback)
@@ -178,8 +184,8 @@ function createInstance ({ propsRef, type, rawOptions, currentInject, validProps
178
184
  createSelectorQuery () {
179
185
  return createSelectorQuery().in(this)
180
186
  },
181
- createIntersectionObserver () {
182
- error('createIntersectionObserver is not supported in react native, please use ref instead')
187
+ createIntersectionObserver (opt) {
188
+ return createIntersectionObserver(this, opt, intersectionCtx)
183
189
  },
184
190
  ...rawOptions.methods
185
191
  }, {
@@ -344,12 +350,13 @@ export function getDefaultOptions ({ type, rawOptions = {}, currentInject }) {
344
350
  const defaultOptions = memo(forwardRef((props, ref) => {
345
351
  const instanceRef = useRef(null)
346
352
  const propsRef = useRef(null)
353
+ const intersectionCtx = useContext(IntersectionObserverContext)
347
354
  const pageId = useContext(RouteContext)
348
355
  propsRef.current = props
349
356
  let isFirst = false
350
357
  if (!instanceRef.current) {
351
358
  isFirst = true
352
- instanceRef.current = createInstance({ propsRef, type, rawOptions, currentInject, validProps, components, pageId })
359
+ instanceRef.current = createInstance({ propsRef, type, rawOptions, currentInject, validProps, components, pageId, intersectionCtx })
353
360
  }
354
361
  const instance = instanceRef.current
355
362
  useImperativeHandle(ref, () => {
@@ -408,21 +415,49 @@ export function getDefaultOptions ({ type, rawOptions = {}, currentInject }) {
408
415
  return root
409
416
  }))
410
417
 
418
+ if (rawOptions.options?.isCustomText) {
419
+ defaultOptions.isCustomText = true
420
+ }
421
+
411
422
  if (type === 'page') {
412
423
  const { Provider, useSafeAreaInsets, GestureHandlerRootView } = global.__navigationHelper
413
424
  const pageConfig = Object.assign({}, global.__mpxPageConfig, currentInject.pageConfig)
414
425
  const Page = ({ navigation, route }) => {
415
426
  const currentPageId = useMemo(() => ++pageId, [])
427
+ const intersectionObservers = useRef({})
416
428
  usePageStatus(navigation, currentPageId)
417
429
 
418
430
  useLayoutEffect(() => {
431
+ const isCustom = pageConfig.navigationStyle === 'custom'
432
+ let opt = {}
433
+ if (__mpx_mode__ === 'android') {
434
+ opt = {
435
+ statusBarTranslucent: isCustom,
436
+ statusBarStyle: pageConfig.statusBarStyle, // 枚举值 'auto' | 'dark' | 'light' 控制statusbar字体颜色
437
+ statusBarColor: isCustom ? 'transparent' : pageConfig.statusBarColor // 控制statusbar背景颜色
438
+ }
439
+ } else if (__mpx_mode__ === 'ios') {
440
+ opt = {
441
+ headerBackTitleVisible: false
442
+ }
443
+ }
419
444
  navigation.setOptions({
420
- headerShown: pageConfig.navigationStyle !== 'custom',
445
+ headerShown: !isCustom,
446
+ headerShadowVisible: false,
421
447
  headerTitle: pageConfig.navigationBarTitleText || '',
422
448
  headerStyle: {
423
449
  backgroundColor: pageConfig.navigationBarBackgroundColor || '#000000'
424
450
  },
425
- headerTintColor: pageConfig.navigationBarTextStyle || 'white'
451
+ headerTitleAlign: 'center',
452
+ headerTintColor: pageConfig.navigationBarTextStyle || 'white',
453
+ ...opt
454
+ })
455
+ }, [])
456
+
457
+ const rootRef = useRef(null)
458
+ const onLayout = useCallback(() => {
459
+ rootRef.current?.measureInWindow((x, y, width, height) => {
460
+ navigation.layout = { x, y, width, height }
426
461
  })
427
462
  }, [])
428
463
 
@@ -430,34 +465,43 @@ export function getDefaultOptions ({ type, rawOptions = {}, currentInject }) {
430
465
 
431
466
  return createElement(GestureHandlerRootView,
432
467
  {
468
+ style: {
469
+ flex: 1
470
+ }
471
+ },
472
+ createElement(ReactNative.View, {
433
473
  style: {
434
474
  flex: 1,
435
475
  backgroundColor: pageConfig.backgroundColor || '#ffffff'
436
476
  },
437
- onLayout (e) {
438
- navigation.layout = e.nativeEvent.layout
439
- }
477
+ ref: rootRef,
478
+ onLayout
440
479
  },
441
- // todo custom portal host for active route
442
- createElement(Provider,
443
- null,
444
- createElement(RouteContext.Provider,
445
- {
446
- value: currentPageId
447
- },
448
- createElement(defaultOptions,
480
+ createElement(Provider,
481
+ null,
482
+ createElement(RouteContext.Provider,
449
483
  {
450
- navigation,
451
- route,
452
- id: currentPageId
453
- }
484
+ value: currentPageId
485
+ },
486
+ createElement(IntersectionObserverContext.Provider,
487
+ {
488
+ value: intersectionObservers.current
489
+ },
490
+ createElement(defaultOptions,
491
+ {
492
+ navigation,
493
+ route,
494
+ id: currentPageId
495
+ }
496
+ )
497
+ )
454
498
  )
455
499
  )
456
500
  )
501
+ // todo custom portal host for active route
457
502
  )
458
503
  }
459
504
  return Page
460
505
  }
461
-
462
506
  return defaultOptions
463
507
  }
@@ -23,11 +23,13 @@ function transformProperties (properties) {
23
23
  } else {
24
24
  newFiled = Object.assign({}, rawFiled)
25
25
  }
26
- newFiled.observer = function (value) {
26
+ const rawObserver = rawFiled?.observer
27
+ newFiled.observer = function (value, oldValue) {
27
28
  if (this.__mpxProxy) {
28
29
  this[key] = value
29
30
  this.__mpxProxy.propsUpdated()
30
31
  }
32
+ rawObserver && rawObserver.call(this, value, oldValue)
31
33
  }
32
34
  newProps[key] = newFiled
33
35
  })