@arcblock/ux 0.78.25 → 1.6.59

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 (172) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +0 -56
  3. package/lib/ActionButton/index.js +6 -4
  4. package/lib/ActivityIndicator/index.js +75 -23
  5. package/lib/Alert/index.js +15 -11
  6. package/lib/Async/index.js +1 -1
  7. package/lib/Badge/index.js +17 -15
  8. package/lib/Blocklet/index.js +261 -0
  9. package/lib/Button/wrap.js +96 -43
  10. package/lib/ButtonGroup/index.js +3 -16
  11. package/lib/Center/index.js +30 -4
  12. package/lib/ClickToCopy/index.js +10 -8
  13. package/lib/CodeBlock/index.js +40 -13
  14. package/lib/Colors/index.js +15 -0
  15. package/lib/Colors/themes/default.js +85 -0
  16. package/lib/ContactForm/index.js +9 -10
  17. package/lib/CookieConsent/index.js +98 -0
  18. package/lib/CountDown/index.js +18 -14
  19. package/lib/Dialog/confirm.js +84 -0
  20. package/lib/Dialog/dialog.js +137 -0
  21. package/lib/Dialog/index.js +23 -0
  22. package/lib/Earth/index.js +33 -33
  23. package/lib/Empty/index.js +61 -0
  24. package/lib/Footer/index.js +16 -18
  25. package/lib/Icon/image.js +10 -13
  26. package/lib/Icon/index.js +10 -8
  27. package/lib/Img/index.js +212 -0
  28. package/lib/InfoRow/index.js +7 -6
  29. package/lib/Layout/dashboard/header.js +60 -42
  30. package/lib/Layout/dashboard/index.js +72 -60
  31. package/lib/Layout/dashboard/sidebar.js +41 -25
  32. package/lib/Layout/index.js +113 -51
  33. package/lib/Locale/browser-lang.js +0 -2
  34. package/lib/Locale/context.js +85 -61
  35. package/lib/Locale/selector.js +33 -20
  36. package/lib/Logo/index.js +15 -13
  37. package/lib/Metric/index.js +5 -6
  38. package/lib/NFTDisplay/README.md +59 -0
  39. package/lib/NFTDisplay/aspect-ratio-container.js +52 -0
  40. package/lib/NFTDisplay/broken.js +25 -0
  41. package/lib/NFTDisplay/index.js +317 -0
  42. package/lib/NFTDisplay/loading.js +23 -0
  43. package/lib/NFTDisplay/svg-embedder/img.js +68 -0
  44. package/lib/NFTDisplay/svg-embedder/inline-svg.js +54 -0
  45. package/lib/PageScroller/index.js +10 -11
  46. package/lib/PageScroller/usePrevValue.js +2 -2
  47. package/lib/PricingTable/PricingPlan.js +12 -15
  48. package/lib/PricingTable/index.js +5 -5
  49. package/lib/Result/common.js +176 -0
  50. package/lib/Result/index.js +61 -0
  51. package/lib/Result/result.js +69 -0
  52. package/lib/Result/translations.js +61 -0
  53. package/lib/Screenshot/index.js +14 -13
  54. package/lib/Spinner/index.js +37 -0
  55. package/lib/SplitButton/index.js +126 -0
  56. package/lib/Switch/index.js +107 -0
  57. package/lib/Tabs/index.js +24 -47
  58. package/lib/Tag/index.js +15 -13
  59. package/lib/Terminal/Player.js +43 -45
  60. package/lib/Terminal/index.js +3 -1
  61. package/lib/Terminal/util.js +2 -3
  62. package/lib/TextCollapse/index.js +21 -14
  63. package/lib/Theme/index.js +79 -63
  64. package/lib/Theme/responsiveFontSizes.js +8 -8
  65. package/lib/Toast/index.js +12 -11
  66. package/lib/Util/index.js +197 -26
  67. package/lib/Video/index.js +8 -11
  68. package/lib/Wallet/Action.js +15 -13
  69. package/lib/Wallet/Download.js +60 -58
  70. package/lib/Wallet/Open.js +2 -2
  71. package/lib/WechatPrompt/index.js +10 -10
  72. package/lib/index.js +6 -6
  73. package/lib/withTheme/index.js +5 -17
  74. package/lib/withTracker/error_boundary.js +3 -3
  75. package/lib/withTracker/index.js +6 -7
  76. package/package.json +22 -17
  77. package/src/ActionButton/index.js +65 -0
  78. package/src/ActivityIndicator/index.js +141 -0
  79. package/src/Alert/index.js +104 -0
  80. package/src/Async/index.js +39 -0
  81. package/src/Badge/index.js +71 -0
  82. package/src/Blocklet/index.js +424 -0
  83. package/src/Button/index.js +4 -0
  84. package/src/Button/wrap.js +101 -0
  85. package/src/ButtonGroup/index.js +6 -0
  86. package/src/Center/index.js +40 -0
  87. package/src/ClickToCopy/index.js +90 -0
  88. package/src/CodeBlock/index.js +160 -0
  89. package/src/Colors/index.js +1 -0
  90. package/src/Colors/themes/default.js +54 -0
  91. package/src/ContactForm/index.js +240 -0
  92. package/src/CookieConsent/index.js +90 -0
  93. package/src/CountDown/index.js +151 -0
  94. package/src/Dialog/confirm.js +76 -0
  95. package/src/Dialog/dialog.js +162 -0
  96. package/src/Dialog/index.js +2 -0
  97. package/src/DriftBot/index.js +81 -0
  98. package/src/Earth/countries.json +8057 -0
  99. package/src/Earth/index.js +511 -0
  100. package/src/Earth/util.js +69 -0
  101. package/src/Empty/index.js +41 -0
  102. package/src/Footer/index.js +110 -0
  103. package/src/Icon/image.js +55 -0
  104. package/src/Icon/index.js +69 -0
  105. package/src/Img/index.js +172 -0
  106. package/src/InfoRow/index.js +83 -0
  107. package/src/Layout/dashboard/header.js +157 -0
  108. package/src/Layout/dashboard/index.js +150 -0
  109. package/src/Layout/dashboard/sidebar.js +122 -0
  110. package/src/Layout/index.js +318 -0
  111. package/src/Locale/browser-lang.js +63 -0
  112. package/src/Locale/context.js +94 -0
  113. package/src/Locale/images/globe-dark.png +0 -0
  114. package/src/Locale/images/globe-light.png +0 -0
  115. package/src/Locale/selector.js +135 -0
  116. package/src/Logo/images/logo-dark-text.svg +3 -0
  117. package/src/Logo/images/logo-dark-top.svg +6 -0
  118. package/src/Logo/images/logo-light-text.svg +3 -0
  119. package/src/Logo/images/logo-light-top.svg +6 -0
  120. package/src/Logo/index.js +47 -0
  121. package/src/Metric/index.js +115 -0
  122. package/src/NFTDisplay/README.md +59 -0
  123. package/src/NFTDisplay/aspect-ratio-container.js +34 -0
  124. package/src/NFTDisplay/broken.js +18 -0
  125. package/src/NFTDisplay/index.js +257 -0
  126. package/src/NFTDisplay/loading.js +17 -0
  127. package/src/NFTDisplay/svg-embedder/img.js +36 -0
  128. package/src/NFTDisplay/svg-embedder/inline-svg.js +37 -0
  129. package/src/PageScroller/index.js +342 -0
  130. package/src/PageScroller/usePrevValue.js +12 -0
  131. package/src/PricingTable/PricingPlan.js +112 -0
  132. package/src/PricingTable/index.js +43 -0
  133. package/src/Result/common.js +116 -0
  134. package/src/Result/index.js +31 -0
  135. package/src/Result/result.js +57 -0
  136. package/src/Result/translations.js +56 -0
  137. package/src/Screenshot/devices.css +1366 -0
  138. package/src/Screenshot/index.js +181 -0
  139. package/src/Spinner/index.js +19 -0
  140. package/src/SplitButton/index.js +112 -0
  141. package/src/Switch/index.js +78 -0
  142. package/src/Tabs/index.js +46 -0
  143. package/src/Tag/index.js +73 -0
  144. package/src/Terminal/Player.js +364 -0
  145. package/src/Terminal/index.js +150 -0
  146. package/src/Terminal/player.css +378 -0
  147. package/src/Terminal/util.js +167 -0
  148. package/src/Terminal/xterm.css +171 -0
  149. package/src/TextCollapse/index.js +92 -0
  150. package/src/Theme/index.js +184 -0
  151. package/src/Theme/responsiveFontSizes.js +94 -0
  152. package/src/Toast/index.js +118 -0
  153. package/src/Util/index.js +281 -0
  154. package/src/Video/index.js +72 -0
  155. package/src/Wallet/Action.js +105 -0
  156. package/src/Wallet/Download.js +130 -0
  157. package/src/Wallet/Open.js +50 -0
  158. package/src/Wallet/images/abtwallet.png +0 -0
  159. package/src/Wallet/images/android_download.svg +23 -0
  160. package/src/Wallet/images/app-store.svg +20 -0
  161. package/src/Wallet/images/google-play.svg +70 -0
  162. package/src/WechatPrompt/images/android.png +0 -0
  163. package/src/WechatPrompt/images/ios.png +0 -0
  164. package/src/WechatPrompt/index.js +81 -0
  165. package/src/index.js +63 -0
  166. package/src/withTheme/index.js +72 -0
  167. package/src/withTracker/README.md +34 -0
  168. package/src/withTracker/error_boundary.js +34 -0
  169. package/src/withTracker/index.js +70 -0
  170. package/lib/GraphQLPlayground/graphiql.css +0 -1850
  171. package/lib/GraphQLPlayground/index.js +0 -302
  172. package/lib/GraphQLPlayground/util.js +0 -55
@@ -0,0 +1,257 @@
1
+ import React, { useState } from 'react';
2
+ import PropTypes from 'prop-types';
3
+ import styled from 'styled-components';
4
+ import clsx from 'clsx';
5
+ import get from 'lodash/get';
6
+ import pako from 'pako';
7
+ import base64 from 'base64-url';
8
+ import isSvg from 'is-svg';
9
+ import AspectRatioContainer from './aspect-ratio-container';
10
+ import ImgSvgEmbedder from './svg-embedder/img';
11
+ import InlineSvgEmbedder from './svg-embedder/inline-svg';
12
+ import DefaultLoading from './loading';
13
+ import DefaultBrokenImage from './broken';
14
+
15
+ /**
16
+ * 从 assetState 中获取 nft data, 兼容新旧两种类型的数据结构, 建议将该方法的返回值传入 NFTDisplay 组件的 data prop
17
+ * - 旧: assetState.data.value (.credentialSubject.display)
18
+ * - 新: assetState.display
19
+ */
20
+ export function getNFTData(assetState) {
21
+ return assetState?.display || assetState?.data?.value;
22
+ }
23
+
24
+ function fromBase64(v) {
25
+ if (typeof v !== 'string') {
26
+ throw new Error('fromBase64 requires input to be a string');
27
+ }
28
+ return Buffer.from(base64.unescape(v), 'base64');
29
+ }
30
+
31
+ // const isVC = type => {
32
+ // return String(type).includes('VerifiableCredential');
33
+ // };
34
+
35
+ // 仅针对非 url type 的情况
36
+ const getSvgEmbedder = preferredSvgEmbedder => {
37
+ const embedders = {
38
+ img: ImgSvgEmbedder,
39
+ svg: InlineSvgEmbedder,
40
+ };
41
+ return embedders[preferredSvgEmbedder];
42
+ };
43
+
44
+ /**
45
+ * TODO:
46
+ * 考虑把 asset data 解析部分和 nft display 分离, android 端有相关使用场景 - 只传入 svg 或 url, 也可以传入 asset data,
47
+ * 目前如果想直接传入 svg 或 url, 需要构造一个 asset data 的数据才能使用 NFTDisplay 组件
48
+ */
49
+ function NFTDisplay({
50
+ data,
51
+ address,
52
+ inset,
53
+ aspect,
54
+ component,
55
+ className,
56
+ renderError,
57
+ renderLoading,
58
+ preferredSvgEmbedder,
59
+ checkSvg,
60
+ minimumLoadingTime,
61
+ onCompleted,
62
+ ...rest
63
+ }) {
64
+ const wrapRoot = children => (
65
+ <Root as={component} {...rest} className={clsx(className, { 'nft-display--inset': inset })}>
66
+ {children}
67
+ </Root>
68
+ );
69
+
70
+ try {
71
+ const parsed = React.useRef(data);
72
+ // 如果是 raw data 先解析
73
+ if (typeof parsed.current === 'string') {
74
+ parsed.current = JSON.parse(data);
75
+ // console.log('[debug] parse data')
76
+ }
77
+ const { vcId } = parsed.current;
78
+ // 需要兼容新旧两种类型的数据结构, nft data 有 credentialSubject 属性, 说明是旧 nft data, 否则是新 nft data
79
+ const display = parsed.current.credentialSubject
80
+ ? get(parsed.current, 'credentialSubject.display')
81
+ : parsed.current;
82
+ const { content, type } = display;
83
+ const isUrlType = type === 'url';
84
+
85
+ // 首次加载, 对于 url type 的情况, loading 为 true
86
+ const [state, setState] = useState({ loading: isUrlType, error: false });
87
+ const [minimumLoadingReady, setMinimumLoadingReady] = useState(minimumLoadingTime <= 0);
88
+ // console.log('[debug] render', {type, minimumLoadingTime}, JSON.stringify(state))
89
+
90
+ React.useEffect(() => {
91
+ let timer;
92
+ if (minimumLoadingTime > 0) {
93
+ timer = setTimeout(() => setMinimumLoadingReady(true), minimumLoadingTime);
94
+ }
95
+ return () => clearTimeout(timer);
96
+ }, []);
97
+
98
+ // onCompleted
99
+ React.useEffect(() => {
100
+ if ((!state.loading && minimumLoadingReady) || state.error) {
101
+ onCompleted();
102
+ }
103
+ }, [state, minimumLoadingReady]);
104
+
105
+ if (state.error) {
106
+ throw new Error('Failed to render NFT Display.');
107
+ }
108
+
109
+ const renderNFT = () => {
110
+ if (content) {
111
+ switch (type) {
112
+ case 'url': {
113
+ const urlObj = new URL(content);
114
+ if (!urlObj.searchParams.has('assetId')) {
115
+ urlObj.searchParams.append('assetId', address);
116
+ }
117
+
118
+ if (!urlObj.searchParams.has('vcId')) {
119
+ urlObj.searchParams.append('vcId', vcId);
120
+ }
121
+
122
+ const url = urlObj.href;
123
+ return (
124
+ <img
125
+ src={url}
126
+ onError={() => setState({ ...state, error: true })}
127
+ onLoad={() => setState({ ...state, loading: false })}
128
+ alt="NFT Display"
129
+ />
130
+ );
131
+ }
132
+ case 'uri': {
133
+ return (
134
+ <img
135
+ src={content}
136
+ onError={() => setState({ ...state, error: true })}
137
+ onLoad={() => setState({ ...state, loading: false })}
138
+ alt="NFT Display"
139
+ />
140
+ );
141
+ }
142
+ case 'svg_gzipped': {
143
+ const buffer = pako.ungzip(fromBase64(content), {});
144
+ const svg = Buffer.from(buffer).toString('utf8');
145
+ if (checkSvg && !isSvg(svg)) {
146
+ throw new Error(`Invalid SVG: ${svg}`);
147
+ }
148
+ const Embedder = getSvgEmbedder(preferredSvgEmbedder);
149
+ return <Embedder svg={svg} />;
150
+ }
151
+ case 'svg': {
152
+ if (checkSvg && !isSvg(content)) {
153
+ throw new Error(`Invalid SVG: ${content}`);
154
+ }
155
+ const Embedder = getSvgEmbedder(preferredSvgEmbedder);
156
+ return <Embedder svg={content} />;
157
+ }
158
+ // TODO: 准备测试数据
159
+ case 'html': {
160
+ break;
161
+ }
162
+ default:
163
+ }
164
+ }
165
+ throw new Error(`unsupported display protocol: ${display.type}`);
166
+ };
167
+
168
+ return wrapRoot(
169
+ <>
170
+ {(state.loading || !minimumLoadingReady) &&
171
+ (renderLoading ? renderLoading() : <DefaultLoading />)}
172
+ {renderNFT()}
173
+ </>
174
+ );
175
+ } catch (e) {
176
+ console.error(e);
177
+ return wrapRoot(renderError ? renderError() : <DefaultBrokenImage />);
178
+ }
179
+ }
180
+
181
+ NFTDisplay.propTypes = {
182
+ // asset data 可以是 raw data 和 parsed data
183
+ data: PropTypes.oneOfType([PropTypes.object, PropTypes.string]).isRequired,
184
+ address: PropTypes.string.isRequired,
185
+ component: PropTypes.string,
186
+ inset: PropTypes.bool,
187
+ aspect: PropTypes.number,
188
+ className: PropTypes.string,
189
+ renderError: PropTypes.func,
190
+ renderLoading: PropTypes.func,
191
+ // 对于非 url type 的情况, 支持优先选用的 svg 嵌入方式, 默认是 img
192
+ preferredSvgEmbedder: PropTypes.oneOf(['img', 'svg']),
193
+ // 针对非 url type 的情况, 检测 svg 有效性, 默认禁用
194
+ checkSvg: PropTypes.bool,
195
+ // loading 最小显示时间 (避免闪烁)
196
+ minimumLoadingTime: PropTypes.number,
197
+ // 完成回调, 无论加载成功|失败
198
+ onCompleted: PropTypes.func,
199
+ };
200
+
201
+ NFTDisplay.defaultProps = {
202
+ component: 'span',
203
+ inset: false,
204
+ aspect: 0,
205
+ className: '',
206
+ renderError: null,
207
+ renderLoading: null,
208
+ preferredSvgEmbedder: 'img',
209
+ checkSvg: false,
210
+ minimumLoadingTime: 0,
211
+ onCompleted: () => {},
212
+ };
213
+
214
+ const Root = styled.div`
215
+ display: flex;
216
+ justify-content: center;
217
+ align-items: center;
218
+ position: relative;
219
+ /* 默认尺寸 */
220
+ width: 150px;
221
+ height: 150px;
222
+ overflow: hidden;
223
+
224
+ &,
225
+ img {
226
+ max-width: 100%;
227
+ max-height: 100%;
228
+ }
229
+
230
+ img {
231
+ width: 100%;
232
+ height: 100%;
233
+ }
234
+
235
+ &.nft-display--inset {
236
+ width: 100%;
237
+ height: 100%;
238
+ }
239
+ `;
240
+
241
+ function withAspectRatio(Component) {
242
+ // eslint-disable-next-line func-names, react/prop-types
243
+ return function ({ aspect, inset, ...rest }) {
244
+ // inset 比 aspect ratio 优先级高, 如果同时设置了 inset 和 aspect, 则后者不生效
245
+ const applyAspectRatio = aspect > 0 && !inset;
246
+ if (applyAspectRatio) {
247
+ return (
248
+ <AspectRatioContainer aspect={aspect}>
249
+ <Component inset {...rest} />
250
+ </AspectRatioContainer>
251
+ );
252
+ }
253
+ return <Component inset={inset} {...rest} />;
254
+ };
255
+ }
256
+
257
+ export default withAspectRatio(NFTDisplay);
@@ -0,0 +1,17 @@
1
+ import React from 'react';
2
+ import styled from 'styled-components';
3
+
4
+ export default function Loading() {
5
+ return <Root className="nft-display__loading">loading...</Root>;
6
+ }
7
+
8
+ const Root = styled.span`
9
+ display: flex;
10
+ justify-content: center;
11
+ align-items: center;
12
+ position: absolute;
13
+ width: 100%;
14
+ height: 100%;
15
+ color: #ccc;
16
+ background-color: #eee;
17
+ `;
@@ -0,0 +1,36 @@
1
+ import React from 'react';
2
+ import PropTypes from 'prop-types';
3
+ import InlineSvgEmbedder from './inline-svg';
4
+
5
+ const svgToImgUrl = svg => {
6
+ // fix: #225, https://stackoverflow.com/a/52135328)
7
+ const blob = new Blob([svg], { type: 'image/svg+xml' });
8
+ return URL.createObjectURL(blob);
9
+ };
10
+
11
+ /**
12
+ * 基于 <img> 嵌入 svg
13
+ */
14
+ function ImgEmbedder({ svg, alt, fallback, ...rest }) {
15
+ // 包含 foreignObject 的 svg, fallback 到 shadow dom
16
+ if (fallback && svg.indexOf('</foreignObject>') > -1) {
17
+ return <InlineSvgEmbedder svg={svg} />;
18
+ }
19
+
20
+ const url = svgToImgUrl(svg);
21
+ return <img src={url} onLoad={() => URL.revokeObjectURL(url)} alt={alt} {...rest} />;
22
+ }
23
+
24
+ ImgEmbedder.propTypes = {
25
+ svg: PropTypes.string.isRequired,
26
+ alt: PropTypes.string,
27
+ // 对于包含 foreignObject 的 svg, fallback 到 inline svg + shadow DOM
28
+ fallback: PropTypes.bool,
29
+ };
30
+
31
+ ImgEmbedder.defaultProps = {
32
+ alt: '',
33
+ fallback: true,
34
+ };
35
+
36
+ export default ImgEmbedder;
@@ -0,0 +1,37 @@
1
+ import React from 'react';
2
+ import PropTypes from 'prop-types';
3
+ import styled from 'styled-components';
4
+ import root from 'react-shadow/styled-components';
5
+
6
+ /**
7
+ * inline svg 的方式嵌入 svg, 使用 shadow DOM 避免样式污染
8
+ */
9
+ function InlineSvg({ svg, ...rest }) {
10
+ return (
11
+ <Root {...rest}>
12
+ <Inner dangerouslySetInnerHTML={{ __html: svg }} />
13
+ </Root>
14
+ );
15
+ }
16
+
17
+ InlineSvg.propTypes = {
18
+ svg: PropTypes.string.isRequired,
19
+ };
20
+
21
+ const Root = styled(root.span)`
22
+ display: block;
23
+ width: 100%;
24
+ height: 100%;
25
+ `;
26
+
27
+ const Inner = styled.div`
28
+ &,
29
+ & > svg {
30
+ height: 100%;
31
+ width: 100%;
32
+ min-width: 100%;
33
+ max-width: 100%;
34
+ }
35
+ `;
36
+
37
+ export default InlineSvg;
@@ -0,0 +1,342 @@
1
+ /* eslint-disable jsx-a11y/no-noninteractive-tabindex */
2
+ import React, { useCallback, useEffect, useRef, useState } from 'react';
3
+ import PropTypes from 'prop-types';
4
+ import isEqual from 'lodash/isEqual';
5
+ import isNil from 'lodash/isNil';
6
+ import isNull from 'lodash/isNull';
7
+
8
+ import usePrevious from './usePrevValue';
9
+
10
+ const Events = {
11
+ TOUCHMOVE: 'touchmove',
12
+ KEYDOWN: 'keydown',
13
+ };
14
+
15
+ const DEFAULT_ANIMATION_TIMER = 500;
16
+ const DEFAULT_ANIMATION = 'ease-in-out';
17
+ const DEFAULT_CONTAINER_HEIGHT = '100vh';
18
+ const DEFAULT_CONTAINER_WIDTH = '100vw';
19
+ const DEFAULT_COMPONENT_INDEX = 0;
20
+ const DEFAULT_COMPONENTS_TO_RENDER_LENGTH = 0;
21
+
22
+ const ANIMATION_TIMER_BUFFER = 100;
23
+ const KEY_UP = 38;
24
+ const KEY_DOWN = 40;
25
+ const DISABLED_CLASS_NAME = 'rps-scroll--disabled';
26
+
27
+ let previousTouchMove = null;
28
+ let isScrolling = false;
29
+ let isMounted = false;
30
+ let isBodyScrollEnabled = true;
31
+ let isTransitionAfterComponentsToRenderChanged = false;
32
+ const containers = [];
33
+
34
+ const PageScroller = ({
35
+ animationTimer,
36
+ blockScrollDown,
37
+ blockScrollUp,
38
+ children,
39
+ height,
40
+ width,
41
+ customPageNumber,
42
+ onScrollUnavailable,
43
+ onChange,
44
+ renderAllPagesOnFirstRender,
45
+ transitionTimingFunction,
46
+ }) => {
47
+ const [componentIndex, setComponentIndex] = useState(DEFAULT_COMPONENT_INDEX);
48
+ const [componentsToRenderLength, setComponentsToRenderLength] = useState(
49
+ DEFAULT_COMPONENTS_TO_RENDER_LENGTH
50
+ );
51
+ const prevComponentIndex = usePrevious(componentIndex);
52
+ const pageContainer = useRef(null);
53
+
54
+ const addNextComponent = useCallback(
55
+ componentsToRenderOnMountLength => {
56
+ let tempComponentsToRenderLength = 0;
57
+
58
+ if (!isNil(componentsToRenderOnMountLength)) {
59
+ tempComponentsToRenderLength = componentsToRenderOnMountLength;
60
+ }
61
+
62
+ tempComponentsToRenderLength = Math.max(
63
+ tempComponentsToRenderLength,
64
+ componentsToRenderLength
65
+ );
66
+
67
+ if (tempComponentsToRenderLength <= componentIndex + 1) {
68
+ if (!isNil(children[componentIndex + 1])) {
69
+ tempComponentsToRenderLength++;
70
+ }
71
+ }
72
+
73
+ setComponentsToRenderLength(tempComponentsToRenderLength);
74
+ },
75
+ [children, componentIndex, componentsToRenderLength]
76
+ );
77
+
78
+ const checkRenderOnMount = useCallback(() => {
79
+ if (renderAllPagesOnFirstRender) {
80
+ setComponentsToRenderLength(React.Children.count(children));
81
+ } else if (!isNil(children[DEFAULT_COMPONENT_INDEX + 1])) {
82
+ addNextComponent(DEFAULT_COMPONENTS_TO_RENDER_LENGTH + 1);
83
+ }
84
+ }, [addNextComponent, children, renderAllPagesOnFirstRender]);
85
+
86
+ const disableScroll = useCallback(() => {
87
+ if (isBodyScrollEnabled) {
88
+ isBodyScrollEnabled = false;
89
+ window.scrollTo({
90
+ left: 0,
91
+ top: 0,
92
+ behavior: 'smooth',
93
+ });
94
+ document.body.classList.add(DISABLED_CLASS_NAME);
95
+ document.documentElement.classList.add(DISABLED_CLASS_NAME);
96
+ }
97
+ }, []);
98
+
99
+ const enableDocumentScroll = useCallback(() => {
100
+ if (!isBodyScrollEnabled) {
101
+ isBodyScrollEnabled = true;
102
+ document.body.classList.remove(DISABLED_CLASS_NAME);
103
+ document.documentElement.classList.remove(DISABLED_CLASS_NAME);
104
+ }
105
+ }, []);
106
+
107
+ const setRenderComponents = useCallback(() => {
108
+ const newComponentsToRender = [];
109
+
110
+ let i = 0;
111
+
112
+ while (i < componentsToRenderLength && !isNil(children[i])) {
113
+ containers[i] = true;
114
+ newComponentsToRender.push(
115
+ <div key={i} style={{ height: '100%', width: '100%' }}>
116
+ {React.cloneElement(children[i], { ...children[i].props })}
117
+ </div>
118
+ );
119
+ i++;
120
+ }
121
+
122
+ return newComponentsToRender;
123
+ }, [children, componentsToRenderLength]);
124
+
125
+ const scrollWindowDown = useCallback(() => {
126
+ if (!isScrolling && !blockScrollDown) {
127
+ if (!isNil(containers[componentIndex + 1])) {
128
+ disableScroll();
129
+ isScrolling = true;
130
+ pageContainer.current.style.transform = `translate3d(0, ${(componentIndex + 1) * -100}%, 0)`; // prettier-ignore
131
+
132
+ setTimeout(() => {
133
+ if (isMounted) {
134
+ setComponentIndex(prevState => prevState + 1);
135
+ }
136
+ }, animationTimer + ANIMATION_TIMER_BUFFER);
137
+ } else {
138
+ enableDocumentScroll();
139
+ if (onScrollUnavailable) {
140
+ onScrollUnavailable();
141
+ }
142
+ }
143
+ }
144
+ }, [
145
+ animationTimer,
146
+ blockScrollDown,
147
+ componentIndex,
148
+ disableScroll,
149
+ enableDocumentScroll,
150
+ onScrollUnavailable,
151
+ ]);
152
+
153
+ const scrollWindowUp = useCallback(() => {
154
+ if (!isScrolling && !blockScrollUp) {
155
+ if (!isNil(containers[componentIndex - 1])) {
156
+ disableScroll();
157
+ isScrolling = true;
158
+ pageContainer.current.style.transform = `translate3d(0, ${(componentIndex - 1) * -100}%, 0)`; // prettier-ignore
159
+
160
+ setTimeout(() => {
161
+ if (isMounted) {
162
+ setComponentIndex(prevState => prevState - 1);
163
+ }
164
+ }, animationTimer + ANIMATION_TIMER_BUFFER);
165
+ } else {
166
+ enableDocumentScroll();
167
+ if (onScrollUnavailable) {
168
+ onScrollUnavailable();
169
+ }
170
+ }
171
+ }
172
+ }, [
173
+ animationTimer,
174
+ blockScrollUp,
175
+ componentIndex,
176
+ disableScroll,
177
+ enableDocumentScroll,
178
+ onScrollUnavailable,
179
+ ]);
180
+
181
+ const touchMove = useCallback(
182
+ event => {
183
+ if (!isNull(previousTouchMove)) {
184
+ if (event.touches[0].clientY > previousTouchMove) {
185
+ scrollWindowUp();
186
+ } else {
187
+ scrollWindowDown();
188
+ }
189
+ } else {
190
+ previousTouchMove = event.touches[0].clientY;
191
+ }
192
+ },
193
+ [scrollWindowDown, scrollWindowUp]
194
+ );
195
+
196
+ const wheelScroll = useCallback(
197
+ event => {
198
+ if (event.deltaY < 0) {
199
+ scrollWindowUp();
200
+ } else {
201
+ scrollWindowDown();
202
+ }
203
+ },
204
+ [scrollWindowDown, scrollWindowUp]
205
+ );
206
+
207
+ const keyPress = useCallback(
208
+ event => {
209
+ if (isEqual(event.keyCode, KEY_UP)) {
210
+ scrollWindowUp();
211
+ }
212
+ if (isEqual(event.keyCode, KEY_DOWN)) {
213
+ scrollWindowDown();
214
+ }
215
+ },
216
+ [scrollWindowDown, scrollWindowUp]
217
+ );
218
+
219
+ useEffect(() => {
220
+ pageContainer.current.addEventListener(Events.TOUCHMOVE, touchMove);
221
+ pageContainer.current.addEventListener(Events.KEYDOWN, keyPress);
222
+ return () => {
223
+ pageContainer.current.removeEventListener(Events.TOUCHMOVE, touchMove);
224
+ pageContainer.current.removeEventListener(Events.KEYDOWN, keyPress);
225
+ };
226
+ }, [touchMove, keyPress]);
227
+
228
+ useEffect(() => {
229
+ isMounted = true;
230
+
231
+ checkRenderOnMount();
232
+ return () => {
233
+ isMounted = false;
234
+ };
235
+ }, []);
236
+
237
+ useEffect(() => {
238
+ isScrolling = false;
239
+ previousTouchMove = null;
240
+ if (componentIndex > prevComponentIndex) {
241
+ addNextComponent();
242
+ }
243
+ }, [addNextComponent, componentIndex, prevComponentIndex]);
244
+
245
+ useEffect(() => {
246
+ if (onChange) {
247
+ onChange(componentIndex);
248
+ }
249
+ }, [onChange, componentIndex]);
250
+
251
+ useEffect(() => {
252
+ if (!isNil(customPageNumber) && !isEqual(customPageNumber, componentIndex)) {
253
+ let newComponentsToRenderLength = componentsToRenderLength;
254
+
255
+ if (!isEqual(componentIndex, customPageNumber)) {
256
+ if (!isNil(containers[customPageNumber]) && !isScrolling) {
257
+ isScrolling = true;
258
+ pageContainer.current.style.transform = `translate3d(0, ${customPageNumber * -100}%, 0)`;
259
+
260
+ if (isNil(containers[customPageNumber]) && !isNil(children[customPageNumber])) {
261
+ newComponentsToRenderLength++;
262
+ }
263
+
264
+ setTimeout(() => {
265
+ setComponentIndex(customPageNumber);
266
+ setComponentsToRenderLength(newComponentsToRenderLength);
267
+ }, animationTimer + ANIMATION_TIMER_BUFFER);
268
+ } else if (!isScrolling && !isNil(children[customPageNumber])) {
269
+ for (let i = componentsToRenderLength; i <= customPageNumber; i++) {
270
+ newComponentsToRenderLength++;
271
+ }
272
+
273
+ if (!isNil(children[customPageNumber])) {
274
+ newComponentsToRenderLength++;
275
+ }
276
+
277
+ isScrolling = true;
278
+ isTransitionAfterComponentsToRenderChanged = true;
279
+ setComponentsToRenderLength(newComponentsToRenderLength);
280
+ }
281
+ }
282
+ }
283
+ }, [customPageNumber]);
284
+
285
+ useEffect(() => {
286
+ if (isTransitionAfterComponentsToRenderChanged) {
287
+ isTransitionAfterComponentsToRenderChanged = false;
288
+
289
+ pageContainer.current.style.transform = `translate3d(0, ${customPageNumber * -100}%, 0)`;
290
+
291
+ setTimeout(() => {
292
+ setComponentIndex(customPageNumber);
293
+ }, animationTimer + ANIMATION_TIMER_BUFFER);
294
+ }
295
+ }, [animationTimer, componentsToRenderLength, customPageNumber]);
296
+
297
+ return (
298
+ <div style={{ height, width, overflow: 'hidden' }}>
299
+ <div
300
+ ref={pageContainer}
301
+ onWheel={wheelScroll}
302
+ style={{
303
+ height: '100%',
304
+ width: '100%',
305
+ transition: `transform ${animationTimer}ms ${transitionTimingFunction}`,
306
+ outline: 'none',
307
+ }}
308
+ tabIndex={0}>
309
+ {setRenderComponents()}
310
+ </div>
311
+ </div>
312
+ );
313
+ };
314
+
315
+ PageScroller.propTypes = {
316
+ animationTimer: PropTypes.number,
317
+ blockScrollDown: PropTypes.bool,
318
+ blockScrollUp: PropTypes.bool,
319
+ children: PropTypes.any.isRequired,
320
+ height: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
321
+ width: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
322
+ customPageNumber: PropTypes.number,
323
+ onScrollUnavailable: PropTypes.func,
324
+ onChange: PropTypes.func,
325
+ renderAllPagesOnFirstRender: PropTypes.bool,
326
+ transitionTimingFunction: PropTypes.string,
327
+ };
328
+
329
+ PageScroller.defaultProps = {
330
+ animationTimer: DEFAULT_ANIMATION_TIMER,
331
+ transitionTimingFunction: DEFAULT_ANIMATION,
332
+ height: DEFAULT_CONTAINER_HEIGHT,
333
+ width: DEFAULT_CONTAINER_WIDTH,
334
+ onChange: undefined,
335
+ onScrollUnavailable: undefined,
336
+ blockScrollUp: false,
337
+ blockScrollDown: false,
338
+ renderAllPagesOnFirstRender: false,
339
+ customPageNumber: undefined,
340
+ };
341
+
342
+ export default PageScroller;
@@ -0,0 +1,12 @@
1
+ // eslint-disable-next-line no-unused-vars
2
+ import React, { useEffect, useRef } from 'react';
3
+
4
+ export default function usePrevious(value) {
5
+ const ref = useRef({});
6
+
7
+ useEffect(() => {
8
+ ref.current = value;
9
+ }, [value]);
10
+
11
+ return ref.current;
12
+ }