Skip to content

feat: make openFilePicker function return a promise, add detection of picker cancellation #108

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 27 additions & 14 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,19 @@ export default function App() {

return (
<div>
<button onClick={() => openFilePicker()}>Select files</button>
<button
onClick={async () => {
// you can await for the openFilePicker to get the selection result!
const result = await openFilePicker({ detectCancellation: 'promiseResultOnly' }); // you can pass overrides to the openFilePicker function
if (result.errors.length) {
console.log(result.errors); // if detectCancellation is not false and user cancels the selection, the errors array will contain the SelectionCancelledError
return;
}
console.log(result); // if user selects files, the result object will contain the filesContent, plainFiles and errors arrays
}}
>
Select files
</button>
<br />
{filesContent.map((file, index) => (
<div key={index}>
Expand Down Expand Up @@ -344,19 +356,20 @@ const Imperative = () => {

### Props

| Prop name | Description | Default value | Example values |
| ------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------- | ------------------------------------------------ |
| multiple | Allow user to pick multiple files at once | true | true, false |
| accept | Set type of files that user can choose from the list | "\*" | [".png", ".txt"], "image/\*", ".txt" |
| readAs | Set a return type of [filesContent](#returns) | "Text" | "DataURL", "Text", "BinaryString", "ArrayBuffer" |
| readFilesContent | Ignores files content and omits reading process if set to false | true | true, false |
| validators | Add validation logic. You can use some of the [built-in validators](#built-in-validators) like FileAmountLimitValidator or create your own [custom validation](#custom-validation) logic | [] | [MyValidator, MySecondValidator] |
| initializeWithCustomParameters | allows for customization of the input element that is created by the file picker. It accepts a function that takes in the input element as a parameter and can be used to set any desired attributes or styles on the element. | n/a | (input) => input.setAttribute("disabled", "") |
| encoding | Specifies the encoding to use when reading text files. Only applicable when readAs is set to "Text". Available options include all standard encodings. | "utf-8" | "latin1", "utf-8", "windows-1252" |
| onFilesSelected | A callback function that is called when files are successfully selected. The function is passed an array of objects with information about each successfully selected file | n/a | (data) => console.log(data) |
| onFilesSuccessfullySelected | A callback function that is called when files are successfully selected. The function is passed an array of objects with information about each successfully selected file | n/a | (data) => console.log(data) |
| onFilesRejected | A callback function that is called when files are rejected due to validation errors or other issues. The function is passed an array of objects with information about each rejected file | n/a | (data) => console.log(data) |
| onClear | A callback function that is called when the selection is cleared. | n/a | () => console.log('selection cleared') |
| Prop name | Description | Default value | Example values |
| ------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------- | ------------------------------------------------------- |
| multiple | Allow user to pick multiple files at once | true | true, false |
| accept | Set type of files that user can choose from the list | "\*" | [".png", ".txt"], "image/\*", ".txt" |
| readAs | Set a return type of [filesContent](#returns) | "Text" | "DataURL", "Text", "BinaryString", "ArrayBuffer" |
| readFilesContent | Ignores files content and omits reading process if set to false | true | true, false |
| validators | Add validation logic. You can use some of the [built-in validators](#built-in-validators) like FileAmountLimitValidator or create your own [custom validation](#custom-validation) logic | [] | [MyValidator, MySecondValidator] |
| initializeWithCustomParameters | allows for customization of the input element that is created by the file picker. It accepts a function that takes in the input element as a parameter and can be used to set any desired attributes or styles on the element. | n/a | (input) => input.setAttribute("disabled", "") |
| encoding | Specifies the encoding to use when reading text files. Only applicable when readAs is set to "Text". Available options include all standard encodings. | "utf-8" | "latin1", "utf-8", "windows-1252" |
| onFilesSelected | A callback function that is called when files are successfully selected. The function is passed an array of objects with information about each successfully selected file | n/a | (data) => console.log(data) |
| onFilesSuccessfullySelected | A callback function that is called when files are successfully selected. The function is passed an array of objects with information about each successfully selected file | n/a | (data) => console.log(data) |
| onFilesRejected | A callback function that is called when files are rejected due to validation errors or other issues. The function is passed an array of objects with information about each rejected file | n/a | (data) => console.log(data) |
| onClear | A callback function that is called when the selection is cleared. | n/a | () => console.log('selection cleared') |
| detectCancellation | Whether to return an error when user cancels the file picker. Has two possible modes: "promiseResultOnly" or "promiseResultAndHookState". When set to "promiseResultOnly", the error will be returned as part of the errors array returned from the openFilePicker async function. When set to "promiseResultAndHookState", the error will also be appended to the errors array returned from the hook call | false | "promiseResultOnly", "promiseResultAndHookState", false |

### Returns

Expand Down
13 changes: 9 additions & 4 deletions packages/example/src/UseFilePicker.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -39,10 +39,8 @@ export const UseFilePicker = () => {
readAs: 'Text', // availible formats: "Text" | "BinaryString" | "ArrayBuffer" | "DataURL"
initializeWithCustomParameters(inputElement) {
inputElement.webkitdirectory = selectionMode === 'dir';
inputElement.addEventListener('cancel', () => {
alert('cancel');
});
},
detectCancellation: 'promiseResultOnly',
// accept: ['.png', '.jpeg', '.heic'],
// readFilesContent: false, // ignores file content,
validators: [
Expand Down Expand Up @@ -73,7 +71,14 @@ export const UseFilePicker = () => {
<button onClick={() => setSelectionMode(selectionMode === 'file' ? 'dir' : 'file')}>
{selectionMode === 'file' ? 'FILE' : 'DIR'}
</button>
<button onClick={async () => openFilePicker()}>Select file</button>
<button
onClick={async () => {
const f = await openFilePicker();
console.log(f);
}}
>
Select file
</button>
<button onClick={() => clear()}>Clear</button>
<br />
Amount of selected files:
Expand Down
9 changes: 8 additions & 1 deletion packages/example/src/UseImperativeFilePicker.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,14 @@ export const UseImperativeFilePicker = () => {
<button onClick={() => setSelectionMode(selectionMode === 'file' ? 'dir' : 'file')}>
{selectionMode === 'file' ? 'FILE' : 'DIR'}
</button>
<button onClick={async () => openFilePicker()}>Select file</button>
<button
onClick={async () => {
const f = await openFilePicker();
console.log(f);
}}
>
Select file
</button>
<button onClick={() => clear()}>Clear</button>
<br />
Amount of selected files:
Expand Down
2 changes: 1 addition & 1 deletion packages/use-file-picker/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "use-file-picker",
"description": "Simple react hook to open browser file selector.",
"version": "2.1.4",
"version": "2.2.0",
"license": "MIT",
"author": "Milosz Jankiewicz, Kamil Planer",
"homepage": "https://github.com/Jaaneek/useFilePicker",
Expand Down
76 changes: 43 additions & 33 deletions packages/use-file-picker/src/helpers/openFileDialog.ts
Original file line number Diff line number Diff line change
@@ -1,41 +1,51 @@
export function openFileDialog(
import type { SelectionCancelledError } from '../interfaces.js';

export async function openFileDialog(
accept: string,
multiple: boolean,
callback: (arg: Event) => void,
initializeWithCustomAttributes?: (arg: HTMLInputElement) => void
): void {
// this function must be called from a user
// activation event (ie an onclick event)
) {
return new Promise<void>((resolve, reject) => {
// this function must be called from a user
// activation event (ie an onclick event)

// Create an input element
const inputElement = document.createElement('input');
// Hide element and append to body (required to run on iOS safari)
inputElement.style.display = 'none';
document.body.appendChild(inputElement);
// Set its type to file
inputElement.type = 'file';
// Set accept to the file types you want the user to select.
// Include both the file extension and the mime type
// if accept is "*" then dont set the accept attribute
if (accept !== '*') inputElement.accept = accept;
// Accept multiple files
inputElement.multiple = multiple;
// set onchange event to call callback when user has selected file
//inputElement.addEventListener('change', callback);
inputElement.addEventListener('change', arg => {
callback(arg);
// remove element
document.body.removeChild(inputElement);
});
// Create an input element
const inputElement = document.createElement('input');
// Hide element and append to body (required to run on iOS safari)
inputElement.style.display = 'none';
document.body.appendChild(inputElement);
// Set its type to file
inputElement.type = 'file';
// Set accept to the file types you want the user to select.
// Include both the file extension and the mime type
// if accept is "*" then dont set the accept attribute
if (accept !== '*') inputElement.accept = accept;
// Accept multiple files
inputElement.multiple = multiple;
// set onchange event to call callback when user has selected file
//inputElement.addEventListener('change', callback);
inputElement.addEventListener('change', arg => {
callback(arg);
// remove element
document.body.removeChild(inputElement);
resolve();
});

inputElement.addEventListener('cancel', () => {
// remove element
document.body.removeChild(inputElement);
});
inputElement.addEventListener('cancel', () => {
// remove element
document.body.removeChild(inputElement);
const error: SelectionCancelledError = {
name: 'SelectionCancelledError',
reason: 'SELECTION_CANCELLED',
};
reject(error);
});

if (initializeWithCustomAttributes) {
initializeWithCustomAttributes(inputElement);
}
// dispatch a click event to open the file dialog
inputElement.dispatchEvent(new MouseEvent('click'));
if (initializeWithCustomAttributes) {
initializeWithCustomAttributes(inputElement);
}
// dispatch a click event to open the file dialog
inputElement.dispatchEvent(new MouseEvent('click'));
});
}
Loading