Skip to content
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

Fix conditional reveal fullstop. #2270

Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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