govuk-frontend
Version:
GOV.UK Frontend contains the code you need to start building a user interface for government platforms and services.
222 lines (214 loc) • 6.89 kB
JavaScript
function getBreakpoint(name) {
const property = `--govuk-frontend-breakpoint-${name}`;
const value = window.getComputedStyle(document.documentElement).getPropertyValue(property);
return {
property,
value: value || undefined
};
}
function isInitialised($root, moduleName) {
return $root instanceof HTMLElement && $root.hasAttribute(`data-${moduleName}-init`);
}
/**
* Checks if GOV.UK Frontend is supported on this page
*
* Some browsers will load and run our JavaScript but GOV.UK Frontend
* won't be supported.
*
* @param {HTMLElement | null} [$scope] - (internal) `<body>` HTML element checked for browser support
* @returns {boolean} Whether GOV.UK Frontend is supported on this page
*/
function isSupported($scope = document.body) {
if (!$scope) {
return false;
}
return $scope.classList.contains('govuk-frontend-supported');
}
function formatErrorMessage(Component, message) {
return `${Component.moduleName}: ${message}`;
}
/**
* @typedef ComponentWithModuleName
* @property {string} moduleName - Name of the component
*/
class GOVUKFrontendError extends Error {
constructor(...args) {
super(...args);
this.name = 'GOVUKFrontendError';
}
}
class SupportError extends GOVUKFrontendError {
/**
* Checks if GOV.UK Frontend is supported on this page
*
* @param {HTMLElement | null} [$scope] - HTML element `<body>` checked for browser support
*/
constructor($scope = document.body) {
const supportMessage = 'noModule' in HTMLScriptElement.prototype ? 'GOV.UK Frontend initialised without `<body class="govuk-frontend-supported">` from template `<script>` snippet' : 'GOV.UK Frontend is not supported in this browser';
super($scope ? supportMessage : 'GOV.UK Frontend initialised without `<script type="module">`');
this.name = 'SupportError';
}
}
class ElementError extends GOVUKFrontendError {
constructor(messageOrOptions) {
let message = typeof messageOrOptions === 'string' ? messageOrOptions : '';
if (typeof messageOrOptions === 'object') {
const {
component,
identifier,
element,
expectedType
} = messageOrOptions;
message = identifier;
message += element ? ` is not of type ${expectedType != null ? expectedType : 'HTMLElement'}` : ' not found';
message = formatErrorMessage(component, message);
}
super(message);
this.name = 'ElementError';
}
}
class InitError extends GOVUKFrontendError {
constructor(componentOrMessage) {
const message = typeof componentOrMessage === 'string' ? componentOrMessage : formatErrorMessage(componentOrMessage, `Root element (\`$root\`) already initialised`);
super(message);
this.name = 'InitError';
}
}
/**
* @typedef {import('../common/index.mjs').ComponentWithModuleName} ComponentWithModuleName
*/
class GOVUKFrontendComponent {
/**
* Returns the root element of the component
*
* @protected
* @returns {RootElementType} - the root element of component
*/
get $root() {
return this._$root;
}
constructor($root) {
this._$root = void 0;
const childConstructor = this.constructor;
if (typeof childConstructor.moduleName !== 'string') {
throw new InitError(`\`moduleName\` not defined in component`);
}
if (!($root instanceof childConstructor.elementType)) {
throw new ElementError({
element: $root,
component: childConstructor,
identifier: 'Root element (`$root`)',
expectedType: childConstructor.elementType.name
});
} else {
this._$root = $root;
}
childConstructor.checkSupport();
this.checkInitialised();
const moduleName = childConstructor.moduleName;
this.$root.setAttribute(`data-${moduleName}-init`, '');
}
checkInitialised() {
const constructor = this.constructor;
const moduleName = constructor.moduleName;
if (moduleName && isInitialised(this.$root, moduleName)) {
throw new InitError(constructor);
}
}
static checkSupport() {
if (!isSupported()) {
throw new SupportError();
}
}
}
/**
* @typedef ChildClass
* @property {string} moduleName - The module name that'll be looked for in the DOM when initialising the component
*/
/**
* @typedef {typeof GOVUKFrontendComponent & ChildClass} ChildClassConstructor
*/
GOVUKFrontendComponent.elementType = HTMLElement;
/**
* Header component
*
* @preserve
*/
class Header extends GOVUKFrontendComponent {
/**
* Apply a matchMedia for desktop which will trigger a state sync if the
* browser viewport moves between states.
*
* @param {Element | null} $root - HTML element to use for header
*/
constructor($root) {
super($root);
this.$menuButton = void 0;
this.$menu = void 0;
this.menuIsOpen = false;
this.mql = null;
const $menuButton = this.$root.querySelector('.govuk-js-header-toggle');
if (!$menuButton) {
return this;
}
const menuId = $menuButton.getAttribute('aria-controls');
if (!menuId) {
throw new ElementError({
component: Header,
identifier: 'Navigation button (`<button class="govuk-js-header-toggle">`) attribute (`aria-controls`)'
});
}
const $menu = document.getElementById(menuId);
if (!$menu) {
throw new ElementError({
component: Header,
element: $menu,
identifier: `Navigation (\`<ul id="${menuId}">\`)`
});
}
this.$menu = $menu;
this.$menuButton = $menuButton;
this.setupResponsiveChecks();
this.$menuButton.addEventListener('click', () => this.handleMenuButtonClick());
}
setupResponsiveChecks() {
const breakpoint = getBreakpoint('desktop');
if (!breakpoint.value) {
throw new ElementError({
component: Header,
identifier: `CSS custom property (\`${breakpoint.property}\`) on pseudo-class \`:root\``
});
}
this.mql = window.matchMedia(`(min-width: ${breakpoint.value})`);
if ('addEventListener' in this.mql) {
this.mql.addEventListener('change', () => this.checkMode());
} else {
this.mql.addListener(() => this.checkMode());
}
this.checkMode();
}
checkMode() {
if (!this.mql || !this.$menu || !this.$menuButton) {
return;
}
if (this.mql.matches) {
this.$menu.removeAttribute('hidden');
this.$menuButton.setAttribute('hidden', '');
} else {
this.$menuButton.removeAttribute('hidden');
this.$menuButton.setAttribute('aria-expanded', this.menuIsOpen.toString());
if (this.menuIsOpen) {
this.$menu.removeAttribute('hidden');
} else {
this.$menu.setAttribute('hidden', '');
}
}
}
handleMenuButtonClick() {
this.menuIsOpen = !this.menuIsOpen;
this.checkMode();
}
}
Header.moduleName = 'govuk-header';
export { Header };
//# sourceMappingURL=header.bundle.mjs.map