react-inlinesvg 3.0.2 → 3.1.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/src/index.tsx CHANGED
@@ -1,15 +1,18 @@
1
1
  import * as React from 'react';
2
2
  import convert from 'react-from-dom';
3
3
 
4
- import { canUseDOM, isSupportedEnvironment, omit, randomString, STATUS } from './helpers';
5
- import { FetchError, Props, State, StorageItem } from './types';
4
+ import CacheStore from './cache';
5
+ import { STATUS } from './config';
6
+ import { canUseDOM, isSupportedEnvironment, omit, randomString, request } from './helpers';
7
+ import { FetchError, Props, State, Status } from './types';
6
8
 
7
- export const cacheStore: { [key: string]: StorageItem } = Object.create(null);
9
+ // eslint-disable-next-line import/no-mutable-exports
10
+ export let cacheStore: CacheStore;
8
11
 
9
- export default class InlineSVG extends React.PureComponent<Props, State> {
10
- private isInitialized = false;
11
- private isActive = false;
12
+ class ReactInlineSVG extends React.PureComponent<Props, State> {
12
13
  private readonly hash: string;
14
+ private isActive = false;
15
+ private isInitialized = false;
13
16
 
14
17
  public static defaultProps = {
15
18
  cacheRequests: true,
@@ -22,8 +25,8 @@ export default class InlineSVG extends React.PureComponent<Props, State> {
22
25
  this.state = {
23
26
  content: '',
24
27
  element: null,
25
- hasCache: !!props.cacheRequests && !!cacheStore[props.src],
26
- status: STATUS.PENDING,
28
+ isCached: !!props.cacheRequests && cacheStore.isCached(props.src),
29
+ status: STATUS.IDLE,
27
30
  };
28
31
 
29
32
  this.hash = props.uniqueHash || randomString(8);
@@ -41,7 +44,7 @@ export default class InlineSVG extends React.PureComponent<Props, State> {
41
44
 
42
45
  try {
43
46
  /* istanbul ignore else */
44
- if (status === STATUS.PENDING) {
47
+ if (status === STATUS.IDLE) {
45
48
  /* istanbul ignore else */
46
49
  if (!isSupportedEnvironment()) {
47
50
  throw new Error('Browser does not support SVG');
@@ -66,13 +69,13 @@ export default class InlineSVG extends React.PureComponent<Props, State> {
66
69
  return;
67
70
  }
68
71
 
69
- const { hasCache, status } = this.state;
72
+ const { isCached, status } = this.state;
70
73
  const { onLoad, src } = this.props;
71
74
 
72
75
  if (previousState.status !== STATUS.READY && status === STATUS.READY) {
73
76
  /* istanbul ignore else */
74
77
  if (onLoad) {
75
- onLoad(src, hasCache);
78
+ onLoad(src, isCached);
76
79
  }
77
80
  }
78
81
 
@@ -91,6 +94,32 @@ export default class InlineSVG extends React.PureComponent<Props, State> {
91
94
  this.isActive = false;
92
95
  }
93
96
 
97
+ private fetchContent = async () => {
98
+ const { fetchOptions, src } = this.props;
99
+
100
+ const content: string = await request(src, fetchOptions);
101
+
102
+ this.handleLoad(content);
103
+ };
104
+
105
+ private getElement() {
106
+ try {
107
+ const node = this.getNode() as Node;
108
+ const element = convert(node);
109
+
110
+ if (!element || !React.isValidElement(element)) {
111
+ throw new Error('Could not convert the src to a React element');
112
+ }
113
+
114
+ this.setState({
115
+ element,
116
+ status: STATUS.READY,
117
+ });
118
+ } catch (error: any) {
119
+ this.handleError(new Error(error.message));
120
+ }
121
+ }
122
+
94
123
  private getNode() {
95
124
  const { description, title } = this.props;
96
125
 
@@ -138,38 +167,6 @@ export default class InlineSVG extends React.PureComponent<Props, State> {
138
167
  }
139
168
  }
140
169
 
141
- private getElement() {
142
- try {
143
- const node = this.getNode() as Node;
144
- const element = convert(node);
145
-
146
- if (!element || !React.isValidElement(element)) {
147
- throw new Error('Could not convert the src to a React element');
148
- }
149
-
150
- this.setState({
151
- element,
152
- status: STATUS.READY,
153
- });
154
- } catch (error: any) {
155
- this.handleError(new Error(error.message));
156
- }
157
- }
158
-
159
- private handleLoad = (content: string, hasCache = false) => {
160
- /* istanbul ignore else */
161
- if (this.isActive) {
162
- this.setState(
163
- {
164
- content,
165
- hasCache,
166
- status: STATUS.LOADED,
167
- },
168
- this.getElement,
169
- );
170
- }
171
- };
172
-
173
170
  private handleError = (error: Error | FetchError) => {
174
171
  const { onError } = this.props;
175
172
  const status =
@@ -186,69 +183,17 @@ export default class InlineSVG extends React.PureComponent<Props, State> {
186
183
  }
187
184
  };
188
185
 
189
- private request = () => {
190
- const { cacheRequests, fetchOptions, src } = this.props;
191
-
192
- try {
193
- if (cacheRequests) {
194
- cacheStore[src] = { content: '', status: STATUS.LOADING };
195
- }
196
-
197
- return fetch(src, fetchOptions)
198
- .then(response => {
199
- const contentType = response.headers.get('content-type');
200
- const [fileType] = (contentType || '').split(/ ?; ?/);
201
-
202
- if (response.status > 299) {
203
- throw new Error('Not found');
204
- }
205
-
206
- if (!['image/svg+xml', 'text/plain'].some(d => fileType.includes(d))) {
207
- throw new Error(`Content type isn't valid: ${fileType}`);
208
- }
209
-
210
- return response.text();
211
- })
212
- .then(content => {
213
- const { src: currentSrc } = this.props;
214
-
215
- // the current src don't match the previous one, skipping...
216
- if (src !== currentSrc) {
217
- if (cacheStore[src].status === STATUS.LOADING) {
218
- delete cacheStore[src];
219
- }
220
-
221
- return;
222
- }
223
-
224
- this.handleLoad(content);
225
-
226
- /* istanbul ignore else */
227
- if (cacheRequests) {
228
- const cache = cacheStore[src];
229
-
230
- /* istanbul ignore else */
231
- if (cache) {
232
- cache.content = content;
233
- cache.status = STATUS.LOADED;
234
- }
235
- }
236
- })
237
- .catch(error => {
238
- this.handleError(error);
239
-
240
- /* istanbul ignore else */
241
- if (cacheRequests) {
242
- const cache = cacheStore[src];
243
-
244
- /* istanbul ignore else */
245
- if (cache) {
246
- delete cacheStore[src];
247
- }
248
- }
249
- });
250
- } catch (error: any) {
251
- return this.handleError(new Error(error.message));
186
+ private handleLoad = (content: string, hasCache = false) => {
187
+ /* istanbul ignore else */
188
+ if (this.isActive) {
189
+ this.setState(
190
+ {
191
+ content,
192
+ isCached: hasCache,
193
+ status: STATUS.LOADED,
194
+ },
195
+ this.getElement,
196
+ );
252
197
  }
253
198
  };
254
199
 
@@ -259,20 +204,13 @@ export default class InlineSVG extends React.PureComponent<Props, State> {
259
204
  {
260
205
  content: '',
261
206
  element: null,
262
- hasCache: false,
207
+ isCached: false,
263
208
  status: STATUS.LOADING,
264
209
  },
265
- () => {
266
- const { cacheRequests, src } = this.props;
267
- const cache = cacheRequests && cacheStore[src];
268
-
269
- if (cache && cache.status === STATUS.LOADED) {
270
- this.handleLoad(cache.content, true);
271
-
272
- return;
273
- }
210
+ async () => {
211
+ const { cacheRequests, fetchOptions, src } = this.props;
274
212
 
275
- const dataURI = src.match(/data:image\/svg[^,]*?(;base64)?,(.*)/);
213
+ const dataURI = src.match(/^data:image\/svg[^,]*?(;base64)?,(.*)/u);
276
214
  let inlineSrc;
277
215
 
278
216
  if (dataURI) {
@@ -287,12 +225,33 @@ export default class InlineSVG extends React.PureComponent<Props, State> {
287
225
  return;
288
226
  }
289
227
 
290
- this.request();
228
+ try {
229
+ if (cacheRequests) {
230
+ const content = await cacheStore.get(src, fetchOptions);
231
+
232
+ this.handleLoad(content, true);
233
+ } else {
234
+ await this.fetchContent();
235
+ }
236
+ } catch (error: any) {
237
+ this.handleError(error);
238
+ }
291
239
  },
292
240
  );
293
241
  }
294
242
  }
295
243
 
244
+ private processSVG() {
245
+ const { content } = this.state;
246
+ const { preProcessor } = this.props;
247
+
248
+ if (preProcessor) {
249
+ return preProcessor(content);
250
+ }
251
+
252
+ return content;
253
+ }
254
+
296
255
  private updateSVGAttributes(node: SVGSVGElement): SVGSVGElement {
297
256
  const { baseURL = '', uniquifyIDs } = this.props;
298
257
  const replaceableAttributes = ['id', 'href', 'xlink:href', 'xlink:role', 'xlink:arcrole'];
@@ -304,7 +263,7 @@ export default class InlineSVG extends React.PureComponent<Props, State> {
304
263
  return node;
305
264
  }
306
265
 
307
- [...node.children].map(d => {
266
+ [...node.children].forEach(d => {
308
267
  if (d.attributes && d.attributes.length) {
309
268
  const attributes = Object.values(d.attributes).map(a => {
310
269
  const attribute = a;
@@ -336,17 +295,6 @@ export default class InlineSVG extends React.PureComponent<Props, State> {
336
295
  return node;
337
296
  }
338
297
 
339
- private processSVG() {
340
- const { content } = this.state;
341
- const { preProcessor } = this.props;
342
-
343
- if (preProcessor) {
344
- return preProcessor(content);
345
- }
346
-
347
- return content;
348
- }
349
-
350
298
  public render(): React.ReactNode {
351
299
  const { element, status } = this.state;
352
300
  const { children = null, innerRef, loader = null } = this.props;
@@ -376,7 +324,7 @@ export default class InlineSVG extends React.PureComponent<Props, State> {
376
324
  return React.cloneElement(element as React.ReactElement, { ref: innerRef, ...elementProps });
377
325
  }
378
326
 
379
- if ([STATUS.UNSUPPORTED, STATUS.FAILED].includes(status)) {
327
+ if (([STATUS.UNSUPPORTED, STATUS.FAILED] as Status[]).includes(status)) {
380
328
  return children;
381
329
  }
382
330
 
@@ -384,4 +332,27 @@ export default class InlineSVG extends React.PureComponent<Props, State> {
384
332
  }
385
333
  }
386
334
 
335
+ export default function InlineSVG(props: Props) {
336
+ if (!cacheStore) {
337
+ cacheStore = new CacheStore();
338
+ }
339
+
340
+ const { loader } = props;
341
+ const [isReady, setReady] = React.useState(cacheStore.isReady);
342
+
343
+ React.useEffect(() => {
344
+ if (!isReady) {
345
+ cacheStore.onReady(() => {
346
+ setReady(true);
347
+ });
348
+ }
349
+ }, [isReady]);
350
+
351
+ if (!isReady) {
352
+ return loader ?? null;
353
+ }
354
+
355
+ return <ReactInlineSVG {...props} />;
356
+ }
357
+
387
358
  export * from './types';
@@ -0,0 +1,23 @@
1
+ import { ReactNode, useEffect } from 'react';
2
+
3
+ interface Props {
4
+ children: ReactNode;
5
+ }
6
+
7
+ declare global {
8
+ interface Window {
9
+ REACT_INLINESVG_PERSISTENT_CACHE?: boolean;
10
+ }
11
+ }
12
+
13
+ export default function CacheProvider({ children }: Props) {
14
+ useEffect(() => {
15
+ window.REACT_INLINESVG_PERSISTENT_CACHE = true;
16
+
17
+ return () => {
18
+ delete window.REACT_INLINESVG_PERSISTENT_CACHE;
19
+ };
20
+ }, []);
21
+
22
+ return children;
23
+ }
package/src/types.ts CHANGED
@@ -1,8 +1,10 @@
1
1
  import * as React from 'react';
2
2
 
3
+ import { STATUS } from './config';
4
+
3
5
  export type ErrorCallback = (error: Error | FetchError) => void;
4
6
  export type LoadCallback = (src: string, isCached: boolean) => void;
5
- export type PlainObject<T = unknown> = Record<string | number | symbol, T>;
7
+ export type PlainObject<T = unknown> = Record<string, T>;
6
8
  export type PreProcessorCallback = (code: string) => string;
7
9
 
8
10
  export interface Props extends Omit<React.SVGProps<SVGElement>, 'onLoad' | 'onError' | 'ref'> {
@@ -25,8 +27,8 @@ export interface Props extends Omit<React.SVGProps<SVGElement>, 'onLoad' | 'onEr
25
27
  export interface State {
26
28
  content: string;
27
29
  element: React.ReactNode;
28
- hasCache: boolean;
29
- status: string;
30
+ isCached: boolean;
31
+ status: Status;
30
32
  }
31
33
 
32
34
  export interface FetchError extends Error {
@@ -36,7 +38,9 @@ export interface FetchError extends Error {
36
38
  type: string;
37
39
  }
38
40
 
41
+ export type Status = (typeof STATUS)[keyof typeof STATUS];
42
+
39
43
  export interface StorageItem {
40
44
  content: string;
41
- status: string;
45
+ status: Status;
42
46
  }
package/esm/helpers.d.ts DELETED
@@ -1,17 +0,0 @@
1
- import { PlainObject } from './types';
2
- export declare const STATUS: {
3
- FAILED: string;
4
- LOADED: string;
5
- LOADING: string;
6
- PENDING: string;
7
- READY: string;
8
- UNSUPPORTED: string;
9
- };
10
- export declare function canUseDOM(): boolean;
11
- export declare function isSupportedEnvironment(): boolean;
12
- export declare function supportsInlineSVG(): boolean;
13
- export declare function randomString(length: number): string;
14
- /**
15
- * Remove properties from an object
16
- */
17
- export declare function omit<T extends PlainObject, K extends keyof T>(input: T, ...filter: K[]): Omit<T, K>;
package/esm/helpers.js DELETED
@@ -1,58 +0,0 @@
1
- import { canUseDOM as canUseDOMFlag } from 'exenv';
2
- export var STATUS = {
3
- FAILED: 'failed',
4
- LOADED: 'loaded',
5
- LOADING: 'loading',
6
- PENDING: 'pending',
7
- READY: 'ready',
8
- UNSUPPORTED: 'unsupported',
9
- };
10
- export function canUseDOM() {
11
- return canUseDOMFlag;
12
- }
13
- export function isSupportedEnvironment() {
14
- return supportsInlineSVG() && typeof window !== 'undefined' && window !== null;
15
- }
16
- export function supportsInlineSVG() {
17
- /* istanbul ignore next */
18
- if (!document) {
19
- return false;
20
- }
21
- var div = document.createElement('div');
22
- div.innerHTML = '<svg />';
23
- var svg = div.firstChild;
24
- return !!svg && svg.namespaceURI === 'http://www.w3.org/2000/svg';
25
- }
26
- function randomCharacter(character) {
27
- return character[Math.floor(Math.random() * character.length)];
28
- }
29
- export function randomString(length) {
30
- var letters = 'abcdefghijklmnopqrstuvwxyz';
31
- var numbers = '1234567890';
32
- var charset = "".concat(letters).concat(letters.toUpperCase()).concat(numbers);
33
- var R = '';
34
- for (var index = 0; index < length; index++) {
35
- R += randomCharacter(charset);
36
- }
37
- return R;
38
- }
39
- /**
40
- * Remove properties from an object
41
- */
42
- export function omit(input) {
43
- var filter = [];
44
- for (var _i = 1; _i < arguments.length; _i++) {
45
- filter[_i - 1] = arguments[_i];
46
- }
47
- var output = {};
48
- for (var key in input) {
49
- /* istanbul ignore else */
50
- if ({}.hasOwnProperty.call(input, key)) {
51
- if (!filter.includes(key)) {
52
- output[key] = input[key];
53
- }
54
- }
55
- }
56
- return output;
57
- }
58
- //# sourceMappingURL=helpers.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"helpers.js","sourceRoot":"","sources":["../src/helpers.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,IAAI,aAAa,EAAE,MAAM,OAAO,CAAC;AAInD,MAAM,CAAC,IAAM,MAAM,GAAG;IACpB,MAAM,EAAE,QAAQ;IAChB,MAAM,EAAE,QAAQ;IAChB,OAAO,EAAE,SAAS;IAClB,OAAO,EAAE,SAAS;IAClB,KAAK,EAAE,OAAO;IACd,WAAW,EAAE,aAAa;CAC3B,CAAC;AAEF,MAAM,UAAU,SAAS;IACvB,OAAO,aAAa,CAAC;AACvB,CAAC;AAED,MAAM,UAAU,sBAAsB;IACpC,OAAO,iBAAiB,EAAE,IAAI,OAAO,MAAM,KAAK,WAAW,IAAI,MAAM,KAAK,IAAI,CAAC;AACjF,CAAC;AAED,MAAM,UAAU,iBAAiB;IAC/B,0BAA0B;IAC1B,IAAI,CAAC,QAAQ,EAAE;QACb,OAAO,KAAK,CAAC;KACd;IAED,IAAM,GAAG,GAAG,QAAQ,CAAC,aAAa,CAAC,KAAK,CAAC,CAAC;IAE1C,GAAG,CAAC,SAAS,GAAG,SAAS,CAAC;IAC1B,IAAM,GAAG,GAAG,GAAG,CAAC,UAA2B,CAAC;IAE5C,OAAO,CAAC,CAAC,GAAG,IAAI,GAAG,CAAC,YAAY,KAAK,4BAA4B,CAAC;AACpE,CAAC;AAED,SAAS,eAAe,CAAC,SAAiB;IACxC,OAAO,SAAS,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,MAAM,EAAE,GAAG,SAAS,CAAC,MAAM,CAAC,CAAC,CAAC;AACjE,CAAC;AAED,MAAM,UAAU,YAAY,CAAC,MAAc;IACzC,IAAM,OAAO,GAAG,4BAA4B,CAAC;IAC7C,IAAM,OAAO,GAAG,YAAY,CAAC;IAC7B,IAAM,OAAO,GAAG,UAAG,OAAO,SAAG,OAAO,CAAC,WAAW,EAAE,SAAG,OAAO,CAAE,CAAC;IAE/D,IAAI,CAAC,GAAG,EAAE,CAAC;IAEX,KAAK,IAAI,KAAK,GAAG,CAAC,EAAE,KAAK,GAAG,MAAM,EAAE,KAAK,EAAE,EAAE;QAC3C,CAAC,IAAI,eAAe,CAAC,OAAO,CAAC,CAAC;KAC/B;IAED,OAAO,CAAC,CAAC;AACX,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,IAAI,CAClB,KAAQ;IACR,gBAAc;SAAd,UAAc,EAAd,qBAAc,EAAd,IAAc;QAAd,+BAAc;;IAEd,IAAM,MAAM,GAAQ,EAAE,CAAC;IAEvB,KAAK,IAAM,GAAG,IAAI,KAAK,EAAE;QACvB,0BAA0B;QAC1B,IAAI,EAAE,CAAC,cAAc,CAAC,IAAI,CAAC,KAAK,EAAE,GAAG,CAAC,EAAE;YACtC,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,GAAmB,CAAC,EAAE;gBACzC,MAAM,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC,GAAG,CAAC,CAAC;aAC1B;SACF;KACF;IAED,OAAO,MAAoB,CAAC;AAC9B,CAAC"}
package/esm/index.d.ts DELETED
@@ -1,28 +0,0 @@
1
- import * as React from 'react';
2
- import { Props, State, StorageItem } from './types';
3
- export declare const cacheStore: {
4
- [key: string]: StorageItem;
5
- };
6
- export default class InlineSVG extends React.PureComponent<Props, State> {
7
- private isInitialized;
8
- private isActive;
9
- private readonly hash;
10
- static defaultProps: {
11
- cacheRequests: boolean;
12
- uniquifyIDs: boolean;
13
- };
14
- constructor(props: Props);
15
- componentDidMount(): void;
16
- componentDidUpdate(previousProps: Props, previousState: State): void;
17
- componentWillUnmount(): void;
18
- private getNode;
19
- private getElement;
20
- private handleLoad;
21
- private handleError;
22
- private request;
23
- private load;
24
- private updateSVGAttributes;
25
- private processSVG;
26
- render(): React.ReactNode;
27
- }
28
- export * from './types';