Forked from derekcavaliero/hubspot-form-a11y-polyfill.js
Created
August 14, 2024 17:23
-
-
Save undfine/d733370b380de1b391a2b17d11e193d6 to your computer and use it in GitHub Desktop.
HubSpot Embedded Form Accessibility Polyfills
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
| /** | |
| * HubSpot Embedded Form Accessibility Pollyfills | |
| * | |
| * This script fixes the HubSpot HTML blunders that make their embedded forms inaccessible for assistive technology. | |
| * - Replaces/removes improper use of <fieldset>, <legend>, <label>, and role attributes. | |
| * - Note - this can cause forms configured for mulitple column field layouts to break - you will need to adjust your CSS accordingly. | |
| **/ | |
| hubspotFormA11y = { | |
| changeTag: function(node, tag) { | |
| const clone = document.createElement(tag); | |
| for (const attr of node.attributes) { | |
| clone.setAttributeNS(null, attr.name, attr.value); | |
| } | |
| while (node.firstChild) { | |
| clone.appendChild(node.firstChild); | |
| } | |
| node.replaceWith(clone); | |
| return clone; | |
| }, | |
| polyfillInitialHtml: function(form) { | |
| form.querySelectorAll('fieldset, legend').forEach(element => { | |
| this.changeTag(element, 'div'); | |
| }); | |
| form.querySelectorAll('.hs-fieldtype-checkbox, .hs-fieldtype-radio').forEach(element => { | |
| element.querySelectorAll('[role]').forEach(item => { | |
| item.removeAttribute('role'); | |
| }); | |
| this.changeTag(element, 'fieldset'); | |
| }); | |
| form.querySelectorAll('.hs-fieldtype-checkbox > label, .hs-fieldtype-radio > label').forEach(element => { | |
| this.changeTag(element, 'legend'); | |
| }); | |
| }, | |
| polyfillErrorMessages: function(fieldRoot) { | |
| let errorRoot = fieldRoot.querySelector('.hs-error-msgs'); | |
| let errors = errorRoot.querySelectorAll('.hs-error-msg'); | |
| errorRoot.removeAttribute('role'); | |
| let input = fieldRoot.querySelector('.hs-input'); | |
| errorRoot.id = input.name + '-error-msgs'; | |
| input.setAttribute('aria-describedby', input.name + '-error-msgs'); | |
| errors.forEach(error => { | |
| let label = fieldRoot.querySelector('label[for="' + input.id + '"]'); | |
| if (error.innerText == 'Please complete this required field.') | |
| error.innerText = 'The "' + label.innerText.replace('*', '') + '" field is required.'; | |
| this.changeTag(error, 'span'); | |
| }); | |
| }, | |
| untetherErrorMessages: function(fieldRoot) { | |
| let input = fieldRoot.querySelector('.hs-input'); | |
| input.removeAttribute('aria-describedby'); | |
| }, | |
| observer: function(form) { | |
| const observer = new MutationObserver((mutationList, observer) => { | |
| for (const mutation of mutationList) { | |
| if (mutation.type !== 'childList') | |
| return; | |
| if (mutation.addedNodes.length) { | |
| let node = mutation.addedNodes[0]; | |
| if (node.nodeType === 3) // text node early return | |
| return; | |
| // console.log('Node(s) added', mutation.addedNodes, mutation.target); | |
| if (node.matches('.hs-error-msgs')) | |
| this.polyfillErrorMessages(mutation.target); | |
| } | |
| if (mutation.removedNodes.length) { | |
| let node = mutation.removedNodes[0]; | |
| if (node.nodeType === 3) // text node early return | |
| return; | |
| // console.log('Node(s) removed', mutation.removedNodes, mutation.target); | |
| if (node.hasOwnProperty('matches') && !node.matches('.hs-error-msgs')) | |
| return; | |
| this.untetherErrorMessages(mutation.target); | |
| } | |
| } | |
| }); | |
| observer.observe(form, { | |
| attributes: false, | |
| childList: true, | |
| subtree: true | |
| }); | |
| } | |
| }; | |
| window.addEventListener('message', msg => { | |
| if (msg.data.type !== 'hsFormCallback') | |
| return; | |
| const formId = msg.data.id; | |
| const form = document.querySelector('form[data-form-id="' + formId + '"]'); | |
| switch(msg.data.eventName) { | |
| case 'onFormReady': | |
| hubspotFormA11y.polyfillInitialHtml(form); | |
| hubspotFormA11y.observer(form); | |
| break; | |
| case 'onFormFailedValidation': | |
| break; | |
| case 'onFormSubmit': | |
| break; | |
| case 'onFormSubmitted': | |
| break; | |
| } | |
| }); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment