diff --git a/docs/src/components/screens/indexes/Header.js b/docs/src/components/screens/indexes/Header.js
index 9fd2e32..1e4c57f 100644
--- a/docs/src/components/screens/indexes/Header.js
+++ b/docs/src/components/screens/indexes/Header.js
@@ -14,7 +14,11 @@ const Header = () => {
{`// Key data by field name instead of index/position
-const results = readString(csvString {
+readString(csvString, {
+ worker: true,
+ complete: (results) => {
+ console.log(results)
+ },
header: true
})`}
diff --git a/docs/src/components/screens/indexes/LocalFile.js b/docs/src/components/screens/indexes/LocalFile.js
index 155e098..030b5ab 100644
--- a/docs/src/components/screens/indexes/LocalFile.js
+++ b/docs/src/components/screens/indexes/LocalFile.js
@@ -41,108 +41,69 @@ const LocalFile = () => {
- {`import React, { Component } from 'react'
+ {`import React, { CSSProperties } from 'react';
-import { CSVReader } from 'react-papaparse'
+import { useCSVReader } from 'react-papaparse';
-const buttonRef = React.createRef()
-
-export default class CSVReader extends Component {
- handleOpenDialog = (e) => {
- // Note that the ref is set async, so it might be null at some point
- if (buttonRef.current) {
- buttonRef.current.open(e)
- }
- }
-
- handleOnFileLoad = (data) => {
- console.log('---------------------------')
- console.log(data)
- console.log('---------------------------')
- }
-
- handleOnError = (err, file, inputElem, reason) => {
- console.log(err)
- }
-
- handleOnRemoveFile = (data) => {
- console.log('---------------------------')
- console.log(data)
- console.log('---------------------------')
- }
+const styles = {
+ csvReader: {
+ display: 'flex',
+ flexDirection: 'row',
+ marginBottom: 10,
+ } as CSSProperties,
+ browseFile: {
+ width: '20%',
+ } as CSSProperties,
+ acceptedFile: {
+ border: '1px solid #ccc',
+ height: 45,
+ lineHeight: 2.5,
+ paddingLeft: 10,
+ width: '80%',
+ } as CSSProperties,
+ remove: {
+ borderRadius: 0,
+ padding: '0 20px',
+ } as CSSProperties,
+ progressBarBackgroundColor: {
+ backgroundColor: 'red',
+ } as CSSProperties,
+};
- handleRemoveFile = (e) => {
- // Note that the ref is set async, so it might be null at some point
- if (buttonRef.current) {
- buttonRef.current.removeFile(e)
- }
- }
+export default function CSVReader() {
+ const { CSVReader } = useCSVReader();
- render() {
- return (
-
- {({ file }) => (
-
-
- Browe file
+ return (
+ {
+ console.log('---------------------------');
+ console.log(results);
+ console.log('---------------------------');
+ }}
+ >
+ {({
+ getRootProps,
+ acceptedFile,
+ ProgressBar,
+ getRemoveFileProps,
+ }: any) => (
+ <>
+
+
+ Browse file
-
- {file && file.name}
+
+ {acceptedFile && acceptedFile.name}
-
+
Remove
-
- )}
-
- )
- }
+
+
+ >
+ )}
+
+ );
}`}
@@ -174,41 +135,166 @@ export default class CSVReader extends Component {
- {`import React, { Component } from 'react'
+ {`import React, { useState, CSSProperties } from 'react';
-import { CSVReader } from 'react-papaparse'
+import {
+ useCSVReader,
+ lightenDarkenColor,
+ formatFileSize,
+} from 'react-papaparse';
-export default class CSVReader extends Component {
- handleOnDrop = (data) => {
- console.log('---------------------------')
- console.log(data)
- console.log('---------------------------')
- }
+const GREY = '#CCC';
+const GREY_LIGHT = 'rgba(255, 255, 255, 0.4)';
+const DEFAULT_REMOVE_HOVER_COLOR = '#A01919';
+const REMOVE_HOVER_COLOR_LIGHT = lightenDarkenColor(
+ DEFAULT_REMOVE_HOVER_COLOR,
+ 40
+);
+const GREY_DIM = '#686868';
- handleOnError = (err, file, inputElem, reason) => {
- console.log(err)
- }
+const styles = {
+ zone: {
+ alignItems: 'center',
+ borderWidth: 2,
+ borderStyle: 'dashed',
+ borderColor: GREY,
+ borderRadius: 20,
+ display: 'flex',
+ flexDirection: 'column',
+ height: '100%',
+ justifyContent: 'center',
+ padding: 20,
+ } as CSSProperties,
+ file: {
+ background: 'linear-gradient(to bottom, #EEE, #DDD)',
+ borderRadius: 20,
+ display: 'flex',
+ height: 120,
+ width: 120,
+ position: 'relative',
+ zIndex: 10,
+ flexDirection: 'column',
+ justifyContent: 'center',
+ } as CSSProperties,
+ info: {
+ alignItems: 'center',
+ display: 'flex',
+ flexDirection: 'column',
+ paddingLeft: 10,
+ paddingRight: 10,
+ } as CSSProperties,
+ size: {
+ backgroundColor: GREY_LIGHT,
+ borderRadius: 3,
+ marginBottom: '0.5em',
+ justifyContent: 'center',
+ display: 'flex',
+ } as CSSProperties,
+ name: {
+ backgroundColor: GREY_LIGHT,
+ borderRadius: 3,
+ fontSize: 12,
+ marginBottom: '0.5em',
+ } as CSSProperties,
+ progressBar: {
+ bottom: 14,
+ position: 'absolute',
+ width: '100%',
+ paddingLeft: 10,
+ paddingRight: 10,
+ } as CSSProperties,
+ zoneHover: {
+ borderColor: GREY_DIM,
+ } as CSSProperties,
+ default: {
+ borderColor: GREY,
+ } as CSSProperties,
+ remove: {
+ height: 23,
+ position: 'absolute',
+ right: 6,
+ top: 6,
+ width: 23,
+ } as CSSProperties,
+};
- handleOnRemoveFile = (data) => {
- console.log('---------------------------')
- console.log(data)
- console.log('---------------------------')
- }
+export default function CSVReader() {
+ const { CSVReader } = useCSVReader();
+ const [zoneHover, setZoneHover] = useState(false);
+ const [removeHoverColor, setRemoveHoverColor] = useState(
+ DEFAULT_REMOVE_HOVER_COLOR
+ );
- render() {
- return (
-
- Drop CSV file here or click to upload.
-
- )
- }
-}
-`}
+ return (
+ {
+ console.log('---------------------------');
+ console.log(results);
+ console.log('---------------------------');
+ setZoneHover(false);
+ }}
+ onDragOver={(event: DragEvent) => {
+ event.preventDefault();
+ setZoneHover(true);
+ }}
+ onDragLeave={(event: DragEvent) => {
+ event.preventDefault();
+ setZoneHover(false);
+ }}
+ >
+ {({
+ getRootProps,
+ acceptedFile,
+ ProgressBar,
+ getRemoveFileProps,
+ Remove,
+ }: any) => (
+ <>
+
+ {acceptedFile ? (
+ <>
+
+
+
+ {formatFileSize(acceptedFile.size)}
+
+ {acceptedFile.name}
+
+
+
{
+ event.preventDefault();
+ setRemoveHoverColor(REMOVE_HOVER_COLOR_LIGHT);
+ }}
+ onMouseOut={(event: Event) => {
+ event.preventDefault();
+ setRemoveHoverColor(DEFAULT_REMOVE_HOVER_COLOR);
+ }}
+ >
+
+
+
+ >
+ ) : (
+ 'Drop CSV file here or click to upload'
+ )}
+
+ >
+ )}
+
+ );
+}`}
@@ -239,40 +325,166 @@ export default class CSVReader extends Component {
- {`import React, { Component } from 'react'
+ {`import React, { useState, CSSProperties } from 'react';
-import { CSVReader } from 'react-papaparse'
+import {
+ useCSVReader,
+ lightenDarkenColor,
+ formatFileSize,
+} from 'react-papaparse';
-export default class CSVReader extends Component {
- handleOnDrop = (data) => {
- console.log('---------------------------')
- console.log(data)
- console.log('---------------------------')
- }
+const GREY = '#CCC';
+const GREY_LIGHT = 'rgba(255, 255, 255, 0.4)';
+const DEFAULT_REMOVE_HOVER_COLOR = '#A01919';
+const REMOVE_HOVER_COLOR_LIGHT = lightenDarkenColor(
+ DEFAULT_REMOVE_HOVER_COLOR,
+ 40
+);
+const GREY_DIM = '#686868';
- handleOnError = (err, file, inputElem, reason) => {
- console.log(err)
- }
+const styles = {
+ zone: {
+ alignItems: 'center',
+ borderWidth: 2,
+ borderStyle: 'dashed',
+ borderColor: GREY,
+ borderRadius: 20,
+ display: 'flex',
+ flexDirection: 'column',
+ height: '100%',
+ justifyContent: 'center',
+ padding: 20,
+ } as CSSProperties,
+ file: {
+ background: 'linear-gradient(to bottom, #EEE, #DDD)',
+ borderRadius: 20,
+ display: 'flex',
+ height: 120,
+ width: 120,
+ position: 'relative',
+ zIndex: 10,
+ flexDirection: 'column',
+ justifyContent: 'center',
+ } as CSSProperties,
+ info: {
+ alignItems: 'center',
+ display: 'flex',
+ flexDirection: 'column',
+ paddingLeft: 10,
+ paddingRight: 10,
+ } as CSSProperties,
+ size: {
+ backgroundColor: GREY_LIGHT,
+ borderRadius: 3,
+ marginBottom: '0.5em',
+ justifyContent: 'center',
+ display: 'flex',
+ } as CSSProperties,
+ name: {
+ backgroundColor: GREY_LIGHT,
+ borderRadius: 3,
+ fontSize: 12,
+ marginBottom: '0.5em',
+ } as CSSProperties,
+ progressBar: {
+ bottom: 14,
+ position: 'absolute',
+ width: '100%',
+ paddingLeft: 10,
+ paddingRight: 10,
+ } as CSSProperties,
+ zoneHover: {
+ borderColor: GREY_DIM,
+ } as CSSProperties,
+ default: {
+ borderColor: GREY,
+ } as CSSProperties,
+ remove: {
+ height: 23,
+ position: 'absolute',
+ right: 6,
+ top: 6,
+ width: 23,
+ } as CSSProperties,
+};
- handleOnRemoveFile = (data) => {
- console.log('---------------------------')
- console.log(data)
- console.log('---------------------------')
- }
+export default function CSVReader() {
+ const { CSVReader } = useCSVReader();
+ const [zoneHover, setZoneHover] = useState(false);
+ const [removeHoverColor, setRemoveHoverColor] = useState(
+ DEFAULT_REMOVE_HOVER_COLOR
+ );
- render() {
- return (
-
- Drop CSV file here to upload.
-
- )
- }
+ return (
+ {
+ console.log('---------------------------');
+ console.log(results);
+ console.log('---------------------------');
+ setZoneHover(false);
+ }}
+ onDragOver={(event: DragEvent) => {
+ event.preventDefault();
+ setZoneHover(true);
+ }}
+ onDragLeave={(event: DragEvent) => {
+ event.preventDefault();
+ setZoneHover(false);
+ }}
+ noClick
+ >
+ {({
+ getRootProps,
+ acceptedFile,
+ ProgressBar,
+ getRemoveFileProps,
+ Remove,
+ }: any) => (
+ <>
+
+ {acceptedFile ? (
+ <>
+
+
+
+ {formatFileSize(acceptedFile.size)}
+
+ {acceptedFile.name}
+
+
+
{
+ event.preventDefault();
+ setRemoveHoverColor(REMOVE_HOVER_COLOR_LIGHT);
+ }}
+ onMouseOut={(event: Event) => {
+ event.preventDefault();
+ setRemoveHoverColor(DEFAULT_REMOVE_HOVER_COLOR);
+ }}
+ >
+
+
+
+ >
+ ) : (
+ 'Drop CSV file here to upload'
+ )}
+
+ >
+ )}
+
+ );
}`}
@@ -304,40 +516,166 @@ export default class CSVReader extends Component {
- {`import React, { Component } from 'react'
+ {`import React, { useState, CSSProperties } from 'react';
-import { CSVReader } from 'react-papaparse'
+import {
+ useCSVReader,
+ lightenDarkenColor,
+ formatFileSize,
+} from 'react-papaparse';
-export default class CSVReader extends Component {
- handleOnDrop = (data) => {
- console.log('---------------------------')
- console.log(data)
- console.log('---------------------------')
- }
+const GREY = '#CCC';
+const GREY_LIGHT = 'rgba(255, 255, 255, 0.4)';
+const DEFAULT_REMOVE_HOVER_COLOR = '#A01919';
+const REMOVE_HOVER_COLOR_LIGHT = lightenDarkenColor(
+ DEFAULT_REMOVE_HOVER_COLOR,
+ 40
+);
+const GREY_DIM = '#686868';
- handleOnError = (err, file, inputElem, reason) => {
- console.log(err)
- }
+const styles = {
+ zone: {
+ alignItems: 'center',
+ borderWidth: 2,
+ borderStyle: 'dashed',
+ borderColor: GREY,
+ borderRadius: 20,
+ display: 'flex',
+ flexDirection: 'column',
+ height: '100%',
+ justifyContent: 'center',
+ padding: 20,
+ } as CSSProperties,
+ file: {
+ background: 'linear-gradient(to bottom, #EEE, #DDD)',
+ borderRadius: 20,
+ display: 'flex',
+ height: 120,
+ width: 120,
+ position: 'relative',
+ zIndex: 10,
+ flexDirection: 'column',
+ justifyContent: 'center',
+ } as CSSProperties,
+ info: {
+ alignItems: 'center',
+ display: 'flex',
+ flexDirection: 'column',
+ paddingLeft: 10,
+ paddingRight: 10,
+ } as CSSProperties,
+ size: {
+ backgroundColor: GREY_LIGHT,
+ borderRadius: 3,
+ marginBottom: '0.5em',
+ justifyContent: 'center',
+ display: 'flex',
+ } as CSSProperties,
+ name: {
+ backgroundColor: GREY_LIGHT,
+ borderRadius: 3,
+ fontSize: 12,
+ marginBottom: '0.5em',
+ } as CSSProperties,
+ progressBar: {
+ bottom: 14,
+ position: 'absolute',
+ width: '100%',
+ paddingLeft: 10,
+ paddingRight: 10,
+ } as CSSProperties,
+ zoneHover: {
+ borderColor: GREY_DIM,
+ } as CSSProperties,
+ default: {
+ borderColor: GREY,
+ } as CSSProperties,
+ remove: {
+ height: 23,
+ position: 'absolute',
+ right: 6,
+ top: 6,
+ width: 23,
+ } as CSSProperties,
+};
- handleOnRemoveFile = (data) => {
- console.log('---------------------------')
- console.log(data)
- console.log('---------------------------')
- }
+export default function CSVReader() {
+ const { CSVReader } = useCSVReader();
+ const [zoneHover, setZoneHover] = useState(false);
+ const [removeHoverColor, setRemoveHoverColor] = useState(
+ DEFAULT_REMOVE_HOVER_COLOR
+ );
- render() {
- return (
-
- Click to upload.
-
- )
- }
+ return (
+ {
+ console.log('---------------------------');
+ console.log(results);
+ console.log('---------------------------');
+ setZoneHover(false);
+ }}
+ onDragOver={(event: DragEvent) => {
+ event.preventDefault();
+ setZoneHover(true);
+ }}
+ onDragLeave={(event: DragEvent) => {
+ event.preventDefault();
+ setZoneHover(false);
+ }}
+ noDrag
+ >
+ {({
+ getRootProps,
+ acceptedFile,
+ ProgressBar,
+ getRemoveFileProps,
+ Remove,
+ }: any) => (
+ <>
+
+ {acceptedFile ? (
+ <>
+
+
+
+ {formatFileSize(acceptedFile.size)}
+
+ {acceptedFile.name}
+
+
+
{
+ event.preventDefault();
+ setRemoveHoverColor(REMOVE_HOVER_COLOR_LIGHT);
+ }}
+ onMouseOut={(event: Event) => {
+ event.preventDefault();
+ setRemoveHoverColor(DEFAULT_REMOVE_HOVER_COLOR);
+ }}
+ >
+
+
+
+ >
+ ) : (
+ 'Click to upload'
+ )}
+
+ >
+ )}
+
+ );
}`}
diff --git a/docs/src/components/screens/indexes/Navbar.js b/docs/src/components/screens/indexes/Navbar.js
index 54680ea..3440e93 100644
--- a/docs/src/components/screens/indexes/Navbar.js
+++ b/docs/src/components/screens/indexes/Navbar.js
@@ -17,7 +17,7 @@ const Navbar = () => {
@@ -32,7 +32,7 @@ const Navbar = () => {
>
diff --git a/docs/src/components/screens/indexes/RemoteFile.js b/docs/src/components/screens/indexes/RemoteFile.js
index 67c592c..1bbe9f8 100644
--- a/docs/src/components/screens/indexes/RemoteFile.js
+++ b/docs/src/components/screens/indexes/RemoteFile.js
@@ -13,12 +13,25 @@ const RemoteFile = () => {
- {`readRemoteFile('http://example.com/file.csv', {
- download: true,
- complete: (results) => {
- console.log(results)
- }
-})`}
+ {`import React from 'react';
+
+import { usePapaParse } from 'react-papaparse';
+
+export default function ReadRemoteFile() {
+ const { readRemoteFile } = usePapaParse();
+
+ const handleReadRemoteFile = () => {
+ readRemoteFile(url, {
+ complete: (results) => {
+ console.log('---------------------------');
+ console.log('Results:', results);
+ console.log('---------------------------');
+ },
+ });
+ };
+
+ return handleReadRemoteFile()}>readRemoteFile ;
+}`}
diff --git a/docs/src/components/screens/indexes/Stream.js b/docs/src/components/screens/indexes/Stream.js
index 19fb9f6..3e44b9f 100644
--- a/docs/src/components/screens/indexes/Stream.js
+++ b/docs/src/components/screens/indexes/Stream.js
@@ -14,14 +14,14 @@ const Stream = () => {
- {`readRemoteFile('http://example.com/big.csv', {
+ {`readRemoteFile(url, {
step: (row) => {
console.log('Row:', row.data)
},
complete: () => {
console.log('All done!')
}
-})`}
+});`}
diff --git a/docs/src/components/screens/indexes/TypeConversion.js b/docs/src/components/screens/indexes/TypeConversion.js
index 42a3428..512db77 100644
--- a/docs/src/components/screens/indexes/TypeConversion.js
+++ b/docs/src/components/screens/indexes/TypeConversion.js
@@ -15,7 +15,11 @@ const TypeConversion = () => {
{`// Converts numeric/boolean data
-const results = readString(csvString {
+readString(csvString, {
+ worker: true,
+ complete: (results) => {
+ console.log(results)
+ },
dynamicTyping: true
})`}
diff --git a/docs/src/components/screens/indexes/Unparse.js b/docs/src/components/screens/indexes/Unparse.js
index 02d0926..57c19c5 100644
--- a/docs/src/components/screens/indexes/Unparse.js
+++ b/docs/src/components/screens/indexes/Unparse.js
@@ -14,8 +14,49 @@ const Unparse = () => {
{`// Output is a properly-formatted CSV string.
-const csv = jsonToCSV(jsonData)
-`}
+
+import React from 'react';
+
+import { usePapaParse } from 'react-papaparse';
+
+export default function JsonToCSV() {
+ const { jsonToCSV } = usePapaParse();
+
+ const handleJsonToCSV = () => {
+ const jsonData = '[
+ {
+ "Column 1": "1-1",
+ "Column 2": "1-2",
+ "Column 3": "1-3",
+ "Column 4": "1-4"
+ },
+ {
+ "Column 1": "2-1",
+ "Column 2": "2-2",
+ "Column 3": "2-3",
+ "Column 4": "2-4"
+ },
+ {
+ "Column 1": "3-1",
+ "Column 2": "3-2",
+ "Column 3": "3-3",
+ "Column 4": "3-4"
+ },
+ {
+ "Column 1": 4,
+ "Column 2": 5,
+ "Column 3": 6,
+ "Column 4": 7
+ }
+ ]';
+ const results = jsonToCSV(jsonData);
+ console.log('---------------------------');
+ console.log('Results:', results);
+ console.log('---------------------------');
+ };
+
+ return handleJsonToCSV()}>jsonToCSV ;
+}`}
diff --git a/docs/src/components/screens/indexes/Welcome.js b/docs/src/components/screens/indexes/Welcome.js
index 1dce0d6..c855600 100644
--- a/docs/src/components/screens/indexes/Welcome.js
+++ b/docs/src/components/screens/indexes/Welcome.js
@@ -29,11 +29,16 @@ const Welcome = () => {
Documentation
-
+ {/*
{`// Parse CSV string
-const data = readString(csvString)
+readString(csvString, {
+ worker: true,
+ complete: (results) => {
+ console.log(results)
+ }
+})
// Convert back to CSV
const csv = jsonToCSV(jsonData)
@@ -70,7 +75,7 @@ readRemoteFile(bigFileURL, {
})`}
-
+
*/}
diff --git a/docs/src/components/screens/indexes/Worker.js b/docs/src/components/screens/indexes/Worker.js
index 19c8ccd..059c47b 100644
--- a/docs/src/components/screens/indexes/Worker.js
+++ b/docs/src/components/screens/indexes/Worker.js
@@ -26,7 +26,7 @@ const Worker = () => {
complete: () => {
console.log('All done!')
}
-})`}
+});`}
diff --git a/docs/static/css/home.css b/docs/static/css/home.css
index 580086c..85c31e8 100644
--- a/docs/static/css/home.css
+++ b/docs/static/css/home.css
@@ -1,5 +1,6 @@
#top {
- padding: 6% 0 8%;
+ /*padding: 6% 0 8%;*/
+ padding: 8% 0 15%;
color: #1D80AB;
background: #FFF;
position: relative;
@@ -176,7 +177,8 @@ footer {
@media (max-width: 1150px) {
#title-main h1 {
- font-size: 5.41vw;
+ /*font-size: 5.41vw;*/
+ font-size: 7.41vw;
letter-spacing: -2px;
}
@@ -216,9 +218,9 @@ footer {
display: none;
}
- #top {
+ /*#top {
padding-top: 0;
- }
+ }*/
#title-main,
#title-main h1,
diff --git a/docs/static/js/prism.js b/docs/static/js/prism.js
deleted file mode 100644
index f5a096e..0000000
--- a/docs/static/js/prism.js
+++ /dev/null
@@ -1,7 +0,0 @@
-/* PrismJS 1.19.0
-https://prismjs.com/download.html#themes=prism&languages=markup+css+clike+javascript */
-var _self="undefined"!=typeof window?window:"undefined"!=typeof WorkerGlobalScope&&self instanceof WorkerGlobalScope?self:{},Prism=function(u){var c=/\blang(?:uage)?-([\w-]+)\b/i,n=0,C={manual:u.Prism&&u.Prism.manual,disableWorkerMessageHandler:u.Prism&&u.Prism.disableWorkerMessageHandler,util:{encode:function(e){return e instanceof _?new _(e.type,C.util.encode(e.content),e.alias):Array.isArray(e)?e.map(C.util.encode):e.replace(/&/g,"&").replace(/e.length)return;if(!(k instanceof _)){if(h&&y!=n.length-1){if(c.lastIndex=v,!(O=c.exec(e)))break;for(var b=O.index+(f&&O[1]?O[1].length:0),w=O.index+O[0].length,A=y,P=v,x=n.length;A"+r.content+""+r.tag+">"},!u.document)return u.addEventListener&&(C.disableWorkerMessageHandler||u.addEventListener("message",function(e){var n=JSON.parse(e.data),r=n.language,t=n.code,a=n.immediateClose;u.postMessage(C.highlight(t,C.languages[r],r)),a&&u.close()},!1)),C;var e=C.util.currentScript();if(e&&(C.filename=e.src,e.hasAttribute("data-manual")&&(C.manual=!0)),!C.manual){function r(){C.manual||C.highlightAll()}var t=document.readyState;"loading"===t||"interactive"===t&&e&&e.defer?document.addEventListener("DOMContentLoaded",r):window.requestAnimationFrame?window.requestAnimationFrame(r):window.setTimeout(r,16)}return C}(_self);"undefined"!=typeof module&&module.exports&&(module.exports=Prism),"undefined"!=typeof global&&(global.Prism=Prism);
-Prism.languages.markup={comment://,prolog:/<\?[\s\S]+?\?>/,doctype:{pattern:/"'[\]]|"[^"]*"|'[^']*')+(?:\[(?:(?!)*\]\s*)?>/i,greedy:!0},cdata://i,tag:{pattern:/<\/?(?!\d)[^\s>\/=$<%]+(?:\s(?:\s*[^\s>\/=]+(?:\s*=\s*(?:"[^"]*"|'[^']*'|[^\s'">=]+(?=[\s>]))|(?=[\s/>])))+)?\s*\/?>/i,greedy:!0,inside:{tag:{pattern:/^<\/?[^\s>\/]+/i,inside:{punctuation:/^<\/?/,namespace:/^[^\s>\/:]+:/}},"attr-value":{pattern:/=\s*(?:"[^"]*"|'[^']*'|[^\s'">=]+)/i,inside:{punctuation:[/^=/,{pattern:/^(\s*)["']|["']$/,lookbehind:!0}]}},punctuation:/\/?>/,"attr-name":{pattern:/[^\s>\/]+/,inside:{namespace:/^[^\s>\/:]+:/}}}},entity:/?[\da-z]{1,8};/i},Prism.languages.markup.tag.inside["attr-value"].inside.entity=Prism.languages.markup.entity,Prism.hooks.add("wrap",function(a){"entity"===a.type&&(a.attributes.title=a.content.replace(/&/,"&"))}),Object.defineProperty(Prism.languages.markup.tag,"addInlined",{value:function(a,e){var s={};s["language-"+e]={pattern:/(^$)/i,lookbehind:!0,inside:Prism.languages[e]},s.cdata=/^$/i;var n={"included-cdata":{pattern://i,inside:s}};n["language-"+e]={pattern:/[\s\S]+/,inside:Prism.languages[e]};var t={};t[a]={pattern:RegExp("(<__[\\s\\S]*?>)(?:\\s*|[\\s\\S])*?(?=<\\/__>)".replace(/__/g,a),"i"),lookbehind:!0,greedy:!0,inside:n},Prism.languages.insertBefore("markup","cdata",t)}}),Prism.languages.xml=Prism.languages.extend("markup",{}),Prism.languages.html=Prism.languages.markup,Prism.languages.mathml=Prism.languages.markup,Prism.languages.svg=Prism.languages.markup;
-!function(s){var t=/("|')(?:\\(?:\r\n|[\s\S])|(?!\1)[^\\\r\n])*\1/;s.languages.css={comment:/\/\*[\s\S]*?\*\//,atrule:{pattern:/@[\w-]+[\s\S]*?(?:;|(?=\s*\{))/,inside:{rule:/@[\w-]+/}},url:{pattern:RegExp("url\\((?:"+t.source+"|[^\n\r()]*)\\)","i"),inside:{function:/^url/i,punctuation:/^\(|\)$/}},selector:RegExp("[^{}\\s](?:[^{};\"']|"+t.source+")*?(?=\\s*\\{)"),string:{pattern:t,greedy:!0},property:/[-_a-z\xA0-\uFFFF][-\w\xA0-\uFFFF]*(?=\s*:)/i,important:/!important\b/i,function:/[-a-z0-9]+(?=\()/i,punctuation:/[(){};:,]/},s.languages.css.atrule.inside.rest=s.languages.css;var e=s.languages.markup;e&&(e.tag.addInlined("style","css"),s.languages.insertBefore("inside","attr-value",{"style-attr":{pattern:/\s*style=("|')(?:\\[\s\S]|(?!\1)[^\\])*\1/i,inside:{"attr-name":{pattern:/^\s*style/i,inside:e.tag.inside},punctuation:/^\s*=\s*['"]|['"]\s*$/,"attr-value":{pattern:/.+/i,inside:s.languages.css}},alias:"language-css"}},e.tag))}(Prism);
-Prism.languages.clike={comment:[{pattern:/(^|[^\\])\/\*[\s\S]*?(?:\*\/|$)/,lookbehind:!0},{pattern:/(^|[^\\:])\/\/.*/,lookbehind:!0,greedy:!0}],string:{pattern:/(["'])(?:\\(?:\r\n|[\s\S])|(?!\1)[^\\\r\n])*\1/,greedy:!0},"class-name":{pattern:/(\b(?:class|interface|extends|implements|trait|instanceof|new)\s+|\bcatch\s+\()[\w.\\]+/i,lookbehind:!0,inside:{punctuation:/[.\\]/}},keyword:/\b(?:if|else|while|do|for|return|in|instanceof|function|new|try|throw|catch|finally|null|break|continue)\b/,boolean:/\b(?:true|false)\b/,function:/\w+(?=\()/,number:/\b0x[\da-f]+\b|(?:\b\d+\.?\d*|\B\.\d+)(?:e[+-]?\d+)?/i,operator:/[<>]=?|[!=]=?=?|--?|\+\+?|&&?|\|\|?|[?*/~^%]/,punctuation:/[{}[\];(),.:]/};
-Prism.languages.javascript=Prism.languages.extend("clike",{"class-name":[Prism.languages.clike["class-name"],{pattern:/(^|[^$\w\xA0-\uFFFF])[_$A-Z\xA0-\uFFFF][$\w\xA0-\uFFFF]*(?=\.(?:prototype|constructor))/,lookbehind:!0}],keyword:[{pattern:/((?:^|})\s*)(?:catch|finally)\b/,lookbehind:!0},{pattern:/(^|[^.]|\.\.\.\s*)\b(?:as|async(?=\s*(?:function\b|\(|[$\w\xA0-\uFFFF]|$))|await|break|case|class|const|continue|debugger|default|delete|do|else|enum|export|extends|for|from|function|get|if|implements|import|in|instanceof|interface|let|new|null|of|package|private|protected|public|return|set|static|super|switch|this|throw|try|typeof|undefined|var|void|while|with|yield)\b/,lookbehind:!0}],number:/\b(?:(?:0[xX](?:[\dA-Fa-f](?:_[\dA-Fa-f])?)+|0[bB](?:[01](?:_[01])?)+|0[oO](?:[0-7](?:_[0-7])?)+)n?|(?:\d(?:_\d)?)+n|NaN|Infinity)\b|(?:\b(?:\d(?:_\d)?)+\.?(?:\d(?:_\d)?)*|\B\.(?:\d(?:_\d)?)+)(?:[Ee][+-]?(?:\d(?:_\d)?)+)?/,function:/#?[_$a-zA-Z\xA0-\uFFFF][$\w\xA0-\uFFFF]*(?=\s*(?:\.\s*(?:apply|bind|call)\s*)?\()/,operator:/--|\+\+|\*\*=?|=>|&&|\|\||[!=]==|<<=?|>>>?=?|[-+*/%&|^!=<>]=?|\.{3}|\?[.?]?|[~:]/}),Prism.languages.javascript["class-name"][0].pattern=/(\b(?:class|interface|extends|implements|instanceof|new)\s+)[\w.\\]+/,Prism.languages.insertBefore("javascript","keyword",{regex:{pattern:/((?:^|[^$\w\xA0-\uFFFF."'\])\s])\s*)\/(?:\[(?:[^\]\\\r\n]|\\.)*]|\\.|[^/\\\[\r\n])+\/[gimyus]{0,6}(?=(?:\s|\/\*[\s\S]*?\*\/)*(?:$|[\r\n,.;:})\]]|\/\/))/,lookbehind:!0,greedy:!0},"function-variable":{pattern:/#?[_$a-zA-Z\xA0-\uFFFF][$\w\xA0-\uFFFF]*(?=\s*[=:]\s*(?:async\s*)?(?:\bfunction\b|(?:\((?:[^()]|\([^()]*\))*\)|[_$a-zA-Z\xA0-\uFFFF][$\w\xA0-\uFFFF]*)\s*=>))/,alias:"function"},parameter:[{pattern:/(function(?:\s+[_$A-Za-z\xA0-\uFFFF][$\w\xA0-\uFFFF]*)?\s*\(\s*)(?!\s)(?:[^()]|\([^()]*\))+?(?=\s*\))/,lookbehind:!0,inside:Prism.languages.javascript},{pattern:/[_$a-z\xA0-\uFFFF][$\w\xA0-\uFFFF]*(?=\s*=>)/i,inside:Prism.languages.javascript},{pattern:/(\(\s*)(?!\s)(?:[^()]|\([^()]*\))+?(?=\s*\)\s*=>)/,lookbehind:!0,inside:Prism.languages.javascript},{pattern:/((?:\b|\s|^)(?!(?:as|async|await|break|case|catch|class|const|continue|debugger|default|delete|do|else|enum|export|extends|finally|for|from|function|get|if|implements|import|in|instanceof|interface|let|new|null|of|package|private|protected|public|return|set|static|super|switch|this|throw|try|typeof|undefined|var|void|while|with|yield)(?![$\w\xA0-\uFFFF]))(?:[_$A-Za-z\xA0-\uFFFF][$\w\xA0-\uFFFF]*\s*)\(\s*)(?!\s)(?:[^()]|\([^()]*\))+?(?=\s*\)\s*\{)/,lookbehind:!0,inside:Prism.languages.javascript}],constant:/\b[A-Z](?:[A-Z_]|\dx?)*\b/}),Prism.languages.insertBefore("javascript","string",{"template-string":{pattern:/`(?:\\[\s\S]|\${(?:[^{}]|{(?:[^{}]|{[^}]*})*})+}|(?!\${)[^\\`])*`/,greedy:!0,inside:{"template-punctuation":{pattern:/^`|`$/,alias:"string"},interpolation:{pattern:/((?:^|[^\\])(?:\\{2})*)\${(?:[^{}]|{(?:[^{}]|{[^}]*})*})+}/,lookbehind:!0,inside:{"interpolation-punctuation":{pattern:/^\${|}$/,alias:"punctuation"},rest:Prism.languages.javascript}},string:/[\s\S]+/}}}),Prism.languages.markup&&Prism.languages.markup.tag.addInlined("script","javascript"),Prism.languages.js=Prism.languages.javascript;
diff --git a/examples/CSVDownloader.tsx b/examples/CSVDownloader.tsx
new file mode 100644
index 0000000..ea0dee0
--- /dev/null
+++ b/examples/CSVDownloader.tsx
@@ -0,0 +1,46 @@
+import React from 'react';
+
+import { useCSVDownloader } from 'react-papaparse';
+
+export default function CSVDownloader() {
+ const { CSVDownloader, Type } = useCSVDownloader();
+
+ return (
+
+ Download
+
+ );
+}
diff --git a/examples/CSVReaderBasicUpload.tsx b/examples/CSVReaderBasicUpload.tsx
new file mode 100644
index 0000000..d791f7b
--- /dev/null
+++ b/examples/CSVReaderBasicUpload.tsx
@@ -0,0 +1,64 @@
+import React, { CSSProperties } from 'react';
+
+import { useCSVReader } from 'react-papaparse';
+
+const styles = {
+ csvReader: {
+ display: 'flex',
+ flexDirection: 'row',
+ marginBottom: 10,
+ } as CSSProperties,
+ browseFile: {
+ width: '20%',
+ } as CSSProperties,
+ acceptedFile: {
+ border: '1px solid #ccc',
+ height: 45,
+ lineHeight: 2.5,
+ paddingLeft: 10,
+ width: '80%',
+ } as CSSProperties,
+ remove: {
+ borderRadius: 0,
+ padding: '0 20px',
+ } as CSSProperties,
+ progressBarBackgroundColor: {
+ backgroundColor: 'red',
+ } as CSSProperties,
+};
+
+export default function CSVReader() {
+ const { CSVReader } = useCSVReader();
+
+ return (
+ {
+ console.log('---------------------------');
+ console.log(results);
+ console.log('---------------------------');
+ }}
+ >
+ {({
+ getRootProps,
+ acceptedFile,
+ ProgressBar,
+ getRemoveFileProps,
+ }: any) => (
+ <>
+
+
+ Browse file
+
+
+ {acceptedFile && acceptedFile.name}
+
+
+ Remove
+
+
+
+ >
+ )}
+
+ );
+}
diff --git a/examples/CSVReaderClickAndDragUpload.tsx b/examples/CSVReaderClickAndDragUpload.tsx
new file mode 100644
index 0000000..bc527ed
--- /dev/null
+++ b/examples/CSVReaderClickAndDragUpload.tsx
@@ -0,0 +1,158 @@
+import React, { useState, CSSProperties } from 'react';
+
+import {
+ useCSVReader,
+ lightenDarkenColor,
+ formatFileSize,
+} from 'react-papaparse';
+
+const GREY = '#CCC';
+const GREY_LIGHT = 'rgba(255, 255, 255, 0.4)';
+const DEFAULT_REMOVE_HOVER_COLOR = '#A01919';
+const REMOVE_HOVER_COLOR_LIGHT = lightenDarkenColor(
+ DEFAULT_REMOVE_HOVER_COLOR,
+ 40
+);
+const GREY_DIM = '#686868';
+
+const styles = {
+ zone: {
+ alignItems: 'center',
+ border: `2px dashed ${GREY}`,
+ borderRadius: 20,
+ display: 'flex',
+ flexDirection: 'column',
+ height: '100%',
+ justifyContent: 'center',
+ padding: 20,
+ } as CSSProperties,
+ file: {
+ background: 'linear-gradient(to bottom, #EEE, #DDD)',
+ borderRadius: 20,
+ display: 'flex',
+ height: 120,
+ width: 120,
+ position: 'relative',
+ zIndex: 10,
+ flexDirection: 'column',
+ justifyContent: 'center',
+ } as CSSProperties,
+ info: {
+ alignItems: 'center',
+ display: 'flex',
+ flexDirection: 'column',
+ paddingLeft: 10,
+ paddingRight: 10,
+ } as CSSProperties,
+ size: {
+ backgroundColor: GREY_LIGHT,
+ borderRadius: 3,
+ marginBottom: '0.5em',
+ justifyContent: 'center',
+ display: 'flex',
+ } as CSSProperties,
+ name: {
+ backgroundColor: GREY_LIGHT,
+ borderRadius: 3,
+ fontSize: 12,
+ marginBottom: '0.5em',
+ } as CSSProperties,
+ progressBar: {
+ bottom: 14,
+ position: 'absolute',
+ width: '100%',
+ paddingLeft: 10,
+ paddingRight: 10,
+ } as CSSProperties,
+ zoneHover: {
+ borderColor: GREY_DIM,
+ } as CSSProperties,
+ default: {
+ borderColor: GREY,
+ } as CSSProperties,
+ remove: {
+ height: 23,
+ position: 'absolute',
+ right: 6,
+ top: 6,
+ width: 23,
+ } as CSSProperties,
+};
+
+export default function CSVReader() {
+ const { CSVReader } = useCSVReader();
+ const [zoneHover, setZoneHover] = useState(false);
+ const [removeHoverColor, setRemoveHoverColor] = useState(
+ DEFAULT_REMOVE_HOVER_COLOR
+ );
+
+ return (
+ {
+ console.log('---------------------------');
+ console.log(results);
+ console.log('---------------------------');
+ setZoneHover(false);
+ }}
+ onDragOver={(event: DragEvent) => {
+ event.preventDefault();
+ setZoneHover(true);
+ }}
+ onDragLeave={(event: DragEvent) => {
+ event.preventDefault();
+ setZoneHover(false);
+ }}
+ >
+ {({
+ getRootProps,
+ acceptedFile,
+ ProgressBar,
+ getRemoveFileProps,
+ Remove,
+ }: any) => (
+ <>
+
+ {acceptedFile ? (
+ <>
+
+
+
+ {formatFileSize(acceptedFile.size)}
+
+ {acceptedFile.name}
+
+
+
{
+ event.preventDefault();
+ setRemoveHoverColor(REMOVE_HOVER_COLOR_LIGHT);
+ }}
+ onMouseOut={(event: Event) => {
+ event.preventDefault();
+ setRemoveHoverColor(DEFAULT_REMOVE_HOVER_COLOR);
+ }}
+ >
+
+
+
+ >
+ ) : (
+ 'Drop CSV file here or click to upload'
+ )}
+
+ >
+ )}
+
+ );
+}
diff --git a/examples/CSVReaderClickNoDragUpload.tsx b/examples/CSVReaderClickNoDragUpload.tsx
new file mode 100644
index 0000000..851823c
--- /dev/null
+++ b/examples/CSVReaderClickNoDragUpload.tsx
@@ -0,0 +1,159 @@
+import React, { useState, CSSProperties } from 'react';
+
+import {
+ useCSVReader,
+ lightenDarkenColor,
+ formatFileSize,
+} from 'react-papaparse';
+
+const GREY = '#CCC';
+const GREY_LIGHT = 'rgba(255, 255, 255, 0.4)';
+const DEFAULT_REMOVE_HOVER_COLOR = '#A01919';
+const REMOVE_HOVER_COLOR_LIGHT = lightenDarkenColor(
+ DEFAULT_REMOVE_HOVER_COLOR,
+ 40
+);
+const GREY_DIM = '#686868';
+
+const styles = {
+ zone: {
+ alignItems: 'center',
+ border: `2px dashed ${GREY}`,
+ borderRadius: 20,
+ display: 'flex',
+ flexDirection: 'column',
+ height: '100%',
+ justifyContent: 'center',
+ padding: 20,
+ } as CSSProperties,
+ file: {
+ background: 'linear-gradient(to bottom, #EEE, #DDD)',
+ borderRadius: 20,
+ display: 'flex',
+ height: 120,
+ width: 120,
+ position: 'relative',
+ zIndex: 10,
+ flexDirection: 'column',
+ justifyContent: 'center',
+ } as CSSProperties,
+ info: {
+ alignItems: 'center',
+ display: 'flex',
+ flexDirection: 'column',
+ paddingLeft: 10,
+ paddingRight: 10,
+ } as CSSProperties,
+ size: {
+ backgroundColor: GREY_LIGHT,
+ borderRadius: 3,
+ marginBottom: '0.5em',
+ justifyContent: 'center',
+ display: 'flex',
+ } as CSSProperties,
+ name: {
+ backgroundColor: GREY_LIGHT,
+ borderRadius: 3,
+ fontSize: 12,
+ marginBottom: '0.5em',
+ } as CSSProperties,
+ progressBar: {
+ bottom: 14,
+ position: 'absolute',
+ width: '100%',
+ paddingLeft: 10,
+ paddingRight: 10,
+ } as CSSProperties,
+ zoneHover: {
+ borderColor: GREY_DIM,
+ } as CSSProperties,
+ default: {
+ borderColor: GREY,
+ } as CSSProperties,
+ remove: {
+ height: 23,
+ position: 'absolute',
+ right: 6,
+ top: 6,
+ width: 23,
+ } as CSSProperties,
+};
+
+export default function CSVReader() {
+ const { CSVReader } = useCSVReader();
+ const [zoneHover, setZoneHover] = useState(false);
+ const [removeHoverColor, setRemoveHoverColor] = useState(
+ DEFAULT_REMOVE_HOVER_COLOR
+ );
+
+ return (
+ {
+ console.log('---------------------------');
+ console.log(results);
+ console.log('---------------------------');
+ setZoneHover(false);
+ }}
+ onDragOver={(event: DragEvent) => {
+ event.preventDefault();
+ setZoneHover(true);
+ }}
+ onDragLeave={(event: DragEvent) => {
+ event.preventDefault();
+ setZoneHover(false);
+ }}
+ noDrag
+ >
+ {({
+ getRootProps,
+ acceptedFile,
+ ProgressBar,
+ getRemoveFileProps,
+ Remove,
+ }: any) => (
+ <>
+
+ {acceptedFile ? (
+ <>
+
+
+
+ {formatFileSize(acceptedFile.size)}
+
+ {acceptedFile.name}
+
+
+
{
+ event.preventDefault();
+ setRemoveHoverColor(REMOVE_HOVER_COLOR_LIGHT);
+ }}
+ onMouseOut={(event: Event) => {
+ event.preventDefault();
+ setRemoveHoverColor(DEFAULT_REMOVE_HOVER_COLOR);
+ }}
+ >
+
+
+
+ >
+ ) : (
+ 'Click to upload'
+ )}
+
+ >
+ )}
+
+ );
+}
diff --git a/examples/CSVReaderDragNoClickUpload.tsx b/examples/CSVReaderDragNoClickUpload.tsx
new file mode 100644
index 0000000..dee2d53
--- /dev/null
+++ b/examples/CSVReaderDragNoClickUpload.tsx
@@ -0,0 +1,159 @@
+import React, { useState, CSSProperties } from 'react';
+
+import {
+ useCSVReader,
+ lightenDarkenColor,
+ formatFileSize,
+} from 'react-papaparse';
+
+const GREY = '#CCC';
+const GREY_LIGHT = 'rgba(255, 255, 255, 0.4)';
+const DEFAULT_REMOVE_HOVER_COLOR = '#A01919';
+const REMOVE_HOVER_COLOR_LIGHT = lightenDarkenColor(
+ DEFAULT_REMOVE_HOVER_COLOR,
+ 40
+);
+const GREY_DIM = '#686868';
+
+const styles = {
+ zone: {
+ alignItems: 'center',
+ border: `2px dashed ${GREY}`,
+ borderRadius: 20,
+ display: 'flex',
+ flexDirection: 'column',
+ height: '100%',
+ justifyContent: 'center',
+ padding: 20,
+ } as CSSProperties,
+ file: {
+ background: 'linear-gradient(to bottom, #EEE, #DDD)',
+ borderRadius: 20,
+ display: 'flex',
+ height: 120,
+ width: 120,
+ position: 'relative',
+ zIndex: 10,
+ flexDirection: 'column',
+ justifyContent: 'center',
+ } as CSSProperties,
+ info: {
+ alignItems: 'center',
+ display: 'flex',
+ flexDirection: 'column',
+ paddingLeft: 10,
+ paddingRight: 10,
+ } as CSSProperties,
+ size: {
+ backgroundColor: GREY_LIGHT,
+ borderRadius: 3,
+ marginBottom: '0.5em',
+ justifyContent: 'center',
+ display: 'flex',
+ } as CSSProperties,
+ name: {
+ backgroundColor: GREY_LIGHT,
+ borderRadius: 3,
+ fontSize: 12,
+ marginBottom: '0.5em',
+ } as CSSProperties,
+ progressBar: {
+ bottom: 14,
+ position: 'absolute',
+ width: '100%',
+ paddingLeft: 10,
+ paddingRight: 10,
+ } as CSSProperties,
+ zoneHover: {
+ borderColor: GREY_DIM,
+ } as CSSProperties,
+ default: {
+ borderColor: GREY,
+ } as CSSProperties,
+ remove: {
+ height: 23,
+ position: 'absolute',
+ right: 6,
+ top: 6,
+ width: 23,
+ } as CSSProperties,
+};
+
+export default function CSVReader() {
+ const { CSVReader } = useCSVReader();
+ const [zoneHover, setZoneHover] = useState(false);
+ const [removeHoverColor, setRemoveHoverColor] = useState(
+ DEFAULT_REMOVE_HOVER_COLOR
+ );
+
+ return (
+ {
+ console.log('---------------------------');
+ console.log(results);
+ console.log('---------------------------');
+ setZoneHover(false);
+ }}
+ onDragOver={(event: DragEvent) => {
+ event.preventDefault();
+ setZoneHover(true);
+ }}
+ onDragLeave={(event: DragEvent) => {
+ event.preventDefault();
+ setZoneHover(false);
+ }}
+ noClick
+ >
+ {({
+ getRootProps,
+ acceptedFile,
+ ProgressBar,
+ getRemoveFileProps,
+ Remove,
+ }: any) => (
+ <>
+
+ {acceptedFile ? (
+ <>
+
+
+
+ {formatFileSize(acceptedFile.size)}
+
+ {acceptedFile.name}
+
+
+
{
+ event.preventDefault();
+ setRemoveHoverColor(REMOVE_HOVER_COLOR_LIGHT);
+ }}
+ onMouseOut={(event: Event) => {
+ event.preventDefault();
+ setRemoveHoverColor(DEFAULT_REMOVE_HOVER_COLOR);
+ }}
+ >
+
+
+
+ >
+ ) : (
+ 'Drop CSV file here to upload'
+ )}
+
+ >
+ )}
+
+ );
+}
diff --git a/examples/jsonToCSV.tsx b/examples/jsonToCSV.tsx
new file mode 100644
index 0000000..4142af9
--- /dev/null
+++ b/examples/jsonToCSV.tsx
@@ -0,0 +1,42 @@
+import React from 'react';
+
+import { usePapaParse } from 'react-papaparse';
+
+export default function JsonToCSV() {
+ const { jsonToCSV } = usePapaParse();
+
+ const handleJsonToCSV = () => {
+ const jsonData = `[
+ {
+ "Column 1": "1-1",
+ "Column 2": "1-2",
+ "Column 3": "1-3",
+ "Column 4": "1-4"
+ },
+ {
+ "Column 1": "2-1",
+ "Column 2": "2-2",
+ "Column 3": "2-3",
+ "Column 4": "2-4"
+ },
+ {
+ "Column 1": "3-1",
+ "Column 2": "3-2",
+ "Column 3": "3-3",
+ "Column 4": "3-4"
+ },
+ {
+ "Column 1": 4,
+ "Column 2": 5,
+ "Column 3": 6,
+ "Column 4": 7
+ }
+ ]`;
+ const results = jsonToCSV(jsonData);
+ console.log('---------------------------');
+ console.log('Results:', results);
+ console.log('---------------------------');
+ };
+
+ return handleJsonToCSV()}>jsonToCSV ;
+}
diff --git a/examples/readRemoteFile.tsx b/examples/readRemoteFile.tsx
new file mode 100644
index 0000000..68ac5d7
--- /dev/null
+++ b/examples/readRemoteFile.tsx
@@ -0,0 +1,19 @@
+import React from 'react';
+
+import { usePapaParse } from 'react-papaparse';
+
+export default function ReadRemoteFile() {
+ const { readRemoteFile } = usePapaParse();
+
+ const handleReadRemoteFile = () => {
+ readRemoteFile('https://react-papaparse.js.org/static/csv/normal.csv', {
+ complete: (results) => {
+ console.log('---------------------------');
+ console.log('Results:', results);
+ console.log('---------------------------');
+ },
+ });
+ };
+
+ return handleReadRemoteFile()}>readRemoteFile ;
+}
diff --git a/examples/readString.tsx b/examples/readString.tsx
new file mode 100644
index 0000000..d631062
--- /dev/null
+++ b/examples/readString.tsx
@@ -0,0 +1,26 @@
+import React from 'react';
+
+import { usePapaParse } from 'react-papaparse';
+
+export default function ReadString() {
+ const { readString } = usePapaParse();
+
+ const handleReadString = () => {
+ const csvString = `Column 1,Column 2,Column 3,Column 4
+1-1,1-2,1-3,1-4
+2-1,2-2,2-3,2-4
+3-1,3-2,3-3,3-4
+4,5,6,7`;
+
+ readString(csvString, {
+ worker: true,
+ complete: (results) => {
+ console.log('---------------------------');
+ console.log(results);
+ console.log('---------------------------');
+ },
+ });
+ };
+
+ return handleReadString()}>readString ;
+}
diff --git a/package.json b/package.json
index 088cc54..5f3062c 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "react-papaparse",
- "version": "3.17.2",
+ "version": "4.0.2",
"description": "The fastest in-browser CSV (or delimited text) parser for React. It is full of useful features such as CSVReader, CSVDownloader, readString, jsonToCSV, readRemoteFile, ... etc.",
"author": "Bunlong ",
"license": "MIT",
@@ -67,7 +67,7 @@
"scripts": {
"test": "jest --runInBand",
"build": "rollup -c",
- "prettier": "prettier --write './src/*.ts' './src/*.tsx' './demo/*js' --config ./.prettierrc",
+ "prettier": "prettier --write './src/*.ts' './src/*.tsx' --config ./.prettierrc",
"lint:check": "eslint ./src --ext .tsx,.ts --report-unused-disable-directives",
"bundlesize": "npm run build && bundlesize",
"dev": "rollup -c -w",
@@ -81,8 +81,7 @@
"@babel/preset-env": "^7.15.0",
"@rollup/plugin-babel": "^5.3.0",
"@rollup/plugin-commonjs": "^20.0.0",
- "@rollup/plugin-node-resolve": "^13.0.4",
- "@types/jest": "^27.0.1",
+ "@types/jest": "^27.0.2",
"@types/react": "^17.0.19",
"@types/react-dom": "^17.0.9",
"@types/react-test-renderer": "^17.0.1",
@@ -92,6 +91,7 @@
"eslint": "^7.32.0",
"eslint-plugin-react": "^7.24.0",
"eslint-plugin-react-hooks": "^4.2.0",
+ "jschardet": "^3.0.0",
"jest": "^27.1.0",
"mutationobserver-shim": "^0.3.7",
"prettier": "^2.3.2",
@@ -109,7 +109,7 @@
"dist"
],
"dependencies": {
- "@types/papaparse": "^5.2.6",
+ "@types/papaparse": "^5.3.1",
"papaparse": "^5.3.1"
},
"bundlesize": [
@@ -119,7 +119,7 @@
},
{
"path": "./dist/*.ts",
- "maxSize": "750 B"
+ "maxSize": "1000 B"
}
]
}
diff --git a/rollup.config.js b/rollup.config.js
index 1422490..f6902b9 100644
--- a/rollup.config.js
+++ b/rollup.config.js
@@ -1,9 +1,13 @@
import typescript from 'rollup-plugin-typescript2';
import babel from '@rollup/plugin-babel';
import commonjs from '@rollup/plugin-commonjs';
-import resolve from '@rollup/plugin-node-resolve';
-import builtins from 'builtin-modules'
import { terser } from "rollup-plugin-terser";
+
+// ======= FOR BUILDING NODE.JS PACKAGE =======
+// import builtins from 'builtin-modules'
+// import resolve, { nodeResolve } from '@rollup/plugin-node-resolve';
+// ============================================
+
import pkg from './package.json';
export default {
@@ -35,7 +39,8 @@ export default {
// },
// },
],
- external: builtins,
+ // external: builtins,
+ external: ['react', 'papaparse'],
plugins: [
typescript({
tsconfig: './tsconfig.json',
@@ -44,9 +49,10 @@ export default {
babel({
exclude: 'node_modules/**'
}),
- resolve({
- preferBuiltins: true
- }),
+ // resolve({
+ // preferBuiltins: true
+ // }),
+ // nodeResolve(),
commonjs({
extensions: ['.js', '.ts', '.tsx']
}),
diff --git a/src/CSVDownloader.tsx b/src/CSVDownloader.tsx
deleted file mode 100644
index a7fb5e0..0000000
--- a/src/CSVDownloader.tsx
+++ /dev/null
@@ -1,96 +0,0 @@
-import React from 'react';
-import PapaParse, { ParseConfig } from 'papaparse';
-
-export const LINK_TYPE = 'link';
-export const BUTTON_TYPE = 'button';
-
-export interface Props {
- children: React.ReactNode;
- data: any;
- filename: string;
- type?: 'link' | 'button';
- style?: any;
- className?: string;
- bom?: boolean;
- config?: ParseConfig;
-}
-
-export default class CSVDownloader extends React.Component> {
- static defaultProps: Partial> = {
- type: LINK_TYPE,
- };
-
- // https://github.com/mholt/PapaParse/issues/175
- download = (
- data: any,
- filename: string,
- bom: boolean,
- config: ParseConfig,
- ): void => {
- const bomCode = bom ? '\ufeff' : '';
- let csvContent = null;
- let csvURL = null;
-
- if (typeof data === 'function') {
- data = data();
- }
-
- if (typeof data === 'object') {
- csvContent = PapaParse.unparse(data, config);
- } else {
- csvContent = data;
- }
-
- const csvData = new Blob([`${bomCode}${csvContent}`], {
- type: 'text/csv;charset=utf-8;',
- });
-
- const navObj: any = window.navigator;
- if (navObj.msSaveBlob) {
- csvURL = navObj.msSaveBlob(csvData, `${filename}.csv`);
- } else {
- csvURL = window.URL.createObjectURL(csvData);
- }
-
- const link = document.createElement('a');
- link.href = csvURL as string;
- link.setAttribute('download', `${filename}.csv`);
- link.click();
- link.remove();
- };
-
- render(): React.ReactNode {
- const {
- children,
- data,
- filename,
- type,
- className,
- style,
- bom = false,
- config = {},
- } = this.props;
-
- if (type === LINK_TYPE) {
- return (
- this.download(data, filename, bom, config)}
- className={className}
- style={style}
- >
- {children}
-
- );
- }
-
- return (
- this.download(data, filename, bom, config)}
- className={className}
- style={style}
- >
- {children}
-
- );
- }
-}
diff --git a/src/CSVReader.tsx b/src/CSVReader.tsx
deleted file mode 100644
index ae34fbf..0000000
--- a/src/CSVReader.tsx
+++ /dev/null
@@ -1,585 +0,0 @@
-import React, { CSSProperties } from 'react';
-import PapaParse, { ParseConfig, ParseResult } from 'papaparse';
-import getSize, { lightenDarkenColor } from './utils';
-import RemoveIcon from './RemoveIcon';
-import ProgressBar from './ProgressBar';
-
-const GREY = '#CCC';
-const GREY_LIGHT = 'rgba(255, 255, 255, 0.4)';
-const REMOVE_ICON_DEFAULT_COLOR = '#A01919';
-const GREY_DIM = '#686868';
-// 'text/csv' for MacOS
-// '.csv' for Linux
-// 'application/vnd.ms-excel' for Window 10
-const DEFAULT_ACCEPT = 'text/csv, .csv, application/vnd.ms-excel';
-
-const styles = {
- dropArea: {
- alignItems: 'center',
- borderStyle: 'dashed',
- borderWidth: 2,
- borderRadius: 20,
- borderColor: GREY,
- display: 'flex',
- flexDirection: 'column',
- height: '100%',
- justifyContent: 'center',
- padding: 20,
- } as CSSProperties,
- dropAreaDefaultBorderColor: {
- borderColor: GREY,
- },
- inputFile: {
- display: 'none',
- } as CSSProperties,
- highlight: {
- borderColor: GREY_DIM,
- },
- unhighlight: {
- borderColor: GREY,
- } as CSSProperties,
- dropFile: {
- background: 'linear-gradient(to bottom, #EEE, #DDD)',
- borderRadius: 20,
- display: 'block',
- height: 120,
- width: 100,
- paddingLeft: 10,
- paddingRight: 10,
- position: 'relative',
- zIndex: 10,
- } as CSSProperties,
- column: {
- alignItems: 'center',
- display: 'flex',
- flexDirection: 'column',
- justifyContent: 'center',
- } as CSSProperties,
- fileSizeInfo: {
- backgroundColor: GREY_LIGHT,
- borderRadius: 3,
- lineHeight: 1,
- marginBottom: '0.5em',
- padding: '0 0.4em',
- } as CSSProperties,
- fileNameInfo: {
- backgroundColor: GREY_LIGHT,
- borderRadius: 3,
- fontSize: 14,
- lineHeight: 1,
- padding: '0 0.4em',
- } as CSSProperties,
- defaultCursor: {
- cursor: 'default',
- } as CSSProperties,
- pointerCursor: {
- cursor: 'pointer',
- } as CSSProperties,
- dropFileRemoveButton: {
- height: 23,
- position: 'absolute',
- right: 6,
- top: 6,
- width: 23,
- } as CSSProperties,
-};
-
-interface Props {
- children: any;
- onDrop?: (data: Array>, file?: any) => void;
- onFileLoad?: (data: Array>, file?: any) => void;
- onError?: (err: any, file: any, inputElem: any, reason: any) => void;
- config?: ParseConfig;
- style?: any;
- noClick?: boolean;
- noDrag?: boolean;
- progressBarColor?: string;
- addRemoveButton?: boolean;
- onRemoveFile?: (data: null) => void;
- noProgressBar?: boolean;
- removeButtonColor?: string;
- isReset?: boolean;
- accept?: string;
-}
-
-interface State {
- dropAreaCustom: any;
- progressBar: number;
- displayProgressBarStatus: string;
- file: any;
- timeout: any;
- files: any;
- removeIconColor: string;
- isCanceled: boolean;
-}
-
-export default class CSVReader extends React.Component<
- Props,
- State
-> {
- static defaultProps: Partial> = {
- isReset: false,
- };
-
- // TODO
- // inputFileRef: React.RefObject = React.createRef()
- inputFileRef: any = React.createRef();
- dropAreaRef: any = React.createRef();
- fileSizeInfoRef: any = React.createRef();
- fileNameInfoRef: any = React.createRef();
-
- // TODO: Delete this.props.removeButtonColor
- REMOVE_ICON_COLOR =
- this.props.removeButtonColor ||
- this.props.style?.dropArea?.dropFile?.removeButton?.color ||
- this.props.style?.dropFile?.removeButton?.color ||
- this.props.style?.removeButton?.color ||
- REMOVE_ICON_DEFAULT_COLOR;
- REMOVE_ICON_COLOR_LIGHT = lightenDarkenColor(this.REMOVE_ICON_COLOR, 40);
-
- state = {
- dropAreaCustom: {},
- progressBar: 0,
- displayProgressBarStatus: 'none',
- file: null,
- timeout: null,
- files: null,
- removeIconColor: this.REMOVE_ICON_COLOR,
- isCanceled: false,
- } as State;
-
- componentDidUpdate = (prevProps: any) => {
- if (this.props.isReset !== prevProps.isReset) {
- this.removeFile();
- }
- };
-
- componentDidMount = () => {
- const currentDropAreaRef = this.dropAreaRef.current;
- if (currentDropAreaRef) {
- const fourDragsEvent = ['dragenter', 'dragover', 'dragleave', 'drop'];
- fourDragsEvent.forEach((item) => {
- currentDropAreaRef.addEventListener(item, this.preventDefaults, false);
- });
-
- if (!this.props.noDrag) {
- const highlightDragsEvent = ['dragenter', 'dragover'];
- highlightDragsEvent.forEach((item) => {
- currentDropAreaRef.addEventListener(item, this.highlight, false);
- });
- currentDropAreaRef.addEventListener(
- 'dragleave',
- this.unhighlight,
- false,
- );
- currentDropAreaRef.addEventListener('drop', this.unhighlight, false);
- currentDropAreaRef.addEventListener(
- 'drop',
- this.visibleProgressBar,
- false,
- );
- currentDropAreaRef.addEventListener('drop', this.handleDrop, false);
- }
- }
- };
-
- componentWillUnmount = () => {
- const currentDropAreaRef = this.dropAreaRef.current;
-
- const fourDragsEvent = ['dragenter', 'dragover', 'dragleave', 'drop'];
- fourDragsEvent.forEach((item) => {
- currentDropAreaRef.removeEventListener(item, this.preventDefaults, false);
- });
-
- if (!this.props.noDrag) {
- const highlightDragsEvent = ['dragenter', 'dragover'];
- highlightDragsEvent.forEach((item) => {
- currentDropAreaRef.removeEventListener(item, this.highlight, false);
- });
- currentDropAreaRef.removeEventListener(
- 'dragleave',
- this.unhighlight,
- false,
- );
- currentDropAreaRef.removeEventListener('drop', this.unhighlight, false);
- currentDropAreaRef.removeEventListener(
- 'drop',
- this.visibleProgressBar,
- false,
- );
- currentDropAreaRef.removeEventListener('drop', this.handleDrop, false);
- }
- };
-
- preventDefaults = (e: any) => {
- e.preventDefault();
- e.stopPropagation();
- };
-
- highlight = () => {
- const { style } = this.props;
- this.setState({
- dropAreaCustom: Object.assign(
- {},
- style?.dropAreaActive
- ? style?.dropAreaActive.borderColor
- ? style?.dropAreaActive
- : Object.assign({}, style?.dropAreaActive, styles.highlight)
- : style?.dropArea?.dropAreaActive
- ? style?.dropArea?.dropAreaActive.borderColor
- ? style?.dropArea?.dropAreaActive
- : Object.assign(
- {},
- style?.dropArea?.dropAreaActive,
- styles.highlight,
- )
- : styles.highlight,
- ),
- });
- this.setState({ progressBar: 0 });
- };
-
- unhighlight = () => {
- this.setState({
- dropAreaCustom: Object.assign(
- {},
- this.props.style?.dropArea?.borderColor
- ? {}
- : styles.dropAreaDefaultBorderColor,
- ),
- });
- };
-
- visibleProgressBar = () => {
- if (!this.props.noProgressBar) {
- this.setState({ displayProgressBarStatus: 'block' });
- }
- };
-
- handleDrop = (e: any) => {
- let files = null;
- let isCanceled = false;
-
- if (e.files === undefined) {
- const dt = e.dataTransfer;
- files = dt.files;
- } else {
- files = e.files;
- }
-
- if (files.length === 0) {
- files = this.state.files;
- isCanceled = true;
- }
-
- this.setState({ files, isCanceled }, () => {
- this.handleFiles();
- });
- };
-
- handleFiles = () => {
- this.setState({ progressBar: 0 });
- let files = null;
- files = [...this.state.files];
- files.forEach(this.uploadFile);
- };
-
- uploadFile = (file: any) => {
- this.displayFileInfo(file);
- this.setState({ file });
-
- const { onDrop, onFileLoad, onError, config = {} } = this.props;
- const reader = new window.FileReader();
- let options = {};
-
- const size = file.size;
- const data: any = [];
- let percent = 0;
-
- if (onDrop || onFileLoad) {
- const self = this;
- options = Object.assign(
- {
- complete:
- config?.complete || config?.step
- ? config.complete
- : () => {
- if (!onDrop && onFileLoad) {
- onFileLoad(data, file);
- } else if (onDrop && !onFileLoad) {
- onDrop(data, file);
- }
- },
- step: config?.step
- ? config.step
- : (row: any) => {
- data.push(row);
- if (config && config.preview) {
- percent = Math.round((data.length / config.preview) * 100);
- self.setState({ progressBar: percent });
- if (data.length === config.preview) {
- if (!onDrop && onFileLoad) {
- onFileLoad(data, file);
- } else if (onDrop && !onFileLoad) {
- onDrop(data, file);
- }
- }
- } else {
- const progress = row.meta.cursor;
- const newPercent = Math.round((progress / size) * 100);
- if (newPercent === percent) {
- return;
- }
- percent = newPercent;
- }
- self.setState({ progressBar: percent });
- },
- },
- options,
- );
- }
-
- if (onError) {
- options = Object.assign({ error: onError }, options);
- }
-
- if (config) {
- options = Object.assign({}, config, options);
- }
-
- reader.onload = (e: any) => {
- PapaParse.parse(e.target.result, options);
- };
-
- reader.onloadend = () => {
- clearTimeout(this.state.timeout);
- this.setState({
- timeout: setTimeout(() => {
- this.disableProgressBar();
- }, 2000),
- });
- };
-
- reader.readAsText(file, config.encoding || 'utf-8');
- };
-
- displayFileInfo = (file: any) => {
- if (!this.childrenIsFunction()) {
- this.fileSizeInfoRef.current.innerHTML = getSize(file.size);
- this.fileNameInfoRef.current.innerHTML = file.name;
- }
- };
-
- disableProgressBar = () => {
- if (!this.props.noProgressBar) {
- this.setState({ displayProgressBarStatus: 'none' });
- }
- };
-
- childrenIsFunction = () => {
- return typeof this.props.children === 'function';
- };
-
- fileChange = (e: any) => {
- const { target } = e;
- if (!this.props.noProgressBar) {
- this.setState({ displayProgressBarStatus: 'block' }, () => {
- this.handleDrop(target);
- });
- } else {
- this.handleDrop(target);
- }
- };
-
- open = (e: any) => {
- const { displayProgressBarStatus } = this.state;
- if (e && displayProgressBarStatus === 'none') {
- this.preventDefaults(e);
- this.inputFileRef.current.value = null;
- this.inputFileRef.current.click();
- }
- };
-
- renderChildren = () => {
- const { children } = this.props;
- const { file, progressBar } = this.state;
- return this.childrenIsFunction()
- ? children({ file, progressBar })
- : children;
- };
-
- handleRemoveFile = (e: any) => {
- if (e) {
- e.stopPropagation();
- this.removeFile();
- }
- };
-
- removeFile = () => {
- this.setState({ files: null, file: null });
-
- const { onRemoveFile } = this.props;
- if (onRemoveFile) {
- onRemoveFile(null);
- }
-
- this.inputFileRef.current.value = null;
- };
-
- changeRemoveIconColor = (color: string) => {
- if (color) {
- this.setState({ removeIconColor: color });
- }
- };
-
- renderDropFileRemoveButton = () => {
- const { addRemoveButton } = this.props;
- const { removeIconColor, displayProgressBarStatus } = this.state;
-
- if (addRemoveButton && displayProgressBarStatus === 'none') {
- return (
- this.handleRemoveFile(e)}
- onMouseOver={() =>
- this.changeRemoveIconColor(this.REMOVE_ICON_COLOR_LIGHT)
- }
- onMouseOut={() => this.changeRemoveIconColor(this.REMOVE_ICON_COLOR)}
- >
-
-
- );
- }
-
- if (addRemoveButton) {
- return (
-
-
-
- );
- }
-
- return null;
- };
-
- render() {
- const {
- style,
- noClick,
- children,
- noProgressBar,
- progressBarColor,
- accept,
- } = this.props;
- const {
- dropAreaCustom,
- files,
- isCanceled,
- progressBar,
- displayProgressBarStatus,
- } = this.state;
-
- return (
- <>
- this.fileChange(e)}
- />
- {!this.childrenIsFunction() ? (
- {
- if (!noClick) {
- this.open(e);
- }
- }}
- >
- {files && files.length > 0 ? (
-
- {this.renderDropFileRemoveButton()}
-
-
-
-
- {files && files.length > 0 && !isCanceled && !noProgressBar && (
-
- )}
-
- ) : (
- children
- )}
-
- ) : (
-
- {this.renderChildren()}
- {files && files.length > 0 && !isCanceled && !noProgressBar && (
-
- )}
-
- )}
- >
- );
- }
-}
diff --git a/src/ProgressBar.tsx b/src/ProgressBar.tsx
index d52fdb7..1d13f05 100644
--- a/src/ProgressBar.tsx
+++ b/src/ProgressBar.tsx
@@ -1,4 +1,4 @@
-import React, { CSSProperties } from 'react';
+import React, { CSSProperties, useState, useEffect } from 'react';
const DEFAULT_PROGRESS_BAR_COLOR = '#659cef';
@@ -7,14 +7,14 @@ const styles = {
borderRadius: 3,
boxShadow: 'inset 0 1px 3px rgba(0, 0, 0, .2)',
bottom: 14,
- position: 'absolute',
- width: '80%',
+ width: '100%',
+ // position: 'absolute',
} as CSSProperties,
- buttonProgressBar: {
+ button: {
position: 'inherit',
width: '100%',
} as CSSProperties,
- progressBarFill: {
+ fill: {
backgroundColor: DEFAULT_PROGRESS_BAR_COLOR,
borderRadius: 3,
height: 10,
@@ -23,36 +23,28 @@ const styles = {
};
interface Props {
- style: any;
- progressBar: number;
- displayProgressBarStatus: string;
- isButtonProgressBar?: boolean;
+ style?: any;
+ className?: string;
+ percentage: number;
+ display: string;
+ isButton?: boolean;
}
-export default class ProgressBar extends React.Component {
- render() {
- const {
- style,
- progressBar,
- displayProgressBarStatus,
- isButtonProgressBar,
- } = this.props;
+export default function ProgressBar(props: Props) {
+ const { style, className, display } = props;
+ const [percentage, setPercentage] = useState(0);
+
+ useEffect(() => {
+ setPercentage(props.percentage);
+ }, [props.percentage]);
- return (
-
-
-
- );
- }
+ return (
+
+ );
}
diff --git a/src/RemoveIcon.tsx b/src/Remove.tsx
similarity index 74%
rename from src/RemoveIcon.tsx
rename to src/Remove.tsx
index 6a1184a..93e369e 100644
--- a/src/RemoveIcon.tsx
+++ b/src/Remove.tsx
@@ -1,15 +1,21 @@
import React from 'react';
-export default function RemoveIcon(props: { color: string }) {
+export interface Props {
+ color?: string;
+ width?: number;
+ height?: number;
+}
+
+export default function Remove({ color, width = 23, height = 23 }: Props) {
return (
https://github.com/DefinitelyTyped/DefinitelyTyped/blob/master/types/papaparse/index.d.ts
+// 5.2 => https://github.com/DefinitelyTyped/DefinitelyTyped/blob/d3737ebd9125505f7ea237b9f17f1426579a3917/types/papaparse/index.d.ts
+
+export interface CustomConfig
+ extends ParseConfig {
+ /**
+ * * * * * * * * * *
+ * ParseAsyncConfig
+ * * * * * * * * * *
+ */
+
+ /**
+ * Whether or not to use a worker thread.
+ * Using a worker will keep your page reactive, but may be slightly slower.
+ * @default false
+ */
+ worker?: boolean | undefined;
+ /**
+ * Overrides `Papa.LocalChunkSize` and `Papa.RemoteChunkSize`.
+ */
+ chunkSize?: number | undefined;
+ /**
+ * A callback function, identical to `step`, which activates streaming.
+ * However, this function is executed after every chunk of the file is loaded and parsed rather than every row.
+ * Works only with local and remote files.
+ * Do not use both `chunk` and `step` callbacks together.
+ */
+ chunk?(results: ParseResult, parser: Parser): void;
+ /**
+ * A callback to execute if FileReader encounters an error.
+ * The function is passed two arguments: the error and the File.
+ */
+ error?(error: Error, file: TInput): void;
+
+ // ParseLocalConfig
+ /** The encoding to use when opening local files. If specified, it must be a value supported by the FileReader API. */
+ encoding?: string | undefined;
+
+ /**
+ * * * * * * * * * * *
+ * ParseRemoteConfig
+ * * * * * * * * * * *
+ */
+
+ /**
+ * This indicates that the string you passed as the first argument to `parse()`
+ * is actually a URL from which to download a file and parse its contents.
+ */
+ // download: true;
+ download?: boolean | true; // default: false
+ /**
+ * If defined, should be an object that describes the headers.
+ * @example { 'Authorization': 'token 123345678901234567890' }
+ * @default undefined
+ */
+ downloadRequestHeaders?: { [headerName: string]: string } | undefined;
+ /**
+ * Use POST request on the URL of the download option. The value passed will be set as the body of the request.
+ * @default undefined
+ */
+ downloadRequestBody?:
+ | Blob
+ | BufferSource
+ | FormData
+ | URLSearchParams
+ | string
+ | undefined;
+ /**
+ * A boolean value passed directly into XMLHttpRequest's "withCredentials" property.
+ * @default undefined
+ */
+ withCredentials?: boolean | undefined;
+}
diff --git a/src/react-papaparse.ts b/src/react-papaparse.ts
index deeec98..5628ee3 100644
--- a/src/react-papaparse.ts
+++ b/src/react-papaparse.ts
@@ -4,16 +4,10 @@ export const BAD_DELIMITERS = PapaParse.BAD_DELIMITERS;
export const RECORD_SEP = PapaParse.RECORD_SEP;
export const UNIT_SEP = PapaParse.UNIT_SEP;
export const WORKERS_SUPPORTED = PapaParse.WORKERS_SUPPORTED;
-
export const LocalChunkSize = PapaParse.LocalChunkSize;
export const DefaultDelimiter = PapaParse.DefaultDelimiter;
-export { default as CSVReader } from './CSVReader';
-export {
- default as CSVDownloader,
- LINK_TYPE,
- BUTTON_TYPE,
-} from './CSVDownloader';
-export { readString } from './readString';
-export { readRemoteFile } from './readRemoteFile';
-export { jsonToCSV } from './jsonToCSV';
+export { formatFileSize, lightenDarkenColor } from './utils';
+export { usePapaParse } from './usePapaParse';
+export { useCSVDownloader } from './useCSVDownloader';
+export { useCSVReader } from './useCSVReader';
diff --git a/src/readRemoteFile.ts b/src/readRemoteFile.ts
index 9dd9531..f92a646 100644
--- a/src/readRemoteFile.ts
+++ b/src/readRemoteFile.ts
@@ -1,5 +1,5 @@
-import PapaParse, { ParseConfig } from 'papaparse';
+import PapaParse, { ParseRemoteConfig } from 'papaparse';
-export function readRemoteFile(url: string, options: ParseConfig = {}) {
- PapaParse.parse(url, Object.assign({}, { download: true }, options));
+export function readRemoteFile(url: string, config: ParseRemoteConfig) {
+ PapaParse.parse(url, Object.assign({}, { download: true }, config));
}
diff --git a/src/readString.ts b/src/readString.ts
index b3efeb3..ee021c9 100644
--- a/src/readString.ts
+++ b/src/readString.ts
@@ -1,5 +1,8 @@
-import PapaParse, { ParseConfig } from 'papaparse';
+import PapaParse, { ParseWorkerConfig } from 'papaparse';
-export function readString(str: string, options: ParseConfig = {}) {
- return PapaParse.parse(str, options);
+export function readString(
+ csvString: string,
+ config: ParseWorkerConfig & { download?: false | undefined },
+) {
+ return PapaParse.parse(csvString, config);
}
diff --git a/src/useCSVDownloader.tsx b/src/useCSVDownloader.tsx
new file mode 100644
index 0000000..91e5610
--- /dev/null
+++ b/src/useCSVDownloader.tsx
@@ -0,0 +1,95 @@
+import React from 'react';
+import PapaParse, { UnparseConfig } from 'papaparse';
+
+const Type = {
+ Link: 'link',
+ Button: 'button',
+} as const;
+
+export interface Props {
+ children: React.ReactNode;
+ data: any;
+ filename: string;
+ type?: 'link' | 'button';
+ style?: any;
+ className?: string;
+ bom?: boolean;
+ config?: UnparseConfig;
+}
+
+function useCSVDownloaderComponent() {
+ const CSVDownloaderComponent = ({
+ children,
+ data = {},
+ filename,
+ type = Type.Link,
+ style = {},
+ className = '',
+ bom = false,
+ config = {},
+ }: Props) => {
+ const download = () => {
+ const bomCode = bom ? '\ufeff' : '';
+ let csvContent = null;
+ let csvURL = null;
+
+ if (typeof data === 'function') {
+ data = data();
+ }
+
+ if (typeof data === 'object') {
+ csvContent = PapaParse.unparse(data, config);
+ } else {
+ csvContent = data;
+ }
+
+ const csvData = new Blob([`${bomCode}${csvContent}`], {
+ type: 'text/csv;charset=utf-8;',
+ });
+
+ const navObj: any = window.navigator;
+ if (navObj.msSaveBlob) {
+ csvURL = navObj.msSaveBlob(csvData, `${filename}.csv`);
+ } else {
+ csvURL = window.URL.createObjectURL(csvData);
+ }
+
+ const link = document.createElement('a');
+ link.href = csvURL as string;
+ link.setAttribute('download', `${filename}.csv`);
+ link.click();
+ link.remove();
+ };
+
+ return (
+ <>
+ {type === Type.Button ? (
+ download()}
+ style={style}
+ className={className}
+ >
+ {children}
+
+ ) : (
+ download()} style={style} className={className}>
+ {children}
+
+ )}
+ >
+ );
+ };
+
+ const CSVDownloader = React.useMemo(() => CSVDownloaderComponent, []) as any;
+
+ return CSVDownloader;
+}
+
+export function useCSVDownloader() {
+ const CSVDownloader = useCSVDownloaderComponent();
+
+ return {
+ CSVDownloader,
+ Type,
+ };
+}
diff --git a/src/useCSVReader.tsx b/src/useCSVReader.tsx
new file mode 100644
index 0000000..204be6c
--- /dev/null
+++ b/src/useCSVReader.tsx
@@ -0,0 +1,686 @@
+/* eslint-disable react-hooks/exhaustive-deps */
+import jschardet from 'jschardet';
+import PapaParse, { ParseResult } from 'papaparse';
+import React, {
+ ReactNode, useCallback, useEffect, useMemo, useReducer, useRef
+} from 'react';
+import { CustomConfig } from './model';
+import ProgressBar from './ProgressBar';
+import Remove, { Props as RemoveComponentProps } from './Remove';
+import {
+ composeEventHandlers, fileAccepted,
+ fileMatchSize, isEventWithFiles, isIeOrEdge, isPropagationStopped, onDocumentDragOver, TOO_MANY_FILES_REJECTION
+} from './utils';
+
+// 'text/csv' for MacOS
+// '.csv' for Linux
+// 'application/vnd.ms-excel' for Window 10
+const DEFAULT_ACCEPT = 'text/csv, .csv, application/vnd.ms-excel';
+
+export interface Props {
+ children: (fn: any) => void | ReactNode;
+ accept?: string;
+ config?: CustomConfig;
+ minSize?: number;
+ maxSize?: number;
+ maxFiles?: number;
+ disabled?: boolean;
+ noClick?: boolean;
+ noDrag?: boolean;
+ noDragEventsBubbling?: boolean;
+ noKeyboard?: boolean;
+ multiple?: boolean;
+ preventDropOnDocument?: boolean;
+ onUploadAccepted?: (
+ data: ParseResult,
+ file?: File,
+ event?: DragEvent | Event,
+ ) => void;
+ onUploadRejected?: (file?: File, event?: DragEvent | Event) => void;
+ validator?: (file: File) => void;
+ onDragEnter?: (event?: DragEvent) => void;
+ onDragOver?: (event?: DragEvent) => void;
+ onDragLeave?: (event?: DragEvent) => void;
+}
+
+export interface ProgressBarComponentProp {
+ style?: any;
+ className?: string;
+}
+
+function useCSVReaderComponent() {
+ const CSVReaderComponent = ({
+ children,
+ accept = DEFAULT_ACCEPT,
+ config = {},
+ minSize = 0,
+ maxSize = Infinity,
+ maxFiles = 1,
+ disabled = false,
+ noClick = false,
+ noDrag = false,
+ noDragEventsBubbling = false,
+ noKeyboard = false,
+ multiple = false,
+ preventDropOnDocument = true,
+ onUploadAccepted,
+ validator,
+ onUploadRejected,
+ onDragEnter,
+ onDragOver,
+ onDragLeave,
+ }: Props) => {
+ const inputRef: any = useRef(null);
+ const rootRef: any = useRef(null);
+ const dragTargetsRef = useRef([]);
+
+ const [state, dispatch] = useReducer(reducer, initialState);
+ const {
+ acceptedFile,
+ displayProgressBar,
+ progressBarPercentage,
+ draggedFiles,
+ isFileDialogActive,
+ } = state;
+
+ const onDocumentDrop = (event: DragEvent) => {
+ if (rootRef.current && rootRef.current.contains(event.target)) {
+ // If we intercepted an event for our instance, let it propagate down to the instance's onDrop handler
+ return;
+ }
+ event.preventDefault();
+ dragTargetsRef.current = [];
+ };
+
+ useEffect(() => {
+ if (preventDropOnDocument) {
+ document.addEventListener('dragover', onDocumentDragOver, false);
+ document.addEventListener('drop', onDocumentDrop, false);
+ }
+
+ return () => {
+ if (preventDropOnDocument) {
+ document.removeEventListener('dragover', onDocumentDragOver);
+ document.removeEventListener('drop', onDocumentDrop);
+ }
+ };
+ }, [rootRef, preventDropOnDocument]);
+
+ // == GLOBAL ==
+ const composeHandler = (fn: any) => {
+ return disabled ? null : fn;
+ };
+
+ const composeDragHandler = (fn: any) => {
+ return noDrag ? null : composeHandler(fn);
+ };
+
+ const stopPropagation = (event: Event) => {
+ if (noDragEventsBubbling) {
+ event.stopPropagation();
+ }
+ };
+
+ const allowDrop = (event: any) => {
+ event.preventDefault(event);
+ // Persist here because we need the event later after getFilesFromEvent() is done
+ event.persist();
+ stopPropagation(event);
+ };
+
+ const setDisplayProgressBar = (display: string) => {
+ dispatch({
+ displayProgressBar: display,
+ type: 'setDisplayProgressBar',
+ });
+ };
+
+ const setProgressBarPercentage = (percentage: number) => {
+ dispatch({
+ progressBarPercentage: percentage,
+ type: 'setProgressBarPercentage',
+ });
+ };
+
+ const ProgressBarComponent = (props: ProgressBarComponentProp) => {
+ return (
+
+ );
+ };
+
+ const RemoveComponent = (props: RemoveComponentProps) => {
+ return ;
+ };
+
+ const renderChildren = () => {
+ return children({
+ getRootProps,
+ acceptedFile,
+ ProgressBar: ProgressBarComponent,
+ getRemoveFileProps,
+ Remove: RemoveComponent,
+ });
+ };
+
+ // Fn for opening the file dialog programmatically
+ const openFileDialog = useCallback(() => {
+ if (inputRef.current && state.displayProgressBar) {
+ dispatch({ type: 'openDialog' });
+ inputRef.current.value = null;
+ inputRef.current.click();
+ }
+ }, [dispatch]);
+
+ // Update file dialog active state when the window is focused on
+ const onWindowFocus = () => {
+ // Execute the timeout only if the file dialog is opened in the browser
+ if (isFileDialogActive) {
+ setTimeout(() => {
+ if (inputRef.current) {
+ const { files } = inputRef.current;
+
+ if (!files.length) {
+ dispatch({ type: 'closeDialog' });
+ }
+ }
+ }, 300);
+ }
+ };
+
+ useEffect(() => {
+ window.addEventListener('focus', onWindowFocus, false);
+ return () => {
+ window.removeEventListener('focus', onWindowFocus, false);
+ };
+ }, [inputRef, isFileDialogActive]);
+
+ // Cb to open the file dialog when click occurs on the dropzone
+ const onClickCb = useCallback(() => {
+ if (noClick) {
+ return;
+ }
+
+ // In IE11/Edge the file-browser dialog is blocking, therefore, use setTimeout()
+ // to ensure React can handle state changes
+ if (isIeOrEdge()) {
+ setTimeout(openFileDialog, 0);
+ } else {
+ openFileDialog();
+ }
+ }, [inputRef, noClick]);
+
+ const onDropCb = useCallback(
+ (event: any) => {
+ allowDrop(event);
+
+ setProgressBarPercentage(0);
+
+ dragTargetsRef.current = [];
+
+ if (isEventWithFiles(event)) {
+ if (isPropagationStopped(event) && !noDragEventsBubbling) {
+ return;
+ }
+
+ const acceptedFiles = [] as any;
+ const fileRejections = [] as any;
+ const files =
+ event.target.files ||
+ (event.dataTransfer && event.dataTransfer.files);
+ Array.from(files).forEach((file) => {
+ const [accepted, acceptError] = fileAccepted(file, accept);
+ const [sizeMatch, sizeError] = fileMatchSize(
+ file,
+ minSize,
+ maxSize,
+ );
+ const customErrors = validator ? validator(file as File) : null;
+
+ if (accepted && sizeMatch && !customErrors) {
+ acceptedFiles.push(file);
+ } else {
+ let errors = [acceptError, sizeError];
+
+ if (customErrors) {
+ errors = errors.concat(customErrors);
+ }
+
+ fileRejections.push({ file, errors: errors.filter((e) => e) });
+ }
+ });
+
+ if (
+ (!multiple && acceptedFiles.length > 1) ||
+ (multiple && maxFiles >= 1 && acceptedFiles.length > maxFiles)
+ ) {
+ // Reject everything and empty accepted files
+ acceptedFiles.forEach((file: File) => {
+ fileRejections.push({ file, errors: [TOO_MANY_FILES_REJECTION] });
+ });
+ acceptedFiles.splice(0);
+ }
+
+ dispatch({
+ acceptedFiles,
+ fileRejections,
+ type: 'setFiles',
+ });
+
+ setDisplayProgressBar('block');
+
+ // if (onDrop) {
+ // onDrop(acceptedFiles, fileRejections, event)
+ // }
+
+ if (fileRejections.length > 0 && onUploadRejected) {
+ onUploadRejected(fileRejections, event);
+ }
+
+ if (acceptedFiles.length > 0 && onUploadAccepted) {
+ let configs = {} as any;
+ const data: any = [];
+ const errors: any = [];
+ const meta: any = [];
+ const reader = new window.FileReader();
+ let percentage = 0;
+
+ acceptedFiles.forEach((file: File) => {
+ dispatch({
+ acceptedFile: file,
+ type: 'setFile',
+ });
+
+ configs = {
+ complete:
+ config?.complete || config?.step
+ ? config.complete
+ : () => {
+ const obj = { data, errors, meta };
+ onUploadAccepted(obj, file);
+ },
+ step: config?.step
+ ? config.step
+ : (row: any) => {
+ data.push(row.data);
+ if (row.errors.length > 0) {
+ errors.push(row.errors);
+ }
+ if (row.length > 0) {
+ meta.push(row[0].meta);
+ }
+ if (config && config.preview) {
+ percentage = Math.round(
+ (data.length / config.preview) * 100,
+ );
+ // setProgressBarPercentage(percentage);
+ if (data.length === config.preview) {
+ const obj = { data, errors, meta };
+ onUploadAccepted(obj, file);
+ }
+ } else {
+ const cursor = row.meta.cursor;
+ const newPercentage = Math.round(
+ (cursor / file.size) * 100,
+ );
+ if (newPercentage === percentage) {
+ return;
+ }
+ percentage = newPercentage;
+ }
+ setProgressBarPercentage(percentage);
+ },
+ };
+ configs = Object.assign({}, config, configs);
+
+ reader.onload = (e: any) => {
+ if (!config.encoding) {
+ config.encoding = jschardet.detect(e.target.result).encoding;
+ configs = Object.assign({}, config, configs);
+ }
+ PapaParse.parse(e.target.result, configs);
+ };
+ reader.onloadend = () => {
+ setTimeout(() => {
+ setDisplayProgressBar('none');
+ }, 2000);
+ };
+ if(config?.encoding){
+ reader.readAsText(file, config.encoding || 'utf-8');
+ }
+ else {
+ reader.readAsBinaryString(file);
+ }
+ });
+ }
+ }
+ // dispatch({ type: 'reset' });
+ },
+ [
+ multiple,
+ accept,
+ minSize,
+ maxSize,
+ maxFiles,
+ validator,
+ onUploadAccepted,
+ ],
+ );
+
+ const onInputElementClick = useCallback((event) => {
+ stopPropagation(event);
+ }, []);
+ // ============
+
+ // == BUTTON | DROP ==
+ const composeKeyboardHandler = (fn: any) => {
+ return noKeyboard ? null : composeHandler(fn);
+ };
+
+ const onDragEnterCb = useCallback(
+ (event: DragEvent) => {
+ allowDrop(event);
+
+ dragTargetsRef.current = [
+ ...dragTargetsRef.current,
+ event.target,
+ ] as never[];
+
+ if (isEventWithFiles(event)) {
+ if (isPropagationStopped(event) && !noDragEventsBubbling) {
+ return;
+ }
+
+ dispatch({
+ draggedFiles,
+ isDragActive: true,
+ type: 'setDraggedFiles',
+ });
+
+ if (onDragEnter) {
+ onDragEnter(event);
+ }
+ }
+ },
+ [onDragEnter, noDragEventsBubbling],
+ );
+
+ const onDragOverCb = useCallback(
+ (event: DragEvent) => {
+ allowDrop(event);
+
+ const hasFiles = isEventWithFiles(event);
+ if (hasFiles && event.dataTransfer) {
+ try {
+ event.dataTransfer.dropEffect = 'copy';
+ } catch {}
+ }
+
+ if (hasFiles && onDragOver) {
+ onDragOver(event);
+ }
+
+ return false;
+ },
+ [onDragOver, noDragEventsBubbling],
+ );
+
+ const onDragLeaveCb = useCallback(
+ (event: DragEvent) => {
+ allowDrop(event);
+
+ // Only deactivate once the dropzone and all children have been left
+ const targets = dragTargetsRef.current.filter(
+ (target) => rootRef.current && rootRef.current.contains(target),
+ );
+ // Make sure to remove a target present multiple times only once
+ // (Firefox may fire dragenter/dragleave multiple times on the same element)
+ const targetIdx = targets.indexOf(event.target as never);
+ if (targetIdx !== -1) {
+ targets.splice(targetIdx, 1);
+ }
+ dragTargetsRef.current = targets;
+ if (targets.length > 0) {
+ return;
+ }
+
+ dispatch({
+ isDragActive: false,
+ type: 'setDraggedFiles',
+ draggedFiles: [],
+ });
+
+ if (isEventWithFiles(event) && onDragLeave) {
+ onDragLeave(event);
+ }
+ },
+ [rootRef, onDragLeave, noDragEventsBubbling],
+ );
+
+ // Cb to open the file dialog when SPACE/ENTER occurs on the dropzone
+ const onKeyDownCb = useCallback(
+ (event: KeyboardEvent) => {
+ // Ignore keyboard events bubbling up the DOM tree
+ if (!rootRef.current || !rootRef.current.isEqualNode(event.target)) {
+ return;
+ }
+
+ if (event.key === 'Space' || event.key === 'Enter') {
+ event.preventDefault();
+ openFileDialog();
+ }
+ },
+ [rootRef, inputRef],
+ );
+
+ // Update focus state for the dropzone
+ const onFocusCb = useCallback(() => {
+ dispatch({ type: 'focus' });
+ }, []);
+
+ const onBlurCb = useCallback(() => {
+ dispatch({ type: 'blur' });
+ }, []);
+
+ const getRootProps = useMemo(
+ () =>
+ ({
+ onClick = () => {},
+ onDrop = () => {},
+ onDragOver = () => {},
+ onDragLeave = () => {},
+ onKeyDown = () => {},
+ onFocus = () => {},
+ onBlur = () => {},
+ onDragEnter = () => {},
+ // refKey = rootRef,
+ ...rest
+ } = {}) => ({
+ onClick: composeHandler(composeEventHandlers(onClick, onClickCb)),
+ onDrop: composeDragHandler(composeEventHandlers(onDrop, onDropCb)),
+ onDragEnter: composeDragHandler(
+ composeEventHandlers(onDragEnter, onDragEnterCb),
+ ),
+ onDragOver: composeDragHandler(
+ composeEventHandlers(onDragOver, onDragOverCb),
+ ),
+ onDragLeave: composeDragHandler(
+ composeEventHandlers(onDragLeave, onDragLeaveCb),
+ ),
+ onKeyDown: composeKeyboardHandler(
+ composeEventHandlers(onKeyDown, onKeyDownCb), // Done
+ ),
+ onFocus: composeKeyboardHandler(
+ composeEventHandlers(onFocus, onFocusCb), // Done
+ ),
+ onBlur: composeKeyboardHandler(
+ composeEventHandlers(onBlur, onBlurCb), // Done
+ ),
+ // [refKey]: rootRef,
+ ...rest,
+ }),
+ [
+ rootRef,
+ onKeyDownCb,
+ onFocusCb,
+ onBlurCb,
+ onClickCb,
+ onDragEnterCb,
+ onDragOverCb,
+ onDragLeaveCb,
+ onDropCb,
+ noKeyboard,
+ noDrag,
+ disabled,
+ ],
+ );
+ // ===================
+
+ // == INPUT ==
+ const getInputProps = useMemo(
+ () =>
+ ({
+ refKey = 'ref',
+ onChange = () => {},
+ onClick = () => {},
+ ...rest
+ } = {}) => {
+ const inputProps = {
+ accept,
+ multiple,
+ type: 'file',
+ style: { display: 'none' },
+ onChange: composeHandler(composeEventHandlers(onChange, onDropCb)),
+ onClick: composeHandler(
+ composeEventHandlers(onClick, onInputElementClick),
+ ),
+ autoComplete: 'off',
+ tabIndex: -1,
+ [refKey]: inputRef,
+ };
+
+ return {
+ ...inputProps,
+ ...rest,
+ };
+ },
+ [inputRef, accept, onDropCb, disabled],
+ );
+ // ===========
+
+ const removeFileProgrammaticallyCb = useCallback((event: Event) => {
+ inputRef.current.value = '';
+ dispatch({ type: 'reset' });
+ // To prevent a parents onclick event from firing when a child is clicked
+ event.stopPropagation();
+ }, []);
+
+ const getRemoveFileProps = useMemo(
+ () =>
+ ({ onClick = () => {}, ...rest } = {}) => ({
+ onClick: composeHandler(
+ composeEventHandlers(onClick, removeFileProgrammaticallyCb),
+ ),
+ ...rest,
+ }),
+ [removeFileProgrammaticallyCb],
+ );
+
+ return (
+ <>
+
+ {renderChildren()}
+ >
+ );
+ };
+
+ const CSVReader = useMemo(() => CSVReaderComponent, []) as any;
+
+ return CSVReader;
+}
+
+export function useCSVReader() {
+ const CSVReader = useCSVReaderComponent();
+
+ return {
+ CSVReader,
+ };
+}
+
+const initialState = {
+ displayProgressBar: 'none',
+ progressBarPercentage: 0,
+ isDragActive: false,
+ isFileDialogActive: false,
+ isFocused: false,
+ draggedFiles: [],
+ acceptedFiles: [],
+ acceptedFile: null,
+
+ // isDragAccept: false,
+ // isDragReject: false,
+ // fileRejections: [],
+};
+
+function reducer(state: any, action: any) {
+ switch (action.type) {
+ case 'openDialog':
+ return {
+ ...state,
+ isFileDialogActive: true,
+ };
+ case 'closeDialog':
+ return {
+ ...state,
+ isFileDialogActive: false,
+ };
+ case 'setFiles':
+ return {
+ ...state,
+ acceptedFiles: action.acceptedFiles,
+ fileRejections: action.fileRejections,
+ };
+ case 'setFile':
+ return {
+ ...state,
+ acceptedFile: action.acceptedFile,
+ };
+ case 'setDisplayProgressBar':
+ return {
+ ...state,
+ displayProgressBar: action.displayProgressBar,
+ };
+ case 'setProgressBarPercentage':
+ return {
+ ...state,
+ progressBarPercentage: action.progressBarPercentage,
+ };
+ case 'setDraggedFiles':
+ /* eslint no-case-declarations: 0 */
+ const { isDragActive, draggedFiles } = action;
+ return {
+ ...state,
+ draggedFiles,
+ isDragActive,
+ };
+ case 'focus':
+ return {
+ ...state,
+ isFocused: true,
+ };
+ case 'blur':
+ return {
+ ...state,
+ isFocused: false,
+ };
+ case 'reset':
+ return {
+ ...initialState,
+ };
+ default:
+ return state;
+ }
+}
diff --git a/src/usePapaParse.tsx b/src/usePapaParse.tsx
new file mode 100644
index 0000000..85236f0
--- /dev/null
+++ b/src/usePapaParse.tsx
@@ -0,0 +1,11 @@
+import { readString } from './readString';
+import { readRemoteFile } from './readRemoteFile';
+import { jsonToCSV } from './jsonToCSV';
+
+export function usePapaParse() {
+ return {
+ readString,
+ readRemoteFile,
+ jsonToCSV,
+ };
+}
diff --git a/src/utils.ts b/src/utils.ts
index 02dbf8f..086c6ab 100644
--- a/src/utils.ts
+++ b/src/utils.ts
@@ -1,4 +1,10 @@
-export default function getSize(size: number) {
+// Error codes
+export const FILE_INVALID_TYPE = 'file-invalid-type';
+export const FILE_TOO_LARGE = 'file-too-large';
+export const FILE_TOO_SMALL = 'file-too-small';
+export const TOO_MANY_FILES = 'too-many-files';
+
+export function formatFileSize(size: number) {
const sizeKb = 1024;
const sizeMb = sizeKb * sizeKb;
const sizeGb = sizeMb * sizeKb;
@@ -49,3 +55,195 @@ export function lightenDarkenColor(col: string, amt: number) {
}
return (usePound ? '#' : '') + (g | (b << 8) | (r << 16)).toString(16);
}
+
+function isIe(userAgent: any) {
+ return (
+ userAgent.indexOf('MSIE') !== -1 || userAgent.indexOf('Trident/') !== -1
+ );
+}
+
+function isEdge(userAgent: any) {
+ return userAgent.indexOf('Edge/') !== -1;
+}
+
+export function isIeOrEdge(userAgent = window.navigator.userAgent) {
+ return isIe(userAgent) || isEdge(userAgent);
+}
+
+// React's synthetic events has event.isPropagationStopped,
+// but to remain compatibility with other libs (Preact) fall back
+// to check event.cancelBubble
+export function isPropagationStopped(event: any) {
+ if (typeof event.isPropagationStopped === 'function') {
+ return event.isPropagationStopped();
+ } else if (typeof event.cancelBubble !== 'undefined') {
+ return event.cancelBubble;
+ }
+ return false;
+}
+
+/**
+ * This is intended to be used to compose event handlers
+ * They are executed in order until one of them calls `event.isPropagationStopped()`.
+ * Note that the check is done on the first invoke too,
+ * meaning that if propagation was stopped before invoking the fns,
+ * no handlers will be executed.
+ *
+ * @param {Function} fns the event hanlder functions
+ * @return {Function} the event handler to add to an element
+ */
+export function composeEventHandlers(...fns: any[]) {
+ return (event: any, ...args: any[]) =>
+ fns.some((fn) => {
+ if (!isPropagationStopped(event) && fn) {
+ fn(event, ...args);
+ }
+ return isPropagationStopped(event);
+ });
+}
+
+export function isEventWithFiles(event: any) {
+ if (!event.dataTransfer) {
+ return !!event.target && !!event.target.files;
+ }
+ // https://developer.mozilla.org/en-US/docs/Web/API/DataTransfer/types
+ // https://developer.mozilla.org/en-US/docs/Web/API/HTML_Drag_and_Drop_API/Recommended_drag_types#file
+ return Array.prototype.some.call(
+ event.dataTransfer.types,
+ (type) => type === 'Files' || type === 'application/x-moz-file',
+ );
+}
+
+/**
+ * Check if the provided file type should be accepted by the input with accept attribute.
+ * https://developer.mozilla.org/en-US/docs/Web/HTML/Element/Input#attr-accept
+ *
+ * Inspired by https://github.com/enyo/dropzone
+ *
+ * @param file {File} https://developer.mozilla.org/en-US/docs/Web/API/File
+ * @param acceptedFiles {string}
+ * @returns {boolean}
+ */
+
+export function accepts(file: any, acceptedFiles: any) {
+ if (file && acceptedFiles) {
+ const acceptedFilesArray = Array.isArray(acceptedFiles)
+ ? acceptedFiles
+ : acceptedFiles.split(',');
+ const fileName = file.name || '';
+ const mimeType = (file.type || '').toLowerCase();
+ const baseMimeType = mimeType.replace(/\/.*$/, '');
+
+ return acceptedFilesArray.some((type: any) => {
+ const validType = type.trim().toLowerCase();
+ if (validType.charAt(0) === '.') {
+ return fileName.toLowerCase().endsWith(validType);
+ } else if (validType.endsWith('/*')) {
+ // This is something like a image/* mime type
+ return baseMimeType === validType.replace(/\/.*$/, '');
+ }
+ return mimeType === validType;
+ });
+ }
+ return true;
+}
+
+// File Errors
+export const getInvalidTypeRejectionErr = (accept: any) => {
+ accept = Array.isArray(accept) && accept.length === 1 ? accept[0] : accept;
+ const messageSuffix = Array.isArray(accept)
+ ? `one of ${accept.join(', ')}`
+ : accept;
+ return {
+ code: FILE_INVALID_TYPE,
+ message: `File type must be ${messageSuffix}`,
+ };
+};
+
+// Firefox versions prior to 53 return a bogus MIME type for every file drag, so dragovers with
+// that MIME type will always be accepted
+export function fileAccepted(file: any, accept: any) {
+ const isAcceptable =
+ file.type === 'application/x-moz-file' || accepts(file, accept);
+ return [
+ isAcceptable,
+ isAcceptable ? null : getInvalidTypeRejectionErr(accept),
+ ];
+}
+
+export function fileMatchSize(file: any, minSize: any, maxSize: any) {
+ if (isDefined(file.size)) {
+ if (isDefined(minSize) && isDefined(maxSize)) {
+ if (file.size > maxSize) {
+ return [false, getTooLargeRejectionErr(maxSize)];
+ }
+ if (file.size < minSize) {
+ return [false, getTooSmallRejectionErr(minSize)];
+ }
+ } else if (isDefined(minSize) && file.size < minSize) {
+ return [false, getTooSmallRejectionErr(minSize)];
+ } else if (isDefined(maxSize) && file.size > maxSize) {
+ return [false, getTooLargeRejectionErr(maxSize)];
+ }
+ }
+ return [true, null];
+}
+
+function isDefined(value: any) {
+ return value !== undefined && value !== null;
+}
+
+export const getTooLargeRejectionErr = (maxSize: any) => {
+ return {
+ code: FILE_TOO_LARGE,
+ message: `File is larger than ${maxSize} bytes`,
+ };
+};
+
+export const getTooSmallRejectionErr = (minSize: any) => {
+ return {
+ code: FILE_TOO_SMALL,
+ message: `File is smaller than ${minSize} bytes`,
+ };
+};
+
+export const TOO_MANY_FILES_REJECTION = {
+ code: TOO_MANY_FILES,
+ message: 'Too many files',
+};
+
+// allow the entire document to be a drag target
+export function onDocumentDragOver(event: any) {
+ event.preventDefault();
+}
+
+interface Params {
+ files?: any;
+ accept?: any;
+ minSize?: number;
+ maxSize?: number;
+ multiple?: any;
+ maxFiles?: any;
+}
+
+export function allFilesAccepted({
+ files,
+ accept,
+ minSize,
+ maxSize,
+ multiple,
+ maxFiles,
+}: Params) {
+ if (
+ (!multiple && files.length > 1) ||
+ (multiple && maxFiles >= 1 && files.length > maxFiles)
+ ) {
+ return false;
+ }
+
+ return files.every((file: any) => {
+ const [accepted] = fileAccepted(file, accept);
+ const [sizeMatch] = fileMatchSize(file, minSize, maxSize);
+ return accepted && sizeMatch;
+ });
+}
diff --git a/supports/create-next-app/pages/index.tsx b/supports/create-next-app/pages/index.tsx
index 55c9a58..68ac5d7 100644
--- a/supports/create-next-app/pages/index.tsx
+++ b/supports/create-next-app/pages/index.tsx
@@ -1,146 +1,19 @@
-import React, { useState } from 'react'
-import { CSVReader, CSVDownloader } from 'react-papaparse'
+import React from 'react';
-export default function Home() {
- const [isReset, setIsReset] = useState(false);
+import { usePapaParse } from 'react-papaparse';
- const handleReset = () => {
- setIsReset(!isReset)
- }
+export default function ReadRemoteFile() {
+ const { readRemoteFile } = usePapaParse();
- const handleOnDrop = (data: any) => {
- console.log('---------------------------')
- console.log(data)
- console.log('---------------------------')
- }
+ const handleReadRemoteFile = () => {
+ readRemoteFile('https://react-papaparse.js.org/static/csv/normal.csv', {
+ complete: (results) => {
+ console.log('---------------------------');
+ console.log('Results:', results);
+ console.log('---------------------------');
+ },
+ });
+ };
- const handleOnError = (
- err: any,
- // file,
- // inputElem,
- // reason
- ) => {
- console.log(err)
- }
-
- const handleOnRemoveFile = (data: any) => {
- console.log('---------------------------')
- console.log(data)
- console.log('---------------------------')
- }
-
- return (
- <>
-
- Click to upload.
-
- handleReset()}>Reset
-
- Download
-
-
- Download
-
- {
- return [
- {
- "Column 1": "1-1",
- "Column 2": "1-2",
- "Column 3": "1-3",
- "Column 4": "1-4",
- }
- ]}
- }
- >
- Download
-
- >
- )
+ return handleReadRemoteFile()}>readRemoteFile ;
}
diff --git a/supports/create-react-app/README.md b/supports/create-react-app/README.md
index 02aac3f..58beeac 100644
--- a/supports/create-react-app/README.md
+++ b/supports/create-react-app/README.md
@@ -6,20 +6,20 @@ This project was bootstrapped with [Create React App](https://github.com/faceboo
In the project directory, you can run:
-### `yarn start`
+### `npm start`
Runs the app in the development mode.\
-Open [http://localhost:3000](http://localhost:3000) to view it in the browser.
+Open [http://localhost:3000](http://localhost:3000) to view it in your browser.
-The page will reload if you make edits.\
-You will also see any lint errors in the console.
+The page will reload when you make changes.\
+You may also see any lint errors in the console.
-### `yarn test`
+### `npm test`
Launches the test runner in the interactive watch mode.\
See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information.
-### `yarn build`
+### `npm run build`
Builds the app for production to the `build` folder.\
It correctly bundles React in production mode and optimizes the build for the best performance.
@@ -29,15 +29,15 @@ Your app is ready to be deployed!
See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information.
-### `yarn eject`
+### `npm run eject`
-**Note: this is a one-way operation. Once you `eject`, you can’t go back!**
+**Note: this is a one-way operation. Once you `eject`, you can't go back!**
-If you aren’t satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project.
+If you aren't satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project.
-Instead, it will copy all the configuration files and the transitive dependencies (webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you’re on your own.
+Instead, it will copy all the configuration files and the transitive dependencies (webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you're on your own.
-You don’t have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn’t feel obligated to use this feature. However we understand that this tool wouldn’t be useful if you couldn’t customize it when you are ready for it.
+You don't have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn't feel obligated to use this feature. However we understand that this tool wouldn't be useful if you couldn't customize it when you are ready for it.
## Learn More
@@ -65,6 +65,6 @@ This section has moved here: [https://facebook.github.io/create-react-app/docs/a
This section has moved here: [https://facebook.github.io/create-react-app/docs/deployment](https://facebook.github.io/create-react-app/docs/deployment)
-### `yarn build` fails to minify
+### `npm run build` fails to minify
This section has moved here: [https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify](https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify)
diff --git a/supports/create-react-app/package.json b/supports/create-react-app/package.json
index 81bcc76..e075974 100644
--- a/supports/create-react-app/package.json
+++ b/supports/create-react-app/package.json
@@ -1,19 +1,19 @@
{
- "name": "react-papaparse",
+ "name": "create-react-app",
"version": "0.1.0",
"private": true,
"dependencies": {
- "@testing-library/jest-dom": "^5.11.4",
- "@testing-library/react": "^11.1.0",
- "@testing-library/user-event": "^12.1.10",
+ "@testing-library/jest-dom": "^5.16.1",
+ "@testing-library/react": "^12.1.2",
+ "@testing-library/user-event": "^13.5.0",
"react": "^17.0.2",
"react-dom": "^17.0.2",
- "react-scripts": "4.0.3",
- "web-vitals": "^1.0.1",
- "react-papaparse": "file:../.."
+ "react-papaparse": "file:../..",
+ "react-scripts": "5.0.0",
+ "web-vitals": "^2.1.2"
},
"scripts": {
- "start": "react-scripts start",
+ "dev": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject"
diff --git a/supports/create-react-app/src/App.js b/supports/create-react-app/src/App.js
index 3db223c..3b8068d 100644
--- a/supports/create-react-app/src/App.js
+++ b/supports/create-react-app/src/App.js
@@ -1,26 +1,11 @@
-import React, { useState } from 'react';
-import { CSVReader, CSVDownloader } from 'react-papaparse'
+import React, { useState } from 'react'
+import { CSVReader, CSVDownloader, readString } from 'react-papaparse'
-const buttonRef= React.createRef()
-
-function App() {
- const [isReset, setIsReset] = useState(true);
+export default function App() {
+ const [isReset, setIsReset] = useState(false);
const handleReset = () => {
- setIsReset(!isReset);
- }
-
- const handleOpenDialog = (e) => {
- // Note that the ref is set async, so it might be null at some point
- if (buttonRef.current) {
- buttonRef.current.open(e)
- }
- }
-
- const handleOnFileLoad = (data) => {
- console.log('---------------------------')
- console.log(data)
- console.log('---------------------------')
+ setIsReset(!isReset)
}
const handleOnDrop = (data) => {
@@ -29,7 +14,12 @@ function App() {
console.log('---------------------------')
}
- const handleOnError = (err, file, inputElem, reason) => {
+ const handleOnError = (
+ err,
+ // file,
+ // inputElem,
+ // reason
+ ) => {
console.log(err)
}
@@ -39,83 +29,73 @@ function App() {
console.log('---------------------------')
}
- const handleRemoveFile = (e) => {
- // Note that the ref is set async, so it might be null at some point
- if (buttonRef.current) {
- buttonRef.current.removeFile(e)
- }
- }
+ const handleClick = () => {
+ const csvString = `Column 1,Column 2,Column 3,Column 4
+1-1,1-2,1-3,1-4
+2-1,2-2,2-3,2-4
+3-1,3-2,3-3,3-4
+4,5,6,7`;
+
+ readString(csvString, {
+ worker: true,
+ complete: (results) => {
+ console.log('---------------------------');
+ console.log(results);
+ console.log('---------------------------');
+ },
+ })
+ };
return (
<>
- {({ file }) => (
-
-
- Browe file
-
-
- {file && file.name}
-
-
- Remove
-
-
- )}
+ Click to upload.
+ handleReset()}>Reset
Download
- handleReset()}>Reset
+
+ Download
+
+ {
+ return [
+ {
+ "Column 1": "1-1",
+ "Column 2": "1-2",
+ "Column 3": "1-3",
+ "Column 4": "1-4",
+ }
+ ]}
+ }
+ >
+ Download
+
+ handleClick()}>readString
>
- );
+ )
}
-
-export default App;
diff --git a/test/CSVReader.spec.tsx b/test/CSVReader.spec.tsx
index 3151080..c23eabc 100644
--- a/test/CSVReader.spec.tsx
+++ b/test/CSVReader.spec.tsx
@@ -1,17 +1,21 @@
+/**
+ * @jest-environment jsdom
+ */
+
import React from 'react';
-import expect from 'expect';
+// import expect from 'expect';
import CustomReader from './CustomReader';
import renderer from 'react-test-renderer';
// eslint-disable-next-line no-undef
describe('CSVReader', () => {
// eslint-disable-next-line no-undef
- it('should match snapshot', function () {
- const handleFileUploaded = jest.fn().mockImplementation();
+ it('should match snapshot', async function () {
+ const handleUploadAccepted = jest.fn().mockImplementation();
const tree = renderer
.create(
)
diff --git a/test/CustomReader.tsx b/test/CustomReader.tsx
index 479b500..5413b3a 100644
--- a/test/CustomReader.tsx
+++ b/test/CustomReader.tsx
@@ -1,19 +1,42 @@
import React from 'react';
-import { CSVReader } from '../src/react-papaparse';
+import { useCSVReader } from '../src/react-papaparse';
interface CustomReaderProps {
- onFileLoaded?: ((data: any, file?: any) => void) | undefined;
+ onUploadAccepted?: ((data: any, file?: any) => void) | undefined;
label: string;
}
const CustomReader: React.FC = (
props: CustomReaderProps
) => {
- const { onFileLoaded, label } = props;
+ const { CSVReader } = useCSVReader();
+ const { onUploadAccepted, label } = props;
return (
-
- {label}
+
+ {({
+ getRootProps,
+ acceptedFile,
+ ProgressBar,
+ getRemoveFileProps,
+ }: any) => (
+ <>
+
+
+ {label}
+
+
+ {acceptedFile && acceptedFile.name}
+
+
+ Remove
+
+
+
+ >
+ )}
);
};
diff --git a/test/__snapshots__/CSVReader.spec.tsx.snap b/test/__snapshots__/CSVReader.spec.tsx.snap
index 67ff1f9..1484542 100644
--- a/test/__snapshots__/CSVReader.spec.tsx.snap
+++ b/test/__snapshots__/CSVReader.spec.tsx.snap
@@ -4,35 +4,52 @@ exports[`CSVReader should match snapshot 1`] = `
Array [
,
-
+
+ Chargez votre offre
+
+
+
+ Remove
+
+
,
+
-
- Chargez votre offre
-
- ,
+ />,
]
`;
diff --git a/test/jsonToCSV.spec.ts b/test/jsonToCSV.spec.ts
index be0a395..feac3fd 100644
--- a/test/jsonToCSV.spec.ts
+++ b/test/jsonToCSV.spec.ts
@@ -1,10 +1,10 @@
-import expect from 'expect'
-import { jsonToCSV } from '../src/jsonToCSV'
+import expect from 'expect';
+import { jsonToCSV } from '../src/jsonToCSV';
// eslint-disable-next-line no-undef
describe('jsonToCSV', () => {
// eslint-disable-next-line no-undef
- it('should return a csv format as expected', function() {
+ it('should return a csv format as expected', function () {
const fixtures = `[
{
"Column 1": "1-1",
@@ -30,14 +30,16 @@ describe('jsonToCSV', () => {
"Column 3": 6,
"Column 4": 7
}
- ]`
+ ]`;
const expected = `Column 1,Column 2,Column 3,Column 4
1-1,1-2,1-3,1-4
2-1,2-2,2-3,2-4
3-1,3-2,3-3,3-4
-4,5,6,7`
- const actual = jsonToCSV(fixtures)
- expect(typeof actual).toBe('string')
- expect(actual.split('\r\n').join('')).toEqual(expected.split('\n').join(''))
- })
-})
+4,5,6,7`;
+ const actual = jsonToCSV(fixtures);
+ expect(typeof actual).toBe('string');
+ expect(actual.split('\r\n').join('')).toEqual(
+ expected.split('\n').join('')
+ );
+ });
+});
diff --git a/test/readString.spec.ts b/test/readString.spec.ts
index 177ba79..122fb01 100644
--- a/test/readString.spec.ts
+++ b/test/readString.spec.ts
@@ -1,24 +1,28 @@
-import expect from 'expect'
-import { readString } from '../src/readString'
+import expect from 'expect';
+import { readString } from '../src/readString';
// eslint-disable-next-line no-undef
describe('readString', () => {
// eslint-disable-next-line no-undef
- it('should return an array as expected', function() {
+ it('should return an array as expected', function () {
const fixtures = `Column 1,Column 2,Column 3,Column 4
1-1,1-2,1-3,1-4
2-1,2-2,2-3,2-4
3-1,3-2,3-3,3-4
-4,5,6,7`
+4,5,6,7`;
const expected = [
['Column 1', 'Column 2', 'Column 3', 'Column 4'],
['1-1', '1-2', '1-3', '1-4'],
['2-1', '2-2', '2-3', '2-4'],
['3-1', '3-2', '3-3', '3-4'],
- ['4', '5', '6', '7']
- ]
- const actual = readString(fixtures)
- expect(Array.isArray(actual.data)).toBe(true)
- expect(actual.data).toEqual(expected)
- })
-})
+ ['4', '5', '6', '7'],
+ ];
+ readString(fixtures, {
+ worker: true,
+ complete: (results) => {
+ expect(Array.isArray(results.data)).toBe(true);
+ expect(results.data).toEqual(expected);
+ },
+ });
+ });
+});
diff --git a/test/utils.spec.ts b/test/utils.spec.ts
index de4cd30..884baf7 100644
--- a/test/utils.spec.ts
+++ b/test/utils.spec.ts
@@ -1,11 +1,11 @@
import expect from 'expect'
-import getSize from '../src/utils'
+import { formatFileSize } from '../src/utils'
// eslint-disable-next-line no-undef
describe('util', () => {
// eslint-disable-next-line no-undef
it('should return 1 KB', function() {
- const strSize = getSize(1000)
+ const strSize = formatFileSize(1000)
expect(strSize).toEqual('1 KB')
})
})
diff --git a/tsconfig.json b/tsconfig.json
index 87f4949..db89097 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -70,5 +70,6 @@
"docs",
"supports",
"setup.ts",
+ "examples",
]
}