Skip to content

Commit

Permalink
Merge pull request #314 from mparvazi/quantity-keyboard
Browse files Browse the repository at this point in the history
Add keyboard events for quantity input
  • Loading branch information
Hlavtox authored Jun 6, 2022
2 parents 45431f0 + b22e178 commit 3c4b32e
Show file tree
Hide file tree
Showing 8 changed files with 92 additions and 46 deletions.
6 changes: 3 additions & 3 deletions src/js/components/useQuantityInput.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,8 +83,8 @@ describe('useQuantityInput', () => {

it('should display confirmation buttons on keyup', () => {
const qtyInput = getHTMLElement<HTMLInputElement>('input');
qtyInput.dispatchEvent(new Event('keyup'));
qtyInput.value = '1';
qtyInput.dispatchEvent(new Event('keyup'));

const decrementButton = getHTMLElement<HTMLButtonElement>(quantityInputMap.decrement);
const cancelIcon = getHTMLElement<HTMLElement>(quantityInputMap.confirm, decrementButton);
Expand All @@ -98,7 +98,7 @@ describe('useQuantityInput', () => {

it('should hide confirmation buttons on decrement', async () => {
const qtyInput = getHTMLElement<HTMLInputElement>('input');
qtyInput.dispatchEvent(new Event('keyup'));
qtyInput.dispatchEvent(new Event('keydown'));

const decrementButton = getHTMLElement<HTMLButtonElement>(quantityInputMap.decrement);
decrementButton.click();
Expand All @@ -114,7 +114,7 @@ describe('useQuantityInput', () => {
const qtyValue = '1';
const confirmedExpectedValue = '5';
resetQtyInputValueInDOM(qtyInput, qtyMin, qtyValue);
qtyInput.dispatchEvent(new Event('keyup'));
qtyInput.dispatchEvent(new Event('keydown'));

const mockedIncrementFetch = mockedResponse(true, false, confirmedExpectedValue);
const incrementButton = getHTMLElement<HTMLButtonElement>(quantityInputMap.increment);
Expand Down
112 changes: 74 additions & 38 deletions src/js/components/useQuantityInput.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,49 +9,78 @@ import debounce from '@helpers/debounce';
import useAlert from './useAlert';
import useToast from './useToast';

const useQuantityInput = (selector = quantityInputMap.default, delay = Quantity.delay) => {
const qtyInputNodeList = document.querySelectorAll(selector) as NodeListOf<HTMLElement>;

if (qtyInputNodeList.length > 0) {
qtyInputNodeList.forEach((qtyInputWrapper: HTMLDivElement) => {
const qtyInput = qtyInputWrapper.querySelector<HTMLInputElement>('input');

if (qtyInput) {
const incrementButton = qtyInputWrapper.querySelector<HTMLButtonElement>(quantityInputMap.increment);
const decrementButton = qtyInputWrapper.querySelector<HTMLButtonElement>(quantityInputMap.decrement);

if (incrementButton && decrementButton) {
const qtyInputGroup: Theme.QuantityInput.InputGroup = {qtyInput, incrementButton, decrementButton};
// The changeQuantity() will be called immediatly and change the input value
incrementButton.addEventListener('click', () => changeQuantity(qtyInput, 1));
decrementButton.addEventListener('click', () => changeQuantity(qtyInput, -1));

// The updateQuantity() will be called after timeout and send the update request with current input value
if (qtyInput.hasAttribute('data-update-url')) {
incrementButton.addEventListener('click', debounce(async () => {
updateQuantity(qtyInputGroup, 1);
}, delay));
decrementButton.addEventListener('click', debounce(async () => {
updateQuantity(qtyInputGroup, -1);
}, delay));

// If the input element has update URL (e.g. Cart)
// then convert the buttons when user changed the value manually
qtyInput.addEventListener('keyup', () => {
showConfirmationButtons(qtyInputGroup);
});
const ENTER_KEY = 'Enter';
const ESCAPE_KEY = 'Escape';
const ARROW_UP_KEY = 'ArrowUp';
const ARROW_DOWN_KEY = 'ArrowDown';

const useQuantityInput: Theme.QuantityInput.Function = (
selector = quantityInputMap.default, delay = Quantity.delay) => {
const qtyInputNodeList = document.querySelectorAll<HTMLElement>(selector);

qtyInputNodeList.forEach((qtyInputWrapper: HTMLElement) => {
const qtyInput = qtyInputWrapper.querySelector<HTMLInputElement>('input');

if (qtyInput) {
const incrementButton = qtyInputWrapper.querySelector<HTMLButtonElement>(quantityInputMap.increment);
const decrementButton = qtyInputWrapper.querySelector<HTMLButtonElement>(quantityInputMap.decrement);

if (incrementButton && decrementButton) {
const qtyInputGroup: Theme.QuantityInput.InputGroup = {qtyInput, incrementButton, decrementButton};

// The changeQuantity() will be called immediatly and change the input value for Mouse actions
incrementButton.addEventListener('click', () => changeQuantity(qtyInput, 1));
decrementButton.addEventListener('click', () => changeQuantity(qtyInput, -1));

// The changeQuantity() will be called immediatly and change the input value for Keyboard actions
qtyInput.addEventListener('keydown', (event: KeyboardEvent) => {
if (event.key === ARROW_UP_KEY) {
changeQuantity(qtyInput, 1, true);
}

if (event.key === ARROW_DOWN_KEY) {
changeQuantity(qtyInput, -1, true);
}
});

// The updateQuantity() will be called after timeout and send the update request with current input value
if (qtyInput.hasAttribute('data-update-url')) {
incrementButton.addEventListener('click', debounce(async () => {
updateQuantity(qtyInputGroup, 1);
}, delay));
decrementButton.addEventListener('click', debounce(async () => {
updateQuantity(qtyInputGroup, -1);
}, delay));

// If the input element has update URL (e.g. Cart)
// then convert the buttons when user changed the value manually
qtyInput.addEventListener('keyup', (event: KeyboardEvent) => {
const baseValue = qtyInput.getAttribute('value');

if (qtyInput.value !== baseValue) {
showConfirmationButtons(qtyInputGroup);
} else {
showSpinButtons(qtyInputGroup);
}

if (event.key === ENTER_KEY) {
updateQuantity(qtyInputGroup, 1);
}

if (event.key === ESCAPE_KEY) {
showSpinButtons(qtyInputGroup);
}
});
}
}
});
}
}
});
};

const changeQuantity = (qtyInput: HTMLInputElement, change: number) => {
const changeQuantity = (qtyInput: HTMLInputElement, change: number, keyboard = false) => {
const {mode} = qtyInput.dataset;

// If the confirmation buttons displayed then skip changing the input value
if (mode !== 'confirmation') {
if (mode !== 'confirmation' || keyboard) {
const currentValue = Number(qtyInput.value);
const baseValue = qtyInput.getAttribute('value');
const min = (qtyInput.dataset.updateUrl === undefined) ? Number(qtyInput.getAttribute('min')) : 0;
Expand Down Expand Up @@ -234,9 +263,16 @@ const sendUpdateCartRequest = async (updateUrl: string, quantity: number) => {
};

document.addEventListener('DOMContentLoaded', () => {
const {prestashop, Theme: {events}} = window;
const {prestashop, Theme: {events, selectors}} = window;

prestashop.on(events.updatedCart, () => {
useQuantityInput();

const {cart: cartMap} = selectors;
const cartOverview = document.querySelector<HTMLElement>(cartMap.overview);
cartOverview?.focus();
});

prestashop.on(events.updatedCart, () => useQuantityInput());
prestashop.on(events.quickviewOpened, () => useQuantityInput(quantityInputMap.modal));
});

Expand Down
1 change: 1 addition & 0 deletions src/js/constants/selectors-map.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ export const listing = {
};

export const cart = {
overview: '.cart-overview',
discountCode: '.js-discount .js-code',
discountName: '[name=discount_name]',
displayPromo: '.display-promo',
Expand Down
6 changes: 3 additions & 3 deletions src/scss/core/components/_qty-input.scss
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
.quantity-button {
input {
max-width: 4.5rem;
height: 2.5rem;
height: 2.375rem;
padding: 0.375rem 0.5rem;
text-align: center;
}

button {
width: 2.5rem;
width: 2.375rem;
padding: 0;
border: 1px solid $input-border-color;

Expand Down Expand Up @@ -36,7 +36,7 @@
}

.material-icons {
font-size: 1.125rem;
font-size: 1.25rem;
pointer-events: none;
}

Expand Down
6 changes: 6 additions & 0 deletions src/scss/custom/components/_cart.scss
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
$component-name: cart;

.#{$component-name} {
&-overview {
&:focus-visible {
outline: 0;
}
}

&-summary {
padding: 1rem;

Expand Down
4 changes: 3 additions & 1 deletion templates/checkout/_partials/cart-detailed.tpl
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@
*}
{block name='cart_detailed_product'}
<div class="cart-overview js-cart"
data-refresh-url="{url entity='cart' params=['ajax' => true, 'action' => 'refresh']}">
data-refresh-url="{url entity='cart' params=['ajax' => true, 'action' => 'refresh']}"
tabindex="-1"
>
<hr />
{if $cart.products}
<ul class="cart__items">
Expand Down
1 change: 1 addition & 0 deletions types/selectors.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ declare type listing = {
};

declare type cart = {
overview: string,
discountCode: string,
discountName: string,
displayPromo: string,
Expand Down
2 changes: 1 addition & 1 deletion types/useQuantityInput.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,6 @@
decrementButton: HTMLButtonElement;
}

type Function = (selector: string, delay?: number) => void
type Function = (selector?: string, delay?: number) => void
}
}

0 comments on commit 3c4b32e

Please sign in to comment.