From d7c6034a121ac2edb6525cda233221752145a559 Mon Sep 17 00:00:00 2001 From: "Mark R. Florkowski" Date: Fri, 12 Jul 2024 10:51:55 -0700 Subject: [PATCH 001/110] add onSelect to --- docs/src/pages/api-reference/react.mdx | 5 +++++ packages/react/src/components/button.tsx | 3 +++ 2 files changed, 8 insertions(+) diff --git a/docs/src/pages/api-reference/react.mdx b/docs/src/pages/api-reference/react.mdx index 4bb3a0d02a..09b3cc8a9b 100644 --- a/docs/src/pages/api-reference/react.mdx +++ b/docs/src/pages/api-reference/react.mdx @@ -120,6 +120,10 @@ export const OurUploadButton = () => ( // Do something once upload begins console.log("Uploading: ", name); }} + onSelect={(acceptedFiles) => { + // Do something with the accepted files + console.log("Accepted files: ", acceptedFiles); + }} /> ); ``` @@ -142,6 +146,7 @@ export const OurUploadButton = () => ( | onUploadBegin | function | No | Added in `v5.4` | callback function for upload begin | | config | Config | No | Added in `v5.7` | object to pass additional configuration for button | | disabled | boolean | No | Added in `v6.7` | Disables the button. | +| onSelect | `(files: File[]) => void` | No | Added in `v6.8` | Callback function that runs when files are selected. | Config object diff --git a/packages/react/src/components/button.tsx b/packages/react/src/components/button.tsx index 6c25e05e22..b01861fb45 100644 --- a/packages/react/src/components/button.tsx +++ b/packages/react/src/components/button.tsx @@ -64,6 +64,7 @@ export type UploadButtonProps< * @see https://docs.uploadthing.com/theming#content-customisation */ content?: ButtonContent; + onSelect?: (files: File[]) => void; disabled?: boolean; }; @@ -165,6 +166,8 @@ export function UploadButton< if (!e.target.files) return; const selectedFiles = Array.from(e.target.files); + if ($props.onSelect) $props.onSelect(selectedFiles); + if (mode === "manual") { setFiles(selectedFiles); return; From 5e5466dad1053696d7e92e7789a72cc8fb1dd0f1 Mon Sep 17 00:00:00 2001 From: "Mark R. Florkowski" Date: Fri, 12 Jul 2024 11:46:50 -0700 Subject: [PATCH 002/110] exhaustive deps --- packages/react/src/components/button.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/react/src/components/button.tsx b/packages/react/src/components/button.tsx index b01861fb45..a7c44d19a3 100644 --- a/packages/react/src/components/button.tsx +++ b/packages/react/src/components/button.tsx @@ -178,7 +178,7 @@ export function UploadButton< disabled: fileTypes.length === 0, tabIndex: fileTypes.length === 0 ? -1 : 0, }), - [fileTypes, mode, multiple, uploadFiles], + [$props, fileTypes, mode, multiple, uploadFiles], ); if ($props.__internal_button_disabled) inputProps.disabled = true; From f011e33bd1be4dd10c45726fa8d424c74cb48667 Mon Sep 17 00:00:00 2001 From: "Mark R. Florkowski" Date: Fri, 12 Jul 2024 12:31:38 -0700 Subject: [PATCH 003/110] solid --- packages/solid/src/components/button.tsx | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/packages/solid/src/components/button.tsx b/packages/solid/src/components/button.tsx index 4b507ee2df..c20f8f446f 100644 --- a/packages/solid/src/components/button.tsx +++ b/packages/solid/src/components/button.tsx @@ -59,6 +59,7 @@ export type UploadButtonProps< * @see https://docs.uploadthing.com/theming#content-customisation */ content?: ButtonContent; + onSelect?: (files: File[]) => void; }; /** @@ -161,9 +162,12 @@ export function UploadButton< accept={generateMimeTypes(fileInfo().fileTypes).join(", ")} onChange={(e) => { if (!e.target.files) return; + const selectedFiles = Array.from(e.target.files); + + if ($props.onSelect) $props.onSelect(selectedFiles); + const input = "input" in $props ? $props.input : undefined; - const files = Array.from(e.target.files); - void uploadedThing.startUpload(files, input); + void uploadedThing.startUpload(selectedFiles, input); }} /> {contentFieldToContent($props.content?.button, styleFieldArg) ?? From 52b616c4477eba78e376149f6be31babb178cc8b Mon Sep 17 00:00:00 2001 From: "Mark R. Florkowski" Date: Fri, 12 Jul 2024 12:31:45 -0700 Subject: [PATCH 004/110] svelte --- packages/svelte/src/lib/component/UploadButton.svelte | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/packages/svelte/src/lib/component/UploadButton.svelte b/packages/svelte/src/lib/component/UploadButton.svelte index c3f99a7a6a..a1d2e9bde9 100644 --- a/packages/svelte/src/lib/component/UploadButton.svelte +++ b/packages/svelte/src/lib/component/UploadButton.svelte @@ -12,6 +12,7 @@ lang="ts" generics="TRouter extends FileRouter , TEndpoint extends keyof TRouter, TSkipPolling extends boolean = false" > + import { on } from "events"; import { onMount } from "svelte"; import { twMerge } from "tailwind-merge"; @@ -26,8 +27,8 @@ generatePermittedFileTypes, } from "uploadthing/client"; - import type { UploadthingComponentProps } from "../types"; import { INTERNAL_createUploadThingGen } from "../create-uploadthing"; + import type { UploadthingComponentProps } from "../types"; import { getFilesFromClipboardEvent, progressWidths } from "./shared"; import Spinner from "./Spinner.svelte"; @@ -62,6 +63,8 @@ // Allow to disable the button export let __internal_button_disabled: boolean | undefined = undefined; + export let onSelect: (files: File[]) => void; + let uploadProgress = 0; let fileInputRef: HTMLInputElement; let labelRef: HTMLLabelElement; @@ -198,13 +201,15 @@ Example: bind:this={fileInputRef} class="sr-only" type="file" - accept={generateMimeTypes(fileTypes ).join(", ")} + accept={generateMimeTypes(fileTypes).join(", ")} disabled={__internal_button_disabled ?? !ready} {multiple} on:change={(e) => { if (!e.currentTarget?.files) return; const selectedFiles = Array.from(e.currentTarget.files); + if (onSelect) onSelect(selectedFiles); + if (mode === "manual") { files = selectedFiles; isManualTriggerDisplayed = true; From 3f050ec7f29ee96e4d97e5422762d0624754050c Mon Sep 17 00:00:00 2001 From: "Mark R. Florkowski" Date: Fri, 12 Jul 2024 12:31:52 -0700 Subject: [PATCH 005/110] vue --- packages/vue/src/components/button.tsx | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/vue/src/components/button.tsx b/packages/vue/src/components/button.tsx index ce669dfc8b..3d344b2968 100644 --- a/packages/vue/src/components/button.tsx +++ b/packages/vue/src/components/button.tsx @@ -61,6 +61,7 @@ export type UploadButtonProps< * @see https://docs.uploadthing.com/theming#content-customisation */ content?: ButtonContent; + onSelect?: (files: File[]) => void; }; export const generateUploadButton = ( @@ -135,6 +136,8 @@ export const generateUploadButton = ( const { files: selectedFiles } = e.target as HTMLInputElement; if (!selectedFiles) return; + if ($props.onSelect) $props.onSelect(Array.from(selectedFiles)); + if (mode === "manual") { files.value = Array.from(selectedFiles); return; From 27bec4d4433dcd57b40ee6482b71133bb9f61b38 Mon Sep 17 00:00:00 2001 From: "Mark R. Florkowski" Date: Fri, 12 Jul 2024 12:38:34 -0700 Subject: [PATCH 006/110] optional on svelte --- packages/svelte/src/lib/component/UploadButton.svelte | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/svelte/src/lib/component/UploadButton.svelte b/packages/svelte/src/lib/component/UploadButton.svelte index a1d2e9bde9..b6f20dd692 100644 --- a/packages/svelte/src/lib/component/UploadButton.svelte +++ b/packages/svelte/src/lib/component/UploadButton.svelte @@ -63,7 +63,7 @@ // Allow to disable the button export let __internal_button_disabled: boolean | undefined = undefined; - export let onSelect: (files: File[]) => void; + export let onSelect: ((files: File[]) => void) | undefined = undefined; let uploadProgress = 0; let fileInputRef: HTMLInputElement; From ad23bdbc3b1602b5d4f283caabe2d3e894e969d9 Mon Sep 17 00:00:00 2001 From: "Mark R. Florkowski" Date: Fri, 12 Jul 2024 12:39:53 -0700 Subject: [PATCH 007/110] rm unused import --- packages/svelte/src/lib/component/UploadButton.svelte | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/svelte/src/lib/component/UploadButton.svelte b/packages/svelte/src/lib/component/UploadButton.svelte index b6f20dd692..23369108f0 100644 --- a/packages/svelte/src/lib/component/UploadButton.svelte +++ b/packages/svelte/src/lib/component/UploadButton.svelte @@ -12,7 +12,6 @@ lang="ts" generics="TRouter extends FileRouter , TEndpoint extends keyof TRouter, TSkipPolling extends boolean = false" > - import { on } from "events"; import { onMount } from "svelte"; import { twMerge } from "tailwind-merge"; From 89e53bb75748c91773d1c5f3f5bba5ef54902f8d Mon Sep 17 00:00:00 2001 From: "Mark R. Florkowski" Date: Fri, 12 Jul 2024 14:26:28 -0700 Subject: [PATCH 008/110] Create giant-candles-wash.md --- .changeset/giant-candles-wash.md | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 .changeset/giant-candles-wash.md diff --git a/.changeset/giant-candles-wash.md b/.changeset/giant-candles-wash.md new file mode 100644 index 0000000000..4bba5cf6a0 --- /dev/null +++ b/.changeset/giant-candles-wash.md @@ -0,0 +1,8 @@ +--- +"@uploadthing/react": minor +"@uploadthing/solid": minor +"@uploadthing/svelte": minor +"@uploadthing/vue": minor +--- + +feat: Add `onSelect` to From 6e38c3bb58fc03934b24813482381ab5cd3fce9d Mon Sep 17 00:00:00 2001 From: "Mark R. Florkowski" Date: Fri, 12 Jul 2024 14:33:34 -0700 Subject: [PATCH 009/110] fix pkg-pr-new action? --- .github/workflows/pkg-pr-new.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/pkg-pr-new.yaml b/.github/workflows/pkg-pr-new.yaml index 678cbb8217..ed85cfe3d0 100644 --- a/.github/workflows/pkg-pr-new.yaml +++ b/.github/workflows/pkg-pr-new.yaml @@ -13,7 +13,7 @@ jobs: - name: Get changed packages id: changed-packages run: | - echo "::set-output name=changed_packages::$(git diff --name-only main ./packages | awk -F'/' '{print "./packages/" $2}' | sort -u | tr '\n' ' ')" + echo "::set-output name=changed_packages::$(git diff --name-only origin/main ./packages | awk -F'/' '{print "./packages/" $2}' | sort -u | tr '\n' ' ')" - name: Setup uses: ./tooling/gh-actions/setup From 4e3e915086c0e81da725e95ba0cc524a0a665b1c Mon Sep 17 00:00:00 2001 From: "Mark R. Florkowski" Date: Fri, 12 Jul 2024 14:38:10 -0700 Subject: [PATCH 010/110] try again --- .github/workflows/pkg-pr-new.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/pkg-pr-new.yaml b/.github/workflows/pkg-pr-new.yaml index ed85cfe3d0..04eb5e08b3 100644 --- a/.github/workflows/pkg-pr-new.yaml +++ b/.github/workflows/pkg-pr-new.yaml @@ -13,7 +13,7 @@ jobs: - name: Get changed packages id: changed-packages run: | - echo "::set-output name=changed_packages::$(git diff --name-only origin/main ./packages | awk -F'/' '{print "./packages/" $2}' | sort -u | tr '\n' ' ')" + echo "::set-output name=changed_packages::$(git diff --name-only origin/main origin/${GITHUB_HEAD_REF} ./packages | awk -F'/' '{print "./packages/" $2}' | sort -u | tr '\n' ' ')" - name: Setup uses: ./tooling/gh-actions/setup From 4c05d8f4571e47e4826fbf4c9685422918b190ef Mon Sep 17 00:00:00 2001 From: "Mark R. Florkowski" Date: Fri, 12 Jul 2024 14:39:57 -0700 Subject: [PATCH 011/110] fetch depth --- .github/workflows/pkg-pr-new.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/pkg-pr-new.yaml b/.github/workflows/pkg-pr-new.yaml index 04eb5e08b3..e0354dafa3 100644 --- a/.github/workflows/pkg-pr-new.yaml +++ b/.github/workflows/pkg-pr-new.yaml @@ -9,6 +9,8 @@ jobs: steps: - name: Checkout uses: actions/checkout@v4 + with: + fetch-depth: 0 - name: Get changed packages id: changed-packages From 5f857b2c5a1d2e8bd5f11b7f25de816ea5fd3095 Mon Sep 17 00:00:00 2001 From: "Mark R. Florkowski" Date: Fri, 12 Jul 2024 14:56:47 -0700 Subject: [PATCH 012/110] docs callout about files [] --- docs/src/pages/api-reference/react.mdx | 34 +++++++++++++------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/docs/src/pages/api-reference/react.mdx b/docs/src/pages/api-reference/react.mdx index 09b3cc8a9b..93a71ae81a 100644 --- a/docs/src/pages/api-reference/react.mdx +++ b/docs/src/pages/api-reference/react.mdx @@ -130,23 +130,23 @@ export const OurUploadButton = () => ( #### Configuration -| Prop | Type | Required | Notes | Description | -| :--------------------- | :--------------------------------------------------- | :---------------------- | :-------------- | :-------------------------------------------------------------------------------------------------------------------------- | -| `` | generic | Yes | | The type from the FileRouter you defined in your backend | -| endpoint | string | Yes | | The name of the [route](./server#FileRouter) you want this button to upload to | -| url | string \| URL | No | Added in `v6.0` | The url to where you are serving your uploadthing file router, required if you're not serving from `/api/uploadthing` | -| input | string | If set on the fileroute | Added in `v5.0` | Object matching the input schema that was set for this endpoint in your File Router | -| headers | `HeadersInit \| (() => MaybePromise\)` | No | Added in `v6.4` | Send custom headers with the API requests to your server. Primarily for authentication when using a separate backend. | -| skipPolling | boolean | No | Added in `v6.3` | Skip polling for server data. Defaults to `true` if no `onClientUploadComplete` is set, else `false`. | -| onClientUploadComplete | (res: UploadedFileResponse[]) => void | No | | callback function that runs **after** the serverside [`onUploadComplete`](/api-reference/server#onuploadcomplete) callback. | -| onUploadError | function | No | | callback function when upload fails | -| onUploadAborted | function | No | Added in `v6.7` | callback function when upload is aborted | -| onUploadProgress | function | No | Added in `v5.1` | callback function for upload progress | -| onBeforeUploadBegin | `(files: File[]) => File[]` | No | Added in `v6.0` | callback function called before uploading starts. The files returned are the files that will be uploaded | -| onUploadBegin | function | No | Added in `v5.4` | callback function for upload begin | -| config | Config | No | Added in `v5.7` | object to pass additional configuration for button | -| disabled | boolean | No | Added in `v6.7` | Disables the button. | -| onSelect | `(files: File[]) => void` | No | Added in `v6.8` | Callback function that runs when files are selected. | +| Prop | Type | Required | Notes | Description | +| :--------------------- | :--------------------------------------------------- | :---------------------- | :-------------- | :---------------------------------------------------------------------------------------------------------------------------------------- | +| `` | generic | Yes | | The type from the FileRouter you defined in your backend | +| endpoint | string | Yes | | The name of the [route](./server#FileRouter) you want this button to upload to | +| url | string \| URL | No | Added in `v6.0` | The url to where you are serving your uploadthing file router, required if you're not serving from `/api/uploadthing` | +| input | string | If set on the fileroute | Added in `v5.0` | Object matching the input schema that was set for this endpoint in your File Router | +| headers | `HeadersInit \| (() => MaybePromise\)` | No | Added in `v6.4` | Send custom headers with the API requests to your server. Primarily for authentication when using a separate backend. | +| skipPolling | boolean | No | Added in `v6.3` | Skip polling for server data. Defaults to `true` if no `onClientUploadComplete` is set, else `false`. | +| onClientUploadComplete | (res: UploadedFileResponse[]) => void | No | | callback function that runs **after** the serverside [`onUploadComplete`](/api-reference/server#onuploadcomplete) callback. | +| onUploadError | function | No | | callback function when upload fails | +| onUploadAborted | function | No | Added in `v6.7` | callback function when upload is aborted | +| onUploadProgress | function | No | Added in `v5.1` | callback function for upload progress | +| onBeforeUploadBegin | `(files: File[]) => File[]` | No | Added in `v6.0` | callback function called before uploading starts. The files returned are the files that will be uploaded | +| onUploadBegin | function | No | Added in `v5.4` | callback function for upload begin | +| config | Config | No | Added in `v5.7` | object to pass additional configuration for button | +| disabled | boolean | No | Added in `v6.7` | Disables the button. | +| onSelect | `(files: File[]) => void` | No | Added in `v6.8` | Callback function that runs when files are selected. Note: `files` is an array containing _all_ selected files, not just the latest ones. | Config object From d49ea8ff8ce616b5d8193b1054e5b77b8d239c94 Mon Sep 17 00:00:00 2001 From: "Mark R. Florkowski" Date: Fri, 12 Jul 2024 21:39:17 -0700 Subject: [PATCH 013/110] onDrop -> onChange --- packages/react/src/components/button.tsx | 7 +++++-- packages/react/src/components/dropzone.tsx | 13 +++++++++++++ packages/solid/src/components/button.tsx | 4 ++-- packages/solid/src/components/dropzone.tsx | 8 ++++++++ packages/vue/src/components/button.tsx | 6 ++++-- packages/vue/src/components/dropzone.tsx | 10 ++++++++++ 6 files changed, 42 insertions(+), 6 deletions(-) diff --git a/packages/react/src/components/button.tsx b/packages/react/src/components/button.tsx index a7c44d19a3..95d428defd 100644 --- a/packages/react/src/components/button.tsx +++ b/packages/react/src/components/button.tsx @@ -64,7 +64,7 @@ export type UploadButtonProps< * @see https://docs.uploadthing.com/theming#content-customisation */ content?: ButtonContent; - onSelect?: (files: File[]) => void; + onChange?: (files: File[]) => void; disabled?: boolean; }; @@ -166,7 +166,7 @@ export function UploadButton< if (!e.target.files) return; const selectedFiles = Array.from(e.target.files); - if ($props.onSelect) $props.onSelect(selectedFiles); + if ($props.onChange) $props.onChange(selectedFiles); if (mode === "manual") { setFiles(selectedFiles); @@ -201,6 +201,9 @@ export function UploadButton< let filesToUpload = pastedFiles; setFiles((prev) => { filesToUpload = [...prev, ...pastedFiles]; + + if ($props.onChange) $props.onChange(filesToUpload); + return filesToUpload; }); diff --git a/packages/react/src/components/dropzone.tsx b/packages/react/src/components/dropzone.tsx index 831ab85385..c3008a14ba 100644 --- a/packages/react/src/components/dropzone.tsx +++ b/packages/react/src/components/dropzone.tsx @@ -71,8 +71,15 @@ export type UploadDropzoneProps< * Callback called when files are dropped or pasted. * * @param acceptedFiles - The files that were accepted. + * @deprecated Use `onChange` instead */ onDrop?: (acceptedFiles: File[]) => void; + /** + * Callback called when files are dropped or pasted. + * + * @param acceptedFiles - The files that were accepted. + */ + onChange?: (files: File[]) => void; disabled?: boolean; }; @@ -165,6 +172,7 @@ export function UploadDropzone< const onDrop = useCallback( (acceptedFiles: File[]) => { $props.onDrop?.(acceptedFiles); + $props.onChange?.(acceptedFiles); setFiles(acceptedFiles); @@ -222,9 +230,14 @@ export function UploadDropzone< let filesToUpload = pastedFiles; setFiles((prev) => { filesToUpload = [...prev, ...pastedFiles]; + + if ($props.onChange) $props.onChange(filesToUpload); + return filesToUpload; }); + if ($props.onChange) $props.onChange(filesToUpload); + if (mode === "auto") uploadFiles(filesToUpload); }; diff --git a/packages/solid/src/components/button.tsx b/packages/solid/src/components/button.tsx index c20f8f446f..72d15c051d 100644 --- a/packages/solid/src/components/button.tsx +++ b/packages/solid/src/components/button.tsx @@ -59,7 +59,7 @@ export type UploadButtonProps< * @see https://docs.uploadthing.com/theming#content-customisation */ content?: ButtonContent; - onSelect?: (files: File[]) => void; + onChange?: (files: File[]) => void; }; /** @@ -164,7 +164,7 @@ export function UploadButton< if (!e.target.files) return; const selectedFiles = Array.from(e.target.files); - if ($props.onSelect) $props.onSelect(selectedFiles); + if ($props.onChange) $props.onChange(selectedFiles); const input = "input" in $props ? $props.input : undefined; void uploadedThing.startUpload(selectedFiles, input); diff --git a/packages/solid/src/components/dropzone.tsx b/packages/solid/src/components/dropzone.tsx index 774c6dc6ac..93b9c531ef 100644 --- a/packages/solid/src/components/dropzone.tsx +++ b/packages/solid/src/components/dropzone.tsx @@ -67,8 +67,15 @@ export type UploadDropzoneProps< * Callback called when files are dropped or pasted. * * @param acceptedFiles - The files that were accepted. + * @deprecated Use `onChange` instead */ onDrop?: (acceptedFiles: File[]) => void; + /** + * Callback called when files are dropped or pasted. + * + * @param acceptedFiles - The files that were accepted. + */ + onChange?: (files: File[]) => void; config?: { mode?: "manual" | "auto"; }; @@ -111,6 +118,7 @@ export const UploadDropzone = < const [files, setFiles] = createSignal([]); const onDrop = (acceptedFiles: File[]) => { $props.onDrop?.(acceptedFiles); + $props.onChange?.(acceptedFiles); setFiles(acceptedFiles); diff --git a/packages/vue/src/components/button.tsx b/packages/vue/src/components/button.tsx index 3d344b2968..67c7fcb533 100644 --- a/packages/vue/src/components/button.tsx +++ b/packages/vue/src/components/button.tsx @@ -61,7 +61,7 @@ export type UploadButtonProps< * @see https://docs.uploadthing.com/theming#content-customisation */ content?: ButtonContent; - onSelect?: (files: File[]) => void; + onChange?: (files: File[]) => void; }; export const generateUploadButton = ( @@ -136,7 +136,7 @@ export const generateUploadButton = ( const { files: selectedFiles } = e.target as HTMLInputElement; if (!selectedFiles) return; - if ($props.onSelect) $props.onSelect(Array.from(selectedFiles)); + if ($props.onChange) $props.onChange(Array.from(selectedFiles)); if (mode === "manual") { files.value = Array.from(selectedFiles); @@ -163,6 +163,8 @@ export const generateUploadButton = ( files.value = [...files.value, ...pastedFiles]; + if ($props.onChange) $props.onChange(files.value); + if (mode === "auto") { const input = "input" in $props ? $props.input : undefined; void startUpload(files.value, input); diff --git a/packages/vue/src/components/dropzone.tsx b/packages/vue/src/components/dropzone.tsx index c2a35d4615..b3be91c732 100644 --- a/packages/vue/src/components/dropzone.tsx +++ b/packages/vue/src/components/dropzone.tsx @@ -70,8 +70,15 @@ export type UploadDropzoneProps< * Callback called when files are dropped or pasted. * * @param acceptedFiles - The files that were accepted. + * @deprecated Use `onChange` instead */ onDrop?: (acceptedFiles: File[]) => void; + /** + * Callback called when files are dropped or pasted. + * + * @param acceptedFiles - The files that were accepted. + */ + onChange?: (files: File[]) => void; }; export const generateUploadDropzone = ( @@ -132,6 +139,7 @@ export const generateUploadDropzone = ( const onDrop = (acceptedFiles: File[]) => { $props.onDrop?.(acceptedFiles); + $props.onChange?.(acceptedFiles); files.value = acceptedFiles; @@ -180,6 +188,8 @@ export const generateUploadDropzone = ( files.value = [...files.value, ...pastedFiles]; + if ($props.onChange) $props.onChange(files.value); + if (mode === "auto") { const input = "input" in $props ? $props.input : undefined; void startUpload(files.value, input); From ea44ee76b0b9c328d6ee9b5551360c970dc9f7c6 Mon Sep 17 00:00:00 2001 From: "Mark R. Florkowski" Date: Sat, 13 Jul 2024 01:01:00 -0700 Subject: [PATCH 014/110] Update dropzone.tsx Co-authored-by: Julius Marminge --- packages/react/src/components/dropzone.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/react/src/components/dropzone.tsx b/packages/react/src/components/dropzone.tsx index c3008a14ba..0f186424fb 100644 --- a/packages/react/src/components/dropzone.tsx +++ b/packages/react/src/components/dropzone.tsx @@ -75,9 +75,9 @@ export type UploadDropzoneProps< */ onDrop?: (acceptedFiles: File[]) => void; /** - * Callback called when files are dropped or pasted. + * Callback called when files are dropped, selected or pasted. * - * @param acceptedFiles - The files that were accepted. + * @param files - The files that were accepted. */ onChange?: (files: File[]) => void; disabled?: boolean; From 7eef82753968a2ee32ccb4e25d4b4bbb5d0173b7 Mon Sep 17 00:00:00 2001 From: "Mark R. Florkowski" Date: Sat, 13 Jul 2024 01:01:49 -0700 Subject: [PATCH 015/110] Update button.tsx Co-authored-by: Julius Marminge --- packages/solid/src/components/button.tsx | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/packages/solid/src/components/button.tsx b/packages/solid/src/components/button.tsx index 72d15c051d..c5404eacbe 100644 --- a/packages/solid/src/components/button.tsx +++ b/packages/solid/src/components/button.tsx @@ -59,6 +59,11 @@ export type UploadButtonProps< * @see https://docs.uploadthing.com/theming#content-customisation */ content?: ButtonContent; + /** + * Callback called when files are selected or pasted. + * + * @param files - The files that were accepted. + */ onChange?: (files: File[]) => void; }; From 683b0f04e08cbdf7eab78558194d1d96ec4e7414 Mon Sep 17 00:00:00 2001 From: "Mark R. Florkowski" Date: Sat, 13 Jul 2024 01:02:20 -0700 Subject: [PATCH 016/110] Update giant-candles-wash.md Co-authored-by: Julius Marminge --- .changeset/giant-candles-wash.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/giant-candles-wash.md b/.changeset/giant-candles-wash.md index 4bba5cf6a0..1540f959ba 100644 --- a/.changeset/giant-candles-wash.md +++ b/.changeset/giant-candles-wash.md @@ -5,4 +5,4 @@ "@uploadthing/vue": minor --- -feat: Add `onSelect` to +feat: Add `onChange` to `` and ``. Deprecate dropzone's `onDrop` From ba82b4b820873269c7ca1ef650694c1f02c320cc Mon Sep 17 00:00:00 2001 From: "Mark R. Florkowski" Date: Sat, 13 Jul 2024 01:02:40 -0700 Subject: [PATCH 017/110] Update react.mdx Co-authored-by: Julius Marminge --- docs/src/pages/api-reference/react.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/src/pages/api-reference/react.mdx b/docs/src/pages/api-reference/react.mdx index 93a71ae81a..3da2dc84db 100644 --- a/docs/src/pages/api-reference/react.mdx +++ b/docs/src/pages/api-reference/react.mdx @@ -120,7 +120,7 @@ export const OurUploadButton = () => ( // Do something once upload begins console.log("Uploading: ", name); }} - onSelect={(acceptedFiles) => { + onChange={(acceptedFiles) => { // Do something with the accepted files console.log("Accepted files: ", acceptedFiles); }} From 2ed096c7eb904b084288631c574bcc7ff6a086c7 Mon Sep 17 00:00:00 2001 From: "Mark R. Florkowski" Date: Sat, 13 Jul 2024 14:18:22 -0700 Subject: [PATCH 018/110] update docs --- docs/src/pages/api-reference/react.mdx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/src/pages/api-reference/react.mdx b/docs/src/pages/api-reference/react.mdx index 3da2dc84db..5217b14e2b 100644 --- a/docs/src/pages/api-reference/react.mdx +++ b/docs/src/pages/api-reference/react.mdx @@ -146,7 +146,7 @@ export const OurUploadButton = () => ( | onUploadBegin | function | No | Added in `v5.4` | callback function for upload begin | | config | Config | No | Added in `v5.7` | object to pass additional configuration for button | | disabled | boolean | No | Added in `v6.7` | Disables the button. | -| onSelect | `(files: File[]) => void` | No | Added in `v6.8` | Callback function that runs when files are selected. Note: `files` is an array containing _all_ selected files, not just the latest ones. | +| onChange | `(files: File[]) => void` | No | Added in `v6.8` | Callback function that runs when files are selected. Note: `files` is an array containing _all_ selected files, not just the latest ones. | Config object @@ -215,7 +215,7 @@ export const OurUploadDropzone = () => ( // Do something once upload begins console.log("Uploading: ", name); }} - onDrop={(acceptedFiles) => { + onChange={(acceptedFiles) => { // Do something with the accepted files console.log("Accepted files: ", acceptedFiles); }} @@ -240,8 +240,8 @@ export const OurUploadDropzone = () => ( | onBeforeUploadBegin | `(files: File[]) => File[]` | No | Added in `v6.0` | callback function called before uploading starts. The files returned are the files that will be uploaded | | onUploadBegin | function | No | Added in `v5.4` | callback function for upload begin | | config | object | No | Added in `v5.4` | object to pass additional configuration for dropzone | -| onDrop | (acceptedFiles: File[]) => void | No | Added in `v6.5` | Callback called when files are dropped or pasted. | | disabled | boolean | No | Added in `v6.7` | Disables the dropzone. | +| onChange | (acceptedFiles: File[]) => void | No | Added in `v6.8` | Callback called when files are dropped or pasted. | Config object From 843b35fe784ccf9187f8094da7a0763ac7b7f781 Mon Sep 17 00:00:00 2001 From: "Mark R. Florkowski" Date: Sat, 13 Jul 2024 14:19:29 -0700 Subject: [PATCH 019/110] svelte --- packages/svelte/src/lib/component/UploadButton.svelte | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/svelte/src/lib/component/UploadButton.svelte b/packages/svelte/src/lib/component/UploadButton.svelte index 23369108f0..d552901ff9 100644 --- a/packages/svelte/src/lib/component/UploadButton.svelte +++ b/packages/svelte/src/lib/component/UploadButton.svelte @@ -62,7 +62,7 @@ // Allow to disable the button export let __internal_button_disabled: boolean | undefined = undefined; - export let onSelect: ((files: File[]) => void) | undefined = undefined; + export let onChange: ((files: File[]) => void) | undefined = undefined; let uploadProgress = 0; let fileInputRef: HTMLInputElement; @@ -133,6 +133,8 @@ files = [...files, ...pastedFiles]; + if (onChange) onChange(files); + if (mode === "auto") { const input = "input" in uploader ? uploader.input : undefined; void startUpload(files, input); @@ -207,7 +209,7 @@ Example: if (!e.currentTarget?.files) return; const selectedFiles = Array.from(e.currentTarget.files); - if (onSelect) onSelect(selectedFiles); + if (onChange) onChange(selectedFiles); if (mode === "manual") { files = selectedFiles; From 2dba137b0aeb493215a8746bd0283e5cecc54bf9 Mon Sep 17 00:00:00 2001 From: "Mark R. Florkowski" Date: Sat, 13 Jul 2024 14:31:38 -0700 Subject: [PATCH 020/110] svelte dropzone --- packages/svelte/src/lib/component/UploadDropzone.svelte | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/packages/svelte/src/lib/component/UploadDropzone.svelte b/packages/svelte/src/lib/component/UploadDropzone.svelte index cab4d8ce53..c51d039ec0 100644 --- a/packages/svelte/src/lib/component/UploadDropzone.svelte +++ b/packages/svelte/src/lib/component/UploadDropzone.svelte @@ -59,10 +59,14 @@ * Callback called when files are dropped or pasted. * * @param acceptedFiles - The files that were accepted. + * @deprecated Use `onChange` instead */ export let onDrop: (acceptedFiles: File[]) => void = () => { /** no-op */ }; + + export let onChange: ((files: File[]) => void) | undefined = undefined; + // Allow to set internal state for testing export let __internal_state: "readying" | "ready" | "uploading" | undefined = undefined; @@ -114,7 +118,8 @@ $: className = ($$props.class as string) ?? ""; const onDropCallback = (acceptedFiles: File[]) => { - onDrop(acceptedFiles); + onDrop?.(acceptedFiles); + onChange?.(acceptedFiles); files = acceptedFiles; @@ -150,6 +155,8 @@ files = [...files, ...pastedFiles]; + if (onChange) onChange(files); + if (mode === "auto") { const input = "input" in uploader ? uploader.input : undefined; void startUpload(files, input); From 324b034e07f80c6153c0d57b8f9f6905d62e89eb Mon Sep 17 00:00:00 2001 From: "Mark R. Florkowski" Date: Sat, 13 Jul 2024 14:56:05 -0700 Subject: [PATCH 021/110] add paste to solid --- packages/solid/src/components/button.tsx | 29 +++++++++++++++- packages/solid/src/components/dropzone.tsx | 40 ++++++++++++++++------ packages/solid/src/components/shared.tsx | 12 +++++++ packages/solid/src/types.ts | 4 +++ 4 files changed, 74 insertions(+), 11 deletions(-) diff --git a/packages/solid/src/components/button.tsx b/packages/solid/src/components/button.tsx index c5404eacbe..eacf11580d 100644 --- a/packages/solid/src/components/button.tsx +++ b/packages/solid/src/components/button.tsx @@ -6,6 +6,7 @@ import { contentFieldToContent, generateMimeTypes, generatePermittedFileTypes, + getFilesFromClipboardEvent, resolveMaybeUrlArg, styleFieldToClassName, styleFieldToCssObject, @@ -19,7 +20,7 @@ import type { FileRouter } from "uploadthing/types"; import type { UploadthingComponentProps } from "../types"; import { INTERNAL_uploadthingHookGen } from "../useUploadThing"; -import { progressWidths, Spinner } from "./shared"; +import { progressWidths, Spinner, usePaste } from "./shared"; type ButtonStyleFieldCallbackArgs = { __runtime: "solid"; @@ -84,9 +85,13 @@ export function UploadButton< : UploadButtonProps, ) { const [uploadProgress, setUploadProgress] = createSignal(0); + const [files, setFiles] = createSignal([]); + let inputRef: HTMLInputElement; const $props = props as UploadButtonProps; + const { mode = "auto", appendOnPaste = false } = $props.config ?? {}; + const useUploadThing = INTERNAL_uploadthingHookGen({ url: resolveMaybeUrlArg($props.url), }); @@ -129,6 +134,23 @@ export function UploadButton< return "uploading"; }; + usePaste((e) => { + if (!appendOnPaste) return; + if (document.activeElement !== inputRef) return; + + const pastedFiles = getFilesFromClipboardEvent(e); + if (!pastedFiles) return; + + setFiles((prevFiles) => [...prevFiles, ...pastedFiles]); + + if ($props.onChange) $props.onChange(files()); + + if (mode === "auto") { + const input = "input" in $props ? $props.input : undefined; + void uploadedThing.startUpload(files(), input); + } + }); + const getUploadButtonText = (fileTypes: string[]) => { if (fileTypes.length === 0) return "Loading..."; return `Choose File${fileInfo().multiple ? `(s)` : ``}`; @@ -171,6 +193,11 @@ export function UploadButton< if ($props.onChange) $props.onChange(selectedFiles); + if (mode === "manual") { + setFiles(selectedFiles); + return; + } + const input = "input" in $props ? $props.input : undefined; void uploadedThing.startUpload(selectedFiles, input); }} diff --git a/packages/solid/src/components/dropzone.tsx b/packages/solid/src/components/dropzone.tsx index 93b9c531ef..196bb9786f 100644 --- a/packages/solid/src/components/dropzone.tsx +++ b/packages/solid/src/components/dropzone.tsx @@ -7,6 +7,7 @@ import { contentFieldToContent, generateClientDropzoneAccept, generatePermittedFileTypes, + getFilesFromClipboardEvent, resolveMaybeUrlArg, styleFieldToClassName, styleFieldToCssObject, @@ -20,7 +21,7 @@ import type { FileRouter } from "uploadthing/types"; import type { UploadthingComponentProps } from "../types"; import { INTERNAL_uploadthingHookGen } from "../useUploadThing"; -import { progressWidths, Spinner } from "./shared"; +import { progressWidths, Spinner, usePaste } from "./shared"; type DropzoneStyleFieldCallbackArgs = { __runtime: "solid"; @@ -93,7 +94,7 @@ export const UploadDropzone = < const [uploadProgress, setUploadProgress] = createSignal(0); const $props = props as UploadDropzoneProps; - const { mode = "manual" } = $props.config ?? {}; + const { mode = "manual", appendOnPaste = false } = $props.config ?? {}; const useUploadThing = INTERNAL_uploadthingHookGen({ url: resolveMaybeUrlArg($props.url), @@ -132,15 +133,17 @@ export const UploadDropzone = < const fileInfo = () => generatePermittedFileTypes(uploadThing.permittedFileInfo()?.config); - const { getRootProps, getInputProps, isDragActive } = createDropzone({ - onDrop, - multiple: fileInfo().multiple, - get accept() { - return fileInfo().fileTypes - ? generateClientDropzoneAccept(fileInfo()?.fileTypes ?? []) - : undefined; + const { getRootProps, getInputProps, isDragActive, rootRef } = createDropzone( + { + onDrop, + multiple: fileInfo().multiple, + get accept() { + return fileInfo().fileTypes + ? generateClientDropzoneAccept(fileInfo()?.fileTypes ?? []) + : undefined; + }, }, - }); + ); const ready = () => fileInfo().fileTypes.length > 0; @@ -159,6 +162,23 @@ export const UploadDropzone = < return "uploading"; }; + usePaste((e) => { + if (!appendOnPaste) return; + if (document.activeElement !== rootRef()) return; + + const pastedFiles = getFilesFromClipboardEvent(e); + if (!pastedFiles) return; + + setFiles((prevFiles) => [...prevFiles, ...pastedFiles]); + + if ($props.onChange) $props.onChange(files()); + + if (mode === "auto") { + const input = "input" in $props ? $props.input : undefined; + void uploadThing.startUpload(files(), input); + } + }); + return (
void) => { + onMount(() => { + document.addEventListener("paste", callback); + }); + + onCleanup(() => { + document.removeEventListener("paste", callback); + }); +}; + export function Spinner() { return ( , { From b52d2d0d3f6a6ba253ebd7e7bd9403ffb82b74a3 Mon Sep 17 00:00:00 2001 From: "Mark R. Florkowski" Date: Tue, 23 Jul 2024 13:17:28 -0700 Subject: [PATCH 022/110] optional chain instead of if check --- packages/react/src/components/button.tsx | 4 ++-- packages/react/src/components/dropzone.tsx | 4 ++-- packages/solid/src/components/button.tsx | 4 ++-- packages/solid/src/components/dropzone.tsx | 2 +- packages/vue/src/components/button.tsx | 4 ++-- packages/vue/src/components/dropzone.tsx | 2 +- 6 files changed, 10 insertions(+), 10 deletions(-) diff --git a/packages/react/src/components/button.tsx b/packages/react/src/components/button.tsx index 95d428defd..dd90aa2a54 100644 --- a/packages/react/src/components/button.tsx +++ b/packages/react/src/components/button.tsx @@ -166,7 +166,7 @@ export function UploadButton< if (!e.target.files) return; const selectedFiles = Array.from(e.target.files); - if ($props.onChange) $props.onChange(selectedFiles); + $props.onChange?.(selectedFiles); if (mode === "manual") { setFiles(selectedFiles); @@ -202,7 +202,7 @@ export function UploadButton< setFiles((prev) => { filesToUpload = [...prev, ...pastedFiles]; - if ($props.onChange) $props.onChange(filesToUpload); + $props.onChange?.(filesToUpload); return filesToUpload; }); diff --git a/packages/react/src/components/dropzone.tsx b/packages/react/src/components/dropzone.tsx index 0f186424fb..5c7231b425 100644 --- a/packages/react/src/components/dropzone.tsx +++ b/packages/react/src/components/dropzone.tsx @@ -231,12 +231,12 @@ export function UploadDropzone< setFiles((prev) => { filesToUpload = [...prev, ...pastedFiles]; - if ($props.onChange) $props.onChange(filesToUpload); + $props.onChange?.(filesToUpload); return filesToUpload; }); - if ($props.onChange) $props.onChange(filesToUpload); + $props.onChange?.(filesToUpload); if (mode === "auto") uploadFiles(filesToUpload); }; diff --git a/packages/solid/src/components/button.tsx b/packages/solid/src/components/button.tsx index eacf11580d..e9434b9cad 100644 --- a/packages/solid/src/components/button.tsx +++ b/packages/solid/src/components/button.tsx @@ -143,7 +143,7 @@ export function UploadButton< setFiles((prevFiles) => [...prevFiles, ...pastedFiles]); - if ($props.onChange) $props.onChange(files()); + $props.onChange?.(files()); if (mode === "auto") { const input = "input" in $props ? $props.input : undefined; @@ -191,7 +191,7 @@ export function UploadButton< if (!e.target.files) return; const selectedFiles = Array.from(e.target.files); - if ($props.onChange) $props.onChange(selectedFiles); + $props.onChange?.(selectedFiles); if (mode === "manual") { setFiles(selectedFiles); diff --git a/packages/solid/src/components/dropzone.tsx b/packages/solid/src/components/dropzone.tsx index 196bb9786f..3861070942 100644 --- a/packages/solid/src/components/dropzone.tsx +++ b/packages/solid/src/components/dropzone.tsx @@ -171,7 +171,7 @@ export const UploadDropzone = < setFiles((prevFiles) => [...prevFiles, ...pastedFiles]); - if ($props.onChange) $props.onChange(files()); + $props.onChange?.(files()); if (mode === "auto") { const input = "input" in $props ? $props.input : undefined; diff --git a/packages/vue/src/components/button.tsx b/packages/vue/src/components/button.tsx index 67c7fcb533..aee70dd8ab 100644 --- a/packages/vue/src/components/button.tsx +++ b/packages/vue/src/components/button.tsx @@ -136,7 +136,7 @@ export const generateUploadButton = ( const { files: selectedFiles } = e.target as HTMLInputElement; if (!selectedFiles) return; - if ($props.onChange) $props.onChange(Array.from(selectedFiles)); + $props.onChange?.(Array.from(selectedFiles)); if (mode === "manual") { files.value = Array.from(selectedFiles); @@ -163,7 +163,7 @@ export const generateUploadButton = ( files.value = [...files.value, ...pastedFiles]; - if ($props.onChange) $props.onChange(files.value); + $props.onChange?.(files.value); if (mode === "auto") { const input = "input" in $props ? $props.input : undefined; diff --git a/packages/vue/src/components/dropzone.tsx b/packages/vue/src/components/dropzone.tsx index b3be91c732..13f9e9f2d5 100644 --- a/packages/vue/src/components/dropzone.tsx +++ b/packages/vue/src/components/dropzone.tsx @@ -188,7 +188,7 @@ export const generateUploadDropzone = ( files.value = [...files.value, ...pastedFiles]; - if ($props.onChange) $props.onChange(files.value); + $props.onChange?.(files.value); if (mode === "auto") { const input = "input" in $props ? $props.input : undefined; From 1c9db32c22da17e00dd07f5ae78db70b612a088b Mon Sep 17 00:00:00 2001 From: "Mark R. Florkowski" Date: Tue, 23 Jul 2024 13:38:58 -0700 Subject: [PATCH 023/110] optional chain for svelte --- packages/svelte/src/lib/component/UploadDropzone.svelte | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/svelte/src/lib/component/UploadDropzone.svelte b/packages/svelte/src/lib/component/UploadDropzone.svelte index c51d039ec0..dc223a465f 100644 --- a/packages/svelte/src/lib/component/UploadDropzone.svelte +++ b/packages/svelte/src/lib/component/UploadDropzone.svelte @@ -155,7 +155,7 @@ files = [...files, ...pastedFiles]; - if (onChange) onChange(files); + onChange?.(files); if (mode === "auto") { const input = "input" in uploader ? uploader.input : undefined; From 1e893b952412e990fc082938697d03ebce3adef5 Mon Sep 17 00:00:00 2001 From: "Mark R. Florkowski" Date: Tue, 23 Jul 2024 13:49:38 -0700 Subject: [PATCH 024/110] missed a file --- packages/svelte/src/lib/component/UploadButton.svelte | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/svelte/src/lib/component/UploadButton.svelte b/packages/svelte/src/lib/component/UploadButton.svelte index d552901ff9..186780c30f 100644 --- a/packages/svelte/src/lib/component/UploadButton.svelte +++ b/packages/svelte/src/lib/component/UploadButton.svelte @@ -133,7 +133,7 @@ files = [...files, ...pastedFiles]; - if (onChange) onChange(files); + onChange?.(files); if (mode === "auto") { const input = "input" in uploader ? uploader.input : undefined; @@ -209,7 +209,7 @@ Example: if (!e.currentTarget?.files) return; const selectedFiles = Array.from(e.currentTarget.files); - if (onChange) onChange(selectedFiles); + onChange?.(selectedFiles); if (mode === "manual") { files = selectedFiles; From 85d3cfa468bfb3327e584a4552949613224381fa Mon Sep 17 00:00:00 2001 From: "Mark R. Florkowski" Date: Tue, 23 Jul 2024 14:38:09 -0700 Subject: [PATCH 025/110] fix pkg-pr-new templates? --- .github/workflows/pkg-pr-new.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/pkg-pr-new.yaml b/.github/workflows/pkg-pr-new.yaml index e0354dafa3..28a943347c 100644 --- a/.github/workflows/pkg-pr-new.yaml +++ b/.github/workflows/pkg-pr-new.yaml @@ -25,4 +25,4 @@ jobs: - name: Release if: steps.changed-packages.outputs.changed_packages != '' - run: pnpx pkg-pr-new --compact publish ${{ steps.changed-packages.outputs.changed_packages }} --template ./examples/minimal* \ No newline at end of file + run: pnpx pkg-pr-new --compact publish ${{ steps.changed-packages.outputs.changed_packages }} --template './examples/minimal-*' \ No newline at end of file From f71ee9587e5bc08bb0be71d6e9e8aa6ee01c1876 Mon Sep 17 00:00:00 2001 From: "Mark R. Florkowski" Date: Thu, 25 Jul 2024 13:23:51 -0700 Subject: [PATCH 026/110] fix paste on solid? --- packages/solid/src/components/button.tsx | 28 ++++++++++++++-------- packages/solid/src/components/dropzone.tsx | 14 ++++++++--- packages/solid/src/components/shared.tsx | 12 ---------- 3 files changed, 29 insertions(+), 25 deletions(-) diff --git a/packages/solid/src/components/button.tsx b/packages/solid/src/components/button.tsx index e9434b9cad..c52c11cc9a 100644 --- a/packages/solid/src/components/button.tsx +++ b/packages/solid/src/components/button.tsx @@ -1,4 +1,4 @@ -import { createSignal } from "solid-js"; +import { createSignal, onCleanup, onMount } from "solid-js"; import { twMerge } from "tailwind-merge"; import { @@ -20,7 +20,7 @@ import type { FileRouter } from "uploadthing/types"; import type { UploadthingComponentProps } from "../types"; import { INTERNAL_uploadthingHookGen } from "../useUploadThing"; -import { progressWidths, Spinner, usePaste } from "./shared"; +import { progressWidths, Spinner } from "./shared"; type ButtonStyleFieldCallbackArgs = { __runtime: "solid"; @@ -96,7 +96,7 @@ export function UploadButton< url: resolveMaybeUrlArg($props.url), }); - const uploadedThing = useUploadThing($props.endpoint, { + const uploadThing = useUploadThing($props.endpoint, { headers: $props.headers, skipPolling: $props.skipPolling, onClientUploadComplete: (res) => { @@ -116,25 +116,25 @@ export function UploadButton< }); const fileInfo = () => - generatePermittedFileTypes(uploadedThing.permittedFileInfo()?.config); + generatePermittedFileTypes(uploadThing.permittedFileInfo()?.config); const ready = () => fileInfo().fileTypes.length > 0; const styleFieldArg = { ready: ready, - isUploading: uploadedThing.isUploading, + isUploading: uploadThing.isUploading, uploadProgress: uploadProgress, fileTypes: () => fileInfo().fileTypes, } as ButtonStyleFieldCallbackArgs; const state = () => { if (!ready()) return "readying"; - if (ready() && !uploadedThing.isUploading()) return "ready"; + if (ready() && !uploadThing.isUploading()) return "ready"; return "uploading"; }; - usePaste((e) => { + const pasteHandler = (e: ClipboardEvent) => { if (!appendOnPaste) return; if (document.activeElement !== inputRef) return; @@ -147,8 +147,16 @@ export function UploadButton< if (mode === "auto") { const input = "input" in $props ? $props.input : undefined; - void uploadedThing.startUpload(files(), input); + void uploadThing.startUpload(files(), input); } + }; + + onMount(() => { + document.addEventListener("paste", pasteHandler); + }); + + onCleanup(() => { + document.removeEventListener("paste", pasteHandler); }); const getUploadButtonText = (fileTypes: string[]) => { @@ -199,7 +207,7 @@ export function UploadButton< } const input = "input" in $props ? $props.input : undefined; - void uploadedThing.startUpload(selectedFiles, input); + void uploadThing.startUpload(selectedFiles, input); }} /> {contentFieldToContent($props.content?.button, styleFieldArg) ?? @@ -226,7 +234,7 @@ export function UploadButton< > {contentFieldToContent($props.content?.allowedContent, styleFieldArg) ?? allowedContentTextLabelGenerator( - uploadedThing.permittedFileInfo()?.config, + uploadThing.permittedFileInfo()?.config, )}
diff --git a/packages/solid/src/components/dropzone.tsx b/packages/solid/src/components/dropzone.tsx index 3861070942..40fcc249eb 100644 --- a/packages/solid/src/components/dropzone.tsx +++ b/packages/solid/src/components/dropzone.tsx @@ -1,4 +1,4 @@ -import { createSignal } from "solid-js"; +import { createSignal, onCleanup, onMount } from "solid-js"; import { twMerge } from "tailwind-merge"; import { createDropzone } from "@uploadthing/dropzone/solid"; @@ -21,7 +21,7 @@ import type { FileRouter } from "uploadthing/types"; import type { UploadthingComponentProps } from "../types"; import { INTERNAL_uploadthingHookGen } from "../useUploadThing"; -import { progressWidths, Spinner, usePaste } from "./shared"; +import { progressWidths, Spinner } from "./shared"; type DropzoneStyleFieldCallbackArgs = { __runtime: "solid"; @@ -162,7 +162,7 @@ export const UploadDropzone = < return "uploading"; }; - usePaste((e) => { + const pasteHandler = (e: ClipboardEvent) => { if (!appendOnPaste) return; if (document.activeElement !== rootRef()) return; @@ -177,6 +177,14 @@ export const UploadDropzone = < const input = "input" in $props ? $props.input : undefined; void uploadThing.startUpload(files(), input); } + }; + + onMount(() => { + document.addEventListener("paste", pasteHandler); + }); + + onCleanup(() => { + document.removeEventListener("paste", pasteHandler); }); return ( diff --git a/packages/solid/src/components/shared.tsx b/packages/solid/src/components/shared.tsx index 0f2725030b..08bf0605e5 100644 --- a/packages/solid/src/components/shared.tsx +++ b/packages/solid/src/components/shared.tsx @@ -1,15 +1,3 @@ -import { onCleanup, onMount } from "solid-js"; - -export const usePaste = (callback: (e: ClipboardEvent) => void) => { - onMount(() => { - document.addEventListener("paste", callback); - }); - - onCleanup(() => { - document.removeEventListener("paste", callback); - }); -}; - export function Spinner() { return ( Date: Thu, 25 Jul 2024 16:03:17 -0700 Subject: [PATCH 027/110] useEventListener --- packages/solid/package.json | 3 +- packages/solid/src/components/button.tsx | 12 +++---- packages/solid/src/components/dropzone.tsx | 12 +++---- pnpm-lock.yaml | 41 ++++++++++++++++++---- 4 files changed, 45 insertions(+), 23 deletions(-) diff --git a/packages/solid/package.json b/packages/solid/package.json index 2794ac676a..5d12ed96d2 100644 --- a/packages/solid/package.json +++ b/packages/solid/package.json @@ -112,7 +112,8 @@ "dependencies": { "@uploadthing/dropzone": "workspace:*", "@uploadthing/shared": "workspace:*", - "tailwind-merge": "^2.2.1" + "tailwind-merge": "^2.2.1", + "solidjs-use": "2.3.0" }, "devDependencies": { "postcss": "8.4.38", diff --git a/packages/solid/src/components/button.tsx b/packages/solid/src/components/button.tsx index c52c11cc9a..3f502577fa 100644 --- a/packages/solid/src/components/button.tsx +++ b/packages/solid/src/components/button.tsx @@ -1,4 +1,5 @@ -import { createSignal, onCleanup, onMount } from "solid-js"; +import { createSignal, onMount } from "solid-js"; +import { useEventListener } from "solidjs-use"; import { twMerge } from "tailwind-merge"; import { @@ -151,13 +152,8 @@ export function UploadButton< } }; - onMount(() => { - document.addEventListener("paste", pasteHandler); - }); - - onCleanup(() => { - document.removeEventListener("paste", pasteHandler); - }); + // onMount will only be called client side, so it guarantees DOM APIs exist. + onMount(() => useEventListener(document, "paste", pasteHandler)); const getUploadButtonText = (fileTypes: string[]) => { if (fileTypes.length === 0) return "Loading..."; diff --git a/packages/solid/src/components/dropzone.tsx b/packages/solid/src/components/dropzone.tsx index 40fcc249eb..14b8b6d46c 100644 --- a/packages/solid/src/components/dropzone.tsx +++ b/packages/solid/src/components/dropzone.tsx @@ -1,4 +1,5 @@ -import { createSignal, onCleanup, onMount } from "solid-js"; +import { createSignal, onMount } from "solid-js"; +import { useEventListener } from "solidjs-use"; import { twMerge } from "tailwind-merge"; import { createDropzone } from "@uploadthing/dropzone/solid"; @@ -179,13 +180,8 @@ export const UploadDropzone = < } }; - onMount(() => { - document.addEventListener("paste", pasteHandler); - }); - - onCleanup(() => { - document.removeEventListener("paste", pasteHandler); - }); + // onMount will only be called client side, so it guarantees DOM APIs exist. + onMount(() => useEventListener(document, "paste", pasteHandler)); return (
Date: Thu, 25 Jul 2024 16:34:41 -0700 Subject: [PATCH 028/110] fix focusing --- packages/solid/src/components/button.tsx | 14 ++++++------- packages/solid/src/components/dropzone.tsx | 23 +++++++++++----------- 2 files changed, 18 insertions(+), 19 deletions(-) diff --git a/packages/solid/src/components/button.tsx b/packages/solid/src/components/button.tsx index 3f502577fa..aed9dc8aa4 100644 --- a/packages/solid/src/components/button.tsx +++ b/packages/solid/src/components/button.tsx @@ -88,7 +88,7 @@ export function UploadButton< const [uploadProgress, setUploadProgress] = createSignal(0); const [files, setFiles] = createSignal([]); - let inputRef: HTMLInputElement; + let rootRef: HTMLElement; const $props = props as UploadButtonProps; const { mode = "auto", appendOnPaste = false } = $props.config ?? {}; @@ -101,9 +101,7 @@ export function UploadButton< headers: $props.headers, skipPolling: $props.skipPolling, onClientUploadComplete: (res) => { - if (inputRef) { - inputRef.value = ""; - } + setFiles([]); $props.onClientUploadComplete?.(res); setUploadProgress(0); }, @@ -137,7 +135,7 @@ export function UploadButton< const pasteHandler = (e: ClipboardEvent) => { if (!appendOnPaste) return; - if (document.activeElement !== inputRef) return; + if (document.activeElement !== rootRef) return; const pastedFiles = getFilesFromClipboardEvent(e); if (!pastedFiles) return; @@ -169,10 +167,11 @@ export function UploadButton< )} style={styleFieldToCssObject($props.appearance?.container, styleFieldArg)} data-state={state()} + ref={(el) => (rootRef = el)} >
); } From afe79cba6659c68ef66dd9a04294be5f8c3b3d31 Mon Sep 17 00:00:00 2001 From: "Mark R. Florkowski" Date: Fri, 26 Jul 2024 11:29:48 -0700 Subject: [PATCH 031/110] button text --- packages/solid/src/components/button.tsx | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/solid/src/components/button.tsx b/packages/solid/src/components/button.tsx index d4f96f3926..ff5e4bf1b5 100644 --- a/packages/solid/src/components/button.tsx +++ b/packages/solid/src/components/button.tsx @@ -156,6 +156,9 @@ export function UploadButton< const getUploadButtonText = (fileTypes: string[]) => { if (fileTypes.length === 0) return "Loading..."; + if (mode === "manual" && files.length > 0) { + return `Upload ${files.length} file${files.length === 1 ? "" : "s"}`; + } return `Choose File${fileInfo().multiple ? `(s)` : ``}`; }; From a4ff731b747a6cfe2840b8c47c5043df14fedda1 Mon Sep 17 00:00:00 2001 From: "Mark R. Florkowski" Date: Fri, 26 Jul 2024 11:44:16 -0700 Subject: [PATCH 032/110] closer to react impl --- packages/solid/src/components/button.tsx | 48 ++++++++++++++++++------ 1 file changed, 37 insertions(+), 11 deletions(-) diff --git a/packages/solid/src/components/button.tsx b/packages/solid/src/components/button.tsx index ff5e4bf1b5..bdd58f618e 100644 --- a/packages/solid/src/components/button.tsx +++ b/packages/solid/src/components/button.tsx @@ -154,12 +154,43 @@ export function UploadButton< // onMount will only be called client side, so it guarantees DOM APIs exist. onMount(() => useEventListener(document, "paste", pasteHandler)); - const getUploadButtonText = (fileTypes: string[]) => { - if (fileTypes.length === 0) return "Loading..."; - if (mode === "manual" && files.length > 0) { - return `Upload ${files.length} file${files.length === 1 ? "" : "s"}`; + const getButtonContent = () => { + const customContent = contentFieldToContent( + $props.content?.button, + styleFieldArg, + ); + + if (customContent) return customContent; + + if (state() === "readying") return "Loading..."; + + if (state() !== "uploading") { + if (mode === "manual" && files.length > 0) { + return `Upload ${files.length} file${files.length === 1 ? "" : "s"}`; + } + return `Choose File${fileInfo().multiple ? `(s)` : ``}`; } - return `Choose File${fileInfo().multiple ? `(s)` : ``}`; + + if (uploadProgress() === 100) return ; + + return ( + + {uploadProgress()}% + + + + + + ); }; return ( @@ -208,12 +239,7 @@ export function UploadButton< void uploadThing.startUpload(selectedFiles, input); }} /> - {contentFieldToContent($props.content?.button, styleFieldArg) ?? - (state() === "uploading" ? ( - - ) : ( - getUploadButtonText(fileInfo().fileTypes) - ))} + {getButtonContent()} {mode === "manual" && files().length > 0 ? (