Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Spike into Config class if defined by GOVUKFrontendComponent #5426

Draft
wants to merge 3 commits into
base: main
Choose a base branch
from

Conversation

patrickpatrickpatrick
Copy link
Contributor

@patrickpatrickpatrick patrickpatrickpatrick commented Oct 23, 2024

In this spike, the config is defined by GOVUKFrontendComponent.

Copy link

github-actions bot commented Oct 23, 2024

📋 Stats

File sizes

File Size
dist/govuk-frontend-development.min.css 118.41 KiB
dist/govuk-frontend-development.min.js 42.87 KiB
packages/govuk-frontend/dist/govuk/all.bundle.js 94.66 KiB
packages/govuk-frontend/dist/govuk/all.bundle.mjs 88.92 KiB
packages/govuk-frontend/dist/govuk/all.mjs 1.18 KiB
packages/govuk-frontend/dist/govuk/govuk-frontend-component.mjs 3.79 KiB
packages/govuk-frontend/dist/govuk/govuk-frontend.min.css 118.4 KiB
packages/govuk-frontend/dist/govuk/govuk-frontend.min.js 42.86 KiB
packages/govuk-frontend/dist/govuk/i18n.mjs 5.55 KiB
packages/govuk-frontend/dist/govuk/init.mjs 6.85 KiB

Modules

File Size (bundled) Size (minified)
all.mjs 85.04 KiB 40.64 KiB
accordion.mjs 28.93 KiB 13.64 KiB
button.mjs 11.44 KiB 4.01 KiB
character-count.mjs 27.91 KiB 11.17 KiB
checkboxes.mjs 13.75 KiB 5.12 KiB
error-summary.mjs 13.34 KiB 4.77 KiB
exit-this-page.mjs 22.55 KiB 10.57 KiB
header.mjs 12.41 KiB 4.91 KiB
notification-banner.mjs 11.7 KiB 3.93 KiB
password-input.mjs 20.6 KiB 8.56 KiB
radios.mjs 12.75 KiB 4.67 KiB
service-navigation.mjs 12.39 KiB 4.95 KiB
skip-link.mjs 12.34 KiB 4.46 KiB
tabs.mjs 17.98 KiB 8.36 KiB

View stats and visualisations on the review app


Action run for 628a37d

Copy link

github-actions bot commented Oct 23, 2024

JavaScript changes to npm package

diff --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

Copy link

github-actions bot commented Oct 23, 2024

Other changes to npm package

The diff could not be posted as a comment. You can download it from the workflow artifacts.


Action run for 628a37d

@patrickpatrickpatrick patrickpatrickpatrick changed the title config if parent defines Spike into Config class if defined by GOVUKFrontendComponent Oct 23, 2024
Copy link
Member

@romaricpascal romaricpascal left a 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).

Comment on lines +69 to +75
// no configuration objects supplied
// then config object is just empty...
if (!configObjects.length) {
this.configObject = /** @type {ConfigType} */ ({})

return
}
Copy link
Member

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?

Comment on lines +97 to +100
const childConstructorWithConfig =
/** @type {ChildClassConstructorConfig<ConfigType>} */ (
this.constructor
)
Copy link
Member

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 ?

Comment on lines +102 to +104
const dataset = /** @type {{ dataset: DOMStringMap }} */ (
/** @type {unknown} */ (this._$root)
).dataset
Copy link
Member

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:

  1. Can we type $root a little more tightly with the expectation that it holds a dataset property which is a DOMStringMap? Maybe optional with a check here that this._$root.dataset is an instance of DOMStringMap?
  2. Could we check that the _$root is either an HTMLElement or SVGElement (MathMLElement do not have a dataset) with an if before running that code to avoid the casting? This seems less future proof as there could be new API introduced that would have a dataset 🤔 .

*/
constructor($root) {
constructor($root, ...configObjects) {
Copy link
Member

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 {
Copy link
Member

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).

Comment on lines +246 to +259
static schema = Object.freeze({
properties: {
attribute: { type: 'string' },
__test: { type: 'boolean' }
}
})

/**
* @type {MockConfig}
*/
static defaults = Object.freeze({
attribute: '',
__test: false
})
Copy link
Member

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?

Comment on lines +109 to +111
const configObjectOveride = /** @type {ConfigType} */ (
childConstructorWithConfig.configOverride(normalisedDataset)
)
Copy link
Member

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?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants