Add useDropzone & useFileInput (#5735)

Co-authored-by: Mikael Finstad <finstaden@gmail.com>
This commit is contained in:
Merlijn Vos 2025-06-09 10:54:28 +02:00 committed by GitHub
parent 52870a1911
commit 5c0c831937
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
51 changed files with 2892 additions and 269 deletions

View file

@ -10,23 +10,28 @@ const scriptDir = path.dirname(fileURLToPath(import.meta.url))
const rootDir = path.resolve(scriptDir, '..')
// Define paths
// source:
const COMPONENTS_DIR = path.join(rootDir, 'packages/@uppy/components/src')
const REACT_DIR = path.join(rootDir, 'packages/@uppy/react/src/headless')
const VUE_DIR = path.join(rootDir, 'packages/@uppy/vue/src/headless')
// destinations:
const REACT_DIR = path.join(rootDir, 'packages/@uppy/react/src/headless/generated')
const VUE_DIR = path.join(rootDir, 'packages/@uppy/vue/src/headless/generated')
const SVELTE_DIR = path.join(
rootDir,
'packages/@uppy/svelte/src/lib/components/headless',
'packages/@uppy/svelte/src/lib/components/headless/generated',
)
// Templates
const REACT_TEMPLATE = `import { useEffect, useRef, useContext, createElement as h } from 'react'
const REACT_TEMPLATE = `\
// This file was generated by build-components.mjs
// ANY EDITS WILL BE OVERWRITTEN!
import { useEffect, useRef, useContext, createElement as h } from 'react'
import {
%%ComponentName%% as %%PreactComponentName%%,
type %%PropsTypeName%%,
} from '@uppy/components'
import { h as preactH } from 'preact'
import { render as preactRender } from 'preact/compat'
import { UppyContext } from './UppyContextProvider.js'
import { UppyContext } from '../UppyContextProvider.js'
export default function %%ComponentName%%(props: Omit<%%PropsTypeName%%, 'ctx'>) {
const ref = useRef(null)
@ -48,7 +53,10 @@ export default function %%ComponentName%%(props: Omit<%%PropsTypeName%%, 'ctx'>)
}
`
const VUE_TEMPLATE = `import { defineComponent, ref, watch, onMounted, h } from 'vue'
const VUE_TEMPLATE = `\
// This file was generated by build-components.mjs
// ANY EDITS WILL BE OVERWRITTEN!
import { defineComponent, ref, watch, onMounted, h } from 'vue'
import {
%%ComponentName%% as %%PreactComponentName%%,
type %%PropsTypeName%%,
@ -56,7 +64,7 @@ import {
import { h as preactH } from 'preact'
import { render as preactRender } from 'preact/compat'
import { shallowEqualObjects } from 'shallow-equal'
import { useUppyContext } from './useUppyContext.js'
import { useUppyContext } from '../useUppyContext.js'
export default defineComponent<Omit<%%PropsTypeName%%, 'ctx'>>({
name: '%%ComponentName%%',
@ -98,16 +106,19 @@ export default defineComponent<Omit<%%PropsTypeName%%, 'ctx'>>({
})
`
const SVELTE_TEMPLATE = `<script lang="ts">
const SVELTE_TEMPLATE = `\
<script lang="ts">
// This file was generated by build-components.mjs
// ANY EDITS WILL BE OVERWRITTEN!
import { getContext, mount } from 'svelte'
import {
%%ComponentName%% as %%PreactComponentName%%,
type %%PropsTypeName%%,
type UppyContext,
} from '@uppy/components'
import { h as preactH } from 'preact'
import { render as preactRender } from 'preact/compat'
import { UppyContextKey } from './UppyContextProvider.svelte'
import { h as preactH } from 'preact'
import { render as preactRender } from 'preact/compat'
import { UppyContextKey } from '../UppyContextProvider.svelte'
const props: Omit<%%PropsTypeName%%, 'ctx'> = $props()
const ctx = getContext<UppyContext>(UppyContextKey)
@ -211,7 +222,7 @@ try {
const reactIndexContent = reactComponents
.map((name) => `export { default as ${name} } from './${name}.js'`)
.join('\n')
await fs.writeFile(path.join(REACT_DIR, 'index.ts'), reactIndexContent)
await fs.writeFile(path.join(REACT_DIR, 'index.ts'), `${reactIndexContent}\n`)
console.log(`\nExporting React components from ${REACT_DIR}`)
}
@ -219,7 +230,7 @@ try {
const vueIndexContent = vueComponents
.map((name) => `export { default as ${name} } from './${name}.js'`)
.join('\n')
await fs.writeFile(path.join(VUE_DIR, 'index.ts'), vueIndexContent)
await fs.writeFile(path.join(VUE_DIR, 'index.ts'), `${vueIndexContent}\n`)
console.log(`Exporting Vue components from ${VUE_DIR}`)
}
@ -227,38 +238,10 @@ try {
const svelteIndexContent = svelteComponents
.map((name) => `export { default as ${name} } from './${name}.svelte'`)
.join('\n')
await fs.writeFile(path.join(SVELTE_DIR, 'index.ts'), svelteIndexContent)
await fs.writeFile(path.join(SVELTE_DIR, 'index.ts'), `${svelteIndexContent}\n`)
console.log(`Exporting Svelte components from ${SVELTE_DIR}`)
}
// Copy CSS file
const CSS_SOURCE_PATH = path.join(
rootDir,
'packages/@uppy/components/dist/styles.css',
)
const FRAMEWORK_DIRS_FOR_CSS = [
path.join(rootDir, 'packages/@uppy/react/dist'),
path.join(rootDir, 'packages/@uppy/vue/dist'),
path.join(rootDir, 'packages/@uppy/svelte/dist'),
]
try {
await fs.access(CSS_SOURCE_PATH) // Check if source CSS exists
await Promise.all(
FRAMEWORK_DIRS_FOR_CSS.map(async (destDir) => {
if (!existsSync(destDir)) {
await fs.mkdir(destDir, { recursive: true })
}
const destPath = path.join(destDir, 'styles.css')
await fs.copyFile(CSS_SOURCE_PATH, destPath)
}),
)
} catch (cssError) {
console.error(
`${CSS_SOURCE_PATH} does not exist yet. Run \`yarn build\` first.`,
)
}
console.log('\nAll wrappers and index files generated successfully!')
} catch (error) {
console.error('Error generating wrappers:', error)

32
bin/copy-tailwind-css.mjs Normal file
View file

@ -0,0 +1,32 @@
#!/usr/bin/env node
import fs from 'node:fs/promises'
import path from 'node:path'
import { fileURLToPath } from 'node:url'
// todo merge this with build-css.js?
// Get the directory of this script
const scriptDir = path.dirname(fileURLToPath(import.meta.url))
const rootDir = path.resolve(scriptDir, '..')
// Copy CSS file
const CSS_SOURCE_PATH = path.join(
rootDir,
'packages/@uppy/components/dist/styles.css',
)
const FRAMEWORK_DIRS_FOR_CSS = [
'packages/@uppy/react/dist',
'packages/@uppy/vue/dist',
'packages/@uppy/svelte/dist',
]
await fs.access(CSS_SOURCE_PATH) // Check if source CSS exists
await Promise.all(
FRAMEWORK_DIRS_FOR_CSS.map(async (destDir) => {
const destDirPath = path.join(rootDir, destDir)
await fs.mkdir(destDirPath, { recursive: true })
const destPath = path.join(destDirPath, 'styles.css')
await fs.copyFile(CSS_SOURCE_PATH, destPath)
}),
)

View file

@ -9,10 +9,10 @@
},
"dependencies": {
"@tailwindcss/vite": "^4.0.9",
"@uppy/core": "^4.4.2",
"@uppy/react": "workspace:^",
"@uppy/tus": "^4.2.2",
"@uppy/webcam": "^4.1.1",
"@uppy/core": "workspace:*",
"@uppy/react": "workspace:*",
"@uppy/tus": "workspace:*",
"@uppy/webcam": "workspace:*",
"react": "^19.0.0",
"react-dom": "^19.0.0",
"tailwindcss": "^4.0.9"

View file

@ -1,11 +1,16 @@
/* eslint-disable react/button-has-type */
/* eslint-disable react/jsx-props-no-spreading */
/* eslint-disable react/react-in-jsx-scope */
import { useState } from 'react'
import {
Dropzone,
FilesGrid,
FilesList,
ProviderIcon,
UploadButton,
UppyContextProvider,
useDropzone,
useFileInput,
} from '@uppy/react'
import Uppy from '@uppy/core'
import Tus from '@uppy/tus'
@ -13,6 +18,37 @@ import Tus from '@uppy/tus'
import './app.css'
import '@uppy/react/dist/styles.css'
function CustomDropzone() {
const { getRootProps, getInputProps } = useDropzone({
noClick: true,
})
const { getButtonProps, getInputProps: getFileInputProps } = useFileInput()
return (
<div>
<input {...getInputProps()} className="hidden" />
<div
{...getRootProps()}
role="button"
className="border-2 border-dashed border-gray-300 rounded-lg p-6 bg-gray-50 transition-colors duration-200"
>
<div className="flex flex-col items-center justify-center h-full space-y-3">
<input {...getFileInputProps()} className="hidden" />
<button
{...getButtonProps()}
className="hover:bg-gray-100 transition-colors p-2 rounded-md flex flex-col items-center gap-2 text-sm"
>
<div className="bg-white shadow-md rounded-md p-1">
<ProviderIcon provider="device" fill="#02B383" />
</div>
Device
</button>
</div>
</div>
</div>
)
}
function App() {
const [uppy] = useState(() =>
new Uppy().use(Tus, {
@ -23,21 +59,25 @@ function App() {
return (
<UppyContextProvider uppy={uppy}>
<main className="p-5 max-w-xl mx-auto">
<h1 className="text-4xl font-bold">Welcome to React.</h1>
<h1 className="text-4xl font-bold my-4">Welcome to React.</h1>
<UploadButton />
<article>
<h2 className="text-2xl my-4">With list</h2>
<Dropzone />
<FilesList />
<UploadButton />
</article>
<article>
<h2 className="text-2xl my-4">With grid</h2>
<Dropzone />
<FilesGrid columns={2} />
<UploadButton />
</article>
<article>
<h2 className="text-2xl my-4">With custom dropzone</h2>
<CustomDropzone />
</article>
</main>
</UppyContextProvider>

View file

@ -0,0 +1,28 @@
<script lang="ts">
import { useDropzone, useFileInput, ProviderIcon } from '@uppy/svelte'
const { getRootProps, getInputProps } = useDropzone({ noClick: true })
const { getButtonProps, getInputProps: getFileInputProps } = useFileInput()
</script>
<div>
<input {...getInputProps()} class="uppy:hidden" />
<div
{...getRootProps()}
role="button"
class="border-2 border-dashed border-gray-300 rounded-lg p-6 bg-gray-50 transition-colors duration-200"
>
<div class="flex flex-col items-center justify-center h-full space-y-3">
<input {...getFileInputProps()} class="hidden" />
<button
{...getButtonProps()}
class="hover:bg-gray-100 transition-colors p-2 rounded-md flex flex-col items-center gap-2 text-sm"
>
<div class="bg-white shadow-md rounded-md p-1">
<ProviderIcon provider="device" fill="#02B383" />
</div>
Device
</button>
</div>
</div>
</div>

View file

@ -10,6 +10,8 @@
} from '@uppy/svelte'
import '@uppy/svelte/dist/styles.css'
import CustomDropzone from '../components/CustomDropzone.svelte'
const uppy = new Uppy().use(Tus, {
endpoint: 'https://tusd.tusdemo.net/files/',
})
@ -17,20 +19,25 @@
<UppyContextProvider {uppy}>
<main class="p-5 max-w-xl mx-auto">
<h1 class="text-4xl font-bold">Welcome to SvelteKit.</h1>
<h1 class="text-4xl font-bold my-4">Welcome to SvelteKit.</h1>
<UploadButton />
<article>
<h2 class="text-2xl my-4">With list</h2>
<Dropzone />
<FilesList />
<UploadButton />
</article>
<article>
<h2 class="text-2xl my-4">With grid</h2>
<Dropzone />
<FilesGrid columns={2} />
<UploadButton />
</article>
<article>
<h2 class="text-2xl my-4">With custom dropzone</h2>
<CustomDropzone />
</article>
</main>
</UppyContextProvider>

View file

@ -1,20 +1,25 @@
<template>
<UppyContextProvider :uppy="uppy">
<main class="p-5 max-w-xl mx-auto">
<h1 class="text-4xl font-bold">Welcome to Vue.</h1>
<h1 class="text-4xl font-bold my-4">Welcome to Vue.</h1>
<UploadButton />
<article>
<h2 class="text-2xl my-4">With list</h2>
<Dropzone />
<FilesList />
<UploadButton />
</article>
<article>
<h2 class="text-2xl my-4">With grid</h2>
<Dropzone />
<FilesGrid :columns="2" />
<UploadButton />
</article>
<article>
<h2 class="text-2xl my-4">With custom dropzone</h2>
<CustomDropzone />
</article>
</main>
</UppyContextProvider>
@ -31,6 +36,7 @@ import {
FilesGrid,
UploadButton,
} from '@uppy/vue'
import CustomDropzone from './Dropzone.vue'
const uppy = computed(() =>
new Uppy().use(Tus, {

View file

@ -0,0 +1,28 @@
<template>
<input v-bind="getInputProps()" class="hidden" />
<div
v-bind="getRootProps()"
class="border-2 border-dashed border-gray-300 rounded-lg p-6 bg-gray-50 transition-colors duration-200"
>
<div class="flex flex-col items-center justify-center h-full space-y-3">
<input v-bind="getFileInputProps()" class="hidden" />
<button
v-bind="getButtonProps()"
class="hover:bg-gray-100 transition-colors p-2 rounded-md flex flex-col items-center gap-2 text-sm"
>
<div class="bg-white shadow-md rounded-md p-1">
<ProviderIcon provider="device" fill="#02B383" />
</div>
Device
</button>
</div>
</div>
</template>
<script setup lang="ts">
import { useDropzone, useFileInput, ProviderIcon } from '@uppy/vue'
const { getRootProps, getInputProps } = useDropzone({
noClick: true,
})
const { getInputProps: getFileInputProps, getButtonProps } = useFileInput()
</script>

View file

@ -115,13 +115,13 @@
"build:bundle": "yarn node ./bin/build-bundle.mjs",
"build:clean": "cp .gitignore .gitignore.bak && printf '!node_modules\n!**/node_modules/**/*\n' >> .gitignore; git clean -Xfd packages e2e .parcel-cache coverage; mv .gitignore.bak .gitignore",
"build:companion": "yarn workspace @uppy/companion build",
"build:css": "yarn node ./bin/build-css.js && yarn workspace @uppy/components build:tailwind",
"build:css": "yarn node ./bin/build-css.js && yarn workspace @uppy/components build:tailwind && yarn node ./bin/copy-tailwind-css.mjs",
"build:svelte": "yarn workspace @uppy/svelte build",
"build:angular": "yarn workspace angular build",
"build:js:typeless": "npm-run-all build:lib build:companion build:svelte",
"build:js": "npm-run-all build:js:typeless build:locale-pack build:angular build:bundle",
"build:ts": "yarn tsc -b tsconfig.build.json && yarn workspace @uppy/svelte build",
"build:lib": "yarn node ./bin/build-lib.mjs",
"build:lib": "yarn build:ts && yarn node ./bin/build-lib.mjs && yarn build:css",
"build:locale-pack": "yarn workspace @uppy-dev/locale-pack build && eslint packages/@uppy/locales/src/en_US.ts --fix && yarn workspace @uppy-dev/locale-pack test unused",
"build": "npm-run-all --serial build:ts --parallel build:js build:css --serial build:components size",
"contributors:save": "yarn node ./bin/update-contributors.mjs",
@ -157,11 +157,9 @@
"test": "npm-run-all lint test:locale-packs:unused test:unit test:companion",
"uploadcdn": "yarn node ./private/upload-to-cdn/index.js",
"version": "yarn node ./bin/after-version-bump.js",
"watch:css": "onchange 'packages/{@uppy/,}*/src/*.{scss,css}' --initial --verbose -- yarn run build:css",
"watch:js:bundle": "onchange 'packages/{@uppy/,}*/src/**/*.{js,ts,jsx,tsx}' --initial --verbose -- yarn run build:bundle",
"watch:js:lib": "onchange 'packages/{@uppy/,}*/src/**/*.{js,ts,jsx,tsx}' --initial --verbose -- yarn run build:lib",
"watch:js": "npm-run-all --parallel watch:js:bundle watch:js:lib",
"watch": "npm-run-all --parallel watch:css watch:js"
"watch:js:bundle": "onchange 'packages/{@uppy/,}*/src/**/*.{js,ts,jsx,tsx,svelte,scss,css}' --initial --verbose -- yarn run build:bundle",
"watch:js:lib": "onchange 'packages/{@uppy/,}*/src/**/*.{js,ts,jsx,tsx,svelte,scss,css}' --initial --verbose -- yarn run build:lib",
"watch": "npm-run-all --parallel watch:js:bundle watch:js:lib"
},
"alias": {
"preact/jsx-dev-runtime": "preact/jsx-runtime"

View file

@ -21,7 +21,7 @@
"url": "git+https://github.com/transloadit/uppy.git"
},
"scripts": {
"build:tailwind": "npx @tailwindcss/cli -i ./src/input.css -o ./dist/styles.css"
"build:tailwind": "tailwindcss -i ./src/input.css -o ./dist/styles.css"
},
"dependencies": {
"@tailwindcss/cli": "^4.0.6",

View file

@ -1,8 +1,10 @@
/* eslint-disable jsx-a11y/click-events-have-key-events */
/* eslint-disable react/jsx-props-no-spreading */
import { h } from 'preact'
import { useState, useRef } from 'preact/hooks'
import { useMemo } from 'preact/hooks'
import { clsx } from 'clsx'
import type { UppyContext } from './types.js'
import type { NonNullableUppyContext, UppyContext } from './types.js'
import { createDropzone } from './hooks/dropzone.js'
export type DropzoneProps = {
width?: string
@ -13,77 +15,23 @@ export type DropzoneProps = {
}
export default function Dropzone(props: DropzoneProps) {
const fileInputRef = useRef<HTMLInputElement>(null)
const [isDragging, setIsDragging] = useState(() => false)
const { width, height, note, noClick, ctx } = props
function handleDrop(event: DragEvent) {
event.preventDefault()
event.stopPropagation()
setIsDragging(false)
const files = Array.from(event.dataTransfer?.files || [])
if (!files.length) return
ctx.uppy?.addFiles(
files.map((file) => ({
source: 'drag-drop',
name: file.name,
type: file.type,
data: file,
})),
)
if (!ctx.uppy) {
throw new Error('Dropzone must be used within a UppyContextProvider')
}
function handleDragOver(event: DragEvent) {
event.preventDefault()
event.stopPropagation()
}
function handleDragEnter(event: DragEvent) {
event.preventDefault()
event.stopPropagation()
setIsDragging(true)
}
function handleDragLeave(event: DragEvent) {
event.preventDefault()
event.stopPropagation()
setIsDragging(false)
}
function handleClick() {
if (noClick) return
fileInputRef.current?.click()
}
function handleFileInputChange(event: Event) {
const input = event.target as HTMLInputElement
const files = Array.from(input.files || [])
if (!files.length) return
ctx.uppy?.addFiles(
files.map((file) => ({
source: 'drag-drop',
name: file.name,
type: file.type,
data: file,
})),
)
// Reset the input value so the same file can be selected again
input.value = ''
}
const { getRootProps, getInputProps } = useMemo(
() => createDropzone(ctx as NonNullableUppyContext, { noClick }),
[ctx, noClick],
)
return (
<div className="uppy-reset" data-uppy-element="dropzone">
<input
type="file"
className="uppy:hidden"
ref={fileInputRef}
multiple
onChange={handleFileInputChange}
/>
<input {...getInputProps()} className="uppy:hidden" />
<div
{...getRootProps()}
role="button"
tabIndex={0}
style={{
width: width || '100%',
height: height || '100%',
@ -92,18 +40,10 @@ export default function Dropzone(props: DropzoneProps) {
'uppy:border-2 uppy:border-dashed uppy:border-gray-300',
'uppy:rounded-lg uppy:p-6 uppy:bg-gray-50',
'uppy:transition-colors uppy:duration-200',
{
'uppy:bg-blue-50': isDragging,
},
{
'uppy:cursor-pointer uppy:hover:bg-blue-50': !noClick,
},
)}
onDrop={handleDrop}
onDragOver={handleDragOver}
onDragEnter={handleDragEnter}
onDragLeave={handleDragLeave}
onClick={handleClick}
>
<div className="uppy:flex uppy:flex-col uppy:items-center uppy:justify-center uppy:h-full uppy:space-y-3">
<p className="uppy:text-gray-600">

View file

@ -0,0 +1,114 @@
import type { NonNullableUppyContext } from '../types.js'
export type DropzoneOptions = {
noClick?: boolean
onDragOver?: (event: Event) => void
onDragEnter?: (event: Event) => void
onDragLeave?: (event: Event) => void
onDrop?: (files: File[]) => void
onFileInputChange?: (files: File[]) => void
}
export type DropzoneReturn<DragEventType, ChangeEventType> = {
getRootProps: () => {
onDragEnter: (event: DragEventType) => void
onDragOver: (event: DragEventType) => void
onDragLeave: (event: DragEventType) => void
onDrop: (event: DragEventType) => void
onClick: () => void
}
getInputProps: () => {
id: string
type: 'file'
multiple: boolean
onChange: (event: ChangeEventType) => void
}
}
const fileInputId = 'uppy-dropzone-file-input' as const
export function createDropzone<
DragEventType extends DragEvent,
ChangeEventType extends Event,
>(
ctx: NonNullableUppyContext,
options: DropzoneOptions = {},
): DropzoneReturn<DragEventType, ChangeEventType> {
const handleDrop = (event: DragEventType) => {
event.preventDefault()
event.stopPropagation()
const files = Array.from(event.dataTransfer?.files ?? [])
if (!files.length) return
options.onDrop?.(files)
ctx.uppy.addFiles(
files.map((file) => ({
name: file.name,
type: file.type,
data: file,
})),
)
}
const handleDragOver = (event: DragEventType) => {
event.preventDefault()
event.stopPropagation()
options.onDragOver?.(event)
}
const handleDragEnter = (event: DragEventType) => {
event.preventDefault()
event.stopPropagation()
options.onDragEnter?.(event)
}
const handleDragLeave = (event: DragEventType) => {
event.preventDefault()
event.stopPropagation()
options.onDragLeave?.(event)
}
const handleClick = () => {
if (options.noClick) return
const input = document.getElementById(fileInputId) as HTMLInputElement
input?.click()
}
const handleFileInputChange = (event: ChangeEventType) => {
const input = event.target as HTMLInputElement
const files = Array.from(input.files ?? [])
if (!files.length) return
options.onFileInputChange?.(files)
ctx.uppy.addFiles(
files.map((file) => ({
source: 'drag-drop',
name: file.name,
type: file.type,
data: file,
})),
)
// Reset the input value so the same file can be selected again
input.value = ''
}
return {
getRootProps: () => ({
onDragEnter: handleDragEnter,
onDragOver: handleDragOver,
onDragLeave: handleDragLeave,
onDrop: handleDrop,
onClick: handleClick,
}),
getInputProps: () => ({
id: fileInputId,
type: 'file',
multiple: true,
onChange: handleFileInputChange,
}),
}
}

View file

@ -0,0 +1,64 @@
import type { NonNullableUppyContext } from '../types.js'
export type FileInputProps = {
multiple?: boolean
accept?: string
}
export type FileInputFunctions<EventType> = {
getInputProps: () => {
id: string
type: 'file'
multiple: boolean
accept?: string
onChange: (event: EventType) => void
}
getButtonProps: () => {
type: 'button'
onClick: () => void
}
}
const fileInputId = 'uppy-file-input' as const
// Use a more generic constraint that works with both DOM Events and React/Vue Events
export function createFileInput<EventType extends Event>(
ctx: NonNullableUppyContext,
props: FileInputProps = {},
): FileInputFunctions<EventType> {
const handleClick = () => {
const input = document.getElementById(fileInputId) as HTMLInputElement
input?.click()
}
const handleFileInputChange = (event: EventType) => {
const input = event.target as HTMLInputElement
const files = Array.from(input.files || [])
if (!files.length) return
ctx.uppy.addFiles(
files.map((file) => ({
name: file.name,
type: file.type,
data: file,
})),
)
// Reset the input value so the same file can be selected again
input.value = ''
}
return {
getInputProps: () => ({
id: fileInputId,
type: 'file',
multiple: props.multiple ?? true,
accept: props.accept,
onChange: handleFileInputChange,
}),
getButtonProps: () => ({
type: 'button',
onClick: handleClick,
}),
}
}

View file

@ -2,6 +2,16 @@ export { default as Thumbnail, type ThumbnailProps } from './Thumbnail.js'
export { default as FilesList, type FilesListProps } from './FilesList.js'
export { default as FilesGrid, type FilesGridProps } from './FilesGrid.js'
export { default as Dropzone, type DropzoneProps } from './Dropzone.js'
export {
createDropzone,
type DropzoneReturn,
type DropzoneOptions,
} from './hooks/dropzone.js'
export {
createFileInput,
type FileInputProps,
type FileInputFunctions,
} from './hooks/file-input.js'
export {
default as UploadButton,
type UploadButtonProps,
@ -11,7 +21,11 @@ export {
type ProviderIconProps,
} from './ProviderIcon.js'
export type { UppyContext } from './types.js'
// Types and utils
export type {
UppyContext,
UppyState,
UploadStatus,
NonNullableUppyContext,
} from './types.js'
export { createUppyEventAdapter } from './uppyEventAdapter.js'
export type { UploadStatus } from './types.js'

View file

@ -8,8 +8,15 @@ export type UploadStatus =
| 'error'
| 'complete'
export type UppyContext = {
uppy: Uppy | undefined
export type UppyState = {
status: UploadStatus
progress: number
}
export type UppyContext = UppyState & {
uppy: Uppy | undefined
}
export type NonNullableUppyContext = UppyContext & {
uppy: Uppy
}

View file

@ -1,4 +1,4 @@
import Uppy from '@uppy/core'
import Uppy, { type UppyEventMap } from '@uppy/core'
import type { UploadStatus } from './types'
export function createUppyEventAdapter({
@ -19,9 +19,13 @@ export function createUppyEventAdapter({
const onResumeAll = () => {
onStatusChange('uploading')
}
const onComplete = () => {
onStatusChange('complete')
onProgressChange(0)
const onComplete: UppyEventMap<any, any>['complete'] = (result) => {
// If there are no uploads in failed or successful 'cancel-all' was called.
// Because 'complete' is called afterwards, we don't want to set the status to 'complete'
if (result?.failed?.length || result?.successful?.length) {
onStatusChange('complete')
onProgressChange(0)
}
}
const onError = () => {
onStatusChange('error')

View file

@ -3,9 +3,14 @@ import React, {
useState,
useEffect,
createElement as h,
useContext,
} from 'react'
import type Uppy from '@uppy/core'
import { createUppyEventAdapter, type UploadStatus } from '@uppy/components'
import {
createUppyEventAdapter,
type UploadStatus,
type NonNullableUppyContext,
} from '@uppy/components'
interface UppyContextValue {
uppy: Uppy | undefined
@ -62,3 +67,13 @@ export function UppyContextProvider({ uppy, children }: Props) {
}
export default UppyContextProvider
export function useUppyContext(): NonNullableUppyContext {
const ctx = useContext(UppyContext)
if (!ctx.uppy) {
throw new Error('useDropzone must be called within a UppyContextProvider')
}
return ctx as NonNullableUppyContext // covered by the if statement above
}

View file

@ -1,3 +1,5 @@
// This file was generated by build-components.mjs
// ANY EDITS WILL BE OVERWRITTEN!
import { useEffect, useRef, useContext, createElement as h } from 'react'
import {
Dropzone as PreactDropzone,
@ -5,7 +7,7 @@ import {
} from '@uppy/components'
import { h as preactH } from 'preact'
import { render as preactRender } from 'preact/compat'
import { UppyContext } from './UppyContextProvider.js'
import { UppyContext } from '../UppyContextProvider.js'
export default function Dropzone(props: Omit<DropzoneProps, 'ctx'>) {
const ref = useRef(null)

View file

@ -1,3 +1,5 @@
// This file was generated by build-components.mjs
// ANY EDITS WILL BE OVERWRITTEN!
import { useEffect, useRef, useContext, createElement as h } from 'react'
import {
FilesGrid as PreactFilesGrid,
@ -5,7 +7,7 @@ import {
} from '@uppy/components'
import { h as preactH } from 'preact'
import { render as preactRender } from 'preact/compat'
import { UppyContext } from './UppyContextProvider.js'
import { UppyContext } from '../UppyContextProvider.js'
export default function FilesGrid(props: Omit<FilesGridProps, 'ctx'>) {
const ref = useRef(null)

View file

@ -1,3 +1,5 @@
// This file was generated by build-components.mjs
// ANY EDITS WILL BE OVERWRITTEN!
import { useEffect, useRef, useContext, createElement as h } from 'react'
import {
FilesList as PreactFilesList,
@ -5,7 +7,7 @@ import {
} from '@uppy/components'
import { h as preactH } from 'preact'
import { render as preactRender } from 'preact/compat'
import { UppyContext } from './UppyContextProvider.js'
import { UppyContext } from '../UppyContextProvider.js'
export default function FilesList(props: Omit<FilesListProps, 'ctx'>) {
const ref = useRef(null)

View file

@ -1,3 +1,5 @@
// This file was generated by build-components.mjs
// ANY EDITS WILL BE OVERWRITTEN!
import { useEffect, useRef, useContext, createElement as h } from 'react'
import {
ProviderIcon as PreactProviderIcon,
@ -5,7 +7,7 @@ import {
} from '@uppy/components'
import { h as preactH } from 'preact'
import { render as preactRender } from 'preact/compat'
import { UppyContext } from './UppyContextProvider.js'
import { UppyContext } from '../UppyContextProvider.js'
export default function ProviderIcon(props: Omit<ProviderIconProps, 'ctx'>) {
const ref = useRef(null)

View file

@ -1,3 +1,5 @@
// This file was generated by build-components.mjs
// ANY EDITS WILL BE OVERWRITTEN!
import { useEffect, useRef, useContext, createElement as h } from 'react'
import {
Thumbnail as PreactThumbnail,
@ -5,7 +7,7 @@ import {
} from '@uppy/components'
import { h as preactH } from 'preact'
import { render as preactRender } from 'preact/compat'
import { UppyContext } from './UppyContextProvider.js'
import { UppyContext } from '../UppyContextProvider.js'
export default function Thumbnail(props: Omit<ThumbnailProps, 'ctx'>) {
const ref = useRef(null)

View file

@ -1,3 +1,5 @@
// This file was generated by build-components.mjs
// ANY EDITS WILL BE OVERWRITTEN!
import { useEffect, useRef, useContext, createElement as h } from 'react'
import {
UploadButton as PreactUploadButton,
@ -5,7 +7,7 @@ import {
} from '@uppy/components'
import { h as preactH } from 'preact'
import { render as preactRender } from 'preact/compat'
import { UppyContext } from './UppyContextProvider.js'
import { UppyContext } from '../UppyContextProvider.js'
export default function UploadButton(props: Omit<UploadButtonProps, 'ctx'>) {
const ref = useRef(null)

View file

@ -6,10 +6,12 @@ export { default as StatusBar } from './StatusBar.js'
export { default as FileInput } from './FileInput.js'
export { default as useUppyState } from './useUppyState.js'
export { default as useUppyEvent } from './useUppyEvent.js'
export { useDropzone } from './useDropzone.js'
export { useFileInput } from './useFileInput.js'
// Headless components
export {
UppyContext,
UppyContextProvider,
} from './headless/UppyContextProvider.js'
export * from './headless/index.js'
export * from './headless/generated/index.js'

View file

@ -0,0 +1,37 @@
import { useMemo } from 'react'
import type {
DragEvent as ReactDragEvent,
ChangeEvent as ReactChangeEvent,
} from 'react'
import {
createDropzone,
type DropzoneOptions,
type DropzoneReturn,
} from '@uppy/components'
import { useUppyContext } from './headless/UppyContextProvider.js'
type TDragEvent = DragEvent & ReactDragEvent<HTMLDivElement>
type TChangeEvent = Event & ReactChangeEvent<HTMLInputElement>
export function useDropzone(
options?: DropzoneOptions,
): DropzoneReturn<TDragEvent, TChangeEvent> {
const ctx = useUppyContext()
const dropzone = useMemo(
() => createDropzone<TDragEvent, TChangeEvent>(ctx, options),
// We need every value on options to be memoized to avoid re-creating the dropzone on every render
// eslint-disable-next-line react-hooks/exhaustive-deps
[
ctx,
options?.noClick,
options?.onDragOver,
options?.onDragEnter,
options?.onDragLeave,
options?.onDrop,
options?.onFileInputChange,
],
)
return dropzone
}

View file

@ -0,0 +1,21 @@
import { useMemo, type ChangeEvent } from 'react'
import type { FileInputProps, FileInputFunctions } from '@uppy/components'
import { createFileInput } from '@uppy/components'
import { useUppyContext } from './headless/UppyContextProvider.js'
type TEvent = Event & ChangeEvent<HTMLInputElement>
export function useFileInput(
props?: FileInputProps,
): FileInputFunctions<TEvent> {
const ctx = useUppyContext()
const fileInput = useMemo(
() => createFileInput<TEvent>(ctx, props),
// We need every value on props to be memoized to avoid re-creating the file input on every render
// eslint-disable-next-line react-hooks/exhaustive-deps
[ctx, props?.accept, props?.multiple],
)
return fileInput
}

View file

@ -1,48 +1,46 @@
<script context="module" lang="ts">
import type Uppy from '@uppy/core';
import { createUppyEventAdapter, type UploadStatus } from '@uppy/components'
<script module lang="ts">
import {
createUppyEventAdapter,
type UploadStatus,
type UppyContext,
} from '@uppy/components'
export const UppyContextKey = 'uppy-context';
export type { UppyContext } from '@uppy/components';
export const UppyContextKey = 'uppy-context'
export type { UppyContext } from '@uppy/components'
</script>
<script lang="ts">
import { setContext, onMount } from 'svelte';
import { setContext, onMount } from 'svelte'
export let uppy: Uppy;
let { uppy, children } = $props()
let status: UploadStatus = 'init';
let progress = 0;
// Create a single reactive context object
const contextValue: UppyContext = $state({
uppy,
status: 'init' as UploadStatus,
progress: 0,
})
onMount(() => {
if (!uppy) {
throw new Error(
'ContextProvider: passing `uppy` as a prop is required',
);
throw new Error('ContextProvider: passing `uppy` as a prop is required')
}
const uppyEventAdapter = createUppyEventAdapter({
uppy,
onStatusChange: (newStatus: UploadStatus) => {
status = newStatus
contextValue.status = newStatus
},
onProgressChange: (newProgress: number) => {
progress = newProgress
contextValue.progress = newProgress
},
})
return () => uppyEventAdapter.cleanup()
});
})
// Create a reactive store from our context values
$: contextValue = {
uppy,
status,
progress,
};
// Set the context for child components to use
$: setContext(UppyContextKey, contextValue);
// Set the single context for child components to use
setContext(UppyContextKey, contextValue)
</script>
<slot />
{@render children()}

View file

@ -1,13 +1,15 @@
<script lang="ts">
// This file was generated by build-components.mjs
// ANY EDITS WILL BE OVERWRITTEN!
import { getContext, mount } from 'svelte'
import {
Dropzone as PreactDropzone,
type DropzoneProps,
type UppyContext,
} from '@uppy/components'
import { h as preactH } from 'preact'
import { render as preactRender } from 'preact/compat'
import { UppyContextKey } from './UppyContextProvider.svelte'
import { h as preactH } from 'preact'
import { render as preactRender } from 'preact/compat'
import { UppyContextKey } from '../UppyContextProvider.svelte'
const props: Omit<DropzoneProps, 'ctx'> = $props()
const ctx = getContext<UppyContext>(UppyContextKey)

View file

@ -1,13 +1,15 @@
<script lang="ts">
// This file was generated by build-components.mjs
// ANY EDITS WILL BE OVERWRITTEN!
import { getContext, mount } from 'svelte'
import {
FilesGrid as PreactFilesGrid,
type FilesGridProps,
type UppyContext,
} from '@uppy/components'
import { h as preactH } from 'preact'
import { render as preactRender } from 'preact/compat'
import { UppyContextKey } from './UppyContextProvider.svelte'
import { h as preactH } from 'preact'
import { render as preactRender } from 'preact/compat'
import { UppyContextKey } from '../UppyContextProvider.svelte'
const props: Omit<FilesGridProps, 'ctx'> = $props()
const ctx = getContext<UppyContext>(UppyContextKey)

View file

@ -1,13 +1,15 @@
<script lang="ts">
// This file was generated by build-components.mjs
// ANY EDITS WILL BE OVERWRITTEN!
import { getContext, mount } from 'svelte'
import {
FilesList as PreactFilesList,
type FilesListProps,
type UppyContext,
} from '@uppy/components'
import { h as preactH } from 'preact'
import { render as preactRender } from 'preact/compat'
import { UppyContextKey } from './UppyContextProvider.svelte'
import { h as preactH } from 'preact'
import { render as preactRender } from 'preact/compat'
import { UppyContextKey } from '../UppyContextProvider.svelte'
const props: Omit<FilesListProps, 'ctx'> = $props()
const ctx = getContext<UppyContext>(UppyContextKey)

View file

@ -1,13 +1,15 @@
<script lang="ts">
// This file was generated by build-components.mjs
// ANY EDITS WILL BE OVERWRITTEN!
import { getContext, mount } from 'svelte'
import {
ProviderIcon as PreactProviderIcon,
type ProviderIconProps,
type UppyContext,
} from '@uppy/components'
import { h as preactH } from 'preact'
import { render as preactRender } from 'preact/compat'
import { UppyContextKey } from './UppyContextProvider.svelte'
import { h as preactH } from 'preact'
import { render as preactRender } from 'preact/compat'
import { UppyContextKey } from '../UppyContextProvider.svelte'
const props: Omit<ProviderIconProps, 'ctx'> = $props()
const ctx = getContext<UppyContext>(UppyContextKey)

View file

@ -1,13 +1,15 @@
<script lang="ts">
// This file was generated by build-components.mjs
// ANY EDITS WILL BE OVERWRITTEN!
import { getContext, mount } from 'svelte'
import {
Thumbnail as PreactThumbnail,
type ThumbnailProps,
type UppyContext,
} from '@uppy/components'
import { h as preactH } from 'preact'
import { render as preactRender } from 'preact/compat'
import { UppyContextKey } from './UppyContextProvider.svelte'
import { h as preactH } from 'preact'
import { render as preactRender } from 'preact/compat'
import { UppyContextKey } from '../UppyContextProvider.svelte'
const props: Omit<ThumbnailProps, 'ctx'> = $props()
const ctx = getContext<UppyContext>(UppyContextKey)

View file

@ -1,13 +1,15 @@
<script lang="ts">
// This file was generated by build-components.mjs
// ANY EDITS WILL BE OVERWRITTEN!
import { getContext, mount } from 'svelte'
import {
UploadButton as PreactUploadButton,
type UploadButtonProps,
type UppyContext,
} from '@uppy/components'
import { h as preactH } from 'preact'
import { render as preactRender } from 'preact/compat'
import { UppyContextKey } from './UppyContextProvider.svelte'
import { h as preactH } from 'preact'
import { render as preactRender } from 'preact/compat'
import { UppyContextKey } from '../UppyContextProvider.svelte'
const props: Omit<UploadButtonProps, 'ctx'> = $props()
const ctx = getContext<UppyContext>(UppyContextKey)

View file

@ -3,4 +3,4 @@ export { default as FilesGrid } from './FilesGrid.svelte'
export { default as FilesList } from './FilesList.svelte'
export { default as ProviderIcon } from './ProviderIcon.svelte'
export { default as Thumbnail } from './Thumbnail.svelte'
export { default as UploadButton } from './UploadButton.svelte'
export { default as UploadButton } from './UploadButton.svelte'

View file

@ -0,0 +1,14 @@
import { getContext } from 'svelte'
import { type UppyContext, type NonNullableUppyContext } from '@uppy/components'
import { UppyContextKey } from './UppyContextProvider.svelte'
export function getUppyContext(): NonNullableUppyContext {
const ctx = getContext<UppyContext>(UppyContextKey)
if (!ctx?.uppy) {
throw new Error('Component must be called within a UppyContextProvider')
}
return ctx as NonNullableUppyContext // covered by the if statement above
}

View file

@ -6,4 +6,8 @@ export { default as StatusBar } from './components/StatusBar.svelte'
// Headless components
export { default as UppyContextProvider } from './components/headless/UppyContextProvider.svelte'
export * from './components/headless/index.js'
export * from './components/headless/generated/index.js'
// Hooks
export * from './useDropzone.js'
export * from './useFileInput.js'

View file

@ -0,0 +1,52 @@
import { getContext } from 'svelte'
import {
createDropzone,
type DropzoneOptions,
} from '@uppy/components'
import { getUppyContext } from './components/headless/uppyContext.js'
export type SvelteDropzoneReturn = {
getRootProps: () => {
ondragenter: (event: DragEvent) => void
ondragover: (event: DragEvent) => void
ondragleave: (event: DragEvent) => void
ondrop: (event: DragEvent) => void
onclick: () => void
}
getInputProps: () => {
id: string
type: 'file'
multiple: boolean
onchange: (event: Event) => void
}
}
export function useDropzone(options?: DropzoneOptions): SvelteDropzoneReturn {
const ctx = getUppyContext()
const dropzone = createDropzone(ctx, options)
return {
// Only Svelte uses lowercase event names so we want to remap them
...dropzone,
getRootProps: () => {
const { onDragEnter, onDragOver, onDragLeave, onDrop, onClick, ...rest } =
dropzone.getRootProps()
return {
...rest,
ondragenter: onDragEnter,
ondragover: onDragOver,
ondragleave: onDragLeave,
ondrop: onDrop,
onclick: onClick,
}
},
getInputProps: () => {
const { onChange, ...rest } = dropzone.getInputProps()
return {
...rest,
onchange: onChange,
}
},
}
}

View file

@ -0,0 +1,47 @@
import {
type FileInputProps,
} from '@uppy/components'
import { createFileInput } from '@uppy/components'
import { getUppyContext } from './components/headless/uppyContext.js'
export type SvelteFileInputFunctions = {
getInputProps: () => {
id: string
type: 'file'
multiple: boolean
accept?: string
onchange: (event: Event) => void
}
getButtonProps: () => {
type: 'button'
onclick: () => void
}
}
export function useFileInput(props?: FileInputProps): SvelteFileInputFunctions {
const ctx = getUppyContext()
const fileinput = createFileInput<Event>(
ctx,
props,
)
return {
// Only Svelte uses lowercase event names so we want to remap them
...fileinput,
getButtonProps: () => {
const { onClick, ...rest } = fileinput.getButtonProps()
return {
...rest,
onclick: onClick,
}
},
getInputProps: () => {
const { onChange, ...rest } = fileinput.getInputProps()
return {
...rest,
onchange: onChange,
}
},
}
}

View file

@ -5,11 +5,12 @@ import {
ref,
onMounted,
onBeforeUnmount,
inject,
} from 'vue'
import type { PropType } from 'vue'
import type Uppy from '@uppy/core'
import { createUppyEventAdapter } from '@uppy/components'
import type { UploadStatus } from '@uppy/components'
import type { NonNullableUppyContext, UploadStatus } from '@uppy/components'
export interface UppyContext {
uppy: Uppy | undefined
@ -71,3 +72,13 @@ export const UppyContextProvider = defineComponent({
})
export default UppyContextProvider
export function injectUppyContext(): NonNullableUppyContext {
const ctx = inject<UppyContext>(UppyContextSymbol)
if (!ctx?.uppy) {
throw new Error('Component must be called within a UppyContextProvider')
}
return ctx as NonNullableUppyContext // covered by the if-statement above
}

View file

@ -1,3 +1,5 @@
// This file was generated by build-components.mjs
// ANY EDITS WILL BE OVERWRITTEN!
import { defineComponent, ref, watch, onMounted, h } from 'vue'
import {
Dropzone as PreactDropzone,
@ -6,7 +8,7 @@ import {
import { h as preactH } from 'preact'
import { render as preactRender } from 'preact/compat'
import { shallowEqualObjects } from 'shallow-equal'
import { useUppyContext } from './useUppyContext.js'
import { useUppyContext } from '../useUppyContext.js'
export default defineComponent<Omit<DropzoneProps, 'ctx'>>({
name: 'Dropzone',

View file

@ -1,3 +1,5 @@
// This file was generated by build-components.mjs
// ANY EDITS WILL BE OVERWRITTEN!
import { defineComponent, ref, watch, onMounted, h } from 'vue'
import {
FilesGrid as PreactFilesGrid,
@ -6,7 +8,7 @@ import {
import { h as preactH } from 'preact'
import { render as preactRender } from 'preact/compat'
import { shallowEqualObjects } from 'shallow-equal'
import { useUppyContext } from './useUppyContext.js'
import { useUppyContext } from '../useUppyContext.js'
export default defineComponent<Omit<FilesGridProps, 'ctx'>>({
name: 'FilesGrid',

View file

@ -1,3 +1,5 @@
// This file was generated by build-components.mjs
// ANY EDITS WILL BE OVERWRITTEN!
import { defineComponent, ref, watch, onMounted, h } from 'vue'
import {
FilesList as PreactFilesList,
@ -6,7 +8,7 @@ import {
import { h as preactH } from 'preact'
import { render as preactRender } from 'preact/compat'
import { shallowEqualObjects } from 'shallow-equal'
import { useUppyContext } from './useUppyContext.js'
import { useUppyContext } from '../useUppyContext.js'
export default defineComponent<Omit<FilesListProps, 'ctx'>>({
name: 'FilesList',

View file

@ -1,3 +1,5 @@
// This file was generated by build-components.mjs
// ANY EDITS WILL BE OVERWRITTEN!
import { defineComponent, ref, watch, onMounted, h } from 'vue'
import {
ProviderIcon as PreactProviderIcon,
@ -6,7 +8,7 @@ import {
import { h as preactH } from 'preact'
import { render as preactRender } from 'preact/compat'
import { shallowEqualObjects } from 'shallow-equal'
import { useUppyContext } from './useUppyContext.js'
import { useUppyContext } from '../useUppyContext.js'
export default defineComponent<Omit<ProviderIconProps, 'ctx'>>({
name: 'ProviderIcon',

View file

@ -1,3 +1,5 @@
// This file was generated by build-components.mjs
// ANY EDITS WILL BE OVERWRITTEN!
import { defineComponent, ref, watch, onMounted, h } from 'vue'
import {
Thumbnail as PreactThumbnail,
@ -6,7 +8,7 @@ import {
import { h as preactH } from 'preact'
import { render as preactRender } from 'preact/compat'
import { shallowEqualObjects } from 'shallow-equal'
import { useUppyContext } from './useUppyContext.js'
import { useUppyContext } from '../useUppyContext.js'
export default defineComponent<Omit<ThumbnailProps, 'ctx'>>({
name: 'Thumbnail',

View file

@ -1,3 +1,5 @@
// This file was generated by build-components.mjs
// ANY EDITS WILL BE OVERWRITTEN!
import { defineComponent, ref, watch, onMounted, h } from 'vue'
import {
UploadButton as PreactUploadButton,
@ -6,7 +8,7 @@ import {
import { h as preactH } from 'preact'
import { render as preactRender } from 'preact/compat'
import { shallowEqualObjects } from 'shallow-equal'
import { useUppyContext } from './useUppyContext.js'
import { useUppyContext } from '../useUppyContext.js'
export default defineComponent<Omit<UploadButtonProps, 'ctx'>>({
name: 'UploadButton',

View file

@ -6,4 +6,7 @@ export { default as StatusBar } from './status-bar.js'
export { default as FileInput } from './file-input.js'
export { UppyContextProvider } from './headless/context-provider.js'
export * from './headless/index.js'
export * from './headless/generated/index.js'
export * from './useDropzone.js'
export * from './useFileInput.js'

View file

@ -0,0 +1,48 @@
import { createDropzone, type DropzoneOptions } from '@uppy/components'
import { injectUppyContext } from './headless/context-provider.js'
export type VueDropzoneReturn = {
getRootProps: () => {
ondragenter: (event: DragEvent) => void
ondragover: (event: DragEvent) => void
ondragleave: (event: DragEvent) => void
ondrop: (event: DragEvent) => void
onclick: () => void
}
getInputProps: () => {
id: string
type: 'file'
multiple: boolean
onchange: (event: Event) => void
}
}
export function useDropzone(options?: DropzoneOptions): VueDropzoneReturn {
const ctx = injectUppyContext()
const dropzone = createDropzone(ctx, options)
return {
// Vue.js uses lowercase event names when using v-bind so we need to remap them
...dropzone,
getRootProps: () => {
const { onDragEnter, onDragOver, onDragLeave, onDrop, onClick, ...rest } =
dropzone.getRootProps()
return {
...rest,
ondragenter: onDragEnter,
ondragover: onDragOver,
ondragleave: onDragLeave,
ondrop: onDrop,
onclick: onClick,
}
},
getInputProps: () => {
const { onChange, ...rest } = dropzone.getInputProps()
return {
...rest,
onchange: onChange,
}
},
}
}

View file

@ -0,0 +1,42 @@
import { type FileInputProps } from '@uppy/components'
import { createFileInput } from '@uppy/components'
import { injectUppyContext } from './headless/context-provider.js'
export type VueFileInputFunctions = {
getInputProps: () => {
id: string
type: 'file'
multiple: boolean
accept?: string
onchange: (event: Event) => void
}
getButtonProps: () => {
type: 'button'
onclick: () => void
}
}
export function useFileInput(props?: FileInputProps): VueFileInputFunctions {
const ctx = injectUppyContext()
const fileinput = createFileInput<Event>(ctx, props)
return {
// Vue.js uses lowercase event names when using v-bind so we need to remap them
...fileinput,
getButtonProps: () => {
const { onClick, ...rest } = fileinput.getButtonProps()
return {
...rest,
onclick: onClick,
}
},
getInputProps: () => {
const { onChange, ...rest } = fileinput.getInputProps()
return {
...rest,
onchange: onChange,
}
},
}
}

2154
yarn.lock

File diff suppressed because it is too large Load diff