diff --git a/blocks/card-carousel/card-carousel.css b/blocks/card-carousel/card-carousel.css index 27dbc18..14870de 100644 --- a/blocks/card-carousel/card-carousel.css +++ b/blocks/card-carousel/card-carousel.css @@ -2,6 +2,7 @@ margin: 0 auto; max-width: calc(1032px + var(--padding--mobile) + var(--padding--mobile)); padding: var(--padding--mobile); + overflow: hidden; } .card-carousel .splide__track { diff --git a/blocks/header/header.css b/blocks/header/header.css index 71b8ec5..8628398 100644 --- a/blocks/header/header.css +++ b/blocks/header/header.css @@ -404,7 +404,7 @@ z-index: 100; top: 0; height: 0; - width: 100%; + width: 100vw; transition: opacity var(--action-ease-duration) var(--action-ease-function); visibility: hidden; display: flex; diff --git a/blocks/header/header.js b/blocks/header/header.js index c6e4057..17ba14a 100644 --- a/blocks/header/header.js +++ b/blocks/header/header.js @@ -8,11 +8,7 @@ import { isMobile, parseFragment, render, - renderBreadCrumbs, } from '../../scripts/scripts.js'; -import '../language-selector/language-selector.js'; -import '../search-bar/search-bar.js'; -import '../theme-toggle/theme-toggle.js'; // const TEMPLATE = /* html */ ` @@ -372,16 +368,19 @@ function addEventListeners(block) { ); /* Search */ + const emitLoadSearch = (() => { + let sent = false; + return () => { + if (sent) return false; + sent = true; + store.emit('load:search'); + return true; + }; + })(); const searchButtonOpen = desktopNav.querySelector('.nav-search-button'); const searchButtonClose = searchPanel.querySelector('.search-panel-close'); - // searchButtonOpen.addEventListener( - // "mouseenter", - // () => { - // import("../search-bar/search-bar.js"); - // }, - // { once: true } - // ); + searchButtonOpen.addEventListener('mouseenter', emitLoadSearch, { once: true }); const focusSearchInput = () => { // Check for searchbar @@ -395,15 +394,23 @@ function addEventListeners(block) { } }; + const openSearch = () => { + // delay to load search before opening first time + const delay = emitLoadSearch('load:search') ? 150 : 1; + setTimeout(() => { + searchPanel.classList.add('active'); + + // Focus on search input + focusSearchInput(); + }, delay); + }; + window.addEventListener('keydown', (e) => { const { key } = e; const searchIsActive = searchPanel.classList.contains('active'); if (key === '/' && !searchIsActive) { - searchPanel.classList.add('active'); - - // Focus on search input - focusSearchInput(); + openSearch(); } if (key === 'Escape' && searchIsActive) { @@ -412,10 +419,7 @@ function addEventListeners(block) { }); searchButtonOpen.addEventListener('click', () => { - searchPanel.classList.add('active'); - - // Focus on search input - focusSearchInput(); + openSearch(); }); searchButtonClose.addEventListener('click', () => { @@ -626,6 +630,11 @@ export default async function decorate(block) { if (!isMobile()) { // renderBreadCrumbs(); } else { - store.once('delayed:loaded', renderBreadCrumbs); + // store.once('delayed:loaded', renderBreadCrumbs); } + + // load custom elements + import('../language-selector/language-selector.js'); + import('../search-bar/search-bar.js'); + import('../theme-toggle/theme-toggle.js'); } diff --git a/blocks/hero-search/hero-search.js b/blocks/hero-search/hero-search.js index b5a2b98..d7563ad 100644 --- a/blocks/hero-search/hero-search.js +++ b/blocks/hero-search/hero-search.js @@ -1,3 +1,4 @@ +import { loadCSS } from '../../scripts/lib-franklin.js'; import { html, renderParallax } from '../../scripts/scripts.js'; import '../search-bar/search-bar.js'; @@ -5,6 +6,7 @@ import '../search-bar/search-bar.js'; * @param {HTMLDivElement} block */ export default function decorate(block) { + loadCSS(`${window.hlx.codeBasePath}/blocks/search-bar/search-bar.css`); block.append(html`
@@ -51,9 +53,7 @@ export default function decorate(block) { - - - `; +`; const section = block.closest('div.section'); const wrapper = section.querySelector('.hero-search-wrapper'); diff --git a/blocks/search-bar/search-bar.css b/blocks/search-bar/search-bar.css index 227319d..7421dbd 100644 --- a/blocks/search-bar/search-bar.css +++ b/blocks/search-bar/search-bar.css @@ -38,6 +38,56 @@ search-bar { display: flex; } +.search-bar #coveo-searchbox #placeholder-searchbox { + flex-grow: 1; + display: flex; +} + +.search-bar #coveo-searchbox #placeholder-searchbox div { + border-top: 1px solid var(--search-border-color); + border-bottom: 1px solid var(--search-border-color); + border-left: 1px solid var(--search-border-color); + border-radius: 0; + border-top-left-radius: var(--search-border-radius); + border-bottom-left-radius: var(--search-border-radius); + height: var(--search-height); + width: 43px; + min-width: 43px; + background: var(--search-background-color); + display: flex; + justify-content: space-around; + align-items: center; +} + +.search-bar #coveo-searchbox #placeholder-searchbox input { + background: var(--search-background-color); + color: var(--search-text-color); + font-family: var(--body-font-family); + font-size: var(--control-font-size--sm); + line-height: var(--control-line-height--sm); + padding: 13px 16px 11px 1px; + padding-right: var(--spacing--4); + border: none; + resize: none; + outline: 0; + margin: 0; + border-top: 1px solid var(--search-border-color); + border-bottom: 1px solid var(--search-border-color); + border-radius: 0; + height: var(--search-height); + margin-left: -1px; + flex-grow: 1; +} + +.search-bar #coveo-searchbox #placeholder-searchbox input::placeholder { + color: var(--search-placeholder); + padding-top: 10px; +} + +.search-bar.coveo-ready #coveo-searchbox #placeholder-searchbox { + display: none; +} + .search-bar #coveo-searchbox .magic-box { border: none; border-radius: 0; @@ -247,22 +297,22 @@ search-bar { } .search-bar - .magic-box.magic-box-hasFocus - .magic-box-suggestions.magic-box-hasSuggestion - .coveo-magicbox-suggestions, +.magic-box.magic-box-hasFocus +.magic-box-suggestions.magic-box-hasSuggestion +.coveo-magicbox-suggestions, .search-bar - .magic-box.magic-box-hasFocus - .magic-box-suggestions.magic-box-hasSuggestion - .coveo-suggestion-container, +.magic-box.magic-box-hasFocus +.magic-box-suggestions.magic-box-hasSuggestion +.coveo-suggestion-container, .search-bar .magic-box .magic-box-suggestions .coveo-magicbox-suggestions { border: none; } .search-bar .dropdown-content, .search-bar - .magic-box - .magic-box-suggestions.magic-box-hasSuggestion - .coveo-magicbox-suggestions { +.magic-box +.magic-box-suggestions.magic-box-hasSuggestion +.coveo-magicbox-suggestions { background: var(--search-background-color); border-radius: var(--search-autosuggest-border-radius); box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.08); @@ -283,9 +333,9 @@ search-bar { .search-bar .dropdown-content a:not(:last-child), .search-bar - .magic-box - .magic-box-suggestions - .magic-box-suggestion:not(:last-child) { +.magic-box +.magic-box-suggestions +.magic-box-suggestion:not(:last-child) { margin-bottom: var(--spacing--1); } diff --git a/blocks/search-bar/search-bar.js b/blocks/search-bar/search-bar.js index 1a9a316..e6f6b32 100644 --- a/blocks/search-bar/search-bar.js +++ b/blocks/search-bar/search-bar.js @@ -22,15 +22,27 @@ const TEMPLATE = /* html */ `
`; -const TEMPLATE_CLOSE_ICON = ` +const TEMPLATE_CLOSE_ICON = /* html */` -Clear - - + Clear + + `; @@ -58,6 +70,8 @@ export class SearchBar extends HTMLElement { this.innerHTML = TEMPLATE; this.root = this.firstElementChild; this.coveoConfig = getCoveoConfig(); + /** @type {Promise} */ + this.inputWhenReady = null; // Allow for mutiple search bars to be initiated on one page this.flags = { @@ -65,7 +79,7 @@ export class SearchBar extends HTMLElement { }; this.init(); - this.loadCoveo(); + this.swapPlaceholderInput(); } async loadCoveo() { @@ -73,6 +87,29 @@ export class SearchBar extends HTMLElement { this._initCoveo(); } + swapPlaceholderInput() { + let resolve; + this.inputWhenReady = new Promise((res) => { + resolve = res; + }); + + const searchbar = this.querySelector('.search-bar'); + const searchbox = searchbar.querySelector('#coveo-searchbox'); + + const observer = new MutationObserver((records, self) => { + const ready = !!records.find( + (record) => !![...record.addedNodes] + .find((node) => node.classList.contains('CoveoOmnibox')), + ); + if (ready) { + self.disconnect(); + searchbar.classList.add('coveo-ready'); + resolve(searchbox.querySelector('input:not(.placeholder)')); + } + }); + observer.observe(searchbox, { childList: true }); + } + init() { const booknameMeta = getMetadata('book-name'); const productMeta = getMetadata('docset-title'); @@ -132,6 +169,29 @@ export class SearchBar extends HTMLElement { this.querySelector('.dropbtn').textContent = targetOption.textContent; } } + + // load coveo on-demand to avoid the bundle blocking initial page load. + // this happens either when the store gets a `load:search` event, eg. from header block + // or when the search bar is entered/touched, eg. on the homepage + store.once('load:search', () => { + this.loadCoveo(); + }); + + this.addEventListener('mouseenter', () => { + this.loadCoveo(); + }, { once: true }); + + this.addEventListener('touchstart', () => { + if (this.flags.hasInit) return; + this.loadCoveo(); + + // if the tap completes + this.addEventListener('touchend', async () => { + // resubmit the event after placeholder is swapped + const input = await this.inputWhenReady; + input.click(); + }, { once: true, passive: true }); + }, { once: true, passive: true }); } static async LoadCoveo() { @@ -152,84 +212,86 @@ export class SearchBar extends HTMLElement { } _initCoveo() { + if (Coveo.SearchEndpoint.defaultEndpoint !== undefined && this.flags.hasInit) { + return; + } + const { orgID, apiKey, searchPageURL } = this.coveoConfig; - if (Coveo.SearchEndpoint.defaultEndpoint === undefined || !this.flags.hasInit) { - const searchBoxRoot = this.querySelector('.searchbox'); - Coveo.SearchEndpoint.configureCloudV2Endpoint(orgID, apiKey); - Coveo.$$(searchBoxRoot).on('newQuery', () => { - const dropdownSelectedValue = this.querySelector( - '.coveo-dropdown-item.selected', - ).getAttribute('data-value'); - try { - if (dropdownSelectedValue !== 'all') { - Coveo.state(searchBoxRoot, 'hq', dropdownSelectedValue); - Coveo.state( - searchBoxRoot, - 'hd', - this.querySelector('.coveo-dropdown-item.selected').getAttribute('data-label').trim(), - ); - } else { - // eslint-disable-next-line no-undef - Coveo.state(searchBoxRoot, 'hq', ''); - // eslint-disable-next-line no-undef - Coveo.state(searchBoxRoot, 'hd', ''); - } - } catch (error) { - console.log(error); - } - }); - Coveo.initSearchbox(searchBoxRoot, searchPageURL); - const dropDown = this.querySelector('.dropdown'); - const dropDownOpen = dropDown.querySelector('.dropbtn'); - const dropDownLoad = dropDown.querySelector('.dropdown-content'); - - dropDownOpen.addEventListener('click', () => { - dropDown.classList.toggle('is-active'); - if (dropDownLoad.style.display === 'none') { - dropDownLoad.style.display = 'block'; + const searchBoxRoot = this.querySelector('.searchbox'); + Coveo.SearchEndpoint.configureCloudV2Endpoint(orgID, apiKey); + Coveo.$$(searchBoxRoot).on('newQuery', () => { + const dropdownSelectedValue = this.querySelector( + '.coveo-dropdown-item.selected', + ).getAttribute('data-value'); + try { + if (dropdownSelectedValue !== 'all') { + Coveo.state(searchBoxRoot, 'hq', dropdownSelectedValue); + Coveo.state( + searchBoxRoot, + 'hd', + this.querySelector('.coveo-dropdown-item.selected').getAttribute('data-label').trim(), + ); } else { - dropDownLoad.style.display = 'none'; + // eslint-disable-next-line no-undef + Coveo.state(searchBoxRoot, 'hq', ''); + // eslint-disable-next-line no-undef + Coveo.state(searchBoxRoot, 'hd', ''); } - }); + } catch (error) { + console.log(error); + } + }); + Coveo.initSearchbox(searchBoxRoot, searchPageURL); + const dropDown = this.querySelector('.dropdown'); + const dropDownOpen = dropDown.querySelector('.dropbtn'); + const dropDownLoad = dropDown.querySelector('.dropdown-content'); + + dropDownOpen.addEventListener('click', () => { + dropDown.classList.toggle('is-active'); + if (dropDownLoad.style.display === 'none') { + dropDownLoad.style.display = 'block'; + } else { + dropDownLoad.style.display = 'none'; + } + }); - // Set default input placeholder for selected item - const defaultLabel = this.querySelector('.coveo-dropdown-item.selected').getAttribute( - 'data-label', - ); + // Set default input placeholder for selected item + const defaultLabel = this.querySelector('.coveo-dropdown-item.selected').getAttribute( + 'data-label', + ); - const searchInput = this.querySelector('.magic-box-input input'); - searchInput.setAttribute('placeholder', `Search ${defaultLabel}`); + const searchInput = this.querySelector('.magic-box-input input'); + searchInput.setAttribute('placeholder', `Search ${defaultLabel}`); - for (const dropoption of this.querySelectorAll('.coveo-dropdown-item')) { - dropoption.addEventListener('click', (event) => { - const label = event.target.getAttribute('data-label'); - this.querySelector('.coveo-dropdown-item.selected').classList.remove('selected'); - event.target.classList.add('selected'); - this.querySelector('.dropbtn').textContent = label; - dropDownLoad.setAttribute('style', 'display : none'); - dropDown.classList.remove('is-active'); + for (const dropoption of this.querySelectorAll('.coveo-dropdown-item')) { + dropoption.addEventListener('click', (event) => { + const label = event.target.getAttribute('data-label'); + this.querySelector('.coveo-dropdown-item.selected').classList.remove('selected'); + event.target.classList.add('selected'); + this.querySelector('.dropbtn').textContent = label; + dropDownLoad.setAttribute('style', 'display : none'); + dropDown.classList.remove('is-active'); - searchInput.setAttribute('placeholder', `Search ${label}`); - }); - } + searchInput.setAttribute('placeholder', `Search ${label}`); + }); + } - const updateClearButton = this.querySelector('.magic-box-clear .magic-box-icon'); + const updateClearButton = this.querySelector('.magic-box-clear .magic-box-icon'); - if (updateClearButton) { - updateClearButton.innerHTML = TEMPLATE_CLOSE_ICON; - } + if (updateClearButton) { + updateClearButton.innerHTML = TEMPLATE_CLOSE_ICON; + } - const searchBoxInput = this.querySelector('.magic-box-input input'); - searchBoxInput.addEventListener('focus', () => { - searchBoxRoot.classList.add('is-focused'); - }); - searchBoxInput.addEventListener('blur', () => { - searchBoxRoot.classList.remove('is-focused'); - }); + const searchBoxInput = this.querySelector('.magic-box-input input'); + searchBoxInput.addEventListener('focus', () => { + searchBoxRoot.classList.add('is-focused'); + }); + searchBoxInput.addEventListener('blur', () => { + searchBoxRoot.classList.remove('is-focused'); + }); - this.flags.hasInit = true; - } + this.flags.hasInit = true; } } diff --git a/styles/styles.css b/styles/styles.css index f9df784..757b853 100644 --- a/styles/styles.css +++ b/styles/styles.css @@ -387,6 +387,8 @@ body.landing-division { ), #fff; background-size: auto; + min-height: 100vh; + max-width: 100vw; } body.landing-division .section:not(.parallax-container) { diff --git a/types/Events.d.ts b/types/Events.d.ts index 86193ba..bb49955 100644 --- a/types/Events.d.ts +++ b/types/Events.d.ts @@ -14,6 +14,7 @@ import { ArticleInfo, ArticleResponse } from "./Article"; import { BookDefinition } from "./Book"; export interface EventMap { + "load:search": void; "delayed:loaded": void; "book:loaded": BookDefinition; "article:fetched": ArticleResponse;