Repository: thecodejack/svelte-file-dropzone
Branch: master
Commit: cedd55fd2ad0
Files: 44
Total size: 38.9 KB
Directory structure:
gitextract_nknodkyk/
├── .gitignore
├── .prettierrc
├── .storybook/
│ ├── main.js
│ └── preview-head.html
├── README.md
├── TODO
├── demo/
│ ├── .gitignore
│ ├── .npmrc
│ ├── .prettierignore
│ ├── .prettierrc
│ ├── README.md
│ ├── package.json
│ ├── src/
│ │ ├── app.d.ts
│ │ ├── app.html
│ │ ├── index.test.ts
│ │ ├── lib/
│ │ │ └── index.ts
│ │ └── routes/
│ │ ├── basic/
│ │ │ └── +page.svelte
│ │ ├── custom-props/
│ │ │ └── +page.svelte
│ │ ├── form/
│ │ │ ├── +page.server.ts
│ │ │ └── +page.svelte
│ │ ├── reactive-disabled/
│ │ │ └── +page.svelte
│ │ └── toggle-multiple/
│ │ └── +page.svelte
│ ├── svelte.config.js
│ ├── tsconfig.json
│ └── vite.config.ts
├── jsconfig.json
├── package.json
├── renovate.json
├── rollup.config.js
├── src/
│ └── lib/
│ ├── components/
│ │ └── Dropzone.svelte
│ ├── index.ts
│ └── utils/
│ ├── attr-accept.js
│ └── index.js
└── stories/
├── 1-dropzone.stories.js
├── 2-advanced-dropzone.stories.js
├── helpers.js
└── views/
├── BasicDropzoneAcceptImagesView.svelte
├── BasicDropzoneView.svelte
├── CustomSlotDropzoneView.svelte
├── DisabledDropzoneView.svelte
├── FullyFeaturedDropzoneView.svelte
├── NoClickDropzoneView.svelte
├── NoDragDropzoneView.svelte
└── WorkingCSVFileUploadView.svelte
================================================
FILE CONTENTS
================================================
================================================
FILE: .gitignore
================================================
node_modules
dist
storybook-static
package-lock.json
.DS_Store
.svelte-kit
================================================
FILE: .prettierrc
================================================
{
"useTabs": false,
"singleQuote": false,
"trailingComma": "none",
"printWidth": 100,
"plugins": ["prettier-plugin-svelte"],
"overrides": [{ "files": "*.svelte", "options": { "parser": "svelte" } }],
"arrowParens": "avoid"
}
================================================
FILE: .storybook/main.js
================================================
export default {
stories: ["../stories/**/*.stories.js"],
addons: ["@storybook/addon-links", "@storybook/addon-essentials", "@storybook/addon-interactions"],
framework: {
name: "@storybook/sveltekit",
options: {},
},
docs: {
autodocs: true,
},
};
================================================
FILE: .storybook/preview-head.html
================================================
================================================
FILE: README.md
================================================
# svelte-file-dropzone
[](https://www.npmjs.com/package/svelte-file-dropzone)
[](https://www.npmjs.com/package/svelte-file-dropzone)
SvelteJS component for file upload and dropzone.The component is Svelte implementation of [react-dropzone](https://github.com/react-dropzone/react-dropzone).
## Demo
[Click here for Storybook link](https://svelte-file-dropzone.netlify.app/?path=/info/examples--basic-dropzone)
## Installation
```
npm install svelte-file-dropzone
or
yarn add svelte-file-dropzone
```
## Usage
```svelte
{#each files.accepted as item}
- {item.name}
{/each}
```
## API
### Props
| Prop Name | Description | Default Value |
| --------------------- | ---------------------------------------------------------------------------------------- | ------------- |
| accept | Set accepted file types. See https://github.com/okonet/attr-accept for more information. | undefined |
| disabled | | false |
| maxSize | | Infinity |
| minSize | | 0 |
| multiple | if true, multiple files can be selected at once | true |
| preventDropOnDocument | 1231 | true |
| noClick | disable click events | false |
| noKeyboard | disable keyboard events | false |
| noDrag | disable drag events | false |
| containerClasses | custom container classes | "" |
| containerStyles | custom inline container styles | "" |
| disableDefaultStyles | don't apply default styles to container | false |
| inputElement | reference to inputElement | undefined |
| required | html5 required attribute added to input | false |
### Events
| Event Name | Description | `event.detail` info |
| ---------------- | ----------- | -------------------------------------- |
| dragenter | | `{dragEvent: event}` |
| dragover | | `{dragEvent: event}` |
| dragleave | | `{dragEvent: event}` |
| drop | | `{acceptedFiles,fileRejections,event}` |
| filedropped | | `{event}` |
| droprejected | | `{fileRejections,event}` |
| dropaccepted | | `{acceptedFiles,event}` |
| filedialogcancel | | |
### Examples
[Click here](https://github.com/thecodejack/svelte-file-dropzone/tree/master/stories/views) to view stories implementation
## Credits
Component is reimplementation [react-dropzone](https://github.com/react-dropzone/react-dropzone). Complete credit goes to author and contributors of [react-dropzone](https://github.com/react-dropzone/react-dropzone).
## License
MIT
================================================
FILE: TODO
================================================
Todo:
✔ Git repo @done(20-06-17 21:58)
✔ npm initial publish @done(20-06-18 22:34)
✔ setup netlify @done(20-06-17 22:55)
✔ Fix README.md @done(20-06-17 22:28)
✔ Add README.md to Notes of storybook @done(20-06-18 22:34)
✔ Build setup @done(20-06-17 22:33)
☐ Advanced Examples
☐ Custom Dropzone with delete file etc
✔ CustomDropzone with different drop behaviour @done(20-06-18 22:34)
================================================
FILE: demo/.gitignore
================================================
.DS_Store
node_modules
/build
/.svelte-kit
/package
.env
.env.*
!.env.example
vite.config.js.timestamp-*
vite.config.ts.timestamp-*
================================================
FILE: demo/.npmrc
================================================
engine-strict=true
================================================
FILE: demo/.prettierignore
================================================
# Ignore files for PNPM, NPM and YARN
pnpm-lock.yaml
package-lock.json
yarn.lock
================================================
FILE: demo/.prettierrc
================================================
{
"useTabs": true,
"singleQuote": true,
"trailingComma": "none",
"printWidth": 100,
"plugins": ["prettier-plugin-svelte"],
"overrides": [{ "files": "*.svelte", "options": { "parser": "svelte" } }]
}
================================================
FILE: demo/README.md
================================================
# create-svelte
Everything you need to build a Svelte project, powered by [`create-svelte`](https://github.com/sveltejs/kit/tree/main/packages/create-svelte).
## Creating a project
If you're seeing this, you've probably already done this step. Congrats!
```bash
# create a new project in the current directory
npm create svelte@latest
# create a new project in my-app
npm create svelte@latest my-app
```
## Developing
Once you've created a project and installed dependencies with `npm install` (or `pnpm install` or `yarn`), start a development server:
```bash
npm run dev
# or start the server and open the app in a new browser tab
npm run dev -- --open
```
## Building
To create a production version of your app:
```bash
npm run build
```
You can preview the production build with `npm run preview`.
> To deploy your app, you may need to install an [adapter](https://kit.svelte.dev/docs/adapters) for your target environment.
================================================
FILE: demo/package.json
================================================
{
"name": "demo",
"version": "0.0.1",
"private": true,
"scripts": {
"dev": "vite dev",
"build": "vite build",
"preview": "vite preview",
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
"test": "vitest",
"lint": "prettier --check .",
"format": "prettier --write ."
},
"devDependencies": {
"@sveltejs/adapter-auto": "^3.0.0",
"@sveltejs/kit": "^2.5.5",
"@sveltejs/vite-plugin-svelte": "^3.0.0",
"prettier": "^3.1.1",
"prettier-plugin-svelte": "^3.1.2",
"svelte": "^4.2.12",
"svelte-check": "^3.6.0",
"tslib": "^2.4.1",
"typescript": "^5.0.0",
"vite": "^5.0.3",
"vitest": "^1.0.0"
},
"type": "module"
}
================================================
FILE: demo/src/app.d.ts
================================================
// See https://kit.svelte.dev/docs/types#app
// for information about these interfaces
declare global {
namespace App {
// interface Error {}
// interface Locals {}
// interface PageData {}
// interface PageState {}
// interface Platform {}
}
}
export {};
================================================
FILE: demo/src/app.html
================================================
%sveltekit.head%
%sveltekit.body%
================================================
FILE: demo/src/index.test.ts
================================================
import { describe, it, expect } from 'vitest';
describe('sum test', () => {
it('adds 1 + 2 to equal 3', () => {
expect(1 + 2).toBe(3);
});
});
================================================
FILE: demo/src/lib/index.ts
================================================
// place files you want to import through the `$lib` alias in this folder.
================================================
FILE: demo/src/routes/basic/+page.svelte
================================================
{#each files.accepted as item}
- {item.name}
{/each}
================================================
FILE: demo/src/routes/custom-props/+page.svelte
================================================
{#each files.accepted as item}
- {item.name}
{/each}
================================================
FILE: demo/src/routes/form/+page.server.ts
================================================
export const actions = {
async postFiles({ request }) {
const formData: FormData = await request.formData();
const files = formData.getAll('files');
return { files: files.map((f) => (f as any).name) };
}
};
================================================
FILE: demo/src/routes/form/+page.svelte
================================================
Files about to upload:
{#each files as file}
- {file.name}
{/each}
Files posted to form action:
{#each $page.form?.files ?? [] as file}
- {file}
{/each}
================================================
FILE: demo/src/routes/reactive-disabled/+page.svelte
================================================
================================================
FILE: demo/src/routes/toggle-multiple/+page.svelte
================================================
================================================
FILE: demo/svelte.config.js
================================================
import adapter from '@sveltejs/adapter-auto';
import { vitePreprocess } from '@sveltejs/vite-plugin-svelte';
/** @type {import('@sveltejs/kit').Config} */
const config = {
// Consult https://kit.svelte.dev/docs/integrations#preprocessors
// for more information about preprocessors
preprocess: vitePreprocess(),
kit: {
// adapter-auto only supports some environments, see https://kit.svelte.dev/docs/adapter-auto for a list.
// If your environment is not supported or you settled on a specific environment, switch out the adapter.
// See https://kit.svelte.dev/docs/adapters for more information about adapters.
adapter: adapter()
}
};
export default config;
================================================
FILE: demo/tsconfig.json
================================================
{
"extends": "./.svelte-kit/tsconfig.json",
"compilerOptions": {
"allowJs": true,
"checkJs": true,
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"resolveJsonModule": true,
"skipLibCheck": true,
"sourceMap": true,
"strict": true,
"moduleResolution": "bundler"
}
// Path aliases are handled by https://kit.svelte.dev/docs/configuration#alias
//
// If you want to overwrite includes/excludes, make sure to copy over the relevant includes/excludes
// from the referenced tsconfig.json - TypeScript does not merge them in
}
================================================
FILE: demo/vite.config.ts
================================================
import { sveltekit } from '@sveltejs/kit/vite';
import { defineConfig } from 'vitest/config';
export default defineConfig({
plugins: [sveltekit()],
test: {
include: ['src/**/*.{test,spec}.{js,ts}']
}
});
================================================
FILE: jsconfig.json
================================================
{}
================================================
FILE: package.json
================================================
{
"name": "svelte-file-dropzone",
"version": "2.0.9",
"description": "Svelte component for fileupload and file dropzone",
"scripts": {
"package": "svelte-kit sync && svelte-package && publint",
"prepublishOnly": "npm run package",
"test": "echo \"Error: no test specified\" && exit 1",
"storybook": "storybook dev -p 6006",
"build-storybook": "storybook build"
},
"exports": {
".": {
"types": "./dist/index.d.ts",
"svelte": "./dist/index.js"
}
},
"typesVersions": {
">4.0": {
"Dropzone.svelte": [
"./dist/components/Dropzone.svelte.d.ts"
]
}
},
"repository": {
"url": "https://github.com/thecodejack/svelte-file-dropzone"
},
"author": "thecodejack",
"license": "MIT",
"dependencies": {
"file-selector": "^0.6.0"
},
"peerDependencies": {
"svelte": "^3.54.0 || ^4.0.0 || ^5"
},
"devDependencies": {
"@storybook/addon-essentials": "^7.6.6",
"@storybook/addon-interactions": "^7.6.6",
"@storybook/addon-links": "^7.6.6",
"@storybook/addons": "7.6.6",
"@storybook/svelte": "7.6.6",
"@storybook/sveltekit": "^7.6.6",
"@sveltejs/kit": "^1.30.4",
"@sveltejs/package": "^2.2.5",
"babel-loader": "9.1.3",
"publint": "^0.2.7",
"storybook": "^7.6.6",
"svelte": "^4.2.12",
"vite": "^4.5.1"
},
"keywords": [
"svelte",
"svelte3",
"svelte-components",
"upload",
"dropzone",
"svelte-file-dropzone",
"svelte-dropzone",
"sveltejs"
],
"files": [
"src",
"dist",
"!dist/**/*.test.*",
"!dist/**/*.spec.*"
],
"svelte": "./dist/index.js",
"types": "./dist/index.d.ts",
"type": "module"
}
================================================
FILE: renovate.json
================================================
{
"extends": [
"config:base"
]
}
================================================
FILE: rollup.config.js
================================================
import svelte from "rollup-plugin-svelte";
import resolve from "@rollup/plugin-node-resolve";
import commonjs from "@rollup/plugin-commonjs";
import pkg from "./package.json";
const name = pkg.name
.replace(/^(@\S+\/)?(svelte-)?(\S+)/, "$3")
.replace(/^\w/, (m) => m.toUpperCase())
.replace(/-\w/g, (m) => m[1].toUpperCase());
export default {
input: "src/index.svelte",
output: [
{ file: pkg.module, format: "es" },
{ file: pkg.main, format: "umd", name },
],
plugins: [svelte(), resolve(), commonjs()],
};
================================================
FILE: src/lib/components/Dropzone.svelte
================================================
================================================
FILE: src/lib/index.ts
================================================
import Dropzone from "./components/Dropzone.svelte";
export default Dropzone
================================================
FILE: src/lib/utils/attr-accept.js
================================================
/**
* 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 default function(file, acceptedFiles) {
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) => {
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: src/lib/utils/index.js
================================================
import accepts from "./attr-accept";
// 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";
// File Errors
export const getInvalidTypeRejectionErr = (accept) => {
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}`,
};
};
export const getTooLargeRejectionErr = (maxSize) => {
return {
code: FILE_TOO_LARGE,
message: `File is larger than ${maxSize} bytes`,
};
};
export const getTooSmallRejectionErr = (minSize) => {
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",
};
// 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, accept) {
const isAcceptable =
file.type === "application/x-moz-file" || accepts(file, accept);
return [
isAcceptable,
isAcceptable ? null : getInvalidTypeRejectionErr(accept),
];
}
export function fileMatchSize(file, minSize, maxSize) {
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) {
return value !== undefined && value !== null;
}
export function allFilesAccepted({
files,
accept,
minSize,
maxSize,
multiple,
}) {
if (!multiple && files.length > 1) {
return false;
}
return files.every((file) => {
const [accepted] = fileAccepted(file, accept);
const [sizeMatch] = fileMatchSize(file, minSize, maxSize);
return accepted && sizeMatch;
});
}
// 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) {
if (typeof event.isPropagationStopped === "function") {
return event.isPropagationStopped();
} else if (typeof event.cancelBubble !== "undefined") {
return event.cancelBubble;
}
return false;
}
export function isEvtWithFiles(event) {
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"
);
}
export function isKindFile(item) {
return typeof item === "object" && item !== null && item.kind === "file";
}
function isIe(userAgent) {
return (
userAgent.indexOf("MSIE") !== -1 || userAgent.indexOf("Trident/") !== -1
);
}
function isEdge(userAgent) {
return userAgent.indexOf("Edge/") !== -1;
}
export function isIeOrEdge(userAgent = window.navigator.userAgent) {
return isIe(userAgent) || isEdge(userAgent);
}
/**
* 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) {
return (event, ...args) =>
fns.some((fn) => {
if (!isPropagationStopped(event) && fn) {
fn(event, ...args);
}
return isPropagationStopped(event);
});
}
================================================
FILE: stories/1-dropzone.stories.js
================================================
//import { action } from "@storybook/addon-actions";
import BasicDropZoneView from "./views/BasicDropzoneView.svelte";
import BasicDropZoneViewSource from "./views/BasicDropzoneView.svelte?raw";
import DisabledDropzoneView from "./views/DisabledDropzoneView.svelte";
import DisabledDropzoneViewSource from "./views/DisabledDropzoneView.svelte?raw";
import CustomSlotDropzoneView from "./views/CustomSlotDropzoneView.svelte";
import CustomSlotDropzoneViewSource from "./views/CustomSlotDropzoneView.svelte?raw";
import NoClickDropZoneView from "./views/NoClickDropzoneView.svelte";
import NoClickDropZoneViewSource from "./views/NoClickDropzoneView.svelte?raw";
import NoDragDropzoneView from "./views/NoDragDropzoneView.svelte";
import NoDragDropzoneViewSource from "./views/NoDragDropzoneView.svelte?raw";
import { sourceParameters } from "./helpers";
export default {
title: "Examples",
component: null,
};
export const BasicDropzone = {
...sourceParameters(BasicDropZoneViewSource),
render: () => ({
Component: BasicDropZoneView,
}),
};
export const DisabledDropzone = {
...sourceParameters(DisabledDropzoneViewSource),
render: () => ({
Component: DisabledDropzoneView,
}),
};
export const CustomSlotDropzone = {
...sourceParameters(CustomSlotDropzoneViewSource),
render: () => ({
Component: CustomSlotDropzoneView,
}),
};
export const DropZoneWithClickDisabled = {
...sourceParameters(NoClickDropZoneViewSource),
render: () => ({
Component: NoClickDropZoneView,
}),
};
export const DropZoneWithDragDisabled = {
...sourceParameters(NoDragDropzoneViewSource),
render: () => ({
Component: NoDragDropzoneView,
}),
};
================================================
FILE: stories/2-advanced-dropzone.stories.js
================================================
import { action } from "@storybook/addon-actions";
import WorkingCSVFileUploadView from "./views/WorkingCSVFileUploadView.svelte";
import WorkingCSVFileUploadViewSource from "./views/WorkingCSVFileUploadView.svelte?raw";
import FullyFeaturedDropzoneView from "./views/FullyFeaturedDropzoneView.svelte";
import FullyFeaturedDropzoneViewSource from "./views/FullyFeaturedDropzoneView.svelte?raw";
import { sourceParameters } from "./helpers";
export default {
title: "Advanced Examples",
component: null,
};
export const WorkingCSVFileUploadDropzone = {
...sourceParameters(WorkingCSVFileUploadViewSource),
render: () => ({
Component: WorkingCSVFileUploadView,
}),
};
export const FullyFeaturedImagesDropzone = {
...sourceParameters(FullyFeaturedDropzoneViewSource),
render: () => ({
Component: FullyFeaturedDropzoneView,
}),
};
================================================
FILE: stories/helpers.js
================================================
export const sourceParameters = (source) => ({
parameters: {
docs: {
source: {
code: source,
},
},
},
});
================================================
FILE: stories/views/BasicDropzoneAcceptImagesView.svelte
================================================
{#each files.accepted as item}
- {item.name}
{/each}
================================================
FILE: stories/views/BasicDropzoneView.svelte
================================================
{#each files.accepted as item}
- {item.name}
{/each}
================================================
FILE: stories/views/CustomSlotDropzoneView.svelte
================================================
Custom Slot Dropzone
{#each files.accepted as item}
- {item.name}
{/each}
================================================
FILE: stories/views/DisabledDropzoneView.svelte
================================================
{#each files.accepted as item}
- {item.name}
{/each}
================================================
FILE: stories/views/FullyFeaturedDropzoneView.svelte
================================================
or
Drag and drop them here
{#if files.accepted.length > 0}
{/if}
{#each files.accepted as item, index}
{item.name}
{/each}
================================================
FILE: stories/views/NoClickDropzoneView.svelte
================================================
{#each files.accepted as item}
- {item.name}
{/each}
================================================
FILE: stories/views/NoDragDropzoneView.svelte
================================================
{#each files.accepted as item}
- {item.name}
{/each}
================================================
FILE: stories/views/WorkingCSVFileUploadView.svelte
================================================
Upload CSV file and preview same
{#each files as item}
{item.name}
{/each}
{#each fileData as row}
{#each row as item}
| {item} |
{/each}
{/each}