@shoutem/ui 9.0.0-rc.4 → 9.0.0
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/html/components/AttachmentRenderer.js +14 -18
- package/html/components/SimpleHtml.js +117 -105
- package/html/services/HTMLElementModels.js +6 -0
- package/package.json +4 -4
- package/theme.js +4 -1
|
@@ -4,39 +4,35 @@ import PropTypes from 'prop-types';
|
|
|
4
4
|
import { resolveDimensions } from '../services/Dimensions';
|
|
5
5
|
import Image from './Image';
|
|
6
6
|
|
|
7
|
-
const AttachmentRenderer = ({
|
|
8
|
-
if (!attachments) {
|
|
7
|
+
const AttachmentRenderer = ({ tnode, style, attachments }) => {
|
|
8
|
+
if (!attachments || _.isEmpty(attachments)) {
|
|
9
9
|
return null;
|
|
10
10
|
}
|
|
11
11
|
|
|
12
|
-
|
|
13
|
-
const image = _.find(attachments, { id });
|
|
12
|
+
const image = _.find(attachments, { id: tnode?.id });
|
|
14
13
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
const imageSize = { width: image.width, height: image.height };
|
|
18
|
-
const { height, width } = resolveDimensions(imageSize, style);
|
|
19
|
-
const resolvedStyle = { alignSelf: 'center', height, width };
|
|
20
|
-
|
|
21
|
-
return <Image source={source} key={id} style={resolvedStyle} />;
|
|
22
|
-
}
|
|
14
|
+
if (!image || !image.src) {
|
|
15
|
+
return null;
|
|
23
16
|
}
|
|
24
17
|
|
|
25
|
-
|
|
18
|
+
const source = { uri: image.src };
|
|
19
|
+
const imageSize = { width: image.width, height: image.height };
|
|
20
|
+
const { height, width } = resolveDimensions(imageSize, style);
|
|
21
|
+
const resolvedStyle = { alignSelf: 'center', height, width };
|
|
22
|
+
|
|
23
|
+
return <Image source={source} key={tnode?.id} style={resolvedStyle} />;
|
|
26
24
|
};
|
|
27
25
|
|
|
28
26
|
AttachmentRenderer.propTypes = {
|
|
29
|
-
attachments: PropTypes.
|
|
30
|
-
id: PropTypes.any,
|
|
27
|
+
attachments: PropTypes.oneOfType([PropTypes.array, PropTypes.object]),
|
|
31
28
|
style: PropTypes.any,
|
|
32
|
-
|
|
29
|
+
tnode: PropTypes.object,
|
|
33
30
|
};
|
|
34
31
|
|
|
35
32
|
AttachmentRenderer.defaultProps = {
|
|
36
|
-
id: undefined,
|
|
37
|
-
type: undefined,
|
|
38
33
|
attachments: undefined,
|
|
39
34
|
style: undefined,
|
|
35
|
+
tnode: {},
|
|
40
36
|
};
|
|
41
37
|
|
|
42
38
|
export default AttachmentRenderer;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
|
|
1
|
+
/* eslint-disable react/prop-types */
|
|
2
|
+
import React, { useCallback, useMemo } from 'react';
|
|
2
3
|
import { Linking } from 'react-native';
|
|
3
4
|
import Html, { defaultSystemFonts } from 'react-native-render-html';
|
|
4
5
|
import WebView from 'react-native-webview';
|
|
@@ -8,64 +9,66 @@ import table, {
|
|
|
8
9
|
defaultTableStylesSpecs,
|
|
9
10
|
tableModel,
|
|
10
11
|
} from '@native-html/table-plugin';
|
|
11
|
-
import autoBindReact from 'auto-bind/react';
|
|
12
12
|
import _ from 'lodash';
|
|
13
|
-
import PropTypes from 'prop-types';
|
|
14
13
|
import { connectStyle } from '@shoutem/theme';
|
|
15
14
|
import VideoRenderer from '@shoutem/ui/html/components/VideoRenderer';
|
|
16
|
-
import {
|
|
15
|
+
import {
|
|
16
|
+
attachmentModel,
|
|
17
|
+
videoModel,
|
|
18
|
+
} from '@shoutem/ui/html/services/HTMLElementModels';
|
|
17
19
|
import { View } from '../../components/View';
|
|
18
20
|
import { resolveMaxWidth } from '../services/Dimensions';
|
|
19
21
|
import { onElement } from '../services/DomVisitors';
|
|
20
22
|
import AttachmentRenderer from './AttachmentRenderer';
|
|
21
23
|
import IframeRenderer from './IframeRenderer';
|
|
22
24
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
25
|
+
const SimpleHtml = ({
|
|
26
|
+
style,
|
|
27
|
+
body,
|
|
28
|
+
unsupportedVideoFormatMessage,
|
|
29
|
+
attachments,
|
|
30
|
+
customDomVisitors = {},
|
|
31
|
+
customCustomRenderers = {},
|
|
32
|
+
customTagStyles = {},
|
|
33
|
+
customHtmlElementModels = {},
|
|
34
|
+
customIgnoredStyles = [],
|
|
35
|
+
customRendererProps = {},
|
|
36
|
+
customHandleLinkPress,
|
|
37
|
+
...otherProps
|
|
38
|
+
}) => {
|
|
39
|
+
const onLinkPress = useCallback(
|
|
40
|
+
(_evt, href) => {
|
|
41
|
+
return customHandleLinkPress
|
|
42
|
+
? customHandleLinkPress(href)
|
|
43
|
+
: Linking.openURL(href);
|
|
44
|
+
},
|
|
45
|
+
[customHandleLinkPress],
|
|
46
|
+
);
|
|
26
47
|
|
|
27
|
-
|
|
28
|
-
|
|
48
|
+
const maxWidth = useMemo(() => {
|
|
49
|
+
return resolveMaxWidth(style);
|
|
50
|
+
}, [style]);
|
|
29
51
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
return customHandleLinkPress
|
|
34
|
-
? customHandleLinkPress(href)
|
|
35
|
-
: Linking.openURL(href);
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
render() {
|
|
39
|
-
const {
|
|
40
|
-
style,
|
|
41
|
-
body,
|
|
42
|
-
unsupportedVideoFormatMessage,
|
|
43
|
-
attachments,
|
|
44
|
-
customDomVisitors,
|
|
45
|
-
customCustomRenderers,
|
|
46
|
-
customTagStyles,
|
|
47
|
-
customHtmlElementModels,
|
|
48
|
-
customIgnoredStyles,
|
|
49
|
-
customRendererProps,
|
|
50
|
-
...otherProps
|
|
51
|
-
} = this.props;
|
|
52
|
-
|
|
53
|
-
const maxWidth = resolveMaxWidth(style);
|
|
54
|
-
|
|
55
|
-
const tagStyles = {
|
|
52
|
+
const tagStyles = useMemo(
|
|
53
|
+
() => ({
|
|
56
54
|
...style.tags,
|
|
57
55
|
...customTagStyles,
|
|
58
|
-
}
|
|
56
|
+
}),
|
|
57
|
+
[style.tags, customTagStyles],
|
|
58
|
+
);
|
|
59
59
|
|
|
60
|
+
const cssRules = useMemo(() => {
|
|
60
61
|
const tableStyle = _.get(style, 'table', {});
|
|
61
62
|
const tableCssStyle = _.get(style, 'tableCss', '');
|
|
62
63
|
const cssStyle = cssRulesFromSpecs({
|
|
63
64
|
...defaultTableStylesSpecs,
|
|
64
65
|
...tableStyle,
|
|
65
66
|
});
|
|
66
|
-
|
|
67
|
+
return `${cssStyle}${tableCssStyle}`;
|
|
68
|
+
}, [style]);
|
|
67
69
|
|
|
68
|
-
|
|
70
|
+
const customRenderers = useMemo(
|
|
71
|
+
() => ({
|
|
69
72
|
iframe: props => (
|
|
70
73
|
<IframeRenderer
|
|
71
74
|
{...props}
|
|
@@ -76,86 +79,95 @@ class SimpleHtml extends PureComponent {
|
|
|
76
79
|
table,
|
|
77
80
|
attachment: props => (
|
|
78
81
|
<AttachmentRenderer
|
|
79
|
-
{
|
|
82
|
+
tnode={props.tnode}
|
|
80
83
|
attachments={attachments}
|
|
81
84
|
style={style}
|
|
82
85
|
/>
|
|
83
86
|
),
|
|
84
87
|
video: VideoRenderer,
|
|
85
88
|
...customCustomRenderers,
|
|
86
|
-
}
|
|
89
|
+
}),
|
|
90
|
+
[style, unsupportedVideoFormatMessage, attachments, customCustomRenderers],
|
|
91
|
+
);
|
|
92
|
+
|
|
93
|
+
const systemFonts = useMemo(
|
|
94
|
+
() => [...defaultSystemFonts, style.baseFont.fontFamily],
|
|
95
|
+
[style.baseFont.fontFamily],
|
|
96
|
+
);
|
|
97
|
+
|
|
98
|
+
const ignoredStyles = useMemo(
|
|
99
|
+
() => ['fontFamily', 'letterSpacing', 'transform', ...customIgnoredStyles],
|
|
100
|
+
[customIgnoredStyles],
|
|
101
|
+
);
|
|
102
|
+
|
|
103
|
+
const renderersProps = useMemo(
|
|
104
|
+
() => ({
|
|
105
|
+
table: {
|
|
106
|
+
cssRules,
|
|
107
|
+
},
|
|
108
|
+
a: { onPress: onLinkPress },
|
|
109
|
+
iframe: {
|
|
110
|
+
webViewProps: {
|
|
111
|
+
renderToHardwareTextureAndroid: true,
|
|
112
|
+
},
|
|
113
|
+
},
|
|
114
|
+
...customRendererProps,
|
|
115
|
+
}),
|
|
116
|
+
[cssRules, onLinkPress, customRendererProps],
|
|
117
|
+
);
|
|
87
118
|
|
|
88
|
-
|
|
119
|
+
const customHTMLElementModels = useMemo(
|
|
120
|
+
() => ({
|
|
121
|
+
table: tableModel,
|
|
122
|
+
iframe: iframeModel,
|
|
123
|
+
video: videoModel,
|
|
124
|
+
attachment: attachmentModel,
|
|
125
|
+
...customHtmlElementModels,
|
|
126
|
+
}),
|
|
127
|
+
[customHtmlElementModels],
|
|
128
|
+
);
|
|
129
|
+
|
|
130
|
+
const domVisitors = useMemo(
|
|
131
|
+
() => ({
|
|
132
|
+
onElement,
|
|
133
|
+
...customDomVisitors,
|
|
134
|
+
}),
|
|
135
|
+
[customDomVisitors],
|
|
136
|
+
);
|
|
137
|
+
|
|
138
|
+
const filteredTagStyles = useMemo(
|
|
139
|
+
() => _.omitBy(tagStyles, tagStyle => !tagStyle),
|
|
140
|
+
[tagStyles],
|
|
141
|
+
);
|
|
142
|
+
|
|
143
|
+
// We have to be really careful with props passed to SimpleHtml, as they'll cause unnecessary re-renders.
|
|
144
|
+
// Component is meant to be static (in almost all scenarios) - it should only render with initially given props.
|
|
145
|
+
// Notice that props below are memoized to only be calculated on mount, because lots of these props non-primitive
|
|
146
|
+
// (reference types), and they'll cause re-renders.
|
|
147
|
+
const htmlProps = useMemo(
|
|
148
|
+
() => ({
|
|
89
149
|
source: { html: body },
|
|
90
150
|
computeEmbeddedMaxWidth: () => maxWidth,
|
|
91
151
|
contentWidth: maxWidth,
|
|
92
|
-
tagsStyles:
|
|
93
|
-
systemFonts
|
|
152
|
+
tagsStyles: filteredTagStyles,
|
|
153
|
+
systemFonts,
|
|
94
154
|
baseStyle: style.baseFont,
|
|
95
|
-
ignoredStyles
|
|
96
|
-
'fontFamily',
|
|
97
|
-
'letterSpacing',
|
|
98
|
-
'transform',
|
|
99
|
-
...customIgnoredStyles,
|
|
100
|
-
],
|
|
155
|
+
ignoredStyles,
|
|
101
156
|
renderers: customRenderers,
|
|
102
|
-
renderersProps
|
|
103
|
-
|
|
104
|
-
cssRules,
|
|
105
|
-
},
|
|
106
|
-
a: { onPress: this.onLinkPress },
|
|
107
|
-
iframe: {
|
|
108
|
-
webViewProps: {
|
|
109
|
-
renderToHardwareTextureAndroid: true,
|
|
110
|
-
},
|
|
111
|
-
},
|
|
112
|
-
...customRendererProps,
|
|
113
|
-
},
|
|
114
|
-
customHTMLElementModels: {
|
|
115
|
-
table: tableModel,
|
|
116
|
-
iframe: iframeModel,
|
|
117
|
-
video: videoModel,
|
|
118
|
-
...customHtmlElementModels,
|
|
119
|
-
},
|
|
157
|
+
renderersProps,
|
|
158
|
+
customHTMLElementModels,
|
|
120
159
|
WebView,
|
|
121
|
-
domVisitors
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
<Html {...htmlProps} {...otherProps} />
|
|
127
|
-
</View>
|
|
128
|
-
);
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
SimpleHtml.propTypes = {
|
|
133
|
-
style: PropTypes.object.isRequired,
|
|
134
|
-
attachments: PropTypes.array,
|
|
135
|
-
body: PropTypes.string,
|
|
136
|
-
customCustomRenderers: PropTypes.object,
|
|
137
|
-
customDomVisitors: PropTypes.object,
|
|
138
|
-
customHandleLinkPress: PropTypes.func,
|
|
139
|
-
customHtmlElementModels: PropTypes.object,
|
|
140
|
-
customIgnoredStyles: PropTypes.arrayOf(PropTypes.string),
|
|
141
|
-
customRendererProps: PropTypes.object,
|
|
142
|
-
customTagStyles: PropTypes.object,
|
|
143
|
-
domVisitors: PropTypes.object,
|
|
144
|
-
unsupportedVideoFormatMessage: PropTypes.string,
|
|
145
|
-
};
|
|
160
|
+
domVisitors,
|
|
161
|
+
}),
|
|
162
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
163
|
+
[],
|
|
164
|
+
);
|
|
146
165
|
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
unsupportedVideoFormatMessage: undefined,
|
|
153
|
-
domVisitors: { onElement },
|
|
154
|
-
customDomVisitors: {},
|
|
155
|
-
customCustomRenderers: {},
|
|
156
|
-
customHtmlElementModels: {},
|
|
157
|
-
customIgnoredStyles: [],
|
|
158
|
-
customRendererProps: {},
|
|
166
|
+
return (
|
|
167
|
+
<View style={style.container}>
|
|
168
|
+
<Html {...htmlProps} {...otherProps} />
|
|
169
|
+
</View>
|
|
170
|
+
);
|
|
159
171
|
};
|
|
160
172
|
|
|
161
|
-
export default connectStyle('shoutem.ui.SimpleHtml')(SimpleHtml);
|
|
173
|
+
export default React.memo(connectStyle('shoutem.ui.SimpleHtml')(SimpleHtml));
|
|
@@ -5,3 +5,9 @@ export const videoModel = HTMLElementModel.fromCustomModel({
|
|
|
5
5
|
tagName: 'video',
|
|
6
6
|
isOpaque: true,
|
|
7
7
|
});
|
|
8
|
+
|
|
9
|
+
export const attachmentModel = HTMLElementModel.fromCustomModel({
|
|
10
|
+
contentModel: HTMLContentModel.block,
|
|
11
|
+
tagName: 'attachment',
|
|
12
|
+
isOpaque: true,
|
|
13
|
+
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@shoutem/ui",
|
|
3
|
-
"version": "9.0.0
|
|
3
|
+
"version": "9.0.0",
|
|
4
4
|
"description": "Styleable set of components for React Native applications",
|
|
5
5
|
"scripts": {
|
|
6
6
|
"lint": "eslint .",
|
|
@@ -15,9 +15,9 @@
|
|
|
15
15
|
"@native-html/table-plugin": "5.3.1",
|
|
16
16
|
"@openspacelabs/react-native-zoomable-view": "2.0.4",
|
|
17
17
|
"@react-native-community/datetimepicker": "6.2.0",
|
|
18
|
-
"@shoutem/animation": "1.0.0
|
|
18
|
+
"@shoutem/animation": "1.0.0",
|
|
19
19
|
"@shoutem/eslint-config-react": "~1.0.6",
|
|
20
|
-
"@shoutem/theme": "1.0.0
|
|
20
|
+
"@shoutem/theme": "1.0.0",
|
|
21
21
|
"auto-bind": "4.0.0",
|
|
22
22
|
"babel-plugin-transform-decorators-legacy": "1.3.5",
|
|
23
23
|
"buffer": "5.6.0",
|
|
@@ -41,7 +41,7 @@
|
|
|
41
41
|
"react-native-toast-message": "2.1.5",
|
|
42
42
|
"react-native-vimeo-iframe": "^1.2.1",
|
|
43
43
|
"react-native-web-refresh-control": "1.1.2",
|
|
44
|
-
"react-native-webview": "
|
|
44
|
+
"react-native-webview": "13.16.0",
|
|
45
45
|
"react-native-youtube-iframe": "2.2.2",
|
|
46
46
|
"react-native-actions-sheet": "0.9.3",
|
|
47
47
|
"stream": "0.0.2",
|
package/theme.js
CHANGED
|
@@ -2407,8 +2407,11 @@ export default () => {
|
|
|
2407
2407
|
paddingHorizontal: 20,
|
|
2408
2408
|
paddingVertical: 16,
|
|
2409
2409
|
borderBottomWidth: 1,
|
|
2410
|
-
borderColor: 'rgba(130, 130, 130, 0.1)',
|
|
2411
2410
|
alignItems: 'center',
|
|
2411
|
+
backgroundColor: resolveVariable('primaryButtonBackgroundColor'),
|
|
2412
|
+
borderColor: resolveVariable('primaryButtonBorderColor'),
|
|
2413
|
+
borderWidth: StyleSheet.hairlineWidth,
|
|
2414
|
+
borderRadius: 13,
|
|
2412
2415
|
},
|
|
2413
2416
|
text: {
|
|
2414
2417
|
fontSize: 15,
|