-
Notifications
You must be signed in to change notification settings - Fork 336
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Improve handling of visual feedback for the drop zone #5605
Improve handling of visual feedback for the drop zone #5605
Conversation
📋 StatsFile sizes
Modules
View stats and visualisations on the review app Action run for 5b9ca62 |
JavaScript changes to npm packagediff --git a/packages/govuk-frontend/dist/govuk/govuk-frontend.min.js b/packages/govuk-frontend/dist/govuk/govuk-frontend.min.js
index 341ec36d6..5a65848c9 100644
--- a/packages/govuk-frontend/dist/govuk/govuk-frontend.min.js
+++ b/packages/govuk-frontend/dist/govuk/govuk-frontend.min.js
@@ -759,7 +759,21 @@ class FileUpload extends ConfigurableComponent {
const s = document.createElement("button");
s.className = "govuk-button govuk-button--secondary govuk-file-upload__button", s.type = "button", s.innerText = this.i18n.t("selectFilesButton"), s.addEventListener("click", this.onClick.bind(this));
const i = document.createElement("span");
- i.className = "govuk-body govuk-file-upload__status", i.innerText = this.i18n.t("filesSelectedDefault"), i.setAttribute("role", "status"), n.insertAdjacentElement("beforeend", s), n.insertAdjacentElement("beforeend", i), this.$root.insertAdjacentElement("afterend", n), n.insertAdjacentElement("afterbegin", this.$root), this.$wrapper = n, this.$button = s, this.$status = i, this.$root.setAttribute("tabindex", "-1"), this.updateDisabledState(), this.observeDisabledState(), this.$root.addEventListener("change", this.onChange.bind(this)), this.$wrapper.addEventListener("drop", this.onDragLeaveOrDrop.bind(this)), document.addEventListener("dragenter", this.onDragEnter.bind(this)), document.addEventListener("dragleave", this.onDragLeaveOrDrop.bind(this))
+ i.className = "govuk-body govuk-file-upload__status", i.innerText = this.i18n.t("filesSelectedDefault"), i.setAttribute("role", "status"), n.insertAdjacentElement("beforeend", s), n.insertAdjacentElement("beforeend", i), this.$root.insertAdjacentElement("afterend", n), n.insertAdjacentElement("afterbegin", this.$root), this.$wrapper = n, this.$button = s, this.$status = i, this.$root.setAttribute("tabindex", "-1"), this.updateDisabledState(), this.observeDisabledState(), this.$root.addEventListener("change", this.onChange.bind(this)), this.$announcements = document.createElement("span"), this.$announcements.classList.add("govuk-file-upload-announcements"), this.$announcements.classList.add("govuk-visually-hidden"), this.$announcements.setAttribute("aria-live", "assertive"), this.$wrapper.insertAdjacentElement("afterend", this.$announcements), this.$wrapper.addEventListener("drop", this.hideDropZone.bind(this)), document.addEventListener("dragenter", this.updateDropzoneVisibility.bind(this)), document.addEventListener("dragenter", (() => {
+ this.enteredAnotherElement = !0
+ })), document.addEventListener("dragleave", (() => {
+ this.enteredAnotherElement || this.hideDropZone(), this.enteredAnotherElement = !1
+ }))
+ }
+ updateDropzoneVisibility(t) {
+ t.target instanceof Node && (this.$wrapper.contains(t.target) ? t.dataTransfer && function(t) {
+ const e = 0 === t.types.length,
+ n = t.types.some((t => "Files" === t));
+ return e || n
+ }(t.dataTransfer) && (this.$wrapper.classList.contains("govuk-file-upload-wrapper--show-dropzone") || (this.$wrapper.classList.add("govuk-file-upload-wrapper--show-dropzone"), this.$announcements.innerText = this.i18n.t("dropZoneEntered"))) : this.$wrapper.classList.contains("govuk-file-upload-wrapper--show-dropzone") && this.hideDropZone())
+ }
+ hideDropZone() {
+ this.$wrapper.classList.remove("govuk-file-upload-wrapper--show-dropzone"), this.$announcements.innerText = this.i18n.t("dropZoneLeft")
}
onChange() {
const t = this.$root.files.length;
@@ -778,12 +792,6 @@ class FileUpload extends ConfigurableComponent {
onClick() {
this.$label.click()
}
- onDragEnter(t) {
- console.log(t), this.$wrapper.classList.add("govuk-file-upload-wrapper--show-dropzone")
- }
- onDragLeaveOrDrop() {
- this.$wrapper.classList.remove("govuk-file-upload-wrapper--show-dropzone")
- }
observeDisabledState() {
new MutationObserver((t => {
for (const e of t) console.log("mutation", e), "attributes" === e.type && "disabled" === e.attributeName && this.updateDisabledState()
@@ -802,7 +810,9 @@ FileUpload.moduleName = "govuk-file-upload", FileUpload.defaults = Object.freeze
filesSelected: {
one: "%{count} file chosen",
other: "%{count} files chosen"
- }
+ },
+ dropZoneEntered: "Entered drop zone",
+ dropZoneLeft: "Left drop zone"
}
}), FileUpload.schema = Object.freeze({
properties: {
Action run for 5b9ca62 |
Other changes to npm packagediff --git a/packages/govuk-frontend/dist/govuk/all.bundle.js b/packages/govuk-frontend/dist/govuk/all.bundle.js
index 63161d0ec..11771add6 100644
--- a/packages/govuk-frontend/dist/govuk/all.bundle.js
+++ b/packages/govuk-frontend/dist/govuk/all.bundle.js
@@ -1700,9 +1700,48 @@
this.updateDisabledState();
this.observeDisabledState();
this.$root.addEventListener('change', this.onChange.bind(this));
- this.$wrapper.addEventListener('drop', this.onDragLeaveOrDrop.bind(this));
- document.addEventListener('dragenter', this.onDragEnter.bind(this));
- document.addEventListener('dragleave', this.onDragLeaveOrDrop.bind(this));
+ this.$announcements = document.createElement('span');
+ this.$announcements.classList.add('govuk-file-upload-announcements');
+ this.$announcements.classList.add('govuk-visually-hidden');
+ this.$announcements.setAttribute('aria-live', 'assertive');
+ this.$wrapper.insertAdjacentElement('afterend', this.$announcements);
+ this.$wrapper.addEventListener('drop', this.hideDropZone.bind(this));
+ document.addEventListener('dragenter', this.updateDropzoneVisibility.bind(this));
+ document.addEventListener('dragenter', () => {
+ this.enteredAnotherElement = true;
+ });
+ document.addEventListener('dragleave', () => {
+ if (!this.enteredAnotherElement) {
+ this.hideDropZone();
+ }
+ this.enteredAnotherElement = false;
+ });
+ }
+
+ /**
+ * Updates the visibility of the dropzone as users enters the various elements on the page
+ *
+ * @param {DragEvent} event - The `dragenter` event
+ */
+ updateDropzoneVisibility(event) {
+ if (event.target instanceof Node) {
+ if (this.$wrapper.contains(event.target)) {
+ if (event.dataTransfer && isContainingFiles(event.dataTransfer)) {
+ if (!this.$wrapper.classList.contains('govuk-file-upload-wrapper--show-dropzone')) {
+ this.$wrapper.classList.add('govuk-file-upload-wrapper--show-dropzone');
+ this.$announcements.innerText = this.i18n.t('dropZoneEntered');
+ }
+ }
+ } else {
+ if (this.$wrapper.classList.contains('govuk-file-upload-wrapper--show-dropzone')) {
+ this.hideDropZone();
+ }
+ }
+ }
+ }
+ hideDropZone() {
+ this.$wrapper.classList.remove('govuk-file-upload-wrapper--show-dropzone');
+ this.$announcements.innerText = this.i18n.t('dropZoneLeft');
}
onChange() {
const fileCount = this.$root.files.length;
@@ -1729,20 +1768,6 @@
onClick() {
this.$label.click();
}
-
- /**
- * When a file is dragged over the container, show a visual indicator that a
- * file can be dropped here.
- *
- * @param {DragEvent} event - the drag event
- */
- onDragEnter(event) {
- console.log(event);
- this.$wrapper.classList.add('govuk-file-upload-wrapper--show-dropzone');
- }
- onDragLeaveOrDrop() {
- this.$wrapper.classList.remove('govuk-file-upload-wrapper--show-dropzone');
- }
observeDisabledState() {
const observer = new MutationObserver(mutationList => {
for (const mutation of mutationList) {
@@ -1760,6 +1785,31 @@
this.$button.disabled = this.$root.disabled;
}
}
+ FileUpload.moduleName = 'govuk-file-upload';
+ FileUpload.defaults = Object.freeze({
+ i18n: {
+ selectFilesButton: 'Choose file',
+ filesSelectedDefault: 'No file chosen',
+ filesSelected: {
+ one: '%{count} file chosen',
+ other: '%{count} files chosen'
+ },
+ dropZoneEntered: 'Entered drop zone',
+ dropZoneLeft: 'Left drop zone'
+ }
+ });
+ FileUpload.schema = Object.freeze({
+ properties: {
+ i18n: {
+ type: 'object'
+ }
+ }
+ });
+ function isContainingFiles(dataTransfer) {
+ const hasNoTypesInfo = dataTransfer.types.length === 0;
+ const isDraggingFiles = dataTransfer.types.some(type => type === 'Files');
+ return hasNoTypesInfo || isDraggingFiles;
+ }
/**
* @typedef {HTMLInputElement & {files: FileList}} HTMLFileInputElement
@@ -1783,30 +1833,16 @@
* @property {string} [selectFiles] - Text of button that opens file browser
* @property {TranslationPluralForms} [filesSelected] - Text indicating how
* many files have been selected
+ * @property {string} [dropZoneEntered] - Text announced to assistive technology
+ * when users entered the drop zone while dragging
+ * @property {string} [dropZoneLeft] - Text announced to assistive technology
+ * when users left the drop zone while dragging
*/
/**
* @typedef {import('../../common/configuration.mjs').Schema} Schema
* @typedef {import('../../i18n.mjs').TranslationPluralForms} TranslationPluralForms
*/
- FileUpload.moduleName = 'govuk-file-upload';
- FileUpload.defaults = Object.freeze({
- i18n: {
- selectFilesButton: 'Choose file',
- filesSelectedDefault: 'No file chosen',
- filesSelected: {
- one: '%{count} file chosen',
- other: '%{count} files chosen'
- }
- }
- });
- FileUpload.schema = Object.freeze({
- properties: {
- i18n: {
- type: 'object'
- }
- }
- });
/**
* Header component
diff --git a/packages/govuk-frontend/dist/govuk/all.bundle.mjs b/packages/govuk-frontend/dist/govuk/all.bundle.mjs
index 34f9a09f1..ebc802c20 100644
--- a/packages/govuk-frontend/dist/govuk/all.bundle.mjs
+++ b/packages/govuk-frontend/dist/govuk/all.bundle.mjs
@@ -1694,9 +1694,48 @@ class FileUpload extends ConfigurableComponent {
this.updateDisabledState();
this.observeDisabledState();
this.$root.addEventListener('change', this.onChange.bind(this));
- this.$wrapper.addEventListener('drop', this.onDragLeaveOrDrop.bind(this));
- document.addEventListener('dragenter', this.onDragEnter.bind(this));
- document.addEventListener('dragleave', this.onDragLeaveOrDrop.bind(this));
+ this.$announcements = document.createElement('span');
+ this.$announcements.classList.add('govuk-file-upload-announcements');
+ this.$announcements.classList.add('govuk-visually-hidden');
+ this.$announcements.setAttribute('aria-live', 'assertive');
+ this.$wrapper.insertAdjacentElement('afterend', this.$announcements);
+ this.$wrapper.addEventListener('drop', this.hideDropZone.bind(this));
+ document.addEventListener('dragenter', this.updateDropzoneVisibility.bind(this));
+ document.addEventListener('dragenter', () => {
+ this.enteredAnotherElement = true;
+ });
+ document.addEventListener('dragleave', () => {
+ if (!this.enteredAnotherElement) {
+ this.hideDropZone();
+ }
+ this.enteredAnotherElement = false;
+ });
+ }
+
+ /**
+ * Updates the visibility of the dropzone as users enters the various elements on the page
+ *
+ * @param {DragEvent} event - The `dragenter` event
+ */
+ updateDropzoneVisibility(event) {
+ if (event.target instanceof Node) {
+ if (this.$wrapper.contains(event.target)) {
+ if (event.dataTransfer && isContainingFiles(event.dataTransfer)) {
+ if (!this.$wrapper.classList.contains('govuk-file-upload-wrapper--show-dropzone')) {
+ this.$wrapper.classList.add('govuk-file-upload-wrapper--show-dropzone');
+ this.$announcements.innerText = this.i18n.t('dropZoneEntered');
+ }
+ }
+ } else {
+ if (this.$wrapper.classList.contains('govuk-file-upload-wrapper--show-dropzone')) {
+ this.hideDropZone();
+ }
+ }
+ }
+ }
+ hideDropZone() {
+ this.$wrapper.classList.remove('govuk-file-upload-wrapper--show-dropzone');
+ this.$announcements.innerText = this.i18n.t('dropZoneLeft');
}
onChange() {
const fileCount = this.$root.files.length;
@@ -1723,20 +1762,6 @@ class FileUpload extends ConfigurableComponent {
onClick() {
this.$label.click();
}
-
- /**
- * When a file is dragged over the container, show a visual indicator that a
- * file can be dropped here.
- *
- * @param {DragEvent} event - the drag event
- */
- onDragEnter(event) {
- console.log(event);
- this.$wrapper.classList.add('govuk-file-upload-wrapper--show-dropzone');
- }
- onDragLeaveOrDrop() {
- this.$wrapper.classList.remove('govuk-file-upload-wrapper--show-dropzone');
- }
observeDisabledState() {
const observer = new MutationObserver(mutationList => {
for (const mutation of mutationList) {
@@ -1754,6 +1779,31 @@ class FileUpload extends ConfigurableComponent {
this.$button.disabled = this.$root.disabled;
}
}
+FileUpload.moduleName = 'govuk-file-upload';
+FileUpload.defaults = Object.freeze({
+ i18n: {
+ selectFilesButton: 'Choose file',
+ filesSelectedDefault: 'No file chosen',
+ filesSelected: {
+ one: '%{count} file chosen',
+ other: '%{count} files chosen'
+ },
+ dropZoneEntered: 'Entered drop zone',
+ dropZoneLeft: 'Left drop zone'
+ }
+});
+FileUpload.schema = Object.freeze({
+ properties: {
+ i18n: {
+ type: 'object'
+ }
+ }
+});
+function isContainingFiles(dataTransfer) {
+ const hasNoTypesInfo = dataTransfer.types.length === 0;
+ const isDraggingFiles = dataTransfer.types.some(type => type === 'Files');
+ return hasNoTypesInfo || isDraggingFiles;
+}
/**
* @typedef {HTMLInputElement & {files: FileList}} HTMLFileInputElement
@@ -1777,30 +1827,16 @@ class FileUpload extends ConfigurableComponent {
* @property {string} [selectFiles] - Text of button that opens file browser
* @property {TranslationPluralForms} [filesSelected] - Text indicating how
* many files have been selected
+ * @property {string} [dropZoneEntered] - Text announced to assistive technology
+ * when users entered the drop zone while dragging
+ * @property {string} [dropZoneLeft] - Text announced to assistive technology
+ * when users left the drop zone while dragging
*/
/**
* @typedef {import('../../common/configuration.mjs').Schema} Schema
* @typedef {import('../../i18n.mjs').TranslationPluralForms} TranslationPluralForms
*/
-FileUpload.moduleName = 'govuk-file-upload';
-FileUpload.defaults = Object.freeze({
- i18n: {
- selectFilesButton: 'Choose file',
- filesSelectedDefault: 'No file chosen',
- filesSelected: {
- one: '%{count} file chosen',
- other: '%{count} files chosen'
- }
- }
-});
-FileUpload.schema = Object.freeze({
- properties: {
- i18n: {
- type: 'object'
- }
- }
-});
/**
* Header component
diff --git a/packages/govuk-frontend/dist/govuk/components/file-upload/file-upload.bundle.js b/packages/govuk-frontend/dist/govuk/components/file-upload/file-upload.bundle.js
index 87cf44235..11a85cff2 100644
--- a/packages/govuk-frontend/dist/govuk/components/file-upload/file-upload.bundle.js
+++ b/packages/govuk-frontend/dist/govuk/components/file-upload/file-upload.bundle.js
@@ -527,9 +527,48 @@
this.updateDisabledState();
this.observeDisabledState();
this.$root.addEventListener('change', this.onChange.bind(this));
- this.$wrapper.addEventListener('drop', this.onDragLeaveOrDrop.bind(this));
- document.addEventListener('dragenter', this.onDragEnter.bind(this));
- document.addEventListener('dragleave', this.onDragLeaveOrDrop.bind(this));
+ this.$announcements = document.createElement('span');
+ this.$announcements.classList.add('govuk-file-upload-announcements');
+ this.$announcements.classList.add('govuk-visually-hidden');
+ this.$announcements.setAttribute('aria-live', 'assertive');
+ this.$wrapper.insertAdjacentElement('afterend', this.$announcements);
+ this.$wrapper.addEventListener('drop', this.hideDropZone.bind(this));
+ document.addEventListener('dragenter', this.updateDropzoneVisibility.bind(this));
+ document.addEventListener('dragenter', () => {
+ this.enteredAnotherElement = true;
+ });
+ document.addEventListener('dragleave', () => {
+ if (!this.enteredAnotherElement) {
+ this.hideDropZone();
+ }
+ this.enteredAnotherElement = false;
+ });
+ }
+
+ /**
+ * Updates the visibility of the dropzone as users enters the various elements on the page
+ *
+ * @param {DragEvent} event - The `dragenter` event
+ */
+ updateDropzoneVisibility(event) {
+ if (event.target instanceof Node) {
+ if (this.$wrapper.contains(event.target)) {
+ if (event.dataTransfer && isContainingFiles(event.dataTransfer)) {
+ if (!this.$wrapper.classList.contains('govuk-file-upload-wrapper--show-dropzone')) {
+ this.$wrapper.classList.add('govuk-file-upload-wrapper--show-dropzone');
+ this.$announcements.innerText = this.i18n.t('dropZoneEntered');
+ }
+ }
+ } else {
+ if (this.$wrapper.classList.contains('govuk-file-upload-wrapper--show-dropzone')) {
+ this.hideDropZone();
+ }
+ }
+ }
+ }
+ hideDropZone() {
+ this.$wrapper.classList.remove('govuk-file-upload-wrapper--show-dropzone');
+ this.$announcements.innerText = this.i18n.t('dropZoneLeft');
}
onChange() {
const fileCount = this.$root.files.length;
@@ -556,20 +595,6 @@
onClick() {
this.$label.click();
}
-
- /**
- * When a file is dragged over the container, show a visual indicator that a
- * file can be dropped here.
- *
- * @param {DragEvent} event - the drag event
- */
- onDragEnter(event) {
- console.log(event);
- this.$wrapper.classList.add('govuk-file-upload-wrapper--show-dropzone');
- }
- onDragLeaveOrDrop() {
- this.$wrapper.classList.remove('govuk-file-upload-wrapper--show-dropzone');
- }
observeDisabledState() {
const observer = new MutationObserver(mutationList => {
for (const mutation of mutationList) {
@@ -587,6 +612,31 @@
this.$button.disabled = this.$root.disabled;
}
}
+ FileUpload.moduleName = 'govuk-file-upload';
+ FileUpload.defaults = Object.freeze({
+ i18n: {
+ selectFilesButton: 'Choose file',
+ filesSelectedDefault: 'No file chosen',
+ filesSelected: {
+ one: '%{count} file chosen',
+ other: '%{count} files chosen'
+ },
+ dropZoneEntered: 'Entered drop zone',
+ dropZoneLeft: 'Left drop zone'
+ }
+ });
+ FileUpload.schema = Object.freeze({
+ properties: {
+ i18n: {
+ type: 'object'
+ }
+ }
+ });
+ function isContainingFiles(dataTransfer) {
+ const hasNoTypesInfo = dataTransfer.types.length === 0;
+ const isDraggingFiles = dataTransfer.types.some(type => type === 'Files');
+ return hasNoTypesInfo || isDraggingFiles;
+ }
/**
* @typedef {HTMLInputElement & {files: FileList}} HTMLFileInputElement
@@ -610,30 +660,16 @@
* @property {string} [selectFiles] - Text of button that opens file browser
* @property {TranslationPluralForms} [filesSelected] - Text indicating how
* many files have been selected
+ * @property {string} [dropZoneEntered] - Text announced to assistive technology
+ * when users entered the drop zone while dragging
+ * @property {string} [dropZoneLeft] - Text announced to assistive technology
+ * when users left the drop zone while dragging
*/
/**
* @typedef {import('../../common/configuration.mjs').Schema} Schema
* @typedef {import('../../i18n.mjs').TranslationPluralForms} TranslationPluralForms
*/
- FileUpload.moduleName = 'govuk-file-upload';
- FileUpload.defaults = Object.freeze({
- i18n: {
- selectFilesButton: 'Choose file',
- filesSelectedDefault: 'No file chosen',
- filesSelected: {
- one: '%{count} file chosen',
- other: '%{count} files chosen'
- }
- }
- });
- FileUpload.schema = Object.freeze({
- properties: {
- i18n: {
- type: 'object'
- }
- }
- });
exports.FileUpload = FileUpload;
diff --git a/packages/govuk-frontend/dist/govuk/components/file-upload/file-upload.bundle.mjs b/packages/govuk-frontend/dist/govuk/components/file-upload/file-upload.bundle.mjs
index 253fa0afd..b21914e73 100644
--- a/packages/govuk-frontend/dist/govuk/components/file-upload/file-upload.bundle.mjs
+++ b/packages/govuk-frontend/dist/govuk/components/file-upload/file-upload.bundle.mjs
@@ -521,9 +521,48 @@ class FileUpload extends ConfigurableComponent {
this.updateDisabledState();
this.observeDisabledState();
this.$root.addEventListener('change', this.onChange.bind(this));
- this.$wrapper.addEventListener('drop', this.onDragLeaveOrDrop.bind(this));
- document.addEventListener('dragenter', this.onDragEnter.bind(this));
- document.addEventListener('dragleave', this.onDragLeaveOrDrop.bind(this));
+ this.$announcements = document.createElement('span');
+ this.$announcements.classList.add('govuk-file-upload-announcements');
+ this.$announcements.classList.add('govuk-visually-hidden');
+ this.$announcements.setAttribute('aria-live', 'assertive');
+ this.$wrapper.insertAdjacentElement('afterend', this.$announcements);
+ this.$wrapper.addEventListener('drop', this.hideDropZone.bind(this));
+ document.addEventListener('dragenter', this.updateDropzoneVisibility.bind(this));
+ document.addEventListener('dragenter', () => {
+ this.enteredAnotherElement = true;
+ });
+ document.addEventListener('dragleave', () => {
+ if (!this.enteredAnotherElement) {
+ this.hideDropZone();
+ }
+ this.enteredAnotherElement = false;
+ });
+ }
+
+ /**
+ * Updates the visibility of the dropzone as users enters the various elements on the page
+ *
+ * @param {DragEvent} event - The `dragenter` event
+ */
+ updateDropzoneVisibility(event) {
+ if (event.target instanceof Node) {
+ if (this.$wrapper.contains(event.target)) {
+ if (event.dataTransfer && isContainingFiles(event.dataTransfer)) {
+ if (!this.$wrapper.classList.contains('govuk-file-upload-wrapper--show-dropzone')) {
+ this.$wrapper.classList.add('govuk-file-upload-wrapper--show-dropzone');
+ this.$announcements.innerText = this.i18n.t('dropZoneEntered');
+ }
+ }
+ } else {
+ if (this.$wrapper.classList.contains('govuk-file-upload-wrapper--show-dropzone')) {
+ this.hideDropZone();
+ }
+ }
+ }
+ }
+ hideDropZone() {
+ this.$wrapper.classList.remove('govuk-file-upload-wrapper--show-dropzone');
+ this.$announcements.innerText = this.i18n.t('dropZoneLeft');
}
onChange() {
const fileCount = this.$root.files.length;
@@ -550,20 +589,6 @@ class FileUpload extends ConfigurableComponent {
onClick() {
this.$label.click();
}
-
- /**
- * When a file is dragged over the container, show a visual indicator that a
- * file can be dropped here.
- *
- * @param {DragEvent} event - the drag event
- */
- onDragEnter(event) {
- console.log(event);
- this.$wrapper.classList.add('govuk-file-upload-wrapper--show-dropzone');
- }
- onDragLeaveOrDrop() {
- this.$wrapper.classList.remove('govuk-file-upload-wrapper--show-dropzone');
- }
observeDisabledState() {
const observer = new MutationObserver(mutationList => {
for (const mutation of mutationList) {
@@ -581,6 +606,31 @@ class FileUpload extends ConfigurableComponent {
this.$button.disabled = this.$root.disabled;
}
}
+FileUpload.moduleName = 'govuk-file-upload';
+FileUpload.defaults = Object.freeze({
+ i18n: {
+ selectFilesButton: 'Choose file',
+ filesSelectedDefault: 'No file chosen',
+ filesSelected: {
+ one: '%{count} file chosen',
+ other: '%{count} files chosen'
+ },
+ dropZoneEntered: 'Entered drop zone',
+ dropZoneLeft: 'Left drop zone'
+ }
+});
+FileUpload.schema = Object.freeze({
+ properties: {
+ i18n: {
+ type: 'object'
+ }
+ }
+});
+function isContainingFiles(dataTransfer) {
+ const hasNoTypesInfo = dataTransfer.types.length === 0;
+ const isDraggingFiles = dataTransfer.types.some(type => type === 'Files');
+ return hasNoTypesInfo || isDraggingFiles;
+}
/**
* @typedef {HTMLInputElement & {files: FileList}} HTMLFileInputElement
@@ -604,30 +654,16 @@ class FileUpload extends ConfigurableComponent {
* @property {string} [selectFiles] - Text of button that opens file browser
* @property {TranslationPluralForms} [filesSelected] - Text indicating how
* many files have been selected
+ * @property {string} [dropZoneEntered] - Text announced to assistive technology
+ * when users entered the drop zone while dragging
+ * @property {string} [dropZoneLeft] - Text announced to assistive technology
+ * when users left the drop zone while dragging
*/
/**
* @typedef {import('../../common/configuration.mjs').Schema} Schema
* @typedef {import('../../i18n.mjs').TranslationPluralForms} TranslationPluralForms
*/
-FileUpload.moduleName = 'govuk-file-upload';
-FileUpload.defaults = Object.freeze({
- i18n: {
- selectFilesButton: 'Choose file',
- filesSelectedDefault: 'No file chosen',
- filesSelected: {
- one: '%{count} file chosen',
- other: '%{count} files chosen'
- }
- }
-});
-FileUpload.schema = Object.freeze({
- properties: {
- i18n: {
- type: 'object'
- }
- }
-});
export { FileUpload };
//# sourceMappingURL=file-upload.bundle.mjs.map
diff --git a/packages/govuk-frontend/dist/govuk/components/file-upload/file-upload.mjs b/packages/govuk-frontend/dist/govuk/components/file-upload/file-upload.mjs
index 1ddbc3dc5..c859f2ebd 100644
--- a/packages/govuk-frontend/dist/govuk/components/file-upload/file-upload.mjs
+++ b/packages/govuk-frontend/dist/govuk/components/file-upload/file-upload.mjs
@@ -50,9 +50,48 @@ class FileUpload extends ConfigurableComponent {
this.updateDisabledState();
this.observeDisabledState();
this.$root.addEventListener('change', this.onChange.bind(this));
- this.$wrapper.addEventListener('drop', this.onDragLeaveOrDrop.bind(this));
- document.addEventListener('dragenter', this.onDragEnter.bind(this));
- document.addEventListener('dragleave', this.onDragLeaveOrDrop.bind(this));
+ this.$announcements = document.createElement('span');
+ this.$announcements.classList.add('govuk-file-upload-announcements');
+ this.$announcements.classList.add('govuk-visually-hidden');
+ this.$announcements.setAttribute('aria-live', 'assertive');
+ this.$wrapper.insertAdjacentElement('afterend', this.$announcements);
+ this.$wrapper.addEventListener('drop', this.hideDropZone.bind(this));
+ document.addEventListener('dragenter', this.updateDropzoneVisibility.bind(this));
+ document.addEventListener('dragenter', () => {
+ this.enteredAnotherElement = true;
+ });
+ document.addEventListener('dragleave', () => {
+ if (!this.enteredAnotherElement) {
+ this.hideDropZone();
+ }
+ this.enteredAnotherElement = false;
+ });
+ }
+
+ /**
+ * Updates the visibility of the dropzone as users enters the various elements on the page
+ *
+ * @param {DragEvent} event - The `dragenter` event
+ */
+ updateDropzoneVisibility(event) {
+ if (event.target instanceof Node) {
+ if (this.$wrapper.contains(event.target)) {
+ if (event.dataTransfer && isContainingFiles(event.dataTransfer)) {
+ if (!this.$wrapper.classList.contains('govuk-file-upload-wrapper--show-dropzone')) {
+ this.$wrapper.classList.add('govuk-file-upload-wrapper--show-dropzone');
+ this.$announcements.innerText = this.i18n.t('dropZoneEntered');
+ }
+ }
+ } else {
+ if (this.$wrapper.classList.contains('govuk-file-upload-wrapper--show-dropzone')) {
+ this.hideDropZone();
+ }
+ }
+ }
+ }
+ hideDropZone() {
+ this.$wrapper.classList.remove('govuk-file-upload-wrapper--show-dropzone');
+ this.$announcements.innerText = this.i18n.t('dropZoneLeft');
}
onChange() {
const fileCount = this.$root.files.length;
@@ -79,20 +118,6 @@ class FileUpload extends ConfigurableComponent {
onClick() {
this.$label.click();
}
-
- /**
- * When a file is dragged over the container, show a visual indicator that a
- * file can be dropped here.
- *
- * @param {DragEvent} event - the drag event
- */
- onDragEnter(event) {
- console.log(event);
- this.$wrapper.classList.add('govuk-file-upload-wrapper--show-dropzone');
- }
- onDragLeaveOrDrop() {
- this.$wrapper.classList.remove('govuk-file-upload-wrapper--show-dropzone');
- }
observeDisabledState() {
const observer = new MutationObserver(mutationList => {
for (const mutation of mutationList) {
@@ -110,6 +135,31 @@ class FileUpload extends ConfigurableComponent {
this.$button.disabled = this.$root.disabled;
}
}
+FileUpload.moduleName = 'govuk-file-upload';
+FileUpload.defaults = Object.freeze({
+ i18n: {
+ selectFilesButton: 'Choose file',
+ filesSelectedDefault: 'No file chosen',
+ filesSelected: {
+ one: '%{count} file chosen',
+ other: '%{count} files chosen'
+ },
+ dropZoneEntered: 'Entered drop zone',
+ dropZoneLeft: 'Left drop zone'
+ }
+});
+FileUpload.schema = Object.freeze({
+ properties: {
+ i18n: {
+ type: 'object'
+ }
+ }
+});
+function isContainingFiles(dataTransfer) {
+ const hasNoTypesInfo = dataTransfer.types.length === 0;
+ const isDraggingFiles = dataTransfer.types.some(type => type === 'Files');
+ return hasNoTypesInfo || isDraggingFiles;
+}
/**
* @typedef {HTMLInputElement & {files: FileList}} HTMLFileInputElement
@@ -133,30 +183,16 @@ class FileUpload extends ConfigurableComponent {
* @property {string} [selectFiles] - Text of button that opens file browser
* @property {TranslationPluralForms} [filesSelected] - Text indicating how
* many files have been selected
+ * @property {string} [dropZoneEntered] - Text announced to assistive technology
+ * when users entered the drop zone while dragging
+ * @property {string} [dropZoneLeft] - Text announced to assistive technology
+ * when users left the drop zone while dragging
*/
/**
* @typedef {import('../../common/configuration.mjs').Schema} Schema
* @typedef {import('../../i18n.mjs').TranslationPluralForms} TranslationPluralForms
*/
-FileUpload.moduleName = 'govuk-file-upload';
-FileUpload.defaults = Object.freeze({
- i18n: {
- selectFilesButton: 'Choose file',
- filesSelectedDefault: 'No file chosen',
- filesSelected: {
- one: '%{count} file chosen',
- other: '%{count} files chosen'
- }
- }
-});
-FileUpload.schema = Object.freeze({
- properties: {
- i18n: {
- type: 'object'
- }
- }
-});
export { FileUpload };
//# sourceMappingURL=file-upload.mjs.map
Action run for 5b9ca62 |
4043936
to
7c5e8e6
Compare
7c5e8e6
to
b1cfdbd
Compare
- Only show the dropzone when the user drags into it rather than when entering the document. This will prevent multiple announcements when we add feedback for screenreaders, in case there's multiple `FileUpload`s on the page - Add a test to check if the user is dragging files before showing dropzone - Fix disappearance of the dropzone due to many `dragleave` events being triggered as user drags over the different elements inside the wrapper - Separate the handler of `drop` event as it doesn't need the same complexity as the `dragleave` one before hiding the dropzone. The component still relies on the native `<input>` receiving the files being dropped, as it ensures a `change` event gets triggered on drop (which we'd have to simulate if setting its `files` properties programmatically).
b1cfdbd
to
3e25b66
Compare
It happened when dragging from the button to the span or the opposite, because Safari does not fill the `relatedTarget` on `dragleave`, making our code believe user had left the window. To accomodate for that, we use `dragenter` to also hide the drop zone when entering an element that's not the wrapper. This may still leave a gap where the component is at the edge of the viewport, either because of scrolling or in a iframe. Will explore in next commit.
Putting this back in draft while I figure how to handle Safari not filling in Unfortunately,
|
2ffa565
to
e412fa7
Compare
Announces when users enter or leave the drop zone. Note: this seems to only be announced by Voice Over when Safari is in the foreground, even when using `aria-live="assertive"`
e412fa7
to
a6fb4f6
Compare
8f53794
to
affc0f9
Compare
affc0f9
to
5b9ca62
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good, tested and works as expected
FileUpload
s on the pagedragleave
events being triggered as user drags over the different elements inside the wrapperThe component still relies on the native
<input>
receiving the files being dropped, as it ensures achange
event gets triggered on drop (which we'd have to simulate if setting itsfiles
properties programmatically).Thoughts
The main thing this PR had to work through are that:
dragleave
events are triggered for each descendant of the drop zone element, including when the mouse moves from the drop zone itself to one of the descendant. As the event bubbles, a listener on the drop zone is triggered for each element the mouse goes over within the drop zone and we need to account for that.relatedTarget
event ondragleave
, meaning we can't use it when leaving the drop zone for outside the window (which can happen if the drop zone is cut by the bottom edge of the browser due to scrolling) and need to rely on the lack ofdragenter
event when exiting the viewport (as the mouse is not going over any element).Regarding announcements, those only happen when the browser is in the foreground (tested in both VO + Safari, NVDA + Chrome and NVDA + Firefox, all showing consistent behaviour).
Any design adjustments will be made in a future PR once we've implemented the chosen design for the component.