├── .editorconfig
├── .eslintignore
├── .eslintrc
├── .gitignore
├── .nvmrc
├── .prettierrc
├── .yarnrc.yml
├── CHANGELOG.md
├── LICENSE.md
├── README.md
├── dist
├── constants
│ ├── events.d.ts
│ ├── events.d.ts.map
│ ├── file.d.ts
│ ├── file.d.ts.map
│ ├── images.d.ts
│ ├── images.d.ts.map
│ ├── style.d.ts
│ ├── style.d.ts.map
│ ├── text.d.ts
│ └── text.d.ts.map
├── file-upload-with-preview.d.ts
├── file-upload-with-preview.d.ts.map
├── file-upload-with-preview.jpg
├── index.cjs
├── index.d.ts
├── index.d.ts.map
├── index.iife.js
├── index.js
├── style.css
├── types
│ ├── events.d.ts
│ ├── events.d.ts.map
│ ├── options.d.ts
│ └── options.d.ts.map
└── utils
│ ├── file.d.ts
│ └── file.d.ts.map
├── docs
├── assets
│ ├── custom-image-de30ffc0.svg
│ ├── index-0a8e647e.css
│ └── index-86f31695.js
├── index.html
└── typedoc
│ ├── .nojekyll
│ ├── assets
│ ├── highlight.css
│ ├── main.js
│ ├── search.js
│ └── style.css
│ ├── classes
│ └── FileUploadWithPreview.html
│ ├── enums
│ └── Events.html
│ ├── index.html
│ ├── interfaces
│ ├── ClearButtonClickedEvent.html
│ ├── ClearButtonClickedEventDetail.html
│ ├── ImageAddedEvent.html
│ ├── ImageAddedEventDetail.html
│ ├── ImageDeletedEvent.html
│ ├── ImageDeletedEventDetail.html
│ ├── ImageMultiItemClickedEvent.html
│ ├── ImageMultiItemClickedEventDetail.html
│ ├── Images.html
│ ├── Options.html
│ └── Text.html
│ ├── modules.html
│ ├── types
│ ├── PresetFiles.html
│ └── RequiredOptions.html
│ └── variables
│ ├── DEFAULT_BACKGROUND_IMAGE.html
│ ├── DEFAULT_BASE_IMAGE.html
│ ├── DEFAULT_BROWSE_TEXT.html
│ ├── DEFAULT_CHOOSE_FILE_TEXT.html
│ ├── DEFAULT_FILES_SELECTED_TEXT.html
│ ├── DEFAULT_LABEL_TEXT.html
│ ├── DEFAULT_SUCCESS_FILE_ALT_IMAGE.html
│ ├── DEFAULT_SUCCESS_PDF_IMAGE.html
│ ├── DEFAULT_SUCCESS_VIDEO_IMAGE.html
│ ├── MULTI_ITEM_CLEAR_ANIMATION_CLASS.html
│ └── UNIQUE_ID_IDENTIFIER.html
├── example
├── custom-image.svg
├── favicon.png
├── index.html
├── index.scss
└── index.ts
├── globals.d.ts
├── jest.config.cjs
├── jest
├── constants
│ └── file.ts
└── style-mock.ts
├── package.json
├── public
└── file-upload-with-preview.jpg
├── src
├── assets
│ └── images
│ │ ├── background.svg
│ │ ├── base-image.svg
│ │ ├── file-alt-success.svg
│ │ ├── pdf-success.svg
│ │ └── video-success.svg
├── constants
│ ├── events.ts
│ ├── file.ts
│ ├── images.ts
│ ├── style.ts
│ └── text.ts
├── file-upload-with-preview.spec.ts
├── file-upload-with-preview.ts
├── index.scss
├── index.ts
├── types
│ ├── events.ts
│ └── options.ts
└── utils
│ └── file.ts
├── tsconfig.json
├── typedoc.json
├── vite.config.app.ts
├── vite.config.library.ts
└── yarn.lock
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | charset = utf-8
5 | end_of_line = lf
6 | indent_size = 2
7 | indent_style = space
8 | insert_final_newline = true
9 | trim_trailing_whitespace = true
10 |
11 | [*.md]
12 | trim_trailing_whitespace = false
13 |
--------------------------------------------------------------------------------
/.eslintignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | dist
3 | docs
4 |
--------------------------------------------------------------------------------
/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "root": true,
3 | "parser": "@typescript-eslint/parser",
4 | "env": {
5 | "browser": true,
6 | "jest": true,
7 | "node": true
8 | },
9 | "extends": [
10 | "eslint:recommended",
11 | "plugin:@typescript-eslint/eslint-recommended",
12 | "plugin:@typescript-eslint/recommended",
13 | "prettier"
14 | ],
15 | "plugins": ["@typescript-eslint", "simple-import-sort", "prettier"],
16 | "rules": {
17 | "@typescript-eslint/ban-ts-comment": "off",
18 | "import/extensions": "off",
19 | "import/prefer-default-export": "off",
20 | "no-console": "off",
21 | "no-prototype-builtins": "off",
22 | "prettier/prettier": 2,
23 | "simple-import-sort/exports": "error",
24 | "simple-import-sort/imports": "error",
25 | "sort-keys": ["error", "asc", { "caseSensitive": true, "natural": false, "minKeys": 2 }]
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | node_modules
3 |
4 | # https://yarnpkg.com/getting-started/qa#which-files-should-be-gitignored
5 | .pnp.*
6 | .yarn/*
7 | !.yarn/patches
8 | !.yarn/plugins
9 | !.yarn/releases
10 | !.yarn/sdks
11 | !.yarn/versions
12 |
--------------------------------------------------------------------------------
/.nvmrc:
--------------------------------------------------------------------------------
1 | 16.17.0
2 |
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "printWidth": 100,
3 | "semi": true,
4 | "singleQuote": true,
5 | "tabWidth": 2,
6 | "trailingComma": "all",
7 | "useTabs": false
8 | }
9 |
--------------------------------------------------------------------------------
/.yarnrc.yml:
--------------------------------------------------------------------------------
1 | enableTelemetry: false
2 | nodeLinker: node-modules
3 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # CHANGELOG.md
2 |
3 | ## 6.0.2
4 |
5 | - Use vite for serving app and building library
6 | - Remove rollup
7 | - Remove babel
8 | - Update package.json exports
9 |
10 | ## 5.0.8
11 |
12 | - Fix index bug. Thank you @tasinttttttt.
13 |
14 | ## 5.0.6
15 |
16 | - Revamped library
17 | - Added TypeScript
18 | - Replaced webpack with vite for example
19 | - Updated all deps
20 | - Added prettier and updated eslint
21 | - Simplified library logic
22 | - Updated SCSS styling
23 |
24 | ## 4.3.0
25 |
26 | - Added image click event and updated deps. Thanks @tasinttttttt.
27 |
28 | ## 4.1.0
29 |
30 | - Added index on delete event. Updates deps. Thank you @tasinttttttt.
31 |
32 | ## 4.0.1, 4.0.2
33 |
34 | - Removing unneeded wrapper divs in preview panels
35 | - Adding `token` attribute on each preview panel
36 |
37 | ## 4.0.0
38 |
39 | - Refactor of basically entire library. Many methods changed, properties changed. Please read the README and update your code accordingly. This refactoring was necessary due to the need for additional methods that allow for more control and replacement of the `cachedFileArray`.
40 | - Removed `rollup-plugin-buble` in favor of `rollup-plugin-bable` due to the former not fully supporting `async/await`.
41 |
42 | ## 3.2.0
43 |
44 | - Moves from Gulp to Webpack
45 |
46 | ## 3.1.0
47 |
48 | - Made updates for accessibility.
49 | - Added `text` object to `options` so that the input copy can be changed.
50 | - Updated the README accordingly with these changes.
51 |
52 | ## 3.0.1
53 |
54 | - Removed the option to _not_ show a grid of files when setting `multiple` on the input. The logic messiness wasn't worth it considering we don't really need the option now that we have a grid view for multiple images.
55 | - Cleaned up logic flow.
56 | - _Added_ buttons for deleting indivudual files in the grid view. Also includes the option to disable this feature.
57 | - Added a deletion event that gets triggered when a file is deleted.
58 | - Updated the README accordingly with these changes.
59 |
60 | ## 3.0.0
61 |
62 | - Rewrite of the library code. Made the handling of multiple images a bit more clear. Tried to clean up the logic and set properties only a single time when needed. Should all be a bit more clear now.
63 | - Showing the grid of images in the case of multiple is now the default setting. To stop that, pass
64 | `{showMultiple: false}` when initializing the class.
65 | - All further options are now set up to be passed in a single `options` object during initialization. This will be good for any further additions.
66 | - Restructured how the `imageSelected` event is done as to make it a bit easier to use - see updated documentation on how to use that event.
67 | - `custom-file-container__image-preview__active` was changed to `custom-file-container__image-preview--active` to be inline with BEM style.
68 | - Added overflow to multiple image pane, so when there's a ton of images it doesn't just keep growing in height.
69 |
70 | ## 2.1.2 (2018-09-21)
71 |
72 | - Merged in #12 from @firstor - support for .gifs in both single and multiple previews.
73 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) John Datserakis
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | # file-upload-with-preview
4 |
5 | 🖼 Simple file-upload utility that shows a preview of the uploaded image. Written in TypeScript. No dependencies. Works well with or without a framework.
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 | ## Links
16 |
17 | - [Demo](https://johndatserakis.github.io/file-upload-with-preview)
18 | - [npm](https://www.npmjs.com/package/file-upload-with-preview)
19 | - [GitHub](https://github.com/johndatserakis/file-upload-with-preview#readme)
20 | - [CodeSandbox - TypeScript](https://codesandbox.io/s/file-upload-with-preview-4ypil8?file=/src/index.ts)
21 | - [CodeSandbox - Browser](https://codesandbox.io/s/file-upload-with-preview-browser-446nc8?file=/src/index.js)
22 | - [Docs](https://johndatserakis.github.io/file-upload-with-preview/typedoc/)
23 |
24 | ## Install
25 |
26 | ```bash
27 | yarn add file-upload-with-preview
28 | ```
29 |
30 | Or, you can include it through the browser.
31 |
32 | ```html
33 |
38 |
39 |
40 | ```
41 |
42 | ## About
43 |
44 | This is a simple frontend utility meant to help the file-upload process on your website.
45 |
46 | It is written in pure JavaScript using TypeScript and has no dependencies. You can check out the live demo [here](https://johndatserakis.github.io/file-upload-with-preview).
47 |
48 | For the most part, browsers do a good job of handling image-uploads. That being said - I find the ability to show our users a preview of their upload can go a long way in increasing the confidence in their upload.
49 |
50 | `file-upload-with-preview` aims to address the issue of showing a preview of a user's uploaded image in a simple to use package.
51 |
52 | ## Features
53 |
54 | - Shows the actual image preview in the case of a single uploaded .jpg, .jpeg, .gif, or .png image. Shows a _success-image_ in the case of an uploaded .pdf file, uploaded video, or other un-renderable file - so the user knows their image was collected successfully. In the case of multiple selected files, the user's selected images will be shown in a grid.
55 | - Shows the image name in the input bar. Shows the count of selected images in the case of multiple selections within the same input.
56 | - Allows the user to clear their upload and clear individual images in the `multiple` grid
57 | - Looks great
58 | - Framework agnostic - to access the uploaded file/files just use the `cachedFileArray` (always will be an array) property of your `FileUploadWithPreview` object.
59 | - For every file-group you want just initialize another `FileUploadWithPreview` object with its own `uniqueId` - this way you can have multiple file-uploads on the same page. You also can just use a single input designated with a `multiple` property to allow multiple files on the same input.
60 |
61 | ## Usage
62 |
63 | This library looks for a specific HTML element to display the file-upload. Simply add the below `div` to your HTML. Make sure to populate your unique id in the `data-upload-id` attribute.
64 |
65 | ```html
66 |
67 | ```
68 |
69 | Then, initialize your file-upload in the JavaScript like below:
70 |
71 | ```javascript
72 | import { FileUploadWithPreview } from 'file-upload-with-preview';
73 | import 'file-upload-with-preview/dist/style.css';
74 |
75 | const upload = new FileUploadWithPreview('my-unique-id');
76 | ```
77 |
78 | Usage with Next.js / Tailwind :
79 |
80 | ```typescript
81 | 'use client';
82 | import { useEffect } from 'react';
83 | import { FileUploadWithPreview } from 'file-upload-with-preview';
84 | import 'file-upload-with-preview/dist/style.css';
85 |
86 | export default function Uploader() {
87 | useEffect(() => {
88 | const upload = new FileUploadWithPreview('my-unique-id');
89 | }, []);
90 |
91 | return
;
92 | }
93 | ```
94 |
95 | If you're importing directly in the browser, use the following instead:
96 |
97 | ```html
98 |
99 |
100 |
101 |
102 |
107 |
108 |
109 |
110 |
111 |
112 |
113 | ```
114 |
115 | Then initialize like this:
116 |
117 | ```javascript
118 | const upload = new FileUploadWithPreview.FileUploadWithPreview('my-unique-id');
119 | ```
120 |
121 | Then when you're ready to use the user's file for an API call or whatever, just access the user's uploaded file/files in the `cachedFileArray` property of your initialized object like this:
122 |
123 | ```javascript
124 | upload.cachedFileArray;
125 | ```
126 |
127 | You can optionally trigger the image browser and clear selected images programmatically. There are additional methods on the class if you'd like to take a look at the source code.
128 |
129 | ```javascript
130 | upload.emulateInputSelection(); // to open image browser
131 | upload.resetPreviewPanel(); // clear all selected images
132 | ```
133 |
134 | You may also want to capture the event when an image is selected.
135 |
136 | ```javascript
137 | import { Events, ImageAddedEvent } from 'file-upload-with-preview';
138 |
139 | window.addEventListener(Events.IMAGE_ADDED, (e: Event) => {
140 | const { detail } = e as unknown as ImageAddedEvent;
141 |
142 | console.log('detail', detail);
143 | });
144 | ```
145 |
146 | ### Note
147 |
148 | The `cachedFileArray` property is always an array. So if you are only allowing the user to upload a single file, you can access that file at `cachedFileArray[0]` - otherwise just send the entire array to your backend to handle it normally.
149 |
150 | Make sure to pass in `multiple: true` in your options if you want to allow the user to select multiple images.
151 |
152 | ## Docs
153 |
154 | View the full docs [here](https://johndatserakis.github.io/file-upload-with-preview/typedoc/).
155 |
156 | ## Full Example
157 |
158 | See the full example in the `./example/index.ts` folder. See the top of this README for some links to a few live CodeSandbox's.
159 |
160 | ## Browser Support
161 |
162 | If you are supporting a browser like IE11, one way to add a polyfill for `fetch` and `promise` is by adding the following in the bottom of your `index.html`:
163 |
164 | ```html
165 |
166 |
167 | ```
168 |
169 | ## Development
170 |
171 | ```bash
172 | # Install dependencies
173 | yarn
174 |
175 | # Watch changes during local development
176 | yarn dev
177 |
178 | # Run tests
179 | yarn test
180 |
181 | # Build library
182 | yarn build
183 | ```
184 |
185 | ## Other
186 |
187 | Go ahead and fork the project! Submit an issue if needed. Have fun!
188 |
189 | ## License
190 |
191 | [MIT](http://opensource.org/licenses/MIT)
192 |
--------------------------------------------------------------------------------
/dist/constants/events.d.ts:
--------------------------------------------------------------------------------
1 | export declare enum Events {
2 | IMAGE_ADDED = "fileUploadWithPreview:imagesAdded",
3 | IMAGE_DELETED = "fileUploadWithPreview:imageDeleted",
4 | CLEAR_BUTTON_CLICKED = "fileUploadWithPreview:clearButtonClicked",
5 | IMAGE_MULTI_ITEM_CLICKED = "fileUploadWithPreview:imageMultiItemClicked"
6 | }
7 | //# sourceMappingURL=events.d.ts.map
--------------------------------------------------------------------------------
/dist/constants/events.d.ts.map:
--------------------------------------------------------------------------------
1 | {"version":3,"file":"events.d.ts","sourceRoot":"","sources":["../../../src/constants/events.ts"],"names":[],"mappings":"AAAA,oBAAY,MAAM;IAChB,WAAW,sCAAsC;IACjD,aAAa,uCAAuC;IACpD,oBAAoB,6CAA6C;IACjE,wBAAwB,gDAAgD;CACzE"}
--------------------------------------------------------------------------------
/dist/constants/file.d.ts:
--------------------------------------------------------------------------------
1 | export declare const UNIQUE_ID_IDENTIFIER = ":upload:";
2 | //# sourceMappingURL=file.d.ts.map
--------------------------------------------------------------------------------
/dist/constants/file.d.ts.map:
--------------------------------------------------------------------------------
1 | {"version":3,"file":"file.d.ts","sourceRoot":"","sources":["../../../src/constants/file.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,oBAAoB,aAAa,CAAC"}
--------------------------------------------------------------------------------
/dist/constants/images.d.ts.map:
--------------------------------------------------------------------------------
1 | {"version":3,"file":"images.d.ts","sourceRoot":"","sources":["../../../src/constants/images.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,kBAAkB,m0DACmyD,CAAC;AAEn0D,eAAO,MAAM,yBAAyB,uqFACgoF,CAAC;AAEvqF,eAAO,MAAM,2BAA2B,m0MAC0xM,CAAC;AAEn0M,eAAO,MAAM,8BAA8B,mhMACu+L,CAAC;AAEnhM,eAAO,MAAM,wBAAwB,2uIACqsI,CAAC"}
--------------------------------------------------------------------------------
/dist/constants/style.d.ts:
--------------------------------------------------------------------------------
1 | export declare const MULTI_ITEM_CLEAR_ANIMATION_CLASS = "multi-item-clear-animation";
2 | //# sourceMappingURL=style.d.ts.map
--------------------------------------------------------------------------------
/dist/constants/style.d.ts.map:
--------------------------------------------------------------------------------
1 | {"version":3,"file":"style.d.ts","sourceRoot":"","sources":["../../../src/constants/style.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,gCAAgC,+BAA+B,CAAC"}
--------------------------------------------------------------------------------
/dist/constants/text.d.ts:
--------------------------------------------------------------------------------
1 | export declare const DEFAULT_CHOOSE_FILE_TEXT = "Choose file...";
2 | export declare const DEFAULT_BROWSE_TEXT = "Browse";
3 | export declare const DEFAULT_FILES_SELECTED_TEXT = "files selected";
4 | export declare const DEFAULT_LABEL_TEXT = "Upload";
5 | //# sourceMappingURL=text.d.ts.map
--------------------------------------------------------------------------------
/dist/constants/text.d.ts.map:
--------------------------------------------------------------------------------
1 | {"version":3,"file":"text.d.ts","sourceRoot":"","sources":["../../../src/constants/text.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,wBAAwB,mBAAmB,CAAC;AACzD,eAAO,MAAM,mBAAmB,WAAW,CAAC;AAC5C,eAAO,MAAM,2BAA2B,mBAAmB,CAAC;AAC5D,eAAO,MAAM,kBAAkB,WAAW,CAAC"}
--------------------------------------------------------------------------------
/dist/file-upload-with-preview.d.ts:
--------------------------------------------------------------------------------
1 | import { Options, PresetFiles, RequiredOptions } from './types/options';
2 | export declare class FileUploadWithPreview {
3 | /**
4 | * Currently selected files
5 | *
6 | * @default []
7 | */
8 | cachedFileArray: File[];
9 | /**
10 | * Button to reset the instance
11 | */
12 | clearButton: Element;
13 | /**
14 | * Main container for the instance
15 | */
16 | el: Element;
17 | /**
18 | * Display panel for the images
19 | */
20 | imagePreview: HTMLDivElement;
21 | /**
22 | * Hidden input
23 | */
24 | inputHidden: HTMLInputElement;
25 | /**
26 | * Visible input
27 | */
28 | inputVisible: Element;
29 | options: RequiredOptions;
30 | /**
31 | * The `id` you set for the instance
32 | */
33 | uploadId: string;
34 | constructor(uploadId: string, options?: Options);
35 | bindClickEvents(): void;
36 | addImagesFromPath(presetFiles: PresetFiles): Promise;
37 | addFiles(files: FileList | File[]): void;
38 | addFileToPreviewPanel(file: File): void;
39 | replaceFiles(files: File[]): void;
40 | replaceFileAtIndex(file: File, index: number): void;
41 | deleteFileAtIndex(index: number): void;
42 | refreshPreviewPanel(): void;
43 | addBrowseButton(text: string): void;
44 | emulateInputSelection(): void;
45 | resetPreviewPanel(): void;
46 | }
47 | //# sourceMappingURL=file-upload-with-preview.d.ts.map
--------------------------------------------------------------------------------
/dist/file-upload-with-preview.d.ts.map:
--------------------------------------------------------------------------------
1 | {"version":3,"file":"file-upload-with-preview.d.ts","sourceRoot":"","sources":["../../src/file-upload-with-preview.ts"],"names":[],"mappings":"AAsBA,OAAO,EAAE,OAAO,EAAE,WAAW,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAGxE,qBAAa,qBAAqB;IAChC;;;;OAIG;IACH,eAAe,EAAE,IAAI,EAAE,CAAC;IACxB;;OAEG;IACH,WAAW,EAAE,OAAO,CAAC;IACrB;;OAEG;IACH,EAAE,EAAE,OAAO,CAAC;IACZ;;OAEG;IACH,YAAY,EAAE,cAAc,CAAC;IAC7B;;OAEG;IACH,WAAW,EAAE,gBAAgB,CAAC;IAC9B;;OAEG;IACH,YAAY,EAAE,OAAO,CAAC;IACtB,OAAO,EAAE,eAAe,CAmBtB;IACF;;OAEG;IACH,QAAQ,EAAE,MAAM,CAAC;gBAEL,QAAQ,EAAE,MAAM,EAAE,OAAO,GAAE,OAAY;IAwFnD,eAAe;IAgET,iBAAiB,CAAC,WAAW,EAAE,WAAW;IAoBhD,QAAQ,CAAC,KAAK,EAAE,QAAQ,GAAG,IAAI,EAAE;IA4CjC,qBAAqB,CAAC,IAAI,EAAE,IAAI;IAwEhC,YAAY,CAAC,KAAK,EAAE,IAAI,EAAE;IAS1B,kBAAkB,CAAC,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,MAAM;IAS5C,iBAAiB,CAAC,KAAK,EAAE,MAAM;IAuB/B,mBAAmB;IAoBnB,eAAe,CAAC,IAAI,EAAE,MAAM;IAI5B,qBAAqB;IAIrB,iBAAiB;CAQlB"}
--------------------------------------------------------------------------------
/dist/file-upload-with-preview.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/johndatserakis/file-upload-with-preview/c0c27abcf8368f733079ecbd131e671d811614d2/dist/file-upload-with-preview.jpg
--------------------------------------------------------------------------------
/dist/index.d.ts:
--------------------------------------------------------------------------------
1 | export * from './constants/events';
2 | export * from './constants/file';
3 | export * from './constants/images';
4 | export * from './constants/style';
5 | export * from './constants/text';
6 | export * from './types/events';
7 | export * from './types/options';
8 | export * from './file-upload-with-preview';
9 | //# sourceMappingURL=index.d.ts.map
--------------------------------------------------------------------------------
/dist/index.d.ts.map:
--------------------------------------------------------------------------------
1 | {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,cAAc,CAAC;AAGtB,cAAc,oBAAoB,CAAC;AACnC,cAAc,kBAAkB,CAAC;AACjC,cAAc,oBAAoB,CAAC;AACnC,cAAc,mBAAmB,CAAC;AAClC,cAAc,kBAAkB,CAAC;AAGjC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,iBAAiB,CAAC;AAGhC,cAAc,4BAA4B,CAAC"}
--------------------------------------------------------------------------------
/dist/style.css:
--------------------------------------------------------------------------------
1 | .custom-file-container{box-sizing:border-box;position:relative;display:block}.custom-file-container *{box-sizing:border-box}.custom-file-container .label-container{align-items:center;display:flex;justify-content:space-between;margin-bottom:4px}.custom-file-container .clear-button{color:#333;font-size:26px;height:26px;line-height:26px;text-decoration:none;transition:color .2s ease-in-out}.custom-file-container .clear-button:hover{color:#777}.custom-file-container .input-container{display:inline-block;height:40px;margin-bottom:8px;position:relative;width:100%}.custom-file-container .input-container:hover{cursor:pointer}.custom-file-container .input-hidden{height:40px;margin:0;max-width:100%;min-width:300px;opacity:0}.custom-file-container .input-visible{background-clip:padding-box;background-color:#fff;border-radius:4px;border:1px solid #c0c0af;color:#333;height:40px;left:0;line-height:1.5;overflow:hidden;padding:8px 12px;position:absolute;right:0;top:0;-webkit-user-select:none;user-select:none;z-index:5}.custom-file-container .browse-button{background-color:#edede8;border-left:1px solid #c0c0af;color:#333;display:block;height:38px;padding:8px 12px;position:absolute;right:0;top:0;z-index:6}.custom-file-container .image-preview{background-color:#edede8;background-position:center center;background-repeat:no-repeat;background-size:cover;border-radius:4px;height:250px;overflow:auto;padding:4px;transition:background .2s ease-in-out;width:100%}.custom-file-container .image-preview-item{background-position:center center;background-repeat:no-repeat;background-size:cover;border-radius:4px;box-shadow:0 4px 10px #33333340;float:left;height:90px;margin:1.858736059%;position:relative;transition:background .2s ease-in-out,opacity .2s ease-in-out;width:29.615861214%}.custom-file-container .image-preview-item.multi-item-clear-animation{opacity:0}.custom-file-container .image-preview-item-clear{background:#edede8;border-radius:50%;box-shadow:0 4px 10px #33333340;height:20px;left:-6px;margin-top:-6px;position:absolute;text-align:center;transition:background .2s ease-in-out,color .2s ease-in-out;width:20px}.custom-file-container .image-preview-item-clear:hover{background:#e2e2da;cursor:pointer}.custom-file-container .image-preview-item-clear-icon{color:#333;display:block;margin-top:-2px}
2 |
--------------------------------------------------------------------------------
/dist/types/events.d.ts:
--------------------------------------------------------------------------------
1 | export interface ImageAddedEventDetail {
2 | addedFilesCount: number;
3 | cachedFileArray: File[];
4 | files: FileList | File[];
5 | uploadId: string;
6 | }
7 | export interface ImageAddedEvent {
8 | detail: ImageAddedEventDetail;
9 | }
10 | export interface ImageDeletedEventDetail {
11 | cachedFileArray: File[];
12 | currentFileCount: number;
13 | index: number;
14 | uploadId: string;
15 | }
16 | export interface ImageDeletedEvent {
17 | detail: ImageDeletedEventDetail;
18 | }
19 | export interface ClearButtonClickedEventDetail {
20 | uploadId: string;
21 | }
22 | export interface ClearButtonClickedEvent {
23 | detail: ClearButtonClickedEventDetail;
24 | }
25 | export interface ImageMultiItemClickedEventDetail {
26 | cachedFileArray: File[];
27 | file: File;
28 | index: number;
29 | uploadId: string;
30 | }
31 | export interface ImageMultiItemClickedEvent {
32 | detail: ImageMultiItemClickedEventDetail;
33 | }
34 | //# sourceMappingURL=events.d.ts.map
--------------------------------------------------------------------------------
/dist/types/events.d.ts.map:
--------------------------------------------------------------------------------
1 | {"version":3,"file":"events.d.ts","sourceRoot":"","sources":["../../../src/types/events.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,qBAAqB;IACpC,eAAe,EAAE,MAAM,CAAC;IACxB,eAAe,EAAE,IAAI,EAAE,CAAC;IACxB,KAAK,EAAE,QAAQ,GAAG,IAAI,EAAE,CAAC;IACzB,QAAQ,EAAE,MAAM,CAAC;CAClB;AACD,MAAM,WAAW,eAAe;IAC9B,MAAM,EAAE,qBAAqB,CAAC;CAC/B;AAED,MAAM,WAAW,uBAAuB;IACtC,eAAe,EAAE,IAAI,EAAE,CAAC;IACxB,gBAAgB,EAAE,MAAM,CAAC;IACzB,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;CAClB;AACD,MAAM,WAAW,iBAAiB;IAChC,MAAM,EAAE,uBAAuB,CAAC;CACjC;AAED,MAAM,WAAW,6BAA6B;IAC5C,QAAQ,EAAE,MAAM,CAAC;CAClB;AACD,MAAM,WAAW,uBAAuB;IACtC,MAAM,EAAE,6BAA6B,CAAC;CACvC;AAED,MAAM,WAAW,gCAAgC;IAC/C,eAAe,EAAE,IAAI,EAAE,CAAC;IACxB,IAAI,EAAE,IAAI,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;CAClB;AACD,MAAM,WAAW,0BAA0B;IACzC,MAAM,EAAE,gCAAgC,CAAC;CAC1C"}
--------------------------------------------------------------------------------
/dist/types/options.d.ts:
--------------------------------------------------------------------------------
1 | export interface Text {
2 | /**
3 | * Browse button text
4 | *
5 | * @default "Browse"
6 | */
7 | browse?: string;
8 | /**
9 | * Placeholder text
10 | *
11 | * @default "Choose file..."
12 | */
13 | chooseFile?: string;
14 | /**
15 | * Main input label text
16 | *
17 | * @default "Upload"
18 | */
19 | label?: string;
20 | /**
21 | * Count descriptor text. Defaults to `${ n } files selected`.
22 | *
23 | * @default "files selected"
24 | */
25 | selectedCount?: string;
26 | }
27 | export interface Images {
28 | /**
29 | * Background image for image grid
30 | *
31 | * @default DEFAULT_BACKGROUND_IMAGE
32 | */
33 | backgroundImage?: string;
34 | /**
35 | * Placeholder image
36 | *
37 | * @default DEFAULT_BASE_IMAGE
38 | */
39 | baseImage?: string;
40 | /**
41 | * Alternate file upload image
42 | *
43 | * @default DEFAULT_SUCCESS_FILE_ALT_IMAGE
44 | */
45 | successFileAltImage?: string;
46 | /**
47 | * PDF upload image
48 | *
49 | * @default DEFAULT_SUCCESS_PDF_IMAGE
50 | */
51 | successPdfImage?: string;
52 | /**
53 | * Video upload image
54 | *
55 | * @default DEFAULT_SUCCESS_VIDEO_IMAGE
56 | */
57 | successVideoImage?: string;
58 | }
59 | export type PresetFiles = string[];
60 | /**
61 | * Options to customize the library
62 | */
63 | export interface Options {
64 | /**
65 | * Type of files to accept in your input
66 | *
67 | * @default '*'
68 | */
69 | accept?: HTMLInputElement['accept'];
70 | /**
71 | * Configurable images for the library
72 | */
73 | images?: Images;
74 | /**
75 | * Set a maximum number of files you'd like the component to deal with. Must be `> 0` if set. By default there is no limit.
76 | *
77 | * @default 0
78 | */
79 | maxFileCount?: number;
80 | /**
81 | * Set to `true` if you want to allow the user to selected multiple images. Will use grid view in the image preview if set.
82 | *
83 | * @default false
84 | */
85 | multiple?: boolean;
86 | /**
87 | * Provide an array of image paths to be automatically uploaded and displayed on page load (can be images hosted on server or URLs)
88 | *
89 | * @default []
90 | */
91 | presetFiles?: PresetFiles;
92 | /**
93 | * Show a delete button on images in the grid
94 | *
95 | * @default true
96 | */
97 | showDeleteButtonOnImages?: boolean;
98 | /**
99 | * Configurable text for the library
100 | */
101 | text?: Text;
102 | }
103 | export type RequiredOptions = Required & {
104 | images: Required;
105 | text: Required;
106 | };
107 | //# sourceMappingURL=options.d.ts.map
--------------------------------------------------------------------------------
/dist/types/options.d.ts.map:
--------------------------------------------------------------------------------
1 | {"version":3,"file":"options.d.ts","sourceRoot":"","sources":["../../../src/types/options.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,IAAI;IACnB;;;;OAIG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB;;;;OAIG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB;;;;OAIG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;IACf;;;;OAIG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,MAAM,WAAW,MAAM;IACrB;;;;OAIG;IACH,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB;;;;OAIG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;;;OAIG;IACH,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B;;;;OAIG;IACH,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB;;;;OAIG;IACH,iBAAiB,CAAC,EAAE,MAAM,CAAC;CAC5B;AAED,MAAM,MAAM,WAAW,GAAG,MAAM,EAAE,CAAC;AAEnC;;GAEG;AACH,MAAM,WAAW,OAAO;IACtB;;;;OAIG;IACH,MAAM,CAAC,EAAE,gBAAgB,CAAC,QAAQ,CAAC,CAAC;IACpC;;OAEG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB;;;;OAIG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB;;;;OAIG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB;;;;OAIG;IACH,WAAW,CAAC,EAAE,WAAW,CAAC;IAC1B;;;;OAIG;IACH,wBAAwB,CAAC,EAAE,OAAO,CAAC;IACnC;;OAEG;IACH,IAAI,CAAC,EAAE,IAAI,CAAC;CACb;AAED,MAAM,MAAM,eAAe,GAAG,QAAQ,CAAC,OAAO,CAAC,GAAG;IAChD,MAAM,EAAE,QAAQ,CAAC,MAAM,CAAC,CAAC;IACzB,IAAI,EAAE,QAAQ,CAAC,IAAI,CAAC,CAAC;CACtB,CAAC"}
--------------------------------------------------------------------------------
/dist/utils/file.d.ts:
--------------------------------------------------------------------------------
1 | export declare const generateUniqueId: () => string;
2 | //# sourceMappingURL=file.d.ts.map
--------------------------------------------------------------------------------
/dist/utils/file.d.ts.map:
--------------------------------------------------------------------------------
1 | {"version":3,"file":"file.d.ts","sourceRoot":"","sources":["../../../src/utils/file.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,gBAAgB,cAA4C,CAAC"}
--------------------------------------------------------------------------------
/docs/assets/custom-image-de30ffc0.svg:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
--------------------------------------------------------------------------------
/docs/assets/index-0a8e647e.css:
--------------------------------------------------------------------------------
1 | html{min-height:100%;position:relative}body{color:#333;font-family:Montserrat,sans-serif;font-size:16px;height:100%;line-height:1.5;margin:0;width:100%}a{color:#3157b3}a:hover{color:#3f7ebd}pre{background-color:#edede8;border-radius:4px;color:#333;font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-size:14px;padding:8px 16px}.header-container{align-items:center;display:flex;flex-direction:column;margin-bottom:16px}.header-container .content{font-size:2.5rem;margin-bottom:8px;text-align:center}.header-container .code{margin-bottom:8px}.demo-upload-container{align-items:center;display:flex;flex-direction:column;gap:32px;justify-content:center;margin-bottom:32px}@media (min-width: 992px){.demo-upload-container{flex-direction:row;gap:64px}}.demo-info-container{align-items:center;background:#3157b3;border-radius:4px;color:#fff;display:flex;flex-direction:column;gap:12px;justify-content:center;margin:0 auto 16px;padding:8px 12px;width:300px}.demo-info-container a{color:#fff}.demo-info-container a:hover{color:#e6e6e6}.github-corner:hover .octo-arm{animation:octocat-wave .56s ease-in-out}@keyframes octocat-wave{0%,to{transform:rotate(0)}20%,60%{transform:rotate(-25deg)}40%,80%{transform:rotate(10deg)}}@media (max-width: 500px){.github-corner:hover .octo-arm{animation:none}.github-corner .octo-arm{animation:octocat-wave .56s ease-in-out}}.custom-file-container{box-sizing:border-box;position:relative;display:block}.custom-file-container *{box-sizing:border-box}.custom-file-container .label-container{align-items:center;display:flex;justify-content:space-between;margin-bottom:4px}.custom-file-container .clear-button{color:#333;font-size:26px;height:26px;line-height:26px;text-decoration:none;transition:color .2s ease-in-out}.custom-file-container .clear-button:hover{color:#777}.custom-file-container .input-container{display:inline-block;height:40px;margin-bottom:8px;position:relative;width:100%}.custom-file-container .input-container:hover{cursor:pointer}.custom-file-container .input-hidden{height:40px;margin:0;max-width:100%;min-width:300px;opacity:0}.custom-file-container .input-visible{background-clip:padding-box;background-color:#fff;border-radius:4px;border:1px solid #c0c0af;color:#333;height:40px;left:0;line-height:1.5;overflow:hidden;padding:8px 12px;position:absolute;right:0;top:0;-webkit-user-select:none;user-select:none;z-index:5}.custom-file-container .browse-button{background-color:#edede8;border-left:1px solid #c0c0af;color:#333;display:block;height:38px;padding:8px 12px;position:absolute;right:0;top:0;z-index:6}.custom-file-container .image-preview{background-color:#edede8;background-position:center center;background-repeat:no-repeat;background-size:cover;border-radius:4px;height:250px;overflow:auto;padding:4px;transition:background .2s ease-in-out;width:100%}.custom-file-container .image-preview-item{background-position:center center;background-repeat:no-repeat;background-size:cover;border-radius:4px;box-shadow:0 4px 10px #33333340;float:left;height:90px;margin:1.858736059%;position:relative;transition:background .2s ease-in-out,opacity .2s ease-in-out;width:29.615861214%}.custom-file-container .image-preview-item.multi-item-clear-animation{opacity:0}.custom-file-container .image-preview-item-clear{background:#edede8;border-radius:50%;box-shadow:0 4px 10px #33333340;height:20px;left:-6px;margin-top:-6px;position:absolute;text-align:center;transition:background .2s ease-in-out,color .2s ease-in-out;width:20px}.custom-file-container .image-preview-item-clear:hover{background:#e2e2da;cursor:pointer}.custom-file-container .image-preview-item-clear-icon{color:#333;display:block;margin-top:-2px}
2 |
--------------------------------------------------------------------------------
/docs/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | file-upload-with-preview | John Datserakis
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
53 |
54 |
55 |
59 |
60 |
67 |
68 |
69 |
70 |
71 |
--------------------------------------------------------------------------------
/docs/typedoc/.nojekyll:
--------------------------------------------------------------------------------
1 | TypeDoc added this file to prevent GitHub Pages from using Jekyll. You can turn off this behavior by setting the `githubPages` option to false.
--------------------------------------------------------------------------------
/docs/typedoc/assets/highlight.css:
--------------------------------------------------------------------------------
1 | :root {
2 | --light-hl-0: #795E26;
3 | --dark-hl-0: #DCDCAA;
4 | --light-hl-1: #000000;
5 | --dark-hl-1: #D4D4D4;
6 | --light-hl-2: #A31515;
7 | --dark-hl-2: #CE9178;
8 | --light-hl-3: #800000;
9 | --dark-hl-3: #808080;
10 | --light-hl-4: #800000;
11 | --dark-hl-4: #569CD6;
12 | --light-hl-5: #E50000;
13 | --dark-hl-5: #9CDCFE;
14 | --light-hl-6: #0000FF;
15 | --dark-hl-6: #CE9178;
16 | --light-hl-7: #000000FF;
17 | --dark-hl-7: #D4D4D4;
18 | --light-hl-8: #AF00DB;
19 | --dark-hl-8: #C586C0;
20 | --light-hl-9: #001080;
21 | --dark-hl-9: #9CDCFE;
22 | --light-hl-10: #0000FF;
23 | --dark-hl-10: #569CD6;
24 | --light-hl-11: #0070C1;
25 | --dark-hl-11: #4FC1FF;
26 | --light-hl-12: #267F99;
27 | --dark-hl-12: #4EC9B0;
28 | --light-hl-13: #008000;
29 | --dark-hl-13: #6A9955;
30 | --light-code-background: #FFFFFF;
31 | --dark-code-background: #1E1E1E;
32 | }
33 |
34 | @media (prefers-color-scheme: light) { :root {
35 | --hl-0: var(--light-hl-0);
36 | --hl-1: var(--light-hl-1);
37 | --hl-2: var(--light-hl-2);
38 | --hl-3: var(--light-hl-3);
39 | --hl-4: var(--light-hl-4);
40 | --hl-5: var(--light-hl-5);
41 | --hl-6: var(--light-hl-6);
42 | --hl-7: var(--light-hl-7);
43 | --hl-8: var(--light-hl-8);
44 | --hl-9: var(--light-hl-9);
45 | --hl-10: var(--light-hl-10);
46 | --hl-11: var(--light-hl-11);
47 | --hl-12: var(--light-hl-12);
48 | --hl-13: var(--light-hl-13);
49 | --code-background: var(--light-code-background);
50 | } }
51 |
52 | @media (prefers-color-scheme: dark) { :root {
53 | --hl-0: var(--dark-hl-0);
54 | --hl-1: var(--dark-hl-1);
55 | --hl-2: var(--dark-hl-2);
56 | --hl-3: var(--dark-hl-3);
57 | --hl-4: var(--dark-hl-4);
58 | --hl-5: var(--dark-hl-5);
59 | --hl-6: var(--dark-hl-6);
60 | --hl-7: var(--dark-hl-7);
61 | --hl-8: var(--dark-hl-8);
62 | --hl-9: var(--dark-hl-9);
63 | --hl-10: var(--dark-hl-10);
64 | --hl-11: var(--dark-hl-11);
65 | --hl-12: var(--dark-hl-12);
66 | --hl-13: var(--dark-hl-13);
67 | --code-background: var(--dark-code-background);
68 | } }
69 |
70 | :root[data-theme='light'] {
71 | --hl-0: var(--light-hl-0);
72 | --hl-1: var(--light-hl-1);
73 | --hl-2: var(--light-hl-2);
74 | --hl-3: var(--light-hl-3);
75 | --hl-4: var(--light-hl-4);
76 | --hl-5: var(--light-hl-5);
77 | --hl-6: var(--light-hl-6);
78 | --hl-7: var(--light-hl-7);
79 | --hl-8: var(--light-hl-8);
80 | --hl-9: var(--light-hl-9);
81 | --hl-10: var(--light-hl-10);
82 | --hl-11: var(--light-hl-11);
83 | --hl-12: var(--light-hl-12);
84 | --hl-13: var(--light-hl-13);
85 | --code-background: var(--light-code-background);
86 | }
87 |
88 | :root[data-theme='dark'] {
89 | --hl-0: var(--dark-hl-0);
90 | --hl-1: var(--dark-hl-1);
91 | --hl-2: var(--dark-hl-2);
92 | --hl-3: var(--dark-hl-3);
93 | --hl-4: var(--dark-hl-4);
94 | --hl-5: var(--dark-hl-5);
95 | --hl-6: var(--dark-hl-6);
96 | --hl-7: var(--dark-hl-7);
97 | --hl-8: var(--dark-hl-8);
98 | --hl-9: var(--dark-hl-9);
99 | --hl-10: var(--dark-hl-10);
100 | --hl-11: var(--dark-hl-11);
101 | --hl-12: var(--dark-hl-12);
102 | --hl-13: var(--dark-hl-13);
103 | --code-background: var(--dark-code-background);
104 | }
105 |
106 | .hl-0 { color: var(--hl-0); }
107 | .hl-1 { color: var(--hl-1); }
108 | .hl-2 { color: var(--hl-2); }
109 | .hl-3 { color: var(--hl-3); }
110 | .hl-4 { color: var(--hl-4); }
111 | .hl-5 { color: var(--hl-5); }
112 | .hl-6 { color: var(--hl-6); }
113 | .hl-7 { color: var(--hl-7); }
114 | .hl-8 { color: var(--hl-8); }
115 | .hl-9 { color: var(--hl-9); }
116 | .hl-10 { color: var(--hl-10); }
117 | .hl-11 { color: var(--hl-11); }
118 | .hl-12 { color: var(--hl-12); }
119 | .hl-13 { color: var(--hl-13); }
120 | pre, code { background: var(--code-background); }
121 |
--------------------------------------------------------------------------------
/docs/typedoc/enums/Events.html:
--------------------------------------------------------------------------------
1 | Events | file-upload-with-preview
11 |
12 |
13 |
14 |
17 |
Enumeration Events
20 |
21 |
22 |
23 | Index
24 |
25 |
26 | Enumeration Members
27 |
32 |
33 | Enumeration Members
34 |
35 | CLEAR_ BUTTON_ CLICKED
36 | CLEAR_ BUTTON_ CLICKED: "fileUploadWithPreview:clearButtonClicked"
39 |
40 | IMAGE_ ADDED
41 | IMAGE_ ADDED: "fileUploadWithPreview:imagesAdded"
44 |
45 | IMAGE_ DELETED
46 | IMAGE_ DELETED: "fileUploadWithPreview:imageDeleted"
49 |
50 | IMAGE_ MULTI_ ITEM_ CLICKED
51 | IMAGE_ MULTI_ ITEM_ CLICKED: "fileUploadWithPreview:imageMultiItemClicked"
54 |
83 |
85 |
--------------------------------------------------------------------------------
/docs/typedoc/interfaces/ClearButtonClickedEvent.html:
--------------------------------------------------------------------------------
1 | ClearButtonClickedEvent | file-upload-with-preview
11 |
12 |
13 |
14 |
17 |
Interface ClearButtonClickedEvent
18 |
19 | Hierarchy
20 |
21 | ClearButtonClickedEvent
24 |
33 |
40 |
66 |
68 |
--------------------------------------------------------------------------------
/docs/typedoc/interfaces/ClearButtonClickedEventDetail.html:
--------------------------------------------------------------------------------
1 | ClearButtonClickedEventDetail | file-upload-with-preview
11 |
12 |
13 |
14 |
17 |
Interface ClearButtonClickedEventDetail
18 |
19 | Hierarchy
20 |
21 | ClearButtonClickedEventDetail
24 |
33 |
34 | Properties
35 |
36 | upload Id
37 | upload Id: string
40 |
66 |
68 |
--------------------------------------------------------------------------------
/docs/typedoc/interfaces/ImageAddedEvent.html:
--------------------------------------------------------------------------------
1 | ImageAddedEvent | file-upload-with-preview
11 |
12 |
13 |
14 |
17 |
Interface ImageAddedEvent
18 |
24 |
33 |
40 |
66 |
68 |
--------------------------------------------------------------------------------
/docs/typedoc/interfaces/ImageAddedEventDetail.html:
--------------------------------------------------------------------------------
1 | ImageAddedEventDetail | file-upload-with-preview
11 |
12 |
13 |
14 |
17 |
Interface ImageAddedEventDetail
18 |
19 | Hierarchy
20 |
21 | ImageAddedEventDetail
24 |
36 |
37 | Properties
38 |
39 | added Files Count
40 | added Files Count: number
43 |
44 | cached File Array
45 | cached File Array: File []
48 |
49 | files
50 | files: File [] | FileList
53 |
54 | upload Id
55 | upload Id: string
58 |
87 |
89 |
--------------------------------------------------------------------------------
/docs/typedoc/interfaces/ImageDeletedEvent.html:
--------------------------------------------------------------------------------
1 | ImageDeletedEvent | file-upload-with-preview
11 |
12 |
13 |
14 |
17 |
Interface ImageDeletedEvent
18 |
24 |
33 |
40 |
66 |
68 |
--------------------------------------------------------------------------------
/docs/typedoc/interfaces/ImageDeletedEventDetail.html:
--------------------------------------------------------------------------------
1 | ImageDeletedEventDetail | file-upload-with-preview
11 |
12 |
13 |
14 |
17 |
Interface ImageDeletedEventDetail
18 |
19 | Hierarchy
20 |
21 | ImageDeletedEventDetail
24 |
36 |
37 | Properties
38 |
39 | cached File Array
40 | cached File Array: File []
43 |
44 | current File Count
45 | current File Count: number
48 |
49 | index
50 | index: number
53 |
54 | upload Id
55 | upload Id: string
58 |
87 |
89 |
--------------------------------------------------------------------------------
/docs/typedoc/interfaces/ImageMultiItemClickedEvent.html:
--------------------------------------------------------------------------------
1 | ImageMultiItemClickedEvent | file-upload-with-preview
11 |
12 |
13 |
14 |
17 |
Interface ImageMultiItemClickedEvent
18 |
19 | Hierarchy
20 |
21 | ImageMultiItemClickedEvent
24 |
33 |
40 |
66 |
68 |
--------------------------------------------------------------------------------
/docs/typedoc/interfaces/ImageMultiItemClickedEventDetail.html:
--------------------------------------------------------------------------------
1 | ImageMultiItemClickedEventDetail | file-upload-with-preview
11 |
12 |
13 |
14 |
17 |
Interface ImageMultiItemClickedEventDetail
18 |
19 | Hierarchy
20 |
21 | ImageMultiItemClickedEventDetail
24 |
36 |
37 | Properties
38 |
39 | cached File Array
40 | cached File Array: File []
43 |
44 | file
45 | file: File
48 |
49 | index
50 | index: number
53 |
54 | upload Id
55 | upload Id: string
58 |
87 |
89 |
--------------------------------------------------------------------------------
/docs/typedoc/interfaces/Text.html:
--------------------------------------------------------------------------------
1 | Text | file-upload-with-preview
11 |
12 |
13 |
18 |
24 |
36 |
37 | Properties
38 |
39 | Optional
browse
40 | browse?: string
41 |
47 |
48 | Optional
choose File
49 | choose File?: string
50 |
56 |
57 | Optional
label
58 | label?: string
59 |
65 |
66 | Optional
selected Count
67 | selected Count?: string
68 |
74 |
103 |
105 |
--------------------------------------------------------------------------------
/example/custom-image.svg:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
--------------------------------------------------------------------------------
/example/favicon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/johndatserakis/file-upload-with-preview/c0c27abcf8368f733079ecbd131e671d811614d2/example/favicon.png
--------------------------------------------------------------------------------
/example/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | file-upload-with-preview | John Datserakis
7 |
8 |
9 |
10 |
11 |
12 |
13 |
51 |
52 |
53 |
57 |
58 |
65 |
66 |
67 |
68 |
69 |
--------------------------------------------------------------------------------
/example/index.scss:
--------------------------------------------------------------------------------
1 | $grey: #edede8;
2 |
3 | html {
4 | min-height: 100%;
5 | position: relative;
6 | }
7 |
8 | body {
9 | color: #333;
10 | font-family: 'Montserrat', sans-serif;
11 | font-size: 16px;
12 | height: 100%;
13 | line-height: 1.5;
14 | margin: 0;
15 | width: 100%;
16 | }
17 |
18 | a {
19 | color: #3157b3;
20 | }
21 |
22 | a:hover {
23 | color: #3f7ebd;
24 | }
25 |
26 | pre {
27 | background-color: #edede8;
28 | border-radius: 4px;
29 | color: #333;
30 | font-family: SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New', monospace;
31 | font-size: 14px;
32 | padding: 8px 16px;
33 | }
34 |
35 | .header-container {
36 | align-items: center;
37 | display: flex;
38 | flex-direction: column;
39 | margin-bottom: 16px;
40 |
41 | .content {
42 | font-size: 2.5rem;
43 | margin-bottom: 8px;
44 | text-align: center;
45 | }
46 |
47 | .code {
48 | margin-bottom: 8px;
49 | }
50 | }
51 |
52 | .demo-upload-container {
53 | align-items: center;
54 | display: flex;
55 | flex-direction: column;
56 | gap: 32px;
57 | justify-content: center;
58 | margin-bottom: 32px;
59 |
60 | @media (min-width: 992px) {
61 | flex-direction: row;
62 | gap: 64px;
63 | }
64 | }
65 |
66 | .demo-info-container {
67 | align-items: center;
68 | background: #3157b3;
69 | border-radius: 4px;
70 | color: white;
71 | display: flex;
72 | flex-direction: column;
73 | gap: 12px;
74 | justify-content: center;
75 | margin: 0 auto 16px;
76 | padding: 8px 12px;
77 | width: 300px;
78 |
79 | a {
80 | color: white;
81 |
82 | &:hover {
83 | color: darken(white, 10%);
84 | }
85 | }
86 | }
87 |
88 | // GitHub Banner
89 | .github-corner:hover .octo-arm {
90 | animation: octocat-wave 560ms ease-in-out;
91 | }
92 |
93 | @keyframes octocat-wave {
94 | 0%,
95 | 100% {
96 | transform: rotate(0);
97 | }
98 |
99 | 20%,
100 | 60% {
101 | transform: rotate(-25deg);
102 | }
103 |
104 | 40%,
105 | 80% {
106 | transform: rotate(10deg);
107 | }
108 | }
109 |
110 | @media (max-width: 500px) {
111 | .github-corner:hover .octo-arm {
112 | animation: none;
113 | }
114 |
115 | .github-corner .octo-arm {
116 | animation: octocat-wave 560ms ease-in-out;
117 | }
118 | }
119 |
--------------------------------------------------------------------------------
/example/index.ts:
--------------------------------------------------------------------------------
1 | import './index.scss';
2 | import '../src/index.scss';
3 |
4 | import {
5 | ClearButtonClickedEvent,
6 | Events,
7 | FileUploadWithPreview,
8 | ImageAddedEvent,
9 | ImageDeletedEvent,
10 | ImageMultiItemClickedEvent,
11 | } from '../src/index';
12 | import importedBaseImage from './custom-image.svg';
13 |
14 | const firstUpload = new FileUploadWithPreview('myFirstImage');
15 |
16 | const secondUpload = new FileUploadWithPreview('mySecondImage', {
17 | images: {
18 | baseImage: importedBaseImage,
19 | },
20 | maxFileCount: 5,
21 | multiple: true,
22 | presetFiles: [
23 | 'https://images.unsplash.com/photo-1557090495-fc9312e77b28?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=668&q=80',
24 | importedBaseImage,
25 | 'https://images.unsplash.com/photo-1632333650998-8842b63f5cfc?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=2787&q=80',
26 | ],
27 | text: {
28 | browse: 'Choose',
29 | chooseFile: 'Take your pick...',
30 | label: 'Choose Files to Upload',
31 | },
32 | });
33 |
34 | const firstUploadInfoButton = document.querySelector('.upload-info-button-first');
35 | const secondUploadInfoButton = document.querySelector('.upload-info-button-second');
36 |
37 | if (firstUploadInfoButton) {
38 | firstUploadInfoButton.addEventListener('click', () => {
39 | console.log('First upload:', firstUpload, firstUpload.cachedFileArray);
40 | });
41 | }
42 |
43 | if (secondUploadInfoButton) {
44 | secondUploadInfoButton.addEventListener('click', () => {
45 | console.log('Second upload:', secondUpload, secondUpload.cachedFileArray);
46 | });
47 | }
48 |
49 | window.addEventListener(Events.IMAGE_ADDED, (e: Event) => {
50 | const { detail } = e as unknown as ImageAddedEvent;
51 |
52 | console.log('detail', detail);
53 | });
54 |
55 | window.addEventListener(Events.IMAGE_DELETED, (e: Event) => {
56 | const { detail } = e as unknown as ImageDeletedEvent;
57 |
58 | console.log('detail', detail);
59 | });
60 |
61 | window.addEventListener(Events.CLEAR_BUTTON_CLICKED, (e: Event) => {
62 | const { detail } = e as unknown as ClearButtonClickedEvent;
63 |
64 | console.log('detail', detail);
65 | });
66 |
67 | window.addEventListener(Events.IMAGE_MULTI_ITEM_CLICKED, (e: Event) => {
68 | const { detail } = e as unknown as ImageMultiItemClickedEvent;
69 |
70 | console.log('detail', detail);
71 | });
72 |
--------------------------------------------------------------------------------
/globals.d.ts:
--------------------------------------------------------------------------------
1 | declare module '*.svg' {
2 | const content: string;
3 | export default content;
4 | }
5 |
--------------------------------------------------------------------------------
/jest.config.cjs:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | moduleFileExtensions: ['js', 'ts'],
3 | testEnvironment: 'jsdom',
4 | transform: {
5 | '^.+\\.(scss|less|svg|png)$': './jest/style-mock.ts',
6 | '^.+\\.[t|j]sx?$': 'ts-jest',
7 | },
8 | };
9 |
--------------------------------------------------------------------------------
/jest/style-mock.ts:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | process() {
3 | return '';
4 | },
5 | };
6 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "author": "John Datserakis ",
3 | "bugs": {
4 | "url": "https://github.com/johndatserakis/file-upload-with-preview/issues"
5 | },
6 | "description": "Simple file-upload utility that shows a preview of the uploaded image. Written in TypeScript. No dependencies. Works well with or without a framework.",
7 | "devDependencies": {
8 | "@types/jest": "^29.5.0",
9 | "@types/node": "^18.15.3",
10 | "@typescript-eslint/eslint-plugin": "^5.55.0",
11 | "@typescript-eslint/parser": "^5.55.0",
12 | "eslint": "^8.36.0",
13 | "eslint-config-prettier": "^8.7.0",
14 | "eslint-plugin-import": "^2.27.5",
15 | "eslint-plugin-prettier": "^4.2.1",
16 | "eslint-plugin-simple-import-sort": "^10.0.0",
17 | "jest": "^27.3.1",
18 | "prettier": "^2.8.4",
19 | "rimraf": "^4.4.0",
20 | "sass": "^1.59.3",
21 | "ts-jest": "^27.1.4",
22 | "typedoc": "^0.23.27",
23 | "typescript": "^4.9.4",
24 | "vite": "^4.0.0",
25 | "vite-plugin-dts": "^2.1.0"
26 | },
27 | "engines": {
28 | "node": ">=10"
29 | },
30 | "exports": {
31 | "./package.json": "./package.json",
32 | ".": {
33 | "import": "./dist/index.js",
34 | "require": "./dist/index.cjs",
35 | "types": "./dist/index.d.ts"
36 | },
37 | "./dist/style.css": "./dist/style.css"
38 | },
39 | "files": [
40 | "dist"
41 | ],
42 | "keywords": [
43 | "upload",
44 | "uploader",
45 | "preview",
46 | "image",
47 | "file",
48 | "bootstrap"
49 | ],
50 | "license": "MIT",
51 | "main": "./dist/index.js",
52 | "name": "file-upload-with-preview",
53 | "repository": {
54 | "type": "git",
55 | "url": "git+https://github.com/johndatserakis/file-upload-with-preview.git"
56 | },
57 | "scripts": {
58 | "build": "yarn clear:dist && yarn clear:docs && yarn typescript:check-types && yarn lint:fix && yarn prettier:format && yarn test && yarn build:example && yarn build:library && yarn build:typedoc",
59 | "build:example": "vite build --config vite.config.app.ts",
60 | "build:library": "vite build --config vite.config.library.ts",
61 | "build:typedoc": "yarn typedoc",
62 | "clear:dist": "rimraf ./dist",
63 | "clear:docs": "rimraf ./docs ",
64 | "dev": "vite --config vite.config.app.ts",
65 | "lint": "eslint .",
66 | "lint:fix": "npm run lint -- --fix",
67 | "prettier:format": "prettier 'src/**/*.ts' --write",
68 | "test": "jest",
69 | "typescript:check-types": "tsc --noEmit"
70 | },
71 | "sideEffects": false,
72 | "style": "./dist/style.css",
73 | "type": "module",
74 | "types": "./dist/index.d.ts",
75 | "version": "6.1.2"
76 | }
77 |
--------------------------------------------------------------------------------
/public/file-upload-with-preview.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/johndatserakis/file-upload-with-preview/c0c27abcf8368f733079ecbd131e671d811614d2/public/file-upload-with-preview.jpg
--------------------------------------------------------------------------------
/src/assets/images/background.svg:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 | background-image
7 | Created with Sketch.
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/src/assets/images/base-image.svg:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 | base-image
7 | Created with Sketch.
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/src/assets/images/file-alt-success.svg:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 | file-alt-success
7 | Created with Sketch.
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/src/assets/images/pdf-success.svg:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 | pdf-success
7 | Created with Sketch.
8 |
9 |
10 |
11 | .PDF
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/src/assets/images/video-success.svg:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 | video-success
7 | Created with Sketch.
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/src/constants/events.ts:
--------------------------------------------------------------------------------
1 | export enum Events {
2 | IMAGE_ADDED = 'fileUploadWithPreview:imagesAdded',
3 | IMAGE_DELETED = 'fileUploadWithPreview:imageDeleted',
4 | CLEAR_BUTTON_CLICKED = 'fileUploadWithPreview:clearButtonClicked',
5 | IMAGE_MULTI_ITEM_CLICKED = 'fileUploadWithPreview:imageMultiItemClicked',
6 | }
7 |
--------------------------------------------------------------------------------
/src/constants/file.ts:
--------------------------------------------------------------------------------
1 | export const UNIQUE_ID_IDENTIFIER = ':upload:';
2 |
--------------------------------------------------------------------------------
/src/constants/style.ts:
--------------------------------------------------------------------------------
1 | export const MULTI_ITEM_CLEAR_ANIMATION_CLASS = 'multi-item-clear-animation';
2 |
--------------------------------------------------------------------------------
/src/constants/text.ts:
--------------------------------------------------------------------------------
1 | export const DEFAULT_CHOOSE_FILE_TEXT = 'Choose file...';
2 | export const DEFAULT_BROWSE_TEXT = 'Browse';
3 | export const DEFAULT_FILES_SELECTED_TEXT = 'files selected';
4 | export const DEFAULT_LABEL_TEXT = 'Upload';
5 |
--------------------------------------------------------------------------------
/src/file-upload-with-preview.spec.ts:
--------------------------------------------------------------------------------
1 | import { DEFAULT_INITIALIZED_OBJECT_OPTIONS } from '../jest/constants/file';
2 | import { FileUploadWithPreview } from './index';
3 |
4 | const TEST_ID = 'myTestImage';
5 |
6 | describe('Module Actions', () => {
7 | beforeAll(() => {
8 | document.body.innerHTML =
9 | '
';
10 | });
11 |
12 | it('loads module', () => {
13 | expect.assertions(1);
14 |
15 | expect(FileUploadWithPreview).toBeTruthy();
16 | });
17 |
18 | it('initializes default options', () => {
19 | expect.assertions(2);
20 |
21 | const upload = new FileUploadWithPreview(TEST_ID);
22 |
23 | expect(upload.uploadId).toBe(TEST_ID);
24 | expect(upload.options).toMatchObject(DEFAULT_INITIALIZED_OBJECT_OPTIONS);
25 | });
26 |
27 | // Testing an actual file upload is tough - so we'll at least check that
28 | // the cachedFileArray can be pushed to.
29 | it('test that the cachedFileArray can be pushed to', () => {
30 | expect.assertions(2);
31 |
32 | const upload = new FileUploadWithPreview(TEST_ID);
33 |
34 | const file = new Blob([''], { type: 'image/jpeg' });
35 | const file1 = new Blob([''], { type: 'image/jpeg' });
36 | upload.cachedFileArray.push(file as File);
37 | upload.cachedFileArray.push(file1 as File);
38 |
39 | expect(upload.cachedFileArray.length).toBe(2);
40 | expect(upload.uploadId).toBe(TEST_ID);
41 | });
42 |
43 | it('clears an added file when the clear button is clicked', () => {
44 | expect.assertions(3);
45 |
46 | const upload = new FileUploadWithPreview(TEST_ID);
47 |
48 | const file = new Blob([''], { type: 'image/jpeg' });
49 | upload.cachedFileArray.push(file as File);
50 | expect(upload.cachedFileArray.length).toBe(1);
51 |
52 | const event = new Event('click', {
53 | bubbles: true,
54 | cancelable: true,
55 | });
56 | upload.clearButton.dispatchEvent(event);
57 |
58 | expect(upload.uploadId).toBe(TEST_ID);
59 | expect(upload.cachedFileArray).toEqual([]);
60 | });
61 | });
62 |
--------------------------------------------------------------------------------
/src/index.scss:
--------------------------------------------------------------------------------
1 | @function toPx($number) {
2 | @return $number + 0px;
3 | }
4 |
5 | $grey: #edede8;
6 | $font-color: #333;
7 | $white: #fff;
8 | $border-color: darken($grey, 20%);
9 | $black: #333;
10 | $hover: #777;
11 |
12 | $input-min-width: 300;
13 | $input-height: 40;
14 | $image-preview-height: 250;
15 | $border-radius: 4;
16 | $box-shadow: 0 4px 10px 0 rgba($black, 0.25);
17 |
18 | .custom-file-container {
19 | box-sizing: border-box;
20 | position: relative;
21 | display: block;
22 |
23 | * {
24 | box-sizing: border-box;
25 | }
26 |
27 | .label-container {
28 | align-items: center;
29 | display: flex;
30 | justify-content: space-between;
31 | margin-bottom: 4px;
32 | }
33 |
34 | .clear-button {
35 | color: $font-color;
36 | font-size: 26px;
37 | height: 26px;
38 | line-height: 26px;
39 | text-decoration: none;
40 | transition: color 0.2s ease-in-out;
41 |
42 | &:hover {
43 | color: $hover;
44 | }
45 | }
46 |
47 | .input-container {
48 | display: inline-block;
49 | height: 40px;
50 | margin-bottom: 8px;
51 | position: relative;
52 | width: 100%;
53 |
54 | &:hover {
55 | cursor: pointer;
56 | }
57 | }
58 |
59 | .input-hidden {
60 | height: toPx($input-height);
61 | margin: 0;
62 | max-width: 100%;
63 | min-width: toPx($input-min-width);
64 | opacity: 0;
65 | }
66 |
67 | .input-visible {
68 | background-clip: padding-box;
69 | background-color: $white;
70 | border-radius: toPx($border-radius);
71 | border: 1px solid $border-color;
72 | color: $font-color;
73 | height: toPx($input-height);
74 | left: 0;
75 | line-height: 1.5;
76 | overflow: hidden;
77 | padding: 8px 12px;
78 | position: absolute;
79 | right: 0;
80 | top: 0;
81 | user-select: none;
82 | z-index: 5;
83 | }
84 |
85 | .browse-button {
86 | background-color: $grey;
87 | border-left: 1px solid $border-color;
88 | color: $font-color;
89 | display: block;
90 | height: toPx($input-height - 2);
91 | padding: 8px 12px;
92 | position: absolute;
93 | right: 0;
94 | top: 0;
95 | z-index: 6;
96 | }
97 |
98 | .image-preview {
99 | background-color: $grey;
100 | background-position: center center;
101 | background-repeat: no-repeat;
102 | background-size: cover;
103 | border-radius: toPx($border-radius);
104 | height: toPx($image-preview-height);
105 | overflow: auto;
106 | padding: 4px;
107 | transition: background 0.2s ease-in-out;
108 | width: 100%;
109 | }
110 |
111 | .image-preview-item {
112 | background-position: center center;
113 | background-repeat: no-repeat;
114 | background-size: cover;
115 | border-radius: toPx($border-radius);
116 | box-shadow: $box-shadow;
117 | float: left;
118 | height: 90px;
119 | margin: 1.858736059%;
120 | position: relative;
121 | transition: background 0.2s ease-in-out, opacity 0.2s ease-in-out;
122 | width: 29.615861214%;
123 |
124 | &.multi-item-clear-animation {
125 | opacity: 0;
126 | }
127 | }
128 |
129 | .image-preview-item-clear {
130 | background: $grey;
131 | border-radius: 50%;
132 | box-shadow: $box-shadow;
133 | height: 20px;
134 | left: -6px;
135 | margin-top: -6px;
136 | position: absolute;
137 | text-align: center;
138 | transition: background 0.2s ease-in-out, color 0.2s ease-in-out;
139 | width: 20px;
140 |
141 | &:hover {
142 | background: darken($grey, 5%);
143 | cursor: pointer;
144 | }
145 | }
146 |
147 | .image-preview-item-clear-icon {
148 | color: $font-color;
149 | display: block;
150 | margin-top: -2px;
151 | }
152 | }
153 |
--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------
1 | import './index.scss'; // Importing the CSS allows it to be exported in the build
2 |
3 | // Constants
4 | export * from './constants/events';
5 | export * from './constants/file';
6 | export * from './constants/images';
7 | export * from './constants/style';
8 | export * from './constants/text';
9 |
10 | // Types
11 | export * from './types/events';
12 | export * from './types/options';
13 |
14 | // Lib
15 | export * from './file-upload-with-preview';
16 |
--------------------------------------------------------------------------------
/src/types/events.ts:
--------------------------------------------------------------------------------
1 | export interface ImageAddedEventDetail {
2 | addedFilesCount: number;
3 | cachedFileArray: File[];
4 | files: FileList | File[];
5 | uploadId: string;
6 | }
7 | export interface ImageAddedEvent {
8 | detail: ImageAddedEventDetail;
9 | }
10 |
11 | export interface ImageDeletedEventDetail {
12 | cachedFileArray: File[];
13 | currentFileCount: number;
14 | index: number;
15 | uploadId: string;
16 | }
17 | export interface ImageDeletedEvent {
18 | detail: ImageDeletedEventDetail;
19 | }
20 |
21 | export interface ClearButtonClickedEventDetail {
22 | uploadId: string;
23 | }
24 | export interface ClearButtonClickedEvent {
25 | detail: ClearButtonClickedEventDetail;
26 | }
27 |
28 | export interface ImageMultiItemClickedEventDetail {
29 | cachedFileArray: File[];
30 | file: File;
31 | index: number;
32 | uploadId: string;
33 | }
34 | export interface ImageMultiItemClickedEvent {
35 | detail: ImageMultiItemClickedEventDetail;
36 | }
37 |
--------------------------------------------------------------------------------
/src/types/options.ts:
--------------------------------------------------------------------------------
1 | export interface Text {
2 | /**
3 | * Browse button text
4 | *
5 | * @default "Browse"
6 | */
7 | browse?: string;
8 | /**
9 | * Placeholder text
10 | *
11 | * @default "Choose file..."
12 | */
13 | chooseFile?: string;
14 | /**
15 | * Main input label text
16 | *
17 | * @default "Upload"
18 | */
19 | label?: string;
20 | /**
21 | * Count descriptor text. Defaults to `${ n } files selected`.
22 | *
23 | * @default "files selected"
24 | */
25 | selectedCount?: string;
26 | }
27 |
28 | export interface Images {
29 | /**
30 | * Background image for image grid
31 | *
32 | * @default DEFAULT_BACKGROUND_IMAGE
33 | */
34 | backgroundImage?: string;
35 | /**
36 | * Placeholder image
37 | *
38 | * @default DEFAULT_BASE_IMAGE
39 | */
40 | baseImage?: string;
41 | /**
42 | * Alternate file upload image
43 | *
44 | * @default DEFAULT_SUCCESS_FILE_ALT_IMAGE
45 | */
46 | successFileAltImage?: string;
47 | /**
48 | * PDF upload image
49 | *
50 | * @default DEFAULT_SUCCESS_PDF_IMAGE
51 | */
52 | successPdfImage?: string;
53 | /**
54 | * Video upload image
55 | *
56 | * @default DEFAULT_SUCCESS_VIDEO_IMAGE
57 | */
58 | successVideoImage?: string;
59 | }
60 |
61 | export type PresetFiles = string[];
62 |
63 | /**
64 | * Options to customize the library
65 | */
66 | export interface Options {
67 | /**
68 | * Type of files to accept in your input
69 | *
70 | * @default '*'
71 | */
72 | accept?: HTMLInputElement['accept'];
73 | /**
74 | * Configurable images for the library
75 | */
76 | images?: Images;
77 | /**
78 | * Set a maximum number of files you'd like the component to deal with. Must be `> 0` if set. By default there is no limit.
79 | *
80 | * @default 0
81 | */
82 | maxFileCount?: number;
83 | /**
84 | * Set to `true` if you want to allow the user to selected multiple images. Will use grid view in the image preview if set.
85 | *
86 | * @default false
87 | */
88 | multiple?: boolean;
89 | /**
90 | * Provide an array of image paths to be automatically uploaded and displayed on page load (can be images hosted on server or URLs)
91 | *
92 | * @default []
93 | */
94 | presetFiles?: PresetFiles;
95 | /**
96 | * Show a delete button on images in the grid
97 | *
98 | * @default true
99 | */
100 | showDeleteButtonOnImages?: boolean;
101 | /**
102 | * Configurable text for the library
103 | */
104 | text?: Text;
105 | }
106 |
107 | export type RequiredOptions = Required & {
108 | images: Required;
109 | text: Required;
110 | };
111 |
--------------------------------------------------------------------------------
/src/utils/file.ts:
--------------------------------------------------------------------------------
1 | export const generateUniqueId = () => Math.random().toString(16).slice(2);
2 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "allowSyntheticDefaultImports": true,
4 | "declaration": true,
5 | "declarationDir": "dist/types",
6 | "declarationMap": true,
7 | "esModuleInterop": true,
8 | "forceConsistentCasingInFileNames": true,
9 | "module": "CommonJS",
10 | "moduleResolution": "node",
11 | "noFallthroughCasesInSwitch": true,
12 | "noImplicitAny": true,
13 | "noImplicitReturns": true,
14 | "noUnusedLocals": true,
15 | "noUnusedParameters": true,
16 | "sourceMap": true,
17 | "strict": true,
18 | "target": "ES6",
19 | "types": ["vite/client", "jest", "node"]
20 | },
21 | "include": ["src/**/*"],
22 | "exclude": ["node_modules", "dist", "docs"],
23 | "files": ["example/index.ts"]
24 | }
25 |
--------------------------------------------------------------------------------
/typedoc.json:
--------------------------------------------------------------------------------
1 | {
2 | "entryPoints": ["src/index.ts"],
3 | "out": "docs/typedoc"
4 | }
5 |
--------------------------------------------------------------------------------
/vite.config.app.ts:
--------------------------------------------------------------------------------
1 | import { defineConfig } from 'vite';
2 |
3 | export default defineConfig({
4 | base: '/file-upload-with-preview/', // For GitHub docs support
5 | build: {
6 | outDir: '../docs', // Actual root "docs" folder because we're in "root" context here
7 | },
8 | root: 'example',
9 | });
10 |
--------------------------------------------------------------------------------
/vite.config.library.ts:
--------------------------------------------------------------------------------
1 | import { resolve } from 'path';
2 | import { defineConfig } from 'vite';
3 | import dts from 'vite-plugin-dts';
4 |
5 | /**
6 | * Vite Library Mode: https://vitejs.dev/guide/build.html#library-mode
7 | */
8 | export default defineConfig({
9 | build: {
10 | lib: {
11 | entry: resolve(__dirname, 'src/index.ts'),
12 | fileName: 'index',
13 | formats: ['es', 'cjs', 'iife'],
14 | name: 'FileUploadWithPreview',
15 | },
16 | outDir: './dist',
17 | rollupOptions: {
18 | external: [], // 'react', 'vue'
19 | output: {
20 | globals: {}, // react: 'React'
21 | },
22 | },
23 | },
24 | plugins: [dts()],
25 | });
26 |
--------------------------------------------------------------------------------
Browse button text
42 | 43 |Default
"Browse"
44 |