From cf1631abcb6ee92cbb08654aa7c2ecd87efc0469 Mon Sep 17 00:00:00 2001 From: NikoHelle Date: Tue, 19 Nov 2024 10:28:26 +0200 Subject: [PATCH] (hds-2521) Added options.focusTargetSelector The focus is moved to given element when banner closes on user action. --- .../cookieConsent/CookieConsent.stories.tsx | 15 +++++++----- .../CookieConsentCore.stories.tsx | 14 +++++++---- .../cookieConsentCore/cookieConsentCore.js | 24 ++++++++++++++++--- .../src/components/cookieConsentCore/types.ts | 1 + 4 files changed, 40 insertions(+), 14 deletions(-) diff --git a/packages/react/src/components/cookieConsent/CookieConsent.stories.tsx b/packages/react/src/components/cookieConsent/CookieConsent.stories.tsx index ae9e5a1ff4..8156deff53 100644 --- a/packages/react/src/components/cookieConsent/CookieConsent.stories.tsx +++ b/packages/react/src/components/cookieConsent/CookieConsent.stories.tsx @@ -60,7 +60,7 @@ const Actions = () => { }; const openBanner = async () => { // eslint-disable-next-line no-console - console.log('Spawning banner', await window.hds.cookieConsent.openBanner(['statistics', 'chat'])); + console.log('Spawning banner', await window.hds.cookieConsent.openBanner(['statistics', 'chat'], '#banner-opener')); }; return (
@@ -71,7 +71,7 @@ const Actions = () => { -
@@ -120,7 +120,8 @@ export const Example = ({ currentTabIndex }: { currentTabIndex?: number } = {}) return ( a', theme }} siteSettings={{ ...siteSettings, remove: false, monitorInterval: 0 }} >
@@ -131,6 +132,7 @@ export const Example = ({ currentTabIndex }: { currentTabIndex?: number } = {}) titleHref="https://hel.fi" logo={} logoAriaLabel="Service logo" + id="actionbar" > @@ -157,13 +159,13 @@ export const Example = ({ currentTabIndex }: { currentTabIndex?: number } = {}) -

Banner ( {language} )

+

Banner ( {language} )

Banner is shown if required consents are not consented.

-

Consents ( {language} )

+

Consents ( {language} )

Banner is also shown here when needed.

@@ -205,9 +207,10 @@ export const Banner = () => {
-

Cookie consent banner

+

Cookie consent banner

The banner is shown only if necessary.

diff --git a/packages/react/src/components/cookieConsentCore/CookieConsentCore.stories.tsx b/packages/react/src/components/cookieConsentCore/CookieConsentCore.stories.tsx index 15dbab4f0b..6f17216320 100644 --- a/packages/react/src/components/cookieConsentCore/CookieConsentCore.stories.tsx +++ b/packages/react/src/components/cookieConsentCore/CookieConsentCore.stories.tsx @@ -36,7 +36,7 @@ const Actions = () => { }; const openBanner = async () => { // eslint-disable-next-line no-console - console.log('Spawning banner', await window.hds.cookieConsent.openBanner(['statistics', 'chat'])); + console.log('Spawning banner', await window.hds.cookieConsent.openBanner(['statistics', 'chat'], '#banner-opener')); }; return ( <> @@ -45,7 +45,9 @@ const Actions = () => { - + ); @@ -71,20 +73,22 @@ const DummyContent = () => ( ); export const Banner = (options: Options = {}) => { + const focusTargetSelector = 'main h1'; + const combinedOptions: Options = { ...options, focusTargetSelector, submitEvent: true }; return (
-

Cookie consent banner

+

Cookie consent banner

The banner is shown only if necessary.

- +
); }; export const SettingsPage = (options: Options = {}) => { - const combinedOptions = { ...options, submitEvent: true }; + const combinedOptions: Options = { ...options, submitEvent: true }; return (
diff --git a/packages/react/src/components/cookieConsentCore/cookieConsentCore.js b/packages/react/src/components/cookieConsentCore/cookieConsentCore.js index 6b8d96e417..5a3ad80791 100644 --- a/packages/react/src/components/cookieConsentCore/cookieConsentCore.js +++ b/packages/react/src/components/cookieConsentCore/cookieConsentCore.js @@ -40,6 +40,7 @@ export class CookieConsentCore { #pageContentSelector; #submitEvent = false; #settingsPageSelector; + #focusTargetSelector; #disableAutoRender; #monitor; #cookieHandler; @@ -74,6 +75,7 @@ export class CookieConsentCore { * @param {string} [options.pageContentSelector='body'] - The selector for where to add scroll-margin-bottom. * @param {boolean} [options.submitEvent=false] - If set to true, do not reload the page, but submit the string as an event after consent. * @param {string} [options.settingsPageSelector=null] - If this string is set and a matching element is found on the page, show cookie settings in a page replacing the matched element. + * @param {string} [options.focusTargetSelector=null] - Selector for the element that will receive focus once the banner is closed. * @param {boolean} [options.disableAutoRender=false] - If true, neither banner or page are rendered automatically * @param {boolean} [calledFromCreate=false] - Indicates if the constructor was called from the create method. * @throws {Error} Throws an error if called from outside the create method. @@ -89,6 +91,7 @@ export class CookieConsentCore { pageContentSelector = 'body', // Where to add scroll-margin-bottom submitEvent = false, // if set, do not reload page, but submit 'hds-cookie-consent-changed' as event after consent settingsPageSelector = null, // If this string is set and a matching element is found on the page, show cookie settings in a page replacing the matched element. + focusTargetSelector = null, disableAutoRender = false, }, calledFromCreate = false, @@ -106,6 +109,7 @@ export class CookieConsentCore { this.#pageContentSelector = pageContentSelector; this.#submitEvent = submitEvent; this.#settingsPageSelector = settingsPageSelector; + this.#focusTargetSelector = focusTargetSelector; this.#disableAutoRender = disableAutoRender; CookieConsentCore.addToHdsScope('cookieConsent', this); @@ -161,6 +165,7 @@ export class CookieConsentCore { * @param {string} [options.pageContentSelector='body'] - The selector for where to add scroll-margin-bottom. * @param {boolean} [options.submitEvent=false] - If set, do not reload the page, but submit 'hds-cookie-consent-changed' event after consent. * @param {string} [options.settingsPageSelector=null] - If this string is set and a matching element is found on the page, show cookie settings in a page replacing the matched element. + * @param {string} [options.focusTargetSelector=null] - Selector for the element that will receive focus once the banner is closed. * @param {boolean} [options.disableAutoRender=false] - If... * @return {Promise} A promise that resolves to a new instance of the CookieConsent class. * @throws {Error} Throws an error if the siteSettingsParam is not a string or an object. @@ -246,13 +251,19 @@ export class CookieConsentCore { /** * Opens banner when not on cookie settings page. + * * @param {Array} highlightedGroups - Groups to highlight when opened + * * @param {string} focusTargetSelector - Selector for the element that will receive focus once the banner is closed. Overrides the options.focusTargetSelector */ - openBanner(highlightedGroups = []) { + openBanner(highlightedGroups = [], focusTargetSelector = '') { if (this.#settingsPageSelector && document.querySelector(this.#settingsPageSelector)) { // eslint-disable-next-line no-console console.error(`Cookie consent: The user is already on settings page`); return; } + + if (focusTargetSelector) { + this.#focusTargetSelector = focusTargetSelector; + } this.removeBanner(); this.#render(this.#language, this.#siteSettings, true, null, highlightedGroups); } @@ -342,7 +353,7 @@ export class CookieConsentCore { * Removes the banner and related elements. * @returns {void} */ - removeBanner() { + removeBanner(setFocus = false) { this.killTimeout(); // Remove banner size observer if (this.#resizeReference.resizeObserver && this.#resizeReference.bannerHeightElement) { @@ -360,6 +371,13 @@ export class CookieConsentCore { // Remove scroll-margin-bottom variable from all elements inside the contentSelector document.documentElement.style.removeProperty('--hds-cookie-consent-height'); + + if (setFocus && this.#focusTargetSelector) { + const element = document.querySelector(this.#focusTargetSelector); + if (element) { + element.focus(); + } + } } // MARK: Private methods @@ -417,7 +435,7 @@ export class CookieConsentCore { } else { window.dispatchEvent(new CustomEvent(cookieEventType.CHANGE, { detail: { acceptedGroups } })); if (!this.#settingsPageElement) { - this.removeBanner(); + this.removeBanner(true); // removeBanner() removes the setTimeout that shows notification // announceSettingsSaved() must be called after the removeBanner() this.#announceSettingsSaved(); diff --git a/packages/react/src/components/cookieConsentCore/types.ts b/packages/react/src/components/cookieConsentCore/types.ts index 94d3ef1816..6eaacba93b 100644 --- a/packages/react/src/components/cookieConsentCore/types.ts +++ b/packages/react/src/components/cookieConsentCore/types.ts @@ -15,6 +15,7 @@ export type Options = { pageContentSelector?: string | undefined; submitEvent?: boolean | undefined; settingsPageSelector?: string | undefined; + focusTargetSelector?: string | undefined; disableAutoRender?: boolean | undefined; };