Skip to content

Commit

Permalink
Merge pull request #1886 from alphagov/release-3.8.0
Browse files Browse the repository at this point in the history
Release v3.8.0
  • Loading branch information
Vanita Barrett authored Jul 29, 2020
2 parents 02bebd7 + 5e2cf67 commit 3e0da06
Show file tree
Hide file tree
Showing 35 changed files with 375 additions and 178 deletions.
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

0 comments on commit 3e0da06

Please sign in to comment.