-
Notifications
You must be signed in to change notification settings - Fork 334
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
[WIP][SPIKE] Accordion refactorings #5551
Draft
romaricpascal
wants to merge
9
commits into
main
Choose a base branch
from
accordion-section-component
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Draft
Conversation
This file contains 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
This will help sharing them with the upcoming AccordionSection class. The ultimate goal is to remove all those used to create then find back elements, in favour of storing these elements in the AccordionSection instance or the Accordion instance itself.
This makes the construction of the header markup easier to follow
Simplifies the constructHeaderMarkup function
govuk-design-system-ci
temporarily deployed
to
govuk-frontend-pr-5551
December 13, 2024 17:48
Inactive
📋 StatsFile sizes
Modules
View stats and visualisations on the review app Action run for d7f1a99 |
JavaScript changes to npm packagediff --git a/packages/govuk-frontend/dist/govuk/govuk-frontend.min.js b/packages/govuk-frontend/dist/govuk/govuk-frontend.min.js
index 5a4b212e3..266a24bd0 100644
--- a/packages/govuk-frontend/dist/govuk/govuk-frontend.min.js
+++ b/packages/govuk-frontend/dist/govuk/govuk-frontend.min.js
@@ -14,13 +14,13 @@ function getBreakpoint(t) {
function setFocus(t, e = {}) {
var n;
- const s = t.getAttribute("tabindex");
+ const i = t.getAttribute("tabindex");
function onBlur() {
var n;
- null == (n = e.onBlur) || n.call(t), s || t.removeAttribute("tabindex")
+ null == (n = e.onBlur) || n.call(t), i || t.removeAttribute("tabindex")
}
- s || t.setAttribute("tabindex", "-1"), t.addEventListener("focus", (function() {
+ i || t.setAttribute("tabindex", "-1"), t.addEventListener("focus", (function() {
t.addEventListener("blur", onBlur, {
once: !0
})
@@ -64,11 +64,11 @@ class ElementError extends GOVUKFrontendError {
if ("object" == typeof t) {
const {
component: n,
- identifier: s,
- element: i,
- expectedType: o
+ identifier: i,
+ element: o,
+ expectedType: s
} = t;
- e = s, e += i ? ` is not of type ${null!=o?o:"HTMLElement"}` : " not found", e = formatErrorMessage(n, e)
+ e = i, e += o ? ` is not of type ${null!=s?s:"HTMLElement"}` : " not found", e = formatErrorMessage(n, e)
}
super(e), this.name = "ElementError"
}
@@ -118,57 +118,66 @@ class ConfigurableComponent extends GOVUKFrontendComponent {
}
constructor(e, n) {
super(e), this._config = void 0;
- const s = this.constructor;
- if (void 0 === s.defaults) throw new ConfigError(formatErrorMessage(s, "Config passed as parameter into constructor but no defaults defined"));
- const i = function(Component, t) {
+ const i = this.constructor;
+ if (void 0 === i.defaults) throw new ConfigError(formatErrorMessage(i, "Config passed as parameter into constructor but no defaults defined"));
+ const o = function(Component, t) {
if (void 0 === Component.schema) throw new ConfigError(formatErrorMessage(Component, "Config passed as parameter into constructor but no schema defined"));
const e = {};
- for (const [n, s] of Object.entries(Component.schema.properties)) n in t && (e[n] = normaliseString(t[n], s)), "object" === (null == s ? void 0 : s.type) && (e[n] = extractConfigByNamespace(Component.schema, t, n));
+ for (const [n, i] of Object.entries(Component.schema.properties)) n in t && (e[n] = normaliseString(t[n], i)), "object" === (null == i ? void 0 : i.type) && (e[n] = extractConfigByNamespace(Component.schema, t, n));
return e
- }(s, this._$root.dataset);
- this._config = mergeConfigs(s.defaults, null != n ? n : {}, this[t](i), i)
+ }(i, this._$root.dataset);
+ this._config = mergeConfigs(i.defaults, null != n ? n : {}, this[t](o), o)
}
}
function normaliseString(t, e) {
const n = t ? t.trim() : "";
- let s, i = null == e ? void 0 : e.type;
- switch (i || (["true", "false"].includes(n) && (i = "boolean"), n.length > 0 && isFinite(Number(n)) && (i = "number")), i) {
+ let i, o = null == e ? void 0 : e.type;
+ switch (o || (["true", "false"].includes(n) && (o = "boolean"), n.length > 0 && isFinite(Number(n)) && (o = "number")), o) {
case "boolean":
- s = "true" === n;
+ i = "true" === n;
break;
case "number":
- s = Number(n);
+ i = Number(n);
break;
default:
- s = t
+ i = t
}
- return s
+ return i
}
function mergeConfigs(...t) {
const e = {};
for (const n of t)
for (const t of Object.keys(n)) {
- const s = e[t],
- i = n[t];
- isObject(s) && isObject(i) ? e[t] = mergeConfigs(s, i) : e[t] = i
+ const i = e[t],
+ o = n[t];
+ isObject(i) && isObject(o) ? e[t] = mergeConfigs(i, o) : e[t] = o
}
return e
}
function extractConfigByNamespace(t, e, n) {
- const s = t.properties[n];
- if ("object" !== (null == s ? void 0 : s.type)) return;
- const i = {
+ const i = t.properties[n];
+ if ("object" !== (null == i ? void 0 : i.type)) return;
+ const o = {
[n]: {}
};
- for (const [o, r] of Object.entries(e)) {
- let t = i;
- const e = o.split(".");
- for (const [s, i] of e.entries()) "object" == typeof t && (s < e.length - 1 ? (isObject(t[i]) || (t[i] = {}), t = t[i]) : o !== n && (t[i] = normaliseString(r)))
+ for (const [s, r] of Object.entries(e)) {
+ let t = o;
+ const e = s.split(".");
+ for (const [i, o] of e.entries()) "object" == typeof t && (i < e.length - 1 ? (isObject(t[o]) || (t[o] = {}), t = t[o]) : s !== n && (t[o] = normaliseString(r)))
}
- return i[n]
+ return o[n]
+}
+
+function createElement(t, e = {}, n) {
+ const i = document.createElement(t);
+ if (Object.entries(e).forEach((([t, e]) => {
+ i.setAttribute(t, e)
+ })), n)
+ for (const o of n) i.appendChild(o);
+ return i
}
class I18n {
constructor(t = {}, e = {}) {
@@ -179,8 +188,8 @@ class I18n {
if (!t) throw new Error("i18n: lookup key missing");
let n = this.translations[t];
if ("number" == typeof(null == e ? void 0 : e.count) && "object" == typeof n) {
- const s = n[this.getPluralSuffix(t, e.count)];
- s && (n = s)
+ const i = n[this.getPluralSuffix(t, e.count)];
+ i && (n = i)
}
if ("string" == typeof n) {
if (n.match(/%{(.\S+)}/)) {
@@ -193,9 +202,9 @@ class I18n {
}
replacePlaceholders(t, e) {
const n = Intl.NumberFormat.supportedLocalesOf(this.locale).length ? new Intl.NumberFormat(this.locale) : void 0;
- return t.replace(/%{(.\S+)}/g, (function(t, s) {
- if (Object.prototype.hasOwnProperty.call(e, s)) {
- const t = e[s];
+ return t.replace(/%{(.\S+)}/g, (function(t, i) {
+ if (Object.prototype.hasOwnProperty.call(e, i)) {
+ const t = e[i];
return !1 === t || "number" != typeof t && "string" != typeof t ? "" : "number" == typeof t ? n ? n.format(t) : `${t}` : t
}
throw new Error(`i18n: no data found to replace ${t} placeholder in string`)
@@ -207,10 +216,10 @@ class I18n {
getPluralSuffix(t, e) {
if (e = Number(e), !isFinite(e)) return "other";
const n = this.translations[t],
- s = this.hasIntlPluralRulesSupport() ? new Intl.PluralRules(this.locale).select(e) : this.selectPluralFormUsingFallbackRules(e);
+ i = this.hasIntlPluralRulesSupport() ? new Intl.PluralRules(this.locale).select(e) : this.selectPluralFormUsingFallbackRules(e);
if ("object" == typeof n) {
- if (s in n) return s;
- if ("other" in n) return console.warn(`i18n: Missing plural form ".${s}" for "${this.locale}" locale. Falling back to ".other".`), "other"
+ if (i in n) return i;
+ if ("other" in n) return console.warn(`i18n: Missing plural form ".${i}" for "${this.locale}" locale. Falling back to ".other".`), "other"
}
throw new Error(`i18n: Plural form ".other" is required for "${this.locale}" locale`)
}
@@ -252,70 +261,106 @@ I18n.pluralRulesMap = {
spanish: t => 1 === t ? "one" : t % 1e6 == 0 && 0 !== t ? "many" : "other",
welsh: t => 0 === t ? "zero" : 1 === t ? "one" : 2 === t ? "two" : 3 === t ? "few" : 6 === t ? "many" : "other"
};
+const e = "govuk-accordion__section",
+ n = "govuk-accordion__section--expanded",
+ i = "govuk-accordion__section-button",
+ o = "govuk-accordion__section-header",
+ s = "govuk-accordion__section-heading",
+ r = "govuk-accordion__section-heading-text",
+ a = "govuk-accordion__section-toggle-text",
+ c = "govuk-accordion-nav__chevron",
+ l = "govuk-accordion-nav__chevron--down",
+ u = "govuk-accordion__section-summary",
+ h = "govuk-accordion__section-content";
class Accordion extends ConfigurableComponent {
- constructor(t, e = {}) {
- super(t, e), this.i18n = void 0, this.controlsClass = "govuk-accordion__controls", this.showAllClass = "govuk-accordion__show-all", this.showAllTextClass = "govuk-accordion__show-all-text", this.sectionClass = "govuk-accordion__section", this.sectionExpandedClass = "govuk-accordion__section--expanded", this.sectionButtonClass = "govuk-accordion__section-button", this.sectionHeaderClass = "govuk-accordion__section-header", this.sectionHeadingClass = "govuk-accordion__section-heading", this.sectionHeadingDividerClass = "govuk-accordion__section-heading-divider", this.sectionHeadingTextClass = "govuk-accordion__section-heading-text", this.sectionHeadingTextFocusClass = "govuk-accordion__section-heading-text-focus", this.sectionShowHideToggleClass = "govuk-accordion__section-toggle", this.sectionShowHideToggleFocusClass = "govuk-accordion__section-toggle-focus", this.sectionShowHideTextClass = "govuk-accordion__section-toggle-text", this.upChevronIconClass = "govuk-accordion-nav__chevron", this.downChevronIconClass = "govuk-accordion-nav__chevron--down", this.sectionSummaryClass = "govuk-accordion__section-summary", this.sectionSummaryFocusClass = "govuk-accordion__section-summary-focus", this.sectionContentClass = "govuk-accordion__section-content", this.$sections = void 0, this.$showAllButton = null, this.$showAllIcon = null, this.$showAllText = null, this.i18n = new I18n(this.config.i18n);
- const n = this.$root.querySelectorAll(`.${this.sectionClass}`);
- if (!n.length) throw new ElementError({
+ constructor(t, n = {}) {
+ super(t, n), this.i18n = void 0, this.$sections = void 0, this.$showAllButton = null, this.$showAllIcon = null, this.$showAllText = null, this.i18n = new I18n(this.config.i18n);
+ const i = this.$root.querySelectorAll(`.${e}`);
+ if (!i.length) throw new ElementError({
component: Accordion,
- identifier: `Sections (\`<div class="${this.sectionClass}">\`)`
+ identifier: `Sections (\`<div class="${e}">\`)`
});
- this.$sections = n, this.initControls(), this.initSectionHeaders(), this.updateShowAllButton(this.areAllSectionsOpen())
+ this.$sections = i, this.initControls(), this.initSectionHeaders(), this.updateShowAllButton(this.areAllSectionsOpen())
}
initControls() {
- this.$showAllButton = document.createElement("button"), this.$showAllButton.setAttribute("type", "button"), this.$showAllButton.setAttribute("class", this.showAllClass), this.$showAllButton.setAttribute("aria-expanded", "false"), this.$showAllIcon = document.createElement("span"), this.$showAllIcon.classList.add(this.upChevronIconClass), this.$showAllButton.appendChild(this.$showAllIcon);
- const t = document.createElement("div");
- t.setAttribute("class", this.controlsClass), t.appendChild(this.$showAllButton), this.$root.insertBefore(t, this.$root.firstChild), this.$showAllText = document.createElement("span"), this.$showAllText.classList.add(this.showAllTextClass), this.$showAllButton.appendChild(this.$showAllText), this.$showAllButton.addEventListener("click", (() => this.onShowOrHideAllToggle())), "onbeforematch" in document && document.addEventListener("beforematch", (t => this.onBeforeMatch(t)))
+ this.$showAllButton = createElement("button", {
+ type: "button",
+ class: "govuk-accordion__show-all",
+ "aria-expanded": "false"
+ }), this.$showAllIcon = createElement("span", {
+ class: c
+ }), this.$showAllButton.appendChild(this.$showAllIcon);
+ const t = createElement("div", {
+ class: "govuk-accordion__controls"
+ });
+ t.appendChild(this.$showAllButton), this.$root.insertBefore(t, this.$root.firstChild), this.$showAllText = createElement("span", {
+ class: "govuk-accordion__show-all-text"
+ }), this.$showAllButton.appendChild(this.$showAllText), this.$showAllButton.addEventListener("click", (() => this.onShowOrHideAllToggle())), "onbeforematch" in document && document.addEventListener("beforematch", (t => this.onBeforeMatch(t)))
}
initSectionHeaders() {
this.$sections.forEach(((t, e) => {
- const n = t.querySelector(`.${this.sectionHeaderClass}`);
+ const n = t.querySelector(`.${o}`);
if (!n) throw new ElementError({
component: Accordion,
- identifier: `Section headers (\`<div class="${this.sectionHeaderClass}">\`)`
+ identifier: `Section headers (\`<div class="${o}">\`)`
});
this.constructHeaderMarkup(n, e), this.setExpanded(this.isExpanded(t), t), n.addEventListener("click", (() => this.onSectionToggle(t))), this.setInitialState(t)
}))
}
constructHeaderMarkup(t, e) {
- const n = t.querySelector(`.${this.sectionButtonClass}`),
- s = t.querySelector(`.${this.sectionHeadingClass}`),
- i = t.querySelector(`.${this.sectionSummaryClass}`);
- if (!s) throw new ElementError({
+ const n = t.querySelector(`.${i}`),
+ o = t.querySelector(`.${s}`),
+ r = t.querySelector(`.${u}`);
+ if (!o) throw new ElementError({
component: Accordion,
- identifier: `Section heading (\`.${this.sectionHeadingClass}\`)`
+ identifier: `Section heading (\`.${s}\`)`
});
if (!n) throw new ElementError({
component: Accordion,
- identifier: `Section button placeholder (\`<span class="${this.sectionButtonClass}">\`)`
+ identifier: `Section button placeholder (\`<span class="${i}">\`)`
});
- const o = document.createElement("button");
- o.setAttribute("type", "button"), o.setAttribute("aria-controls", `${this.$root.id}-content-${e+1}`);
- for (const d of Array.from(n.attributes)) "id" !== d.name && o.setAttribute(d.name, d.value);
- const r = document.createElement("span");
- r.classList.add(this.sectionHeadingTextClass), r.id = n.id;
- const a = document.createElement("span");
- a.classList.add(this.sectionHeadingTextFocusClass), r.appendChild(a), Array.from(n.childNodes).forEach((t => a.appendChild(t)));
- const c = document.createElement("span");
- c.classList.add(this.sectionShowHideToggleClass), c.setAttribute("data-nosnippet", "");
- const l = document.createElement("span");
- l.classList.add(this.sectionShowHideToggleFocusClass), c.appendChild(l);
- const h = document.createElement("span"),
- u = document.createElement("span");
- if (u.classList.add(this.upChevronIconClass), l.appendChild(u), h.classList.add(this.sectionShowHideTextClass), l.appendChild(h), o.appendChild(r), o.appendChild(this.getButtonPunctuationEl()), i) {
- const t = document.createElement("span"),
- e = document.createElement("span");
- e.classList.add(this.sectionSummaryFocusClass), t.appendChild(e);
- for (const n of Array.from(i.attributes)) t.setAttribute(n.name, n.value);
- Array.from(i.childNodes).forEach((t => e.appendChild(t))), i.remove(), o.appendChild(t), o.appendChild(this.getButtonPunctuationEl())
- }
- o.appendChild(c), s.removeChild(n), s.appendChild(o)
+ const a = createElement("button", {
+ type: "button",
+ "aria-controls": `${this.$root.id}-content-${e+1}`
+ });
+ for (const i of Array.from(n.attributes)) "id" !== i.name && a.setAttribute(i.name, i.value);
+ a.appendChild(this.createHeadingText(n)), a.appendChild(this.getButtonPunctuationEl()), r && (r.remove(), a.appendChild(this.createSummarySpan(r)), a.appendChild(this.getButtonPunctuationEl())), a.appendChild(this.createShowHideToggle()), o.removeChild(n), o.appendChild(a)
+ }
+ createShowHideToggle() {
+ const t = createElement("span", {
+ class: "govuk-accordion__section-toggle-focus"
+ }, [createElement("span", {
+ class: c
+ }), createElement("span", {
+ class: a
+ })]);
+ return createElement("span", {
+ class: "govuk-accordion__section-toggle",
+ "data-nosnippet": ""
+ }, [t])
+ }
+ createHeadingText(t) {
+ const e = createElement("span", {
+ class: "govuk-accordion__section-heading-text-focus"
+ }, Array.from(t.childNodes));
+ return createElement("span", {
+ class: r,
+ id: t.id
+ }, [e])
+ }
+ createSummarySpan(t) {
+ const e = createElement("span", {
+ class: "govuk-accordion__section-summary-focus"
+ }, Array.from(t.childNodes)),
+ n = createElement("span", {}, [e]);
+ for (const i of Array.from(t.attributes)) n.setAttribute(i.name, i.value);
+ return n
}
onBeforeMatch(t) {
- const e = t.target;
- if (!(e instanceof Element)) return;
- const n = e.closest(`.${this.sectionClass}`);
- n && this.setExpanded(!0, n)
+ const n = t.target;
+ if (!(n instanceof Element)) return;
+ const i = n.closest(`.${e}`);
+ i && this.setExpanded(!0, i)
}
onSectionToggle(t) {
const e = !this.isExpanded(t);
@@ -328,36 +373,36 @@ class Accordion extends ConfigurableComponent {
})), this.updateShowAllButton(t)
}
setExpanded(t, e) {
- const n = e.querySelector(`.${this.upChevronIconClass}`),
- s = e.querySelector(`.${this.sectionShowHideTextClass}`),
- i = e.querySelector(`.${this.sectionButtonClass}`),
- o = e.querySelector(`.${this.sectionContentClass}`);
- if (!o) throw new ElementError({
+ const o = e.querySelector(`.${c}`),
+ s = e.querySelector(`.${a}`),
+ d = e.querySelector(`.${i}`),
+ m = e.querySelector(`.${h}`);
+ if (!m) throw new ElementError({
component: Accordion,
- identifier: `Section content (\`<div class="${this.sectionContentClass}">\`)`
+ identifier: `Section content (\`<div class="${h}">\`)`
});
- if (!n || !s || !i) return;
- const r = t ? this.i18n.t("hideSection") : this.i18n.t("showSection");
- s.textContent = r, i.setAttribute("aria-expanded", `${t}`);
- const a = [],
- c = e.querySelector(`.${this.sectionHeadingTextClass}`);
- c && a.push(`${c.textContent}`.trim());
- const l = e.querySelector(`.${this.sectionSummaryClass}`);
- l && a.push(`${l.textContent}`.trim());
- const h = t ? this.i18n.t("hideSectionAriaLabel") : this.i18n.t("showSectionAriaLabel");
- a.push(h), i.setAttribute("aria-label", a.join(" , ")), t ? (o.removeAttribute("hidden"), e.classList.add(this.sectionExpandedClass), n.classList.remove(this.downChevronIconClass)) : (o.setAttribute("hidden", "until-found"), e.classList.remove(this.sectionExpandedClass), n.classList.add(this.downChevronIconClass)), this.updateShowAllButton(this.areAllSectionsOpen())
+ if (!o || !s || !d) return;
+ const p = t ? this.i18n.t("hideSection") : this.i18n.t("showSection");
+ s.textContent = p, d.setAttribute("aria-expanded", `${t}`);
+ const g = [],
+ f = e.querySelector(`.${r}`);
+ f && g.push(`${f.textContent}`.trim());
+ const b = e.querySelector(`.${u}`);
+ b && g.push(`${b.textContent}`.trim());
+ const v = t ? this.i18n.t("hideSectionAriaLabel") : this.i18n.t("showSectionAriaLabel");
+ g.push(v), d.setAttribute("aria-label", g.join(" , ")), t ? (m.removeAttribute("hidden"), e.classList.add(n), o.classList.remove(l)) : (m.setAttribute("hidden", "until-found"), e.classList.remove(n), o.classList.add(l)), this.updateShowAllButton(this.areAllSectionsOpen())
}
isExpanded(t) {
- return t.classList.contains(this.sectionExpandedClass)
+ return t.classList.contains(n)
}
areAllSectionsOpen() {
return Array.from(this.$sections).every((t => this.isExpanded(t)))
}
updateShowAllButton(t) {
- this.$showAllButton && this.$showAllText && this.$showAllIcon && (this.$showAllButton.setAttribute("aria-expanded", t.toString()), this.$showAllText.textContent = t ? this.i18n.t("hideAllSections") : this.i18n.t("showAllSections"), this.$showAllIcon.classList.toggle(this.downChevronIconClass, !t))
+ this.$showAllButton && this.$showAllText && this.$showAllIcon && (this.$showAllButton.setAttribute("aria-expanded", t.toString()), this.$showAllText.textContent = t ? this.i18n.t("hideAllSections") : this.i18n.t("showAllSections"), this.$showAllIcon.classList.toggle(l, !t))
}
getIdentifier(t) {
- const e = t.querySelector(`.${this.sectionButtonClass}`);
+ const e = t.querySelector(`.${i}`);
return null == e ? void 0 : e.getAttribute("aria-controls")
}
storeState(t, e) {
@@ -365,7 +410,7 @@ class Accordion extends ConfigurableComponent {
const n = this.getIdentifier(t);
if (n) try {
window.sessionStorage.setItem(n, e.toString())
- } catch (s) {}
+ } catch (i) {}
}
setInitialState(t) {
if (!this.config.rememberExpanded) return;
@@ -376,8 +421,10 @@ class Accordion extends ConfigurableComponent {
} catch (n) {}
}
getButtonPunctuationEl() {
- const t = document.createElement("span");
- return t.classList.add("govuk-visually-hidden", this.sectionHeadingDividerClass), t.textContent = ", ", t
+ const t = createElement("span", {
+ class: "govuk-visually-hidden govuk-accordion__section-heading-divider"
+ });
+ return t.textContent = ", ", t
}
}
Accordion.moduleName = "govuk-accordion", Accordion.defaults = Object.freeze({
@@ -437,34 +484,34 @@ class CharacterCount extends ConfigurableComponent {
}), e
}
constructor(t, e = {}) {
- var n, s;
+ var n, i;
super(t, e), this.$textarea = void 0, this.$visibleCountMessage = void 0, this.$screenReaderCountMessage = void 0, this.lastInputTimestamp = null, this.lastInputValue = "", this.valueChecker = null, this.i18n = void 0, this.maxLength = void 0;
- const i = this.$root.querySelector(".govuk-js-character-count");
- if (!(i instanceof HTMLTextAreaElement || i instanceof HTMLInputElement)) throw new ElementError({
+ const o = this.$root.querySelector(".govuk-js-character-count");
+ if (!(o instanceof HTMLTextAreaElement || o instanceof HTMLInputElement)) throw new ElementError({
component: CharacterCount,
- element: i,
+ element: o,
expectedType: "HTMLTextareaElement or HTMLInputElement",
identifier: "Form field (`.govuk-js-character-count`)"
});
- const o = function(t, e) {
+ const s = function(t, e) {
const n = [];
- for (const [s, i] of Object.entries(t)) {
+ for (const [i, o] of Object.entries(t)) {
const t = [];
- if (Array.isArray(i)) {
+ if (Array.isArray(o)) {
for (const {
required: n,
- errorMessage: s
+ errorMessage: i
}
- of i) n.every((t => !!e[t])) || t.push(s);
- "anyOf" !== s || i.length - t.length >= 1 || n.push(...t)
+ of o) n.every((t => !!e[t])) || t.push(i);
+ "anyOf" !== i || o.length - t.length >= 1 || n.push(...t)
}
}
return n
}(CharacterCount.schema, this.config);
- if (o[0]) throw new ConfigError(formatErrorMessage(CharacterCount, o[0]));
+ if (s[0]) throw new ConfigError(formatErrorMessage(CharacterCount, s[0]));
this.i18n = new I18n(this.config.i18n, {
locale: closestAttributeValue(this.$root, "lang")
- }), this.maxLength = null != (n = null != (s = this.config.maxwords) ? s : this.config.maxlength) ? n : 1 / 0, this.$textarea = i;
+ }), this.maxLength = null != (n = null != (i = this.config.maxwords) ? i : this.config.maxlength) ? n : 1 / 0, this.$textarea = o;
const r = `${this.$textarea.id}-info`,
a = document.getElementById(r);
if (!a) throw new ElementError({
@@ -643,8 +690,8 @@ class ErrorSummary extends ConfigurableComponent {
if (!e) return !1;
const n = document.getElementById(e);
if (!n) return !1;
- const s = this.getAssociatedLegendOrLabel(n);
- return !!s && (s.scrollIntoView(), n.focus({
+ const i = this.getAssociatedLegendOrLabel(n);
+ return !!i && (i.scrollIntoView(), n.focus({
preventScroll: !0
}), !0)
}
@@ -656,10 +703,10 @@ class ErrorSummary extends ConfigurableComponent {
if (e.length) {
const n = e[0];
if (t instanceof HTMLInputElement && ("checkbox" === t.type || "radio" === t.type)) return n;
- const s = n.getBoundingClientRect().top,
- i = t.getBoundingClientRect();
- if (i.height && window.innerHeight) {
- if (i.top + i.height - s < window.innerHeight / 2) return n
+ const i = n.getBoundingClientRect().top,
+ o = t.getBoundingClientRect();
+ if (o.height && window.innerHeight) {
+ if (o.top + o.height - i < window.innerHeight / 2) return n
}
}
}
@@ -686,21 +733,26 @@ class ExitThisPage extends ConfigurableComponent {
identifier: "Button (`.govuk-exit-this-page__button`)"
});
this.i18n = new I18n(this.config.i18n), this.$button = n;
- const s = document.querySelector(".govuk-js-exit-this-page-skiplink");
- s instanceof HTMLAnchorElement && (this.$skiplinkButton = s), this.buildIndicator(), this.initUpdateSpan(), this.initButtonClickHandler(), "govukFrontendExitThisPageKeypress" in document.body.dataset || (document.addEventListener("keyup", this.handleKeypress.bind(this), !0), document.body.dataset.govukFrontendExitThisPageKeypress = "true"), window.addEventListener("pageshow", this.resetPage.bind(this))
+ const i = document.querySelector(".govuk-js-exit-this-page-skiplink");
+ i instanceof HTMLAnchorElement && (this.$skiplinkButton = i), this.buildIndicator(), this.initUpdateSpan(), this.initButtonClickHandler(), "govukFrontendExitThisPageKeypress" in document.body.dataset || (document.addEventListener("keyup", this.handleKeypress.bind(this), !0), document.body.dataset.govukFrontendExitThisPageKeypress = "true"), window.addEventListener("pageshow", this.resetPage.bind(this))
}
initUpdateSpan() {
- this.$updateSpan = document.createElement("span"), this.$updateSpan.setAttribute("role", "status"), this.$updateSpan.className = "govuk-visually-hidden", this.$root.appendChild(this.$updateSpan)
+ this.$updateSpan = createElement("span", {
+ role: "status",
+ class: "govuk-visually-hidden"
+ }), this.$root.appendChild(this.$updateSpan)
}
initButtonClickHandler() {
this.$button.addEventListener("click", this.handleClick.bind(this)), this.$skiplinkButton && this.$skiplinkButton.addEventListener("click", this.handleClick.bind(this))
}
buildIndicator() {
- this.$indicatorContainer = document.createElement("div"), this.$indicatorContainer.className = "govuk-exit-this-page__indicator", this.$indicatorContainer.setAttribute("aria-hidden", "true");
- for (let t = 0; t < 3; t++) {
- const t = document.createElement("div");
- t.className = "govuk-exit-this-page__indicator-light", this.$indicatorContainer.appendChild(t)
- }
+ this.$indicatorContainer = createElement("div", {
+ class: "govuk-exit-this-page__indicator",
+ "aria-hidden": "true"
+ });
+ for (let t = 0; t < 3; t++) this.$indicatorContainer.appendChild(createElement("div", {
+ class: "govuk-exit-this-page__indicator-light"
+ }));
this.$button.appendChild(this.$indicatorContainer)
}
updateIndicator() {
@@ -711,7 +763,10 @@ class ExitThisPage extends ConfigurableComponent {
}))
}
exitPage() {
- this.$updateSpan && (this.$updateSpan.textContent = "", document.body.classList.add("govuk-exit-this-page-hide-content"), this.$overlay = document.createElement("div"), this.$overlay.className = "govuk-exit-this-page-overlay", this.$overlay.setAttribute("role", "alert"), document.body.appendChild(this.$overlay), this.$overlay.textContent = this.i18n.t("activated"), window.location.href = this.$button.href)
+ this.$updateSpan && (this.$updateSpan.textContent = "", document.body.classList.add("govuk-exit-this-page-hide-content"), this.$overlay = createElement("div", {
+ class: "govuk-exit-this-page-overlay",
+ role: "alert"
+ }), document.body.appendChild(this.$overlay), this.$overlay.textContent = this.i18n.t("activated"), window.location.href = this.$button.href)
}
handleClick(t) {
t.preventDefault(), this.exitPage()
@@ -758,13 +813,13 @@ class Header extends GOVUKFrontendComponent {
component: Header,
identifier: 'Navigation button (`<button class="govuk-js-header-toggle">`) attribute (`aria-controls`)'
});
- const s = document.getElementById(n);
- if (!s) throw new ElementError({
+ const i = document.getElementById(n);
+ if (!i) throw new ElementError({
component: Header,
- element: s,
+ element: i,
identifier: `Navigation (\`<ul id="${n}">\`)`
});
- this.$menu = s, this.$menuButton = e, this.setupResponsiveChecks(), this.$menuButton.addEventListener("click", (() => this.handleMenuButtonClick()))
+ this.$menu = i, this.$menuButton = e, this.setupResponsiveChecks(), this.$menuButton.addEventListener("click", (() => this.handleMenuButtonClick()))
}
setupResponsiveChecks() {
const t = getBreakpoint("desktop");
@@ -807,19 +862,22 @@ class PasswordInput extends ConfigurableComponent {
identifier: "Form field (`.govuk-js-password-input-input`)"
});
if ("password" !== n.type) throw new ElementError("Password input: Form field (`.govuk-js-password-input-input`) must be of type `password`.");
- const s = this.$root.querySelector(".govuk-js-password-input-toggle");
- if (!(s instanceof HTMLButtonElement)) throw new ElementError({
+ const i = this.$root.querySelector(".govuk-js-password-input-toggle");
+ if (!(i instanceof HTMLButtonElement)) throw new ElementError({
component: PasswordInput,
- element: s,
+ element: i,
expectedType: "HTMLButtonElement",
identifier: "Button (`.govuk-js-password-input-toggle`)"
});
- if ("button" !== s.type) throw new ElementError("Password input: Button (`.govuk-js-password-input-toggle`) must be of type `button`.");
- this.$input = n, this.$showHideButton = s, this.i18n = new I18n(this.config.i18n, {
+ if ("button" !== i.type) throw new ElementError("Password input: Button (`.govuk-js-password-input-toggle`) must be of type `button`.");
+ this.$input = n, this.$showHideButton = i, this.i18n = new I18n(this.config.i18n, {
locale: closestAttributeValue(this.$root, "lang")
}), this.$showHideButton.removeAttribute("hidden");
- const i = document.createElement("div");
- i.className = "govuk-password-input__sr-status govuk-visually-hidden", i.setAttribute("aria-live", "polite"), this.$screenReaderStatusMessage = i, this.$input.insertAdjacentElement("afterend", i), this.$showHideButton.addEventListener("click", this.toggle.bind(this)), this.$input.form && this.$input.form.addEventListener("submit", (() => this.hide())), window.addEventListener("pageshow", (t => {
+ const o = createElement("div", {
+ class: "govuk-password-input__sr-status govuk-visually-hidden",
+ "aria-live": "polite"
+ });
+ this.$screenReaderStatusMessage = o, this.$input.insertAdjacentElement("afterend", o), this.$showHideButton.addEventListener("click", this.toggle.bind(this)), this.$input.form && this.$input.form.addEventListener("submit", (() => this.hide())), window.addEventListener("pageshow", (t => {
t.persisted && "password" !== this.$input.type && this.hide()
})), this.hide()
}
@@ -837,8 +895,8 @@ class PasswordInput extends ConfigurableComponent {
this.$input.setAttribute("type", t);
const e = "password" === t,
n = e ? "show" : "hide",
- s = e ? "passwordHidden" : "passwordShown";
- this.$showHideButton.innerText = this.i18n.t(`${n}Password`), this.$showHideButton.setAttribute("aria-label", this.i18n.t(`${n}PasswordAriaLabel`)), this.$screenReaderStatusMessage.innerText = this.i18n.t(`${s}Announcement`)
+ i = e ? "passwordHidden" : "passwordShown";
+ this.$showHideButton.innerText = this.i18n.t(`${n}Password`), this.$showHideButton.setAttribute("aria-label", this.i18n.t(`${n}PasswordAriaLabel`)), this.$screenReaderStatusMessage.innerText = this.i18n.t(`${i}Announcement`)
}
}
PasswordInput.moduleName = "govuk-password-input", PasswordInput.defaults = Object.freeze({
@@ -892,11 +950,11 @@ class Radios extends GOVUKFrontendComponent {
const e = t.target;
if (!(e instanceof HTMLInputElement) || "radio" !== e.type) return;
const n = document.querySelectorAll('input[type="radio"][aria-controls]'),
- s = e.form,
- i = e.name;
+ i = e.form,
+ o = e.name;
n.forEach((t => {
- const e = t.form === s;
- t.name === i && e && this.syncConditionalRevealWithInputState(t)
+ const e = t.form === i;
+ t.name === o && e && this.syncConditionalRevealWithInputState(t)
}))
}
}
@@ -911,13 +969,13 @@ class ServiceNavigation extends GOVUKFrontendComponent {
component: ServiceNavigation,
identifier: 'Navigation button (`<button class="govuk-js-service-navigation-toggle">`) attribute (`aria-controls`)'
});
- const s = document.getElementById(n);
- if (!s) throw new ElementError({
+ const i = document.getElementById(n);
+ if (!i) throw new ElementError({
component: ServiceNavigation,
- element: s,
+ element: i,
identifier: `Navigation (\`<ul id="${n}">\`)`
});
- this.$menu = s, this.$menuButton = e, this.setupResponsiveChecks(), this.$menuButton.addEventListener("click", (() => this.handleMenuButtonClick()))
+ this.$menu = i, this.$menuButton = e, this.setupResponsiveChecks(), this.$menuButton.addEventListener("click", (() => this.handleMenuButtonClick()))
}
setupResponsiveChecks() {
const t = getBreakpoint("tablet");
@@ -940,21 +998,21 @@ class SkipLink extends GOVUKFrontendComponent {
var e;
super(t);
const n = this.$root.hash,
- s = null != (e = this.$root.getAttribute("href")) ? e : "";
- let i;
+ i = null != (e = this.$root.getAttribute("href")) ? e : "";
+ let o;
try {
- i = new window.URL(this.$root.href)
+ o = new window.URL(this.$root.href)
} catch (a) {
- throw new ElementError(`Skip link: Target link (\`href="${s}"\`) is invalid`)
+ throw new ElementError(`Skip link: Target link (\`href="${i}"\`) is invalid`)
}
- if (i.origin !== window.location.origin || i.pathname !== window.location.pathname) return;
- const o = getFragmentFromUrl(n);
- if (!o) throw new ElementError(`Skip link: Target link (\`href="${s}"\`) has no hash fragment`);
- const r = document.getElementById(o);
+ if (o.origin !== window.location.origin || o.pathname !== window.location.pathname) return;
+ const s = getFragmentFromUrl(n);
+ if (!s) throw new ElementError(`Skip link: Target link (\`href="${i}"\`) has no hash fragment`);
+ const r = document.getElementById(s);
if (!r) throw new ElementError({
component: SkipLink,
element: r,
- identifier: `Target content (\`id="${o}"\`)`
+ identifier: `Target content (\`id="${s}"\`)`
});
this.$root.addEventListener("click", (() => setFocus(r, {
onBeforeFocus() {
@@ -977,16 +1035,16 @@ class Tabs extends GOVUKFrontendComponent {
});
this.$tabs = e, this.boundTabClick = this.onTabClick.bind(this), this.boundTabKeydown = this.onTabKeydown.bind(this), this.boundOnHashChange = this.onHashChange.bind(this);
const n = this.$root.querySelector(".govuk-tabs__list"),
- s = this.$root.querySelectorAll("li.govuk-tabs__list-item");
+ i = this.$root.querySelectorAll("li.govuk-tabs__list-item");
if (!n) throw new ElementError({
component: Tabs,
identifier: 'List (`<ul class="govuk-tabs__list">`)'
});
- if (!s.length) throw new ElementError({
+ if (!i.length) throw new ElementError({
component: Tabs,
identifier: 'List items (`<li class="govuk-tabs__list-item">`)'
});
- this.$tabList = n, this.$tabListItems = s, this.setupResponsiveChecks()
+ this.$tabList = n, this.$tabListItems = i, this.setupResponsiveChecks()
}
setupResponsiveChecks() {
const t = getBreakpoint("tablet");
@@ -1127,30 +1185,30 @@ function initAll(t) {
[SkipLink],
[Tabs]
],
- s = {
+ i = {
scope: null != (e = t.scope) ? e : document,
onError: t.onError
};
n.forEach((([Component, t]) => {
- createAll(Component, t, s)
+ createAll(Component, t, i)
}))
}
function createAll(Component, t, e) {
- let n, s = document;
- var i;
- "object" == typeof e && (s = null != (i = e.scope) ? i : s, n = e.onError);
- "function" == typeof e && (n = e), e instanceof HTMLElement && (s = e);
- const o = s.querySelectorAll(`[data-module="${Component.moduleName}"]`);
- return isSupported() ? Array.from(o).map((e => {
+ let n, i = document;
+ var o;
+ "object" == typeof e && (i = null != (o = e.scope) ? o : i, n = e.onError);
+ "function" == typeof e && (n = e), e instanceof HTMLElement && (i = e);
+ const s = i.querySelectorAll(`[data-module="${Component.moduleName}"]`);
+ return isSupported() ? Array.from(s).map((e => {
try {
return void 0 !== t ? new Component(e, t) : new Component(e)
- } catch (s) {
- return n ? n(s, {
+ } catch (i) {
+ return n ? n(i, {
element: e,
component: Component,
config: t
- }) : console.log(s), null
+ }) : console.log(i), null
}
})).filter(Boolean) : (n ? n(new SupportError, {
component: Component,
Action run for d7f1a99 |
Other changes to npm packagediff --git a/packages/govuk-frontend/dist/govuk/all.bundle.js b/packages/govuk-frontend/dist/govuk/all.bundle.js
index 7b511f0cb..48ec3e764 100644
--- a/packages/govuk-frontend/dist/govuk/all.bundle.js
+++ b/packages/govuk-frontend/dist/govuk/all.bundle.js
@@ -343,6 +343,19 @@
* @typedef {typeof GOVUKFrontendComponent & ChildClass<ConfigurationType>} ChildClassConstructor<ConfigurationType>
*/
+ function createElement(tagName, attributes = {}, children) {
+ const el = document.createElement(tagName);
+ Object.entries(attributes).forEach(([name, value]) => {
+ el.setAttribute(name, value);
+ });
+ if (children) {
+ for (const child of children) {
+ el.appendChild(child);
+ }
+ }
+ return el;
+ }
+
class I18n {
constructor(translations = {}, config = {}) {
var _config$locale;
@@ -536,6 +549,18 @@
}
};
+ const sectionClass = 'govuk-accordion__section';
+ const sectionExpandedModifier = 'govuk-accordion__section--expanded';
+ const sectionButtonClass = 'govuk-accordion__section-button';
+ const sectionHeaderClass = 'govuk-accordion__section-header';
+ const sectionHeadingClass = 'govuk-accordion__section-heading';
+ const sectionHeadingTextClass = 'govuk-accordion__section-heading-text';
+ const sectionToggleTextClass = 'govuk-accordion__section-toggle-text';
+ const iconClass = 'govuk-accordion-nav__chevron';
+ const iconOpenModifier = 'govuk-accordion-nav__chevron--down';
+ const sectionSummaryClass = 'govuk-accordion__section-summary';
+ const sectionContentClass = 'govuk-accordion__section-content';
+
/**
* Accordion component
*
@@ -559,35 +584,16 @@
constructor($root, config = {}) {
super($root, config);
this.i18n = void 0;
- this.controlsClass = 'govuk-accordion__controls';
- this.showAllClass = 'govuk-accordion__show-all';
- this.showAllTextClass = 'govuk-accordion__show-all-text';
- this.sectionClass = 'govuk-accordion__section';
- this.sectionExpandedClass = 'govuk-accordion__section--expanded';
- this.sectionButtonClass = 'govuk-accordion__section-button';
- this.sectionHeaderClass = 'govuk-accordion__section-header';
- this.sectionHeadingClass = 'govuk-accordion__section-heading';
- this.sectionHeadingDividerClass = 'govuk-accordion__section-heading-divider';
- this.sectionHeadingTextClass = 'govuk-accordion__section-heading-text';
- this.sectionHeadingTextFocusClass = 'govuk-accordion__section-heading-text-focus';
- this.sectionShowHideToggleClass = 'govuk-accordion__section-toggle';
- this.sectionShowHideToggleFocusClass = 'govuk-accordion__section-toggle-focus';
- this.sectionShowHideTextClass = 'govuk-accordion__section-toggle-text';
- this.upChevronIconClass = 'govuk-accordion-nav__chevron';
- this.downChevronIconClass = 'govuk-accordion-nav__chevron--down';
- this.sectionSummaryClass = 'govuk-accordion__section-summary';
- this.sectionSummaryFocusClass = 'govuk-accordion__section-summary-focus';
- this.sectionContentClass = 'govuk-accordion__section-content';
this.$sections = void 0;
this.$showAllButton = null;
this.$showAllIcon = null;
this.$showAllText = null;
this.i18n = new I18n(this.config.i18n);
- const $sections = this.$root.querySelectorAll(`.${this.sectionClass}`);
+ const $sections = this.$root.querySelectorAll(`.${sectionClass}`);
if (!$sections.length) {
throw new ElementError({
component: Accordion,
- identifier: `Sections (\`<div class="${this.sectionClass}">\`)`
+ identifier: `Sections (\`<div class="${sectionClass}">\`)`
});
}
this.$sections = $sections;
@@ -596,19 +602,23 @@
this.updateShowAllButton(this.areAllSectionsOpen());
}
initControls() {
- this.$showAllButton = document.createElement('button');
- this.$showAllButton.setAttribute('type', 'button');
- this.$showAllButton.setAttribute('class', this.showAllClass);
- this.$showAllButton.setAttribute('aria-expanded', 'false');
- this.$showAllIcon = document.createElement('span');
- this.$showAllIcon.classList.add(this.upChevronIconClass);
+ this.$showAllButton = createElement('button', {
+ type: 'button',
+ class: 'govuk-accordion__show-all',
+ 'aria-expanded': 'false'
+ });
+ this.$showAllIcon = createElement('span', {
+ class: iconClass
+ });
this.$showAllButton.appendChild(this.$showAllIcon);
- const $accordionControls = document.createElement('div');
- $accordionControls.setAttribute('class', this.controlsClass);
+ const $accordionControls = createElement('div', {
+ class: 'govuk-accordion__controls'
+ });
$accordionControls.appendChild(this.$showAllButton);
this.$root.insertBefore($accordionControls, this.$root.firstChild);
- this.$showAllText = document.createElement('span');
- this.$showAllText.classList.add(this.showAllTextClass);
+ this.$showAllText = createElement('span', {
+ class: 'govuk-accordion__show-all-text'
+ });
this.$showAllButton.appendChild(this.$showAllText);
this.$showAllButton.addEventListener('click', () => this.onShowOrHideAllToggle());
if ('onbeforematch' in document) {
@@ -617,11 +627,11 @@
}
initSectionHeaders() {
this.$sections.forEach(($section, i) => {
- const $header = $section.querySelector(`.${this.sectionHeaderClass}`);
+ const $header = $section.querySelector(`.${sectionHeaderClass}`);
if (!$header) {
throw new ElementError({
component: Accordion,
- identifier: `Section headers (\`<div class="${this.sectionHeaderClass}">\`)`
+ identifier: `Section headers (\`<div class="${sectionHeaderClass}">\`)`
});
}
this.constructHeaderMarkup($header, i);
@@ -631,73 +641,105 @@
});
}
constructHeaderMarkup($header, index) {
- const $span = $header.querySelector(`.${this.sectionButtonClass}`);
- const $heading = $header.querySelector(`.${this.sectionHeadingClass}`);
- const $summary = $header.querySelector(`.${this.sectionSummaryClass}`);
+ const $span = $header.querySelector(`.${sectionButtonClass}`);
+ const $heading = $header.querySelector(`.${sectionHeadingClass}`);
+ const $summary = $header.querySelector(`.${sectionSummaryClass}`);
if (!$heading) {
throw new ElementError({
component: Accordion,
- identifier: `Section heading (\`.${this.sectionHeadingClass}\`)`
+ identifier: `Section heading (\`.${sectionHeadingClass}\`)`
});
}
if (!$span) {
throw new ElementError({
component: Accordion,
- identifier: `Section button placeholder (\`<span class="${this.sectionButtonClass}">\`)`
+ identifier: `Section button placeholder (\`<span class="${sectionButtonClass}">\`)`
});
}
- const $button = document.createElement('button');
- $button.setAttribute('type', 'button');
- $button.setAttribute('aria-controls', `${this.$root.id}-content-${index + 1}`);
+ const $button = createElement('button', {
+ type: 'button',
+ 'aria-controls': `${this.$root.id}-content-${index + 1}`
+ });
for (const attr of Array.from($span.attributes)) {
if (attr.name !== 'id') {
$button.setAttribute(attr.name, attr.value);
}
}
- const $headingText = document.createElement('span');
- $headingText.classList.add(this.sectionHeadingTextClass);
- $headingText.id = $span.id;
- const $headingTextFocus = document.createElement('span');
- $headingTextFocus.classList.add(this.sectionHeadingTextFocusClass);
- $headingText.appendChild($headingTextFocus);
- Array.from($span.childNodes).forEach($child => $headingTextFocus.appendChild($child));
- const $showHideToggle = document.createElement('span');
- $showHideToggle.classList.add(this.sectionShowHideToggleClass);
- $showHideToggle.setAttribute('data-nosnippet', '');
- const $showHideToggleFocus = document.createElement('span');
- $showHideToggleFocus.classList.add(this.sectionShowHideToggleFocusClass);
- $showHideToggle.appendChild($showHideToggleFocus);
- const $showHideText = document.createElement('span');
- const $showHideIcon = document.createElement('span');
- $showHideIcon.classList.add(this.upChevronIconClass);
- $showHideToggleFocus.appendChild($showHideIcon);
- $showHideText.classList.add(this.sectionShowHideTextClass);
- $showHideToggleFocus.appendChild($showHideText);
- $button.appendChild($headingText);
+ $button.appendChild(this.createHeadingText($span));
$button.appendChild(this.getButtonPunctuationEl());
if ($summary) {
- const $summarySpan = document.createElement('span');
- const $summarySpanFocus = document.createElement('span');
- $summarySpanFocus.classList.add(this.sectionSummaryFocusClass);
- $summarySpan.appendChild($summarySpanFocus);
- for (const attr of Array.from($summary.attributes)) {
- $summarySpan.setAttribute(attr.name, attr.value);
- }
- Array.from($summary.childNodes).forEach($child => $summarySpanFocus.appendChild($child));
$summary.remove();
- $button.appendChild($summarySpan);
+ $button.appendChild(this.createSummarySpan($summary));
$button.appendChild(this.getButtonPunctuationEl());
}
- $button.appendChild($showHideToggle);
+ $button.appendChild(this.createShowHideToggle());
$heading.removeChild($span);
$heading.appendChild($button);
}
+
+ /**
+ * Creates a `<span>` rendering the 'Show'/'Hide' toggle
+ *
+ * @returns {HTMLSpanElement} - The `<span>` with the visual representation of the 'Show/Hide' toggle
+ */
+ createShowHideToggle() {
+ const $showHideToggleFocus = createElement('span', {
+ class: 'govuk-accordion__section-toggle-focus'
+ }, [createElement('span', {
+ class: iconClass
+ }), createElement('span', {
+ class: sectionToggleTextClass
+ })]);
+ const $showHideToggle = createElement('span', {
+ class: 'govuk-accordion__section-toggle',
+ 'data-nosnippet': ''
+ }, [$showHideToggleFocus]);
+ return $showHideToggle;
+ }
+
+ /**
+ * Creates the `<span>` containing the text of the section's heading
+ *
+ * @param {Element} $span - The heading of the span
+ * @returns {HTMLSpanElement} - The `<span>` containing the text of the section's heading
+ */
+ createHeadingText($span) {
+ const $headingTextFocus = createElement('span', {
+ class: 'govuk-accordion__section-heading-text-focus'
+ }, Array.from($span.childNodes));
+ const $headingText = createElement('span', {
+ class: sectionHeadingTextClass,
+ id: $span.id
+ }, [$headingTextFocus]);
+ return $headingText;
+ }
+
+ /**
+ * Creates the `<span>` element with the summary for the section
+ *
+ * This is necessary because the summary line text is now inside
+ * a button element, which can only contain phrasing content, and
+ * not a `<div>` element
+ *
+ * @param {Element} $summary - The original `<div>` containing the summary
+ * @returns {HTMLSpanElement} - The `<span>` element containing the summary
+ */
+ createSummarySpan($summary) {
+ const $summarySpanFocus = createElement('span', {
+ class: 'govuk-accordion__section-summary-focus'
+ }, Array.from($summary.childNodes));
+ const $summarySpan = createElement('span', {}, [$summarySpanFocus]);
+ for (const attr of Array.from($summary.attributes)) {
+ $summarySpan.setAttribute(attr.name, attr.value);
+ }
+ return $summarySpan;
+ }
onBeforeMatch(event) {
const $fragment = event.target;
if (!($fragment instanceof Element)) {
return;
}
- const $section = $fragment.closest(`.${this.sectionClass}`);
+ const $section = $fragment.closest(`.${sectionClass}`);
if ($section) {
this.setExpanded(true, $section);
}
@@ -716,14 +758,14 @@
this.updateShowAllButton(nowExpanded);
}
setExpanded(expanded, $section) {
- const $showHideIcon = $section.querySelector(`.${this.upChevronIconClass}`);
- const $showHideText = $section.querySelector(`.${this.sectionShowHideTextClass}`);
- const $button = $section.querySelector(`.${this.sectionButtonClass}`);
- const $content = $section.querySelector(`.${this.sectionContentClass}`);
+ const $showHideIcon = $section.querySelector(`.${iconClass}`);
+ const $showHideText = $section.querySelector(`.${sectionToggleTextClass}`);
+ const $button = $section.querySelector(`.${sectionButtonClass}`);
+ const $content = $section.querySelector(`.${sectionContentClass}`);
if (!$content) {
throw new ElementError({
component: Accordion,
- identifier: `Section content (\`<div class="${this.sectionContentClass}">\`)`
+ identifier: `Section content (\`<div class="${sectionContentClass}">\`)`
});
}
if (!$showHideIcon || !$showHideText || !$button) {
@@ -733,11 +775,11 @@
$showHideText.textContent = newButtonText;
$button.setAttribute('aria-expanded', `${expanded}`);
const ariaLabelParts = [];
- const $headingText = $section.querySelector(`.${this.sectionHeadingTextClass}`);
+ const $headingText = $section.querySelector(`.${sectionHeadingTextClass}`);
if ($headingText) {
ariaLabelParts.push(`${$headingText.textContent}`.trim());
}
- const $summary = $section.querySelector(`.${this.sectionSummaryClass}`);
+ const $summary = $section.querySelector(`.${sectionSummaryClass}`);
if ($summary) {
ariaLabelParts.push(`${$summary.textContent}`.trim());
}
@@ -746,17 +788,17 @@
$button.setAttribute('aria-label', ariaLabelParts.join(' , '));
if (expanded) {
$content.removeAttribute('hidden');
- $section.classList.add(this.sectionExpandedClass);
- $showHideIcon.classList.remove(this.downChevronIconClass);
+ $section.classList.add(sectionExpandedModifier);
+ $showHideIcon.classList.remove(iconOpenModifier);
} else {
$content.setAttribute('hidden', 'until-found');
- $section.classList.remove(this.sectionExpandedClass);
- $showHideIcon.classList.add(this.downChevronIconClass);
+ $section.classList.remove(sectionExpandedModifier);
+ $showHideIcon.classList.add(iconOpenModifier);
}
this.updateShowAllButton(this.areAllSectionsOpen());
}
isExpanded($section) {
- return $section.classList.contains(this.sectionExpandedClass);
+ return $section.classList.contains(sectionExpandedModifier);
}
areAllSectionsOpen() {
return Array.from(this.$sections).every($section => this.isExpanded($section));
@@ -767,7 +809,7 @@
}
this.$showAllButton.setAttribute('aria-expanded', expanded.toString());
this.$showAllText.textContent = expanded ? this.i18n.t('hideAllSections') : this.i18n.t('showAllSections');
- this.$showAllIcon.classList.toggle(this.downChevronIconClass, !expanded);
+ this.$showAllIcon.classList.toggle(iconOpenModifier, !expanded);
}
/**
@@ -781,7 +823,7 @@
* @returns {string | undefined | null} Identifier for section
*/
getIdentifier($section) {
- const $button = $section.querySelector(`.${this.sectionButtonClass}`);
+ const $button = $section.querySelector(`.${sectionButtonClass}`);
return $button == null ? void 0 : $button.getAttribute('aria-controls');
}
storeState($section, isExpanded) {
@@ -810,10 +852,11 @@
}
}
getButtonPunctuationEl() {
- const $punctuationEl = document.createElement('span');
- $punctuationEl.classList.add('govuk-visually-hidden', this.sectionHeadingDividerClass);
- $punctuationEl.textContent = ', ';
- return $punctuationEl;
+ const $element = createElement('span', {
+ class: 'govuk-visually-hidden govuk-accordion__section-heading-divider'
+ });
+ $element.textContent = ', ';
+ return $element;
}
}
@@ -1486,9 +1529,10 @@
window.addEventListener('pageshow', this.resetPage.bind(this));
}
initUpdateSpan() {
- this.$updateSpan = document.createElement('span');
- this.$updateSpan.setAttribute('role', 'status');
- this.$updateSpan.className = 'govuk-visually-hidden';
+ this.$updateSpan = createElement('span', {
+ role: 'status',
+ class: 'govuk-visually-hidden'
+ });
this.$root.appendChild(this.$updateSpan);
}
initButtonClickHandler() {
@@ -1498,13 +1542,14 @@
}
}
buildIndicator() {
- this.$indicatorContainer = document.createElement('div');
- this.$indicatorContainer.className = 'govuk-exit-this-page__indicator';
- this.$indicatorContainer.setAttribute('aria-hidden', 'true');
+ this.$indicatorContainer = createElement('div', {
+ class: 'govuk-exit-this-page__indicator',
+ 'aria-hidden': 'true'
+ });
for (let i = 0; i < 3; i++) {
- const $indicator = document.createElement('div');
- $indicator.className = 'govuk-exit-this-page__indicator-light';
- this.$indicatorContainer.appendChild($indicator);
+ this.$indicatorContainer.appendChild(createElement('div', {
+ class: 'govuk-exit-this-page__indicator-light'
+ }));
}
this.$button.appendChild(this.$indicatorContainer);
}
@@ -1524,9 +1569,10 @@
}
this.$updateSpan.textContent = '';
document.body.classList.add('govuk-exit-this-page-hide-content');
- this.$overlay = document.createElement('div');
- this.$overlay.className = 'govuk-exit-this-page-overlay';
- this.$overlay.setAttribute('role', 'alert');
+ this.$overlay = createElement('div', {
+ class: 'govuk-exit-this-page-overlay',
+ role: 'alert'
+ });
document.body.appendChild(this.$overlay);
this.$overlay.textContent = this.i18n.t('activated');
window.location.href = this.$button.href;
@@ -1827,9 +1873,10 @@
locale: closestAttributeValue(this.$root, 'lang')
});
this.$showHideButton.removeAttribute('hidden');
- const $screenReaderStatusMessage = document.createElement('div');
- $screenReaderStatusMessage.className = 'govuk-password-input__sr-status govuk-visually-hidden';
- $screenReaderStatusMessage.setAttribute('aria-live', 'polite');
+ const $screenReaderStatusMessage = createElement('div', {
+ class: 'govuk-password-input__sr-status govuk-visually-hidden',
+ 'aria-live': 'polite'
+ });
this.$screenReaderStatusMessage = $screenReaderStatusMessage;
this.$input.insertAdjacentElement('afterend', $screenReaderStatusMessage);
this.$showHideButton.addEventListener('click', this.toggle.bind(this));
diff --git a/packages/govuk-frontend/dist/govuk/all.bundle.mjs b/packages/govuk-frontend/dist/govuk/all.bundle.mjs
index 536f4b22d..9be57b023 100644
--- a/packages/govuk-frontend/dist/govuk/all.bundle.mjs
+++ b/packages/govuk-frontend/dist/govuk/all.bundle.mjs
@@ -337,6 +337,19 @@ function extractConfigByNamespace(schema, dataset, namespace) {
* @typedef {typeof GOVUKFrontendComponent & ChildClass<ConfigurationType>} ChildClassConstructor<ConfigurationType>
*/
+function createElement(tagName, attributes = {}, children) {
+ const el = document.createElement(tagName);
+ Object.entries(attributes).forEach(([name, value]) => {
+ el.setAttribute(name, value);
+ });
+ if (children) {
+ for (const child of children) {
+ el.appendChild(child);
+ }
+ }
+ return el;
+}
+
class I18n {
constructor(translations = {}, config = {}) {
var _config$locale;
@@ -530,6 +543,18 @@ I18n.pluralRules = {
}
};
+const sectionClass = 'govuk-accordion__section';
+const sectionExpandedModifier = 'govuk-accordion__section--expanded';
+const sectionButtonClass = 'govuk-accordion__section-button';
+const sectionHeaderClass = 'govuk-accordion__section-header';
+const sectionHeadingClass = 'govuk-accordion__section-heading';
+const sectionHeadingTextClass = 'govuk-accordion__section-heading-text';
+const sectionToggleTextClass = 'govuk-accordion__section-toggle-text';
+const iconClass = 'govuk-accordion-nav__chevron';
+const iconOpenModifier = 'govuk-accordion-nav__chevron--down';
+const sectionSummaryClass = 'govuk-accordion__section-summary';
+const sectionContentClass = 'govuk-accordion__section-content';
+
/**
* Accordion component
*
@@ -553,35 +578,16 @@ class Accordion extends ConfigurableComponent {
constructor($root, config = {}) {
super($root, config);
this.i18n = void 0;
- this.controlsClass = 'govuk-accordion__controls';
- this.showAllClass = 'govuk-accordion__show-all';
- this.showAllTextClass = 'govuk-accordion__show-all-text';
- this.sectionClass = 'govuk-accordion__section';
- this.sectionExpandedClass = 'govuk-accordion__section--expanded';
- this.sectionButtonClass = 'govuk-accordion__section-button';
- this.sectionHeaderClass = 'govuk-accordion__section-header';
- this.sectionHeadingClass = 'govuk-accordion__section-heading';
- this.sectionHeadingDividerClass = 'govuk-accordion__section-heading-divider';
- this.sectionHeadingTextClass = 'govuk-accordion__section-heading-text';
- this.sectionHeadingTextFocusClass = 'govuk-accordion__section-heading-text-focus';
- this.sectionShowHideToggleClass = 'govuk-accordion__section-toggle';
- this.sectionShowHideToggleFocusClass = 'govuk-accordion__section-toggle-focus';
- this.sectionShowHideTextClass = 'govuk-accordion__section-toggle-text';
- this.upChevronIconClass = 'govuk-accordion-nav__chevron';
- this.downChevronIconClass = 'govuk-accordion-nav__chevron--down';
- this.sectionSummaryClass = 'govuk-accordion__section-summary';
- this.sectionSummaryFocusClass = 'govuk-accordion__section-summary-focus';
- this.sectionContentClass = 'govuk-accordion__section-content';
this.$sections = void 0;
this.$showAllButton = null;
this.$showAllIcon = null;
this.$showAllText = null;
this.i18n = new I18n(this.config.i18n);
- const $sections = this.$root.querySelectorAll(`.${this.sectionClass}`);
+ const $sections = this.$root.querySelectorAll(`.${sectionClass}`);
if (!$sections.length) {
throw new ElementError({
component: Accordion,
- identifier: `Sections (\`<div class="${this.sectionClass}">\`)`
+ identifier: `Sections (\`<div class="${sectionClass}">\`)`
});
}
this.$sections = $sections;
@@ -590,19 +596,23 @@ class Accordion extends ConfigurableComponent {
this.updateShowAllButton(this.areAllSectionsOpen());
}
initControls() {
- this.$showAllButton = document.createElement('button');
- this.$showAllButton.setAttribute('type', 'button');
- this.$showAllButton.setAttribute('class', this.showAllClass);
- this.$showAllButton.setAttribute('aria-expanded', 'false');
- this.$showAllIcon = document.createElement('span');
- this.$showAllIcon.classList.add(this.upChevronIconClass);
+ this.$showAllButton = createElement('button', {
+ type: 'button',
+ class: 'govuk-accordion__show-all',
+ 'aria-expanded': 'false'
+ });
+ this.$showAllIcon = createElement('span', {
+ class: iconClass
+ });
this.$showAllButton.appendChild(this.$showAllIcon);
- const $accordionControls = document.createElement('div');
- $accordionControls.setAttribute('class', this.controlsClass);
+ const $accordionControls = createElement('div', {
+ class: 'govuk-accordion__controls'
+ });
$accordionControls.appendChild(this.$showAllButton);
this.$root.insertBefore($accordionControls, this.$root.firstChild);
- this.$showAllText = document.createElement('span');
- this.$showAllText.classList.add(this.showAllTextClass);
+ this.$showAllText = createElement('span', {
+ class: 'govuk-accordion__show-all-text'
+ });
this.$showAllButton.appendChild(this.$showAllText);
this.$showAllButton.addEventListener('click', () => this.onShowOrHideAllToggle());
if ('onbeforematch' in document) {
@@ -611,11 +621,11 @@ class Accordion extends ConfigurableComponent {
}
initSectionHeaders() {
this.$sections.forEach(($section, i) => {
- const $header = $section.querySelector(`.${this.sectionHeaderClass}`);
+ const $header = $section.querySelector(`.${sectionHeaderClass}`);
if (!$header) {
throw new ElementError({
component: Accordion,
- identifier: `Section headers (\`<div class="${this.sectionHeaderClass}">\`)`
+ identifier: `Section headers (\`<div class="${sectionHeaderClass}">\`)`
});
}
this.constructHeaderMarkup($header, i);
@@ -625,73 +635,105 @@ class Accordion extends ConfigurableComponent {
});
}
constructHeaderMarkup($header, index) {
- const $span = $header.querySelector(`.${this.sectionButtonClass}`);
- const $heading = $header.querySelector(`.${this.sectionHeadingClass}`);
- const $summary = $header.querySelector(`.${this.sectionSummaryClass}`);
+ const $span = $header.querySelector(`.${sectionButtonClass}`);
+ const $heading = $header.querySelector(`.${sectionHeadingClass}`);
+ const $summary = $header.querySelector(`.${sectionSummaryClass}`);
if (!$heading) {
throw new ElementError({
component: Accordion,
- identifier: `Section heading (\`.${this.sectionHeadingClass}\`)`
+ identifier: `Section heading (\`.${sectionHeadingClass}\`)`
});
}
if (!$span) {
throw new ElementError({
component: Accordion,
- identifier: `Section button placeholder (\`<span class="${this.sectionButtonClass}">\`)`
+ identifier: `Section button placeholder (\`<span class="${sectionButtonClass}">\`)`
});
}
- const $button = document.createElement('button');
- $button.setAttribute('type', 'button');
- $button.setAttribute('aria-controls', `${this.$root.id}-content-${index + 1}`);
+ const $button = createElement('button', {
+ type: 'button',
+ 'aria-controls': `${this.$root.id}-content-${index + 1}`
+ });
for (const attr of Array.from($span.attributes)) {
if (attr.name !== 'id') {
$button.setAttribute(attr.name, attr.value);
}
}
- const $headingText = document.createElement('span');
- $headingText.classList.add(this.sectionHeadingTextClass);
- $headingText.id = $span.id;
- const $headingTextFocus = document.createElement('span');
- $headingTextFocus.classList.add(this.sectionHeadingTextFocusClass);
- $headingText.appendChild($headingTextFocus);
- Array.from($span.childNodes).forEach($child => $headingTextFocus.appendChild($child));
- const $showHideToggle = document.createElement('span');
- $showHideToggle.classList.add(this.sectionShowHideToggleClass);
- $showHideToggle.setAttribute('data-nosnippet', '');
- const $showHideToggleFocus = document.createElement('span');
- $showHideToggleFocus.classList.add(this.sectionShowHideToggleFocusClass);
- $showHideToggle.appendChild($showHideToggleFocus);
- const $showHideText = document.createElement('span');
- const $showHideIcon = document.createElement('span');
- $showHideIcon.classList.add(this.upChevronIconClass);
- $showHideToggleFocus.appendChild($showHideIcon);
- $showHideText.classList.add(this.sectionShowHideTextClass);
- $showHideToggleFocus.appendChild($showHideText);
- $button.appendChild($headingText);
+ $button.appendChild(this.createHeadingText($span));
$button.appendChild(this.getButtonPunctuationEl());
if ($summary) {
- const $summarySpan = document.createElement('span');
- const $summarySpanFocus = document.createElement('span');
- $summarySpanFocus.classList.add(this.sectionSummaryFocusClass);
- $summarySpan.appendChild($summarySpanFocus);
- for (const attr of Array.from($summary.attributes)) {
- $summarySpan.setAttribute(attr.name, attr.value);
- }
- Array.from($summary.childNodes).forEach($child => $summarySpanFocus.appendChild($child));
$summary.remove();
- $button.appendChild($summarySpan);
+ $button.appendChild(this.createSummarySpan($summary));
$button.appendChild(this.getButtonPunctuationEl());
}
- $button.appendChild($showHideToggle);
+ $button.appendChild(this.createShowHideToggle());
$heading.removeChild($span);
$heading.appendChild($button);
}
+
+ /**
+ * Creates a `<span>` rendering the 'Show'/'Hide' toggle
+ *
+ * @returns {HTMLSpanElement} - The `<span>` with the visual representation of the 'Show/Hide' toggle
+ */
+ createShowHideToggle() {
+ const $showHideToggleFocus = createElement('span', {
+ class: 'govuk-accordion__section-toggle-focus'
+ }, [createElement('span', {
+ class: iconClass
+ }), createElement('span', {
+ class: sectionToggleTextClass
+ })]);
+ const $showHideToggle = createElement('span', {
+ class: 'govuk-accordion__section-toggle',
+ 'data-nosnippet': ''
+ }, [$showHideToggleFocus]);
+ return $showHideToggle;
+ }
+
+ /**
+ * Creates the `<span>` containing the text of the section's heading
+ *
+ * @param {Element} $span - The heading of the span
+ * @returns {HTMLSpanElement} - The `<span>` containing the text of the section's heading
+ */
+ createHeadingText($span) {
+ const $headingTextFocus = createElement('span', {
+ class: 'govuk-accordion__section-heading-text-focus'
+ }, Array.from($span.childNodes));
+ const $headingText = createElement('span', {
+ class: sectionHeadingTextClass,
+ id: $span.id
+ }, [$headingTextFocus]);
+ return $headingText;
+ }
+
+ /**
+ * Creates the `<span>` element with the summary for the section
+ *
+ * This is necessary because the summary line text is now inside
+ * a button element, which can only contain phrasing content, and
+ * not a `<div>` element
+ *
+ * @param {Element} $summary - The original `<div>` containing the summary
+ * @returns {HTMLSpanElement} - The `<span>` element containing the summary
+ */
+ createSummarySpan($summary) {
+ const $summarySpanFocus = createElement('span', {
+ class: 'govuk-accordion__section-summary-focus'
+ }, Array.from($summary.childNodes));
+ const $summarySpan = createElement('span', {}, [$summarySpanFocus]);
+ for (const attr of Array.from($summary.attributes)) {
+ $summarySpan.setAttribute(attr.name, attr.value);
+ }
+ return $summarySpan;
+ }
onBeforeMatch(event) {
const $fragment = event.target;
if (!($fragment instanceof Element)) {
return;
}
- const $section = $fragment.closest(`.${this.sectionClass}`);
+ const $section = $fragment.closest(`.${sectionClass}`);
if ($section) {
this.setExpanded(true, $section);
}
@@ -710,14 +752,14 @@ class Accordion extends ConfigurableComponent {
this.updateShowAllButton(nowExpanded);
}
setExpanded(expanded, $section) {
- const $showHideIcon = $section.querySelector(`.${this.upChevronIconClass}`);
- const $showHideText = $section.querySelector(`.${this.sectionShowHideTextClass}`);
- const $button = $section.querySelector(`.${this.sectionButtonClass}`);
- const $content = $section.querySelector(`.${this.sectionContentClass}`);
+ const $showHideIcon = $section.querySelector(`.${iconClass}`);
+ const $showHideText = $section.querySelector(`.${sectionToggleTextClass}`);
+ const $button = $section.querySelector(`.${sectionButtonClass}`);
+ const $content = $section.querySelector(`.${sectionContentClass}`);
if (!$content) {
throw new ElementError({
component: Accordion,
- identifier: `Section content (\`<div class="${this.sectionContentClass}">\`)`
+ identifier: `Section content (\`<div class="${sectionContentClass}">\`)`
});
}
if (!$showHideIcon || !$showHideText || !$button) {
@@ -727,11 +769,11 @@ class Accordion extends ConfigurableComponent {
$showHideText.textContent = newButtonText;
$button.setAttribute('aria-expanded', `${expanded}`);
const ariaLabelParts = [];
- const $headingText = $section.querySelector(`.${this.sectionHeadingTextClass}`);
+ const $headingText = $section.querySelector(`.${sectionHeadingTextClass}`);
if ($headingText) {
ariaLabelParts.push(`${$headingText.textContent}`.trim());
}
- const $summary = $section.querySelector(`.${this.sectionSummaryClass}`);
+ const $summary = $section.querySelector(`.${sectionSummaryClass}`);
if ($summary) {
ariaLabelParts.push(`${$summary.textContent}`.trim());
}
@@ -740,17 +782,17 @@ class Accordion extends ConfigurableComponent {
$button.setAttribute('aria-label', ariaLabelParts.join(' , '));
if (expanded) {
$content.removeAttribute('hidden');
- $section.classList.add(this.sectionExpandedClass);
- $showHideIcon.classList.remove(this.downChevronIconClass);
+ $section.classList.add(sectionExpandedModifier);
+ $showHideIcon.classList.remove(iconOpenModifier);
} else {
$content.setAttribute('hidden', 'until-found');
- $section.classList.remove(this.sectionExpandedClass);
- $showHideIcon.classList.add(this.downChevronIconClass);
+ $section.classList.remove(sectionExpandedModifier);
+ $showHideIcon.classList.add(iconOpenModifier);
}
this.updateShowAllButton(this.areAllSectionsOpen());
}
isExpanded($section) {
- return $section.classList.contains(this.sectionExpandedClass);
+ return $section.classList.contains(sectionExpandedModifier);
}
areAllSectionsOpen() {
return Array.from(this.$sections).every($section => this.isExpanded($section));
@@ -761,7 +803,7 @@ class Accordion extends ConfigurableComponent {
}
this.$showAllButton.setAttribute('aria-expanded', expanded.toString());
this.$showAllText.textContent = expanded ? this.i18n.t('hideAllSections') : this.i18n.t('showAllSections');
- this.$showAllIcon.classList.toggle(this.downChevronIconClass, !expanded);
+ this.$showAllIcon.classList.toggle(iconOpenModifier, !expanded);
}
/**
@@ -775,7 +817,7 @@ class Accordion extends ConfigurableComponent {
* @returns {string | undefined | null} Identifier for section
*/
getIdentifier($section) {
- const $button = $section.querySelector(`.${this.sectionButtonClass}`);
+ const $button = $section.querySelector(`.${sectionButtonClass}`);
return $button == null ? void 0 : $button.getAttribute('aria-controls');
}
storeState($section, isExpanded) {
@@ -804,10 +846,11 @@ class Accordion extends ConfigurableComponent {
}
}
getButtonPunctuationEl() {
- const $punctuationEl = document.createElement('span');
- $punctuationEl.classList.add('govuk-visually-hidden', this.sectionHeadingDividerClass);
- $punctuationEl.textContent = ', ';
- return $punctuationEl;
+ const $element = createElement('span', {
+ class: 'govuk-visually-hidden govuk-accordion__section-heading-divider'
+ });
+ $element.textContent = ', ';
+ return $element;
}
}
@@ -1480,9 +1523,10 @@ class ExitThisPage extends ConfigurableComponent {
window.addEventListener('pageshow', this.resetPage.bind(this));
}
initUpdateSpan() {
- this.$updateSpan = document.createElement('span');
- this.$updateSpan.setAttribute('role', 'status');
- this.$updateSpan.className = 'govuk-visually-hidden';
+ this.$updateSpan = createElement('span', {
+ role: 'status',
+ class: 'govuk-visually-hidden'
+ });
this.$root.appendChild(this.$updateSpan);
}
initButtonClickHandler() {
@@ -1492,13 +1536,14 @@ class ExitThisPage extends ConfigurableComponent {
}
}
buildIndicator() {
- this.$indicatorContainer = document.createElement('div');
- this.$indicatorContainer.className = 'govuk-exit-this-page__indicator';
- this.$indicatorContainer.setAttribute('aria-hidden', 'true');
+ this.$indicatorContainer = createElement('div', {
+ class: 'govuk-exit-this-page__indicator',
+ 'aria-hidden': 'true'
+ });
for (let i = 0; i < 3; i++) {
- const $indicator = document.createElement('div');
- $indicator.className = 'govuk-exit-this-page__indicator-light';
- this.$indicatorContainer.appendChild($indicator);
+ this.$indicatorContainer.appendChild(createElement('div', {
+ class: 'govuk-exit-this-page__indicator-light'
+ }));
}
this.$button.appendChild(this.$indicatorContainer);
}
@@ -1518,9 +1563,10 @@ class ExitThisPage extends ConfigurableComponent {
}
this.$updateSpan.textContent = '';
document.body.classList.add('govuk-exit-this-page-hide-content');
- this.$overlay = document.createElement('div');
- this.$overlay.className = 'govuk-exit-this-page-overlay';
- this.$overlay.setAttribute('role', 'alert');
+ this.$overlay = createElement('div', {
+ class: 'govuk-exit-this-page-overlay',
+ role: 'alert'
+ });
document.body.appendChild(this.$overlay);
this.$overlay.textContent = this.i18n.t('activated');
window.location.href = this.$button.href;
@@ -1821,9 +1867,10 @@ class PasswordInput extends ConfigurableComponent {
locale: closestAttributeValue(this.$root, 'lang')
});
this.$showHideButton.removeAttribute('hidden');
- const $screenReaderStatusMessage = document.createElement('div');
- $screenReaderStatusMessage.className = 'govuk-password-input__sr-status govuk-visually-hidden';
- $screenReaderStatusMessage.setAttribute('aria-live', 'polite');
+ const $screenReaderStatusMessage = createElement('div', {
+ class: 'govuk-password-input__sr-status govuk-visually-hidden',
+ 'aria-live': 'polite'
+ });
this.$screenReaderStatusMessage = $screenReaderStatusMessage;
this.$input.insertAdjacentElement('afterend', $screenReaderStatusMessage);
this.$showHideButton.addEventListener('click', this.toggle.bind(this));
diff --git a/packages/govuk-frontend/dist/govuk/common/create-element.mjs b/packages/govuk-frontend/dist/govuk/common/create-element.mjs
new file mode 100644
index 000000000..ed0e48c0a
--- /dev/null
+++ b/packages/govuk-frontend/dist/govuk/common/create-element.mjs
@@ -0,0 +1,15 @@
+function createElement(tagName, attributes = {}, children) {
+ const el = document.createElement(tagName);
+ Object.entries(attributes).forEach(([name, value]) => {
+ el.setAttribute(name, value);
+ });
+ if (children) {
+ for (const child of children) {
+ el.appendChild(child);
+ }
+ }
+ return el;
+}
+
+export { createElement };
+//# sourceMappingURL=create-element.mjs.map
diff --git a/packages/govuk-frontend/dist/govuk/components/accordion/accordion.bundle.js b/packages/govuk-frontend/dist/govuk/components/accordion/accordion.bundle.js
index a427fd6a9..906a27ef0 100644
--- a/packages/govuk-frontend/dist/govuk/components/accordion/accordion.bundle.js
+++ b/packages/govuk-frontend/dist/govuk/components/accordion/accordion.bundle.js
@@ -283,6 +283,19 @@
* @typedef {typeof GOVUKFrontendComponent & ChildClass<ConfigurationType>} ChildClassConstructor<ConfigurationType>
*/
+ function createElement(tagName, attributes = {}, children) {
+ const el = document.createElement(tagName);
+ Object.entries(attributes).forEach(([name, value]) => {
+ el.setAttribute(name, value);
+ });
+ if (children) {
+ for (const child of children) {
+ el.appendChild(child);
+ }
+ }
+ return el;
+ }
+
class I18n {
constructor(translations = {}, config = {}) {
var _config$locale;
@@ -476,6 +489,18 @@
}
};
+ const sectionClass = 'govuk-accordion__section';
+ const sectionExpandedModifier = 'govuk-accordion__section--expanded';
+ const sectionButtonClass = 'govuk-accordion__section-button';
+ const sectionHeaderClass = 'govuk-accordion__section-header';
+ const sectionHeadingClass = 'govuk-accordion__section-heading';
+ const sectionHeadingTextClass = 'govuk-accordion__section-heading-text';
+ const sectionToggleTextClass = 'govuk-accordion__section-toggle-text';
+ const iconClass = 'govuk-accordion-nav__chevron';
+ const iconOpenModifier = 'govuk-accordion-nav__chevron--down';
+ const sectionSummaryClass = 'govuk-accordion__section-summary';
+ const sectionContentClass = 'govuk-accordion__section-content';
+
/**
* Accordion component
*
@@ -499,35 +524,16 @@
constructor($root, config = {}) {
super($root, config);
this.i18n = void 0;
- this.controlsClass = 'govuk-accordion__controls';
- this.showAllClass = 'govuk-accordion__show-all';
- this.showAllTextClass = 'govuk-accordion__show-all-text';
- this.sectionClass = 'govuk-accordion__section';
- this.sectionExpandedClass = 'govuk-accordion__section--expanded';
- this.sectionButtonClass = 'govuk-accordion__section-button';
- this.sectionHeaderClass = 'govuk-accordion__section-header';
- this.sectionHeadingClass = 'govuk-accordion__section-heading';
- this.sectionHeadingDividerClass = 'govuk-accordion__section-heading-divider';
- this.sectionHeadingTextClass = 'govuk-accordion__section-heading-text';
- this.sectionHeadingTextFocusClass = 'govuk-accordion__section-heading-text-focus';
- this.sectionShowHideToggleClass = 'govuk-accordion__section-toggle';
- this.sectionShowHideToggleFocusClass = 'govuk-accordion__section-toggle-focus';
- this.sectionShowHideTextClass = 'govuk-accordion__section-toggle-text';
- this.upChevronIconClass = 'govuk-accordion-nav__chevron';
- this.downChevronIconClass = 'govuk-accordion-nav__chevron--down';
- this.sectionSummaryClass = 'govuk-accordion__section-summary';
- this.sectionSummaryFocusClass = 'govuk-accordion__section-summary-focus';
- this.sectionContentClass = 'govuk-accordion__section-content';
this.$sections = void 0;
this.$showAllButton = null;
this.$showAllIcon = null;
this.$showAllText = null;
this.i18n = new I18n(this.config.i18n);
- const $sections = this.$root.querySelectorAll(`.${this.sectionClass}`);
+ const $sections = this.$root.querySelectorAll(`.${sectionClass}`);
if (!$sections.length) {
throw new ElementError({
component: Accordion,
- identifier: `Sections (\`<div class="${this.sectionClass}">\`)`
+ identifier: `Sections (\`<div class="${sectionClass}">\`)`
});
}
this.$sections = $sections;
@@ -536,19 +542,23 @@
this.updateShowAllButton(this.areAllSectionsOpen());
}
initControls() {
- this.$showAllButton = document.createElement('button');
- this.$showAllButton.setAttribute('type', 'button');
- this.$showAllButton.setAttribute('class', this.showAllClass);
- this.$showAllButton.setAttribute('aria-expanded', 'false');
- this.$showAllIcon = document.createElement('span');
- this.$showAllIcon.classList.add(this.upChevronIconClass);
+ this.$showAllButton = createElement('button', {
+ type: 'button',
+ class: 'govuk-accordion__show-all',
+ 'aria-expanded': 'false'
+ });
+ this.$showAllIcon = createElement('span', {
+ class: iconClass
+ });
this.$showAllButton.appendChild(this.$showAllIcon);
- const $accordionControls = document.createElement('div');
- $accordionControls.setAttribute('class', this.controlsClass);
+ const $accordionControls = createElement('div', {
+ class: 'govuk-accordion__controls'
+ });
$accordionControls.appendChild(this.$showAllButton);
this.$root.insertBefore($accordionControls, this.$root.firstChild);
- this.$showAllText = document.createElement('span');
- this.$showAllText.classList.add(this.showAllTextClass);
+ this.$showAllText = createElement('span', {
+ class: 'govuk-accordion__show-all-text'
+ });
this.$showAllButton.appendChild(this.$showAllText);
this.$showAllButton.addEventListener('click', () => this.onShowOrHideAllToggle());
if ('onbeforematch' in document) {
@@ -557,11 +567,11 @@
}
initSectionHeaders() {
this.$sections.forEach(($section, i) => {
- const $header = $section.querySelector(`.${this.sectionHeaderClass}`);
+ const $header = $section.querySelector(`.${sectionHeaderClass}`);
if (!$header) {
throw new ElementError({
component: Accordion,
- identifier: `Section headers (\`<div class="${this.sectionHeaderClass}">\`)`
+ identifier: `Section headers (\`<div class="${sectionHeaderClass}">\`)`
});
}
this.constructHeaderMarkup($header, i);
@@ -571,73 +581,105 @@
});
}
constructHeaderMarkup($header, index) {
- const $span = $header.querySelector(`.${this.sectionButtonClass}`);
- const $heading = $header.querySelector(`.${this.sectionHeadingClass}`);
- const $summary = $header.querySelector(`.${this.sectionSummaryClass}`);
+ const $span = $header.querySelector(`.${sectionButtonClass}`);
+ const $heading = $header.querySelector(`.${sectionHeadingClass}`);
+ const $summary = $header.querySelector(`.${sectionSummaryClass}`);
if (!$heading) {
throw new ElementError({
component: Accordion,
- identifier: `Section heading (\`.${this.sectionHeadingClass}\`)`
+ identifier: `Section heading (\`.${sectionHeadingClass}\`)`
});
}
if (!$span) {
throw new ElementError({
component: Accordion,
- identifier: `Section button placeholder (\`<span class="${this.sectionButtonClass}">\`)`
+ identifier: `Section button placeholder (\`<span class="${sectionButtonClass}">\`)`
});
}
- const $button = document.createElement('button');
- $button.setAttribute('type', 'button');
- $button.setAttribute('aria-controls', `${this.$root.id}-content-${index + 1}`);
+ const $button = createElement('button', {
+ type: 'button',
+ 'aria-controls': `${this.$root.id}-content-${index + 1}`
+ });
for (const attr of Array.from($span.attributes)) {
if (attr.name !== 'id') {
$button.setAttribute(attr.name, attr.value);
}
}
- const $headingText = document.createElement('span');
- $headingText.classList.add(this.sectionHeadingTextClass);
- $headingText.id = $span.id;
- const $headingTextFocus = document.createElement('span');
- $headingTextFocus.classList.add(this.sectionHeadingTextFocusClass);
- $headingText.appendChild($headingTextFocus);
- Array.from($span.childNodes).forEach($child => $headingTextFocus.appendChild($child));
- const $showHideToggle = document.createElement('span');
- $showHideToggle.classList.add(this.sectionShowHideToggleClass);
- $showHideToggle.setAttribute('data-nosnippet', '');
- const $showHideToggleFocus = document.createElement('span');
- $showHideToggleFocus.classList.add(this.sectionShowHideToggleFocusClass);
- $showHideToggle.appendChild($showHideToggleFocus);
- const $showHideText = document.createElement('span');
- const $showHideIcon = document.createElement('span');
- $showHideIcon.classList.add(this.upChevronIconClass);
- $showHideToggleFocus.appendChild($showHideIcon);
- $showHideText.classList.add(this.sectionShowHideTextClass);
- $showHideToggleFocus.appendChild($showHideText);
- $button.appendChild($headingText);
+ $button.appendChild(this.createHeadingText($span));
$button.appendChild(this.getButtonPunctuationEl());
if ($summary) {
- const $summarySpan = document.createElement('span');
- const $summarySpanFocus = document.createElement('span');
- $summarySpanFocus.classList.add(this.sectionSummaryFocusClass);
- $summarySpan.appendChild($summarySpanFocus);
- for (const attr of Array.from($summary.attributes)) {
- $summarySpan.setAttribute(attr.name, attr.value);
- }
- Array.from($summary.childNodes).forEach($child => $summarySpanFocus.appendChild($child));
$summary.remove();
- $button.appendChild($summarySpan);
+ $button.appendChild(this.createSummarySpan($summary));
$button.appendChild(this.getButtonPunctuationEl());
}
- $button.appendChild($showHideToggle);
+ $button.appendChild(this.createShowHideToggle());
$heading.removeChild($span);
$heading.appendChild($button);
}
+
+ /**
+ * Creates a `<span>` rendering the 'Show'/'Hide' toggle
+ *
+ * @returns {HTMLSpanElement} - The `<span>` with the visual representation of the 'Show/Hide' toggle
+ */
+ createShowHideToggle() {
+ const $showHideToggleFocus = createElement('span', {
+ class: 'govuk-accordion__section-toggle-focus'
+ }, [createElement('span', {
+ class: iconClass
+ }), createElement('span', {
+ class: sectionToggleTextClass
+ })]);
+ const $showHideToggle = createElement('span', {
+ class: 'govuk-accordion__section-toggle',
+ 'data-nosnippet': ''
+ }, [$showHideToggleFocus]);
+ return $showHideToggle;
+ }
+
+ /**
+ * Creates the `<span>` containing the text of the section's heading
+ *
+ * @param {Element} $span - The heading of the span
+ * @returns {HTMLSpanElement} - The `<span>` containing the text of the section's heading
+ */
+ createHeadingText($span) {
+ const $headingTextFocus = createElement('span', {
+ class: 'govuk-accordion__section-heading-text-focus'
+ }, Array.from($span.childNodes));
+ const $headingText = createElement('span', {
+ class: sectionHeadingTextClass,
+ id: $span.id
+ }, [$headingTextFocus]);
+ return $headingText;
+ }
+
+ /**
+ * Creates the `<span>` element with the summary for the section
+ *
+ * This is necessary because the summary line text is now inside
+ * a button element, which can only contain phrasing content, and
+ * not a `<div>` element
+ *
+ * @param {Element} $summary - The original `<div>` containing the summary
+ * @returns {HTMLSpanElement} - The `<span>` element containing the summary
+ */
+ createSummarySpan($summary) {
+ const $summarySpanFocus = createElement('span', {
+ class: 'govuk-accordion__section-summary-focus'
+ }, Array.from($summary.childNodes));
+ const $summarySpan = createElement('span', {}, [$summarySpanFocus]);
+ for (const attr of Array.from($summary.attributes)) {
+ $summarySpan.setAttribute(attr.name, attr.value);
+ }
+ return $summarySpan;
+ }
onBeforeMatch(event) {
const $fragment = event.target;
if (!($fragment instanceof Element)) {
return;
}
- const $section = $fragment.closest(`.${this.sectionClass}`);
+ const $section = $fragment.closest(`.${sectionClass}`);
if ($section) {
this.setExpanded(true, $section);
}
@@ -656,14 +698,14 @@
this.updateShowAllButton(nowExpanded);
}
setExpanded(expanded, $section) {
- const $showHideIcon = $section.querySelector(`.${this.upChevronIconClass}`);
- const $showHideText = $section.querySelector(`.${this.sectionShowHideTextClass}`);
- const $button = $section.querySelector(`.${this.sectionButtonClass}`);
- const $content = $section.querySelector(`.${this.sectionContentClass}`);
+ const $showHideIcon = $section.querySelector(`.${iconClass}`);
+ const $showHideText = $section.querySelector(`.${sectionToggleTextClass}`);
+ const $button = $section.querySelector(`.${sectionButtonClass}`);
+ const $content = $section.querySelector(`.${sectionContentClass}`);
if (!$content) {
throw new ElementError({
component: Accordion,
- identifier: `Section content (\`<div class="${this.sectionContentClass}">\`)`
+ identifier: `Section content (\`<div class="${sectionContentClass}">\`)`
});
}
if (!$showHideIcon || !$showHideText || !$button) {
@@ -673,11 +715,11 @@
$showHideText.textContent = newButtonText;
$button.setAttribute('aria-expanded', `${expanded}`);
const ariaLabelParts = [];
- const $headingText = $section.querySelector(`.${this.sectionHeadingTextClass}`);
+ const $headingText = $section.querySelector(`.${sectionHeadingTextClass}`);
if ($headingText) {
ariaLabelParts.push(`${$headingText.textContent}`.trim());
}
- const $summary = $section.querySelector(`.${this.sectionSummaryClass}`);
+ const $summary = $section.querySelector(`.${sectionSummaryClass}`);
if ($summary) {
ariaLabelParts.push(`${$summary.textContent}`.trim());
}
@@ -686,17 +728,17 @@
$button.setAttribute('aria-label', ariaLabelParts.join(' , '));
if (expanded) {
$content.removeAttribute('hidden');
- $section.classList.add(this.sectionExpandedClass);
- $showHideIcon.classList.remove(this.downChevronIconClass);
+ $section.classList.add(sectionExpandedModifier);
+ $showHideIcon.classList.remove(iconOpenModifier);
} else {
$content.setAttribute('hidden', 'until-found');
- $section.classList.remove(this.sectionExpandedClass);
- $showHideIcon.classList.add(this.downChevronIconClass);
+ $section.classList.remove(sectionExpandedModifier);
+ $showHideIcon.classList.add(iconOpenModifier);
}
this.updateShowAllButton(this.areAllSectionsOpen());
}
isExpanded($section) {
- return $section.classList.contains(this.sectionExpandedClass);
+ return $section.classList.contains(sectionExpandedModifier);
}
areAllSectionsOpen() {
return Array.from(this.$sections).every($section => this.isExpanded($section));
@@ -707,7 +749,7 @@
}
this.$showAllButton.setAttribute('aria-expanded', expanded.toString());
this.$showAllText.textContent = expanded ? this.i18n.t('hideAllSections') : this.i18n.t('showAllSections');
- this.$showAllIcon.classList.toggle(this.downChevronIconClass, !expanded);
+ this.$showAllIcon.classList.toggle(iconOpenModifier, !expanded);
}
/**
@@ -721,7 +763,7 @@
* @returns {string | undefined | null} Identifier for section
*/
getIdentifier($section) {
- const $button = $section.querySelector(`.${this.sectionButtonClass}`);
+ const $button = $section.querySelector(`.${sectionButtonClass}`);
return $button == null ? void 0 : $button.getAttribute('aria-controls');
}
storeState($section, isExpanded) {
@@ -750,10 +792,11 @@
}
}
getButtonPunctuationEl() {
- const $punctuationEl = document.createElement('span');
- $punctuationEl.classList.add('govuk-visually-hidden', this.sectionHeadingDividerClass);
- $punctuationEl.textContent = ', ';
- return $punctuationEl;
+ const $element = createElement('span', {
+ class: 'govuk-visually-hidden govuk-accordion__section-heading-divider'
+ });
+ $element.textContent = ', ';
+ return $element;
}
}
diff --git a/packages/govuk-frontend/dist/govuk/components/accordion/accordion.bundle.mjs b/packages/govuk-frontend/dist/govuk/components/accordion/accordion.bundle.mjs
index f16d67139..66dd11b75 100644
--- a/packages/govuk-frontend/dist/govuk/components/accordion/accordion.bundle.mjs
+++ b/packages/govuk-frontend/dist/govuk/components/accordion/accordion.bundle.mjs
@@ -277,6 +277,19 @@ function extractConfigByNamespace(schema, dataset, namespace) {
* @typedef {typeof GOVUKFrontendComponent & ChildClass<ConfigurationType>} ChildClassConstructor<ConfigurationType>
*/
+function createElement(tagName, attributes = {}, children) {
+ const el = document.createElement(tagName);
+ Object.entries(attributes).forEach(([name, value]) => {
+ el.setAttribute(name, value);
+ });
+ if (children) {
+ for (const child of children) {
+ el.appendChild(child);
+ }
+ }
+ return el;
+}
+
class I18n {
constructor(translations = {}, config = {}) {
var _config$locale;
@@ -470,6 +483,18 @@ I18n.pluralRules = {
}
};
+const sectionClass = 'govuk-accordion__section';
+const sectionExpandedModifier = 'govuk-accordion__section--expanded';
+const sectionButtonClass = 'govuk-accordion__section-button';
+const sectionHeaderClass = 'govuk-accordion__section-header';
+const sectionHeadingClass = 'govuk-accordion__section-heading';
+const sectionHeadingTextClass = 'govuk-accordion__section-heading-text';
+const sectionToggleTextClass = 'govuk-accordion__section-toggle-text';
+const iconClass = 'govuk-accordion-nav__chevron';
+const iconOpenModifier = 'govuk-accordion-nav__chevron--down';
+const sectionSummaryClass = 'govuk-accordion__section-summary';
+const sectionContentClass = 'govuk-accordion__section-content';
+
/**
* Accordion component
*
@@ -493,35 +518,16 @@ class Accordion extends ConfigurableComponent {
constructor($root, config = {}) {
super($root, config);
this.i18n = void 0;
- this.controlsClass = 'govuk-accordion__controls';
- this.showAllClass = 'govuk-accordion__show-all';
- this.showAllTextClass = 'govuk-accordion__show-all-text';
- this.sectionClass = 'govuk-accordion__section';
- this.sectionExpandedClass = 'govuk-accordion__section--expanded';
- this.sectionButtonClass = 'govuk-accordion__section-button';
- this.sectionHeaderClass = 'govuk-accordion__section-header';
- this.sectionHeadingClass = 'govuk-accordion__section-heading';
- this.sectionHeadingDividerClass = 'govuk-accordion__section-heading-divider';
- this.sectionHeadingTextClass = 'govuk-accordion__section-heading-text';
- this.sectionHeadingTextFocusClass = 'govuk-accordion__section-heading-text-focus';
- this.sectionShowHideToggleClass = 'govuk-accordion__section-toggle';
- this.sectionShowHideToggleFocusClass = 'govuk-accordion__section-toggle-focus';
- this.sectionShowHideTextClass = 'govuk-accordion__section-toggle-text';
- this.upChevronIconClass = 'govuk-accordion-nav__chevron';
- this.downChevronIconClass = 'govuk-accordion-nav__chevron--down';
- this.sectionSummaryClass = 'govuk-accordion__section-summary';
- this.sectionSummaryFocusClass = 'govuk-accordion__section-summary-focus';
- this.sectionContentClass = 'govuk-accordion__section-content';
this.$sections = void 0;
this.$showAllButton = null;
this.$showAllIcon = null;
this.$showAllText = null;
this.i18n = new I18n(this.config.i18n);
- const $sections = this.$root.querySelectorAll(`.${this.sectionClass}`);
+ const $sections = this.$root.querySelectorAll(`.${sectionClass}`);
if (!$sections.length) {
throw new ElementError({
component: Accordion,
- identifier: `Sections (\`<div class="${this.sectionClass}">\`)`
+ identifier: `Sections (\`<div class="${sectionClass}">\`)`
});
}
this.$sections = $sections;
@@ -530,19 +536,23 @@ class Accordion extends ConfigurableComponent {
this.updateShowAllButton(this.areAllSectionsOpen());
}
initControls() {
- this.$showAllButton = document.createElement('button');
- this.$showAllButton.setAttribute('type', 'button');
- this.$showAllButton.setAttribute('class', this.showAllClass);
- this.$showAllButton.setAttribute('aria-expanded', 'false');
- this.$showAllIcon = document.createElement('span');
- this.$showAllIcon.classList.add(this.upChevronIconClass);
+ this.$showAllButton = createElement('button', {
+ type: 'button',
+ class: 'govuk-accordion__show-all',
+ 'aria-expanded': 'false'
+ });
+ this.$showAllIcon = createElement('span', {
+ class: iconClass
+ });
this.$showAllButton.appendChild(this.$showAllIcon);
- const $accordionControls = document.createElement('div');
- $accordionControls.setAttribute('class', this.controlsClass);
+ const $accordionControls = createElement('div', {
+ class: 'govuk-accordion__controls'
+ });
$accordionControls.appendChild(this.$showAllButton);
this.$root.insertBefore($accordionControls, this.$root.firstChild);
- this.$showAllText = document.createElement('span');
- this.$showAllText.classList.add(this.showAllTextClass);
+ this.$showAllText = createElement('span', {
+ class: 'govuk-accordion__show-all-text'
+ });
this.$showAllButton.appendChild(this.$showAllText);
this.$showAllButton.addEventListener('click', () => this.onShowOrHideAllToggle());
if ('onbeforematch' in document) {
@@ -551,11 +561,11 @@ class Accordion extends ConfigurableComponent {
}
initSectionHeaders() {
this.$sections.forEach(($section, i) => {
- const $header = $section.querySelector(`.${this.sectionHeaderClass}`);
+ const $header = $section.querySelector(`.${sectionHeaderClass}`);
if (!$header) {
throw new ElementError({
component: Accordion,
- identifier: `Section headers (\`<div class="${this.sectionHeaderClass}">\`)`
+ identifier: `Section headers (\`<div class="${sectionHeaderClass}">\`)`
});
}
this.constructHeaderMarkup($header, i);
@@ -565,73 +575,105 @@ class Accordion extends ConfigurableComponent {
});
}
constructHeaderMarkup($header, index) {
- const $span = $header.querySelector(`.${this.sectionButtonClass}`);
- const $heading = $header.querySelector(`.${this.sectionHeadingClass}`);
- const $summary = $header.querySelector(`.${this.sectionSummaryClass}`);
+ const $span = $header.querySelector(`.${sectionButtonClass}`);
+ const $heading = $header.querySelector(`.${sectionHeadingClass}`);
+ const $summary = $header.querySelector(`.${sectionSummaryClass}`);
if (!$heading) {
throw new ElementError({
component: Accordion,
- identifier: `Section heading (\`.${this.sectionHeadingClass}\`)`
+ identifier: `Section heading (\`.${sectionHeadingClass}\`)`
});
}
if (!$span) {
throw new ElementError({
component: Accordion,
- identifier: `Section button placeholder (\`<span class="${this.sectionButtonClass}">\`)`
+ identifier: `Section button placeholder (\`<span class="${sectionButtonClass}">\`)`
});
}
- const $button = document.createElement('button');
- $button.setAttribute('type', 'button');
- $button.setAttribute('aria-controls', `${this.$root.id}-content-${index + 1}`);
+ const $button = createElement('button', {
+ type: 'button',
+ 'aria-controls': `${this.$root.id}-content-${index + 1}`
+ });
for (const attr of Array.from($span.attributes)) {
if (attr.name !== 'id') {
$button.setAttribute(attr.name, attr.value);
}
}
- const $headingText = document.createElement('span');
- $headingText.classList.add(this.sectionHeadingTextClass);
- $headingText.id = $span.id;
- const $headingTextFocus = document.createElement('span');
- $headingTextFocus.classList.add(this.sectionHeadingTextFocusClass);
- $headingText.appendChild($headingTextFocus);
- Array.from($span.childNodes).forEach($child => $headingTextFocus.appendChild($child));
- const $showHideToggle = document.createElement('span');
- $showHideToggle.classList.add(this.sectionShowHideToggleClass);
- $showHideToggle.setAttribute('data-nosnippet', '');
- const $showHideToggleFocus = document.createElement('span');
- $showHideToggleFocus.classList.add(this.sectionShowHideToggleFocusClass);
- $showHideToggle.appendChild($showHideToggleFocus);
- const $showHideText = document.createElement('span');
- const $showHideIcon = document.createElement('span');
- $showHideIcon.classList.add(this.upChevronIconClass);
- $showHideToggleFocus.appendChild($showHideIcon);
- $showHideText.classList.add(this.sectionShowHideTextClass);
- $showHideToggleFocus.appendChild($showHideText);
- $button.appendChild($headingText);
+ $button.appendChild(this.createHeadingText($span));
$button.appendChild(this.getButtonPunctuationEl());
if ($summary) {
- const $summarySpan = document.createElement('span');
- const $summarySpanFocus = document.createElement('span');
- $summarySpanFocus.classList.add(this.sectionSummaryFocusClass);
- $summarySpan.appendChild($summarySpanFocus);
- for (const attr of Array.from($summary.attributes)) {
- $summarySpan.setAttribute(attr.name, attr.value);
- }
- Array.from($summary.childNodes).forEach($child => $summarySpanFocus.appendChild($child));
$summary.remove();
- $button.appendChild($summarySpan);
+ $button.appendChild(this.createSummarySpan($summary));
$button.appendChild(this.getButtonPunctuationEl());
}
- $button.appendChild($showHideToggle);
+ $button.appendChild(this.createShowHideToggle());
$heading.removeChild($span);
$heading.appendChild($button);
}
+
+ /**
+ * Creates a `<span>` rendering the 'Show'/'Hide' toggle
+ *
+ * @returns {HTMLSpanElement} - The `<span>` with the visual representation of the 'Show/Hide' toggle
+ */
+ createShowHideToggle() {
+ const $showHideToggleFocus = createElement('span', {
+ class: 'govuk-accordion__section-toggle-focus'
+ }, [createElement('span', {
+ class: iconClass
+ }), createElement('span', {
+ class: sectionToggleTextClass
+ })]);
+ const $showHideToggle = createElement('span', {
+ class: 'govuk-accordion__section-toggle',
+ 'data-nosnippet': ''
+ }, [$showHideToggleFocus]);
+ return $showHideToggle;
+ }
+
+ /**
+ * Creates the `<span>` containing the text of the section's heading
+ *
+ * @param {Element} $span - The heading of the span
+ * @returns {HTMLSpanElement} - The `<span>` containing the text of the section's heading
+ */
+ createHeadingText($span) {
+ const $headingTextFocus = createElement('span', {
+ class: 'govuk-accordion__section-heading-text-focus'
+ }, Array.from($span.childNodes));
+ const $headingText = createElement('span', {
+ class: sectionHeadingTextClass,
+ id: $span.id
+ }, [$headingTextFocus]);
+ return $headingText;
+ }
+
+ /**
+ * Creates the `<span>` element with the summary for the section
+ *
+ * This is necessary because the summary line text is now inside
+ * a button element, which can only contain phrasing content, and
+ * not a `<div>` element
+ *
+ * @param {Element} $summary - The original `<div>` containing the summary
+ * @returns {HTMLSpanElement} - The `<span>` element containing the summary
+ */
+ createSummarySpan($summary) {
+ const $summarySpanFocus = createElement('span', {
+ class: 'govuk-accordion__section-summary-focus'
+ }, Array.from($summary.childNodes));
+ const $summarySpan = createElement('span', {}, [$summarySpanFocus]);
+ for (const attr of Array.from($summary.attributes)) {
+ $summarySpan.setAttribute(attr.name, attr.value);
+ }
+ return $summarySpan;
+ }
onBeforeMatch(event) {
const $fragment = event.target;
if (!($fragment instanceof Element)) {
return;
}
- const $section = $fragment.closest(`.${this.sectionClass}`);
+ const $section = $fragment.closest(`.${sectionClass}`);
if ($section) {
this.setExpanded(true, $section);
}
@@ -650,14 +692,14 @@ class Accordion extends ConfigurableComponent {
this.updateShowAllButton(nowExpanded);
}
setExpanded(expanded, $section) {
- const $showHideIcon = $section.querySelector(`.${this.upChevronIconClass}`);
- const $showHideText = $section.querySelector(`.${this.sectionShowHideTextClass}`);
- const $button = $section.querySelector(`.${this.sectionButtonClass}`);
- const $content = $section.querySelector(`.${this.sectionContentClass}`);
+ const $showHideIcon = $section.querySelector(`.${iconClass}`);
+ const $showHideText = $section.querySelector(`.${sectionToggleTextClass}`);
+ const $button = $section.querySelector(`.${sectionButtonClass}`);
+ const $content = $section.querySelector(`.${sectionContentClass}`);
if (!$content) {
throw new ElementError({
component: Accordion,
- identifier: `Section content (\`<div class="${this.sectionContentClass}">\`)`
+ identifier: `Section content (\`<div class="${sectionContentClass}">\`)`
});
}
if (!$showHideIcon || !$showHideText || !$button) {
@@ -667,11 +709,11 @@ class Accordion extends ConfigurableComponent {
$showHideText.textContent = newButtonText;
$button.setAttribute('aria-expanded', `${expanded}`);
const ariaLabelParts = [];
- const $headingText = $section.querySelector(`.${this.sectionHeadingTextClass}`);
+ const $headingText = $section.querySelector(`.${sectionHeadingTextClass}`);
if ($headingText) {
ariaLabelParts.push(`${$headingText.textContent}`.trim());
}
- const $summary = $section.querySelector(`.${this.sectionSummaryClass}`);
+ const $summary = $section.querySelector(`.${sectionSummaryClass}`);
if ($summary) {
ariaLabelParts.push(`${$summary.textContent}`.trim());
}
@@ -680,17 +722,17 @@ class Accordion extends ConfigurableComponent {
$button.setAttribute('aria-label', ariaLabelParts.join(' , '));
if (expanded) {
$content.removeAttribute('hidden');
- $section.classList.add(this.sectionExpandedClass);
- $showHideIcon.classList.remove(this.downChevronIconClass);
+ $section.classList.add(sectionExpandedModifier);
+ $showHideIcon.classList.remove(iconOpenModifier);
} else {
$content.setAttribute('hidden', 'until-found');
- $section.classList.remove(this.sectionExpandedClass);
- $showHideIcon.classList.add(this.downChevronIconClass);
+ $section.classList.remove(sectionExpandedModifier);
+ $showHideIcon.classList.add(iconOpenModifier);
}
this.updateShowAllButton(this.areAllSectionsOpen());
}
isExpanded($section) {
- return $section.classList.contains(this.sectionExpandedClass);
+ return $section.classList.contains(sectionExpandedModifier);
}
areAllSectionsOpen() {
return Array.from(this.$sections).every($section => this.isExpanded($section));
@@ -701,7 +743,7 @@ class Accordion extends ConfigurableComponent {
}
this.$showAllButton.setAttribute('aria-expanded', expanded.toString());
this.$showAllText.textContent = expanded ? this.i18n.t('hideAllSections') : this.i18n.t('showAllSections');
- this.$showAllIcon.classList.toggle(this.downChevronIconClass, !expanded);
+ this.$showAllIcon.classList.toggle(iconOpenModifier, !expanded);
}
/**
@@ -715,7 +757,7 @@ class Accordion extends ConfigurableComponent {
* @returns {string | undefined | null} Identifier for section
*/
getIdentifier($section) {
- const $button = $section.querySelector(`.${this.sectionButtonClass}`);
+ const $button = $section.querySelector(`.${sectionButtonClass}`);
return $button == null ? void 0 : $button.getAttribute('aria-controls');
}
storeState($section, isExpanded) {
@@ -744,10 +786,11 @@ class Accordion extends ConfigurableComponent {
}
}
getButtonPunctuationEl() {
- const $punctuationEl = document.createElement('span');
- $punctuationEl.classList.add('govuk-visually-hidden', this.sectionHeadingDividerClass);
- $punctuationEl.textContent = ', ';
- return $punctuationEl;
+ const $element = createElement('span', {
+ class: 'govuk-visually-hidden govuk-accordion__section-heading-divider'
+ });
+ $element.textContent = ', ';
+ return $element;
}
}
diff --git a/packages/govuk-frontend/dist/govuk/components/accordion/accordion.mjs b/packages/govuk-frontend/dist/govuk/components/accordion/accordion.mjs
index 57b4b84d0..5e367ea04 100644
--- a/packages/govuk-frontend/dist/govuk/components/accordion/accordion.mjs
+++ b/packages/govuk-frontend/dist/govuk/components/accordion/accordion.mjs
@@ -1,7 +1,20 @@
import { ConfigurableComponent } from '../../common/configuration.mjs';
+import { createElement } from '../../common/create-element.mjs';
import { ElementError } from '../../errors/index.mjs';
import { I18n } from '../../i18n.mjs';
+const sectionClass = 'govuk-accordion__section';
+const sectionExpandedModifier = 'govuk-accordion__section--expanded';
+const sectionButtonClass = 'govuk-accordion__section-button';
+const sectionHeaderClass = 'govuk-accordion__section-header';
+const sectionHeadingClass = 'govuk-accordion__section-heading';
+const sectionHeadingTextClass = 'govuk-accordion__section-heading-text';
+const sectionToggleTextClass = 'govuk-accordion__section-toggle-text';
+const iconClass = 'govuk-accordion-nav__chevron';
+const iconOpenModifier = 'govuk-accordion-nav__chevron--down';
+const sectionSummaryClass = 'govuk-accordion__section-summary';
+const sectionContentClass = 'govuk-accordion__section-content';
+
/**
* Accordion component
*
@@ -25,35 +38,16 @@ class Accordion extends ConfigurableComponent {
constructor($root, config = {}) {
super($root, config);
this.i18n = void 0;
- this.controlsClass = 'govuk-accordion__controls';
- this.showAllClass = 'govuk-accordion__show-all';
- this.showAllTextClass = 'govuk-accordion__show-all-text';
- this.sectionClass = 'govuk-accordion__section';
- this.sectionExpandedClass = 'govuk-accordion__section--expanded';
- this.sectionButtonClass = 'govuk-accordion__section-button';
- this.sectionHeaderClass = 'govuk-accordion__section-header';
- this.sectionHeadingClass = 'govuk-accordion__section-heading';
- this.sectionHeadingDividerClass = 'govuk-accordion__section-heading-divider';
- this.sectionHeadingTextClass = 'govuk-accordion__section-heading-text';
- this.sectionHeadingTextFocusClass = 'govuk-accordion__section-heading-text-focus';
- this.sectionShowHideToggleClass = 'govuk-accordion__section-toggle';
- this.sectionShowHideToggleFocusClass = 'govuk-accordion__section-toggle-focus';
- this.sectionShowHideTextClass = 'govuk-accordion__section-toggle-text';
- this.upChevronIconClass = 'govuk-accordion-nav__chevron';
- this.downChevronIconClass = 'govuk-accordion-nav__chevron--down';
- this.sectionSummaryClass = 'govuk-accordion__section-summary';
- this.sectionSummaryFocusClass = 'govuk-accordion__section-summary-focus';
- this.sectionContentClass = 'govuk-accordion__section-content';
this.$sections = void 0;
this.$showAllButton = null;
this.$showAllIcon = null;
this.$showAllText = null;
this.i18n = new I18n(this.config.i18n);
- const $sections = this.$root.querySelectorAll(`.${this.sectionClass}`);
+ const $sections = this.$root.querySelectorAll(`.${sectionClass}`);
if (!$sections.length) {
throw new ElementError({
component: Accordion,
- identifier: `Sections (\`<div class="${this.sectionClass}">\`)`
+ identifier: `Sections (\`<div class="${sectionClass}">\`)`
});
}
this.$sections = $sections;
@@ -62,19 +56,23 @@ class Accordion extends ConfigurableComponent {
this.updateShowAllButton(this.areAllSectionsOpen());
}
initControls() {
- this.$showAllButton = document.createElement('button');
- this.$showAllButton.setAttribute('type', 'button');
- this.$showAllButton.setAttribute('class', this.showAllClass);
- this.$showAllButton.setAttribute('aria-expanded', 'false');
- this.$showAllIcon = document.createElement('span');
- this.$showAllIcon.classList.add(this.upChevronIconClass);
+ this.$showAllButton = createElement('button', {
+ type: 'button',
+ class: 'govuk-accordion__show-all',
+ 'aria-expanded': 'false'
+ });
+ this.$showAllIcon = createElement('span', {
+ class: iconClass
+ });
this.$showAllButton.appendChild(this.$showAllIcon);
- const $accordionControls = document.createElement('div');
- $accordionControls.setAttribute('class', this.controlsClass);
+ const $accordionControls = createElement('div', {
+ class: 'govuk-accordion__controls'
+ });
$accordionControls.appendChild(this.$showAllButton);
this.$root.insertBefore($accordionControls, this.$root.firstChild);
- this.$showAllText = document.createElement('span');
- this.$showAllText.classList.add(this.showAllTextClass);
+ this.$showAllText = createElement('span', {
+ class: 'govuk-accordion__show-all-text'
+ });
this.$showAllButton.appendChild(this.$showAllText);
this.$showAllButton.addEventListener('click', () => this.onShowOrHideAllToggle());
if ('onbeforematch' in document) {
@@ -83,11 +81,11 @@ class Accordion extends ConfigurableComponent {
}
initSectionHeaders() {
this.$sections.forEach(($section, i) => {
- const $header = $section.querySelector(`.${this.sectionHeaderClass}`);
+ const $header = $section.querySelector(`.${sectionHeaderClass}`);
if (!$header) {
throw new ElementError({
component: Accordion,
- identifier: `Section headers (\`<div class="${this.sectionHeaderClass}">\`)`
+ identifier: `Section headers (\`<div class="${sectionHeaderClass}">\`)`
});
}
this.constructHeaderMarkup($header, i);
@@ -97,73 +95,105 @@ class Accordion extends ConfigurableComponent {
});
}
constructHeaderMarkup($header, index) {
- const $span = $header.querySelector(`.${this.sectionButtonClass}`);
- const $heading = $header.querySelector(`.${this.sectionHeadingClass}`);
- const $summary = $header.querySelector(`.${this.sectionSummaryClass}`);
+ const $span = $header.querySelector(`.${sectionButtonClass}`);
+ const $heading = $header.querySelector(`.${sectionHeadingClass}`);
+ const $summary = $header.querySelector(`.${sectionSummaryClass}`);
if (!$heading) {
throw new ElementError({
component: Accordion,
- identifier: `Section heading (\`.${this.sectionHeadingClass}\`)`
+ identifier: `Section heading (\`.${sectionHeadingClass}\`)`
});
}
if (!$span) {
throw new ElementError({
component: Accordion,
- identifier: `Section button placeholder (\`<span class="${this.sectionButtonClass}">\`)`
+ identifier: `Section button placeholder (\`<span class="${sectionButtonClass}">\`)`
});
}
- const $button = document.createElement('button');
- $button.setAttribute('type', 'button');
- $button.setAttribute('aria-controls', `${this.$root.id}-content-${index + 1}`);
+ const $button = createElement('button', {
+ type: 'button',
+ 'aria-controls': `${this.$root.id}-content-${index + 1}`
+ });
for (const attr of Array.from($span.attributes)) {
if (attr.name !== 'id') {
$button.setAttribute(attr.name, attr.value);
}
}
- const $headingText = document.createElement('span');
- $headingText.classList.add(this.sectionHeadingTextClass);
- $headingText.id = $span.id;
- const $headingTextFocus = document.createElement('span');
- $headingTextFocus.classList.add(this.sectionHeadingTextFocusClass);
- $headingText.appendChild($headingTextFocus);
- Array.from($span.childNodes).forEach($child => $headingTextFocus.appendChild($child));
- const $showHideToggle = document.createElement('span');
- $showHideToggle.classList.add(this.sectionShowHideToggleClass);
- $showHideToggle.setAttribute('data-nosnippet', '');
- const $showHideToggleFocus = document.createElement('span');
- $showHideToggleFocus.classList.add(this.sectionShowHideToggleFocusClass);
- $showHideToggle.appendChild($showHideToggleFocus);
- const $showHideText = document.createElement('span');
- const $showHideIcon = document.createElement('span');
- $showHideIcon.classList.add(this.upChevronIconClass);
- $showHideToggleFocus.appendChild($showHideIcon);
- $showHideText.classList.add(this.sectionShowHideTextClass);
- $showHideToggleFocus.appendChild($showHideText);
- $button.appendChild($headingText);
+ $button.appendChild(this.createHeadingText($span));
$button.appendChild(this.getButtonPunctuationEl());
if ($summary) {
- const $summarySpan = document.createElement('span');
- const $summarySpanFocus = document.createElement('span');
- $summarySpanFocus.classList.add(this.sectionSummaryFocusClass);
- $summarySpan.appendChild($summarySpanFocus);
- for (const attr of Array.from($summary.attributes)) {
- $summarySpan.setAttribute(attr.name, attr.value);
- }
- Array.from($summary.childNodes).forEach($child => $summarySpanFocus.appendChild($child));
$summary.remove();
- $button.appendChild($summarySpan);
+ $button.appendChild(this.createSummarySpan($summary));
$button.appendChild(this.getButtonPunctuationEl());
}
- $button.appendChild($showHideToggle);
+ $button.appendChild(this.createShowHideToggle());
$heading.removeChild($span);
$heading.appendChild($button);
}
+
+ /**
+ * Creates a `<span>` rendering the 'Show'/'Hide' toggle
+ *
+ * @returns {HTMLSpanElement} - The `<span>` with the visual representation of the 'Show/Hide' toggle
+ */
+ createShowHideToggle() {
+ const $showHideToggleFocus = createElement('span', {
+ class: 'govuk-accordion__section-toggle-focus'
+ }, [createElement('span', {
+ class: iconClass
+ }), createElement('span', {
+ class: sectionToggleTextClass
+ })]);
+ const $showHideToggle = createElement('span', {
+ class: 'govuk-accordion__section-toggle',
+ 'data-nosnippet': ''
+ }, [$showHideToggleFocus]);
+ return $showHideToggle;
+ }
+
+ /**
+ * Creates the `<span>` containing the text of the section's heading
+ *
+ * @param {Element} $span - The heading of the span
+ * @returns {HTMLSpanElement} - The `<span>` containing the text of the section's heading
+ */
+ createHeadingText($span) {
+ const $headingTextFocus = createElement('span', {
+ class: 'govuk-accordion__section-heading-text-focus'
+ }, Array.from($span.childNodes));
+ const $headingText = createElement('span', {
+ class: sectionHeadingTextClass,
+ id: $span.id
+ }, [$headingTextFocus]);
+ return $headingText;
+ }
+
+ /**
+ * Creates the `<span>` element with the summary for the section
+ *
+ * This is necessary because the summary line text is now inside
+ * a button element, which can only contain phrasing content, and
+ * not a `<div>` element
+ *
+ * @param {Element} $summary - The original `<div>` containing the summary
+ * @returns {HTMLSpanElement} - The `<span>` element containing the summary
+ */
+ createSummarySpan($summary) {
+ const $summarySpanFocus = createElement('span', {
+ class: 'govuk-accordion__section-summary-focus'
+ }, Array.from($summary.childNodes));
+ const $summarySpan = createElement('span', {}, [$summarySpanFocus]);
+ for (const attr of Array.from($summary.attributes)) {
+ $summarySpan.setAttribute(attr.name, attr.value);
+ }
+ return $summarySpan;
+ }
onBeforeMatch(event) {
const $fragment = event.target;
if (!($fragment instanceof Element)) {
return;
}
- const $section = $fragment.closest(`.${this.sectionClass}`);
+ const $section = $fragment.closest(`.${sectionClass}`);
if ($section) {
this.setExpanded(true, $section);
}
@@ -182,14 +212,14 @@ class Accordion extends ConfigurableComponent {
this.updateShowAllButton(nowExpanded);
}
setExpanded(expanded, $section) {
- const $showHideIcon = $section.querySelector(`.${this.upChevronIconClass}`);
- const $showHideText = $section.querySelector(`.${this.sectionShowHideTextClass}`);
- const $button = $section.querySelector(`.${this.sectionButtonClass}`);
- const $content = $section.querySelector(`.${this.sectionContentClass}`);
+ const $showHideIcon = $section.querySelector(`.${iconClass}`);
+ const $showHideText = $section.querySelector(`.${sectionToggleTextClass}`);
+ const $button = $section.querySelector(`.${sectionButtonClass}`);
+ const $content = $section.querySelector(`.${sectionContentClass}`);
if (!$content) {
throw new ElementError({
component: Accordion,
- identifier: `Section content (\`<div class="${this.sectionContentClass}">\`)`
+ identifier: `Section content (\`<div class="${sectionContentClass}">\`)`
});
}
if (!$showHideIcon || !$showHideText || !$button) {
@@ -199,11 +229,11 @@ class Accordion extends ConfigurableComponent {
$showHideText.textContent = newButtonText;
$button.setAttribute('aria-expanded', `${expanded}`);
const ariaLabelParts = [];
- const $headingText = $section.querySelector(`.${this.sectionHeadingTextClass}`);
+ const $headingText = $section.querySelector(`.${sectionHeadingTextClass}`);
if ($headingText) {
ariaLabelParts.push(`${$headingText.textContent}`.trim());
}
- const $summary = $section.querySelector(`.${this.sectionSummaryClass}`);
+ const $summary = $section.querySelector(`.${sectionSummaryClass}`);
if ($summary) {
ariaLabelParts.push(`${$summary.textContent}`.trim());
}
@@ -212,17 +242,17 @@ class Accordion extends ConfigurableComponent {
$button.setAttribute('aria-label', ariaLabelParts.join(' , '));
if (expanded) {
$content.removeAttribute('hidden');
- $section.classList.add(this.sectionExpandedClass);
- $showHideIcon.classList.remove(this.downChevronIconClass);
+ $section.classList.add(sectionExpandedModifier);
+ $showHideIcon.classList.remove(iconOpenModifier);
} else {
$content.setAttribute('hidden', 'until-found');
- $section.classList.remove(this.sectionExpandedClass);
- $showHideIcon.classList.add(this.downChevronIconClass);
+ $section.classList.remove(sectionExpandedModifier);
+ $showHideIcon.classList.add(iconOpenModifier);
}
this.updateShowAllButton(this.areAllSectionsOpen());
}
isExpanded($section) {
- return $section.classList.contains(this.sectionExpandedClass);
+ return $section.classList.contains(sectionExpandedModifier);
}
areAllSectionsOpen() {
return Array.from(this.$sections).every($section => this.isExpanded($section));
@@ -233,7 +263,7 @@ class Accordion extends ConfigurableComponent {
}
this.$showAllButton.setAttribute('aria-expanded', expanded.toString());
this.$showAllText.textContent = expanded ? this.i18n.t('hideAllSections') : this.i18n.t('showAllSections');
- this.$showAllIcon.classList.toggle(this.downChevronIconClass, !expanded);
+ this.$showAllIcon.classList.toggle(iconOpenModifier, !expanded);
}
/**
@@ -247,7 +277,7 @@ class Accordion extends ConfigurableComponent {
* @returns {string | undefined | null} Identifier for section
*/
getIdentifier($section) {
- const $button = $section.querySelector(`.${this.sectionButtonClass}`);
+ const $button = $section.querySelector(`.${sectionButtonClass}`);
return $button == null ? void 0 : $button.getAttribute('aria-controls');
}
storeState($section, isExpanded) {
@@ -276,10 +306,11 @@ class Accordion extends ConfigurableComponent {
}
}
getButtonPunctuationEl() {
- const $punctuationEl = document.createElement('span');
- $punctuationEl.classList.add('govuk-visually-hidden', this.sectionHeadingDividerClass);
- $punctuationEl.textContent = ', ';
- return $punctuationEl;
+ const $element = createElement('span', {
+ class: 'govuk-visually-hidden govuk-accordion__section-heading-divider'
+ });
+ $element.textContent = ', ';
+ return $element;
}
}
diff --git a/packages/govuk-frontend/dist/govuk/components/exit-this-page/exit-this-page.bundle.js b/packages/govuk-frontend/dist/govuk/components/exit-this-page/exit-this-page.bundle.js
index 7268abbf0..92f35dc0b 100644
--- a/packages/govuk-frontend/dist/govuk/components/exit-this-page/exit-this-page.bundle.js
+++ b/packages/govuk-frontend/dist/govuk/components/exit-this-page/exit-this-page.bundle.js
@@ -283,6 +283,14 @@
* @typedef {typeof GOVUKFrontendComponent & ChildClass<ConfigurationType>} ChildClassConstructor<ConfigurationType>
*/
+ function createElement(tagName, attributes = {}, children) {
+ const el = document.createElement(tagName);
+ Object.entries(attributes).forEach(([name, value]) => {
+ el.setAttribute(name, value);
+ });
+ return el;
+ }
+
class I18n {
constructor(translations = {}, config = {}) {
var _config$locale;
@@ -525,9 +533,10 @@
window.addEventListener('pageshow', this.resetPage.bind(this));
}
initUpdateSpan() {
- this.$updateSpan = document.createElement('span');
- this.$updateSpan.setAttribute('role', 'status');
- this.$updateSpan.className = 'govuk-visually-hidden';
+ this.$updateSpan = createElement('span', {
+ role: 'status',
+ class: 'govuk-visually-hidden'
+ });
this.$root.appendChild(this.$updateSpan);
}
initButtonClickHandler() {
@@ -537,13 +546,14 @@
}
}
buildIndicator() {
- this.$indicatorContainer = document.createElement('div');
- this.$indicatorContainer.className = 'govuk-exit-this-page__indicator';
- this.$indicatorContainer.setAttribute('aria-hidden', 'true');
+ this.$indicatorContainer = createElement('div', {
+ class: 'govuk-exit-this-page__indicator',
+ 'aria-hidden': 'true'
+ });
for (let i = 0; i < 3; i++) {
- const $indicator = document.createElement('div');
- $indicator.className = 'govuk-exit-this-page__indicator-light';
- this.$indicatorContainer.appendChild($indicator);
+ this.$indicatorContainer.appendChild(createElement('div', {
+ class: 'govuk-exit-this-page__indicator-light'
+ }));
}
this.$button.appendChild(this.$indicatorContainer);
}
@@ -563,9 +573,10 @@
}
this.$updateSpan.textContent = '';
document.body.classList.add('govuk-exit-this-page-hide-content');
- this.$overlay = document.createElement('div');
- this.$overlay.className = 'govuk-exit-this-page-overlay';
- this.$overlay.setAttribute('role', 'alert');
+ this.$overlay = createElement('div', {
+ class: 'govuk-exit-this-page-overlay',
+ role: 'alert'
+ });
document.body.appendChild(this.$overlay);
this.$overlay.textContent = this.i18n.t('activated');
window.location.href = this.$button.href;
diff --git a/packages/govuk-frontend/dist/govuk/components/exit-this-page/exit-this-page.bundle.mjs b/packages/govuk-frontend/dist/govuk/components/exit-this-page/exit-this-page.bundle.mjs
index bba2c7eae..ab05e73b1 100644
--- a/packages/govuk-frontend/dist/govuk/components/exit-this-page/exit-this-page.bundle.mjs
+++ b/packages/govuk-frontend/dist/govuk/components/exit-this-page/exit-this-page.bundle.mjs
@@ -277,6 +277,14 @@ function extractConfigByNamespace(schema, dataset, namespace) {
* @typedef {typeof GOVUKFrontendComponent & ChildClass<ConfigurationType>} ChildClassConstructor<ConfigurationType>
*/
+function createElement(tagName, attributes = {}, children) {
+ const el = document.createElement(tagName);
+ Object.entries(attributes).forEach(([name, value]) => {
+ el.setAttribute(name, value);
+ });
+ return el;
+}
+
class I18n {
constructor(translations = {}, config = {}) {
var _config$locale;
@@ -519,9 +527,10 @@ class ExitThisPage extends ConfigurableComponent {
window.addEventListener('pageshow', this.resetPage.bind(this));
}
initUpdateSpan() {
- this.$updateSpan = document.createElement('span');
- this.$updateSpan.setAttribute('role', 'status');
- this.$updateSpan.className = 'govuk-visually-hidden';
+ this.$updateSpan = createElement('span', {
+ role: 'status',
+ class: 'govuk-visually-hidden'
+ });
this.$root.appendChild(this.$updateSpan);
}
initButtonClickHandler() {
@@ -531,13 +540,14 @@ class ExitThisPage extends ConfigurableComponent {
}
}
buildIndicator() {
- this.$indicatorContainer = document.createElement('div');
- this.$indicatorContainer.className = 'govuk-exit-this-page__indicator';
- this.$indicatorContainer.setAttribute('aria-hidden', 'true');
+ this.$indicatorContainer = createElement('div', {
+ class: 'govuk-exit-this-page__indicator',
+ 'aria-hidden': 'true'
+ });
for (let i = 0; i < 3; i++) {
- const $indicator = document.createElement('div');
- $indicator.className = 'govuk-exit-this-page__indicator-light';
- this.$indicatorContainer.appendChild($indicator);
+ this.$indicatorContainer.appendChild(createElement('div', {
+ class: 'govuk-exit-this-page__indicator-light'
+ }));
}
this.$button.appendChild(this.$indicatorContainer);
}
@@ -557,9 +567,10 @@ class ExitThisPage extends ConfigurableComponent {
}
this.$updateSpan.textContent = '';
document.body.classList.add('govuk-exit-this-page-hide-content');
- this.$overlay = document.createElement('div');
- this.$overlay.className = 'govuk-exit-this-page-overlay';
- this.$overlay.setAttribute('role', 'alert');
+ this.$overlay = createElement('div', {
+ class: 'govuk-exit-this-page-overlay',
+ role: 'alert'
+ });
document.body.appendChild(this.$overlay);
this.$overlay.textContent = this.i18n.t('activated');
window.location.href = this.$button.href;
diff --git a/packages/govuk-frontend/dist/govuk/components/exit-this-page/exit-this-page.mjs b/packages/govuk-frontend/dist/govuk/components/exit-this-page/exit-this-page.mjs
index 7e6bc888e..bf3fda9a9 100644
--- a/packages/govuk-frontend/dist/govuk/components/exit-this-page/exit-this-page.mjs
+++ b/packages/govuk-frontend/dist/govuk/components/exit-this-page/exit-this-page.mjs
@@ -1,4 +1,5 @@
import { ConfigurableComponent } from '../../common/configuration.mjs';
+import { createElement } from '../../common/create-element.mjs';
import { ElementError } from '../../errors/index.mjs';
import { I18n } from '../../i18n.mjs';
@@ -51,9 +52,10 @@ class ExitThisPage extends ConfigurableComponent {
window.addEventListener('pageshow', this.resetPage.bind(this));
}
initUpdateSpan() {
- this.$updateSpan = document.createElement('span');
- this.$updateSpan.setAttribute('role', 'status');
- this.$updateSpan.className = 'govuk-visually-hidden';
+ this.$updateSpan = createElement('span', {
+ role: 'status',
+ class: 'govuk-visually-hidden'
+ });
this.$root.appendChild(this.$updateSpan);
}
initButtonClickHandler() {
@@ -63,13 +65,14 @@ class ExitThisPage extends ConfigurableComponent {
}
}
buildIndicator() {
- this.$indicatorContainer = document.createElement('div');
- this.$indicatorContainer.className = 'govuk-exit-this-page__indicator';
- this.$indicatorContainer.setAttribute('aria-hidden', 'true');
+ this.$indicatorContainer = createElement('div', {
+ class: 'govuk-exit-this-page__indicator',
+ 'aria-hidden': 'true'
+ });
for (let i = 0; i < 3; i++) {
- const $indicator = document.createElement('div');
- $indicator.className = 'govuk-exit-this-page__indicator-light';
- this.$indicatorContainer.appendChild($indicator);
+ this.$indicatorContainer.appendChild(createElement('div', {
+ class: 'govuk-exit-this-page__indicator-light'
+ }));
}
this.$button.appendChild(this.$indicatorContainer);
}
@@ -89,9 +92,10 @@ class ExitThisPage extends ConfigurableComponent {
}
this.$updateSpan.textContent = '';
document.body.classList.add('govuk-exit-this-page-hide-content');
- this.$overlay = document.createElement('div');
- this.$overlay.className = 'govuk-exit-this-page-overlay';
- this.$overlay.setAttribute('role', 'alert');
+ this.$overlay = createElement('div', {
+ class: 'govuk-exit-this-page-overlay',
+ role: 'alert'
+ });
document.body.appendChild(this.$overlay);
this.$overlay.textContent = this.i18n.t('activated');
window.location.href = this.$button.href;
diff --git a/packages/govuk-frontend/dist/govuk/components/password-input/password-input.bundle.js b/packages/govuk-frontend/dist/govuk/components/password-input/password-input.bundle.js
index 68ac91f75..c9f0764e1 100644
--- a/packages/govuk-frontend/dist/govuk/components/password-input/password-input.bundle.js
+++ b/packages/govuk-frontend/dist/govuk/components/password-input/password-input.bundle.js
@@ -288,6 +288,14 @@
* @typedef {typeof GOVUKFrontendComponent & ChildClass<ConfigurationType>} ChildClassConstructor<ConfigurationType>
*/
+ function createElement(tagName, attributes = {}, children) {
+ const el = document.createElement(tagName);
+ Object.entries(attributes).forEach(([name, value]) => {
+ el.setAttribute(name, value);
+ });
+ return el;
+ }
+
class I18n {
constructor(translations = {}, config = {}) {
var _config$locale;
@@ -528,9 +536,10 @@
locale: closestAttributeValue(this.$root, 'lang')
});
this.$showHideButton.removeAttribute('hidden');
- const $screenReaderStatusMessage = document.createElement('div');
- $screenReaderStatusMessage.className = 'govuk-password-input__sr-status govuk-visually-hidden';
- $screenReaderStatusMessage.setAttribute('aria-live', 'polite');
+ const $screenReaderStatusMessage = createElement('div', {
+ class: 'govuk-password-input__sr-status govuk-visually-hidden',
+ 'aria-live': 'polite'
+ });
this.$screenReaderStatusMessage = $screenReaderStatusMessage;
this.$input.insertAdjacentElement('afterend', $screenReaderStatusMessage);
this.$showHideButton.addEventListener('click', this.toggle.bind(this));
diff --git a/packages/govuk-frontend/dist/govuk/components/password-input/password-input.bundle.mjs b/packages/govuk-frontend/dist/govuk/components/password-input/password-input.bundle.mjs
index 768df71f6..105580dcc 100644
--- a/packages/govuk-frontend/dist/govuk/components/password-input/password-input.bundle.mjs
+++ b/packages/govuk-frontend/dist/govuk/components/password-input/password-input.bundle.mjs
@@ -282,6 +282,14 @@ function extractConfigByNamespace(schema, dataset, namespace) {
* @typedef {typeof GOVUKFrontendComponent & ChildClass<ConfigurationType>} ChildClassConstructor<ConfigurationType>
*/
+function createElement(tagName, attributes = {}, children) {
+ const el = document.createElement(tagName);
+ Object.entries(attributes).forEach(([name, value]) => {
+ el.setAttribute(name, value);
+ });
+ return el;
+}
+
class I18n {
constructor(translations = {}, config = {}) {
var _config$locale;
@@ -522,9 +530,10 @@ class PasswordInput extends ConfigurableComponent {
locale: closestAttributeValue(this.$root, 'lang')
});
this.$showHideButton.removeAttribute('hidden');
- const $screenReaderStatusMessage = document.createElement('div');
- $screenReaderStatusMessage.className = 'govuk-password-input__sr-status govuk-visually-hidden';
- $screenReaderStatusMessage.setAttribute('aria-live', 'polite');
+ const $screenReaderStatusMessage = createElement('div', {
+ class: 'govuk-password-input__sr-status govuk-visually-hidden',
+ 'aria-live': 'polite'
+ });
this.$screenReaderStatusMessage = $screenReaderStatusMessage;
this.$input.insertAdjacentElement('afterend', $screenReaderStatusMessage);
this.$showHideButton.addEventListener('click', this.toggle.bind(this));
diff --git a/packages/govuk-frontend/dist/govuk/components/password-input/password-input.mjs b/packages/govuk-frontend/dist/govuk/components/password-input/password-input.mjs
index 6e23e64b1..e622bfd83 100644
--- a/packages/govuk-frontend/dist/govuk/components/password-input/password-input.mjs
+++ b/packages/govuk-frontend/dist/govuk/components/password-input/password-input.mjs
@@ -1,5 +1,6 @@
import { closestAttributeValue } from '../../common/closest-attribute-value.mjs';
import { ConfigurableComponent } from '../../common/configuration.mjs';
+import { createElement } from '../../common/create-element.mjs';
import { ElementError } from '../../errors/index.mjs';
import { I18n } from '../../i18n.mjs';
@@ -50,9 +51,10 @@ class PasswordInput extends ConfigurableComponent {
locale: closestAttributeValue(this.$root, 'lang')
});
this.$showHideButton.removeAttribute('hidden');
- const $screenReaderStatusMessage = document.createElement('div');
- $screenReaderStatusMessage.className = 'govuk-password-input__sr-status govuk-visually-hidden';
- $screenReaderStatusMessage.setAttribute('aria-live', 'polite');
+ const $screenReaderStatusMessage = createElement('div', {
+ class: 'govuk-password-input__sr-status govuk-visually-hidden',
+ 'aria-live': 'polite'
+ });
this.$screenReaderStatusMessage = $screenReaderStatusMessage;
this.$input.insertAdjacentElement('afterend', $screenReaderStatusMessage);
this.$showHideButton.addEventListener('click', this.toggle.bind(this));
Action run for d7f1a99 |
Allows regrouping code for creating components a little more, as well as getting rid of a couple of loops over child nodes
romaricpascal
force-pushed
the
accordion-section-component
branch
from
December 17, 2024 15:06
764dda8
to
d7f1a99
Compare
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Note
Best reviewed commit by commit
Building upon works from @36degrees to introduce a
createElement
function, this PR aims to explore a couple of refactorings that look useful for the accordion.Especially, the end goal would be to introduce an
AccordionSection
class representing each section of theAccordion
an properly split responsibilities between:Accordion
, responsible for controlling all sections at once (and the initial opening state)AccordionSection
responsible for controlling an individual sectionImportant
This PR is far from being ready for review, just chipping at things at the moment
The overall plan is to:
children
argument ofcreateElement
, as well as create a reverse pyramid mirroring the structure of the DOM being createdAccordionSection
class with initial aim to store key elements of the section for reuse (insetExpanded
, for example) without having to query the DOM againAccordionSection
as anexpanded
getterAccordionSection
as anexpanded
setterAccordionSection
beforematch
and us storing state client side whenrememberExpanded
is set).