-
Notifications
You must be signed in to change notification settings - Fork 328
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
Spike into Config
class if defined by GOVUKFrontendComponent
#5426
base: main
Are you sure you want to change the base?
Conversation
📋 StatsFile sizes
Modules
View stats and visualisations on the review app Action run for 628a37d |
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 0416a210b..ba82aea9f 100644
--- a/packages/govuk-frontend/dist/govuk/govuk-frontend.min.js
+++ b/packages/govuk-frontend/dist/govuk/govuk-frontend.min.js
@@ -1,44 +1,33 @@
const version = "development";
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, s = null == e ? void 0 : e.type;
+ switch (s || (["true", "false"].includes(n) && (s = "boolean"), n.length > 0 && isFinite(Number(n)) && (s = "number")), s) {
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
-}
-
-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
- }
- return e
+ return i
}
function extractConfigByNamespace(Component, t, e) {
- const s = Component.schema.properties[e];
- if ("object" !== (null == s ? void 0 : s.type)) return;
- const n = {
+ const n = Component.schema.properties[e];
+ if ("object" !== (null == n ? void 0 : n.type)) return;
+ const i = {
[e]: {}
};
- for (const [i, o] of Object.entries(t)) {
- let t = n;
- const s = i.split(".");
- for (const [n, r] of s.entries()) "object" == typeof t && (n < s.length - 1 ? (isObject(t[r]) || (t[r] = {}), t = t[r]) : i !== e && (t[r] = normaliseString(o)))
+ for (const [s, o] of Object.entries(t)) {
+ let t = i;
+ const n = s.split(".");
+ for (const [i, r] of n.entries()) "object" == typeof t && (i < n.length - 1 ? (isObject(t[r]) || (t[r] = {}), t = t[r]) : s !== e && (t[r] = normaliseString(o)))
}
- return n[e]
+ return i[e]
}
function getFragmentFromUrl(t) {
@@ -54,20 +43,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) {
@@ -83,12 +72,6 @@ function isObject(t) {
function formatErrorMessage(Component, t) {
return `${Component.moduleName}: ${t}`
}
-
-function normaliseDataset(Component, t) {
- const e = {};
- for (const [s, n] of Object.entries(Component.schema.properties)) s in t && (e[s] = normaliseString(t[s], n)), "object" === (null == n ? void 0 : n.type) && (e[s] = extractConfigByNamespace(Component, t, s));
- return e
-}
class GOVUKFrontendError extends Error {
constructor(...t) {
super(...t), this.name = "GOVUKFrontendError"
@@ -110,12 +93,12 @@ class ElementError extends GOVUKFrontendError {
let e = "string" == typeof t ? t : "";
if ("object" == typeof t) {
const {
- component: s,
- identifier: n,
- element: i,
+ component: n,
+ identifier: i,
+ element: s,
expectedType: o
} = t;
- e = n, e += i ? ` is not of type ${null!=o?o:"HTMLElement"}` : " not found", e = formatErrorMessage(s, e)
+ e = i, e += s ? ` is not of type ${null!=o?o:"HTMLElement"}` : " not found", e = formatErrorMessage(n, e)
}
super(e), this.name = "ElementError"
}
@@ -125,23 +108,57 @@ class InitError extends GOVUKFrontendError {
super("string" == typeof t ? t : formatErrorMessage(t, "Root element (`$root`) already initialised")), this.name = "InitError"
}
}
+class Config {
+ static mergeConfigs(...t) {
+ const e = {};
+ for (const n of t)
+ for (const t of Object.keys(n)) {
+ const i = e[t],
+ s = n[t];
+ isObject(i) && isObject(s) ? e[t] = Config.mergeConfigs(i, s) : e[t] = s
+ }
+ return e
+ }
+ constructor(t, ...e) {
+ if (this.configObject = void 0, this.component = void 0, this.component = t, !e.length) return void(this.configObject = {});
+ if (void 0 === t.defaults) throw new ConfigError("No defaults specified in component");
+ if (void 0 === t.schema) throw new ConfigError("No schema specified in component");
+ this.configObject = Config.mergeConfigs(this.component.defaults, ...e);
+ const n = this.configObject;
+ return new Proxy(this, {
+ get: (t, e, i) => Reflect.has(t, e) ? Reflect.get(t, e, i) : n[String(e)]
+ })
+ }
+}
class GOVUKFrontendComponent {
+ get config() {
+ return this._config
+ }
get $root() {
return this._$root
}
- constructor(t) {
- this._$root = void 0;
- const e = this.constructor;
- if ("string" != typeof e.moduleName) throw new InitError("`moduleName` not defined in component");
- if (!(t instanceof e.elementType)) throw new ElementError({
+ constructor(t, ...e) {
+ this._config = void 0, this._$root = void 0;
+ const n = this.constructor;
+ if ("string" != typeof n.moduleName) throw new InitError("`moduleName` not defined in component");
+ if (!(t instanceof n.elementType)) throw new ElementError({
element: t,
- component: e,
+ component: n,
identifier: "Root element (`$root`)",
- expectedType: e.elementType.name
+ expectedType: n.elementType.name
});
- this._$root = t, e.checkSupport(), this.checkInitialised();
- const s = e.moduleName;
- this.$root.setAttribute(`data-${s}-init`, "")
+ if (this._$root = t, n.checkSupport(), this.checkInitialised(), e.length) {
+ const t = this.constructor,
+ i = function(Component, t) {
+ const e = {};
+ for (const [n, i] of Object.entries(Component.schema.properties)) n in t && (e[n] = normaliseString(t[n], i)), "object" === (null == i ? void 0 : i.type) && (e[n] = extractConfigByNamespace(Component, t, n));
+ return e
+ }(t, this._$root.dataset),
+ s = t.configOverride(i);
+ this._config = new Config(n, ...e, s, i)
+ } else this._config = new Config(n);
+ const i = n.moduleName;
+ this.$root.setAttribute(`data-${i}-init`, "")
}
checkInitialised() {
const t = this.constructor,
@@ -153,35 +170,38 @@ class GOVUKFrontendComponent {
static checkSupport() {
if (!isSupported()) throw new SupportError
}
+ static configOverride(t) {
+ return t
+ }
}
GOVUKFrontendComponent.elementType = HTMLElement;
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`)
}))
@@ -191,11 +211,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`)
}
@@ -207,8 +227,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
}
}
}
@@ -230,8 +250,8 @@ 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",
@@ -239,13 +259,13 @@ I18n.pluralRulesMap = {
};
class Accordion extends GOVUKFrontendComponent {
constructor(t, e = {}) {
- super(t), this.config = void 0, 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.config = mergeConfigs(Accordion.defaults, e, normaliseDataset(Accordion, this.$root.dataset)), this.i18n = new I18n(this.config.i18n);
- const s = this.$root.querySelectorAll(`.${this.sectionClass}`);
- if (!s.length) throw new ElementError({
+ super(t, e), this.i18n = void 0, this.controlsClass = "govuk-accordion__controls", this.showAllClass = "govuk-accordion__show-all", this.showAllTextClass = "govuk-accordion__show-all-text", this.sectionClass = "govuk-accordion__section", this.sectionExpandedClass = "govuk-accordion__section--expanded", this.sectionButtonClass = "govuk-accordion__section-button", this.sectionHeaderClass = "govuk-accordion__section-header", this.sectionHeadingClass = "govuk-accordion__section-heading", this.sectionHeadingDividerClass = "govuk-accordion__section-heading-divider", this.sectionHeadingTextClass = "govuk-accordion__section-heading-text", this.sectionHeadingTextFocusClass = "govuk-accordion__section-heading-text-focus", this.sectionShowHideToggleClass = "govuk-accordion__section-toggle", this.sectionShowHideToggleFocusClass = "govuk-accordion__section-toggle-focus", this.sectionShowHideTextClass = "govuk-accordion__section-toggle-text", this.upChevronIconClass = "govuk-accordion-nav__chevron", this.downChevronIconClass = "govuk-accordion-nav__chevron--down", this.sectionSummaryClass = "govuk-accordion__section-summary", this.sectionSummaryFocusClass = "govuk-accordion__section-summary-focus", this.sectionContentClass = "govuk-accordion__section-content", this.$sections = void 0, this.$showAllButton = null, this.$showAllIcon = null, this.$showAllText = null, this.i18n = new I18n(this.config.i18n);
+ const n = this.$root.querySelectorAll(`.${this.sectionClass}`);
+ if (!n.length) throw new ElementError({
component: Accordion,
identifier: `Sections (\`<div class="${this.sectionClass}">\`)`
});
- this.$sections = s, this.initControls(), this.initSectionHeaders(), this.updateShowAllButton(this.areAllSectionsOpen())
+ this.$sections = n, 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);
@@ -254,53 +274,53 @@ class Accordion extends GOVUKFrontendComponent {
}
initSectionHeaders() {
this.$sections.forEach(((t, e) => {
- const s = t.querySelector(`.${this.sectionHeaderClass}`);
- if (!s) throw new ElementError({
+ const n = t.querySelector(`.${this.sectionHeaderClass}`);
+ if (!n) throw new ElementError({
component: Accordion,
identifier: `Section headers (\`<div class="${this.sectionHeaderClass}">\`)`
});
- 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(`.${this.sectionButtonClass}`),
+ i = t.querySelector(`.${this.sectionHeadingClass}`),
+ s = t.querySelector(`.${this.sectionSummaryClass}`);
+ if (!i) throw new ElementError({
component: Accordion,
identifier: `Section heading (\`.${this.sectionHeadingClass}\`)`
});
- if (!s) throw new ElementError({
+ if (!n) throw new ElementError({
component: Accordion,
identifier: `Section button placeholder (\`<span class="${this.sectionButtonClass}">\`)`
});
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);
+ for (const d of Array.from(n.attributes)) "id" !== d.name && o.setAttribute(d.name, d.value);
const r = document.createElement("span");
- r.classList.add(this.sectionHeadingTextClass), r.id = s.id;
+ r.classList.add(this.sectionHeadingTextClass), r.id = n.id;
const a = document.createElement("span");
- a.classList.add(this.sectionHeadingTextFocusClass), r.appendChild(a), Array.from(s.childNodes).forEach((t => a.appendChild(t)));
+ a.classList.add(this.sectionHeadingTextFocusClass), r.appendChild(a), Array.from(n.childNodes).forEach((t => a.appendChild(t)));
const c = document.createElement("span");
c.classList.add(this.sectionShowHideToggleClass), c.setAttribute("data-nosnippet", "");
const l = document.createElement("span");
l.classList.add(this.sectionShowHideToggleFocusClass), c.appendChild(l);
const h = document.createElement("span"),
u = document.createElement("span");
- if (u.classList.add(this.upChevronIconClass), l.appendChild(u), h.classList.add(this.sectionShowHideTextClass), l.appendChild(h), o.appendChild(r), o.appendChild(this.getButtonPunctuationEl()), i) {
+ if (u.classList.add(this.upChevronIconClass), l.appendChild(u), h.classList.add(this.sectionShowHideTextClass), l.appendChild(h), o.appendChild(r), o.appendChild(this.getButtonPunctuationEl()), s) {
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())
+ for (const n of Array.from(s.attributes)) t.setAttribute(n.name, n.value);
+ Array.from(s.childNodes).forEach((t => e.appendChild(t))), s.remove(), o.appendChild(t), o.appendChild(this.getButtonPunctuationEl())
}
- o.appendChild(c), n.removeChild(s), n.appendChild(o)
+ o.appendChild(c), i.removeChild(n), i.appendChild(o)
}
onBeforeMatch(t) {
const e = t.target;
if (!(e instanceof Element)) return;
- const s = e.closest(`.${this.sectionClass}`);
- s && this.setExpanded(!0, s)
+ const n = e.closest(`.${this.sectionClass}`);
+ n && this.setExpanded(!0, n)
}
onSectionToggle(t) {
const e = !this.isExpanded(t);
@@ -313,24 +333,24 @@ class Accordion extends GOVUKFrontendComponent {
})), this.updateShowAllButton(t)
}
setExpanded(t, e) {
- const s = e.querySelector(`.${this.upChevronIconClass}`),
- n = e.querySelector(`.${this.sectionShowHideTextClass}`),
- i = e.querySelector(`.${this.sectionButtonClass}`),
+ const n = e.querySelector(`.${this.upChevronIconClass}`),
+ i = e.querySelector(`.${this.sectionShowHideTextClass}`),
+ s = e.querySelector(`.${this.sectionButtonClass}`),
o = e.querySelector(`.${this.sectionContentClass}`);
if (!o) throw new ElementError({
component: Accordion,
identifier: `Section content (\`<div class="${this.sectionContentClass}">\`)`
});
- if (!s || !n || !i) return;
+ if (!n || !i || !s) return;
const r = t ? this.i18n.t("hideSection") : this.i18n.t("showSection");
- n.textContent = r, i.setAttribute("aria-expanded", `${t}`);
+ i.textContent = r, s.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())
+ a.push(h), s.setAttribute("aria-label", a.join(" , ")), t ? (o.removeAttribute("hidden"), e.classList.add(this.sectionExpandedClass), n.classList.remove(this.downChevronIconClass)) : (o.setAttribute("hidden", "until-found"), e.classList.remove(this.sectionExpandedClass), n.classList.add(this.downChevronIconClass)), this.updateShowAllButton(this.areAllSectionsOpen())
}
isExpanded(t) {
return t.classList.contains(this.sectionExpandedClass)
@@ -347,18 +367,18 @@ class Accordion extends GOVUKFrontendComponent {
}
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");
@@ -387,7 +407,7 @@ Accordion.moduleName = "govuk-accordion", Accordion.defaults = Object.freeze({
});
class Button extends GOVUKFrontendComponent {
constructor(t, e = {}) {
- super(t), this.config = void 0, this.debounceFormSubmitTimer = null, this.config = mergeConfigs(Button.defaults, e, normaliseDataset(Button, this.$root.dataset)), this.$root.addEventListener("keydown", (t => this.handleKeyDown(t))), this.$root.addEventListener("click", (t => this.debounce(t)))
+ super(t, e), this.debounceFormSubmitTimer = null, this.$root.addEventListener("keydown", (t => this.handleKeyDown(t))), this.$root.addEventListener("click", (t => this.debounce(t)))
}
handleKeyDown(t) {
const e = t.target;
@@ -401,8 +421,8 @@ class Button extends GOVUKFrontendComponent {
}
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
@@ -415,54 +435,49 @@ Button.moduleName = "govuk-button", Button.defaults = Object.freeze({
});
class CharacterCount extends GOVUKFrontendComponent {
constructor(t, e = {}) {
- var s, n;
- super(t), this.$textarea = void 0, this.$visibleCountMessage = void 0, this.$screenReaderCountMessage = void 0, this.lastInputTimestamp = null, this.lastInputValue = "", this.valueChecker = null, this.config = void 0, 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({
+ 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 s = this.$root.querySelector(".govuk-js-character-count");
+ if (!(s instanceof HTMLTextAreaElement || s instanceof HTMLInputElement)) throw new ElementError({
component: CharacterCount,
- element: i,
+ element: s,
expectedType: "HTMLTextareaElement or HTMLInputElement",
identifier: "Form field (`.govuk-js-character-count`)"
});
- const o = normaliseDataset(CharacterCount, this.$root.dataset);
- let r = {};
- ("maxwords" in o || "maxlength" in o) && (r = {
- maxlength: void 0,
- maxwords: void 0
- }), this.config = mergeConfigs(CharacterCount.defaults, e, r, o);
- const a = function(t, e) {
- const s = [];
- for (const [n, i] of Object.entries(t)) {
- const t = [];
- if (Array.isArray(i)) {
+ const o = function(t) {
+ const e = [],
+ n = t.component.schema;
+ for (const [i, s] of Object.entries(n)) {
+ const n = [];
+ if (Array.isArray(s)) {
for (const {
- required: s,
- errorMessage: n
+ required: e,
+ errorMessage: i
}
- of i) s.every((t => !!e[t])) || t.push(n);
- "anyOf" !== n || i.length - t.length >= 1 || s.push(...t)
+ of s) e.every((e => t.configObject[e])) || n.push(i);
+ "anyOf" !== i || s.length - n.length >= 1 || e.push(...n)
}
}
- return s
- }(CharacterCount.schema, this.config);
- if (a[0]) throw new ConfigError(formatErrorMessage(CharacterCount, a[0]));
+ return e
+ }(this.config);
+ if (o[0]) throw new ConfigError(formatErrorMessage(CharacterCount, o[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;
- const c = `${this.$textarea.id}-info`,
- l = document.getElementById(c);
- if (!l) throw new ElementError({
+ }), this.maxLength = null != (n = null != (i = this.config.maxwords) ? i : this.config.maxlength) ? n : 1 / 0, this.$textarea = s;
+ const r = `${this.$textarea.id}-info`,
+ a = document.getElementById(r);
+ if (!a) throw new ElementError({
component: CharacterCount,
- element: l,
- identifier: `Count message (\`id="${c}"\`)`
+ element: a,
+ identifier: `Count message (\`id="${r}"\`)`
});
- `${l.textContent}`.match(/^\s*$/) && (l.textContent = this.i18n.t("textareaDescription", {
+ `${a.textContent}`.match(/^\s*$/) && (a.textContent = this.i18n.t("textareaDescription", {
count: this.maxLength
- })), this.$textarea.insertAdjacentElement("afterend", l);
- const h = document.createElement("div");
- h.className = "govuk-character-count__sr-status govuk-visually-hidden", h.setAttribute("aria-live", "polite"), this.$screenReaderCountMessage = h, l.insertAdjacentElement("afterend", h);
- const u = document.createElement("div");
- u.className = l.className, u.classList.add("govuk-character-count__status"), u.setAttribute("aria-hidden", "true"), this.$visibleCountMessage = u, l.insertAdjacentElement("afterend", u), l.classList.add("govuk-visually-hidden"), this.$textarea.removeAttribute("maxlength"), this.bindChangeEvents(), window.addEventListener("pageshow", (() => this.updateCountMessage())), this.updateCountMessage()
+ })), this.$textarea.insertAdjacentElement("afterend", a);
+ const c = document.createElement("div");
+ c.className = "govuk-character-count__sr-status govuk-visually-hidden", c.setAttribute("aria-live", "polite"), this.$screenReaderCountMessage = c, a.insertAdjacentElement("afterend", c);
+ const l = document.createElement("div");
+ l.className = a.className, l.classList.add("govuk-character-count__status"), l.setAttribute("aria-hidden", "true"), this.$visibleCountMessage = l, a.insertAdjacentElement("afterend", l), a.classList.add("govuk-visually-hidden"), this.$textarea.removeAttribute("maxlength"), this.bindChangeEvents(), window.addEventListener("pageshow", (() => this.updateCountMessage())), this.updateCountMessage()
}
bindChangeEvents() {
this.$textarea.addEventListener("keyup", (() => this.handleKeyUp())), this.$textarea.addEventListener("focus", (() => this.handleFocus())), this.$textarea.addEventListener("blur", (() => this.handleBlur()))
@@ -505,8 +520,8 @@ class CharacterCount extends GOVUKFrontendComponent {
}
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)
})
}
@@ -516,7 +531,13 @@ class CharacterCount extends GOVUKFrontendComponent {
return this.maxLength * this.config.threshold / 100 <= t
}
}
-CharacterCount.moduleName = "govuk-character-count", CharacterCount.defaults = Object.freeze({
+CharacterCount.configOverride = t => {
+ let e = {};
+ return ("maxwords" in t || "maxlength" in t) && (e = {
+ maxlength: void 0,
+ maxwords: void 0
+ }), e
+}, CharacterCount.moduleName = "govuk-character-count", CharacterCount.defaults = Object.freeze({
threshold: 0,
i18n: {
charactersUnderLimit: {
@@ -589,10 +610,10 @@ class Checkboxes extends GOVUKFrontendComponent {
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) {
@@ -615,7 +636,7 @@ class Checkboxes extends GOVUKFrontendComponent {
Checkboxes.moduleName = "govuk-checkboxes";
class ErrorSummary extends GOVUKFrontendComponent {
constructor(t, e = {}) {
- super(t), this.config = void 0, this.config = mergeConfigs(ErrorSummary.defaults, e, normaliseDataset(ErrorSummary, this.$root.dataset)), this.config.disableAutoFocus || setFocus(this.$root), this.$root.addEventListener("click", (t => this.handleClick(t)))
+ super(t, e), this.config.disableAutoFocus || setFocus(this.$root), this.$root.addEventListener("click", (t => this.handleClick(t)))
}
handleClick(t) {
const e = t.target;
@@ -625,25 +646,25 @@ class ErrorSummary extends GOVUKFrontendComponent {
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,
+ s = t.getBoundingClientRect();
+ if (s.height && window.innerHeight) {
+ if (s.top + s.height - i < window.innerHeight / 2) return n
}
}
}
@@ -661,17 +682,17 @@ ErrorSummary.moduleName = "govuk-error-summary", ErrorSummary.defaults = Object.
});
class ExitThisPage extends GOVUKFrontendComponent {
constructor(t, e = {}) {
- super(t), this.config = void 0, 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({
+ 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 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.config = mergeConfigs(ExitThisPage.defaults, e, normaliseDataset(ExitThisPage, this.$root.dataset)), 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)
@@ -737,18 +758,18 @@ class Header extends GOVUKFrontendComponent {
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");
@@ -768,7 +789,7 @@ class Header extends GOVUKFrontendComponent {
Header.moduleName = "govuk-header";
class NotificationBanner extends GOVUKFrontendComponent {
constructor(t, e = {}) {
- super(t), this.config = void 0, this.config = mergeConfigs(NotificationBanner.defaults, e, normaliseDataset(NotificationBanner, this.$root.dataset)), "alert" !== this.$root.getAttribute("role") || this.config.disableAutoFocus || setFocus(this.$root)
+ super(t, e), "alert" !== this.$root.getAttribute("role") || this.config.disableAutoFocus || setFocus(this.$root)
}
}
NotificationBanner.moduleName = "govuk-notification-banner", NotificationBanner.defaults = Object.freeze({
@@ -782,28 +803,28 @@ NotificationBanner.moduleName = "govuk-notification-banner", NotificationBanner.
});
class PasswordInput extends GOVUKFrontendComponent {
constructor(t, e = {}) {
- super(t), this.config = void 0, 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({
+ super(t, e), this.i18n = void 0, this.$input = void 0, this.$showHideButton = void 0, this.$screenReaderStatusMessage = void 0;
+ 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.config = mergeConfigs(PasswordInput.defaults, e, normaliseDataset(PasswordInput, this.$root.dataset)), 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 s = document.createElement("div");
+ s.className = "govuk-password-input__sr-status govuk-visually-hidden", s.setAttribute("aria-live", "polite"), this.$screenReaderStatusMessage = s, this.$input.insertAdjacentElement("afterend", s), 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()
}
@@ -820,9 +841,9 @@ class PasswordInput extends GOVUKFrontendComponent {
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({
@@ -866,21 +887,21 @@ class Radios extends GOVUKFrontendComponent {
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,
+ s = e.name;
+ n.forEach((t => {
+ const e = t.form === i;
+ t.name === s && e && this.syncConditionalRevealWithInputState(t)
}))
}
}
@@ -890,18 +911,18 @@ class ServiceNavigation extends GOVUKFrontendComponent {
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");
@@ -923,17 +944,17 @@ class SkipLink extends GOVUKFrontendComponent {
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 s;
try {
- i = new window.URL(this.$root.href)
+ s = 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`);
+ if (s.origin !== window.location.origin || s.pathname !== window.location.pathname) return;
+ const o = getFragmentFromUrl(n);
+ if (!o) throw new ElementError(`Skip link: Target link (\`href="${i}"\`) has no hash fragment`);
const r = document.getElementById(o);
if (!r) throw new ElementError({
component: SkipLink,
@@ -960,17 +981,17 @@ class Tabs extends GOVUKFrontendComponent {
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");
@@ -1006,8 +1027,8 @@ class Tabs extends GOVUKFrontendComponent {
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)
@@ -1022,8 +1043,8 @@ class Tabs extends GOVUKFrontendComponent {
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");
@@ -1032,14 +1053,14 @@ class Tabs extends GOVUKFrontendComponent {
}
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) {
@@ -1057,16 +1078,16 @@ class Tabs extends GOVUKFrontendComponent {
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);
@@ -1096,7 +1117,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],
@@ -1111,32 +1132,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}"]`);
+ let n, i = document;
+ var s;
+ "object" == typeof e && (i = null != (s = e.scope) ? s : i, n = e.onError);
+ "function" == typeof e && (n = e), e instanceof HTMLElement && (i = e);
+ const o = i.querySelectorAll(`[data-module="${Component.moduleName}"]`);
return isSupported() ? Array.from(o).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 628a37d |
Other changes to npm packageThe diff could not be posted as a comment. You can download it from the workflow artifacts. Action run for 628a37d |
Config
class if defined by GOVUKFrontendComponent
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just saving some thoughts ahead of tomorrow's discussion. I quite like that this approach clearly encapsulates where what gets merged comes from and in which order things get merged, reducing the code in each component further. Wondering if it's too early an encapsulation for a public API (though certainly a refactoring that we'd benefit from if we can figure why it make the bundle size increase even though we're moving code in a parent class).
// no configuration objects supplied | ||
// then config object is just empty... | ||
if (!configObjects.length) { | ||
this.configObject = /** @type {ConfigType} */ ({}) | ||
|
||
return | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
question Is it so it can work with components that do not have configs? If that's the case, maybe we could explore having a ConfigurableGOVUKFrontendComponent
class?
const childConstructorWithConfig = | ||
/** @type {ChildClassConstructorConfig<ConfigType>} */ ( | ||
this.constructor | ||
) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
question Is there a way we could cast only once when we do childConstructor
?
const dataset = /** @type {{ dataset: DOMStringMap }} */ ( | ||
/** @type {unknown} */ (this._$root) | ||
).dataset |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
suggestion That's a lot of casting, a couple of thoughts on that:
- Can we type
$root
a little more tightly with the expectation that it holds adataset
property which is aDOMStringMap
? Maybe optional with a check here thatthis._$root.dataset
is an instance ofDOMStringMap
? - Could we check that the
_$root
is either anHTMLElement
orSVGElement
(MathMLElement
do not have adataset
) with anif
before running that code to avoid the casting? This seems less future proof as there could be new API introduced that would have adataset
🤔 .
*/ | ||
constructor($root) { | ||
constructor($root, ...configObjects) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
issue This looks troublesome in the long term if the component needs a 3rd or 4th argument. If in the long run we realise we need to accept more than one config object, that second parameter could accept an Array
of configs, which we'd easily be able to distinguish from a single configuration object.
What prompted the use of rest parameters to receive a list of config objects?
@@ -8,8 +10,27 @@ import { ElementError, InitError, SupportError } from './errors/index.mjs' | |||
* | |||
* @virtual | |||
* @template {Element} [RootElementType=HTMLElement] | |||
* @template {{[key:string]: unknown}} [ConfigType={}] | |||
*/ | |||
export class GOVUKFrontendComponent { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
question As not all components use a config, would there be any issue having an intermetiate ConfigurableGOVUKFrontendComponent
that handles the configuration bit? This would likely reduce the size of the final bundle if importing only non-configurable components (as the code for handling configuration would be in a class that would be dropped by tree-shaking).
static schema = Object.freeze({ | ||
properties: { | ||
attribute: { type: 'string' }, | ||
__test: { type: 'boolean' } | ||
} | ||
}) | ||
|
||
/** | ||
* @type {MockConfig} | ||
*/ | ||
static defaults = Object.freeze({ | ||
attribute: '', | ||
__test: false | ||
}) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
suggestion Is there a way the GOVUKFrontendComponent
class can ensure these objects get frozen, allowing people to just set regular objects (maybe through a setter that freezes whatever it receives)? Is it even necessary?
const configObjectOveride = /** @type {ConfigType} */ ( | ||
childConstructorWithConfig.configOverride(normalisedDataset) | ||
) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
suggestion Rather than casting, we could check the presence of configOverride
with typeof childConstructorWithConfig.configOverride === 'function'
before adding it to the list of configs to merge, maybe?
In this spike, the config is defined by
GOVUKFrontendComponent
.