@mpxjs/webpack-plugin 2.10.7-beta.1 → 2.10.7-beta.10

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.
Files changed (45) hide show
  1. package/lib/dependencies/RecordPageConfigsMapDependency.js +1 -1
  2. package/lib/file-loader.js +1 -1
  3. package/lib/index.js +78 -86
  4. package/lib/parser.js +1 -1
  5. package/lib/platform/json/wx/index.js +25 -43
  6. package/lib/platform/style/wx/index.js +7 -0
  7. package/lib/platform/template/wx/component-config/fix-component-name.js +2 -2
  8. package/lib/platform/template/wx/component-config/movable-view.js +1 -10
  9. package/lib/platform/template/wx/index.js +1 -2
  10. package/lib/react/index.js +1 -3
  11. package/lib/react/processJSON.js +11 -66
  12. package/lib/react/processScript.js +3 -4
  13. package/lib/react/script-helper.js +18 -92
  14. package/lib/runtime/components/react/AsyncContainer.tsx +7 -35
  15. package/lib/runtime/components/react/context.ts +12 -2
  16. package/lib/runtime/components/react/dist/AsyncContainer.jsx +4 -23
  17. package/lib/runtime/components/react/dist/context.js +1 -1
  18. package/lib/runtime/components/react/dist/getInnerListeners.js +1 -1
  19. package/lib/runtime/components/react/dist/mpx-movable-area.jsx +63 -9
  20. package/lib/runtime/components/react/dist/mpx-movable-view.jsx +308 -63
  21. package/lib/runtime/components/react/dist/mpx-scroll-view.jsx +15 -10
  22. package/lib/runtime/components/react/dist/mpx-sticky-header.jsx +3 -1
  23. package/lib/runtime/components/react/dist/mpx-web-view.jsx +28 -14
  24. package/lib/runtime/components/react/dist/useAnimationHooks.js +87 -2
  25. package/lib/runtime/components/react/dist/utils.jsx +2 -13
  26. package/lib/runtime/components/react/getInnerListeners.ts +1 -1
  27. package/lib/runtime/components/react/mpx-movable-area.tsx +98 -11
  28. package/lib/runtime/components/react/mpx-movable-view.tsx +358 -64
  29. package/lib/runtime/components/react/mpx-scroll-view.tsx +16 -9
  30. package/lib/runtime/components/react/mpx-sticky-header.tsx +3 -1
  31. package/lib/runtime/components/react/mpx-web-view.tsx +33 -13
  32. package/lib/runtime/components/react/types/global.d.ts +0 -15
  33. package/lib/runtime/components/react/useAnimationHooks.ts +85 -2
  34. package/lib/runtime/components/react/utils.tsx +2 -13
  35. package/lib/runtime/components/web/mpx-scroll-view.vue +4 -7
  36. package/lib/runtime/components/web/mpx-sticky-header.vue +39 -31
  37. package/lib/script-setup-compiler/index.js +27 -5
  38. package/lib/template-compiler/bind-this.js +2 -1
  39. package/lib/template-compiler/compiler.js +4 -3
  40. package/lib/utils/dom-tag-config.js +3 -17
  41. package/lib/web/script-helper.js +1 -1
  42. package/package.json +2 -2
  43. package/lib/react/LoadAsyncChunkModule.js +0 -68
  44. package/lib/runtime/components/react/AsyncSuspense.tsx +0 -81
  45. package/lib/runtime/components/react/dist/AsyncSuspense.jsx +0 -68
@@ -3,74 +3,30 @@ const createHelpers = require('../helpers')
3
3
  const parseRequest = require('../utils/parse-request')
4
4
  const shallowStringify = require('../utils/shallow-stringify')
5
5
  const normalize = require('../utils/normalize')
6
- const addQuery = require('../utils/add-query')
7
- const path = require('path')
8
- const { isBuildInReactTag } = require('../utils/dom-tag-config')
9
6
 
10
7
  function stringifyRequest (loaderContext, request) {
11
8
  return loaderUtils.stringifyRequest(loaderContext, request)
12
9
  }
13
10
 
14
- function getMpxComponentRequest (component) {
15
- return JSON.stringify(addQuery(`@mpxjs/webpack-plugin/lib/runtime/components/react/dist/${component}`, { isComponent: true }))
16
- }
17
-
18
- const mpxAsyncSuspense = getMpxComponentRequest('AsyncSuspense')
19
-
20
- function getAsyncChunkName (chunkName) {
21
- if (chunkName && typeof chunkName !== 'boolean') {
22
- return `/* webpackChunkName: "${chunkName}/index" */`
23
- }
24
- return ''
25
- }
11
+ // function getAsyncChunkName (chunkName) {
12
+ // if (chunkName && typeof chunkName !== 'boolean') {
13
+ // return `/* webpackChunkName: "${chunkName}" */`
14
+ // }
15
+ // return ''
16
+ // }
26
17
 
27
- function getAsyncComponent (componentName, componentRequest, chunkName, fallback) {
28
- return `getComponent(memo(forwardRef(function(props, ref) {
29
- return createElement(
30
- getComponent(require(${mpxAsyncSuspense})),
31
- {
32
- type: 'component',
33
- props: Object.assign({}, props, { ref }),
34
- chunkName: ${JSON.stringify(chunkName)},
35
- request: ${JSON.stringify(componentRequest)},
36
- loading: getComponent(require(${fallback})),
37
- getChildren: () => import(${getAsyncChunkName(chunkName)}${componentRequest})
38
- }
39
- )
40
- })))`
41
- }
42
-
43
- function getAsyncPage (componentName, componentRequest, chunkName, fallback, loading) {
44
- fallback = fallback && `getComponent(require('${fallback}?isComponent=true'))`
45
- loading = loading && `getComponent(require('${loading}?isComponent=true'))`
46
- return `getComponent(function(props) {
47
- return createElement(
48
- getComponent(require(${mpxAsyncSuspense})),
49
- {
50
- type: 'page',
51
- props: props,
52
- chunkName: ${JSON.stringify(chunkName)},
53
- request: ${JSON.stringify(componentRequest)},
54
- fallback: ${fallback},
55
- loading: ${loading},
56
- getChildren: () => import(${getAsyncChunkName(chunkName)}${componentRequest})
57
- }
58
- )
59
- })`
60
- }
61
-
62
- function buildPagesMap ({ localPagesMap, loaderContext, jsonConfig, rnConfig }) {
18
+ function buildPagesMap ({ localPagesMap, loaderContext, jsonConfig }) {
63
19
  let firstPage = ''
64
20
  const pagesMap = {}
65
21
  Object.keys(localPagesMap).forEach((pagePath) => {
66
22
  const pageCfg = localPagesMap[pagePath]
67
23
  const pageRequest = stringifyRequest(loaderContext, pageCfg.resource)
68
- if (pageCfg.async) {
69
- pagesMap[pagePath] = getAsyncPage(pagePath, pageRequest, pageCfg.async, rnConfig.asyncChunk && rnConfig.asyncChunk.fallback, rnConfig.asyncChunk && rnConfig.asyncChunk.loading)
70
- } else {
24
+ // if (pageCfg.async) {
25
+ // pagesMap[pagePath] = `lazy(function(){return import(${getAsyncChunkName(pageCfg.async)} ${pageRequest}).then(function(res){return getComponent(res, {__mpxPageRoute: ${JSON.stringify(pagePath)}, displayName: "Page"})})})`
26
+ // } else {
71
27
  // 为了保持小程序中app->page->component的js执行顺序,所有的page和component都改为require引入
72
- pagesMap[pagePath] = `getComponent(require(${pageRequest}), {__mpxPageRoute: ${JSON.stringify(pagePath)}, displayName: "Page"})`
73
- }
28
+ pagesMap[pagePath] = `getComponent(require(${pageRequest}), {__mpxPageRoute: ${JSON.stringify(pagePath)}, displayName: "Page"})`
29
+ // }
74
30
  if (pagePath === jsonConfig.entryPagePath) {
75
31
  firstPage = pagePath
76
32
  }
@@ -86,45 +42,16 @@ function buildPagesMap ({ localPagesMap, loaderContext, jsonConfig, rnConfig })
86
42
 
87
43
  function buildComponentsMap ({ localComponentsMap, builtInComponentsMap, loaderContext, jsonConfig }) {
88
44
  const componentsMap = {}
89
- const mpx = loaderContext.getMpx()
90
45
  if (localComponentsMap) {
91
46
  Object.keys(localComponentsMap).forEach((componentName) => {
92
47
  const componentCfg = localComponentsMap[componentName]
93
48
  const componentRequest = stringifyRequest(loaderContext, componentCfg.resource)
94
- if (componentCfg.async) {
95
- const placeholder = jsonConfig.componentPlaceholder && jsonConfig.componentPlaceholder[componentName]
96
- if (placeholder) {
97
- if (localComponentsMap[placeholder]) {
98
- const placeholderCfg = localComponentsMap[placeholder]
99
- const placeholderRequest = stringifyRequest(loaderContext, placeholderCfg.resource)
100
- if (placeholderCfg.async) {
101
- loaderContext.emitWarning(
102
- new Error(`[json processor][${loaderContext.resource}]: componentPlaceholder ${placeholder} should not be a async component, please check!`)
103
- )
104
- }
105
- componentsMap[componentName] = getAsyncComponent(componentName, componentRequest, componentCfg.async, placeholderRequest)
106
- } else if (mpx.globalComponents[placeholder]) {
107
- const { queryObj, rawResourcePath } = parseRequest(mpx.globalComponents[placeholder])
108
- const placeholderRequest = JSON.stringify(path.resolve(queryObj.context, rawResourcePath))
109
- componentsMap[componentName] = getAsyncComponent(componentName, componentRequest, componentCfg.async, placeholderRequest)
110
- } else {
111
- const tag = `mpx-${placeholder}`
112
- if (!isBuildInReactTag(tag)) {
113
- loaderContext.emitError(
114
- new Error(`[json processor][${loaderContext.resource}]: componentPlaceholder ${placeholder} is not built-in component, please check!`)
115
- )
116
- }
117
- componentsMap[componentName] = getAsyncComponent(componentName, componentRequest, componentCfg.async, getMpxComponentRequest(tag))
118
- }
119
- } else {
120
- loaderContext.emitError(
121
- new Error(`[json processor][${loaderContext.resource}]: ${componentName} has no componentPlaceholder, please check!`)
122
- )
123
- componentsMap[componentName] = getAsyncComponent(componentName, componentRequest, componentCfg.async)
124
- }
125
- } else {
126
- componentsMap[componentName] = `getComponent(require(${componentRequest}), {displayName: ${JSON.stringify(componentName)}})`
127
- }
49
+ // RN中暂不支持异步加载
50
+ // if (componentCfg.async) {
51
+ // componentsMap[componentName] = `lazy(function(){return import(${getAsyncChunkName(componentCfg.async)}${componentRequest}).then(function(res){return getComponent(res, {displayName: ${JSON.stringify(componentName)}})})})`
52
+ // } else {
53
+ componentsMap[componentName] = `getComponent(require(${componentRequest}), {displayName: ${JSON.stringify(componentName)}})`
54
+ // }
128
55
  })
129
56
  }
130
57
  if (builtInComponentsMap) {
@@ -180,7 +107,6 @@ global.__mpxPageConfig = ${JSON.stringify(jsonConfig.window)}
180
107
  global.__getAppComponents = function () {
181
108
  return ${shallowStringify(componentsMap)}
182
109
  }
183
- global.__preloadRule = ${JSON.stringify(jsonConfig.preloadRule)}
184
110
  global.currentInject.getPages = function () {
185
111
  return ${shallowStringify(pagesMap)}
186
112
  }
@@ -1,29 +1,6 @@
1
- import { ComponentType, ReactNode, Component, Suspense } from 'react'
1
+ import { ComponentType, ReactNode, Component, Fragment, Suspense } from 'react'
2
2
  import { View, Text, Image, StyleSheet, TouchableOpacity } from 'react-native'
3
3
  import FastImage from '@d11/react-native-fast-image'
4
- import Animated, { useAnimatedStyle } from 'react-native-reanimated'
5
-
6
- type PageWrapper = {
7
- children: ReactNode
8
- }
9
-
10
- export const PageWrapper = ({ children }: PageWrapper) => {
11
- const animatedStyle = useAnimatedStyle(() => ({
12
- transform: [{ translateY: -0 }],
13
- flexBasis: 'auto',
14
- flex: 1
15
- }))
16
-
17
- return (
18
- <Animated.View
19
- style={[
20
- animatedStyle
21
- ]}
22
- >
23
- {children}
24
- </Animated.View>
25
- )
26
- }
27
4
 
28
5
  const styles = StyleSheet.create({
29
6
  container: {
@@ -82,7 +59,7 @@ interface PropsType<T extends AsyncType> {
82
59
  props: object
83
60
  loading: ComponentType<unknown>
84
61
  fallback: ComponentType<unknown>
85
- children: (props: any) => ReactNode | ReactNode
62
+ children: (props: unknown) => ReactNode
86
63
  }
87
64
 
88
65
  interface StateType {
@@ -95,7 +72,7 @@ interface ComponentError extends Error {
95
72
  type: 'timeout' | 'fail'
96
73
  }
97
74
 
98
- export const DefaultLoading = () => {
75
+ const DefaultLoading = () => {
99
76
  return (
100
77
  <View style={styles.container}>
101
78
  <FastImage
@@ -106,11 +83,11 @@ export const DefaultLoading = () => {
106
83
  )
107
84
  }
108
85
 
109
- export interface DefaultFallbackProps {
86
+ interface DefaultFallbackProps {
110
87
  onReload: () => void
111
88
  }
112
89
 
113
- export const DefaultFallback = ({ onReload }: DefaultFallbackProps) => {
90
+ const DefaultFallback = ({ onReload }: DefaultFallbackProps) => {
114
91
  return (
115
92
  <View style={styles.container}>
116
93
  <Image
@@ -200,16 +177,11 @@ export default class AsyncContainer extends Component<PropsType<AsyncType>, Stat
200
177
 
201
178
  render () {
202
179
  if (this.state.hasError) {
203
- if (this.props.type === 'component') {
204
- return this.errorFallback
205
- } else {
206
- return (<PageWrapper>{this.errorFallback}</PageWrapper>)
207
- }
180
+ return this.errorFallback
208
181
  } else {
209
182
  return (
210
183
  <Suspense fallback={this.suspenseFallback} key={this.state.key}>
211
- {typeof this.props.children === 'function' ? this.props.children(this.props.props) : this.props.children}
212
- {/* {this.props.children(this.props.props)} */}
184
+ {this.props.children(this.props.props)}
213
185
  </Suspense>
214
186
  )
215
187
  }
@@ -51,14 +51,24 @@ export interface RouteContextValue {
51
51
  pageId: number
52
52
  navigation: Record<string, any>
53
53
  }
54
+ export interface MovableAreaContextValue {
55
+ width: number
56
+ height: number
57
+ scaleArea: boolean
58
+ onAreaScale?: (scaleInfo: { scale: number }) => void
59
+ registerMovableView?: (id: string, callbacks: {
60
+ onScale: (scaleInfo: { scale: number }) => void
61
+ onScaleEnd?: () => void
62
+ }) => void
63
+ unregisterMovableView?: (id: string) => void
64
+ }
54
65
 
66
+ export const MovableAreaContext = createContext<MovableAreaContextValue>({ width: 0, height: 0, scaleArea: false })
55
67
  export interface StickyContextValue {
56
68
  registerStickyHeader: Function,
57
69
  unregisterStickyHeader: Function
58
70
  }
59
71
 
60
- export const MovableAreaContext = createContext({ width: 0, height: 0 })
61
-
62
72
  export const FormContext = createContext<FormContextValue | null>(null)
63
73
 
64
74
  export const CheckboxGroupContext = createContext<GroupContextValue | null>(null)
@@ -1,19 +1,6 @@
1
1
  import { Component, Suspense } from 'react';
2
2
  import { View, Text, Image, StyleSheet, TouchableOpacity } from 'react-native';
3
3
  import FastImage from '@d11/react-native-fast-image';
4
- import Animated, { useAnimatedStyle } from 'react-native-reanimated';
5
- export const PageWrapper = ({ children }) => {
6
- const animatedStyle = useAnimatedStyle(() => ({
7
- transform: [{ translateY: -0 }],
8
- flexBasis: 'auto',
9
- flex: 1
10
- }));
11
- return (<Animated.View style={[
12
- animatedStyle
13
- ]}>
14
- {children}
15
- </Animated.View>);
16
- };
17
4
  const styles = StyleSheet.create({
18
5
  container: {
19
6
  flex: 1,
@@ -63,12 +50,12 @@ const styles = StyleSheet.create({
63
50
  textAlign: 'center'
64
51
  }
65
52
  });
66
- export const DefaultLoading = () => {
53
+ const DefaultLoading = () => {
67
54
  return (<View style={styles.container}>
68
55
  <FastImage style={styles.loadingImage} source={{ uri: 'https://dpubstatic.udache.com/static/dpubimg/439jiCVOtNOnEv9F2LaDs_loading.gif' }} resizeMode={FastImage.resizeMode.contain}></FastImage>
69
56
  </View>);
70
57
  };
71
- export const DefaultFallback = ({ onReload }) => {
58
+ const DefaultFallback = ({ onReload }) => {
72
59
  return (<View style={styles.container}>
73
60
  <Image source={{ uri: 'https://dpubstatic.udache.com/static/dpubimg/Vak5mZvezPpKV5ZJI6P9b_drn-fallbak.png' }} style={styles.errorImage} resizeMode="contain"/>
74
61
  <Text style={styles.errorText}>网络出了点问题,请查看网络环境</Text>
@@ -143,17 +130,11 @@ export default class AsyncContainer extends Component {
143
130
  }
144
131
  render() {
145
132
  if (this.state.hasError) {
146
- if (this.props.type === 'component') {
147
- return this.errorFallback;
148
- }
149
- else {
150
- return (<PageWrapper>{this.errorFallback}</PageWrapper>);
151
- }
133
+ return this.errorFallback;
152
134
  }
153
135
  else {
154
136
  return (<Suspense fallback={this.suspenseFallback} key={this.state.key}>
155
- {typeof this.props.children === 'function' ? this.props.children(this.props.props) : this.props.children}
156
- {/* {this.props.children(this.props.props)} */}
137
+ {this.props.children(this.props.props)}
157
138
  </Suspense>);
158
139
  }
159
140
  }
@@ -1,7 +1,7 @@
1
1
  import { createContext } from 'react';
2
2
  import { Animated } from 'react-native';
3
3
  import { noop } from '@mpxjs/utils';
4
- export const MovableAreaContext = createContext({ width: 0, height: 0 });
4
+ export const MovableAreaContext = createContext({ width: 0, height: 0, scaleArea: false });
5
5
  export const FormContext = createContext(null);
6
6
  export const CheckboxGroupContext = createContext(null);
7
7
  export const RadioGroupContext = createContext(null);
@@ -8,7 +8,7 @@ const globalEventState = {
8
8
  const getTouchEvent = (type, event, config) => {
9
9
  const { navigation, propsRef, layoutRef } = config;
10
10
  const props = propsRef.current;
11
- const { top: navigationY = 0 } = navigation?.layout || {};
11
+ const { y: navigationY = 0 } = navigation?.layout || {};
12
12
  const nativeEvent = event.nativeEvent;
13
13
  const { timestamp, pageX, pageY, touches, changedTouches } = nativeEvent;
14
14
  const { id } = props;
@@ -1,33 +1,87 @@
1
1
  /**
2
- * scale-area
2
+ * scale-area
3
3
  */
4
4
  import { View } from 'react-native';
5
- import { forwardRef, useRef, useMemo, createElement } from 'react';
5
+ import { forwardRef, useRef, useMemo, useCallback, createElement } from 'react';
6
+ import { GestureDetector, Gesture } from 'react-native-gesture-handler';
7
+ import { useSharedValue } from 'react-native-reanimated';
6
8
  import useNodesRef from './useNodesRef';
7
9
  import useInnerProps from './getInnerListeners';
8
10
  import { MovableAreaContext } from './context';
9
11
  import { useTransformStyle, wrapChildren, useLayout, extendObject } from './utils';
10
12
  import Portal from './mpx-portal';
11
13
  const _MovableArea = forwardRef((props, ref) => {
12
- const { style = {}, 'enable-var': enableVar, 'external-var-context': externalVarContext, 'parent-font-size': parentFontSize, 'parent-width': parentWidth, 'parent-height': parentHeight } = props;
14
+ const { style = {}, 'scale-area': scaleArea = false, 'enable-var': enableVar, 'external-var-context': externalVarContext, 'parent-font-size': parentFontSize, 'parent-width': parentWidth, 'parent-height': parentHeight } = props;
13
15
  const { hasSelfPercent, normalStyle, hasVarDec, varContextRef, hasPositionFixed, setWidth, setHeight } = useTransformStyle(style, { enableVar, externalVarContext, parentFontSize, parentWidth, parentHeight });
14
- const movableViewRef = useRef(null);
15
- useNodesRef(props, ref, movableViewRef, {
16
+ const movableAreaRef = useRef(null);
17
+ const movableViewsValue = useSharedValue({});
18
+ useNodesRef(props, ref, movableAreaRef, {
16
19
  style: normalStyle
17
20
  });
21
+ // 注册/注销 MovableView 的回调
22
+ const registerMovableView = useCallback((id, callbacks) => {
23
+ movableViewsValue.value = extendObject(movableViewsValue.value, { [id]: callbacks });
24
+ }, []);
25
+ const unregisterMovableView = useCallback((id) => {
26
+ delete movableViewsValue.value[id];
27
+ }, []);
28
+ // 处理区域缩放手势
29
+ const handleAreaScale = useCallback((scaleInfo) => {
30
+ 'worklet';
31
+ if (scaleArea) {
32
+ // 将缩放信息广播给所有注册的 MovableView
33
+ Object.values(movableViewsValue.value).forEach((callbacks) => {
34
+ callbacks.onScale && callbacks.onScale(scaleInfo);
35
+ });
36
+ }
37
+ }, [scaleArea]);
38
+ // 处理区域缩放结束
39
+ const handleAreaScaleEnd = useCallback(() => {
40
+ 'worklet';
41
+ if (scaleArea) {
42
+ // 通知所有注册的 MovableView 缩放结束
43
+ Object.values(movableViewsValue.value).forEach((callbacks) => {
44
+ callbacks.onScaleEnd && callbacks.onScaleEnd();
45
+ });
46
+ }
47
+ }, [scaleArea]);
18
48
  const contextValue = useMemo(() => ({
19
49
  height: normalStyle.height || 10,
20
- width: normalStyle.width || 10
21
- }), [normalStyle.width, normalStyle.height]);
22
- const { layoutRef, layoutStyle, layoutProps } = useLayout({ props, hasSelfPercent, setWidth, setHeight, nodeRef: movableViewRef });
50
+ width: normalStyle.width || 10,
51
+ scaleArea,
52
+ registerMovableView,
53
+ unregisterMovableView
54
+ }), [normalStyle.width, normalStyle.height, scaleArea]);
55
+ const { layoutRef, layoutStyle, layoutProps } = useLayout({ props, hasSelfPercent, setWidth, setHeight, nodeRef: movableAreaRef });
56
+ // 创建缩放手势
57
+ const scaleGesture = useMemo(() => {
58
+ if (!scaleArea)
59
+ return null;
60
+ return Gesture.Pinch()
61
+ .onUpdate((e) => {
62
+ 'worklet';
63
+ handleAreaScale(e);
64
+ })
65
+ .onEnd(() => {
66
+ 'worklet';
67
+ handleAreaScaleEnd();
68
+ });
69
+ }, [scaleArea]);
23
70
  const innerProps = useInnerProps(extendObject({}, props, layoutProps, {
24
71
  style: extendObject({ height: contextValue.height, width: contextValue.width }, normalStyle, layoutStyle),
25
- ref: movableViewRef
72
+ ref: movableAreaRef
26
73
  }), [], { layoutRef });
27
74
  let movableComponent = createElement(MovableAreaContext.Provider, { value: contextValue }, createElement(View, innerProps, wrapChildren(props, {
28
75
  hasVarDec,
29
76
  varContext: varContextRef.current
30
77
  })));
78
+ // 如果启用了 scale-area,包装一个 GestureDetector
79
+ if (scaleArea && scaleGesture) {
80
+ movableComponent = createElement(MovableAreaContext.Provider, { value: contextValue }, createElement(GestureDetector, { gesture: scaleGesture }, createElement(View, innerProps, wrapChildren(props, {
81
+ hasVarDec,
82
+ varContext: varContextRef.current
83
+ }))));
84
+ }
31
85
  if (hasPositionFixed) {
32
86
  movableComponent = createElement(Portal, null, movableComponent);
33
87
  }