Skip to content

Commit

Permalink
Fixed special chars in IDs for conditional reveals.
Browse files Browse the repository at this point in the history
  • Loading branch information
nataliecarey committed Jul 8, 2021
1 parent a71bfc6 commit de275cb
Show file tree
Hide file tree
Showing 18 changed files with 304 additions and 89 deletions.
2 changes: 1 addition & 1 deletion dist/govuk-frontend-3.13.0.min.css

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion dist/govuk-frontend-3.13.0.min.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion dist/govuk-frontend-ie8-3.13.0.min.css

Large diffs are not rendered by default.

56 changes: 28 additions & 28 deletions package/govuk/all.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,12 @@ function generateUniqueID () {
})
}

function getSelectorForID (id) {
if (id && id.replace) {
return '#' + (id).replace(/([.:\][])/g, '\\$1')
}
}

(function(undefined) {

// Detection from https://github.com/Financial-Times/polyfill-service/blob/master/packages/polyfill-library/polyfills/Object/defineProperty/detect.js
Expand Down Expand Up @@ -1697,7 +1703,7 @@ Checkboxes.prototype.init = function () {

// Skip checkboxes without data-aria-controls attributes, or where the
// target element does not exist.
if (!target || !$module.querySelector('#' + target)) {
if (!target || !$module.querySelector(getSelectorForID(target))) {
return
}

Expand Down Expand Up @@ -1741,13 +1747,17 @@ Checkboxes.prototype.syncAllConditionalReveals = function () {
* @param {HTMLInputElement} $input Checkbox input
*/
Checkboxes.prototype.syncConditionalRevealWithInputState = function ($input) {
var $target = this.$module.querySelector('#' + $input.getAttribute('aria-controls'));
var selector = getSelectorForID($input.getAttribute('aria-controls'));

if ($target && $target.classList.contains('govuk-checkboxes__conditional')) {
var inputIsChecked = $input.checked;
if (selector) {
var $target = this.$module.querySelector(selector);

$input.setAttribute('aria-expanded', inputIsChecked);
$target.classList.toggle('govuk-checkboxes__conditional--hidden', !inputIsChecked);
if ($target && $target.classList.contains('govuk-checkboxes__conditional')) {
var inputIsChecked = $input.checked;

$input.setAttribute('aria-expanded', inputIsChecked);
$target.classList.toggle('govuk-checkboxes__conditional--hidden', !inputIsChecked);
}
}
};

Expand Down Expand Up @@ -2121,36 +2131,22 @@ function Radios ($module) {
this.$inputs = $module.querySelectorAll('input[type="radio"]');
}

/**
* Initialise Radios
*
* Radios can be associated with a 'conditionally revealed' content block – for
* example, a radio for 'Phone' could reveal an additional form field for the
* user to enter their phone number.
*
* These associations are made using a `data-aria-controls` attribute, which is
* promoted to an aria-controls attribute during initialisation.
*
* We also need to restore the state of any conditional reveals on the page (for
* example if the user has navigated back), and set up event handlers to keep
* the reveal in sync with the radio state.
*/
Radios.prototype.init = function () {
var $module = this.$module;
var $inputs = this.$inputs;

nodeListForEach($inputs, function ($input) {
var target = $input.getAttribute('data-aria-controls');
var targetSelector = getSelectorForID($input.getAttribute('data-aria-controls'));

// Skip radios without data-aria-controls attributes, or where the
// target element does not exist.
if (!target || !$module.querySelector('#' + target)) {
if (!targetSelector || !$module.querySelector(targetSelector)) {
return
}

// Promote the data-aria-controls attribute to a aria-controls attribute
// so that the relationship is exposed in the AOM
$input.setAttribute('aria-controls', target);
$input.setAttribute('aria-controls', $input.getAttribute('data-aria-controls'));
$input.removeAttribute('data-aria-controls');
});

Expand Down Expand Up @@ -2189,13 +2185,17 @@ Radios.prototype.syncAllConditionalReveals = function () {
* @param {HTMLInputElement} $input Radio input
*/
Radios.prototype.syncConditionalRevealWithInputState = function ($input) {
var $target = document.querySelector('#' + $input.getAttribute('aria-controls'));
var selectorForID = getSelectorForID($input.getAttribute('aria-controls'));

if ($target && $target.classList.contains('govuk-radios__conditional')) {
var inputIsChecked = $input.checked;
if (selectorForID) {
var $target = document.querySelector(selectorForID);

$input.setAttribute('aria-expanded', inputIsChecked);
$target.classList.toggle('govuk-radios__conditional--hidden', !inputIsChecked);
if ($target && $target.classList.contains('govuk-radios__conditional')) {
var inputIsChecked = $input.checked;

$input.setAttribute('aria-expanded', inputIsChecked);
$target.classList.toggle('govuk-radios__conditional--hidden', !inputIsChecked);
}
}
};

Expand Down
7 changes: 7 additions & 0 deletions package/govuk/common.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,14 @@ function generateUniqueID () {
})
}

function getSelectorForID (id) {
if (id && id.replace) {
return '#' + (id).replace(/([.:\][])/g, '\\$1')
}
}

exports.nodeListForEach = nodeListForEach;
exports.generateUniqueID = generateUniqueID;
exports.getSelectorForID = getSelectorForID;

})));
14 changes: 14 additions & 0 deletions package/govuk/components/checkboxes/_index.scss
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,20 @@
// Focused state
.govuk-checkboxes__input:focus + .govuk-checkboxes__label:before {
border-width: 4px;

// When colours are overridden, the yellow box-shadow becomes invisible
// which means the focus state is less obvious. By adding a transparent
// outline, which becomes solid (text-coloured) in that context, we ensure
// the focus remains clearly visible.
outline: $govuk-focus-width solid transparent;
outline-offset: 1px;

// When in an explicit forced-color mode, we can use the Highlight system
// color for the outline to better match focus states of native controls
@media screen and (forced-colors: active) {
outline-color: Highlight;
}

box-shadow: 0 0 0 $govuk-focus-width $govuk-focus-colour;
}

Expand Down
22 changes: 16 additions & 6 deletions package/govuk/components/checkboxes/checkboxes.js
Original file line number Diff line number Diff line change
Expand Up @@ -1028,6 +1028,12 @@ function nodeListForEach (nodes, callback) {
}
}

function getSelectorForID (id) {
if (id && id.replace) {
return '#' + (id).replace(/([.:\][])/g, '\\$1')
}
}

function Checkboxes ($module) {
this.$module = $module;
this.$inputs = $module.querySelectorAll('input[type="checkbox"]');
Expand Down Expand Up @@ -1056,7 +1062,7 @@ Checkboxes.prototype.init = function () {

// Skip checkboxes without data-aria-controls attributes, or where the
// target element does not exist.
if (!target || !$module.querySelector('#' + target)) {
if (!target || !$module.querySelector(getSelectorForID(target))) {
return
}

Expand Down Expand Up @@ -1100,13 +1106,17 @@ Checkboxes.prototype.syncAllConditionalReveals = function () {
* @param {HTMLInputElement} $input Checkbox input
*/
Checkboxes.prototype.syncConditionalRevealWithInputState = function ($input) {
var $target = this.$module.querySelector('#' + $input.getAttribute('aria-controls'));
var selector = getSelectorForID($input.getAttribute('aria-controls'));

if ($target && $target.classList.contains('govuk-checkboxes__conditional')) {
var inputIsChecked = $input.checked;
if (selector) {
var $target = this.$module.querySelector(selector);

$input.setAttribute('aria-expanded', inputIsChecked);
$target.classList.toggle('govuk-checkboxes__conditional--hidden', !inputIsChecked);
if ($target && $target.classList.contains('govuk-checkboxes__conditional')) {
var inputIsChecked = $input.checked;

$input.setAttribute('aria-expanded', inputIsChecked);
$target.classList.toggle('govuk-checkboxes__conditional--hidden', !inputIsChecked);
}
}
};

Expand Down
44 changes: 44 additions & 0 deletions package/govuk/components/checkboxes/fixtures.json
Original file line number Diff line number Diff line change
Expand Up @@ -490,6 +490,50 @@
"html": "<div class=\"govuk-form-group\">\n\n <fieldset class=\"govuk-fieldset\">\n \n <legend class=\"govuk-fieldset__legend\">\n \n How do you want to be contacted?\n \n </legend>\n \n\n <div class=\"govuk-checkboxes\"\n data-module=\"govuk-checkboxes\">\n \n \n \n \n \n \n \n \n <div class=\"govuk-checkboxes__item\">\n <input class=\"govuk-checkboxes__input\" id=\"how-contacted\" name=\"with-conditional-items\" type=\"checkbox\" value=\"email\" data-aria-controls=\"conditional-how-contacted\">\n <label class=\"govuk-label govuk-checkboxes__label\" for=\"how-contacted\">\n Email\n </label>\n \n </div>\n \n <div class=\"govuk-checkboxes__conditional govuk-checkboxes__conditional--hidden\" id=\"conditional-how-contacted\">\n <label class=\"govuk-label\" for=\"context-email\">Email address</label>\n<input class=\"govuk-input govuk-!-width-one-third\" name=\"context-email\" type=\"text\" id=\"context-email\">\n\n </div>\n \n \n \n \n \n \n \n \n \n \n <div class=\"govuk-checkboxes__item\">\n <input class=\"govuk-checkboxes__input\" id=\"how-contacted-2\" name=\"with-conditional-items\" type=\"checkbox\" value=\"phone\" data-aria-controls=\"conditional-how-contacted-2\">\n <label class=\"govuk-label govuk-checkboxes__label\" for=\"how-contacted-2\">\n Phone\n </label>\n \n </div>\n \n <div class=\"govuk-checkboxes__conditional govuk-checkboxes__conditional--hidden\" id=\"conditional-how-contacted-2\">\n <label class=\"govuk-label\" for=\"contact-phone\">Phone number</label>\n<input class=\"govuk-input govuk-!-width-one-third\" name=\"contact-phone\" type=\"text\" id=\"contact-phone\">\n\n </div>\n \n \n \n \n \n \n \n \n \n \n <div class=\"govuk-checkboxes__item\">\n <input class=\"govuk-checkboxes__input\" id=\"how-contacted-3\" name=\"with-conditional-items\" type=\"checkbox\" value=\"text\" data-aria-controls=\"conditional-how-contacted-3\">\n <label class=\"govuk-label govuk-checkboxes__label\" for=\"how-contacted-3\">\n Text message\n </label>\n \n </div>\n \n <div class=\"govuk-checkboxes__conditional govuk-checkboxes__conditional--hidden\" id=\"conditional-how-contacted-3\">\n <label class=\"govuk-label\" for=\"contact-text-message\">Mobile phone number</label>\n<input class=\"govuk-input govuk-!-width-one-third\" name=\"contact-text-message\" type=\"text\" id=\"contact-text-message\">\n\n </div>\n \n \n \n \n </div>\n \n\n</fieldset>\n\n\n</div>",
"hidden": false
},
{
"name": "with conditional items with all special chars in id",
"options": {
"name": "with-conditional-items-with-all-special-chars-in-id",
"idPrefix": "with-conditional-items-with-all-special-chars-in-id",
"fieldset": {
"legend": {
"text": "How do you want to be contacted?"
}
},
"items": [
{
"value": "email",
"text": "Email",
"id": "option-1:choice.condition[email]",
"conditional": {
"html": "<label class=\"govuk-label\" for=\"context-email\">Email address</label>\n<input class=\"govuk-input govuk-!-width-one-third\" name=\"context-email\" type=\"text\" id=\"context-email\">\n"
}
},
{
"value": "phone",
"text": "Phone",
"id": "option-1:choice.condition[phone]",
"conditional": {
"html": "<label class=\"govuk-label\" for=\"contact-phone\">Phone number</label>\n<input class=\"govuk-input govuk-!-width-one-third\" name=\"contact-phone\" type=\"text\" id=\"contact-phone\">\n"
}
},
{
"value": "text",
"text": "Text message",
"id": "option-1:choice.condition[text]",
"conditional": {
"html": "<label class=\"govuk-label\" for=\"contact-text-message\">Mobile phone number</label>\n<input class=\"govuk-input govuk-!-width-one-third\" name=\"contact-text-message\" type=\"text\" id=\"contact-text-message\">\n"
}
},
{
"value": "none",
"text": "None of the above"
}
]
},
"html": "<div class=\"govuk-form-group\">\n\n <fieldset class=\"govuk-fieldset\">\n \n <legend class=\"govuk-fieldset__legend\">\n \n How do you want to be contacted?\n \n </legend>\n \n\n <div class=\"govuk-checkboxes\"\n data-module=\"govuk-checkboxes\">\n \n \n \n \n \n \n \n <div class=\"govuk-checkboxes__item\">\n <input class=\"govuk-checkboxes__input\" id=\"option-1:choice.condition[email]\" name=\"with-conditional-items-with-all-special-chars-in-id\" type=\"checkbox\" value=\"email\" data-aria-controls=\"conditional-option-1:choice.condition[email]\">\n <label class=\"govuk-label govuk-checkboxes__label\" for=\"option-1:choice.condition[email]\">\n Email\n </label>\n \n </div>\n \n <div class=\"govuk-checkboxes__conditional govuk-checkboxes__conditional--hidden\" id=\"conditional-option-1:choice.condition[email]\">\n <label class=\"govuk-label\" for=\"context-email\">Email address</label>\n<input class=\"govuk-input govuk-!-width-one-third\" name=\"context-email\" type=\"text\" id=\"context-email\">\n\n </div>\n \n \n \n \n \n \n \n \n \n \n <div class=\"govuk-checkboxes__item\">\n <input class=\"govuk-checkboxes__input\" id=\"option-1:choice.condition[phone]\" name=\"with-conditional-items-with-all-special-chars-in-id\" type=\"checkbox\" value=\"phone\" data-aria-controls=\"conditional-option-1:choice.condition[phone]\">\n <label class=\"govuk-label govuk-checkboxes__label\" for=\"option-1:choice.condition[phone]\">\n Phone\n </label>\n \n </div>\n \n <div class=\"govuk-checkboxes__conditional govuk-checkboxes__conditional--hidden\" id=\"conditional-option-1:choice.condition[phone]\">\n <label class=\"govuk-label\" for=\"contact-phone\">Phone number</label>\n<input class=\"govuk-input govuk-!-width-one-third\" name=\"contact-phone\" type=\"text\" id=\"contact-phone\">\n\n </div>\n \n \n \n \n \n \n \n \n \n \n <div class=\"govuk-checkboxes__item\">\n <input class=\"govuk-checkboxes__input\" id=\"option-1:choice.condition[text]\" name=\"with-conditional-items-with-all-special-chars-in-id\" type=\"checkbox\" value=\"text\" data-aria-controls=\"conditional-option-1:choice.condition[text]\">\n <label class=\"govuk-label govuk-checkboxes__label\" for=\"option-1:choice.condition[text]\">\n Text message\n </label>\n \n </div>\n \n <div class=\"govuk-checkboxes__conditional govuk-checkboxes__conditional--hidden\" id=\"conditional-option-1:choice.condition[text]\">\n <label class=\"govuk-label\" for=\"contact-text-message\">Mobile phone number</label>\n<input class=\"govuk-input govuk-!-width-one-third\" name=\"contact-text-message\" type=\"text\" id=\"contact-text-message\">\n\n </div>\n \n \n \n \n \n \n \n \n \n \n <div class=\"govuk-checkboxes__item\">\n <input class=\"govuk-checkboxes__input\" id=\"with-conditional-items-with-all-special-chars-in-id-4\" name=\"with-conditional-items-with-all-special-chars-in-id\" type=\"checkbox\" value=\"none\">\n <label class=\"govuk-label govuk-checkboxes__label\" for=\"with-conditional-items-with-all-special-chars-in-id-4\">\n None of the above\n </label>\n \n </div>\n \n \n \n \n </div>\n \n\n</fieldset>\n\n\n</div>",
"hidden": false
},
{
"name": "with conditional item checked",
"options": {
Expand Down
14 changes: 14 additions & 0 deletions package/govuk/components/radios/_index.scss
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,20 @@
// Focused state
.govuk-radios__input:focus + .govuk-radios__label:before {
border-width: 4px;

// When colours are overridden, the yellow box-shadow becomes invisible
// which means the focus state is less obvious. By adding a transparent
// outline, which becomes solid (text-coloured) in that context, we ensure
// the focus remains clearly visible.
outline: $govuk-focus-width solid transparent;
outline-offset: 1px;

// When in an explicit forced-color mode, we can use the Highlight system
// color for the outline to better match focus states of native controls
@media screen and (forced-colors: active) {
outline-color: Highlight;
}

box-shadow: 0 0 0 $govuk-radios-focus-width $govuk-focus-colour;
}

Expand Down
Loading

0 comments on commit de275cb

Please sign in to comment.