-
Notifications
You must be signed in to change notification settings - Fork 338
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
+270
−186
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
📋 StatsFile sizes
Modules
View stats and visualisations on the review app Action run for 6c61aa5 |
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 40ce6ccf5..2301d0305 100644
--- a/packages/govuk-frontend/dist/govuk/govuk-frontend.min.js
+++ b/packages/govuk-frontend/dist/govuk/govuk-frontend.min.js
@@ -13,20 +13,20 @@ function getBreakpoint(t) {
}
function setFocus(t, e = {}) {
- var s;
- const n = t.getAttribute("tabindex");
+ var n;
+ const i = t.getAttribute("tabindex");
function onBlur() {
- var s;
- null == (s = e.onBlur) || s.call(t), n || t.removeAttribute("tabindex")
+ var n;
+ null == (n = e.onBlur) || n.call(t), i || t.removeAttribute("tabindex")
}
- n || t.setAttribute("tabindex", "-1"), t.addEventListener("focus", (function() {
+ i || t.setAttribute("tabindex", "-1"), t.addEventListener("focus", (function() {
t.addEventListener("blur", onBlur, {
once: !0
})
}), {
once: !0
- }), null == (s = e.onBeforeFocus) || s.call(t), t.focus()
+ }), null == (n = e.onBeforeFocus) || n.call(t), t.focus()
}
function isSupported(t = document.body) {
@@ -63,12 +63,12 @@ class ElementError extends GOVUKFrontendError {
let e = "string" == typeof t ? t : "";
if ("object" == typeof t) {
const {
- component: s,
- identifier: n,
- element: i,
- expectedType: o
+ component: n,
+ identifier: i,
+ element: o,
+ expectedType: s
} = t;
- e = n, e += i ? ` is not of type ${null!=o?o:"HTMLElement"}` : " not found", e = formatErrorMessage(s, e)
+ e = i, e += o ? ` is not of type ${null!=s?s:"HTMLElement"}` : " not found", e = formatErrorMessage(n, e)
}
super(e), this.name = "ElementError"
}
@@ -93,8 +93,8 @@ class Component {
expectedType: e.elementType.name
});
this._$root = t, e.checkSupport(), this.checkInitialised();
- const s = e.moduleName;
- this.$root.setAttribute(`data-${s}-init`, "")
+ const n = e.moduleName;
+ this.$root.setAttribute(`data-${n}-init`, "")
}
checkInitialised() {
const t = this.constructor,
@@ -116,91 +116,100 @@ class ConfigurableComponent extends Component {
get config() {
return this._config
}
- constructor(e, s) {
+ constructor(e, n) {
super(e), this._config = void 0;
- const n = this.constructor;
- if (!isObject(n.defaults)) throw new ConfigError(formatErrorMessage(n, "Config passed as parameter into constructor but no defaults defined"));
- const i = function(Component, t) {
+ const i = this.constructor;
+ if (!isObject(i.defaults)) throw new ConfigError(formatErrorMessage(i, "Config passed as parameter into constructor but no defaults defined"));
+ const o = function(Component, t) {
if (!isObject(Component.schema)) throw new ConfigError(formatErrorMessage(Component, "Config passed as parameter into constructor but no schema defined"));
const e = {},
- s = Object.entries(Component.schema.properties);
- for (const n of s) {
- const [s, i] = n, o = s.toString();
- o in t && (e[o] = normaliseString(t[o], i)), "object" === (null == i ? void 0 : i.type) && (e[o] = extractConfigByNamespace(Component.schema, t, s))
+ n = Object.entries(Component.schema.properties);
+ for (const i of n) {
+ const [n, o] = i, s = n.toString();
+ s in t && (e[s] = normaliseString(t[s], o)), "object" === (null == o ? void 0 : o.type) && (e[s] = extractConfigByNamespace(Component.schema, t, n))
}
return e
- }(n, this._$root.dataset);
- this._config = mergeConfigs(n.defaults, null != s ? s : {}, 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 s = t ? t.trim() : "";
- let n, i = null == e ? void 0 : e.type;
- switch (i || (["true", "false"].includes(s) && (i = "boolean"), s.length > 0 && isFinite(Number(s)) && (i = "number")), i) {
+ const n = t ? t.trim() : "";
+ 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":
- n = "true" === s;
+ i = "true" === n;
break;
case "number":
- n = Number(s);
+ i = Number(n);
break;
default:
- n = t
+ i = t
}
- return n
+ return i
}
function mergeConfigs(...t) {
const e = {};
- for (const s of t)
- for (const t of Object.keys(s)) {
- const n = e[t],
- i = s[t];
- isObject(n) && isObject(i) ? e[t] = mergeConfigs(n, i) : e[t] = i
+ for (const n of t)
+ for (const t of Object.keys(n)) {
+ 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, s) {
- const n = t.properties[s];
- if ("object" !== (null == n ? void 0 : n.type)) return;
- const i = {
- [s]: {}
+function extractConfigByNamespace(t, e, n) {
+ 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 [n, i] of e.entries()) isObject(t) && (n < e.length - 1 ? (isObject(t[i]) || (t[i] = {}), t = t[i]) : o !== s && (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()) isObject(t) && (i < e.length - 1 ? (isObject(t[o]) || (t[o] = {}), t = t[o]) : s !== n && (t[o] = normaliseString(r)))
}
- return i[s]
+ 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 = {}) {
- var s;
- this.translations = void 0, this.locale = void 0, this.translations = t, this.locale = null != (s = e.locale) ? s : document.documentElement.lang || "en"
+ var n;
+ this.translations = void 0, this.locale = void 0, this.translations = t, this.locale = null != (n = e.locale) ? n : document.documentElement.lang || "en"
}
t(t, e) {
if (!t) throw new Error("i18n: lookup key missing");
- let s = this.translations[t];
- if ("number" == typeof(null == e ? void 0 : e.count) && "object" == typeof s) {
- const n = s[this.getPluralSuffix(t, e.count)];
- n && (s = n)
+ let n = this.translations[t];
+ if ("number" == typeof(null == e ? void 0 : e.count) && "object" == typeof n) {
+ const i = n[this.getPluralSuffix(t, e.count)];
+ i && (n = i)
}
- if ("string" == typeof s) {
- if (s.match(/%{(.\S+)}/)) {
+ if ("string" == typeof n) {
+ if (n.match(/%{(.\S+)}/)) {
if (!e) throw new Error("i18n: cannot replace placeholders in string if no option data provided");
- return this.replacePlaceholders(s, e)
+ return this.replacePlaceholders(n, e)
}
- return s
+ return n
}
return t
}
replacePlaceholders(t, e) {
- const s = Intl.NumberFormat.supportedLocalesOf(this.locale).length ? new Intl.NumberFormat(this.locale) : void 0;
- return t.replace(/%{(.\S+)}/g, (function(t, n) {
- if (Object.prototype.hasOwnProperty.call(e, n)) {
- const t = e[n];
- return !1 === t || "number" != typeof t && "string" != typeof t ? "" : "number" == typeof t ? s ? s.format(t) : `${t}` : t
+ const n = Intl.NumberFormat.supportedLocalesOf(this.locale).length ? new Intl.NumberFormat(this.locale) : void 0;
+ 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`)
}))
@@ -210,11 +219,11 @@ class I18n {
}
getPluralSuffix(t, e) {
if (e = Number(e), !isFinite(e)) return "other";
- const s = this.translations[t],
- n = this.hasIntlPluralRulesSupport() ? new Intl.PluralRules(this.locale).select(e) : this.selectPluralFormUsingFallbackRules(e);
- if ("object" == typeof s) {
- if (n in s) return n;
- if ("other" in s) return console.warn(`i18n: Missing plural form ".${n}" for "${this.locale}" locale. Falling back to ".other".`), "other"
+ const n = this.translations[t],
+ i = this.hasIntlPluralRulesSupport() ? new Intl.PluralRules(this.locale).select(e) : this.selectPluralFormUsingFallbackRules(e);
+ if ("object" == typeof n) {
+ 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`)
}
@@ -226,8 +235,8 @@ class I18n {
getPluralRulesForLocale() {
const t = this.locale.split("-")[0];
for (const e in I18n.pluralRulesMap) {
- const s = I18n.pluralRulesMap[e];
- if (s.includes(this.locale) || s.includes(t)) return e
+ const n = I18n.pluralRulesMap[e];
+ if (n.includes(this.locale) || n.includes(t)) return e
}
}
}
@@ -249,77 +258,113 @@ I18n.pluralRulesMap = {
irish: t => 1 === t ? "one" : 2 === t ? "two" : t >= 3 && t <= 6 ? "few" : t >= 7 && t <= 10 ? "many" : "other",
russian(t) {
const e = t % 100,
- s = e % 10;
- return 1 === s && 11 !== e ? "one" : s >= 2 && s <= 4 && !(e >= 12 && e <= 14) ? "few" : 0 === s || s >= 5 && s <= 9 || e >= 11 && e <= 14 ? "many" : "other"
+ n = e % 10;
+ return 1 === n && 11 !== e ? "one" : n >= 2 && n <= 4 && !(e >= 12 && e <= 14) ? "few" : 0 === n || n >= 5 && n <= 9 || e >= 11 && e <= 14 ? "many" : "other"
},
scottish: t => 1 === t || 11 === t ? "one" : 2 === t || 12 === t ? "two" : t >= 3 && t <= 10 || t >= 13 && t <= 19 ? "few" : "other",
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 s = this.$root.querySelectorAll(`.${this.sectionClass}`);
- if (!s.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 = s, 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 s = t.querySelector(`.${this.sectionHeaderClass}`);
- if (!s) throw new ElementError({
+ 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(s, e), this.setExpanded(this.isExpanded(t), t), s.addEventListener("click", (() => this.onSectionToggle(t))), this.setInitialState(t)
+ this.constructHeaderMarkup(n, e), this.setExpanded(this.isExpanded(t), t), n.addEventListener("click", (() => this.onSectionToggle(t))), this.setInitialState(t)
}))
}
constructHeaderMarkup(t, e) {
- const s = t.querySelector(`.${this.sectionButtonClass}`),
- n = t.querySelector(`.${this.sectionHeadingClass}`),
- i = t.querySelector(`.${this.sectionSummaryClass}`);
- if (!n) 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 (!s) throw new ElementError({
+ 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(s.attributes)) "id" !== d.name && o.setAttribute(d.name, d.value);
- const r = document.createElement("span");
- r.classList.add(this.sectionHeadingTextClass), r.id = s.id;
- const a = document.createElement("span");
- a.classList.add(this.sectionHeadingTextFocusClass), r.appendChild(a), Array.from(s.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 s of Array.from(i.attributes)) t.setAttribute(s.name, s.value);
- Array.from(i.childNodes).forEach((t => e.appendChild(t))), i.remove(), o.appendChild(t), o.appendChild(this.getButtonPunctuationEl())
- }
- o.appendChild(c), n.removeChild(s), n.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 s = e.closest(`.${this.sectionClass}`);
- s && this.setExpanded(!0, s)
+ 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);
@@ -332,56 +377,58 @@ class Accordion extends ConfigurableComponent {
})), this.updateShowAllButton(t)
}
setExpanded(t, e) {
- const s = e.querySelector(`.${this.upChevronIconClass}`),
- n = 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 (!s || !n || !i) return;
- const r = t ? this.i18n.t("hideSection") : this.i18n.t("showSection");
- n.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), s.classList.remove(this.downChevronIconClass)) : (o.setAttribute("hidden", "until-found"), e.classList.remove(this.sectionExpandedClass), s.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) {
if (!this.config.rememberExpanded) return;
- const s = this.getIdentifier(t);
- if (s) try {
- window.sessionStorage.setItem(s, e.toString())
- } catch (n) {}
+ const n = this.getIdentifier(t);
+ if (n) try {
+ window.sessionStorage.setItem(n, e.toString())
+ } catch (i) {}
}
setInitialState(t) {
if (!this.config.rememberExpanded) return;
const e = this.getIdentifier(t);
if (e) try {
- const s = window.sessionStorage.getItem(e);
- null !== s && this.setExpanded("true" === s, t)
- } catch (s) {}
+ const n = window.sessionStorage.getItem(e);
+ null !== n && this.setExpanded("true" === n, t)
+ } 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({
@@ -420,8 +467,8 @@ class Button extends ConfigurableComponent {
}
function closestAttributeValue(t, e) {
- const s = t.closest(`[${e}]`);
- return s ? s.getAttribute(e) : null
+ const n = t.closest(`[${e}]`);
+ return n ? n.getAttribute(e) : null
}
Button.moduleName = "govuk-button", Button.defaults = Object.freeze({
preventDoubleClick: !1
@@ -441,34 +488,34 @@ class CharacterCount extends ConfigurableComponent {
}), e
}
constructor(t, e = {}) {
- var s, n;
+ 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 = [];
- for (const [n, i] of Object.entries(t)) {
+ const s = function(t, e) {
+ const n = [];
+ for (const [i, o] of Object.entries(t)) {
const t = [];
- if (Array.isArray(i)) {
+ if (Array.isArray(o)) {
for (const {
- required: s,
- errorMessage: n
+ required: n,
+ errorMessage: i
}
- of i) s.every((t => !!e[t])) || t.push(n);
- "anyOf" !== n || i.length - t.length >= 1 || s.push(...t)
+ of o) n.every((t => !!e[t])) || t.push(i);
+ "anyOf" !== i || o.length - t.length >= 1 || n.push(...t)
}
}
- return s
+ 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 != (s = null != (n = this.config.maxwords) ? n : this.config.maxlength) ? s : 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({
@@ -525,8 +572,8 @@ class CharacterCount extends ConfigurableComponent {
}
formatCountMessage(t, e) {
if (0 === t) return this.i18n.t(`${e}AtLimit`);
- const s = t < 0 ? "OverLimit" : "UnderLimit";
- return this.i18n.t(`${e}${s}`, {
+ const n = t < 0 ? "OverLimit" : "UnderLimit";
+ return this.i18n.t(`${e}${n}`, {
count: Math.abs(t)
})
}
@@ -609,10 +656,10 @@ class Checkboxes extends Component {
syncConditionalRevealWithInputState(t) {
const e = t.getAttribute("aria-controls");
if (!e) return;
- const s = document.getElementById(e);
- if (null != s && s.classList.contains("govuk-checkboxes__conditional")) {
+ const n = document.getElementById(e);
+ if (null != n && n.classList.contains("govuk-checkboxes__conditional")) {
const e = t.checked;
- t.setAttribute("aria-expanded", e.toString()), s.classList.toggle("govuk-checkboxes__conditional--hidden", !e)
+ t.setAttribute("aria-expanded", e.toString()), n.classList.toggle("govuk-checkboxes__conditional--hidden", !e)
}
}
unCheckAllInputsExcept(t) {
@@ -645,25 +692,25 @@ class ErrorSummary extends ConfigurableComponent {
if (!(t instanceof HTMLAnchorElement)) return !1;
const e = getFragmentFromUrl(t.href);
if (!e) return !1;
- const s = document.getElementById(e);
- if (!s) return !1;
- const n = this.getAssociatedLegendOrLabel(s);
- return !!n && (n.scrollIntoView(), s.focus({
+ const n = document.getElementById(e);
+ if (!n) return !1;
+ const i = this.getAssociatedLegendOrLabel(n);
+ return !!i && (i.scrollIntoView(), n.focus({
preventScroll: !0
}), !0)
}
getAssociatedLegendOrLabel(t) {
var e;
- const s = t.closest("fieldset");
- if (s) {
- const e = s.getElementsByTagName("legend");
+ const n = t.closest("fieldset");
+ if (n) {
+ const e = n.getElementsByTagName("legend");
if (e.length) {
- const s = e[0];
- if (t instanceof HTMLInputElement && ("checkbox" === t.type || "radio" === t.type)) return s;
- const n = s.getBoundingClientRect().top,
- i = t.getBoundingClientRect();
- if (i.height && window.innerHeight) {
- if (i.top + i.height - n < window.innerHeight / 2) return s
+ const n = e[0];
+ if (t instanceof HTMLInputElement && ("checkbox" === t.type || "radio" === t.type)) 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
}
}
}
@@ -682,29 +729,34 @@ ErrorSummary.moduleName = "govuk-error-summary", ErrorSummary.defaults = Object.
class ExitThisPage extends ConfigurableComponent {
constructor(t, e = {}) {
super(t, e), this.i18n = void 0, this.$button = void 0, this.$skiplinkButton = null, this.$updateSpan = null, this.$indicatorContainer = null, this.$overlay = null, this.keypressCounter = 0, this.lastKeyWasModified = !1, this.timeoutTime = 5e3, this.keypressTimeoutId = null, this.timeoutMessageId = null;
- const s = this.$root.querySelector(".govuk-exit-this-page__button");
- if (!(s instanceof HTMLAnchorElement)) throw new ElementError({
+ const n = this.$root.querySelector(".govuk-exit-this-page__button");
+ if (!(n instanceof HTMLAnchorElement)) throw new ElementError({
component: ExitThisPage,
- element: s,
+ element: n,
expectedType: "HTMLAnchorElement",
identifier: "Button (`.govuk-exit-this-page__button`)"
});
- this.i18n = new I18n(this.config.i18n), this.$button = s;
- const n = document.querySelector(".govuk-js-exit-this-page-skiplink");
- n instanceof HTMLAnchorElement && (this.$skiplinkButton = n), 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))
+ this.i18n = new I18n(this.config.i18n), this.$button = n;
+ 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() {
@@ -715,7 +767,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()
@@ -757,18 +812,18 @@ class Header extends Component {
super(t), this.$menuButton = void 0, this.$menu = void 0, this.menuIsOpen = !1, this.mql = null;
const e = this.$root.querySelector(".govuk-js-header-toggle");
if (!e) return this;
- const s = e.getAttribute("aria-controls");
- if (!s) throw new ElementError({
+ const n = e.getAttribute("aria-controls");
+ if (!n) throw new ElementError({
component: Header,
identifier: 'Navigation button (`<button class="govuk-js-header-toggle">`) attribute (`aria-controls`)'
});
- const n = document.getElementById(s);
- if (!n) throw new ElementError({
+ const i = document.getElementById(n);
+ if (!i) throw new ElementError({
component: Header,
- element: n,
- identifier: `Navigation (\`<ul id="${s}">\`)`
+ element: i,
+ identifier: `Navigation (\`<ul id="${n}">\`)`
});
- this.$menu = n, 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");
@@ -803,27 +858,30 @@ NotificationBanner.moduleName = "govuk-notification-banner", NotificationBanner.
class PasswordInput extends ConfigurableComponent {
constructor(t, e = {}) {
super(t, e), this.i18n = void 0, this.$input = void 0, this.$showHideButton = void 0, this.$screenReaderStatusMessage = void 0;
- const s = this.$root.querySelector(".govuk-js-password-input-input");
- if (!(s instanceof HTMLInputElement)) throw new ElementError({
+ const n = this.$root.querySelector(".govuk-js-password-input-input");
+ if (!(n instanceof HTMLInputElement)) throw new ElementError({
component: PasswordInput,
- element: s,
+ element: n,
expectedType: "HTMLInputElement",
identifier: "Form field (`.govuk-js-password-input-input`)"
});
- if ("password" !== s.type) throw new ElementError("Password input: Form field (`.govuk-js-password-input-input`) must be of type `password`.");
- const n = this.$root.querySelector(".govuk-js-password-input-toggle");
- if (!(n instanceof HTMLButtonElement)) throw new ElementError({
+ if ("password" !== n.type) throw new ElementError("Password input: Form field (`.govuk-js-password-input-input`) must be of type `password`.");
+ const i = this.$root.querySelector(".govuk-js-password-input-toggle");
+ if (!(i instanceof HTMLButtonElement)) throw new ElementError({
component: PasswordInput,
- element: n,
+ element: i,
expectedType: "HTMLButtonElement",
identifier: "Button (`.govuk-js-password-input-toggle`)"
});
- if ("button" !== n.type) throw new ElementError("Password input: Button (`.govuk-js-password-input-toggle`) must be of type `button`.");
- this.$input = s, this.$showHideButton = n, 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()
}
@@ -840,9 +898,9 @@ class PasswordInput extends ConfigurableComponent {
if (t === this.$input.type) return;
this.$input.setAttribute("type", t);
const e = "password" === t,
- s = e ? "show" : "hide",
- n = e ? "passwordHidden" : "passwordShown";
- this.$showHideButton.innerText = this.i18n.t(`${s}Password`), this.$showHideButton.setAttribute("aria-label", this.i18n.t(`${s}PasswordAriaLabel`)), this.$screenReaderStatusMessage.innerText = this.i18n.t(`${n}Announcement`)
+ n = e ? "show" : "hide",
+ 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({
@@ -886,21 +944,21 @@ class Radios extends Component {
syncConditionalRevealWithInputState(t) {
const e = t.getAttribute("aria-controls");
if (!e) return;
- const s = document.getElementById(e);
- if (null != s && s.classList.contains("govuk-radios__conditional")) {
+ const n = document.getElementById(e);
+ if (null != n && n.classList.contains("govuk-radios__conditional")) {
const e = t.checked;
- t.setAttribute("aria-expanded", e.toString()), s.classList.toggle("govuk-radios__conditional--hidden", !e)
+ t.setAttribute("aria-expanded", e.toString()), n.classList.toggle("govuk-radios__conditional--hidden", !e)
}
}
handleClick(t) {
const e = t.target;
if (!(e instanceof HTMLInputElement) || "radio" !== e.type) return;
- const s = document.querySelectorAll('input[type="radio"][aria-controls]'),
- n = e.form,
- i = e.name;
- s.forEach((t => {
- const e = t.form === n;
- t.name === i && e && this.syncConditionalRevealWithInputState(t)
+ const n = document.querySelectorAll('input[type="radio"][aria-controls]'),
+ i = e.form,
+ o = e.name;
+ n.forEach((t => {
+ const e = t.form === i;
+ t.name === o && e && this.syncConditionalRevealWithInputState(t)
}))
}
}
@@ -910,18 +968,18 @@ class ServiceNavigation extends Component {
super(t), this.$menuButton = void 0, this.$menu = void 0, this.menuIsOpen = !1, this.mql = null;
const e = this.$root.querySelector(".govuk-js-service-navigation-toggle");
if (!e) return this;
- const s = e.getAttribute("aria-controls");
- if (!s) throw new ElementError({
+ const n = e.getAttribute("aria-controls");
+ if (!n) throw new ElementError({
component: ServiceNavigation,
identifier: 'Navigation button (`<button class="govuk-js-service-navigation-toggle">`) attribute (`aria-controls`)'
});
- const n = document.getElementById(s);
- if (!n) throw new ElementError({
+ const i = document.getElementById(n);
+ if (!i) throw new ElementError({
component: ServiceNavigation,
- element: n,
- identifier: `Navigation (\`<ul id="${s}">\`)`
+ element: i,
+ identifier: `Navigation (\`<ul id="${n}">\`)`
});
- this.$menu = n, 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");
@@ -943,22 +1001,22 @@ class SkipLink extends Component {
constructor(t) {
var e;
super(t);
- const s = this.$root.hash,
- n = null != (e = this.$root.getAttribute("href")) ? e : "";
- let i;
+ const n = this.$root.hash,
+ 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="${n}"\`) 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(s);
- if (!o) throw new ElementError(`Skip link: Target link (\`href="${n}"\`) 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() {
@@ -980,17 +1038,17 @@ class Tabs extends Component {
identifier: 'Links (`<a class="govuk-tabs__tab">`)'
});
this.$tabs = e, this.boundTabClick = this.onTabClick.bind(this), this.boundTabKeydown = this.onTabKeydown.bind(this), this.boundOnHashChange = this.onHashChange.bind(this);
- const s = this.$root.querySelector(".govuk-tabs__list"),
- n = this.$root.querySelectorAll("li.govuk-tabs__list-item");
- if (!s) throw new ElementError({
+ const n = this.$root.querySelector(".govuk-tabs__list"),
+ i = this.$root.querySelectorAll("li.govuk-tabs__list-item");
+ if (!n) throw new ElementError({
component: Tabs,
identifier: 'List (`<ul class="govuk-tabs__list">`)'
});
- if (!n.length) throw new ElementError({
+ if (!i.length) throw new ElementError({
component: Tabs,
identifier: 'List items (`<li class="govuk-tabs__list-item">`)'
});
- this.$tabList = s, this.$tabListItems = n, this.setupResponsiveChecks()
+ this.$tabList = n, this.$tabListItems = i, this.setupResponsiveChecks()
}
setupResponsiveChecks() {
const t = getBreakpoint("tablet");
@@ -1026,8 +1084,8 @@ class Tabs extends Component {
e = this.getTab(t);
if (!e) return;
if (this.changingHash) return void(this.changingHash = !1);
- const s = this.getCurrentTab();
- s && (this.hideTab(s), this.showTab(e), e.focus())
+ const n = this.getCurrentTab();
+ n && (this.hideTab(n), this.showTab(e), e.focus())
}
hideTab(t) {
this.unhighlightTab(t), this.hidePanel(t)
@@ -1042,8 +1100,8 @@ class Tabs extends Component {
const e = getFragmentFromUrl(t.href);
if (!e) return;
t.setAttribute("id", `tab_${e}`), t.setAttribute("role", "tab"), t.setAttribute("aria-controls", e), t.setAttribute("aria-selected", "false"), t.setAttribute("tabindex", "-1");
- const s = this.getPanel(t);
- s && (s.setAttribute("role", "tabpanel"), s.setAttribute("aria-labelledby", t.id), s.classList.add(this.jsHiddenClass))
+ const n = this.getPanel(t);
+ n && (n.setAttribute("role", "tabpanel"), n.setAttribute("aria-labelledby", t.id), n.classList.add(this.jsHiddenClass))
}
unsetAttributes(t) {
t.removeAttribute("id"), t.removeAttribute("role"), t.removeAttribute("aria-controls"), t.removeAttribute("aria-selected"), t.removeAttribute("tabindex");
@@ -1052,14 +1110,14 @@ class Tabs extends Component {
}
onTabClick(t) {
const e = this.getCurrentTab(),
- s = t.currentTarget;
- e && s instanceof HTMLAnchorElement && (t.preventDefault(), this.hideTab(e), this.showTab(s), this.createHistoryEntry(s))
+ n = t.currentTarget;
+ e && n instanceof HTMLAnchorElement && (t.preventDefault(), this.hideTab(e), this.showTab(n), this.createHistoryEntry(n))
}
createHistoryEntry(t) {
const e = this.getPanel(t);
if (!e) return;
- const s = e.id;
- e.id = "", this.changingHash = !0, window.location.hash = s, e.id = s
+ const n = e.id;
+ e.id = "", this.changingHash = !0, window.location.hash = n, e.id = n
}
onTabKeydown(t) {
switch (t.key) {
@@ -1077,16 +1135,16 @@ class Tabs extends Component {
if (null == t || !t.parentElement) return;
const e = t.parentElement.nextElementSibling;
if (!e) return;
- const s = e.querySelector("a.govuk-tabs__tab");
- s && (this.hideTab(t), this.showTab(s), s.focus(), this.createHistoryEntry(s))
+ const n = e.querySelector("a.govuk-tabs__tab");
+ n && (this.hideTab(t), this.showTab(n), n.focus(), this.createHistoryEntry(n))
}
activatePreviousTab() {
const t = this.getCurrentTab();
if (null == t || !t.parentElement) return;
const e = t.parentElement.previousElementSibling;
if (!e) return;
- const s = e.querySelector("a.govuk-tabs__tab");
- s && (this.hideTab(t), this.showTab(s), s.focus(), this.createHistoryEntry(s))
+ const n = e.querySelector("a.govuk-tabs__tab");
+ n && (this.hideTab(t), this.showTab(n), n.focus(), this.createHistoryEntry(n))
}
getPanel(t) {
const e = getFragmentFromUrl(t.href);
@@ -1116,7 +1174,7 @@ function initAll(t) {
if (t = void 0 !== t ? t : {}, !isSupported()) return void(t.onError ? t.onError(new SupportError, {
config: t
}) : console.log(new SupportError));
- const s = [
+ const n = [
[Accordion, t.accordion],
[Button, t.button],
[CharacterCount, t.characterCount],
@@ -1131,32 +1189,32 @@ function initAll(t) {
[SkipLink],
[Tabs]
],
- n = {
+ i = {
scope: null != (e = t.scope) ? e : document,
onError: t.onError
};
- s.forEach((([Component, t]) => {
- createAll(Component, t, n)
+ n.forEach((([Component, t]) => {
+ createAll(Component, t, i)
}))
}
function createAll(Component, t, e) {
- let s, n = document;
- var i;
- "object" == typeof e && (n = null != (i = e.scope) ? i : n, s = e.onError);
- "function" == typeof e && (s = e), e instanceof HTMLElement && (n = e);
- const o = n.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 (n) {
- return s ? s(n, {
+ } catch (i) {
+ return n ? n(i, {
element: e,
component: Component,
config: t
- }) : console.log(n), null
+ }) : console.log(i), null
}
- })).filter(Boolean) : (s ? s(new SupportError, {
+ })).filter(Boolean) : (n ? n(new SupportError, {
component: Component,
config: t
}) : console.log(new SupportError), [])
Action run for 6c61aa5 |
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 a04a0facd..bbca92d85 100644
--- a/packages/govuk-frontend/dist/govuk/all.bundle.js
+++ b/packages/govuk-frontend/dist/govuk/all.bundle.js
@@ -351,6 +351,19 @@
* @typedef {typeof Component & 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;
@@ -544,6 +557,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
*
@@ -567,35 +592,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;
@@ -604,19 +610,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) {
@@ -625,11 +635,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);
@@ -639,73 +649,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);
}
@@ -724,14 +766,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) {
@@ -741,11 +783,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());
}
@@ -754,17 +796,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));
@@ -775,7 +817,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);
}
/**
@@ -789,7 +831,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) {
@@ -818,10 +860,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;
}
}
@@ -1494,9 +1537,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() {
@@ -1506,13 +1550,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);
}
@@ -1532,9 +1577,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;
@@ -1835,9 +1881,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 adc70f031..d1aaf3f8e 100644
--- a/packages/govuk-frontend/dist/govuk/all.bundle.mjs
+++ b/packages/govuk-frontend/dist/govuk/all.bundle.mjs
@@ -345,6 +345,19 @@ function extractConfigByNamespace(schema, dataset, namespace) {
* @typedef {typeof Component & 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;
@@ -538,6 +551,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
*
@@ -561,35 +586,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;
@@ -598,19 +604,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) {
@@ -619,11 +629,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);
@@ -633,73 +643,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);
}
@@ -718,14 +760,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) {
@@ -735,11 +777,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());
}
@@ -748,17 +790,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));
@@ -769,7 +811,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);
}
/**
@@ -783,7 +825,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) {
@@ -812,10 +854,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;
}
}
@@ -1488,9 +1531,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() {
@@ -1500,13 +1544,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);
}
@@ -1526,9 +1571,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;
@@ -1829,9 +1875,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 e80a3ac42..7c6b0a5eb 100644
--- a/packages/govuk-frontend/dist/govuk/components/accordion/accordion.bundle.js
+++ b/packages/govuk-frontend/dist/govuk/components/accordion/accordion.bundle.js
@@ -291,6 +291,19 @@
* @typedef {typeof Component & 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;
@@ -484,6 +497,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
*
@@ -507,35 +532,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;
@@ -544,19 +550,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) {
@@ -565,11 +575,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);
@@ -579,73 +589,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);
}
@@ -664,14 +706,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) {
@@ -681,11 +723,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());
}
@@ -694,17 +736,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));
@@ -715,7 +757,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);
}
/**
@@ -729,7 +771,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) {
@@ -758,10 +800,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 b36f3fcab..e1f6eb729 100644
--- a/packages/govuk-frontend/dist/govuk/components/accordion/accordion.bundle.mjs
+++ b/packages/govuk-frontend/dist/govuk/components/accordion/accordion.bundle.mjs
@@ -285,6 +285,19 @@ function extractConfigByNamespace(schema, dataset, namespace) {
* @typedef {typeof Component & 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;
@@ -478,6 +491,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
*
@@ -501,35 +526,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;
@@ -538,19 +544,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) {
@@ -559,11 +569,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);
@@ -573,73 +583,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);
}
@@ -658,14 +700,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) {
@@ -675,11 +717,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());
}
@@ -688,17 +730,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));
@@ -709,7 +751,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);
}
/**
@@ -723,7 +765,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) {
@@ -752,10 +794,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 bb0c3776c..1b7bb226a 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 04aca7263..902df5230 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
@@ -291,6 +291,14 @@
* @typedef {typeof Component & 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;
@@ -533,9 +541,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() {
@@ -545,13 +554,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);
}
@@ -571,9 +581,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 e66c5301d..3b319bd8f 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
@@ -285,6 +285,14 @@ function extractConfigByNamespace(schema, dataset, namespace) {
* @typedef {typeof Component & 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;
@@ -527,9 +535,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() {
@@ -539,13 +548,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);
}
@@ -565,9 +575,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 a16cca753..4247162cd 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 ba44a62b9..86d0dea29 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
@@ -296,6 +296,14 @@
* @typedef {typeof Component & 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;
@@ -536,9 +544,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 2e2e65f28..5f6a12425 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
@@ -290,6 +290,14 @@ function extractConfigByNamespace(schema, dataset, namespace) {
* @typedef {typeof Component & 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;
@@ -530,9 +538,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 d246fcb7a..87fa6992a 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 6c61aa5 |
764dda8
to
d7f1a99
Compare
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
Allows regrouping code for creating components a little more, as well as getting rid of a couple of loops over child nodes
d7f1a99
to
6c61aa5
Compare
eb6bc2d
to
6c61aa5
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, on the
accordion-section-component-wip
branchThe 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
setterexpanded
toAccordionSection
AccordionSection
beforematch
and us storing state client side whenrememberExpanded
is set).