Repository navigation
[Dropzone] Leave out the dropped files that do not match accept and the files over max_files, and dispatch dropzone:reject - #4149
Merged
Conversation
Contributor
📊 Packages dist files size differenceThanks for the PR! Here is the difference in size of the packages dist files between the base branch and the PR.
|
||||||||||||
ker0x
force-pushed
the
feat/dropzone-reject
branch
from
October 26, 2026 12:12
3f65c98 to
1886f20
Compare
accept or go over max_files, and dispatch dropzone:rejectaccept and the files over max_files, and dispatch dropzone:reject
ker0x
added a commit
to ker0x/symfony-ux
that referenced
this pull request
Oct 26, 2026
| Q | A | -------------- | --- | Bug fix? | no | New feature? | yes | Deprecations? | no | Documentation? | yes | Issues | Fix symfony#4142 | License | MIT Sixth and last step of the RFC in symfony#4142. Based on symfony#4143, symfony#4145, symfony#4144, symfony#4146, symfony#4148 and symfony#4149, replayed in that order: only the last commit belongs to this PR. The two modes of the controller had grown into two code paths. Single-file mode showed one file with the preview of the form theme, cleared the input when the controller connected, and dispatched `dropzone:clear`; `multiple` mode kept a list, re-added the files already in the input on connect, and never dispatched `dropzone:clear`. Each feature of the RFC had to be written twice, and the modes differed in ways nobody chose: a prevented or rejected selection emptied the drop zone in single-file mode but kept the files in `multiple` mode, and the preview template only worked in `multiple` mode. Single-file mode is now the stored list of files capped at one, and both modes go through the same steps for each selection: the input is put back to the stored files right away (also while `dropzone:pre-add` listeners are processing), the dropped files are checked against `accept`, the `dropzone:pre-add` listeners run, then the files are added (single-file mode: the new file replaces the previous one, and the files over the first one are rejected with `max_files`), the input is updated, the preview is rendered and `dropzone:change` is dispatched. | Case | Before | After | ------------------------------------------------- | ---------------------------------------------- | --- | Single-file mode, controller connects with a file | input emptied, `dropzone:clear` | file kept and shown | `multiple` mode, last file removed | no `dropzone:clear` | `dropzone:clear` | Form reset | drop zone keeps showing the files | cleared, `dropzone:clear` | Single-file mode, selection prevented by a `dropzone:pre-add` listener | drop zone emptied | previous file kept, as in `multiple` mode | Single-file mode, preview template | not used | rendered in the preview, its remove slot clears | `multiple` mode, selection of files already in the list | `dropzone:change` | no event | Drag of text or a link over the drop zone | `dragging` | ignored | Drag over a child of the drop zone in Safari | `dragging` ends (`relatedTarget` is `null`) | `dragging` kept | Image previews | read again on every render, an outdated read can show the image of a replaced file | read once per file, never shown for another file `dropzone:change` keeps its payload (a `File` in single-file mode, a `FileList` in `multiple` mode), to stay compatible: changing it is left for 4.0. The form theme now renders the `dropzone_preview_item_template` block and the `remove_label` value in both modes. The drag state keeps the elements a drag has entered, from `dragenter` and `dragleave`, instead of reading `relatedTarget`, which WebKit does not set, and forgets the elements removed during the drag. `onMultipleChange()` is merged into `onInputChange()`. `src/Dropzone/UPGRADE.md` explains the changes for code listening to `dropzone:clear` on connect or to `dropzone:change`, and for controllers that extend this one. Tests: unit tests for a file kept on connect without `dropzone:clear`, a prevented selection and a pending one in single-file mode, a selection of several files in single-file mode, an outdated image read, a form reset, a prevented form reset, a disconnect during a form reset, the clear button after a failed connect, the preview template in single-file mode, `dropzone:clear` after the last removal in `multiple` mode, a selection that adds no file in `multiple` mode, one read per image, an image read again after a failed read, a drag without files, a drag over a child without `relatedTarget`, and a drag over an element removed during the drag; PHPUnit tests for the template block and the `remove_label` value in single-file mode; Playwright tests for a form reset and the last removal in `multiple` mode. The new tests fail on the base of this PR. Two tests of the base changed: the single-file mode `file chosen` test now picks the file with a writable `files` property like the other tests, as `@testing-library/user-event` makes it read-only while the controller now writes the input on every selection; and the test of a prevented selection after a pending one now expects the previous file to be kept.
… the files over `max_files`, and dispatch `dropzone:reject` | Q | A | -------------- | --- | Bug fix? | no | New feature? | yes | Deprecations? | no | Documentation? | yes | Issues | symfony#4142 | License | MIT Seventh step of the RFC in symfony#4142. The file picker only shows the files that match the `accept` attribute of the input, but drag and drop ignores it, so a drop zone for images happily takes a dropped PDF. There is also no way to limit the number of files in `multiple` mode, and no way for the app to tell the user why a file was not taken. The controller now checks the dropped files against `accept`, in both modes: an extension (`.pdf`) is checked against the file name, a MIME type (`image/png`, `image/*`) against the type of the file. Picked files are not checked again: the picker already filters them with the file types of the operating system, which the browser reports differently (on Windows, a CSV file is often `application/vnd.ms-excel`). `*` and `*/*` accept every file, and a file with no type (HEIC photos, for example) is not rejected because of a MIME type. A new `max_files` option on `DropzoneType` (`null` or a positive integer, `multiple` mode only) limits the number of files, picked or dropped. When a selection goes over the limit, the files that fit are added, in selection order, and the others are left out. Duplicates are skipped as before and do not count. When the Validator component is installed, the option also adds a `Count` constraint, after the field's own constraints, so the limit is checked on the server too. The files left out are announced with a new `dropzone:reject` event, once per reason and after `dropzone:change`: `event.detail.files` holds the files and `event.detail.reason` is `accept` or `max_files`. In single-file mode, a rejected drop puts the file selected before back into the input, so it is kept, and the input only ever holds the file that is shown. | Selection | Before | After | -------------------------------------------------- | --------------- | --- | `accept="image/*"`, a PDF dropped | added | left out, `dropzone:reject` (`accept`), previous file kept | `accept="image/*"`, a file picked with "All files" | added | added | `accept="image/*"`, a file with no type dropped | added | added | `max_files: 2` with 1 file, 3 files picked | not possible | 1 added, 2 left out, `dropzone:reject` (`max_files`) | `max_files: 2`, 3 files submitted | not possible | `Count` constraint violation (with the Validator component) | no `accept`, no `max_files` | added | added The first row is a behavior change for apps whose `accept` is narrower than the files they want to receive by drop. The `accept` check only helps the user: the docs show the `File` constraint to validate the type on the server. Tests: unit tests for dropped files (extensions, MIME types, wildcards, `*` and `*/*`, a file with no type, the previous file kept in single mode, a drop of several files in single mode), picked files that are not checked, a drop without `change` that does not affect the next pick, the `max_files` limit (removing a file makes room again), duplicates, both reasons in one selection and their order after `dropzone:change`; PHPUnit tests for the `max_files` value, a `max_files` without `multiple` or of 0, and the `Count` constraint with and without the Validator component, including a submission over the limit; Playwright tests that replay a drop (Playwright cannot drop files: a drop event, then the files and `change` in the same task) and submit only the kept files, a picked file that is not checked, and the previous file kept in single mode, on Chrome lowest, Chrome latest and Firefox latest. The new tests fail on the base branch, except the ones checking that matching files, no `accept`, duplicates and the absence of the Validator component behave as before. The unit tests now stop the Stimulus applications after each test, with the same code as symfony#4146: the controllers of earlier tests otherwise connect to the next test's markup, and the `max_files` test failed only in the full run. `symfony/validator` is added to `require-dev` for the `Count` constraint tests.
Kocal
force-pushed
the
feat/dropzone-reject
branch
from
November 2, 2026 22:12
1886f20 to
bf81cb9
Compare
Kocal
approved these changes
Nov 2, 2026
Member
|
Thank you @ker0x. |
Kocal
pushed a commit
to ker0x/symfony-ux
that referenced
this pull request
Nov 2, 2026
| Q | A | -------------- | --- | Bug fix? | no | New feature? | yes | Deprecations? | no | Documentation? | yes | Issues | Fix symfony#4142 | License | MIT Sixth and last step of the RFC in symfony#4142. Based on symfony#4143, symfony#4145, symfony#4144, symfony#4146, symfony#4148 and symfony#4149, replayed in that order: only the last commit belongs to this PR. The two modes of the controller had grown into two code paths. Single-file mode showed one file with the preview of the form theme, cleared the input when the controller connected, and dispatched `dropzone:clear`; `multiple` mode kept a list, re-added the files already in the input on connect, and never dispatched `dropzone:clear`. Each feature of the RFC had to be written twice, and the modes differed in ways nobody chose: a prevented or rejected selection emptied the drop zone in single-file mode but kept the files in `multiple` mode, and the preview template only worked in `multiple` mode. Single-file mode is now the stored list of files capped at one, and both modes go through the same steps for each selection: the input is put back to the stored files right away (also while `dropzone:pre-add` listeners are processing), the dropped files are checked against `accept`, the `dropzone:pre-add` listeners run, then the files are added (single-file mode: the new file replaces the previous one, and the files over the first one are rejected with `max_files`), the input is updated, the preview is rendered and `dropzone:change` is dispatched. | Case | Before | After | ------------------------------------------------- | ---------------------------------------------- | --- | Single-file mode, controller connects with a file | input emptied, `dropzone:clear` | file kept and shown | `multiple` mode, last file removed | no `dropzone:clear` | `dropzone:clear` | Form reset | drop zone keeps showing the files | cleared, `dropzone:clear` | Single-file mode, selection prevented by a `dropzone:pre-add` listener | drop zone emptied | previous file kept, as in `multiple` mode | Single-file mode, preview template | not used | rendered in the preview, its remove slot clears | `multiple` mode, selection of files already in the list | `dropzone:change` | no event | Drag of text or a link over the drop zone | `dragging` | ignored | Drag over a child of the drop zone in Safari | `dragging` ends (`relatedTarget` is `null`) | `dragging` kept | Image previews | read again on every render, an outdated read can show the image of a replaced file | read once per file, never shown for another file `dropzone:change` keeps its payload (a `File` in single-file mode, a `FileList` in `multiple` mode), to stay compatible: changing it is left for 4.0. The form theme now renders the `dropzone_preview_item_template` block and the `remove_label` value in both modes. The drag state keeps the elements a drag has entered, from `dragenter` and `dragleave`, instead of reading `relatedTarget`, which WebKit does not set, and forgets the elements removed during the drag. `onMultipleChange()` is merged into `onInputChange()`. `src/Dropzone/UPGRADE.md` explains the changes for code listening to `dropzone:clear` on connect or to `dropzone:change`, and for controllers that extend this one. Tests: unit tests for a file kept on connect without `dropzone:clear`, a prevented selection and a pending one in single-file mode, a selection of several files in single-file mode, an outdated image read, a form reset, a prevented form reset, a disconnect during a form reset, the clear button after a failed connect, the preview template in single-file mode, `dropzone:clear` after the last removal in `multiple` mode, a selection that adds no file in `multiple` mode, one read per image, an image read again after a failed read, a drag without files, a drag over a child without `relatedTarget`, and a drag over an element removed during the drag; PHPUnit tests for the template block and the `remove_label` value in single-file mode; Playwright tests for a form reset and the last removal in `multiple` mode. The new tests fail on the base of this PR. Two tests of the base changed: the single-file mode `file chosen` test now picks the file with a writable `files` property like the other tests, as `@testing-library/user-event` makes it read-only while the controller now writes the input on every selection; and the test of a prevented selection after a pending one now expects the previous file to be kept.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Seventh step of the RFC in #4142.
The file picker only shows the files that match the
acceptattribute of the input, but drag and drop ignores it, so a drop zone for images happily takes a dropped PDF. There is also no way to limit the number of files inmultiplemode, and no way for the app to tell the user why a file was not taken.The controller now checks the dropped files against
accept, in both modes: an extension (.pdf) is checked against the file name, a MIME type (image/png,image/*) against the type of the file. Picked files are not checked again: the picker already filters them with the file types of the operating system, which the browser reports differently (on Windows, a CSV file is oftenapplication/vnd.ms-excel).*and*/*accept every file, and a file with no type (HEIC photos, for example) is not rejected because of a MIME type.A new
max_filesoption onDropzoneType(nullor a positive integer,multiplemode only) limits the number of files, picked or dropped. When a selection goes over the limit, the files that fit are added, in selection order, and the others are left out. Duplicates are skipped as before and do not count. When the Validator component is installed, the option also adds aCountconstraint, after the field's own constraints, so the limit is checked on the server too.The files left out are announced with a new
dropzone:rejectevent, once per reason and afterdropzone:change:event.detail.filesholds the files andevent.detail.reasonisacceptormax_files. In single-file mode, a rejected drop puts the file selected before back into the input, so it is kept, and the input only ever holds the file that is shown.accept="image/*", a PDF droppeddropzone:reject(accept), previous file keptaccept="image/*", a file picked with "All files"accept="image/*", a file with no type droppedmax_files: 2with 1 file, 3 files pickeddropzone:reject(max_files)max_files: 2, 3 files submittedCountconstraint violation (with the Validator component)accept, nomax_filesThe first row is a behavior change for apps whose
acceptis narrower than the files they want to receive by drop. Theacceptcheck only helps the user: the docs show theFileconstraint to validate the type on the server.Tests: unit tests for dropped files (extensions, MIME types, wildcards,
*and*/*, a file with no type, the previous file kept in single mode, a drop of several files in single mode), picked files that are not checked, a drop withoutchangethat does not affect the next pick, themax_fileslimit (removing a file makes room again), duplicates, both reasons in one selection and their order afterdropzone:change; PHPUnit tests for themax_filesvalue, amax_fileswithoutmultipleor of 0, and theCountconstraint with and without the Validator component, including a submission over the limit; Playwright tests that replay a drop (Playwright cannot drop files: a drop event, then the files andchangein the same task) and submit only the kept files, a picked file that is not checked, and the previous file kept in single mode, on Chrome lowest, Chrome latest and Firefox latest. The new tests fail on the base branch, except the ones checking that matching files, noaccept, duplicates and the absence of the Validator component behave as before.symfony/validatoris added torequire-devfor theCountconstraint tests.