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

Release v3.8.0 #1886

Merged
merged 1 commit into from
Jul 29, 2020
Merged
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: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

## Unreleased

## 3.8.0 (Feature release)

### New features

#### The secondary text colour is now darker
Expand Down
2 changes: 1 addition & 1 deletion dist/VERSION.txt
Original file line number Diff line number Diff line change
@@ -1 +1 @@
3.7.0
3.8.0
3 changes: 0 additions & 3 deletions dist/govuk-frontend-3.7.0.min.css

This file was deleted.

1 change: 0 additions & 1 deletion dist/govuk-frontend-3.7.0.min.js

This file was deleted.

3 changes: 3 additions & 0 deletions dist/govuk-frontend-3.8.0.min.css

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions dist/govuk-frontend-3.8.0.min.js

Large diffs are not rendered by default.

1 change: 0 additions & 1 deletion dist/govuk-frontend-ie8-3.7.0.min.css

This file was deleted.

1 change: 1 addition & 0 deletions dist/govuk-frontend-ie8-3.8.0.min.css

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

217 changes: 160 additions & 57 deletions package/govuk/all.js
Original file line number Diff line number Diff line change
Expand Up @@ -1542,13 +1542,22 @@ CharacterCount.prototype.init = function () {
// Remove hard limit if set
$module.removeAttribute('maxlength');

// Bind event changes to the textarea
var boundChangeEvents = this.bindChangeEvents.bind(this);
boundChangeEvents();
// When the page is restored after navigating 'back' in some browsers the
// state of the character count is not restored until *after* the DOMContentLoaded
// event is fired, so we need to sync after the pageshow event in browsers
// that support it.
if ('onpageshow' in window) {
window.addEventListener('pageshow', this.sync.bind(this));
} else {
window.addEventListener('DOMContentLoaded', this.sync.bind(this));
}

this.sync();
};

// Update count message
var boundUpdateCountMessage = this.updateCountMessage.bind(this);
boundUpdateCountMessage();
CharacterCount.prototype.sync = function () {
this.bindChangeEvents();
this.updateCountMessage();
};

// Read data attributes
Expand Down Expand Up @@ -1596,8 +1605,7 @@ CharacterCount.prototype.checkIfValueChanged = function () {
if (!this.$textarea.oldValue) this.$textarea.oldValue = '';
if (this.$textarea.value !== this.$textarea.oldValue) {
this.$textarea.oldValue = this.$textarea.value;
var boundUpdateCountMessage = this.updateCountMessage.bind(this);
boundUpdateCountMessage();
this.updateCountMessage();
}
};

Expand Down Expand Up @@ -1666,52 +1674,99 @@ function Checkboxes ($module) {
this.$inputs = $module.querySelectorAll('input[type="checkbox"]');
}

/**
* Initialise Checkboxes
*
* Checkboxes can be associated with a 'conditionally revealed' content block –
* for example, a checkbox 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 checkbox state.
*/
Checkboxes.prototype.init = function () {
var $module = this.$module;
var $inputs = this.$inputs;

/**
* Loop over all items with [data-controls]
* Check if they have a matching conditional reveal
* If they do, assign attributes.
**/
nodeListForEach($inputs, function ($input) {
var controls = $input.getAttribute('data-aria-controls');
var target = $input.getAttribute('data-aria-controls');

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

// If we have content that is controlled, set attributes.
$input.setAttribute('aria-controls', controls);
// 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.removeAttribute('data-aria-controls');
this.setAttributes($input);
}.bind(this));
});

// When the page is restored after navigating 'back' in some browsers the
// state of form controls is not restored until *after* the DOMContentLoaded
// event is fired, so we need to sync after the pageshow event in browsers
// that support it.
if ('onpageshow' in window) {
window.addEventListener('pageshow', this.syncAllConditionalReveals.bind(this));
} else {
window.addEventListener('DOMContentLoaded', this.syncAllConditionalReveals.bind(this));
}

// Although we've set up handlers to sync state on the pageshow or
// DOMContentLoaded event, init could be called after those events have fired,
// for example if they are added to the page dynamically, so sync now too.
this.syncAllConditionalReveals();

// Handle events
$module.addEventListener('click', this.handleClick.bind(this));
};

Checkboxes.prototype.setAttributes = function ($input) {
var inputIsChecked = $input.checked;
$input.setAttribute('aria-expanded', inputIsChecked);
/**
* Sync the conditional reveal states for all inputs in this $module.
*/
Checkboxes.prototype.syncAllConditionalReveals = function () {
nodeListForEach(this.$inputs, this.syncConditionalRevealWithInputState.bind(this));
};

/**
* Sync conditional reveal with the input state
*
* Synchronise the visibility of the conditional reveal, and its accessible
* state, with the input's checked state.
*
* @param {HTMLInputElement} $input Checkbox input
*/
Checkboxes.prototype.syncConditionalRevealWithInputState = function ($input) {
var $target = this.$module.querySelector('#' + $input.getAttribute('aria-controls'));

var $content = this.$module.querySelector('#' + $input.getAttribute('aria-controls'));
if ($content) {
$content.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);
}
};

/**
* Click event handler
*
* Handle a click within the $module – if the click occurred on a checkbox, sync
* the state of any associated conditional reveal with the checkbox state.
*
* @param {MouseEvent} event Click event
*/
Checkboxes.prototype.handleClick = function (event) {
var $target = event.target;

// If a checkbox with aria-controls, handle click
var isCheckbox = $target.getAttribute('type') === 'checkbox';
var hasAriaControls = $target.getAttribute('aria-controls');
if (isCheckbox && hasAriaControls) {
this.setAttributes($target);
this.syncConditionalRevealWithInputState($target);
}
};

Expand Down Expand Up @@ -1959,67 +2014,115 @@ Header.prototype.handleClick = function (event) {

function Radios ($module) {
this.$module = $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 = $module.querySelectorAll('input[type="radio"]');
var $inputs = this.$inputs;

/**
* Loop over all items with [data-controls]
* Check if they have a matching conditional reveal
* If they do, assign attributes.
**/
nodeListForEach($inputs, function ($input) {
var controls = $input.getAttribute('data-aria-controls');
var target = $input.getAttribute('data-aria-controls');

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

// If we have content that is controlled, set attributes.
$input.setAttribute('aria-controls', controls);
// 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.removeAttribute('data-aria-controls');
this.setAttributes($input);
}.bind(this));
});

// When the page is restored after navigating 'back' in some browsers the
// state of form controls is not restored until *after* the DOMContentLoaded
// event is fired, so we need to sync after the pageshow event in browsers
// that support it.
if ('onpageshow' in window) {
window.addEventListener('pageshow', this.syncAllConditionalReveals.bind(this));
} else {
window.addEventListener('DOMContentLoaded', this.syncAllConditionalReveals.bind(this));
}

// Although we've set up handlers to sync state on the pageshow or
// DOMContentLoaded event, init could be called after those events have fired,
// for example if they are added to the page dynamically, so sync now too.
this.syncAllConditionalReveals();

// Handle events
$module.addEventListener('click', this.handleClick.bind(this));
};

Radios.prototype.setAttributes = function ($input) {
var $content = document.querySelector('#' + $input.getAttribute('aria-controls'));
/**
* Sync the conditional reveal states for all inputs in this $module.
*/
Radios.prototype.syncAllConditionalReveals = function () {
nodeListForEach(this.$inputs, this.syncConditionalRevealWithInputState.bind(this));
};

if ($content && $content.classList.contains('govuk-radios__conditional')) {
/**
* Sync conditional reveal with the input state
*
* Synchronise the visibility of the conditional reveal, and its accessible
* state, with the input's checked state.
*
* @param {HTMLInputElement} $input Radio input
*/
Radios.prototype.syncConditionalRevealWithInputState = function ($input) {
var $target = document.querySelector('#' + $input.getAttribute('aria-controls'));

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

$input.setAttribute('aria-expanded', inputIsChecked);

$content.classList.toggle('govuk-radios__conditional--hidden', !inputIsChecked);
$target.classList.toggle('govuk-radios__conditional--hidden', !inputIsChecked);
}
};

/**
* Click event handler
*
* Handle a click within the $module – if the click occurred on a radio, sync
* the state of the conditional reveal for all radio buttons in the same form
* with the same name (because checking one radio could have un-checked a radio
* in another $module)
*
* @param {MouseEvent} event Click event
*/
Radios.prototype.handleClick = function (event) {
var $clickedInput = event.target;
// We only want to handle clicks for radio inputs

// Ignore clicks on things that aren't radio buttons
if ($clickedInput.type !== 'radio') {
return
}
// Because checking one radio can uncheck a radio in another $module,
// we need to call set attributes on all radios in the same form, or document if they're not in a form.
//
// We also only want radios which have aria-controls, as they support conditional reveals.

// We only need to consider radios with conditional reveals, which will have
// aria-controls attributes.
var $allInputs = document.querySelectorAll('input[type="radio"][aria-controls]');

nodeListForEach($allInputs, function ($input) {
// Only inputs with the same form owner should change.
var hasSameFormOwner = ($input.form === $clickedInput.form);

// In radios, only radios with the same name will affect each other.
var hasSameName = ($input.name === $clickedInput.name);

if (hasSameName && hasSameFormOwner) {
this.setAttributes($input);
this.syncConditionalRevealWithInputState($input);
}
}.bind(this));
};
Expand Down
24 changes: 16 additions & 8 deletions package/govuk/components/character-count/character-count.js
Original file line number Diff line number Diff line change
Expand Up @@ -1062,13 +1062,22 @@ CharacterCount.prototype.init = function () {
// Remove hard limit if set
$module.removeAttribute('maxlength');

// Bind event changes to the textarea
var boundChangeEvents = this.bindChangeEvents.bind(this);
boundChangeEvents();
// When the page is restored after navigating 'back' in some browsers the
// state of the character count is not restored until *after* the DOMContentLoaded
// event is fired, so we need to sync after the pageshow event in browsers
// that support it.
if ('onpageshow' in window) {
window.addEventListener('pageshow', this.sync.bind(this));
} else {
window.addEventListener('DOMContentLoaded', this.sync.bind(this));
}

this.sync();
};

// Update count message
var boundUpdateCountMessage = this.updateCountMessage.bind(this);
boundUpdateCountMessage();
CharacterCount.prototype.sync = function () {
this.bindChangeEvents();
this.updateCountMessage();
};

// Read data attributes
Expand Down Expand Up @@ -1116,8 +1125,7 @@ CharacterCount.prototype.checkIfValueChanged = function () {
if (!this.$textarea.oldValue) this.$textarea.oldValue = '';
if (this.$textarea.value !== this.$textarea.oldValue) {
this.$textarea.oldValue = this.$textarea.value;
var boundUpdateCountMessage = this.updateCountMessage.bind(this);
boundUpdateCountMessage();
this.updateCountMessage();
}
};

Expand Down
6 changes: 6 additions & 0 deletions package/govuk/components/character-count/macro-options.json
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,12 @@
"required": false,
"description": "HTML attributes (for example data attributes) to add to the textarea."
},
{
"name": "spellcheck",
"type": "boolean",
"required": false,
"description": "Optional field to enable or disable the spellcheck attribute on the character count."
},
{
"name": "countMessage",
"type": "object",
Expand Down
1 change: 1 addition & 0 deletions package/govuk/components/character-count/template.njk
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
name: params.name,
describedBy: params.id + '-info',
rows: params.rows,
spellcheck: params.spellcheck,
value: params.value,
formGroup: params.formGroup,
classes: 'govuk-js-character-count' + (' govuk-textarea--error' if params.errorMessage) + (' ' + params.classes if params.classes),
Expand Down
Loading