From f53928d13bdf6c2c5a5557fbca7c6e11877260cb Mon Sep 17 00:00:00 2001 From: nichoth Date: Sun, 3 Nov 2024 20:36:48 -0800 Subject: [PATCH 1/8] setup --- .eslintignore | 3 +++ .eslintrc | 46 +++++++++++++++++++++++++++++++++++ .gitignore | 5 +++- .npmignore | 1 + .npmrc | 1 + README.md | 7 ++++++ lib.es5.d.ts | 19 +++++++++++++++ package.json | 58 +++++++++++++++++++++++++++++---------------- tsconfig.build.json | 8 +++++++ tsconfig.json | 32 +++++++++++++++++++++++++ vite.config.js | 43 +++++++++++++++++++++++++++++++++ 11 files changed, 202 insertions(+), 21 deletions(-) create mode 100644 .eslintignore create mode 100644 .eslintrc create mode 100644 .npmrc create mode 100644 lib.es5.d.ts create mode 100644 tsconfig.build.json create mode 100644 tsconfig.json create mode 100644 vite.config.js diff --git a/.eslintignore b/.eslintignore new file mode 100644 index 0000000..660d20d --- /dev/null +++ b/.eslintignore @@ -0,0 +1,3 @@ +dist/* +public/* +test/*.js \ No newline at end of file diff --git a/.eslintrc b/.eslintrc new file mode 100644 index 0000000..4167350 --- /dev/null +++ b/.eslintrc @@ -0,0 +1,46 @@ +{ + "parser": "@typescript-eslint/parser", + "ignorePatterns": ["lib.es5.d.ts"], + "parserOptions": { + "requireConfigFile": false + }, + "extends": [ + "standard", + "plugin:@typescript-eslint/recommended" + ], + "plugins": [ + "@typescript-eslint" + ], + "rules": { + "@typescript-eslint/no-explicit-any": "off", + "@typescript-eslint/no-unused-vars": [ + "error", + { + "argsIgnorePattern": "^_", + "varsIgnorePattern": "^_", + "caughtErrorsIgnorePattern": "^_" + } + ], + "operator-linebreak": ["off"], + "multiline-ternary": "off", + "@typescript-eslint/consistent-type-imports": [ + "error", + { + "prefer": "type-imports" + } + ], + "no-multiple-empty-lines": [ + "error", + { + "max": 1, + "maxEOF": 1 + } + ], + "indent": ["error", 4, { + "SwitchCase": 1, + "ignoredNodes": ["TemplateLiteral *"] + }], + "comma-dangle": "off", + "no-multi-spaces": ["error", { "ignoreEOLComments": true }] + } +} \ No newline at end of file diff --git a/.gitignore b/.gitignore index a1ed9c0..048577b 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,4 @@ -example/drag-drop.js +node_modules +bundle.js +.DS_Store +.env diff --git a/.npmignore b/.npmignore index 957b209..f4cd498 100644 --- a/.npmignore +++ b/.npmignore @@ -1,3 +1,4 @@ .github/ example/ test/ +.env diff --git a/.npmrc b/.npmrc new file mode 100644 index 0000000..43c97e7 --- /dev/null +++ b/.npmrc @@ -0,0 +1 @@ +package-lock=false diff --git a/README.md b/README.md index e646d22..652ca55 100644 --- a/README.md +++ b/README.md @@ -9,6 +9,13 @@ [standard-image]: https://img.shields.io/badge/code_style-standard-brightgreen.svg [standard-url]: https://standardjs.com +

Contents

+ +
+ +## fork +This is a fork of [feross/drag-drop](https://github.com/feross/drag-drop). + ### HTML5 drag & drop for humans In case you didn't know, the diff --git a/lib.es5.d.ts b/lib.es5.d.ts new file mode 100644 index 0000000..fef7749 --- /dev/null +++ b/lib.es5.d.ts @@ -0,0 +1,19 @@ +/** See https://stackoverflow.com/a/51390763/1470607 */ +type Falsy = false | 0 | '' | null | undefined; + +/** + * see https://www.karltarvas.com/typescript-array-filter-boolean.html + */ +interface Array { + /** + * Returns the elements of an array that meet the condition specified in a + * callback function. + * @param predicate A function that accepts up to three arguments. The filter + * method calls the predicate function one time for each element in the array. + * @param thisArg An object to which the this keyword can refer in the + * predicate function. If thisArg is omitted, undefined is used as the + * this value. + */ + filter(predicate: BooleanConstructor, thisArg?: any) + : Exclude[]; +} \ No newline at end of file diff --git a/package.json b/package.json index 7be5348..3cd0ddb 100644 --- a/package.json +++ b/package.json @@ -1,27 +1,40 @@ { - "name": "drag-drop", + "name": "@bicycle-codes/drag-drop", "description": "HTML5 drag & drop for humans", "version": "7.2.0", - "author": { - "name": "Feross Aboukhadijeh", - "email": "feross@feross.org", - "url": "https://feross.org" - }, - "bugs": { - "url": "https://github.com/feross/drag-drop/issues" + "files": [ + "./dist/*" + ], + "scripts": { + "start": "vite", + "lint": "eslint \"./**/*.{ts,js}\"", + "example": "browserify -s DragDrop -r . > example/drag-drop.js && ecstatic example --port 4000", + "size": "browserify -s DragDrop -r . | minify | gzip | wc -c", + "toc": "markdown-toc --maxdepth 3 -i README.md", + "build": "mkdir -p ./dist && rm -rf ./dist/* && npm run build-cjs && npm run build-esm && npm run build-esm:min && npm run build-cjs:min", + "build-example": "mkdir -p ./public && rm -rf ./public/* && VITE_DEBUG_MODE=staging vite --mode staging --base=\"/drag-drop\" build", + "test": "tape test/*.js", + "preversion": "npm run lint", + "version": "npm run toc && auto-changelog -p --template keepachangelog --breaking-pattern 'BREAKING CHANGE:' && git add CHANGELOG.md README.md", + "postversion": "git push --follow-tags && npm publish", + "prepublishOnly": "npm run build" }, "dependencies": { "blob-to-buffer": "^1.2.9", "run-parallel": "^1.2.0" }, "devDependencies": { - "babel-minify": "^0.5.1", - "browserify": "^17.0.0", - "ecstatic": "^4.1.4", - "standard": "*", - "tape": "^5.2.2" + "@bicycle-codes/debug": "^0.6.16", + "@bicycle-codes/tapzero": "^0.10.5", + "eslint": "^8.57.1", + "eslint-config-standard": "^17.1.0", + "markdown-toc": "^1.2.0", + "postcss-nesting": "^13.0.1", + "tap-spec": "^5.0.0", + "typescript": "^5.6.3", + "vite": "^5.4.10" }, - "homepage": "https://github.com/feross/drag-drop", + "homepage": "https://github.com/bicycle-codes/drag-drop", "keywords": [ "drag", "drop", @@ -30,8 +43,7 @@ "drag drop", "html5", "drag & drop", - "frontend", - "browserify" + "frontend" ], "license": "MIT", "main": "index.js", @@ -39,10 +51,16 @@ "type": "git", "url": "git://github.com/feross/drag-drop.git" }, - "scripts": { - "example": "browserify -s DragDrop -r . > example/drag-drop.js && ecstatic example --port 4000", - "size": "browserify -s DragDrop -r . | minify | gzip | wc -c", - "test": "standard && tape test/*.js" + "author": { + "name": "Feross Aboukhadijeh", + "email": "feross@feross.org", + "url": "https://feross.org" + }, + "contributors": [ + "Nick Thomas (https://nichoth./com)" + ], + "bugs": { + "url": "https://github.com/bicycle-codes/drag-drop/issues" }, "funding": [ { diff --git a/tsconfig.build.json b/tsconfig.build.json new file mode 100644 index 0000000..1d7d702 --- /dev/null +++ b/tsconfig.build.json @@ -0,0 +1,8 @@ +{ + "extends": "./tsconfig.json", + "exclude": [ + "example", + "test" + ] +} + diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..1b1ebd5 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,32 @@ +{ + "compilerOptions": { + "listFiles": true, + "module": "ES2022", + "target": "ES2022", + "moduleResolution": "Bundler", + "esModuleInterop": false, + "lib": ["ES2022", "DOM", "WebWorker"], + "types": ["vite/client"], + "allowJs": false, + "skipLibCheck": true, + "outDir": "dist", + "allowSyntheticDefaultImports": true, + "experimentalDecorators": true, + "emitDecoratorMetadata": true, + "strict": true, + "sourceMap": true, + "forceConsistentCasingInFileNames": true, + "resolveJsonModule": true, + "isolatedModules": true, + "noImplicitAny": false, + "declaration": true, + "declarationDir": "dist", + "declarationMap": true + }, + "include": [ + "example", + "src/**/*", + "test", + "lib.es5.d.ts" + ] +} diff --git a/vite.config.js b/vite.config.js new file mode 100644 index 0000000..fbe1de5 --- /dev/null +++ b/vite.config.js @@ -0,0 +1,43 @@ +import { defineConfig } from 'vite' +import preact from '@preact/preset-vite' +import postcssNesting from 'postcss-nesting' + +// https://vitejs.dev/config/ +export default defineConfig({ + define: { + global: 'globalThis' + }, + root: 'example', + plugins: [ + preact({ + devtoolsInProd: false, + prefreshEnabled: true, + babel: { + sourceMaps: 'both' + } + }) + ], + // https://github.com/vitejs/vite/issues/8644#issuecomment-1159308803 + esbuild: { + logOverride: { 'this-is-undefined-in-esm': 'silent' } + }, + publicDir: '_public', + css: { + postcss: { + plugins: [ + postcssNesting + ], + }, + }, + server: { + port: 8888, + host: true, + open: true, + }, + build: { + minify: false, + outDir: '../public', + emptyOutDir: true, + sourcemap: 'inline' + } +}) From 2266b8616bf6a223655b581f49f3c85620ccec12 Mon Sep 17 00:00:00 2001 From: nichoth Date: Sun, 3 Nov 2024 21:20:21 -0800 Subject: [PATCH 2/8] wip --- buffer.js | 27 --------------------------- buffer.ts | 25 +++++++++++++++++++++++++ package.json | 11 ++++++++--- test/basic.js | 11 ----------- test/basic.ts | 14 ++++++++++++++ 5 files changed, 47 insertions(+), 41 deletions(-) delete mode 100644 buffer.js create mode 100644 buffer.ts delete mode 100644 test/basic.js create mode 100644 test/basic.ts diff --git a/buffer.js b/buffer.js deleted file mode 100644 index 8b8cca5..0000000 --- a/buffer.js +++ /dev/null @@ -1,27 +0,0 @@ -module.exports = dragDropAsBuffer - -const dragDrop = require('./') -const parallel = require('run-parallel') -const blobToBuffer = require('blob-to-buffer') - -function dragDropAsBuffer (elem, cb) { - return dragDrop(elem, function (files, pos, fileList) { - const tasks = files.map(function (file) { - return function (cb) { - blobToBuffer(file, function (err, buffer) { - if (err) return cb(err) - buffer.name = file.name - buffer.fullPath = file.fullPath - buffer.size = file.size - buffer.type = file.type - buffer.lastModifiedDate = file.lastModifiedDate - cb(null, buffer) - }) - } - }) - parallel(tasks, function (err, results) { - if (err) throw err - cb(results, pos, fileList) - }) - }) -} diff --git a/buffer.ts b/buffer.ts new file mode 100644 index 0000000..6078156 --- /dev/null +++ b/buffer.ts @@ -0,0 +1,25 @@ +const dragDrop = require('./') +const parallel = require('run-parallel') +const blobToBuffer = require('blob-to-buffer') + +export function dragDropAsBuffer (elem, cb) { + return dragDrop(elem, function (files, pos, fileList) { + const tasks = files.map(function (file) { + return function (cb) { + blobToBuffer(file, function (err, buffer) { + if (err) return cb(err) + buffer.name = file.name + buffer.fullPath = file.fullPath + buffer.size = file.size + buffer.type = file.type + buffer.lastModifiedDate = file.lastModifiedDate + cb(null, buffer) + }) + } + }) + parallel(tasks, function (err, results) { + if (err) throw err + cb(results, pos, fileList) + }) + }) +} diff --git a/package.json b/package.json index 3cd0ddb..9d6c985 100644 --- a/package.json +++ b/package.json @@ -2,18 +2,21 @@ "name": "@bicycle-codes/drag-drop", "description": "HTML5 drag & drop for humans", "version": "7.2.0", + "type": "module", "files": [ "./dist/*" ], "scripts": { "start": "vite", "lint": "eslint \"./**/*.{ts,js}\"", - "example": "browserify -s DragDrop -r . > example/drag-drop.js && ecstatic example --port 4000", - "size": "browserify -s DragDrop -r . | minify | gzip | wc -c", "toc": "markdown-toc --maxdepth 3 -i README.md", "build": "mkdir -p ./dist && rm -rf ./dist/* && npm run build-cjs && npm run build-esm && npm run build-esm:min && npm run build-cjs:min", + "build-cjs": "esbuild src/*.ts --format=cjs --keep-names --tsconfig=tsconfig.build.json --outdir=./dist --out-extension:.js=.cjs --sourcemap", + "build-cjs:min": "esbuild src/*.ts --format=cjs --minify --keep-names --tsconfig=tsconfig.build.json --outdir=./dist --out-extension:.js=.min.cjs --sourcemap", + "build-esm": "esbuild src/*.ts --format=esm --metafile=dist/meta.json --keep-names --tsconfig=tsconfig.build.json --outdir=./dist --sourcemap && tsc --emitDeclarationOnly --project tsconfig.build.json --outDir dist", + "build-esm:min": "esbuild ./src/*.ts --format=esm --keep-names --bundle --tsconfig=tsconfig.build.json --minify --out-extension:.js=.min.js --outdir=./dist --sourcemap", "build-example": "mkdir -p ./public && rm -rf ./public/* && VITE_DEBUG_MODE=staging vite --mode staging --base=\"/drag-drop\" build", - "test": "tape test/*.js", + "test": "esbuild ./test/basic.ts", "preversion": "npm run lint", "version": "npm run toc && auto-changelog -p --template keepachangelog --breaking-pattern 'BREAKING CHANGE:' && git add CHANGELOG.md README.md", "postversion": "git push --follow-tags && npm publish", @@ -26,6 +29,8 @@ "devDependencies": { "@bicycle-codes/debug": "^0.6.16", "@bicycle-codes/tapzero": "^0.10.5", + "@typescript-eslint/eslint-plugin": "^8.12.2", + "esbuild": "^0.24.0", "eslint": "^8.57.1", "eslint-config-standard": "^17.1.0", "markdown-toc": "^1.2.0", diff --git a/test/basic.js b/test/basic.js deleted file mode 100644 index 57a43d0..0000000 --- a/test/basic.js +++ /dev/null @@ -1,11 +0,0 @@ -const test = require('tape') - -// writing tests for this would be hard, so let's just check that requiring the module -// doesn't throw - -test('requiring drag-drop does not throw', t => { - t.doesNotThrow(() => { - require('../') - }) - t.end() -}) diff --git a/test/basic.ts b/test/basic.ts new file mode 100644 index 0000000..4183a01 --- /dev/null +++ b/test/basic.ts @@ -0,0 +1,14 @@ +import { test } from '@bicycle-codes/tapzero' + +// writing tests for this would be hard, see the example for interactive testing + +test("Doesn't throw", t => { + +}) + +// test('requiring drag-drop does not throw', t => { +// t.doesNotThrow(() => { +// require('../') +// }) +// t.end() +// }) From 5f46f2e946496fc6ee4f4755d9b62abc29185a64 Mon Sep 17 00:00:00 2001 From: nichoth Date: Sun, 3 Nov 2024 21:29:45 -0800 Subject: [PATCH 3/8] wip --- index.js | 270 ------------------------------------------------------ index.ts | 273 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 273 insertions(+), 270 deletions(-) delete mode 100644 index.js create mode 100644 index.ts diff --git a/index.js b/index.js deleted file mode 100644 index b461f63..0000000 --- a/index.js +++ /dev/null @@ -1,270 +0,0 @@ -/*! drag-drop. MIT License. Feross Aboukhadijeh */ -module.exports = dragDrop -module.exports.processItems = processItems - -const parallel = require('run-parallel') - -function dragDrop (elem, listeners) { - if (typeof elem === 'string') { - const selector = elem - elem = window.document.querySelector(elem) - if (!elem) { - throw new Error(`"${selector}" does not match any HTML elements`) - } - } - - if (!elem) { - throw new Error(`"${elem}" is not a valid HTML element`) - } - - if (typeof listeners === 'function') { - listeners = { onDrop: listeners } - } - - elem.addEventListener('dragenter', onDragEnter, false) - elem.addEventListener('dragover', onDragOver, false) - elem.addEventListener('dragleave', onDragLeave, false) - elem.addEventListener('drop', onDrop, false) - - let isEntered = false - let numIgnoredEnters = 0 - - // Function to remove drag-drop listeners - return function cleanup () { - removeDragClass() - elem.removeEventListener('dragenter', onDragEnter, false) - elem.removeEventListener('dragover', onDragOver, false) - elem.removeEventListener('dragleave', onDragLeave, false) - elem.removeEventListener('drop', onDrop, false) - } - - function isEventHandleable (event) { - if (event.dataTransfer.items || event.dataTransfer.types) { - // Only add "drag" class when `items` contains items that are able to be - // handled by the registered listeners (files vs. text) - const items = Array.from(event.dataTransfer.items) - const types = Array.from(event.dataTransfer.types) - - let fileItems - let textItems - if (items.length) { - fileItems = items.filter(item => { return item.kind === 'file' }) - textItems = items.filter(item => { return item.kind === 'string' }) - } else if (types.length) { - // event.dataTransfer.items is empty during 'dragover' in Safari, so use - // event.dataTransfer.types as a fallback - fileItems = types.filter(item => item === 'Files') - textItems = types.filter(item => item.startsWith('text/')) - } else { - return false - } - - if (fileItems.length === 0 && !listeners.onDropText) return false - if (textItems.length === 0 && !listeners.onDrop) return false - if (fileItems.length === 0 && textItems.length === 0) return false - - return true - } - return false - } - - function onDragEnter (event) { - event.stopPropagation() - event.preventDefault() - - if (!isEventHandleable(event)) return - - if (isEntered) { - numIgnoredEnters += 1 - return false // early return - } - - isEntered = true - - if (listeners.onDragEnter) { - listeners.onDragEnter(event) - } - - addDragClass() - - return false - } - - function onDragOver (event) { - event.stopPropagation() - event.preventDefault() - - if (!isEventHandleable(event)) return - - if (listeners.onDragOver) { - listeners.onDragOver(event) - } - - event.dataTransfer.dropEffect = 'copy' - - return false - } - - function onDragLeave (event) { - event.stopPropagation() - event.preventDefault() - - if (!isEventHandleable(event)) return - - if (numIgnoredEnters > 0) { - numIgnoredEnters -= 1 - return false - } - - isEntered = false - - if (listeners.onDragLeave) { - listeners.onDragLeave(event) - } - - removeDragClass() - - return false - } - - function onDrop (event) { - event.stopPropagation() - event.preventDefault() - - if (listeners.onDragLeave) { - listeners.onDragLeave(event) - } - - removeDragClass() - - isEntered = false - numIgnoredEnters = 0 - - const pos = { - x: event.clientX, - y: event.clientY - } - - // text drop support - const text = event.dataTransfer.getData('text') - if (text && listeners.onDropText) { - listeners.onDropText(text, pos) - } - - // File drop support. The `dataTransfer.items` API supports directories, so we - // use it instead of `dataTransfer.files`, even though it's much more - // complicated to use. - // See: https://github.com/feross/drag-drop/issues/39 - if (listeners.onDrop && event.dataTransfer.items) { - processItems(event.dataTransfer.items, (err, files, directories) => { - if (err) { - // TODO: A future version of this library might expose this somehow - console.error(err) - return - } - - if (files.length === 0) return - - const fileList = event.dataTransfer.files - - // TODO: This callback has too many arguments, and the order is too - // arbitrary. In next major version, it should be cleaned up. - listeners.onDrop(files, pos, fileList, directories) - }) - } - - return false - } - - function addDragClass () { - elem.classList.add('drag') - } - - function removeDragClass () { - elem.classList.remove('drag') - } -} - -function processItems (items, cb) { - // Handle directories in Chrome using the proprietary FileSystem API - items = Array.from(items).filter(item => { - return item.kind === 'file' - }) - - if (items.length === 0) { - cb(null, [], []) - } - - parallel(items.map(item => { - return cb => { - processEntry(item.webkitGetAsEntry(), cb) - } - }), (err, results) => { - // This catches permission errors with file:// in Chrome - if (err) { - cb(err) - return - } - - const entries = results.flat(Infinity) - - const files = entries.filter(item => { - return item.isFile - }) - - const directories = entries.filter(item => { - return item.isDirectory - }) - - cb(null, files, directories) - }) -} - -function processEntry (entry, cb) { - let entries = [] - - if (entry.isFile) { - entry.file(file => { - file.fullPath = entry.fullPath // preserve path for consumer - file.isFile = true - file.isDirectory = false - cb(null, file) - }, err => { - cb(err) - }) - } else if (entry.isDirectory) { - const reader = entry.createReader() - readEntries(reader) - } - - function readEntries (reader) { - reader.readEntries(currentEntries => { - if (currentEntries.length > 0) { - entries = entries.concat(Array.from(currentEntries)) - readEntries(reader) // continue reading entries until `readEntries` returns no more - } else { - doneEntries() - } - }) - } - - function doneEntries () { - parallel(entries.map(entry => { - return cb => { - processEntry(entry, cb) - } - }), (err, results) => { - if (err) { - cb(err) - } else { - results.push({ - fullPath: entry.fullPath, - name: entry.name, - isFile: false, - isDirectory: true - }) - cb(null, results) - } - }) - } -} diff --git a/index.ts b/index.ts new file mode 100644 index 0000000..3af6e5e --- /dev/null +++ b/index.ts @@ -0,0 +1,273 @@ +/*! drag-drop. MIT License. Feross Aboukhadijeh */ +import parallel from 'run-parallel' +export default dragDrop + +function dragDrop (elem, listeners) { + if (typeof elem === 'string') { + const selector = elem + elem = window.document.querySelector(elem) + if (!elem) { + throw new Error(`"${selector}" does not match any HTML elements`) + } + } + + if (!elem) { + throw new Error(`"${elem}" is not a valid HTML element`) + } + + if (typeof listeners === 'function') { + listeners = { onDrop: listeners } + } + + elem.addEventListener('dragenter', onDragEnter, false) + elem.addEventListener('dragover', onDragOver, false) + elem.addEventListener('dragleave', onDragLeave, false) + elem.addEventListener('drop', onDrop, false) + + let isEntered = false + let numIgnoredEnters = 0 + + // Function to remove drag-drop listeners + return function cleanup () { + removeDragClass() + elem.removeEventListener('dragenter', onDragEnter, false) + elem.removeEventListener('dragover', onDragOver, false) + elem.removeEventListener('dragleave', onDragLeave, false) + elem.removeEventListener('drop', onDrop, false) + } + + function isEventHandleable (event:DragEvent) { + if (!event.dataTransfer) throw new Error('not event.dataTransfer') + + if (event.dataTransfer.items || event.dataTransfer.types) { + // Only add "drag" class when `items` contains items that are able to be + // handled by the registered listeners (files vs. text) + const items = Array.from(event.dataTransfer.items) + const types = Array.from(event.dataTransfer.types) + + let fileItems + let textItems + if (items.length) { + fileItems = items.filter(item => { return item.kind === 'file' }) + textItems = items.filter(item => { return item.kind === 'string' }) + } else if (types.length) { + // event.dataTransfer.items is empty during 'dragover' in Safari, so use + // event.dataTransfer.types as a fallback + fileItems = types.filter(item => item === 'Files') + textItems = types.filter(item => item.startsWith('text/')) + } else { + return false + } + + if (fileItems.length === 0 && !listeners.onDropText) return false + if (textItems.length === 0 && !listeners.onDrop) return false + if (fileItems.length === 0 && textItems.length === 0) return false + + return true + } + return false + } + + function onDragEnter (event) { + event.stopPropagation() + event.preventDefault() + + if (!isEventHandleable(event)) return + + if (isEntered) { + numIgnoredEnters += 1 + return false // early return + } + + isEntered = true + + if (listeners.onDragEnter) { + listeners.onDragEnter(event) + } + + addDragClass() + + return false + } + + function onDragOver (event) { + event.stopPropagation() + event.preventDefault() + + if (!isEventHandleable(event)) return + + if (listeners.onDragOver) { + listeners.onDragOver(event) + } + + event.dataTransfer.dropEffect = 'copy' + + return false + } + + function onDragLeave (event) { + event.stopPropagation() + event.preventDefault() + + if (!isEventHandleable(event)) return + + if (numIgnoredEnters > 0) { + numIgnoredEnters -= 1 + return false + } + + isEntered = false + + if (listeners.onDragLeave) { + listeners.onDragLeave(event) + } + + removeDragClass() + + return false + } + + function onDrop (event:DragEvent) { + event.stopPropagation() + event.preventDefault() + if (!event.dataTransfer) throw new Error('not dataTransfer') + + if (listeners.onDragLeave) { + listeners.onDragLeave(event) + } + + removeDragClass() + + isEntered = false + numIgnoredEnters = 0 + + const pos = { + x: event.clientX, + y: event.clientY + } + + // text drop support + const text = event.dataTransfer.getData('text') + if (text && listeners.onDropText) { + listeners.onDropText(text, pos) + } + + // File drop support. The `dataTransfer.items` API supports directories, so we + // use it instead of `dataTransfer.files`, even though it's much more + // complicated to use. + // See: https://github.com/feross/drag-drop/issues/39 + if (listeners.onDrop && event.dataTransfer.items) { + processItems(event.dataTransfer.items, (err, files, directories) => { + if (err) { + // TODO: A future version of this library might expose this somehow + console.error(err) + return + } + + if (files.length === 0) return + + // for TS + if (!event.dataTransfer) throw new Error('not dataTransfer') + const fileList = event.dataTransfer.files + + // TODO: This callback has too many arguments, and the order is too + // arbitrary. In next major version, it should be cleaned up. + listeners.onDrop(files, pos, fileList, directories) + }) + } + + return false + } + + function addDragClass () { + elem.classList.add('drag') + } + + function removeDragClass () { + elem.classList.remove('drag') + } +} + +export function processItems (_items:DataTransferItemList, cb) { + // Handle directories in Chrome using the proprietary FileSystem API + const items = Array.from(_items).filter(item => { + return item.kind === 'file' + }) + + if (items.length === 0) { + cb(null, [], []) + } + + parallel(items.map(item => { + return cb => { + processEntry(item.webkitGetAsEntry(), cb) + } + }), (err, results) => { + // This catches permission errors with file:// in Chrome + if (err) { + cb(err) + return + } + + const entries = results.flat(Infinity) + + const files = entries.filter(item => { + return item.isFile + }) + + const directories = entries.filter(item => { + return item.isDirectory + }) + + cb(null, files, directories) + }) +} + +function processEntry (entry, cb) { + let entries = [] + + if (entry.isFile) { + entry.file(file => { + file.fullPath = entry.fullPath // preserve path for consumer + file.isFile = true + file.isDirectory = false + cb(null, file) + }, err => { + cb(err) + }) + } else if (entry.isDirectory) { + const reader = entry.createReader() + readEntries(reader) + } + + function readEntries (reader) { + reader.readEntries(currentEntries => { + if (currentEntries.length > 0) { + entries = entries.concat(Array.from(currentEntries)) + readEntries(reader) // continue reading entries until `readEntries` returns no more + } else { + doneEntries() + } + }) + } + + function doneEntries () { + parallel(entries.map(entry => { + return cb => { + processEntry(entry, cb) + } + }), (err, results) => { + if (err) { + cb(err) + } else { + results.push({ + fullPath: entry.fullPath, + name: entry.name, + isFile: false, + isDirectory: true + }) + cb(null, results) + } + }) + } +} From 4cf6c55e371e2b7a35fb38a7eb920487df1ac5de Mon Sep 17 00:00:00 2001 From: nichoth Date: Sun, 3 Nov 2024 21:44:58 -0800 Subject: [PATCH 4/8] tsify --- buffer.ts | 9 +++++---- index.ts | 17 ++++++++++------- 2 files changed, 15 insertions(+), 11 deletions(-) diff --git a/buffer.ts b/buffer.ts index 6078156..288d1ff 100644 --- a/buffer.ts +++ b/buffer.ts @@ -1,8 +1,8 @@ -const dragDrop = require('./') -const parallel = require('run-parallel') -const blobToBuffer = require('blob-to-buffer') +import { dragDrop } from './index.js' +import parallel from 'run-parallel' +import blobToBuffer from 'blob-to-buffer' -export function dragDropAsBuffer (elem, cb) { +export function dragDropAsBuffer (elem:Element, cb) { return dragDrop(elem, function (files, pos, fileList) { const tasks = files.map(function (file) { return function (cb) { @@ -17,6 +17,7 @@ export function dragDropAsBuffer (elem, cb) { }) } }) + parallel(tasks, function (err, results) { if (err) throw err cb(results, pos, fileList) diff --git a/index.ts b/index.ts index 3af6e5e..eb1f403 100644 --- a/index.ts +++ b/index.ts @@ -2,13 +2,16 @@ import parallel from 'run-parallel' export default dragDrop -function dragDrop (elem, listeners) { - if (typeof elem === 'string') { - const selector = elem - elem = window.document.querySelector(elem) +export function dragDrop (_elem:Element|string, listeners) { + let elem:Element|null + if (typeof _elem === 'string') { + const selector = _elem + elem = window.document.querySelector(_elem) if (!elem) { throw new Error(`"${selector}" does not match any HTML elements`) } + } else { + elem = _elem } if (!elem) { @@ -127,7 +130,7 @@ function dragDrop (elem, listeners) { return false } - function onDrop (event:DragEvent) { + function onDrop (event) { event.stopPropagation() event.preventDefault() if (!event.dataTransfer) throw new Error('not dataTransfer') @@ -180,11 +183,11 @@ function dragDrop (elem, listeners) { } function addDragClass () { - elem.classList.add('drag') + elem!.classList.add('drag') } function removeDragClass () { - elem.classList.remove('drag') + elem!.classList.remove('drag') } } From 26b7d16bcb6da1472978ed0282581cd0028bfc61 Mon Sep 17 00:00:00 2001 From: nichoth Date: Sun, 3 Nov 2024 22:33:46 -0800 Subject: [PATCH 5/8] tests and example work --- .gitignore | 2 ++ example/index.html | 18 +++++++++++------- package.json | 4 +++- buffer.ts => src/buffer.ts | 0 index.ts => src/index.ts | 30 ++++++++++++++++++++++++------ test/basic.ts | 14 -------------- test/index.html | 13 +++++++++++++ test/index.ts | 9 +++++++++ vite.config.js | 8 -------- 9 files changed, 62 insertions(+), 36 deletions(-) rename buffer.ts => src/buffer.ts (100%) rename index.ts => src/index.ts (90%) delete mode 100644 test/basic.ts create mode 100644 test/index.html create mode 100644 test/index.ts diff --git a/.gitignore b/.gitignore index 048577b..f82ae81 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,5 @@ node_modules bundle.js .DS_Store .env +*.js +dist diff --git a/example/index.html b/example/index.html index d07d3e8..f4fef68 100644 --- a/example/index.html +++ b/example/index.html @@ -12,22 +12,26 @@

Drag something onto this page

- - diff --git a/package.json b/package.json index 9d6c985..59a1831 100644 --- a/package.json +++ b/package.json @@ -16,7 +16,8 @@ "build-esm": "esbuild src/*.ts --format=esm --metafile=dist/meta.json --keep-names --tsconfig=tsconfig.build.json --outdir=./dist --sourcemap && tsc --emitDeclarationOnly --project tsconfig.build.json --outDir dist", "build-esm:min": "esbuild ./src/*.ts --format=esm --keep-names --bundle --tsconfig=tsconfig.build.json --minify --out-extension:.js=.min.js --outdir=./dist --sourcemap", "build-example": "mkdir -p ./public && rm -rf ./public/* && VITE_DEBUG_MODE=staging vite --mode staging --base=\"/drag-drop\" build", - "test": "esbuild ./test/basic.ts", + "build-tests": "esbuild test/index.ts --target=es2020 --bundle --keep-names > test/test-bundle.js", + "test": "npm run build && npm run build-tests && cat test/index.html | tape-run --input=html --static=test | tap-spec", "preversion": "npm run lint", "version": "npm run toc && auto-changelog -p --template keepachangelog --breaking-pattern 'BREAKING CHANGE:' && git add CHANGELOG.md README.md", "postversion": "git push --follow-tags && npm publish", @@ -36,6 +37,7 @@ "markdown-toc": "^1.2.0", "postcss-nesting": "^13.0.1", "tap-spec": "^5.0.0", + "tape-run": "^11.0.0", "typescript": "^5.6.3", "vite": "^5.4.10" }, diff --git a/buffer.ts b/src/buffer.ts similarity index 100% rename from buffer.ts rename to src/buffer.ts diff --git a/index.ts b/src/index.ts similarity index 90% rename from index.ts rename to src/index.ts index eb1f403..b93e240 100644 --- a/index.ts +++ b/src/index.ts @@ -2,11 +2,22 @@ import parallel from 'run-parallel' export default dragDrop -export function dragDrop (_elem:Element|string, listeners) { +// type Listener = ((ev:DragEvent)=>any) +type Listener = ((files, pos?, fileList?, directories?)=>any) + +type ListenerObject = { + onDrop:(files, pos, fileList, directories)=>any; + onDropText?:(text, pos)=>any; + onDragEnter?:(event)=>any; + onDragOver?:(event)=>any; + onDragLeave?:(event)=>any; +} + +export function dragDrop (_elem:Element|string, _listeners:Listener|ListenerObject) { let elem:Element|null if (typeof _elem === 'string') { const selector = _elem - elem = window.document.querySelector(_elem) + elem = document.querySelector(_elem) if (!elem) { throw new Error(`"${selector}" does not match any HTML elements`) } @@ -18,8 +29,11 @@ export function dragDrop (_elem:Element|string, listeners) { throw new Error(`"${elem}" is not a valid HTML element`) } - if (typeof listeners === 'function') { - listeners = { onDrop: listeners } + let listeners:ListenerObject + if (typeof _listeners === 'function') { + listeners = { onDrop: _listeners } + } else { + listeners = _listeners } elem.addEventListener('dragenter', onDragEnter, false) @@ -54,14 +68,18 @@ export function dragDrop (_elem:Element|string, listeners) { fileItems = items.filter(item => { return item.kind === 'file' }) textItems = items.filter(item => { return item.kind === 'string' }) } else if (types.length) { - // event.dataTransfer.items is empty during 'dragover' in Safari, so use - // event.dataTransfer.types as a fallback + // event.dataTransfer.items is empty during 'dragover' in Safari, + // so use event.dataTransfer.types as a fallback fileItems = types.filter(item => item === 'Files') textItems = types.filter(item => item.startsWith('text/')) } else { return false } + if (!('onDropText' in listeners)) { + return false + } + if (fileItems.length === 0 && !listeners.onDropText) return false if (textItems.length === 0 && !listeners.onDrop) return false if (fileItems.length === 0 && textItems.length === 0) return false diff --git a/test/basic.ts b/test/basic.ts deleted file mode 100644 index 4183a01..0000000 --- a/test/basic.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { test } from '@bicycle-codes/tapzero' - -// writing tests for this would be hard, see the example for interactive testing - -test("Doesn't throw", t => { - -}) - -// test('requiring drag-drop does not throw', t => { -// t.doesNotThrow(() => { -// require('../') -// }) -// t.end() -// }) diff --git a/test/index.html b/test/index.html new file mode 100644 index 0000000..1508669 --- /dev/null +++ b/test/index.html @@ -0,0 +1,13 @@ + + + + + tests + + + + +
+ + + \ No newline at end of file diff --git a/test/index.ts b/test/index.ts new file mode 100644 index 0000000..fbabe59 --- /dev/null +++ b/test/index.ts @@ -0,0 +1,9 @@ +import { test } from '@bicycle-codes/tapzero' +import { dragDrop } from '../src/index.js' + +// writing tests for this would be hard, see the example for interactive testing + +test('Basics', t => { + dragDrop('#drop-target', () => null) + t.ok("Doesn't throw") +}) diff --git a/vite.config.js b/vite.config.js index fbe1de5..1f53db3 100644 --- a/vite.config.js +++ b/vite.config.js @@ -1,5 +1,4 @@ import { defineConfig } from 'vite' -import preact from '@preact/preset-vite' import postcssNesting from 'postcss-nesting' // https://vitejs.dev/config/ @@ -9,13 +8,6 @@ export default defineConfig({ }, root: 'example', plugins: [ - preact({ - devtoolsInProd: false, - prefreshEnabled: true, - babel: { - sourceMaps: 'both' - } - }) ], // https://github.com/vitejs/vite/issues/8644#issuecomment-1159308803 esbuild: { From c2176899530e3a71f021bf193683162944d05966 Mon Sep 17 00:00:00 2001 From: nichoth Date: Sun, 3 Nov 2024 22:36:43 -0800 Subject: [PATCH 6/8] change npm namespace --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 59a1831..5c6e6d5 100644 --- a/package.json +++ b/package.json @@ -1,5 +1,5 @@ { - "name": "@bicycle-codes/drag-drop", + "name": "drag-drop", "description": "HTML5 drag & drop for humans", "version": "7.2.0", "type": "module", From 3f91ff1b437553d05d8f0421f026e4d6c544c59e Mon Sep 17 00:00:00 2001 From: nichoth Date: Sun, 3 Nov 2024 22:55:18 -0800 Subject: [PATCH 7/8] wip --- README.md | 37 +++++++++++++++++++++++++++++-------- package.json | 18 ++++++++++++++++++ src/index.ts | 6 +++--- 3 files changed, 50 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index 652ca55..20e7f19 100644 --- a/README.md +++ b/README.md @@ -10,13 +10,32 @@ [standard-url]: https://standardjs.com

Contents

+ + +- [fork](#fork) + * [HTML5 drag & drop for humans](#html5-drag--drop-for-humans) + * [live demo](#live-demo) + * [features](#features) + * [install](#install) + * [usage](#usage) + * [complete example](#complete-example) + * [get files as buffers](#get-files-as-buffers) + * [detect drag-and-dropped text](#detect-drag-and-dropped-text) + * [detect `dragenter`, `dragover` and `dragleave` events](#detect-dragenter-dragover-and-dragleave-events) + * [remove listeners](#remove-listeners) + * [support pasting files from the clipboard](#support-pasting-files-from-the-clipboard) + * [a note about `file://` urls](#a-note-about-file-urls) + * [license](#license) + + +
## fork This is a fork of [feross/drag-drop](https://github.com/feross/drag-drop). -### HTML5 drag & drop for humans +## HTML5 drag & drop for humans In case you didn't know, the [HTML5 drag and drop API](https://developer.mozilla.org/en-US/docs/Web/API/HTML_Drag_and_Drop_API) @@ -24,11 +43,17 @@ is a [total disaster](http://www.quirksmode.org/blog/archives/2009/09/the_html5_drag.html)! This is an attempt to make the API usable by mere mortals. -### live demo +## install + +```sh +npm i -S drag-drop +``` + +## live demo See [https://instant.io](https://instant.io). -### features +## features - simple API - supports files and directories @@ -36,11 +61,7 @@ See [https://instant.io](https://instant.io). - adds a `drag` class to the drop target on hover, for easy styling! - optionally, get the file(s) as a Buffer (see [buffer](https://github.com/feross/buffer)) -### install - -``` -npm install drag-drop -``` +This package works in the browser via [native ES modules](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Modules) + the [exports field](https://docs.npmjs.com/cli/v10/configuring-npm/package-json#exports) in `package.json`. This package works in the browser with [browserify](https://browserify.org). If you do not use a bundler, you can use the [standalone script](https://bundle.run/drag-drop) directly in a ` - \ No newline at end of file +