@shoutem/ui 6.4.1-rc.0 → 6.4.1
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/components/ListView.js
CHANGED
|
@@ -415,10 +415,31 @@ StyledListView.prototype.scrollTo = function scrollTo(coordinates) {
|
|
|
415
415
|
}
|
|
416
416
|
};
|
|
417
417
|
|
|
418
|
-
StyledListView.prototype.scrollToEnd = function scrollToEnd(
|
|
418
|
+
StyledListView.prototype.scrollToEnd = function scrollToEnd(config) {
|
|
419
419
|
const listView = getRNListViewComponent(this);
|
|
420
420
|
if (listView) {
|
|
421
|
-
listView.scrollToEnd(
|
|
421
|
+
listView.scrollToEnd(config);
|
|
422
|
+
}
|
|
423
|
+
};
|
|
424
|
+
|
|
425
|
+
StyledListView.prototype.scrollToOffset = function scrollToOffset(config) {
|
|
426
|
+
const listView = getRNListViewComponent(this);
|
|
427
|
+
if (listView) {
|
|
428
|
+
listView.scrollToOffset(config);
|
|
429
|
+
}
|
|
430
|
+
};
|
|
431
|
+
|
|
432
|
+
StyledListView.prototype.scrollToIndex = function scrollToIndex(config) {
|
|
433
|
+
const listView = getRNListViewComponent(this);
|
|
434
|
+
if (listView) {
|
|
435
|
+
listView.scrollToIndex(config);
|
|
436
|
+
}
|
|
437
|
+
};
|
|
438
|
+
|
|
439
|
+
StyledListView.prototype.scrollToItem = function scrollToItem(config) {
|
|
440
|
+
const listView = getRNListViewComponent(this);
|
|
441
|
+
if (listView) {
|
|
442
|
+
listView.scrollToItem(config);
|
|
422
443
|
}
|
|
423
444
|
};
|
|
424
445
|
|
|
@@ -13,8 +13,11 @@ import autoBindReact from 'auto-bind/react';
|
|
|
13
13
|
import _ from 'lodash';
|
|
14
14
|
import PropTypes from 'prop-types';
|
|
15
15
|
import { connectStyle } from '@shoutem/theme';
|
|
16
|
+
import VideoRenderer from '@shoutem/ui/html/components/VideoRenderer';
|
|
17
|
+
import { videoModel } from '@shoutem/ui/html/services/HTMLElementModels';
|
|
16
18
|
import { View } from '../../components/View';
|
|
17
19
|
import { resolveMaxWidth } from '../services/Dimensions';
|
|
20
|
+
import { onElement } from '../services/DomVisitors';
|
|
18
21
|
import AttachmentRenderer from './AttachmentRenderer';
|
|
19
22
|
import IframeRenderer from './IframeRenderer';
|
|
20
23
|
|
|
@@ -37,9 +40,14 @@ class SimpleHtml extends PureComponent {
|
|
|
37
40
|
const {
|
|
38
41
|
style,
|
|
39
42
|
body,
|
|
40
|
-
customTagStyles,
|
|
41
43
|
unsupportedVideoFormatMessage,
|
|
42
44
|
attachments,
|
|
45
|
+
customDomVisitors,
|
|
46
|
+
customCustomRenderers,
|
|
47
|
+
customTagStyles,
|
|
48
|
+
customHtmlElementModels,
|
|
49
|
+
customIgnoredStyles,
|
|
50
|
+
customRendererProps,
|
|
43
51
|
...otherProps
|
|
44
52
|
} = this.props;
|
|
45
53
|
|
|
@@ -74,6 +82,8 @@ class SimpleHtml extends PureComponent {
|
|
|
74
82
|
style={style}
|
|
75
83
|
/>
|
|
76
84
|
),
|
|
85
|
+
video: VideoRenderer,
|
|
86
|
+
...customCustomRenderers,
|
|
77
87
|
};
|
|
78
88
|
|
|
79
89
|
const htmlProps = {
|
|
@@ -83,7 +93,12 @@ class SimpleHtml extends PureComponent {
|
|
|
83
93
|
tagsStyles: _.omitBy(tagStyles, tagStyle => !tagStyle),
|
|
84
94
|
systemFonts: [...defaultSystemFonts, style.baseFont.fontFamily],
|
|
85
95
|
baseStyle: style.baseFont,
|
|
86
|
-
ignoredStyles: [
|
|
96
|
+
ignoredStyles: [
|
|
97
|
+
'fontFamily',
|
|
98
|
+
'letterSpacing',
|
|
99
|
+
'transform',
|
|
100
|
+
...customIgnoredStyles,
|
|
101
|
+
],
|
|
87
102
|
renderers: customRenderers,
|
|
88
103
|
renderersProps: {
|
|
89
104
|
table: {
|
|
@@ -95,13 +110,17 @@ class SimpleHtml extends PureComponent {
|
|
|
95
110
|
renderToHardwareTextureAndroid: true,
|
|
96
111
|
},
|
|
97
112
|
},
|
|
113
|
+
...customRendererProps,
|
|
98
114
|
},
|
|
99
115
|
customHTMLElementModels: {
|
|
100
116
|
table: tableModel,
|
|
101
117
|
iframe: iframeModel,
|
|
118
|
+
video: videoModel,
|
|
119
|
+
...customHtmlElementModels,
|
|
102
120
|
},
|
|
103
121
|
WebView,
|
|
104
122
|
ignoredTags: IGNORED_TAGS,
|
|
123
|
+
domVisitors: { onElement, ...customDomVisitors },
|
|
105
124
|
};
|
|
106
125
|
|
|
107
126
|
return (
|
|
@@ -116,19 +135,29 @@ SimpleHtml.propTypes = {
|
|
|
116
135
|
style: PropTypes.object.isRequired,
|
|
117
136
|
attachments: PropTypes.array,
|
|
118
137
|
body: PropTypes.string,
|
|
119
|
-
|
|
138
|
+
customCustomRenderers: PropTypes.object,
|
|
139
|
+
customDomVisitors: PropTypes.object,
|
|
120
140
|
customHandleLinkPress: PropTypes.func,
|
|
141
|
+
customHtmlElementModels: PropTypes.object,
|
|
142
|
+
customIgnoredStyles: PropTypes.arrayOf(PropTypes.string),
|
|
143
|
+
customRendererProps: PropTypes.object,
|
|
121
144
|
customTagStyles: PropTypes.object,
|
|
145
|
+
domVisitors: PropTypes.object,
|
|
122
146
|
unsupportedVideoFormatMessage: PropTypes.string,
|
|
123
147
|
};
|
|
124
148
|
|
|
125
149
|
SimpleHtml.defaultProps = {
|
|
126
150
|
attachments: undefined,
|
|
127
151
|
body: undefined,
|
|
128
|
-
customAlterNode: undefined,
|
|
129
152
|
customHandleLinkPress: undefined,
|
|
130
|
-
customTagStyles:
|
|
153
|
+
customTagStyles: {},
|
|
131
154
|
unsupportedVideoFormatMessage: undefined,
|
|
155
|
+
domVisitors: { onElement },
|
|
156
|
+
customDomVisitors: {},
|
|
157
|
+
customCustomRenderers: {},
|
|
158
|
+
customHtmlElementModels: {},
|
|
159
|
+
customIgnoredStyles: [],
|
|
160
|
+
customRendererProps: {},
|
|
132
161
|
};
|
|
133
162
|
|
|
134
163
|
export default connectStyle('shoutem.ui.SimpleHtml')(SimpleHtml);
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import {
|
|
3
|
+
useComputeMaxWidthForTag,
|
|
4
|
+
useContentWidth,
|
|
5
|
+
} from 'react-native-render-html';
|
|
6
|
+
import WebView from 'react-native-webview';
|
|
7
|
+
import { findOne } from 'domutils';
|
|
8
|
+
import PropTypes from 'prop-types';
|
|
9
|
+
|
|
10
|
+
function findSource(tnode) {
|
|
11
|
+
if (tnode.attributes.src) {
|
|
12
|
+
return tnode.attributes.src;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const sourceElms = findOne(
|
|
16
|
+
elm => elm.tagName === 'source',
|
|
17
|
+
tnode.domNode.children,
|
|
18
|
+
);
|
|
19
|
+
|
|
20
|
+
return sourceElms ? sourceElms.attribs.src : '';
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
*
|
|
25
|
+
* Generate video html with only selected attributes and parameters.
|
|
26
|
+
* @returns HTML video element
|
|
27
|
+
*/
|
|
28
|
+
function generateVideoHtml(tnode) {
|
|
29
|
+
const url = findSource(tnode);
|
|
30
|
+
|
|
31
|
+
const containsTimeParameter = url.match(/(#|\?)t=[^&$]*/g, '');
|
|
32
|
+
// If video has no time parameter, we want to add time parameter at 0.1, just to be able to
|
|
33
|
+
// display the image, first frame of the video. Video tags that don't have autoplay attribute
|
|
34
|
+
// are not showing first frame without this solution.
|
|
35
|
+
// Otherwise, if user has defined time parameter, keep it as is.
|
|
36
|
+
const resolvedUrl = `${url}${containsTimeParameter ? '' : '#t=0.1'}`;
|
|
37
|
+
|
|
38
|
+
return `<video controls src="${resolvedUrl}" style="width: 100%; height: 100%;"/>`;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export const VideoRenderer = ({ tnode, style }) => {
|
|
42
|
+
const computeMaxWidth = useComputeMaxWidthForTag('video');
|
|
43
|
+
const width = computeMaxWidth(useContentWidth());
|
|
44
|
+
|
|
45
|
+
// Using uri as a Webview source is not an option, because we don't have as much control of video.
|
|
46
|
+
// E.g. if video from uri has autoplay=true, we can't prevent it from auto-playing when Webview renders.
|
|
47
|
+
// Instead, create video HTML element with only picked attributes and parameters.
|
|
48
|
+
const html = generateVideoHtml(tnode);
|
|
49
|
+
|
|
50
|
+
return (
|
|
51
|
+
<WebView
|
|
52
|
+
scrollEnabled={false}
|
|
53
|
+
source={{
|
|
54
|
+
html,
|
|
55
|
+
}}
|
|
56
|
+
style={[{ aspectRatio: 16 / 9 }, style, { width }]}
|
|
57
|
+
/>
|
|
58
|
+
);
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
VideoRenderer.propTypes = {
|
|
62
|
+
tnode: PropTypes.object.isRequired,
|
|
63
|
+
style: PropTypes.object,
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
VideoRenderer.defaultProps = {
|
|
67
|
+
style: {},
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
export default VideoRenderer;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import _ from 'lodash';
|
|
2
|
+
|
|
3
|
+
// Shoutem RTE component wraps any video attachment (hosted inside iframe or video) with <figure>
|
|
4
|
+
// element. That element contains style that breaks the SimpleHtml UI - enormous blank
|
|
5
|
+
// space below attachment.
|
|
6
|
+
// This function removes height and padding-bottom styles from figure to fix this UI issue.
|
|
7
|
+
export const removeShoutemRteVideoAttachmentWrapper = element => {
|
|
8
|
+
if (
|
|
9
|
+
(element.name === 'figure' || element.name === 'video') &&
|
|
10
|
+
element.children.length > 0 &&
|
|
11
|
+
// <figure> can have multiple generated children too - type:text. Probably caused by empty space between
|
|
12
|
+
// figure and iframe elements in generated HTML.
|
|
13
|
+
_.some(
|
|
14
|
+
element.children,
|
|
15
|
+
({ name }) => name === 'iframe' || name === 'video',
|
|
16
|
+
)
|
|
17
|
+
) {
|
|
18
|
+
// Remove height and padding-bottom styles because they break the UI
|
|
19
|
+
element.attribs.style = element.attribs.style
|
|
20
|
+
? element.attribs.style
|
|
21
|
+
.replace(/height:[^;]+;?/, '')
|
|
22
|
+
.replace(/padding-bottom:[^;]+;?/, '')
|
|
23
|
+
: '';
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
return element;
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Collection of node (HTML element) alteration callbacks. Define any html element
|
|
31
|
+
* manipulations inside.
|
|
32
|
+
* @param {*} element - HTML element to check against and manipulate if necessary
|
|
33
|
+
* @returns Manipulated HTML element
|
|
34
|
+
*/
|
|
35
|
+
export const onElement = element => {
|
|
36
|
+
return removeShoutemRteVideoAttachmentWrapper(element);
|
|
37
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@shoutem/ui",
|
|
3
|
-
"version": "6.4.1
|
|
3
|
+
"version": "6.4.1",
|
|
4
4
|
"description": "Styleable set of components for React Native applications",
|
|
5
5
|
"scripts": {
|
|
6
6
|
"lint": "eslint .",
|
|
@@ -11,13 +11,13 @@
|
|
|
11
11
|
"test": "jest"
|
|
12
12
|
},
|
|
13
13
|
"dependencies": {
|
|
14
|
+
"@native-html/iframe-plugin": "2.6.1",
|
|
15
|
+
"@native-html/table-plugin": "5.3.1",
|
|
14
16
|
"@openspacelabs/react-native-zoomable-view": "2.0.4",
|
|
15
17
|
"@react-native-community/datetimepicker": "6.2.0",
|
|
16
18
|
"@shoutem/animation": "~0.15.0",
|
|
17
19
|
"@shoutem/eslint-config-react": "~1.0.6",
|
|
18
20
|
"@shoutem/theme": "~0.13.0",
|
|
19
|
-
"@native-html/iframe-plugin": "2.6.1",
|
|
20
|
-
"@native-html/table-plugin": "5.3.1",
|
|
21
21
|
"auto-bind": "4.0.0",
|
|
22
22
|
"babel-plugin-transform-decorators-legacy": "1.3.5",
|
|
23
23
|
"buffer": "5.6.0",
|
|
@@ -39,9 +39,9 @@
|
|
|
39
39
|
"react-native-svg": "12.3.0",
|
|
40
40
|
"react-native-svg-transformer": "1.0.0",
|
|
41
41
|
"react-native-toast-message": "2.1.5",
|
|
42
|
+
"react-native-vimeo-iframe": "^1.2.1",
|
|
42
43
|
"react-native-webview": "11.23.0",
|
|
43
44
|
"react-native-youtube-iframe": "2.2.2",
|
|
44
|
-
"react-native-vimeo-iframe": "1.2.1",
|
|
45
45
|
"stream": "0.0.2",
|
|
46
46
|
"tinycolor2": "1.4.1"
|
|
47
47
|
},
|
|
@@ -51,8 +51,8 @@
|
|
|
51
51
|
},
|
|
52
52
|
"devDependencies": {
|
|
53
53
|
"@babel/core": "7.16.7",
|
|
54
|
-
"@babel/register": "7.9.0",
|
|
55
54
|
"@babel/plugin-proposal-decorators": "7.16.7",
|
|
55
|
+
"@babel/register": "7.9.0",
|
|
56
56
|
"@shoutem/eslint-config-react-native": "~1.0.2",
|
|
57
57
|
"babel-eslint": "10.1.0",
|
|
58
58
|
"eslint": "6.8.0",
|
package/theme.js
CHANGED
|
@@ -2286,7 +2286,7 @@ export default () => {
|
|
|
2286
2286
|
},
|
|
2287
2287
|
// Just enough to display portrait mode Vimeo videos and all Vimeo player controls
|
|
2288
2288
|
vimeoVideo: {
|
|
2289
|
-
height:
|
|
2289
|
+
height: responsiveHeight(220),
|
|
2290
2290
|
},
|
|
2291
2291
|
fallback: {
|
|
2292
2292
|
width: window.width,
|