From ee456f632edc1ba88a05b09c15a07a3fa6bd13e7 Mon Sep 17 00:00:00 2001 From: Eugene Kasimov <105315663+eugenekasimov@users.noreply.github.com> Date: Thu, 29 Feb 2024 09:55:42 -0800 Subject: [PATCH] Quick order keyboard interactions (#2962) * Add basic functionality. Add placeholder. Make a row rerender instead of the whole section. * Style placeholder * Fix removeAll button * Remove comments and unnecessary code. * Remove debouncing. * Remove placeholder for input * Add selection on focus for inputs * Move eventListener to a parent element * Put debouncing back * Refactor switchVariants method * Refactor allInputs array * Remove unnecessary input placeholder * Fix typo * Refactor variants names * Remove unused prop * Put back prop 'name' for updateQuantity * Refactoring * [Quick Order List] Prevent selected inputs being covered by total bar or header (#3278) * Prevent input elements going under total bar or sticky header during keyboard navigation. * Fix a typo * Refactoring * Increase z-index for volume pricing popup * Fix conflicts after merge --- assets/quantity-popover.css | 4 +- assets/quick-order-list.css | 6 ++- assets/quick-order-list.js | 86 ++++++++++++++++++++++++++++++------- 3 files changed, 78 insertions(+), 18 deletions(-) diff --git a/assets/quantity-popover.css b/assets/quantity-popover.css index f7b0c87c987..ea1978490d9 100644 --- a/assets/quantity-popover.css +++ b/assets/quantity-popover.css @@ -17,7 +17,7 @@ quantity-popover volume-pricing li { .quantity-popover__info.global-settings-popup { width: 100%; - z-index: 2; + z-index: 3; position: absolute; background-color: rgb(var(--color-background)); max-width: 36rem; @@ -161,4 +161,4 @@ quantity-popover .quantity__rules .divider:nth-child(2)::before { quantity-popover .quantity__button:not(:focus-visible):not(.focused), quantity-popover .quantity__input:not(:focus-visible):not(.focused) { background-color: initial; -} \ No newline at end of file +} diff --git a/assets/quick-order-list.css b/assets/quick-order-list.css index a733c922ac4..be0ba2c33f0 100644 --- a/assets/quick-order-list.css +++ b/assets/quick-order-list.css @@ -11,6 +11,10 @@ quick-order-list .quantity__button { width: calc(3.5rem / var(--font-body-scale)); } +.quick-order-list__contents { + position: relative; +} + .quick-order-list__container { padding-bottom: 4rem; } @@ -36,7 +40,7 @@ quick-order-list .quantity__button { .quick-order-list__total { position: sticky; bottom: 0; - z-index: 1; + z-index: 2; background-color: rgb(var(--color-background)); } diff --git a/assets/quick-order-list.js b/assets/quick-order-list.js index 68171d17df8..deab5470af2 100644 --- a/assets/quick-order-list.js +++ b/assets/quick-order-list.js @@ -14,16 +14,15 @@ customElements.define('quick-order-list-remove-button', QuickOrderListRemoveButt class QuickOrderListRemoveAllButton extends HTMLElement { constructor() { super(); - const allVariants = Array.from(document.querySelectorAll('[data-variant-id]')); + const allVariants = Array.from(document.querySelectorAll('[data-quantity-variant-id]')); const items = {} let hasVariantsInCart = false; this.quickOrderList = this.closest('quick-order-list'); - allVariants.forEach((variant) => { - const cartQty = parseInt(variant.dataset.cartQty); + const cartQty = parseInt(variant.dataset.cartQuantity); if (cartQty > 0) { hasVariantsInCart = true; - items[parseInt(variant.dataset.variantId)] = 0; + items[parseInt(variant.dataset.quantityVariantId)] = 0; } }); @@ -67,12 +66,28 @@ class QuickOrderList extends HTMLElement { add: 'ADD', update: 'UPDATE' } + this.defineInputsAndQuickOrderTable(); this.quickOrderListId = 'quick-order-list' this.variantItemStatusElement = document.getElementById('shopping-cart-variant-item-status'); const form = this.querySelector('form'); + this.inputFieldHeight = this.querySelector('.variant-item__quantity-wrapper').offsetHeight; + this.stickyHeaderElement = document.querySelector('sticky-header'); + + if (this.stickyHeaderElement) { + this.stickyHeader = { + height: this.stickyHeaderElement.offsetHeight, + type: `${this.stickyHeaderElement.getAttribute('data-sticky-type')}` + }; + } - form.addEventListener('submit', this.onSubmit.bind(this)); + this.totalBarPosition = window.innerHeight - this.querySelector('.quick-order-list__total').offsetHeight; + window.addEventListener('resize', () => { + this.totalBarPosition = window.innerHeight - this.querySelector('.quick-order-list__total').offsetHeight; + this.stickyHeader.height = this.stickyHeaderElement ? this.stickyHeaderElement.offsetHeight: null; + }); + + form.addEventListener('submit', this.onSubmit.bind(this)); const debouncedOnChange = debounce((event) => { this.onChange(event); }, ON_CHANGE_DEBOUNCE_TIMER); @@ -102,6 +117,12 @@ class QuickOrderList extends HTMLElement { } } + defineInputsAndQuickOrderTable() { + this.allInputsArray = Array.from(this.querySelectorAll('input[type="number"]')); + this.quickOrderListTable = this.querySelector('.quick-order-list__table'); + this.quickOrderListTable.addEventListener('focusin', this.switchVariants.bind(this)); + } + onChange(event) { const inputValue = parseInt(event.target.value); const cartQuantity = parseInt(event.target.dataset.cartQuantity); @@ -160,23 +181,63 @@ class QuickOrderList extends HTMLElement { ]; } - renderSections(parsedState) { + renderSections(parsedState, id) { this.getSectionsToRender().forEach((section => { const sectionElement = document.getElementById(section.id); if (sectionElement && sectionElement.parentElement && sectionElement.parentElement.classList.contains('drawer')) { parsedState.items.length > 0 ? sectionElement.parentElement.classList.remove('is-empty') : sectionElement.parentElement.classList.add('is-empty'); - setTimeout(() => { document.querySelector('#CartDrawer-Overlay').addEventListener('click', this.cart.close.bind(this.cart)); }); } const elementToReplace = sectionElement && sectionElement.querySelector(section.selector) ? sectionElement.querySelector(section.selector) : sectionElement; if (elementToReplace) { - elementToReplace.innerHTML = - this.getSectionInnerHTML(parsedState.sections[section.section], section.selector); + if (section.selector === '.js-contents' && id !== undefined) { + elementToReplace.querySelector(`#Variant-${id}`).innerHTML = + this.getSectionInnerHTML(parsedState.sections[section.section], `#Variant-${id}`); + } else { + elementToReplace.innerHTML = this.getSectionInnerHTML(parsedState.sections[section.section], section.selector); + } } })); + this.defineInputsAndQuickOrderTable(); + } + switchVariants(event) { + if (event.target.tagName !== 'INPUT') { + return; + } + this.variantListInput = event.target; + this.variantListInput.select() + this.variantListInput.addEventListener('keydown', (e) => { + if (e.key === 'Enter') { + e.preventDefault(); + e.target.blur(); + const currentIndex = this.allInputsArray.indexOf(e.target); + + if (!e.shiftKey) { + const nextIndex = currentIndex + 1; + const nextVariant = this.allInputsArray[nextIndex] || this.allInputsArray[0]; + nextVariant.select(); + } else { + const previousIndex = currentIndex - 1; + const previousVariant = this.allInputsArray[previousIndex] || this.allInputsArray[this.allInputsArray.length - 1]; + previousVariant.select(); + } + } + }); + + const inputTopBorder = this.variantListInput.getBoundingClientRect().top; + const inputBottomBorder = this.variantListInput.getBoundingClientRect().bottom; + const stickyHeaderBottomBorder = this.stickyHeaderElement && this.stickyHeaderElement.getBoundingClientRect().bottom; + const totalBarCrossesInput = inputBottomBorder > this.totalBarPosition; + const inputOutsideOfViewPort = inputBottomBorder < this.inputFieldHeight; + const stickyHeaderCrossesInput = this.stickyHeaderElement && this.stickyHeader.type !== 'on-scroll-up' && this.stickyHeader.height > inputTopBorder; + const stickyHeaderScrollupCrossesInput = this.stickyHeaderElement && this.stickyHeader.type === 'on-scroll-up' && this.stickyHeader.height > inputTopBorder && stickyHeaderBottomBorder > 0; + + if (totalBarCrossesInput || inputOutsideOfViewPort || stickyHeaderCrossesInput || stickyHeaderScrollupCrossesInput) { + this.variantListInput.scrollIntoView({ block: 'center', behavior: 'smooth' }); + } } updateMultipleQty(items) { @@ -258,7 +319,7 @@ class QuickOrderList extends HTMLElement { this.classList.toggle('is-empty', parsedState.item_count === 0); - this.renderSections(parsedState); + this.renderSections(parsedState, id); let hasError = false; @@ -269,10 +330,6 @@ class QuickOrderList extends HTMLElement { hasError = true; } - const variantItem = document.getElementById(`Variant-${id}`); - if (variantItem && variantItem.querySelector(`[name="${name}"]`)) { - variantItem.querySelector(`[name="${name}"]`).focus(); - } publish(PUB_SUB_EVENTS.cartUpdate, { source: this.quickOrderListId, cartData: parsedState }); if (hasError) { @@ -380,7 +437,6 @@ class QuickOrderList extends HTMLElement { if (enable) { quickOrderList.classList.add('quick-order-list__container--disabled'); [...quickOrderListItems].forEach((overlay) => overlay.classList.remove('hidden')); - document.activeElement.blur(); this.variantItemStatusElement.setAttribute('aria-hidden', false); } else { quickOrderList.classList.remove('quick-order-list__container--disabled');