[READ-ONLY] Mirror of https://github.com/probablykasper/svelte-droplet. File dropzone for Svelte svelte-droplet.kasper.space
file-drop svelte typescript
0

Configure Feed

Select the types of activity you want to include in your feed.

Add `droppable` slot prop

Kasper (Mar 24, 2022, 3:38 AM +0100) c8788834 f771d016

+281 -26
+44
README.md
··· 2 2 3 3 File dropzone for Svelte 4 4 5 + ## Usage 6 + 7 + ```svelte 8 + <script lang="ts"> 9 + function handleFiles(files: File[]) { 10 + console.log(file.name) 11 + } 12 + </script> 13 + <FileDrop {handleFiles} let:droppable> 14 + <div class="zone" class:droppable> 15 + Select or drop files here 16 + </div> 17 + </FileDrop> 18 + ``` 19 + 20 + ## Dev instructions 21 + 22 + ### Get started 23 + 24 + 1. Install Node.js (v14 works) 25 + 2. Run `npm install` 26 + 5 27 ## Commands 6 28 7 29 - `npm run dev`: Start in dev mode ··· 9 31 - `npm run preview`: Preview production app 10 32 - `npm run lint`: Lint 11 33 - `npm run format`: Format 34 + 35 + ### Publish new version 36 + 37 + 1. Update `CHANGELOG.md` 38 + 2. Check for errors 39 + ``` 40 + npm run check 41 + ``` 42 + 3. Bump the version number 43 + ``` 44 + npm version --no-git-tag <version> 45 + ``` 46 + 4. Generate the package 47 + ``` 48 + npm run build:package 49 + ``` 50 + 5. Publish the package 51 + ``` 52 + npm publish ./package 53 + ``` 54 + 6. Commit with a tag in format "v#.#.#" 55 + 7. Create GitHub release with release notes
+138
package-lock.json
··· 5 5 "packages": { 6 6 "": { 7 7 "name": "svelte-droplet", 8 + "license": "MIT", 8 9 "devDependencies": { 9 10 "@sveltejs/adapter-static": "^1.0.0-next.29", 10 11 "@sveltejs/kit": "^1.0.0-next.303", ··· 19 20 "svelte": "^3.46.4", 20 21 "svelte-check": "^2.4.6", 21 22 "svelte-preprocess": "^4.10.4", 23 + "svelte2tsx": "^0.5.6", 22 24 "typescript": "^4.6.2" 23 25 } 24 26 }, ··· 629 631 "optional": true 630 632 } 631 633 } 634 + }, 635 + "node_modules/dedent-js": { 636 + "version": "1.0.1", 637 + "resolved": "https://registry.npmjs.org/dedent-js/-/dedent-js-1.0.1.tgz", 638 + "integrity": "sha1-vuX7fJ5yfYXf+iRZDRDsGrElUwU=", 639 + "dev": true 632 640 }, 633 641 "node_modules/deep-is": { 634 642 "version": "0.1.4", ··· 1674 1682 "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", 1675 1683 "dev": true 1676 1684 }, 1685 + "node_modules/lower-case": { 1686 + "version": "2.0.2", 1687 + "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-2.0.2.tgz", 1688 + "integrity": "sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==", 1689 + "dev": true, 1690 + "dependencies": { 1691 + "tslib": "^2.0.3" 1692 + } 1693 + }, 1694 + "node_modules/lower-case/node_modules/tslib": { 1695 + "version": "2.3.1", 1696 + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz", 1697 + "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==", 1698 + "dev": true 1699 + }, 1677 1700 "node_modules/lru-cache": { 1678 1701 "version": "6.0.0", 1679 1702 "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", ··· 1792 1815 "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", 1793 1816 "dev": true 1794 1817 }, 1818 + "node_modules/no-case": { 1819 + "version": "3.0.4", 1820 + "resolved": "https://registry.npmjs.org/no-case/-/no-case-3.0.4.tgz", 1821 + "integrity": "sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==", 1822 + "dev": true, 1823 + "dependencies": { 1824 + "lower-case": "^2.0.2", 1825 + "tslib": "^2.0.3" 1826 + } 1827 + }, 1828 + "node_modules/no-case/node_modules/tslib": { 1829 + "version": "2.3.1", 1830 + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz", 1831 + "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==", 1832 + "dev": true 1833 + }, 1795 1834 "node_modules/normalize-path": { 1796 1835 "version": "3.0.0", 1797 1836 "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", ··· 1838 1877 "engines": { 1839 1878 "node": ">=6" 1840 1879 } 1880 + }, 1881 + "node_modules/pascal-case": { 1882 + "version": "3.1.2", 1883 + "resolved": "https://registry.npmjs.org/pascal-case/-/pascal-case-3.1.2.tgz", 1884 + "integrity": "sha512-uWlGT3YSnK9x3BQJaOdcZwrnV6hPpd8jFH1/ucpiLRPh/2zCVJKS19E4GvYHvaCcACn3foXZ0cLB9Wrx1KGe5g==", 1885 + "dev": true, 1886 + "dependencies": { 1887 + "no-case": "^3.0.4", 1888 + "tslib": "^2.0.3" 1889 + } 1890 + }, 1891 + "node_modules/pascal-case/node_modules/tslib": { 1892 + "version": "2.3.1", 1893 + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz", 1894 + "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==", 1895 + "dev": true 1841 1896 }, 1842 1897 "node_modules/path-is-absolute": { 1843 1898 "version": "1.0.1", ··· 2404 2459 "dev": true, 2405 2460 "dependencies": { 2406 2461 "sourcemap-codec": "^1.4.8" 2462 + } 2463 + }, 2464 + "node_modules/svelte2tsx": { 2465 + "version": "0.5.6", 2466 + "resolved": "https://registry.npmjs.org/svelte2tsx/-/svelte2tsx-0.5.6.tgz", 2467 + "integrity": "sha512-B4WZUtoTdVD+F73H1RQEH3Hrv7m2/ahThmAUkjT5CTWRigQaJqYQpSjisCH1Pzfi9B37YikDnAi4u4uxwYM+iw==", 2468 + "dev": true, 2469 + "dependencies": { 2470 + "dedent-js": "^1.0.1", 2471 + "pascal-case": "^3.1.1" 2472 + }, 2473 + "peerDependencies": { 2474 + "svelte": "^3.24", 2475 + "typescript": "^4.1.2" 2407 2476 } 2408 2477 }, 2409 2478 "node_modules/text-table": { ··· 2990 3059 "ms": "2.1.2" 2991 3060 } 2992 3061 }, 3062 + "dedent-js": { 3063 + "version": "1.0.1", 3064 + "resolved": "https://registry.npmjs.org/dedent-js/-/dedent-js-1.0.1.tgz", 3065 + "integrity": "sha1-vuX7fJ5yfYXf+iRZDRDsGrElUwU=", 3066 + "dev": true 3067 + }, 2993 3068 "deep-is": { 2994 3069 "version": "0.1.4", 2995 3070 "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", ··· 3692 3767 "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", 3693 3768 "dev": true 3694 3769 }, 3770 + "lower-case": { 3771 + "version": "2.0.2", 3772 + "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-2.0.2.tgz", 3773 + "integrity": "sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==", 3774 + "dev": true, 3775 + "requires": { 3776 + "tslib": "^2.0.3" 3777 + }, 3778 + "dependencies": { 3779 + "tslib": { 3780 + "version": "2.3.1", 3781 + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz", 3782 + "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==", 3783 + "dev": true 3784 + } 3785 + } 3786 + }, 3695 3787 "lru-cache": { 3696 3788 "version": "6.0.0", 3697 3789 "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", ··· 3780 3872 "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", 3781 3873 "dev": true 3782 3874 }, 3875 + "no-case": { 3876 + "version": "3.0.4", 3877 + "resolved": "https://registry.npmjs.org/no-case/-/no-case-3.0.4.tgz", 3878 + "integrity": "sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==", 3879 + "dev": true, 3880 + "requires": { 3881 + "lower-case": "^2.0.2", 3882 + "tslib": "^2.0.3" 3883 + }, 3884 + "dependencies": { 3885 + "tslib": { 3886 + "version": "2.3.1", 3887 + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz", 3888 + "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==", 3889 + "dev": true 3890 + } 3891 + } 3892 + }, 3783 3893 "normalize-path": { 3784 3894 "version": "3.0.0", 3785 3895 "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", ··· 3816 3926 "dev": true, 3817 3927 "requires": { 3818 3928 "callsites": "^3.0.0" 3929 + } 3930 + }, 3931 + "pascal-case": { 3932 + "version": "3.1.2", 3933 + "resolved": "https://registry.npmjs.org/pascal-case/-/pascal-case-3.1.2.tgz", 3934 + "integrity": "sha512-uWlGT3YSnK9x3BQJaOdcZwrnV6hPpd8jFH1/ucpiLRPh/2zCVJKS19E4GvYHvaCcACn3foXZ0cLB9Wrx1KGe5g==", 3935 + "dev": true, 3936 + "requires": { 3937 + "no-case": "^3.0.4", 3938 + "tslib": "^2.0.3" 3939 + }, 3940 + "dependencies": { 3941 + "tslib": { 3942 + "version": "2.3.1", 3943 + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz", 3944 + "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==", 3945 + "dev": true 3946 + } 3819 3947 } 3820 3948 }, 3821 3949 "path-is-absolute": { ··· 4156 4284 "sourcemap-codec": "^1.4.8" 4157 4285 } 4158 4286 } 4287 + } 4288 + }, 4289 + "svelte2tsx": { 4290 + "version": "0.5.6", 4291 + "resolved": "https://registry.npmjs.org/svelte2tsx/-/svelte2tsx-0.5.6.tgz", 4292 + "integrity": "sha512-B4WZUtoTdVD+F73H1RQEH3Hrv7m2/ahThmAUkjT5CTWRigQaJqYQpSjisCH1Pzfi9B37YikDnAi4u4uxwYM+iw==", 4293 + "dev": true, 4294 + "requires": { 4295 + "dedent-js": "^1.0.1", 4296 + "pascal-case": "^3.1.1" 4159 4297 } 4160 4298 }, 4161 4299 "text-table": {
+2
package.json
··· 3 3 "type": "module", 4 4 "scripts": { 5 5 "dev": "svelte-kit dev", 6 + "build": "svelte-kit package && svelte-kit build", 6 7 "build:package": "svelte-kit package", 7 8 "build:site": "svelte-kit build", 8 9 "preview": "svelte-kit preview", ··· 23 24 "svelte": "^3.46.4", 24 25 "svelte-check": "^2.4.6", 25 26 "svelte-preprocess": "^4.10.4", 27 + "svelte2tsx": "^0.5.6", 26 28 "typescript": "^4.6.2" 27 29 }, 28 30 "license": "MIT",
+69 -22
src/lib/FileDrop.svelte
··· 1 1 <script lang="ts"> 2 - export let handleFiles: (files: FileList) => void = () => { 2 + export let handleFiles: (files: File[]) => void = () => { 3 3 /* noop */ 4 4 } 5 5 6 + /** 7 + * List of allowed mime types, like `image/jpeg` or `image/*`. Invalid files are simply filtered out. 8 + * 9 + * Null: all file extensions are allowed (default) 10 + */ 11 + export let acceptedMimes: string[] | null = null 12 + 6 13 let droppable = false 7 14 let input: HTMLInputElement 8 15 9 - function handleEnter() { 10 - droppable = true 16 + function getAcceptedFiles(files: FileList | File[] = []): File[] { 17 + let acceptedFiles = [] 18 + for (let i = 0; i < files.length; i++) { 19 + if (acceptedMimes === null || isAcceptedMime(files[i].type)) { 20 + acceptedFiles.push(files[i]) 21 + } 22 + } 23 + return acceptedFiles 11 24 } 12 25 13 - function handleLeave() { 14 - droppable = false 26 + function isAcceptedMime(mime: string): boolean { 27 + if (acceptedMimes === null) { 28 + return true 29 + } 30 + for (const acceptedMime of acceptedMimes) { 31 + if (acceptedMime === 'application/*' && mime.startsWith('application/')) { 32 + return true 33 + } else if (acceptedMime === 'audio/*' && mime.startsWith('audio/')) { 34 + return true 35 + } else if (acceptedMime === 'video/*' && mime.startsWith('video/')) { 36 + return true 37 + } else if (acceptedMime === 'image/*' && mime.startsWith('image/')) { 38 + return true 39 + } else if (acceptedMime === 'text/*' && mime.startsWith('text/')) { 40 + return true 41 + } else if (mime === acceptedMime) { 42 + return true 43 + } 44 + } 45 + return false 15 46 } 16 47 17 - function handleDrop(e: DragEvent) { 18 - e.preventDefault() 19 - const files = e.dataTransfer?.files 20 - if (files) { 21 - handleFiles(files) 48 + function dragOver(e: DragEvent) { 49 + const items = Array.from(e.dataTransfer?.items || []) 50 + for (const item of items) { 51 + if (item.kind === 'file' && isAcceptedMime(item.type)) { 52 + droppable = true 53 + return 54 + } 22 55 } 56 + } 57 + 58 + function dragLeave() { 23 59 droppable = false 24 60 } 25 61 26 - function handleDragOver(e: DragEvent) { 27 - e.preventDefault() 62 + function drop(e: DragEvent) { 63 + const acceptedFiles = getAcceptedFiles(e.dataTransfer?.files) 64 + if (acceptedFiles.length > 0) { 65 + handleFiles(acceptedFiles) 66 + } 67 + droppable = false 28 68 } 29 69 30 - let files: FileList 31 - function handleChange(e: Event) { 32 - e.preventDefault() 33 - if (files && files.length > 0) { 34 - handleFiles(files) 70 + let inputFiles: FileList 71 + function handleChange() { 72 + const acceptedFiles = getAcceptedFiles(inputFiles) 73 + if (acceptedFiles && acceptedFiles.length > 0) { 74 + handleFiles(acceptedFiles) 35 75 } 36 76 } 37 77 </script> 38 78 39 79 <div 40 - on:drop={handleDrop} 41 - on:dragover={handleDragOver} 42 - on:dragenter={handleEnter} 43 - on:dragleave={handleLeave} 80 + on:drop|preventDefault={drop} 81 + on:dragover|preventDefault={dragOver} 82 + on:dragenter|preventDefault={dragOver} 83 + on:dragleave|preventDefault={dragLeave} 44 84 on:click={() => input.click()} 45 85 > 46 86 <slot {droppable}>Upload</slot> 47 87 </div> 48 - <input type="file" bind:files on:change={handleChange} bind:this={input} multiple /> 88 + <input 89 + type="file" 90 + accept={acceptedMimes === null ? null : acceptedMimes.join(',')} 91 + bind:files={inputFiles} 92 + on:change|preventDefault={handleChange} 93 + bind:this={input} 94 + multiple 95 + /> 49 96 50 97 <style lang="sass"> 51 98 input
+28 -4
src/routes/index.svelte
··· 1 1 <script lang="ts"> 2 2 import FileDrop from '$lib/FileDrop.svelte' 3 + 4 + let handledFiles: File[] = [] 5 + function handleFiles(files: File[]) { 6 + handledFiles = files 7 + } 3 8 </script> 4 9 5 10 <svelte:head> ··· 7 12 </svelte:head> 8 13 9 14 <h1>Svelte Droplet</h1> 10 - <p>Hello <code>world</code></p> 11 15 12 16 <p>Demo</p> 13 - <FileDrop>Upload a file</FileDrop> 17 + <div class="container"> 18 + <FileDrop acceptedMimes={['image/png']} {handleFiles} let:droppable> 19 + <div class="zone"> 20 + {#if droppable} 21 + Drop a file 22 + {:else} 23 + Upload a file 24 + {/if} 25 + {#each handledFiles as file} 26 + <p class="file">{file.name}</p> 27 + {/each} 28 + </div> 29 + </FileDrop> 30 + </div> 14 31 15 32 <style lang="sass"> 16 33 :global(body) ··· 22 39 text-align: center 23 40 h1 24 41 color: #00c3ff 25 - code 42 + .container 43 + display: inline-block 44 + .zone 26 45 background: #282b33 27 - padding: 2px 5px 46 + display: inline-block 47 + width: 560px 48 + padding: 40px 0px 28 49 border-radius: 4px 50 + .file 51 + margin-top: 10px 52 + margin-bottom: 0px 29 53 </style>