function closestAttributeValue($element, attributeName) { const $closestElementWithAttribute = $element.closest(`[${attributeName}]`); return $closestElementWithAttribute ? $closestElementWithAttribute.getAttribute(attributeName) : null; } function normaliseString(value, property) { const trimmedValue = value ? value.trim() : ''; let output; let outputType = property == null ? void 0 : property.type; if (!outputType) { if (['true', 'false'].includes(trimmedValue)) { outputType = 'boolean'; } if (trimmedValue.length > 0 && isFinite(Number(trimmedValue))) { outputType = 'number'; } } switch (outputType) { case 'boolean': output = trimmedValue === 'true'; break; case 'number': output = Number(trimmedValue); break; default: output = value; } return output; } /** * @typedef {import('./index.mjs').SchemaProperty} SchemaProperty */ function mergeConfigs(...configObjects) { const formattedConfigObject = {}; for (const configObject of configObjects) { for (const key of Object.keys(configObject)) { const option = formattedConfigObject[key]; const override = configObject[key]; if (isObject(option) && isObject(override)) { formattedConfigObject[key] = mergeConfigs(option, override); } else { formattedConfigObject[key] = override; } } } return formattedConfigObject; } function extractConfigByNamespace(Component, dataset, namespace) { const property = Component.schema.properties[namespace]; if ((property == null ? void 0 : property.type) !== 'object') { return; } const newObject = { [namespace]: ({}) }; for (const [key, value] of Object.entries(dataset)) { let current = newObject; const keyParts = key.split('.'); for (const [index, name] of keyParts.entries()) { if (typeof current === 'object') { if (index < keyParts.length - 1) { if (!isObject(current[name])) { current[name] = {}; } current = current[name]; } else if (key !== namespace) { current[name] = normaliseString(value); } } } } return newObject[namespace]; } function isSupported($scope = document.body) { if (!$scope) { return false; } return $scope.classList.contains('govuk-frontend-supported'); } function isArray(option) { return Array.isArray(option); } function isObject(option) { return !!option && typeof option === 'object' && !isArray(option); } /** * Schema for component config * * @typedef {object} Schema * @property {{ [field: string]: SchemaProperty | undefined }} properties - Schema properties * @property {SchemaCondition[]} [anyOf] - List of schema conditions */ /** * Schema property for component config * * @typedef {object} SchemaProperty * @property {'string' | 'boolean' | 'number' | 'object'} type - Property type */ /** * Schema condition for component config * * @typedef {object} SchemaCondition * @property {string[]} required - List of required config fields * @property {string} errorMessage - Error message when required config fields not provided */ function normaliseDataset(Component, dataset) { const out = {}; for (const [field, property] of Object.entries(Component.schema.properties)) { if (field in dataset) { out[field] = normaliseString(dataset[field], property); } if ((property == null ? void 0 : property.type) === 'object') { out[field] = extractConfigByNamespace(Component, dataset, field); } } return out; } 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 `
` checked for browser support */ constructor($scope = document.body) { const supportMessage = 'noModule' in HTMLScriptElement.prototype ? 'GOV.UK Frontend initialised without `` from template `